Compare commits

..

1 Commits

Author SHA1 Message Date
wildcard bd23a55ad8 [vulkan] Fix vuid 04553 2026-02-20 23:37:49 +01:00
108 changed files with 1333 additions and 1255 deletions
+35
View File
@@ -0,0 +1,35 @@
diff --git a/library/aesni.h b/library/aesni.h
index 754c984c79..59e27afd3e 100644
--- a/library/aesni.h
+++ b/library/aesni.h
@@ -35,7 +35,7 @@
/* GCC-like compilers: currently, we only support intrinsics if the requisite
* target flag is enabled when building the library (e.g. `gcc -mpclmul -msse2`
* or `clang -maes -mpclmul`). */
-#if (defined(__GNUC__) || defined(__clang__)) && defined(__AES__) && defined(__PCLMUL__)
+#if defined(__GNUC__) || defined(__clang__)
#define MBEDTLS_AESNI_HAVE_INTRINSICS
#endif
/* For 32-bit, we only support intrinsics */
diff --git a/library/aesni.c b/library/aesni.c
index 2857068..3e104ab 100644
--- a/library/aesni.c
+++ b/library/aesni.c
@@ -31,16 +31,14 @@
#include <immintrin.h>
#endif
-#if defined(MBEDTLS_ARCH_IS_X86)
#if defined(MBEDTLS_COMPILER_IS_GCC)
#pragma GCC push_options
#pragma GCC target ("pclmul,sse2,aes")
#define MBEDTLS_POP_TARGET_PRAGMA
-#elif defined(__clang__) && (__clang_major__ >= 5)
+#elif defined(__clang__)
#pragma clang attribute push (__attribute__((target("pclmul,sse2,aes"))), apply_to=function)
#define MBEDTLS_POP_TARGET_PRAGMA
#endif
-#endif
#if !defined(MBEDTLS_AES_USE_HARDWARE_ONLY)
/*
+20
View File
@@ -0,0 +1,20 @@
diff --git a/library/common.h b/library/common.h
index 50f2a29..c60d9dc 100644
--- a/library/common.h
+++ b/library/common.h
@@ -19,11 +19,11 @@
#include <stdint.h>
#include <stddef.h>
-#if defined(__ARM_NEON)
-#include <arm_neon.h>
+#if defined(MBEDTLS_PLATFORM_IS_WINDOWS_ON_ARM64)
+#include <arm64_neon.h>
#define MBEDTLS_HAVE_NEON_INTRINSICS
-#elif defined(MBEDTLS_PLATFORM_IS_WINDOWS_ON_ARM64)
-#include <arm64_neon.h>
+#elif (defined(__ANDROID__) && defined(__ARM_FP)) || defined(__ARM_NEON)
+#include <arm_neon.h>
#define MBEDTLS_HAVE_NEON_INTRINSICS
#endif
+147 -86
View File
@@ -55,23 +55,29 @@ if (YUZU_STATIC_ROOM)
set(ENABLE_UPDATE_CHECKER OFF)
set(USE_DISCORD_PRESENCE OFF)
set(BUILD_TESTING OFF)
set(ENABLE_OPENSSL OFF)
set(ENABLE_WEB_SERVICE OFF)
set(ENABLE_LIBUSB OFF)
# allow static libs for boost though
# allow static libs for boost and mbedtls though
set(Boost_USE_STATIC_LIBS ON)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
set(OPENSSL_USE_STATIC_LIBS ON)
set(MBEDTLS_LIB_SUFFIX "_static")
set(YUZU_USE_CPM ON)
set(zstd_FORCE_BUNDLED ON)
set(fmt_FORCE_BUNDLED ON)
endif()
# common network mbedtls
# common: xbyak? booost fmt lz4 zstd
# network: enet boost
# qt stuff
option(ENABLE_QT "Enable the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF)
# option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
cmake_dependent_option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF "NOT YUZU_USE_BUNDLED_QT" OFF)
set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries")
cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF)
@@ -124,7 +130,12 @@ if (YUZU_STATIC_BUILD)
set(YUZU_USE_BUNDLED_OPENSSL ON)
set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF)
## some libraries define a Library::Name_static alternative ##
set(MBEDTLS_LIB_SUFFIX _static)
elseif(APPLE)
set(YUZU_USE_CPM ON)
set(YUZU_USE_BUNDLED_FFMPEG ON)
set(YUZU_USE_BUNDLED_SDL2 ON)
set(YUZU_USE_BUNDLED_OPENSSL ON)
@@ -134,6 +145,7 @@ if (YUZU_STATIC_BUILD)
set(SPIRV-Tools_FORCE_BUNDLED ON)
set(SPIRV-Headers_FORCE_BUNDLED ON)
set(zstd_FORCE_BUNDLED ON)
set(MbedTLS_FORCE_BUNDLED ON)
endif()
endif()
@@ -186,6 +198,8 @@ if (MSVC OR ANDROID)
set(EXT_DEFAULT ON)
endif()
option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT})
# ffmpeg
option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${PLATFORM_SUN}" "NOT WIN32 AND NOT ANDROID" OFF)
@@ -231,12 +245,30 @@ cmake_dependent_option(YUZU_USE_BUNDLED_MOLTENVK "Download bundled MoltenVK lib"
option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF)
set(DEFAULT_ENABLE_OPENSSL ON)
if (ANDROID OR WIN32 OR APPLE OR PLATFORM_SUN OR PLATFORM_OPENBSD)
# - Windows defaults to the Schannel backend.
# - macOS defaults to the SecureTransport backend.
# - Android currently has no SSL backend as the NDK doesn't include any SSL
# library; a proper 'native' backend would have to go through Java.
# - Solaris and OpenBSD have too old backends
# But you can force builds for those platforms to use OpenSSL if you have
# your own copy of it.
set(DEFAULT_ENABLE_OPENSSL OFF)
endif()
if (ENABLE_WEB_SERVICE OR USE_DISCORD_PRESENCE)
set(DEFAULT_ENABLE_OPENSSL ON)
endif()
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL OFF)
if (EXT_DEFAULT OR PLATFORM_SUN OR PLATFORM_OPENBSD)
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL ON)
endif()
option(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" ${DEFAULT_YUZU_USE_BUNDLED_OPENSSL})
cmake_dependent_option(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" ${DEFAULT_YUZU_USE_BUNDLED_OPENSSL} "ENABLE_OPENSSL" OFF)
if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
AddJsonPackage(vulkan-validation-layers)
@@ -364,94 +396,123 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
find_package(RenderDoc MODULE)
# openssl funniness
if (YUZU_USE_BUNDLED_OPENSSL)
if (ENABLE_OPENSSL)
if (YUZU_USE_BUNDLED_OPENSSL)
set(BUILD_SHARED_LIBS OFF)
AddJsonPackage(openssl)
if (OpenSSL_ADDED)
add_compile_definitions(YUZU_BUNDLED_OPENSSL)
endif()
endif()
find_package(OpenSSL 1.1.1 REQUIRED)
endif()
if (YUZU_USE_CPM)
message(STATUS "Fetching needed dependencies with CPM")
set(BUILD_SHARED_LIBS OFF)
AddJsonPackage(openssl)
if (OpenSSL_ADDED)
add_compile_definitions(YUZU_BUNDLED_OPENSSL)
endif()
endif()
set(BUILD_TESTING OFF)
set(ENABLE_TESTING OFF)
find_package(OpenSSL 3 REQUIRED)
# TODO(crueter): renderdoc?
message(STATUS "Fetching needed dependencies with CPM")
# boost
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap asio headers process filesystem crc variant)
set(BUILD_SHARED_LIBS OFF)
set(BUILD_TESTING OFF)
set(ENABLE_TESTING OFF)
AddJsonPackage(boost)
# boost
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap asio headers process filesystem crc variant)
# really annoying thing where boost::headers doesn't work with cpm
# TODO(crueter) investigate
set(BOOST_NO_HEADERS ${Boost_ADDED})
AddJsonPackage(boost)
# really annoying thing where boost::headers doesn't work with cpm
# TODO(crueter) investigate
set(BOOST_NO_HEADERS ${Boost_ADDED})
if (Boost_ADDED)
if (MSVC OR ANDROID)
add_compile_definitions(YUZU_BOOST_v1)
endif()
if (NOT MSVC OR CXX_CLANG)
# boost sucks
if (PLATFORM_SUN)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthreads")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthreads")
if (Boost_ADDED)
if (MSVC OR ANDROID)
add_compile_definitions(YUZU_BOOST_v1)
endif()
target_compile_options(boost_heap INTERFACE -Wno-shadow)
target_compile_options(boost_icl INTERFACE -Wno-shadow)
target_compile_options(boost_asio INTERFACE -Wno-conversion -Wno-implicit-fallthrough)
endif()
endif()
if (NOT MSVC OR CXX_CLANG)
# boost sucks
if (PLATFORM_SUN)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthreads")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthreads")
endif()
# fmt
AddJsonPackage(fmt)
# lz4
AddJsonPackage(lz4)
if (lz4_ADDED)
add_library(lz4::lz4 ALIAS lz4_static)
endif()
# zstd
AddJsonPackage(zstd)
if (zstd_ADDED)
add_library(zstd::zstd ALIAS libzstd_static)
add_library(zstd::libzstd ALIAS libzstd_static)
endif()
if (NOT YUZU_STATIC_ROOM)
# nlohmann
AddJsonPackage(nlohmann)
# zlib
AddJsonPackage(zlib)
if (ZLIB_ADDED)
add_library(ZLIB::ZLIB ALIAS zlibstatic)
endif()
# Opus
AddJsonPackage(opus)
if (Opus_ADDED)
if (MSVC AND CXX_CLANG)
target_compile_options(opus PRIVATE
-Wno-implicit-function-declaration
)
target_compile_options(boost_heap INTERFACE -Wno-shadow)
target_compile_options(boost_icl INTERFACE -Wno-shadow)
target_compile_options(boost_asio INTERFACE -Wno-conversion -Wno-implicit-fallthrough)
endif()
endif()
if (NOT TARGET Opus::opus)
add_library(Opus::opus ALIAS opus)
# fmt
AddJsonPackage(fmt)
# lz4
AddJsonPackage(lz4)
if (lz4_ADDED)
add_library(lz4::lz4 ALIAS lz4_static)
endif()
# zstd
AddJsonPackage(zstd)
if (zstd_ADDED)
add_library(zstd::zstd ALIAS libzstd_static)
add_library(zstd::libzstd ALIAS libzstd_static)
endif()
if (NOT YUZU_STATIC_ROOM)
# nlohmann
AddJsonPackage(nlohmann)
# zlib
AddJsonPackage(zlib)
if (ZLIB_ADDED)
add_library(ZLIB::ZLIB ALIAS zlibstatic)
endif()
# Opus
AddJsonPackage(opus)
if (Opus_ADDED)
if (MSVC AND CXX_CLANG)
target_compile_options(opus PRIVATE
-Wno-implicit-function-declaration
)
endif()
endif()
if (NOT TARGET Opus::opus)
add_library(Opus::opus ALIAS opus)
endif()
endif()
else()
# TODO: we can probably just use CPM for this... right?
# Enforce the search mode of non-required packages for better and shorter failure messages
find_package(fmt 8 REQUIRED)
if (NOT YUZU_DISABLE_LLVM)
find_package(LLVM MODULE COMPONENTS Demangle)
endif()
find_package(nlohmann_json 3.8 REQUIRED)
find_package(lz4 REQUIRED)
find_package(RenderDoc MODULE)
find_package(stb MODULE)
find_package(Opus 1.3 MODULE REQUIRED)
find_package(ZLIB 1.2 REQUIRED)
find_package(zstd 1.5 REQUIRED MODULE)
# wow
find_package(Boost 1.57.0 CONFIG REQUIRED OPTIONAL_COMPONENTS headers context system fiber filesystem)
if (ENABLE_OPENSSL)
find_package(OpenSSL 1.1.1 REQUIRED)
endif()
endif()
@@ -513,8 +574,8 @@ message(STATUS "Platform Libraries: ${PLATFORM_LIBRARIES}")
add_subdirectory(externals)
# pass targets from externals
# TODO(crueter): CPMUtil Propagate func?
find_package(enet)
find_package(MbedTLS)
find_package(unordered_dense REQUIRED)
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
@@ -582,9 +643,9 @@ if (ENABLE_QT)
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Charts Concurrent)
# if (YUZU_USE_QT_MULTIMEDIA)
# find_package(Qt6 REQUIRED COMPONENTS Multimedia)
# endif()
if (YUZU_USE_QT_MULTIMEDIA)
find_package(Qt6 REQUIRED COMPONENTS Multimedia)
endif()
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
# yes Qt, we get it
@@ -623,9 +684,9 @@ if (ENABLE_QT)
if (PLATFORM_LINUX)
list(APPEND YUZU_QT_COMPONENTS DBus)
endif()
# if (YUZU_USE_QT_MULTIMEDIA)
# list(APPEND YUZU_QT_COMPONENTS Multimedia)
# endif()
if (YUZU_USE_QT_MULTIMEDIA)
list(APPEND YUZU_QT_COMPONENTS Multimedia)
endif()
if (YUZU_USE_QT_WEB_ENGINE)
list(APPEND YUZU_QT_COMPONENTS WebEngineCore WebEngineWidgets)
endif()
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "openssl",
"repo": "crueter-ci/OpenSSL",
"version": "3.6.0-1cb0d36b39",
"min_version": "3"
"min_version": "1.1.1"
},
"boost": {
"package": "Boost",
+3 -11
View File
@@ -22,25 +22,17 @@ Debug logs can be found in General -> Debug -> Open Log Location on desktop, and
Ignoring SIGSEGV when debugging in host:
- **gdb**: `handle SIGSEGV nostop pass`.
- **gdb**: `handle all nostop pass`.
- **lldb**: `pro hand -p true -s false -n false SIGSEGV`.
## Debugging (guest code)
### gdb
You must have GDB installed for aarch64 to debug the target. Install it through your package manager, e.g.:
* On Arch:
* `sudo pacman -Syu aarch64-linux-gnu-gdb`
* On Gentoo:
* `sudo emerge --ask crossdev`
* `sudo crossdev -t aarch64-unknown-linux-gnu --ex-gdb`
Run `./build/bin/eden-cli -c <path to your config file (see logs where you run eden normally to see where it is)> -d -g <path to game>`
Or `Enable GDB Stub` at General > Debug, then hook up an aarch64-gdb:
* `target remote localhost:6543`
Type `c` (for continue) and then if it crashes just do a `bt` (backtrace) and `layout asm`
Then hook up an aarch64-gdb (use `yay aarch64-gdb` or `sudo pkg in arch64-gdb` to install)
Then type `target remote localhost:1234` and type `c` (for continue) - and then if it crashes just do a `bt` (backtrace) and `layout asm`.
### gdb cheatsheet
+16 -14
View File
@@ -51,11 +51,12 @@ All other dependencies will be downloaded and built by [CPM](https://github.com/
* [fmt](https://fmt.dev/) 8.0.1+
* [lz4](http://www.lz4.org)
* [nlohmann\_json](https://github.com/nlohmann/json) 3.8+
* [OpenSSL](https://www.openssl.org/source/) 3+
* [OpenSSL](https://www.openssl.org/source/) 1.1.1+
* [ZLIB](https://www.zlib.net/) 1.2+
* [zstd](https://facebook.github.io/zstd/) 1.5+
* [enet](http://enet.bespin.org/) 1.3+
* [Opus](https://opus-codec.org/) 1.3+
* [MbedTLS](https://github.com/Mbed-TLS/mbedtls) 3+
Vulkan 1.3.274+ is also needed:
@@ -120,7 +121,7 @@ sudo emerge -a \
dev-util/vulkan-utility-libraries dev-util/glslang \
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
media-libs/VulkanMemoryAllocator media-libs/libsdl2 media-libs/cubeb \
net-libs/enet \
net-libs/enet net-libs/mbedtls \
sys-libs/zlib \
dev-cpp/nlohmann_json dev-cpp/simpleini dev-cpp/cpp-httplib dev-cpp/cpp-jwt \
games-util/gamemode \
@@ -138,6 +139,7 @@ Required USE flags:
* `dev-qt/qtbase network concurrent dbus gui widgets`
* `dev-libs/quazip qt6`
* `net-libs/mbedtls cmac`
* `media-libs/libsdl2 haptic joystick sound video`
* `dev-cpp/cpp-httplib ssl`
@@ -149,7 +151,7 @@ Required USE flags:
<summary>Arch Linux</summary>
```sh
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl2 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 mbedtls ninja nlohmann-json openssl opus qt6-base qt6-multimedia sdl2 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
```
* Building with QT Web Engine requires `qt6-webengine` as well.
@@ -162,7 +164,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
<summary>Ubuntu, Debian, Mint Linux</summary>
```sh
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl2-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev libmbedtls-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl2-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
```
* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required.
@@ -176,7 +178,7 @@ sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev l
Fedora:
```sh
sudo dnf install autoconf cmake fmt-devel gcc{,-c++} glslang hidapi-devel json-devel libtool libusb1-devel libzstd-devel lz4-devel nasm ninja-build openssl-devel pulseaudio-libs-devel qt6-linguist qt6-qtbase{-private,}-devel qt6-qtwebengine-devel qt6-qtmultimedia-devel qt6-charts-devel speexdsp-devel wayland-devel zlib-devel ffmpeg-devel libXext-devel boost jq
sudo dnf install autoconf cmake fmt-devel gcc{,-c++} glslang hidapi-devel json-devel libtool libusb1-devel libzstd-devel lz4-devel nasm ninja-build openssl-devel pulseaudio-libs-devel qt6-linguist qt6-qtbase{-private,}-devel qt6-qtwebengine-devel qt6-qtmultimedia-devel speexdsp-devel wayland-devel zlib-devel ffmpeg-devel libXext-devel boost jq
```
AlmaLinux (use `YUZU_USE_CPM=ON`):
@@ -209,15 +211,17 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/
# Enable the community repository
setup-apkrepos -c
# Install
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl2-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch
apk add g++ git cmake make mbedtls-dev mbedtls-static mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev libusb-dev libtool boost-dev sdl2-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch
```
`mbedtls-static` has to be specified otherwise `libeverest.a` and `libp256m.a` will fail to be found.
</details>
<details>
<summary>Void Linux</summary>
```sh
xbps-install -Su git make cmake clang pkg-config patch SPIRV-Tools-devel SPIRV-Headers lz4 liblz4-devel boost-devel ffmpeg6-devel catch2 Vulkan-Utility-Libraries Vulkan-Headers glslang openssl-devel SDL2-devel quazip-qt6-devel qt6-base-devel qt6-qt5compat-devel qt6-charts-devel fmt-devel json-c++ libenet-devel libusb-devel
xbps-install -Su git make cmake clang pkg-config patch mbedtls-devel SPIRV-Tools-devel SPIRV-Headers lz4 liblz4-devel boost-devel ffmpeg6-devel catch2 Vulkan-Utility-Libraries Vulkan-Headers glslang openssl-devel SDL2-devel quazip-qt6-devel qt6-base-devel qt6-qt5compat-devel fmt-devel json-c++ libenet-devel libusb-devel
```
Yes, `nlohmann-json` is just named `json-c++`. Why?
@@ -229,8 +233,6 @@ Yes, `nlohmann-json` is just named `json-c++`. Why?
A convenience script is provided on the root of this project [shell.nix](../shell.nix). Run the usual `nix-shell`.
If you're going for a pure build (i.e no downloaded deps), use `-DYUZU_USE_CPM=ON -DCPMUTIL_FORCE_SYSTEM=ON`.
</details>
<details>
<summary>macOS</summary>
@@ -255,7 +257,7 @@ brew install molten-vk
<details>
<summary>FreeBSD</summary>
As root run: `pkg install devel/cmake devel/sdl20 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6`
As root run: `pkg install devel/cmake devel/sdl20 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense mbedtls3 vulkan-headers quazip-qt6`
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
@@ -265,7 +267,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
<details>
<summary>NetBSD</summary>
For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx`.
For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6 mbedtls3 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx`.
[Caveats](./Caveats.md#netbsd).
@@ -285,7 +287,7 @@ pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gm
<summary>DragonFlyBSD</summary>
```sh
pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl2 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 unordered-dense libva-vdpau-driver libva-utils libva-intel-driver
pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl2 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base quazip-qt6 unordered-dense libva-vdpau-driver libva-utils libva-intel-driver
```
[Caveats](./Caveats.md#dragonflybsd).
@@ -309,7 +311,7 @@ sudo pkg install qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json ope
```sh
BASE="git make autoconf libtool automake-wrapper jq patch"
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb unordered_dense openssl SDL2"
MINGW="qt6-base qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus mbedtls libusb unordered_dense openssl SDL2"
# Either x86_64 or clang-aarch64 (Windows on ARM)
packages="$BASE"
for pkg in $MINGW; do
@@ -335,7 +337,7 @@ pacman -Syuu --needed --noconfirm $packages
<summary>HaikuOS</summary>
```sh
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.89_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl2_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.89_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl2_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel mbedtls3_devel glslang qt6_devel
```
[Caveats](./Caveats.md#haikuos).
+9
View File
@@ -286,6 +286,15 @@ Exclusive OR (i.e.: XOR)
Memory access.
### Terminal: Interpret
```c++
SetTerm(IR::Term::Interpret{next})
```
This terminal instruction calls the interpreter, starting at `next`.
The interpreter must interpret exactly one instruction.
### Terminal: ReturnToDispatch
```c++
+5
View File
@@ -117,6 +117,11 @@ public:
MemoryWrite32(vaddr + 4, u32(value >> 32));
}
void InterpreterFallback(u32 pc, size_t num_instructions) override {
// This is never called in practice.
std::terminate();
}
void CallSVC(u32 swi) override {
// Do something.
}
+2 -41
View File
@@ -12,13 +12,12 @@ The setting ranges from 0 to 65535 (0x0000 to 0xFFFF), where each bit represents
* [Accessing Debug Knobs (dev side)](#accessing-debug-knobs-dev-side)
* [Setting Debug Knobs (user side)](#setting-debug-knobs-user-side)
* [Bit Manipulation Examples](#bit-manipulation-examples)
3. [Terminology and user communication](#terminology-and-user-communication)
4. [Examples](#examples)
3. [Examples](#examples)
* [Example 1: Conditional Debug Logging](#example-1-conditional-debug-logging)
* [Example 2: Performance Tuning](#example-2-performance-tuning)
* [Example 3: Feature Gating](#example-3-feature-gating)
5. [Best Practices](#best-practices)
4. [Best Practices](#best-practices)
---
@@ -78,44 +77,6 @@ To enable specific features, calculate the decimal value by setting the appropri
* **Enable bits 0 and 1**: Value = 3 (2^0 + 2^1)
* **Enable bit 15**: Value = 32768 (2^15)
## Terminology and user communication
There are two main confusions when talking about knobs:
### Whether it's zero-based or one-based
Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1).
Debug knobs are **zero-based**, which means:
* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise)
* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc.
### Whether one is talking about the knob itself or about the entire parameter value (which represents all knobs)
Sometimes when an user reports: knob 3 results, it's unclear whether he's referring to knob setting with value 3 (which means both knob 0 and 1 are enabled), or to knob(3) specifically.
Whenever you're instructing tests or reporting results, be precise about whether one you're talking to avoid confusion:
### Setting based terminology
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
Examples:
- **knobs=0**: no knobs enabled
- **knobs=1**: knob0 enabled, others disabled
- **knobs=2**: knob1 enabled, others disabled
- **knobs=3**: knobs 0 and 1 enabled, others disabled
...
### Knob based terminology
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
Examples:
- **knob0**: knob 0 enabled, others disabled
- **knob1**: knob 1 enabled, others disabled
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
...
## Examples
### Example 1: Conditional Debug Logging
+6 -1
View File
@@ -41,6 +41,9 @@ if (NOT TARGET enet::enet)
add_library(enet::enet ALIAS enet)
endif()
# mbedtls
AddJsonPackage(mbedtls)
# stb
add_library(stb stb/stb_dxt.cpp)
target_include_directories(stb PUBLIC ./stb)
@@ -231,7 +234,9 @@ if (VulkanMemoryAllocator_ADDED)
endif()
# httplib
AddJsonPackage(httplib)
if (ENABLE_WEB_SERVICE OR ENABLE_UPDATE_CHECKER OR USE_DISCORD_PRESENCE OR ENABLE_OPENSSL)
AddJsonPackage(httplib)
endif()
# cpp-jwt
if (ENABLE_WEB_SERVICE OR ENABLE_UPDATE_CHECKER)
+17 -6
View File
@@ -33,9 +33,6 @@
"find_args": "MODULE GLOBAL",
"patches": [
"0001-mingw.patch"
],
"options": [
"HTTPLIB_REQUIRE_OPENSSL ON"
]
},
"cpp-jwt": {
@@ -91,6 +88,20 @@
"0001-avoid-memset-when-clearing-an-empty-table.patch"
]
},
"mbedtls": {
"package": "MbedTLS",
"repo": "Mbed-TLS/mbedtls",
"tag": "mbedtls-%VERSION%",
"hash": "6671fb8fcaa832e5b115dfdce8f78baa6a4aea71f5c89a640583634cdee27aefe3bf4be075744da91f7c3ae5ea4e0c765c8fc3937b5cfd9ea73d87ef496524da",
"version": "3",
"git_version": "3.6.4",
"artifact": "%TAG%.tar.bz2",
"skip_updates": true,
"patches": [
"0001-aesni-fix.patch",
"0002-arm64-aes-fix.patch"
]
},
"enet": {
"repo": "lsalzman/enet",
"tag": "v%VERSION%",
@@ -231,8 +242,8 @@
},
"ffmpeg": {
"repo": "FFmpeg/FFmpeg",
"sha": "c7b5f1537d",
"hash": "ed177621176b3961bdcaa339187d3a7688c1c8b060b79c4bb0257cbc67ad7021ae5d5adca5303b45625abbbe3d9aafdd87ce777b8690ac295290d744c875489a",
"sha": "5e56937b74",
"hash": "9ab0457dcd6ce6359b5053c1662f57910d332f68ca0cca9d4134d858464840917027374de3d97e0863c3a7daaea2fe4f4cd17d1c6d8e7f740f4ad91e71c2932b",
"bundled": true
},
"ffmpeg-ci": {
@@ -240,7 +251,7 @@
"package": "FFmpeg",
"name": "ffmpeg",
"repo": "crueter-ci/FFmpeg",
"version": "8.0.1-c7b5f1537d",
"version": "8.0.1-5e56937b74",
"min_version": "4.1"
},
"tzdb": {
+3 -6
View File
@@ -94,10 +94,6 @@ if (MSVC AND NOT CXX_CLANG)
/wd4324 # 'struct_name': structure was padded due to __declspec(align())
/wd4201 # nonstandard extension used : nameless struct/union
/wd4702 # unreachable code (when used with LTO)
$<$<CONFIG:Release>:/GS-> # No stack buffer overflow checks
/Gy # Enable function level linking
/GR- # Disable run time type information
)
if (NOT CXX_CLANG)
@@ -112,14 +108,15 @@ if (MSVC AND NOT CXX_CLANG)
add_compile_options(/QIntel-jcc-erratum)
endif()
# /GS- - No stack buffer overflow checks
add_compile_options("$<$<CONFIG:Release>:/GS->")
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
else()
if (NOT MSVC)
add_compile_options(
-fwrapv
-fno-rtti # Disable RTTI
-pipe
)
endif()
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -24,7 +24,6 @@ import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.PowerStateUpdater
import org.yuzu.yuzu_emu.utils.ControllerNavigationGlobalHook
import java.util.Locale
fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
@@ -73,7 +72,6 @@ class YuzuApplication : Application() {
NativeLibrary.logDeviceInfo()
PowerStateUpdater.start()
Log.logDeviceInfo()
ControllerNavigationGlobalHook.install(this)
createNotificationChannels()
}
@@ -37,7 +37,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
PICTURE_IN_PICTURE("picture_in_picture"),
USE_CUSTOM_RTC("custom_rtc_enabled"),
BLACK_BACKGROUNDS("black_backgrounds"),
INVERT_CONFIRM_BACK_CONTROLLER_BUTTONS("invert_confirm_back_controller_buttons"),
ENABLE_FOLDER_BUTTON("enable_folder_button"),
ENABLE_QLAUNCH_BUTTON("enable_qlaunch_button"),
@@ -68,6 +68,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
MY_PAGE_APPLET("my_page_applet_mode"),
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
OVERLAY_GRID_SIZE("overlay_grid_size"),
DEBUG_KNOBS("debug_knobs"),
GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size")
;
@@ -12,7 +12,6 @@ enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit"),
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"),
DEBUG_KNOBS("debug_knobs")
;
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
@@ -29,4 +28,4 @@ enum class ShortSetting(override val key: String) : AbstractShortSetting {
override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString()
override fun reset() = NativeConfig.setShort(key, defaultValue)
}
}
@@ -378,13 +378,6 @@ abstract class SettingsItem(
warningMessage = R.string.warning_resolution
)
)
put(
SwitchSetting(
BooleanSetting.INVERT_CONFIRM_BACK_CONTROLLER_BUTTONS,
titleId = R.string.invert_confirm_back_controller_buttons,
descriptionId = R.string.invert_confirm_back_controller_buttons_description
)
)
put(
SwitchSetting(
BooleanSetting.SHOW_INPUT_OVERLAY,
@@ -903,7 +896,7 @@ abstract class SettingsItem(
)
put(
SpinBoxSetting(
ShortSetting.DEBUG_KNOBS,
IntSetting.DEBUG_KNOBS,
titleId = R.string.debug_knobs,
descriptionId = R.string.debug_knobs_description,
valueHint = R.string.debug_knobs_hint,
@@ -1076,7 +1076,6 @@ class SettingsFragmentPresenter(
}
add(BooleanSetting.ENABLE_QUICK_SETTINGS.key)
add(BooleanSetting.INVERT_CONFIRM_BACK_CONTROLLER_BUTTONS.key)
add(HeaderSetting(R.string.theme_and_color))
@@ -1230,7 +1229,7 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.general))
add(ShortSetting.DEBUG_KNOBS.key)
add(IntSetting.DEBUG_KNOBS.key)
add(HeaderSetting(R.string.gpu_logging_header))
add(BooleanSetting.GPU_LOGGING_ENABLED.key)
@@ -1,168 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
import android.app.Activity
import android.app.Application
import android.os.Bundle
import android.view.InputDevice
import android.view.KeyEvent
import android.view.View
import android.view.Window
import androidx.activity.ComponentActivity
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import java.util.concurrent.atomic.AtomicBoolean
object ControllerNavigationGlobalHook {
private val installed = AtomicBoolean(false)
fun install(application: Application) {
if (!installed.compareAndSet(false, true)) {
return
}
application.registerActivityLifecycleCallbacks(HookInstaller)
}
private object HookInstaller : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
installHookIfNeeded(activity)
}
override fun onActivityResumed(activity: Activity) {
installHookIfNeeded(activity)
}
override fun onActivityStarted(activity: Activity) = Unit
override fun onActivityPaused(activity: Activity) = Unit
override fun onActivityStopped(activity: Activity) = Unit
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
override fun onActivityDestroyed(activity: Activity) = Unit
}
private fun installHookIfNeeded(activity: Activity) {
val window = activity.window ?: return
val currentCallback = window.callback ?: return
if (currentCallback is ControllerNavigationWindowCallback) {
return
}
window.callback = ControllerNavigationWindowCallback(activity, currentCallback)
}
private class ControllerNavigationWindowCallback(
private val activity: Activity,
private val delegate: Window.Callback
) : Window.Callback by delegate {
private val componentActivity = activity as? ComponentActivity
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (activity.isFinishing || activity.isDestroyed) {
return delegate.dispatchKeyEvent(event)
}
if (!BooleanSetting.INVERT_CONFIRM_BACK_CONTROLLER_BUTTONS.getBoolean()) {
return delegate.dispatchKeyEvent(event)
}
if (!isControllerInput(event) || componentActivity == null) {
return delegate.dispatchKeyEvent(event)
}
if (shouldBypassInGameplay()) {
return delegate.dispatchKeyEvent(event)
}
if (isConfirmAction(event.keyCode)) {
return when (event.action) {
KeyEvent.ACTION_DOWN -> {
if (event.repeatCount == 0) {
componentActivity.onBackPressedDispatcher.onBackPressed()
}
true
}
KeyEvent.ACTION_UP -> true
else -> false
}
}
if (isBackAction(event.keyCode)) {
val remappedEvent = KeyEvent(
event.downTime,
event.eventTime,
event.action,
KeyEvent.KEYCODE_DPAD_CENTER,
event.repeatCount,
event.metaState,
event.deviceId,
event.scanCode,
event.flags,
event.source
)
return delegate.dispatchKeyEvent(remappedEvent)
}
return delegate.dispatchKeyEvent(event)
}
private fun shouldBypassInGameplay(): Boolean {
if (activity.javaClass.name != "org.yuzu.yuzu_emu.activities.EmulationActivity") {
return false
}
if (!NativeLibrary.isRunning()) {
return false
}
val surface = activity.findViewById<View?>(R.id.surface_emulation) ?: return false
if (!surface.isShown || surface.visibility != View.VISIBLE) {
return false
}
val focused = activity.currentFocus
if (focused != null) {
val inputOverlay = activity.findViewById<View?>(R.id.surface_input_overlay)
if (!isDescendantOf(focused, inputOverlay)) {
return false
}
}
return true
}
private fun isDescendantOf(view: View, parent: View?): Boolean {
if (parent == null) {
return false
}
var current: View? = view
while (current != null) {
if (current === parent) {
return true
}
current = current.parent as? View
}
return false
}
private fun isControllerInput(event: KeyEvent): Boolean {
val source = event.source
val deviceSources = event.device?.sources ?: InputDevice.getDevice(event.deviceId)?.sources ?: 0
return hasControllerSource(source) || hasControllerSource(deviceSources)
}
private fun isConfirmAction(keyCode: Int): Boolean {
return keyCode == KeyEvent.KEYCODE_BUTTON_A
}
private fun isBackAction(keyCode: Int): Boolean {
return keyCode == KeyEvent.KEYCODE_BUTTON_B
}
private fun hasControllerSource(source: Int): Boolean {
return source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
source and InputDevice.SOURCE_DPAD == InputDevice.SOURCE_DPAD
}
}
}
@@ -65,8 +65,6 @@ namespace AndroidSettings {
Settings::Category::Android};
Settings::Setting<bool> enable_qlaunch_button{linkage, false, "enable_qlaunch_button",
Settings::Category::Android};
Settings::Setting<bool> invert_confirm_back_controller_buttons{
linkage, false, "invert_confirm_back_controller_buttons", Settings::Category::Android};
// Input/performance overlay settings
std::vector<OverlayControlData> overlay_control_data;
@@ -36,8 +36,6 @@
<string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string>
<string name="hide_overlay_on_controller_input">Hide Overlay on Controller Input</string>
<string name="hide_overlay_on_controller_input_description">Automatically hide the touch controls overlay when a physical controller is used. Overlay reappears when controller is disconnected.</string>
<string name="invert_confirm_back_controller_buttons">Invert Confirm/Back Controller Buttons</string>
<string name="invert_confirm_back_controller_buttons_description">Swap Android Confirm and Back button handling to match both Switch and Xbox styles while using the app UI.</string>
<string name="input_overlay_options">Input Overlay</string>
<string name="input_overlay_options_description">Configure on-screen controls</string>
+1 -2
View File
@@ -25,8 +25,7 @@ void AssertFailSoftImpl();
#define ASSERT_MSG(_a_, ...) \
([&]() YUZU_NO_INLINE { \
auto&& assert_condition = (_a_); \
if (!(assert_condition)) [[unlikely]] { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, __FILE__ ": assert " __VA_ARGS__); \
AssertFailSoftImpl(); \
} \
+1 -1
View File
@@ -791,7 +791,7 @@ struct Values {
0,
65535,
"debug_knobs",
Category::Core,
Category::Debugging,
Specialization::Countable,
true,
true};
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -162,7 +162,7 @@ public:
/**
* @returns A unique identifier for the Setting's internal data type.
*/
[[nodiscard]] virtual std::string_view TypeId() const = 0;
[[nodiscard]] virtual std::type_index TypeId() const = 0;
/**
* Returns true if the Setting's internal data type is an enum.
+3 -9
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -220,14 +220,8 @@ public:
*
* @returns the type_index of the setting's type
*/
[[nodiscard]] std::string_view TypeId() const override final {
if constexpr (std::is_same_v<Type, std::string>) {
return "string";
} else if constexpr (std::is_same_v<Type, bool>) {
return "bool";
} else {
return "other";
}
[[nodiscard]] std::type_index TypeId() const override final {
return std::type_index(typeid(Type));
}
[[nodiscard]] constexpr u32 EnumIndex() const override final {
+24 -19
View File
@@ -1219,9 +1219,13 @@ target_link_libraries(core PRIVATE
fmt::fmt
nlohmann_json::nlohmann_json
RenderDoc::API
ZLIB::ZLIB)
ZLIB::ZLIB
MbedTLS::mbedcrypto${MBEDTLS_LIB_SUFFIX}
MbedTLS::mbedtls${MBEDTLS_LIB_SUFFIX})
target_link_libraries(core PRIVATE httplib::httplib)
if (ENABLE_WEB_SERVICE OR ENABLE_OPENSSL)
target_link_libraries(core PRIVATE httplib::httplib)
endif()
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PUBLIC ENABLE_WEB_SERVICE)
@@ -1267,24 +1271,25 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_link_libraries(core PRIVATE dynarmic::dynarmic)
endif()
target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp)
if(ENABLE_OPENSSL)
target_sources(core PRIVATE
hle/service/ssl/ssl_backend_openssl.cpp)
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
target_compile_definitions(core PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
find_package(OpenSSL REQUIRED)
# TODO
# elseif (APPLE)
# target_sources(core PRIVATE
# hle/service/ssl/ssl_backend_securetransport.cpp)
# target_link_libraries(core PRIVATE "-framework Security")
# elseif (WIN32)
# target_sources(core PRIVATE
# hle/service/ssl/ssl_backend_schannel.cpp)
# target_link_libraries(core PRIVATE crypt32 secur32)
# else()
# target_sources(core PRIVATE
# hle/service/ssl/ssl_backend_none.cpp)
# endif()
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
target_compile_definitions(core PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
elseif (APPLE)
target_sources(core PRIVATE
hle/service/ssl/ssl_backend_securetransport.cpp)
target_link_libraries(core PRIVATE "-framework Security")
elseif (WIN32)
target_sources(core PRIVATE
hle/service/ssl/ssl_backend_schannel.cpp)
target_link_libraries(core PRIVATE crypt32 secur32)
else()
target_sources(core PRIVATE
hle/service/ssl/ssl_backend_none.cpp)
endif()
create_target_directory_groups(core)
@@ -88,6 +88,13 @@ bool DynarmicCallbacks32::MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expec
m_memory.WriteExclusive64(vaddr, value, expected);
}
void DynarmicCallbacks32::InterpreterFallback(u32 pc, std::size_t num_instructions) {
m_parent.LogBacktrace(m_process);
LOG_ERROR(Core_ARM,
"Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc,
num_instructions, m_memory.Read32(pc));
}
void DynarmicCallbacks32::ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) {
switch (exception) {
case Dynarmic::A32::Exception::NoExecuteFault:
+1 -3
View File
@@ -35,9 +35,6 @@ public:
u32 MemoryRead32(u32 vaddr) override;
u64 MemoryRead64(u32 vaddr) override;
std::optional<u32> MemoryReadCode(u32 vaddr) override;
void InstructionSynchronizationBarrierRaised() override {
last_code_addr = 0; //reset back, force refetch
}
void MemoryWrite8(u32 vaddr, u8 value) override;
void MemoryWrite16(u32 vaddr, u16 value) override;
void MemoryWrite32(u32 vaddr, u32 value) override;
@@ -46,6 +43,7 @@ public:
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override;
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override;
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override;
void InterpreterFallback(u32 pc, std::size_t num_instructions) override;
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override;
void CallSVC(u32 swi) override;
void AddTicks(u64 ticks) override;
@@ -102,6 +102,13 @@ bool DynarmicCallbacks64::MemoryWriteExclusive128(u64 vaddr, Dynarmic::A64::Vect
m_memory.WriteExclusive128(vaddr, value, expected);
}
void DynarmicCallbacks64::InterpreterFallback(u64 pc, std::size_t num_instructions) {
m_parent.LogBacktrace(m_process);
LOG_ERROR(Core_ARM, "Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc,
num_instructions, m_memory.Read32(pc));
ReturnException(pc, PrefetchAbort);
}
void DynarmicCallbacks64::InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, u64 value) {
switch (op) {
case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: {
+1 -3
View File
@@ -42,9 +42,6 @@ public:
u64 MemoryRead64(u64 vaddr) override;
Dynarmic::A64::Vector MemoryRead128(u64 vaddr) override;
std::optional<u32> MemoryReadCode(u64 vaddr) override;
void InstructionSynchronizationBarrierRaised() override {
last_code_addr = 0; //reset back, force refetch
}
void MemoryWrite8(u64 vaddr, u8 value) override;
void MemoryWrite16(u64 vaddr, u16 value) override;
void MemoryWrite32(u64 vaddr, u32 value) override;
@@ -55,6 +52,7 @@ public:
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override;
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override;
bool MemoryWriteExclusive128(u64 vaddr, Dynarmic::A64::Vector value, Dynarmic::A64::Vector expected) override;
void InterpreterFallback(u64 pc, std::size_t num_instructions) override;
void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, u64 value) override;
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override;
void CallSVC(u32 svc) override;
+7 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -772,8 +772,12 @@ std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, m
u32 instruction = memory.Read32(pc);
bool was_executed = false;
auto decoder = Dynarmic::A64::Decode<VisitorBase>(instruction);
was_executed = decoder.get().call(visitor, instruction);
if (auto decoder = Dynarmic::A64::Decode<VisitorBase>(instruction)) {
was_executed = decoder->get().call(visitor, instruction);
} else {
LOG_ERROR(Core_ARM, "Unallocated encoding: {:#x}", instruction);
}
return was_executed ? std::optional<u64>(pc + 4) : std::nullopt;
}
+4
View File
@@ -806,6 +806,10 @@ void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
impl->content_provider->SetSlot(slot, provider);
}
void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
impl->content_provider->ClearSlot(slot);
}
const Reporter& System::GetReporter() const {
return impl->reporter;
}
+5 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
@@ -358,7 +358,10 @@ public:
[[nodiscard]] Service::FileSystem::FileSystemController& GetFileSystemController();
[[nodiscard]] const Service::FileSystem::FileSystemController& GetFileSystemController() const;
void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot, FileSys::ContentProvider* provider);
void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
FileSys::ContentProvider* provider);
void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
[[nodiscard]] const Reporter& GetReporter() const;
+60 -99
View File
@@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cstring>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <mbedtls/cipher.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
@@ -28,121 +28,83 @@ NintendoTweak CalculateNintendoTweak(std::size_t sector_id) {
}
} // Anonymous namespace
// Structure to hide OpenSSL types from header file
static_assert(static_cast<std::size_t>(Mode::CTR) ==
static_cast<std::size_t>(MBEDTLS_CIPHER_AES_128_CTR),
"CTR has incorrect value.");
static_assert(static_cast<std::size_t>(Mode::ECB) ==
static_cast<std::size_t>(MBEDTLS_CIPHER_AES_128_ECB),
"ECB has incorrect value.");
static_assert(static_cast<std::size_t>(Mode::XTS) ==
static_cast<std::size_t>(MBEDTLS_CIPHER_AES_128_XTS),
"XTS has incorrect value.");
// Structure to hide mbedtls types from header file
struct CipherContext {
EVP_CIPHER_CTX* encryption_context = nullptr;
EVP_CIPHER_CTX* decryption_context = nullptr;
EVP_CIPHER* cipher = nullptr;
mbedtls_cipher_context_t encryption_context;
mbedtls_cipher_context_t decryption_context;
};
static inline const std::string GetCipherName(Mode mode, u32 key_size) {
std::string cipher;
std::size_t effective_bits = key_size * 8;
switch (mode) {
case Mode::CTR:
cipher = "CTR";
break;
case Mode::ECB:
cipher = "ECB";
break;
case Mode::XTS:
cipher = "XTS";
effective_bits /= 2;
break;
default:
UNREACHABLE();
}
return fmt::format("AES-{}-{}", effective_bits, cipher);
};
static EVP_CIPHER *GetCipher(Mode mode, u32 key_size) {
static auto fetch_cipher = [](Mode m, u32 k) {
return EVP_CIPHER_fetch(nullptr, GetCipherName(m, k).c_str(), nullptr);
};
static const struct {
EVP_CIPHER* ctr_16 = fetch_cipher(Mode::CTR, 16);
EVP_CIPHER* ecb_16 = fetch_cipher(Mode::ECB, 16);
EVP_CIPHER* xts_16 = fetch_cipher(Mode::XTS, 16);
EVP_CIPHER* ctr_32 = fetch_cipher(Mode::CTR, 32);
EVP_CIPHER* ecb_32 = fetch_cipher(Mode::ECB, 32);
EVP_CIPHER* xts_32 = fetch_cipher(Mode::XTS, 32);
} ciphers = {};
switch (mode) {
case Mode::CTR:
return key_size == 16 ? ciphers.ctr_16 : ciphers.ctr_32;
case Mode::ECB:
return key_size == 16 ? ciphers.ecb_16 : ciphers.ecb_32;
case Mode::XTS:
return key_size == 16 ? ciphers.xts_16 : ciphers.xts_32;
default:
UNIMPLEMENTED();
}
return nullptr;
}
// TODO: WHY TEMPLATE???????
template <typename Key, std::size_t KeySize>
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
: ctx(std::make_unique<CipherContext>()) {
mbedtls_cipher_init(&ctx->encryption_context);
mbedtls_cipher_init(&ctx->decryption_context);
ctx->encryption_context = EVP_CIPHER_CTX_new();
ctx->decryption_context = EVP_CIPHER_CTX_new();
ctx->cipher = GetCipher(mode, KeySize);
if (ctx->cipher) {
EVP_CIPHER_up_ref(ctx->cipher);
} else {
UNIMPLEMENTED();
}
ASSERT_MSG((mbedtls_cipher_setup(
&ctx->encryption_context,
mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) ||
mbedtls_cipher_setup(
&ctx->decryption_context,
mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode)))) == 0,
"Failed to initialize mbedtls ciphers.");
ASSERT_MSG(ctx->encryption_context && ctx->decryption_context && ctx->cipher,
"OpenSSL cipher context failed init!");
// now init ciphers
ASSERT(EVP_CipherInit_ex2(ctx->encryption_context, ctx->cipher, key.data(), NULL, 1, NULL));
ASSERT(EVP_CipherInit_ex2(ctx->decryption_context, ctx->cipher, key.data(), NULL, 0, NULL));
EVP_CIPHER_CTX_set_padding(ctx->encryption_context, 0);
EVP_CIPHER_CTX_set_padding(ctx->decryption_context, 0);
ASSERT(
!mbedtls_cipher_setkey(&ctx->encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT));
ASSERT(
!mbedtls_cipher_setkey(&ctx->decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT));
//"Failed to set key on mbedtls ciphers.");
}
template <typename Key, std::size_t KeySize>
AESCipher<Key, KeySize>::~AESCipher() {
EVP_CIPHER_CTX_free(ctx->encryption_context);
EVP_CIPHER_CTX_free(ctx->decryption_context);
EVP_CIPHER_free(ctx->cipher);
mbedtls_cipher_free(&ctx->encryption_context);
mbedtls_cipher_free(&ctx->decryption_context);
}
template <typename Key, std::size_t KeySize>
void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* dest, Op op) const {
auto* const context = op == Op::Encrypt ? ctx->encryption_context : ctx->decryption_context;
auto* const context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context;
mbedtls_cipher_reset(context);
if (size == 0)
return;
// reset
ASSERT(EVP_CipherInit_ex(context, nullptr, nullptr, nullptr, nullptr, -1));
const auto mode = mbedtls_cipher_get_cipher_mode(context);
std::size_t written = 0;
const int block_size = EVP_CIPHER_CTX_get_block_size(context);
ASSERT(block_size > 0 && block_size <= int(AesBlockBytes));
if (mode != MBEDTLS_MODE_ECB) {
const int ret = mbedtls_cipher_update(context, src, size, dest, &written);
ASSERT(ret == 0);
if (written != size) {
LOG_WARNING(Crypto, "Not all data was processed requested={:016X}, actual={:016X}.", size, written);
}
return;
}
const auto block_size = mbedtls_cipher_get_block_size(context);
ASSERT(block_size <= AesBlockBytes);
const std::size_t whole_block_bytes = size - (size % block_size);
int written = 0;
if (whole_block_bytes != 0) {
ASSERT(EVP_CipherUpdate(context, dest, &written, src, static_cast<int>(whole_block_bytes)));
if (std::size_t(written) != whole_block_bytes) {
const int ret = mbedtls_cipher_update(context, src, whole_block_bytes, dest, &written);
ASSERT(ret == 0);
if (written != whole_block_bytes) {
LOG_WARNING(Crypto, "Not all data was processed requested={:016X}, actual={:016X}.",
whole_block_bytes, written);
}
}
// tail
const std::size_t tail = size - whole_block_bytes;
if (tail == 0)
return;
@@ -150,13 +112,13 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* des
std::array<u8, AesBlockBytes> tail_buffer{};
std::memcpy(tail_buffer.data(), src + whole_block_bytes, tail);
int tail_written = 0;
ASSERT(EVP_CipherUpdate(context, tail_buffer.data(), &tail_written, tail_buffer.data(), block_size));
std::size_t tail_written = 0;
const int ret = mbedtls_cipher_update(context, tail_buffer.data(), block_size, tail_buffer.data(),
&tail_written);
ASSERT(ret == 0);
if (tail_written != block_size) {
LOG_WARNING(Crypto, "Tail block not fully processed requested={:016X}, actual={:016X}.",
block_size, tail_written);
LOG_WARNING(Crypto, "Not all data was processed requested={:016X}, actual={:016X}.", block_size,
tail_written);
}
std::memcpy(dest + whole_block_bytes, tail_buffer.data(), tail);
@@ -175,10 +137,9 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8*
template <typename Key, std::size_t KeySize>
void AESCipher<Key, KeySize>::SetIV(std::span<const u8> data) {
const int ret_enc = EVP_CipherInit_ex(ctx->encryption_context, nullptr, nullptr, nullptr, data.data(), -1);
const int ret_dec = EVP_CipherInit_ex(ctx->decryption_context, nullptr, nullptr, nullptr, data.data(), -1);
ASSERT_MSG(ret_enc == 1 && ret_dec == 1, "Failed to set IV on OpenSSL contexts");
ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, data.data(), data.size()) ||
mbedtls_cipher_set_iv(&ctx->decryption_context, data.data(), data.size())) == 0,
"Failed to set IV on mbedtls ciphers.");
}
template class AESCipher<Key128>;
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -10,6 +10,7 @@
#include <span>
#include <type_traits>
#include "common/common_types.h"
#include "core/file_sys/vfs/vfs.h"
namespace Core::Crypto {
@@ -61,5 +62,4 @@ public:
private:
std::unique_ptr<CipherContext> ctx;
};
} // namespace Core::Crypto
+39 -48
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -9,26 +9,35 @@
#include <bitset>
#include <cctype>
#include <fstream>
#include <locale>
#include <map>
#include <sstream>
#include <tuple>
#include <vector>
#include <openssl/evp.h>
#include <mbedtls/bignum.h>
#include <mbedtls/cipher.h>
#include <mbedtls/cmac.h>
#include <mbedtls/sha256.h>
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#ifndef MBEDTLS_CMAC_C
#error mbedtls was compiled without CMAC support. Check your USE flags (Gentoo) or contact your package maintainer.
#endif
namespace Core::Crypto {
namespace {
@@ -518,27 +527,15 @@ static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) {
std::array<u8, in_size + 4> seed_exp{};
std::memcpy(seed_exp.data(), seed.data(), in_size);
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
const EVP_MD* sha256 = EVP_sha256();
std::vector<u8> out;
size_t i = 0;
while (out.size() < target_size) {
size_t offset = out.size();
out.resize(offset + 0x20);
seed_exp[in_size + 3] = u8(i);
u32 hash_len = 0;
EVP_DigestInit_ex(ctx, sha256, nullptr);
EVP_DigestUpdate(ctx, seed_exp.data(), seed_exp.size());
EVP_DigestFinal_ex(ctx, out.data() + offset, &hash_len);
out.resize(out.size() + 0x20);
seed_exp[in_size + 3] = static_cast<u8>(i);
mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0);
++i;
}
EVP_MD_CTX_free(ctx);
std::array<u8, target_size> target;
std::memcpy(target.data(), out.data(), target_size);
return target;
@@ -591,28 +588,32 @@ std::optional<Key128> KeyManager::ParseTicketTitleKey(const Ticket& ticket) {
return std::nullopt;
}
mbedtls_mpi D; // RSA Private Exponent
mbedtls_mpi N; // RSA Modulus
mbedtls_mpi S; // Input
mbedtls_mpi M; // Output
mbedtls_mpi_init(&D);
mbedtls_mpi_init(&N);
mbedtls_mpi_init(&S);
mbedtls_mpi_init(&M);
const auto& title_key_block = ticket.GetData().title_key_block;
mbedtls_mpi_read_binary(&D, eticket_rsa_keypair.decryption_key.data(),
eticket_rsa_keypair.decryption_key.size());
mbedtls_mpi_read_binary(&N, eticket_rsa_keypair.modulus.data(),
eticket_rsa_keypair.modulus.size());
mbedtls_mpi_read_binary(&S, title_key_block.data(), title_key_block.size());
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
std::array<u8, 0x100> rsa_step;
{
// Private context for OpenSSL bignumbers
// Inside block because I dont wanna pollute the space...
const auto& title_key_block = ticket.GetData().title_key_block;
BIGNUM* D = BN_bin2bn(eticket_rsa_keypair.decryption_key.data(), int(eticket_rsa_keypair.decryption_key.size()), NULL);
BIGNUM* N = BN_bin2bn(eticket_rsa_keypair.modulus.data(), int(eticket_rsa_keypair.modulus.size()), NULL);
BIGNUM* S = BN_bin2bn(title_key_block.data(), int(title_key_block.size()), NULL);
BIGNUM* M = BN_new();
// M = S ^ D mod N
BN_mod_exp(M, S, D, N, NULL);
BN_bn2bin(M, rsa_step.data());
BN_free(D);
BN_free(N);
BN_free(S);
BN_free(M);
}
mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size());
u8 m_0 = rsa_step[0];
std::array<u8, 0x20> m_1;
std::array<u8, 0xDF> m_2;
std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size());
std::array<u8, 0xDF> m_2;
std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
if (m_0 != 0) {
@@ -953,18 +954,8 @@ void KeyManager::DeriveSDSeedLazy() {
static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
Key128 out{};
static EVP_MAC* mac = EVP_MAC_fetch(nullptr, "cmac", nullptr);
if (!mac) return out;
static EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(mac);
if (!ctx) return out;
EVP_MAC_init(ctx, key.data(), key.size() * CHAR_BIT, NULL);
EVP_MAC_update(ctx, source, size);
size_t len;
EVP_MAC_final(ctx, out.data(), &len, out.size());
mbedtls_cipher_cmac(mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB), key.data(),
key.size() * 8, source, size, out.data());
return out;
}
+7 -2
View File
@@ -1,20 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cctype>
#include <cstring>
#include <mbedtls/sha256.h>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "common/swap.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
#include "core/crypto/xts_encryption_layer.h"
#include "core/file_sys/kernel_executable.h"
#include "core/file_sys/vfs/vfs.h"
#include "core/file_sys/vfs/vfs_offset.h"
#include "core/file_sys/vfs/vfs_vector.h"
#include "core/loader/loader.h"
@@ -250,4 +255,4 @@ std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const {
prodinfo_decrypted->Read(out.data(), out.size(), 0x3890);
return out;
}
} // namespace Core::Crypto
} // namespace Core::Crypto
-4
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -40,7 +37,6 @@ enum class ContentRecordType : u8 {
HtmlDocument = 4,
LegalInformation = 5,
DeltaFragment = 6,
Count,
};
struct ContentRecord {
+7 -7
View File
@@ -143,7 +143,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
bool checked_external = false;
bool checked_manual = false;
const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider);
const auto* content_union = dynamic_cast<const ContentProviderUnion*>(&content_provider);
const auto update_tid = GetUpdateTitleID(title_id);
if (content_union) {
@@ -167,7 +167,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
// Also check ManualContentProvider (for Android)
if (!checked_external) {
const auto* manual_provider = static_cast<const ManualContentProvider*>(
const auto* manual_provider = dynamic_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider) {
const auto manual_update_versions = manual_provider->ListUpdateVersions(update_tid);
@@ -243,7 +243,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
// Also try ManualContentProvider
if (update == nullptr) {
const auto* manual_provider = static_cast<const ManualContentProvider*>(
const auto* manual_provider = dynamic_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider) {
auto file = manual_provider->GetEntryForVersion(update_tid, ContentRecordType::Program, *enabled_version);
@@ -570,7 +570,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
bool checked_external = false;
bool checked_manual = false;
const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider);
const auto* content_union = dynamic_cast<const ContentProviderUnion*>(&content_provider);
if (content_union) {
// First, check ExternalContentProvider
const auto* external_provider = content_union->GetExternalProvider();
@@ -592,7 +592,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
}
if (!checked_external) {
const auto* manual_provider = static_cast<const ManualContentProvider*>(
const auto* manual_provider = dynamic_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider) {
const auto manual_update_versions = manual_provider->ListUpdateVersions(update_tid);
@@ -690,7 +690,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
std::vector<Patch> external_update_patches;
const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider);
const auto* content_union = dynamic_cast<const ContentProviderUnion*>(&content_provider);
if (content_union) {
// First, check ExternalContentProvider for updates
@@ -721,7 +721,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
}
}
const auto* manual_provider = static_cast<const ManualContentProvider*>(
const auto* manual_provider = dynamic_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider && external_update_patches.empty()) {
const auto manual_update_versions = manual_provider->ListUpdateVersions(update_tid);
+178 -124
View File
@@ -7,11 +7,12 @@
#include <algorithm>
#include <random>
#include <regex>
#include <openssl/evp.h>
#include <mbedtls/sha256.h>
#include "common/assert.h"
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
@@ -63,23 +64,17 @@ static bool FollowsNcaIdFormat(std::string_view name) {
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
bool within_two_digit, bool cnmt_suffix) {
const auto nca_str = Common::HexToString(nca_id, second_hex_upper);
if (!within_two_digit) {
const auto format_str = fmt::runtime(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca");
return fmt::format(format_str, nca_str);
return fmt::format(format_str, Common::HexToString(nca_id, second_hex_upper));
}
Core::Crypto::SHA256Hash hash{};
u32 hash_len = 0;
EVP_Digest(nca_id.data(), nca_id.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
const auto format_str =
fmt::runtime(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca");
LOG_DEBUG(Loader, "Decoded {} bytes, nca id {}", hash_len, nca_str);
return fmt::format(format_str, hash[0], nca_str);
return fmt::format(format_str, hash[0], Common::HexToString(nca_id, second_hex_upper));
}
static std::string GetCNMTName(TitleType type, u64 title_id) {
@@ -157,11 +152,7 @@ bool PlaceholderCache::Create(const NcaID& id, u64 size) const {
}
Core::Crypto::SHA256Hash hash{};
u32 hash_len = 0;
EVP_Digest(id.data(), id.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);
LOG_DEBUG(Loader, "Decoded {} bytes, nca id {}", hash_len, id);
mbedtls_sha256(id.data(), id.size(), hash.data(), 0);
const auto dirname = fmt::format("000000{:02X}", hash[0]);
const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname);
@@ -185,11 +176,7 @@ bool PlaceholderCache::Delete(const NcaID& id) const {
}
Core::Crypto::SHA256Hash hash{};
u32 hash_len = 0;
EVP_Digest(id.data(), id.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);
LOG_DEBUG(Loader, "Decoded {} bytes, nca id {}", hash_len, id);
mbedtls_sha256(id.data(), id.size(), hash.data(), 0);
const auto dirname = fmt::format("000000{:02X}", hash[0]);
const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname);
@@ -366,7 +353,8 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
return file;
}
static std::optional<NcaID> CheckMapForContentRecord(const ankerl::unordered_dense::map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
static std::optional<NcaID> CheckMapForContentRecord(const std::map<u64, CNMT>& map, u64 title_id,
ContentRecordType type) {
const auto cmnt_iter = map.find(title_id);
if (cmnt_iter == map.cend()) {
return std::nullopt;
@@ -683,12 +671,7 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
const OptionalHeader opt_header{0, 0};
ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca.GetType()), {}};
const auto& data = nca.GetBaseFile()->ReadBytes(0x100000);
u32 hash_len = 0;
EVP_Digest(data.data(), data.size(), c_rec.hash.data(), &hash_len, EVP_sha256(), nullptr);
LOG_DEBUG(Loader, "Decoded {} bytes, nca {}", hash_len, nca.GetName());
mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0);
std::memcpy(&c_rec.nca_id, &c_rec.hash, 16);
const CNMT new_cnmt(header, opt_header, {c_rec}, {});
if (!RawInstallYuzuMeta(new_cnmt)) {
@@ -799,12 +782,7 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti
id = *override_id;
} else {
const auto& data = in->ReadBytes(0x100000);
u32 hash_len = 0;
EVP_Digest(data.data(), data.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);
LOG_DEBUG(Loader, "Decoded {} bytes, nca {}", hash_len, nca.GetName());
mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
memcpy(id.data(), hash.data(), 16);
}
@@ -851,119 +829,174 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
}
}
Refresh();
return std::find_if(yuzu_meta.begin(), yuzu_meta.end(), [&cnmt](const std::pair<u64, CNMT>& kv) {
return kv.second.GetType() == cnmt.GetType() && kv.second.GetTitleID() == cnmt.GetTitleID();
}) != yuzu_meta.end();
return std::find_if(yuzu_meta.begin(), yuzu_meta.end(),
[&cnmt](const std::pair<u64, CNMT>& kv) {
return kv.second.GetType() == cnmt.GetType() &&
kv.second.GetTitleID() == cnmt.GetTitleID();
}) != yuzu_meta.end();
}
ContentProviderUnion::~ContentProviderUnion() = default;
void ContentProviderUnion::SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider) {
providers[size_t(slot)] = provider;
providers[slot] = provider;
}
void ContentProviderUnion::ClearSlot(ContentProviderUnionSlot slot) {
providers[slot] = nullptr;
}
void ContentProviderUnion::Refresh() {
for (auto e : providers)
if (e != nullptr)
e->Refresh();
for (auto& provider : providers) {
if (provider.second == nullptr)
continue;
provider.second->Refresh();
}
}
bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const {
for (auto const e : providers)
if (e && e->HasEntry(title_id, type))
for (const auto& provider : providers) {
if (provider.second == nullptr)
continue;
if (provider.second->HasEntry(title_id, type))
return true;
}
return false;
}
std::optional<u32> ContentProviderUnion::GetEntryVersion(u64 title_id) const {
for (auto const e : providers) {
if (e == nullptr)
for (const auto& provider : providers) {
if (provider.second == nullptr)
continue;
if (auto const res = e->GetEntryVersion(title_id); res != std::nullopt)
const auto res = provider.second->GetEntryVersion(title_id);
if (res != std::nullopt)
return res;
}
return std::nullopt;
}
VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
for (auto const e : providers) {
if (e == nullptr)
for (const auto& provider : providers) {
if (provider.second == nullptr)
continue;
if (auto const res = e->GetEntryUnparsed(title_id, type); res != nullptr)
const auto res = provider.second->GetEntryUnparsed(title_id, type);
if (res != nullptr)
return res;
}
return nullptr;
}
VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
for (auto const e : providers) {
if (e == nullptr)
for (const auto& provider : providers) {
if (provider.second == nullptr)
continue;
if (auto const res = e->GetEntryRaw(title_id, type); res != nullptr)
const auto res = provider.second->GetEntryRaw(title_id, type);
if (res != nullptr)
return res;
}
return nullptr;
}
std::unique_ptr<NCA> ContentProviderUnion::GetEntry(u64 title_id, ContentRecordType type) const {
for (auto const e : providers) {
if (e == nullptr)
for (const auto& provider : providers) {
if (provider.second == nullptr)
continue;
if (auto res = e->GetEntry(title_id, type); res != nullptr)
auto res = provider.second->GetEntry(title_id, type);
if (res != nullptr)
return res;
}
return nullptr;
}
std::vector<ContentProviderEntry> ContentProviderUnion::ListEntriesFilter(
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const {
std::vector<ContentProviderEntry> out;
for (const auto& provider : providers) {
if (provider.second == nullptr)
continue;
const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
std::copy(vec.begin(), vec.end(), std::back_inserter(out));
}
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>>
ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnionSlot> origin,
std::optional<TitleType> title_type,
std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const {
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> out;
for (const auto& provider : providers) {
if (provider.second == nullptr)
continue;
if (origin.has_value() && *origin != provider.first)
continue;
const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
std::transform(vec.begin(), vec.end(), std::back_inserter(out),
[&provider](const ContentProviderEntry& entry) {
return std::make_pair(provider.first, entry);
});
}
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry(
u64 title_id, ContentRecordType type) const {
const auto iter =
std::find_if(providers.begin(), providers.end(), [title_id, type](const auto& provider) {
return provider.second != nullptr && provider.second->HasEntry(title_id, type);
});
if (iter == providers.end()) {
return std::nullopt;
}
return iter->first;
}
const ExternalContentProvider* ContentProviderUnion::GetExternalProvider() const {
auto it = providers.find(ContentProviderUnionSlot::External);
if (it != providers.end() && it->second != nullptr) {
return dynamic_cast<const ExternalContentProvider*>(it->second);
}
return nullptr;
}
std::vector<ContentProviderEntry> ContentProviderUnion::ListEntriesFilter(std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type, std::optional<u64> title_id) const {
std::vector<ContentProviderEntry> out;
for (auto const& e : providers) {
if (e != nullptr) {
auto const vec = e->ListEntriesFilter(title_type, record_type, title_id);
std::copy(vec.begin(), vec.end(), std::back_inserter(out));
}
const ContentProvider* ContentProviderUnion::GetSlotProvider(ContentProviderUnionSlot slot) const {
auto it = providers.find(slot);
if (it != providers.end()) {
return it->second;
}
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnionSlot> origin, std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type, std::optional<u64> title_id) const {
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> out;
for (size_t i = 0; i < providers.size(); ++i) {
auto const& e = providers[i];
if (e == nullptr)
continue;
if (origin.has_value() && *origin != ContentProviderUnionSlot(i))
continue;
auto const vec = e->ListEntriesFilter(title_type, record_type, title_id);
std::transform(vec.begin(), vec.end(), std::back_inserter(out), [i](const ContentProviderEntry& entry) {
return std::make_pair(ContentProviderUnionSlot(i), entry);
});
}
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry(u64 title_id, ContentRecordType type) const {
for (size_t i = 0; i < providers.size(); ++i) {
auto const& e = providers[i];
if (e != nullptr && e->HasEntry(title_id, type))
return {ContentProviderUnionSlot(i)};
}
return std::nullopt;
}
const ExternalContentProvider* ContentProviderUnion::GetExternalProvider() const {
return static_cast<const ExternalContentProvider*>(providers[size_t(ContentProviderUnionSlot::External)]);
return nullptr;
}
ManualContentProvider::~ManualContentProvider() = default;
void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type, u64 title_id, VirtualFile file) {
void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
u64 title_id, VirtualFile file) {
entries.insert_or_assign({title_type, content_type, title_id}, file);
}
@@ -971,13 +1004,14 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec
u64 title_id, u32 version,
const std::string& version_string, VirtualFile file) {
if (title_type == TitleType::Update) {
auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(), [title_id, version](const ExternalUpdateEntry& entry) {
return entry.title_id == title_id && entry.version == version;
});
auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(),
[title_id, version](const ExternalUpdateEntry& entry) {
return entry.title_id == title_id && entry.version == version;
});
if (it != multi_version_entries.end()) {
// Update existing entry
it->files[size_t(content_type)] = file;
it->files[content_type] = file;
if (!version_string.empty()) {
it->version_string = version_string;
}
@@ -987,7 +1021,7 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec
new_entry.title_id = title_id;
new_entry.version = version;
new_entry.version_string = version_string;
new_entry.files[size_t(content_type)] = file;
new_entry.files[content_type] = file;
multi_version_entries.push_back(new_entry);
}
@@ -1084,19 +1118,26 @@ std::vector<ExternalUpdateEntry> ManualContentProvider::ListUpdateVersions(u64 t
VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const {
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id && entry.version == version) {
if (auto const p = entry.files[size_t(type)])
return p;
auto it = entry.files.find(type);
if (it != entry.files.end()) {
return it->second;
}
}
}
return nullptr;
}
bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
size_t count = 0;
for (const auto& entry : multi_version_entries)
if (entry.title_id == title_id && entry.files[size_t(type)])
++count;
return count > 0;
int count = 0;
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id && entry.files.count(type) > 0) {
count++;
if (count > 1) {
return true;
}
}
}
return false;
}
ExternalContentProvider::ExternalContentProvider(std::vector<VirtualDir> load_directories)
@@ -1215,7 +1256,7 @@ void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
}
}
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
std::map<std::pair<u64, u32>, std::map<ContentRecordType, VirtualFile>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
@@ -1236,7 +1277,7 @@ void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
version = ver_it->second;
}
version_files[{title_id, version}][size_t(content_type)] = nca_file;
version_files[{title_id, version}][content_type] = nca_file;
}
LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}",
@@ -1257,7 +1298,9 @@ void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
existing.files = files_map;
for (const auto& [content_type, _file] : files_map) {
existing.files[content_type] = _file;
}
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
@@ -1340,7 +1383,7 @@ void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
}
}
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
std::map<std::pair<u64, u32>, std::map<ContentRecordType, VirtualFile>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
@@ -1361,7 +1404,7 @@ void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
version = ver_it->second;
}
version_files[{title_id, version}][size_t(content_type)] = nca_file;
version_files[{title_id, version}][content_type] = nca_file;
}
}
}
@@ -1379,7 +1422,9 @@ void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
existing.files = files_map;
for (const auto& [content_type, _file] : files_map) {
existing.files[content_type] = _file;
}
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
@@ -1484,19 +1529,28 @@ std::vector<ExternalUpdateEntry> ExternalContentProvider::ListUpdateVersions(u64
}
VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const {
for (const auto& entry : multi_version_entries)
if (entry.title_id == title_id && entry.version == version)
if (auto const p = entry.files[size_t(type)])
return p;
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id && entry.version == version) {
auto it = entry.files.find(type);
if (it != entry.files.end()) {
return it->second;
}
}
}
return nullptr;
}
bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
size_t count = 0;
for (const auto& entry : multi_version_entries)
if (entry.title_id == title_id && entry.files[size_t(type)])
++count;
return count > 1;
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id && entry.files.count(type) > 0) {
count++;
if (count > 1) {
return true;
}
}
}
return false;
}
} // namespace FileSys
+17 -16
View File
@@ -11,19 +11,19 @@
#include <memory>
#include <string>
#include <vector>
#include <ankerl/unordered_dense.h>
#include <boost/container/flat_map.hpp>
#include "common/common_types.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/vfs/vfs.h"
#include "core/file_sys/nca_metadata.h"
namespace FileSys {
class ExternalContentProvider;
class CNMT;
class ExternalContentProvider;
class CNMT;
class NCA;
class NSP;
class XCI;
enum class ContentRecordType : u8;
enum class NCAContentType : u8;
enum class TitleType : u8;
@@ -56,7 +56,7 @@ struct ExternalUpdateEntry {
u64 title_id;
u32 version;
std::string version_string;
std::array<VirtualFile, size_t(ContentRecordType::Count)> files;
std::map<ContentRecordType, VirtualFile> files;
};
constexpr u64 GetUpdateTitleID(u64 base_title_id) {
@@ -207,11 +207,11 @@ private:
ContentProviderParsingFunction parser;
// maps tid -> NcaID of meta
ankerl::unordered_dense::map<u64, NcaID> meta_id;
std::map<u64, NcaID> meta_id;
// maps tid -> meta
ankerl::unordered_dense::map<u64, CNMT> meta;
std::map<u64, CNMT> meta;
// maps tid -> meta for CNMT in yuzu_meta
ankerl::unordered_dense::map<u64, CNMT> yuzu_meta;
std::map<u64, CNMT> yuzu_meta;
};
enum class ContentProviderUnionSlot {
@@ -220,7 +220,6 @@ enum class ContentProviderUnionSlot {
SDMC, ///< SD Card
FrontendManual, ///< Frontend-defined game list or similar
External, ///< External content from NSP/XCI files in configured directories
Count,
};
// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.
@@ -229,6 +228,8 @@ public:
~ContentProviderUnion() override;
void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider);
void ClearSlot(ContentProviderUnionSlot slot);
void Refresh() override;
bool HasEntry(u64 title_id, ContentRecordType type) const override;
std::optional<u32> GetEntryVersion(u64 title_id) const override;
@@ -240,18 +241,18 @@ public:
std::optional<u64> title_id) const override;
const ExternalContentProvider* GetExternalProvider() const;
[[nodiscard]] inline const ContentProvider* GetSlotProvider(ContentProviderUnionSlot slot) const {
return providers[size_t(slot)];
}
const ContentProvider* GetSlotProvider(ContentProviderUnionSlot slot) const;
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ListEntriesFilterOrigin(
std::optional<ContentProviderUnionSlot> origin = {},
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
std::optional<u64> title_id = {}) const;
std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id, ContentRecordType type) const;
std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id,
ContentRecordType type) const;
private:
std::array<ContentProvider*, size_t(ContentProviderUnionSlot::Count)> providers;
std::map<ContentProviderUnionSlot, ContentProvider*> providers;
};
class ManualContentProvider : public ContentProvider {
@@ -311,8 +312,8 @@ private:
void ProcessXCI(const VirtualFile& file);
std::vector<VirtualDir> load_dirs;
ankerl::unordered_dense::map<std::tuple<u64, ContentRecordType, TitleType>, VirtualFile> entries;
ankerl::unordered_dense::map<u64, u32> versions;
std::map<std::tuple<u64, ContentRecordType, TitleType>, VirtualFile> entries;
std::map<u64, u32> versions;
std::vector<ExternalUpdateEntry> multi_version_entries;
};
+2 -7
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -52,9 +52,7 @@ constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
} // Anonymous namespace
RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {}
RealVfsFilesystem::~RealVfsFilesystem() {
in_dtor = true;
}
RealVfsFilesystem::~RealVfsFilesystem() = default;
std::string RealVfsFilesystem::GetName() const {
return "Real";
@@ -226,9 +224,6 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri
}
void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) {
if (in_dtor)
return;
std::scoped_lock lk{list_lock};
// Remove from list.
-5
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -55,8 +52,6 @@ private:
ReferenceListType closed_references;
std::mutex list_lock;
size_t num_open_files{};
// TODO: Workaround for improper dtor() ordering on clang + FreeBSD
bool in_dtor = false;
private:
friend class RealVfsFile;
+15 -24
View File
@@ -1,16 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cstring>
#include <regex>
#include <string>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <mbedtls/md.h>
#include <mbedtls/sha256.h>
#include "common/fs/path_util.h"
#include "common/hex_util.h"
@@ -30,24 +31,19 @@ constexpr u64 NAX_HEADER_PADDING_SIZE = 0x4000;
template <typename SourceData, typename SourceKey, typename Destination>
static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t key_length,
const SourceData* data, std::size_t data_length) {
size_t out_len = 0;
mbedtls_md_context_t context;
mbedtls_md_init(&context);
static EVP_MAC* mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
if (!mac) return false;
static EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(mac);
if (!ctx) return false;
static OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string("digest", (char*)"SHA256", 0),
OSSL_PARAM_construct_end()
};
if (!EVP_MAC_init(ctx, reinterpret_cast<const unsigned char*>(key), key_length, params))
if (mbedtls_md_setup(&context, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1) ||
mbedtls_md_hmac_starts(&context, reinterpret_cast<const u8*>(key), key_length) ||
mbedtls_md_hmac_update(&context, reinterpret_cast<const u8*>(data), data_length) ||
mbedtls_md_hmac_finish(&context, reinterpret_cast<u8*>(out))) {
mbedtls_md_free(&context);
return false;
}
return EVP_MAC_update(ctx, reinterpret_cast<const unsigned char*>(data), data_length) &&
EVP_MAC_final(ctx, reinterpret_cast<unsigned char*>(out), &out_len, 32);
mbedtls_md_free(&context);
return true;
}
NAX::NAX(VirtualFile file_)
@@ -72,12 +68,7 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
: header(std::make_unique<NAXHeader>()),
file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
Core::Crypto::SHA256Hash hash{};
u32 hash_len = 0;
EVP_Digest(nca_id.data(), nca_id.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);
LOG_DEBUG(Loader, "Decoded {} bytes, nca id {}", hash_len, nca_id);
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
Common::HexToString(nca_id, false)));
}
+1 -3
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -8,6 +5,7 @@
#include <array>
#include <cctype>
#include <mbedtls/md5.h>
#include "core/hle/service/bcat/bcat_result.h"
#include "core/hle/service/bcat/bcat_types.h"
@@ -1,11 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <openssl/err.h>
#include <openssl/evp.h>
#include "common/string_util.h"
#include "core/file_sys/vfs/vfs_types.h"
#include "core/hle/service/bcat/bcat_result.h"
@@ -20,10 +18,7 @@ namespace Service::BCAT {
static BcatDigest DigestFile(const FileSys::VirtualFile& file) {
BcatDigest out{};
const auto bytes = file->ReadAllBytes();
u32 hash_len = 0;
EVP_Digest(bytes.data(), bytes.size(), out.data(), &hash_len, EVP_md5(), nullptr);
mbedtls_md5(bytes.data(), bytes.size(), out.data());
return out;
}
+3 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -444,9 +444,9 @@ void CmifReplyWrapImpl(HLERequestContext& ctx, T& t, Result (T::*f)(A...)) {
const bool _is_domain = _mgr ? _mgr->IsDomain() : false;
ASSERT_MSG(!_is_domain,
"Non-domain reply used on domain session\n"
"Service={} (TIPC={} CmdType={} Cmd=0x{:08X}\n"
"Service={} (type={})\nTIPC={} CmdType={} Cmd=0x{:08X}\n"
"HasDomainHeader={} DomainHandlers={}\nDesc={}",
t.GetServiceName(), ctx.IsTipc(),
t.GetServiceName(), typeid(T).name(), ctx.IsTipc(),
static_cast<u32>(ctx.GetCommandType()), static_cast<u32>(ctx.GetCommand()),
ctx.HasDomainMessageHeader(), _mgr ? static_cast<u32>(_mgr->DomainHandlerCount()) : 0u,
ctx.Description());
+6 -3
View File
@@ -56,9 +56,6 @@ public:
}
return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)];
}
void InstructionSynchronizationBarrierRaised() override {
last_code_addr = 0; //reset back, force refetch
}
u8 MemoryRead8(u64 vaddr) override {
return ReadMemory<u8>(vaddr);
}
@@ -119,6 +116,7 @@ public:
void CallSVC(u32 swi) override;
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override;
void InterpreterFallback(u64 pc, size_t num_instructions) override;
void AddTicks(u64 ticks) override {}
u64 GetTicksRemaining() override {
@@ -434,6 +432,11 @@ void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception excep
parent.jit->HaltExecution();
}
void DynarmicCallbacks64::InterpreterFallback(u64 pc, size_t num_instructions) {
LOG_CRITICAL(Service_JIT, "Unimplemented instruction PC @ {:08x}", pc);
parent.jit->HaltExecution();
}
JITContext::JITContext(Core::Memory::Memory& memory)
: impl{std::make_unique<JITContextImpl>(memory)} {}
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -8,9 +5,8 @@
// SPDX-License-Identifier: MIT
#include <array>
#include <openssl/evp.h>
#include <openssl/core_names.h>
#include <mbedtls/aes.h>
#include <mbedtls/hmac_drbg.h>
#include "common/fs/file.h"
#include "common/fs/fs.h"
@@ -183,7 +179,7 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
return output;
}
void CryptoInit(CryptoCtx& ctx, EVP_MAC_CTX* hmac_ctx, const HmacKey& hmac_key,
void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
std::span<const u8> seed) {
// Initialize context
ctx.used = false;
@@ -192,17 +188,15 @@ void CryptoInit(CryptoCtx& ctx, EVP_MAC_CTX* hmac_ctx, const HmacKey& hmac_key,
memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size());
// Initialize HMAC context
OSSL_PARAM params[2];
params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, (char*)"SHA256", 0);
params[1] = OSSL_PARAM_construct_end();
EVP_MAC_init(hmac_ctx, hmac_key.data(), hmac_key.size(), params);
mbedtls_md_init(&hmac_ctx);
mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size());
}
void CryptoStep(CryptoCtx& ctx, EVP_MAC_CTX* hmac_ctx, DrgbOutput& output) {
void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) {
// If used at least once, reinitialize the HMAC
if (ctx.used) {
EVP_MAC_init(hmac_ctx, nullptr, 0, nullptr);
mbedtls_md_hmac_reset(&hmac_ctx);
}
ctx.used = true;
@@ -213,10 +207,9 @@ void CryptoStep(CryptoCtx& ctx, EVP_MAC_CTX* hmac_ctx, DrgbOutput& output) {
ctx.counter++;
// Do HMAC magic
size_t out_len = 0;
EVP_MAC_update(hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()),
ctx.buffer_size);
EVP_MAC_final(hmac_ctx, output.data(), &out_len, output.size());
mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()),
ctx.buffer_size);
mbedtls_md_hmac_finish(&hmac_ctx, output.data());
}
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
@@ -227,9 +220,7 @@ DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
// Initialize context
CryptoCtx ctx{};
EVP_MAC* mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
EVP_MAC_CTX* hmac_ctx = EVP_MAC_CTX_new(mac);
mbedtls_md_context_t hmac_ctx;
CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key);
// Generate derived keys
@@ -240,25 +231,26 @@ DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys));
// Cleanup context
EVP_MAC_CTX_free(hmac_ctx);
EVP_MAC_free(mac);
mbedtls_md_free(&hmac_ctx);
return derived_keys;
}
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) {
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_128_ctr(), nullptr, keys.aes_key.data(), keys.aes_iv.data());
mbedtls_aes_context aes;
std::size_t nc_off = 0;
std::array<u8, sizeof(keys.aes_iv)> nonce_counter{};
std::array<u8, sizeof(keys.aes_iv)> stream_block{};
const auto aes_key_size = static_cast<u32>(keys.aes_key.size() * 8);
mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size);
memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv));
constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START;
int out_len1 = 0;
int out_len2 = 0;
EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(&out_data.settings), &out_len1,
reinterpret_cast<const unsigned char*>(&in_data.settings), encrypted_data_size);
EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(&out_data.settings) + out_len1, &out_len2);
EVP_CIPHER_CTX_free(ctx);
mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(),
stream_block.data(),
reinterpret_cast<const unsigned char*>(&in_data.settings),
reinterpret_cast<unsigned char*>(&out_data.settings));
// Copy the rest of the data directly
out_data.uid = in_data.uid;
@@ -325,18 +317,16 @@ bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& t
// Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
size_t out_len = 0;
EVP_Q_mac(nullptr, "HMAC", nullptr, "SHA256", nullptr,
tag_keys.hmac_key.data(), sizeof(HmacKey),
reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length,
reinterpret_cast<unsigned char*>(&tag_data.hmac_tag), sizeof(tag_data.hmac_tag), &out_len);
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
// Regenerate data HMAC
constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START;
EVP_Q_mac(nullptr, "HMAC", nullptr, "SHA256", nullptr,
data_keys.hmac_key.data(), sizeof(HmacKey),
reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2,
reinterpret_cast<unsigned char*>(&tag_data.hmac_data), sizeof(tag_data.hmac_data), &out_len);
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(),
sizeof(HmacKey),
reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2,
reinterpret_cast<unsigned char*>(&tag_data.hmac_data));
if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) {
LOG_ERROR(Service_NFP, "hmac_data doesn't match");
@@ -364,33 +354,31 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t
const auto tag_keys = GenerateKey(locked_secret, tag_data);
NTAG215File encoded_tag_data{};
size_t out_len = 0;
// Generate tag HMAC
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
EVP_Q_mac(nullptr, "HMAC", nullptr, "SHA256", nullptr,
tag_keys.hmac_key.data(), sizeof(HmacKey),
reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length,
reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), sizeof(encoded_tag_data.hmac_tag), &out_len);
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
// Init OpenSSL HMAC context
EVP_MAC* mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(mac);
OSSL_PARAM params[2];
params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, (char*)"SHA256", 0);
params[1] = OSSL_PARAM_construct_end();
// Init mbedtls HMAC context
mbedtls_md_context_t ctx;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
// Generate data HMAC
EVP_MAC_init(ctx, data_keys.hmac_key.data(), sizeof(HmacKey), params);
EVP_MAC_update(ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2); // data
EVP_MAC_update(ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), sizeof(HashData)); // tag hmax
EVP_MAC_update(ctx, reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length);
EVP_MAC_final(ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data), &out_len, sizeof(encoded_tag_data.hmac_data));
mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey));
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
input_length2); // Data
mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
sizeof(HashData)); // Tag HMAC
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uid),
input_length);
mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
// HMAC cleanup
EVP_MAC_CTX_free(ctx);
EVP_MAC_free(mac);
mbedtls_md_free(&ctx);
// Encrypt
Cipher(data_keys, tag_data, encoded_tag_data);
@@ -1,18 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <span>
#include <openssl/err.h>
#include "core/hle/service/nfp/nfp_types.h"
typedef struct evp_mac_ctx_st EVP_MAC_CTX;
struct mbedtls_md_context_t;
namespace Service::NFP::AmiiboCrypto {
// Byte locations in Service::NFP::NTAG215File
@@ -78,12 +73,12 @@ HashSeed GetSeed(const NTAG215File& data);
// Middle step on the generation of derived keys
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed);
// Initializes OpenSSL HMAC context
void CryptoInit(CryptoCtx& ctx, EVP_MAC_CTX* hmac_ctx, const HmacKey& hmac_key,
// Initializes mbedtls context
void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
std::span<const u8> seed);
// Feeds data to OpenSSL context to generate the derived key
void CryptoStep(CryptoCtx& ctx, EVP_MAC_CTX* hmac_ctx, DrgbOutput& output);
// Feeds data to mbedtls context to generate the derived key
void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output);
// Generates the derived key from amiibo data
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data);
+5 -6
View File
@@ -1,21 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <openssl/err.h>
#include <openssl/evp.h>
#include <mbedtls/sha256.h>
#include "common/scope_exit.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ro/ro.h"
#include "core/hle/service/ro/ro_nro_utils.h"
#include "core/hle/service/ro/ro_results.h"
#include "core/hle/service/ro/ro_types.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::RO {
@@ -181,8 +181,7 @@ struct ProcessContext {
std::vector<u8> nro_data(size);
m_process->GetMemory().ReadBlock(base_address, nro_data.data(), size);
u32 hash_len = 0;
EVP_Digest(nro_data.data(), nro_data.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);
mbedtls_sha256(nro_data.data(), size, hash.data(), 0);
}
for (size_t i = 0; i < MaxNrrInfos; i++) {
+10 -18
View File
@@ -1,12 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
#include <openssl/err.h>
#include <openssl/evp.h>
#include "common/hex_util.h"
#include "common/scope_exit.h"
@@ -19,6 +17,7 @@
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nca.h"
#include "mbedtls/sha256.h"
#include "common/literals.h"
namespace Loader {
@@ -134,8 +133,9 @@ ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)>
const auto name = file->GetName();
// We won't try to verify meta NCAs.
if (name.ends_with(".cnmt.nca"))
if (name.ends_with(".cnmt.nca")) {
return ResultStatus::Success;
}
// Check if we can verify this file. NCAs should be named after their hashes.
if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) {
@@ -151,18 +151,15 @@ ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)>
std::vector<u8> buffer(4_MiB);
// Initialize sha256 verification context.
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
if (!ctx)
return ResultStatus::ErrorNotInitialized;
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0);
// Ensure we maintain a clean state on exit.
SCOPE_EXIT {
EVP_MD_CTX_free(ctx);
mbedtls_sha256_free(&ctx);
};
if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr))
return ResultStatus::ErrorIntegrityVerificationFailed;
// Declare counters.
const size_t total_size = file->GetSize();
size_t processed_size = 0;
@@ -174,9 +171,7 @@ ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)>
const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size);
// Update the hash function with the buffer contents.
if (!EVP_DigestUpdate(ctx, buffer.data(), read_size)) {
return ResultStatus::ErrorIntegrityVerificationFailed;
}
mbedtls_sha256_update(&ctx, buffer.data(), read_size);
// Update counters.
processed_size += read_size;
@@ -189,10 +184,7 @@ ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)>
// Finalize context and compute the output hash.
std::array<u8, NcaSha256HashLength> output_hash;
unsigned int output_len = 0;
if (!EVP_DigestFinal_ex(ctx, output_hash.data(), &output_len)) {
return ResultStatus::ErrorIntegrityVerificationFailed;
}
mbedtls_sha256_finish(&ctx, output_hash.data());
// Compare to expected.
if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) {
+101 -83
View File
@@ -6,7 +6,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <bit>
#include <cstring>
#include <mutex>
#include <span>
@@ -128,14 +127,83 @@ struct Memory::Impl {
}
}
[[nodiscard]] inline u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const {
auto const paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
return paddr ? system.DeviceMemory().GetPointer<u8>(paddr + vaddr) : nullptr;
[[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const {
Common::PhysicalAddress const paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
if (paddr)
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
return {};
}
[[nodiscard]] inline u8* GetPointerFromDebugMemory(u64 vaddr) const {
auto const paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
return paddr ? system.DeviceMemory().GetPointer<u8>(paddr + vaddr) : nullptr;
[[nodiscard]] u8* GetPointerFromDebugMemory(u64 vaddr) const {
const Common::PhysicalAddress paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
if (paddr != 0)
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
return {};
}
u8 Read8(const Common::ProcessAddress addr) {
return Read<u8>(addr);
}
u16 Read16(const Common::ProcessAddress addr) {
if ((addr & 1) == 0) {
return Read<u16_le>(addr);
} else {
const u32 a{Read<u8>(addr)};
const u32 b{Read<u8>(addr + sizeof(u8))};
return static_cast<u16>((b << 8) | a);
}
}
u32 Read32(const Common::ProcessAddress addr) {
if ((addr & 3) == 0) {
return Read<u32_le>(addr);
} else {
const u32 a{Read16(addr)};
const u32 b{Read16(addr + sizeof(u16))};
return (b << 16) | a;
}
}
u64 Read64(const Common::ProcessAddress addr) {
if ((addr & 7) == 0) {
return Read<u64_le>(addr);
} else {
const u32 a{Read32(addr)};
const u32 b{Read32(addr + sizeof(u32))};
return (static_cast<u64>(b) << 32) | a;
}
}
void Write8(const Common::ProcessAddress addr, const u8 data) {
Write<u8>(addr, data);
}
void Write16(const Common::ProcessAddress addr, const u16 data) {
if ((addr & 1) == 0) {
Write<u16_le>(addr, data);
} else {
Write<u8>(addr, static_cast<u8>(data));
Write<u8>(addr + sizeof(u8), static_cast<u8>(data >> 8));
}
}
void Write32(const Common::ProcessAddress addr, const u32 data) {
if ((addr & 3) == 0) {
Write<u32_le>(addr, data);
} else {
Write16(addr, static_cast<u16>(data));
Write16(addr + sizeof(u16), static_cast<u16>(data >> 16));
}
}
void Write64(const Common::ProcessAddress addr, const u64 data) {
if ((addr & 7) == 0) {
Write<u64_le>(addr, data);
} else {
Write32(addr, static_cast<u32>(data));
Write32(addr + sizeof(u32), static_cast<u32>(data >> 32));
}
}
bool WriteExclusive8(const Common::ProcessAddress addr, const u8 data, const u8 expected) {
@@ -590,7 +658,7 @@ struct Memory::Impl {
}
template<typename F, typename G>
[[nodiscard]] inline u8* GetPointerImpl(u64 vaddr, F&& on_unmapped, G&& on_rasterizer) const {
[[nodiscard]] u8* GetPointerImpl(u64 vaddr, F&& on_unmapped, G&& on_rasterizer) const {
// AARCH64 masks the upper 16 bit of all memory accesses
vaddr &= 0xffffffffffffULL;
if (AddressSpaceContains(*current_page_table, vaddr, 1)) [[likely]] {
@@ -645,42 +713,18 @@ struct Memory::Impl {
/// @returns The instance of T read from the specified virtual address.
template <typename T>
inline T Read(Common::ProcessAddress vaddr) noexcept requires(std::is_trivially_copyable_v<T>) {
auto const addr_c1 = GetInteger(vaddr);
if (!(sizeof(T) > 1 && (addr_c1 & 4095) + sizeof(T) > 4096)) {
if (auto const ptr_c1 = GetPointerImpl(addr_c1, [addr_c1] {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr_c1);
}, [&] {
HandleRasterizerDownload(addr_c1, sizeof(T));
}); ptr_c1) {
// It may be tempting to rewrite this particular section to use "reinterpret_cast";
// afterall, it's trivially copyable so surely it can be copied ov- Alignment.
// Remember, alignment. memcpy() will deal with all the alignment extremely fast.
T result{};
std::memcpy(&result, ptr_c1, sizeof(T));
return result;
}
} else {
auto const addr_c2 = (addr_c1 & (~0xfff)) + 0x1000;
// page crossing: say if sizeof(T) = 2, vaddr = 4095
// 4095 + 2 mod 4096 = 1 => 2 - 1 = 1, thus c1=1, c2=1
auto const count_c2 = (addr_c1 + sizeof(T)) & 4095;
auto const count_c1 = sizeof(T) - count_c2;
if (auto const ptr_c1 = GetPointerImpl(addr_c1, [addr_c1] {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr_c1);
}, [&] {
HandleRasterizerDownload(addr_c1, count_c1);
}); ptr_c1) {
if (auto const ptr_c2 = GetPointerImpl(addr_c2, [addr_c2] {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr_c2);
}, [&] {
HandleRasterizerDownload(addr_c2, count_c2);
}); ptr_c2) {
std::array<char, sizeof(T)> result{};
std::memcpy(result.data() + 0, ptr_c1, count_c1);
std::memcpy(result.data() + count_c1, ptr_c2, count_c2);
return std::bit_cast<T>(result);
}
}
const u64 addr = GetInteger(vaddr);
if (auto const ptr = GetPointerImpl(addr, [addr]() {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr);
}, [&]() {
HandleRasterizerDownload(addr, sizeof(T));
}); ptr) [[likely]] {
// It may be tempting to rewrite this particular section to use "reinterpret_cast";
// afterall, it's trivially copyable so surely it can be copied ov- Alignment.
// Remember, alignment. memcpy() will deal with all the alignment extremely fast.
T result{};
std::memcpy(&result, ptr, sizeof(T));
return result;
}
return T{};
}
@@ -690,37 +734,11 @@ struct Memory::Impl {
/// @tparam T The data type to write to memory.
template <typename T>
inline void Write(Common::ProcessAddress vaddr, const T data) noexcept requires(std::is_trivially_copyable_v<T>) {
auto const addr_c1 = GetInteger(vaddr);
if (!(sizeof(T) > 1 && (addr_c1 & 4095) + sizeof(T) > 4096)) {
if (auto const ptr_c1 = GetPointerImpl(addr_c1, [addr_c1] {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr_c1);
}, [&] {
HandleRasterizerWrite(addr_c1, sizeof(T));
}); ptr_c1) {
std::memcpy(ptr_c1, &data, sizeof(T));
}
} else {
auto const addr_c2 = (addr_c1 & (~0xfff)) + 0x1000;
// page crossing: say if sizeof(T) = 2, vaddr = 4095
// 4095 + 2 mod 4096 = 1 => 2 - 1 = 1, thus c1=1, c2=1
auto const count_c2 = (addr_c1 + sizeof(T)) & 4095;
auto const count_c1 = sizeof(T) - count_c2;
if (auto const ptr_c1 = GetPointerImpl(addr_c1, [addr_c1] {
LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X}", sizeof(T) * 8, addr_c1);
}, [&] {
HandleRasterizerWrite(addr_c1, count_c1);
}); ptr_c1) {
if (auto const ptr_c2 = GetPointerImpl(addr_c2, [addr_c2] {
LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X}", sizeof(T) * 8, addr_c2);
}, [&] {
HandleRasterizerWrite(addr_c2, count_c2);
}); ptr_c2) {
std::array<char, sizeof(T)> tmp = std::bit_cast<std::array<char, sizeof(T)>>(data);
std::memcpy(ptr_c1, tmp.data() + 0, count_c1);
std::memcpy(ptr_c2, tmp.data() + count_c1, count_c2);
}
}
}
const u64 addr = GetInteger(vaddr);
if (auto const ptr = GetPointerImpl(addr, [addr, data]() {
LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, addr, u64(data));
}, [&]() { HandleRasterizerWrite(addr, sizeof(T)); }); ptr) [[likely]]
std::memcpy(ptr, &data, sizeof(T));
}
template <typename T>
@@ -924,35 +942,35 @@ const u8* Memory::GetPointer(Common::ProcessAddress vaddr) const {
}
u8 Memory::Read8(const Common::ProcessAddress addr) {
return impl->Read<u8>(addr);
return impl->Read8(addr);
}
u16 Memory::Read16(const Common::ProcessAddress addr) {
return impl->Read<u16_le>(addr);
return impl->Read16(addr);
}
u32 Memory::Read32(const Common::ProcessAddress addr) {
return impl->Read<u32_le>(addr);
return impl->Read32(addr);
}
u64 Memory::Read64(const Common::ProcessAddress addr) {
return impl->Read<u64_le>(addr);
return impl->Read64(addr);
}
void Memory::Write8(Common::ProcessAddress addr, u8 data) {
impl->Write<u8>(addr, data);
impl->Write8(addr, data);
}
void Memory::Write16(Common::ProcessAddress addr, u16 data) {
impl->Write<u16_le>(addr, data);
impl->Write16(addr, data);
}
void Memory::Write32(Common::ProcessAddress addr, u32 data) {
impl->Write<u32_le>(addr, data);
impl->Write32(addr, data);
}
void Memory::Write64(Common::ProcessAddress addr, u64 data) {
impl->Write<u64_le>(addr, data);
impl->Write64(addr, data);
}
bool Memory::WriteExclusive8(Common::ProcessAddress addr, u8 data, u8 expected) {
+2 -5
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2017 Citra Emulator Project
@@ -16,10 +16,7 @@ if (ENABLE_WEB_SERVICE)
target_link_libraries(yuzu-room PRIVATE web_service)
endif()
target_link_libraries(yuzu-room PRIVATE
OpenSSL::SSL
OpenSSL::Crypto)
target_link_libraries(yuzu-room PRIVATE MbedTLS::mbedcrypto${MBEDTLS_LIB_SUFFIX} MbedTLS::mbedtls${MBEDTLS_LIB_SUFFIX})
if (MSVC)
target_link_libraries(yuzu-room PRIVATE getopt)
endif()
+18 -8
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
@@ -22,7 +22,7 @@
#include <shellapi.h>
#endif
#include <openssl/evp.h>
#include <mbedtls/base64.h>
#include "common/common_types.h"
#include "common/detached_tasks.h"
#include "common/fs/file.h"
@@ -84,11 +84,15 @@ static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
static constexpr char token_delimiter{':'};
static void PadToken(std::string& token) {
std::size_t outlen = 0;
std::array<unsigned char, 512> output{};
std::array<unsigned char, 2048> roundtrip{};
for (size_t i = 0; i < 3; i++) {
EVP_DecodeBlock(output.data(), reinterpret_cast<const unsigned char*>(token.c_str()), token.size());
EVP_EncodeBlock(output.data(), roundtrip.data(), roundtrip.size());
mbedtls_base64_decode(output.data(), output.size(), &outlen,
reinterpret_cast<const unsigned char*>(token.c_str()),
token.length());
mbedtls_base64_encode(roundtrip.data(), roundtrip.size(), &outlen, output.data(), outlen);
if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) {
break;
}
@@ -97,17 +101,23 @@ static void PadToken(std::string& token) {
}
static std::string UsernameFromDisplayToken(const std::string& display_token) {
std::size_t outlen = 4 * ((display_token.length() + 2) / 3);
std::size_t outlen;
std::array<unsigned char, 512> output{};
EVP_DecodeBlock(output.data(), reinterpret_cast<const unsigned char*>(display_token.c_str()), display_token.length());
mbedtls_base64_decode(output.data(), output.size(), &outlen,
reinterpret_cast<const unsigned char*>(display_token.c_str()),
display_token.length());
std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
return decoded_display_token.substr(0, decoded_display_token.find(token_delimiter));
}
static std::string TokenFromDisplayToken(const std::string& display_token) {
std::size_t outlen = 4 * ((display_token.length() + 2) / 3);
std::size_t outlen;
std::array<unsigned char, 512> output{};
EVP_DecodeBlock(output.data(), reinterpret_cast<const unsigned char*>(display_token.c_str()), display_token.length());
mbedtls_base64_decode(output.data(), output.size(), &outlen,
reinterpret_cast<const unsigned char*>(display_token.c_str()),
display_token.length());
std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
return decoded_display_token.substr(decoded_display_token.find(token_delimiter) + 1);
}
@@ -212,7 +212,7 @@ EmittedBlockInfo EmitArm64(oaknut::CodeGenerator& code, IR::Block block, const E
oaknut::Label pass;
pass = conf.emit_cond(code, ctx, ctx.block.GetCondition());
EmitAddCycles(code, ctx, ctx.block.cond_failed_cycle_count);
EmitAddCycles(code, ctx, ctx.block.ConditionFailedCycleCount());
conf.emit_condition_failed_terminal(code, ctx);
code.l(pass);
@@ -254,7 +254,7 @@ EmittedBlockInfo EmitArm64(oaknut::CodeGenerator& code, IR::Block block, const E
reg_alloc.AssertNoMoreUses();
EmitAddCycles(code, ctx, block.cycle_count);
EmitAddCycles(code, ctx, block.CycleCount());
conf.emit_terminal(code, ctx);
code.BRK(0);
@@ -36,6 +36,10 @@ oaknut::Label EmitA32Cond(oaknut::CodeGenerator& code, EmitContext&, IR::Cond co
void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step);
void EmitA32Terminal(oaknut::CodeGenerator&, EmitContext&, IR::Term::Interpret, IR::LocationDescriptor, bool) {
UNREACHABLE();
}
void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) {
EmitRelocation(code, ctx, LinkTarget::ReturnToDispatcher);
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -35,6 +35,10 @@ oaknut::Label EmitA64Cond(oaknut::CodeGenerator& code, EmitContext&, IR::Cond co
void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step);
void EmitA64Terminal(oaknut::CodeGenerator&, EmitContext&, IR::Term::Interpret, IR::LocationDescriptor, bool) {
UNREACHABLE();
}
void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) {
EmitRelocation(code, ctx, LinkTarget::ReturnToDispatcher);
}
@@ -151,7 +151,7 @@ EmittedBlockInfo EmitRV64(biscuit::Assembler& as, IR::Block block, const EmitCon
reg_alloc.AssertNoMoreUses();
if (emit_conf.enable_cycle_counting) {
const size_t cycles_to_add = block.cycle_count;
const size_t cycles_to_add = block.CycleCount();
as.LD(Xscratch0, offsetof(StackLayout, cycles_remaining), sp);
if (mcl::bit::sign_extend<12>(-cycles_to_add) == -cycles_to_add) {
as.ADDI(Xscratch0, Xscratch0, -cycles_to_add);
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -114,6 +114,10 @@ void EmitA32Cond(biscuit::Assembler& as, EmitContext&, IR::Cond cond, biscuit::L
void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step);
void EmitA32Terminal(biscuit::Assembler&, EmitContext&, IR::Term::Interpret, IR::LocationDescriptor, bool) {
UNREACHABLE();
}
void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) {
EmitRelocation(as, ctx, LinkTarget::ReturnFromRunCode);
}
@@ -153,7 +153,7 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
reg_alloc.AssertNoMoreUses();
if (conf.enable_cycle_counting) {
EmitAddCycles(block.cycle_count);
EmitAddCycles(block.CycleCount());
}
EmitTerminal(block.GetTerminal(), ctx.Location().SetSingleStepping(false), ctx.IsSingleStep());
code.int3();
@@ -197,7 +197,7 @@ void A32EmitX64::EmitCondPrelude(const A32EmitContext& ctx) {
Xbyak::Label pass = EmitCond(ctx.block.GetCondition());
if (conf.enable_cycle_counting) {
EmitAddCycles(ctx.block.cond_failed_cycle_count);
EmitAddCycles(ctx.block.ConditionFailedCycleCount());
}
EmitTerminal(IR::Term::LinkBlock{ctx.block.ConditionFailedLocation()}, ctx.Location().SetSingleStepping(false), ctx.IsSingleStep());
code.L(pass);
@@ -1135,6 +1135,19 @@ void A32EmitX64::EmitSetUpperLocationDescriptor(IR::LocationDescriptor new_locat
}
namespace {
void EmitTerminalImpl(A32EmitX64& e, IR::Term::Interpret terminal, IR::LocationDescriptor initial_location, bool) {
ASSERT(A32::LocationDescriptor{terminal.next}.TFlag() == A32::LocationDescriptor{initial_location}.TFlag() && "Unimplemented");
ASSERT(A32::LocationDescriptor{terminal.next}.EFlag() == A32::LocationDescriptor{initial_location}.EFlag() && "Unimplemented");
ASSERT(terminal.num_instructions == 1 && "Unimplemented");
e.code.mov(e.code.ABI_PARAM2.cvt32(), A32::LocationDescriptor{terminal.next}.PC());
e.code.mov(e.code.ABI_PARAM3.cvt32(), 1);
e.code.mov(MJitStateReg(A32::Reg::PC), e.code.ABI_PARAM2.cvt32());
e.code.SwitchMxcsrOnExit();
Devirtualize<&A32::UserCallbacks::InterpreterFallback>(e.conf.callbacks).EmitCall(e.code);
e.code.ReturnFromRunCode(true); // TODO: Check cycles
}
void EmitTerminalImpl(A32EmitX64& e, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) {
e.code.ReturnFromRunCode();
}
@@ -146,7 +146,7 @@ finish_this_inst:
reg_alloc.AssertNoMoreUses();
if (conf.enable_cycle_counting) {
EmitAddCycles(block.cycle_count);
EmitAddCycles(block.CycleCount());
}
EmitTerminal(block.GetTerminal(), ctx.Location().SetSingleStepping(false), ctx.IsSingleStep());
code.int3();
@@ -619,6 +619,16 @@ std::string A64EmitX64::LocationDescriptorToFriendlyName(const IR::LocationDescr
}
namespace {
void EmitTerminalImpl(A64EmitX64& e, IR::Term::Interpret terminal, IR::LocationDescriptor, bool) {
e.code.SwitchMxcsrOnExit();
Devirtualize<&A64::UserCallbacks::InterpreterFallback>(e.conf.callbacks).EmitCall(e.code, [&](RegList param) {
e.code.mov(param[0], A64::LocationDescriptor{terminal.next}.PC());
e.code.mov(qword[e.code.ABI_JIT_PTR + offsetof(A64JitState, pc)], param[0]);
e.code.mov(param[1].cvt32(), terminal.num_instructions);
});
e.code.ReturnFromRunCode(true); // TODO: Check cycles
}
void EmitTerminalImpl(A64EmitX64& e, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) {
e.code.ReturnFromRunCode();
}
@@ -1199,44 +1199,50 @@ void EmitX64::EmitUnsignedDiv64(EmitContext& ctx, IR::Inst* inst) {
void EmitX64::EmitSignedDiv32(EmitContext& ctx, IR::Inst* inst) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
ctx.reg_alloc.ScratchGpr(code, HostLoc::RAX);
ctx.reg_alloc.ScratchGpr(code, HostLoc::RDX);
ctx.reg_alloc.UseScratch(code, args[0], HostLoc::RAX);
auto const divisor = ctx.reg_alloc.UseGpr(code, args[1]).cvt32();
Xbyak::Label end, ok;
const Xbyak::Reg32 dividend = ctx.reg_alloc.UseGpr(code, args[0]).cvt32();
const Xbyak::Reg32 divisor = ctx.reg_alloc.UseScratchGpr(code, args[1]).cvt32();
Xbyak::Label end;
code.xor_(eax, eax);
code.test(divisor, divisor);
code.jz(end, code.T_NEAR);
code.cmp(divisor, u32(-1)); // is sign extended
code.jne(ok, code.T_NEAR);
code.cmp(eax, u32(1ULL << 31));
code.je(end, code.T_NEAR);
code.L(ok);
code.cdq();
code.idiv(divisor);
code.jz(end);
code.movsxd(rax, dividend);
code.movsxd(divisor.cvt64(), divisor);
code.cqo();
code.idiv(divisor.cvt64());
code.L(end);
ctx.reg_alloc.DefineValue(code, inst, eax);
}
void EmitX64::EmitSignedDiv64(EmitContext& ctx, IR::Inst* inst) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
ctx.reg_alloc.ScratchGpr(code, HostLoc::RAX);
ctx.reg_alloc.ScratchGpr(code, HostLoc::RDX);
auto const dividend = ctx.reg_alloc.UseGpr(code, args[0]);
auto const divisor = ctx.reg_alloc.UseGpr(code, args[1]);
const Xbyak::Reg64 dividend = ctx.reg_alloc.UseGpr(code, args[0]);
const Xbyak::Reg64 divisor = ctx.reg_alloc.UseGpr(code, args[1]);
Xbyak::Label end, ok;
code.xor_(eax, eax);
code.test(divisor, divisor);
code.jz(end, code.T_NEAR); // rax = 0, if divisor == 0
code.mov(rdx, u64(-1));
code.cmp(divisor, rdx); // is sign extended
code.jne(ok, code.T_NEAR);
code.mov(rax, u64(1ULL << 63));
code.jz(end);
code.cmp(divisor, 0xffffffff); // is sign extended
code.jne(ok);
code.mov(rax, 0x8000000000000000);
code.cmp(dividend, rax);
code.je(end, code.T_NEAR); // rax = 0x8000_0000 if dividend is same
code.je(end);
code.L(ok);
code.mov(rax, dividend);
code.cqo();
code.idiv(divisor);
code.L(end);
ctx.reg_alloc.DefineValue(code, inst, rax);
}
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
@@ -9,7 +9,7 @@
// TODO: Use source_info?
[[noreturn]] void assert_terminate_impl(const char* s);
#ifndef ASSERT
# define ASSERT(expr) do { auto&& condition = !(expr); if(condition) [[unlikely]] assert_terminate_impl(__FILE__ ": " #expr); } while(0)
# define ASSERT(expr) do if(!(expr)) [[unlikely]] assert_terminate_impl(__FILE__ ": " #expr); while(0)
#endif
#ifndef UNREACHABLE
# ifdef _MSC_VER
@@ -48,7 +48,7 @@ bool IsConditionPassed(TranslatorVisitor& v, IR::Cond cond) {
} else {
if (cond == v.ir.block.GetCondition()) {
v.ir.block.SetConditionFailedLocation(v.ir.current_location.AdvancePC(static_cast<int>(v.current_instruction_size)).AdvanceIT());
v.ir.block.cond_failed_cycle_count++;
v.ir.block.ConditionFailedCycleCount()++;
return true;
}
@@ -79,7 +79,7 @@ bool IsConditionPassed(TranslatorVisitor& v, IR::Cond cond) {
v.cond_state = ConditionalState::Translating;
v.ir.block.SetCondition(cond);
v.ir.block.SetConditionFailedLocation(v.ir.current_location.AdvancePC(static_cast<int>(v.current_instruction_size)).AdvanceIT());
v.ir.block.cond_failed_cycle_count = v.ir.block.cycle_count + 1;
v.ir.block.ConditionFailedCycleCount() = v.ir.block.CycleCount() + 1;
return true;
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -35,6 +35,11 @@ bool TranslatorVisitor::VFPConditionPassed(Cond cond) {
return ArmConditionPassed(cond);
}
bool TranslatorVisitor::InterpretThisInstruction() {
ir.SetTerm(IR::Term::Interpret(ir.current_location));
return false;
}
bool TranslatorVisitor::UnpredictableInstruction() {
return RaiseException(Exception::UnpredictableInstruction);
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -39,6 +39,7 @@ struct TranslatorVisitor final {
bool ThumbConditionPassed();
bool VFPConditionPassed(Cond cond);
bool InterpretThisInstruction();
bool UnpredictableInstruction();
bool UndefinedInstruction();
bool DecodeError();
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -871,11 +871,11 @@ bool TranslatorVisitor::arm_LDMIB(Cond cond, bool W, Reg n, RegList list) {
}
bool TranslatorVisitor::arm_LDM_usr() {
UNREACHABLE();
return InterpretThisInstruction();
}
bool TranslatorVisitor::arm_LDM_eret() {
UNREACHABLE();
return InterpretThisInstruction();
}
static bool STMHelper(A32::IREmitter& ir, bool W, Reg n, RegList list, IR::U32 start_address, IR::U32 writeback_address) {
@@ -956,7 +956,7 @@ bool TranslatorVisitor::arm_STMIB(Cond cond, bool W, Reg n, RegList list) {
}
bool TranslatorVisitor::arm_STM_usr() {
UNREACHABLE();
return InterpretThisInstruction();
}
} // namespace Dynarmic::A32
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -15,7 +15,7 @@ namespace Dynarmic::A32 {
// CPS<effect> <iflags>{, #<mode>}
// CPS #<mode>
bool TranslatorVisitor::arm_CPS() {
UNREACHABLE();
return InterpretThisInstruction();
}
// MRS<c> <Rd>, <spec_reg>
@@ -107,7 +107,7 @@ bool TranslatorVisitor::arm_MSR_reg(Cond cond, unsigned mask, Reg n) {
// RFE{<amode>} <Rn>{!}
bool TranslatorVisitor::arm_RFE() {
UNREACHABLE();
return InterpretThisInstruction();
}
// SETEND <endian_specifier>
@@ -118,7 +118,7 @@ bool TranslatorVisitor::arm_SETEND(bool E) {
// SRS{<amode>} SP{!}, #<mode>
bool TranslatorVisitor::arm_SRS() {
UNREACHABLE();
return InterpretThisInstruction();
}
} // namespace Dynarmic::A32
@@ -61,7 +61,7 @@ void TranslateArm(IR::Block& block, LocationDescriptor descriptor, TranslateCall
}
visitor.ir.current_location = visitor.ir.current_location.AdvancePC(4);
block.cycle_count += ticks_for_instruction;
block.CycleCount() += ticks_for_instruction;
} while (should_continue && CondCanContinue(visitor.cond_state, visitor.ir) && !single_step);
if (visitor.cond_state == ConditionalState::Translating || visitor.cond_state == ConditionalState::Trailing || single_step) {
@@ -101,7 +101,7 @@ bool TranslateSingleArmInstruction(IR::Block& block, LocationDescriptor descript
// TODO: Feedback resulting cond status to caller somehow.
visitor.ir.current_location = visitor.ir.current_location.AdvancePC(4);
block.cycle_count += ticks_for_instruction;
block.CycleCount() += ticks_for_instruction;
block.SetEndLocation(visitor.ir.current_location);
@@ -161,7 +161,7 @@ void TranslateThumb(IR::Block& block, LocationDescriptor descriptor, TranslateCa
}
visitor.ir.current_location = visitor.ir.current_location.AdvancePC(static_cast<int>(visitor.current_instruction_size)).AdvanceIT();
block.cycle_count += ticks_for_instruction;
block.CycleCount() += ticks_for_instruction;
} while (should_continue && CondCanContinue(visitor.cond_state, visitor.ir) && !single_step);
if (visitor.cond_state == ConditionalState::Translating || visitor.cond_state == ConditionalState::Trailing || single_step) {
@@ -214,7 +214,7 @@ bool TranslateSingleThumbInstruction(IR::Block& block, LocationDescriptor descri
const s32 advance_pc = is_thumb_16 ? 2 : 4;
visitor.ir.current_location = visitor.ir.current_location.AdvancePC(advance_pc);
block.cycle_count += ticks_for_instruction;
block.CycleCount() += ticks_for_instruction;
block.SetEndLocation(visitor.ir.current_location);
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -68,16 +68,14 @@ constexpr DecodeTable<V> GetDecodeTable() {
return table;
}
/// In practice it must always suceed, otherwise something else unrelated would have gone awry
template<typename V>
std::reference_wrapper<const Matcher<V>> Decode(u32 instruction) {
std::optional<std::reference_wrapper<const Matcher<V>>> Decode(u32 instruction) {
alignas(64) static const auto table = GetDecodeTable<V>();
const auto& subtable = table[detail::ToFastLookupIndex(instruction)];
auto iter = std::find_if(subtable.begin(), subtable.end(), [instruction](const auto& matcher) {
return matcher.Matches(instruction);
});
DEBUG_ASSERT(iter != subtable.end());
return std::reference_wrapper<const Matcher<V>>(*iter);
return iter != subtable.end() ? std::optional<std::reference_wrapper<const Matcher<V>>>(*iter) : std::nullopt;
}
template<typename V>
@@ -23,14 +23,19 @@ void Translate(IR::Block& block, LocationDescriptor descriptor, MemoryReadCodeFu
bool should_continue = true;
do {
const u64 pc = visitor.ir.current_location->PC();
if (const auto instruction = memory_read_code(pc)) {
auto decoder = Decode<TranslatorVisitor>(*instruction);
should_continue = decoder.get().call(visitor, *instruction);
if (auto decoder = Decode<TranslatorVisitor>(*instruction)) {
should_continue = decoder->get().call(visitor, *instruction);
} else {
should_continue = visitor.InterpretThisInstruction();
}
} else {
should_continue = visitor.RaiseException(Exception::NoExecuteFault);
}
visitor.ir.current_location = visitor.ir.current_location->AdvancePC(4);
block.cycle_count++;
block.CycleCount()++;
} while (should_continue && !single_step);
if (single_step && should_continue) {
@@ -44,11 +49,14 @@ bool TranslateSingleInstruction(IR::Block& block, LocationDescriptor descriptor,
TranslatorVisitor visitor{block, descriptor, {}};
bool should_continue = true;
auto const decoder = Decode<TranslatorVisitor>(instruction);
should_continue = decoder.get().call(visitor, instruction);
if (auto decoder = Decode<TranslatorVisitor>(instruction)) {
should_continue = decoder->get().call(visitor, instruction);
} else {
should_continue = visitor.InterpretThisInstruction();
}
visitor.ir.current_location = visitor.ir.current_location->AdvancePC(4);
block.cycle_count++;
block.CycleCount()++;
block.SetEndLocation(*visitor.ir.current_location);
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -16,6 +16,11 @@
namespace Dynarmic::A64 {
bool TranslatorVisitor::InterpretThisInstruction() {
ir.SetTerm(IR::Term::Interpret(*ir.current_location));
return false;
}
bool TranslatorVisitor::UnpredictableInstruction() {
return RaiseException(Exception::UnpredictableInstruction);
}
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -27,6 +24,7 @@ struct TranslatorVisitor final {
A64::IREmitter ir;
TranslationOptions options;
bool InterpretThisInstruction();
bool UnpredictableInstruction();
bool DecodeError();
bool ReservedValue();
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -66,7 +63,9 @@ bool TranslatorVisitor::FCMLA_vec(bool Q, Imm<2> size, Vec Vm, Imm<2> rot, Vec V
const size_t esize = 8U << size.ZeroExtend();
// TODO: Currently we don't support half-precision floating point
ASSERT(esize != 16);
if (esize == 16) {
return InterpretThisInstruction();
}
const size_t datasize = Q ? 128 : 64;
const size_t num_elements = datasize / esize;
@@ -135,7 +134,9 @@ bool TranslatorVisitor::FCADD_vec(bool Q, Imm<2> size, Vec Vm, Imm<1> rot, Vec V
const size_t esize = 8U << size.ZeroExtend();
// TODO: Currently we don't support half-precision floating point
ASSERT(esize != 16);
if (esize == 16) {
return InterpretThisInstruction();
}
const size_t datasize = Q ? 128 : 64;
const size_t num_elements = datasize / esize;
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -223,7 +223,9 @@ bool TranslatorVisitor::FCMLA_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4
const size_t esize = 8U << size.ZeroExtend();
// TODO: We don't support the half-precision floating point variant yet.
ASSERT(esize != 16);
if (esize == 16) {
return InterpretThisInstruction();
}
const size_t index = [=] {
if (size == 0b01) {
@@ -118,7 +118,7 @@ bool TranslatorVisitor::MSR_reg(Imm<1> o0, Imm<3> op1, Imm<4> CRn, Imm<4> CRm, I
default:
break;
}
UNREACHABLE();
return InterpretThisInstruction();
}
bool TranslatorVisitor::MRS(Imm<1> o0, Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3> op2, Reg Rt) {
@@ -130,7 +130,7 @@ bool TranslatorVisitor::MRS(Imm<1> o0, Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3
case SystemRegisterEncoding::CNTPCT_EL0:
// HACK: Ensure that this is the first instruction in the block it's emitted in, so the cycle count is most up-to-date.
if (!ir.block.instructions.empty() && !options.wall_clock_cntpct) {
ir.block.cycle_count--;
ir.block.CycleCount()--;
ir.SetTerm(IR::Term::LinkBlock{*ir.current_location});
return false;
}
@@ -158,7 +158,7 @@ bool TranslatorVisitor::MRS(Imm<1> o0, Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3
X(64, Rt, ir.GetTPIDRRO());
return true;
}
UNREACHABLE();
return InterpretThisInstruction();
}
} // namespace Dynarmic::A64
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -103,6 +103,9 @@ struct UserCallbacks : public TranslateCallbacks {
// A conservative implementation that always returns false is safe.
virtual bool IsReadOnlyMemory(VAddr /*vaddr*/) { return false; }
/// The interpreter must execute exactly num_instructions starting from PC.
virtual void InterpreterFallback(VAddr pc, size_t num_instructions) = 0;
// This callback is called whenever a SVC instruction is executed.
virtual void CallSVC(std::uint32_t swi) = 0;
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -118,6 +118,9 @@ struct UserCallbacks {
// A conservative implementation that always returns false is safe.
virtual bool IsReadOnlyMemory(VAddr /*vaddr*/) { return false; }
/// The interpreter must execute exactly num_instructions starting from PC.
virtual void InterpreterFallback(VAddr pc, size_t num_instructions) = 0;
// This callback is called whenever a SVC instruction is executed.
virtual void CallSVC(std::uint32_t swi) = 0;
+25 -10
View File
@@ -30,26 +30,38 @@ Block::Block(LocationDescriptor location) noexcept
/// Prepends a new instruction to this basic block before the insertion point,
/// handling any allocations necessary to do so.
/// @param insertion_point Where to insert the new instruction.
/// @param op Opcode representing the instruction to add.
/// @param args A sequence of Value instances used as arguments for the instruction.
/// @param op Opcode representing the instruction to add.
/// @param args A sequence of Value instances used as arguments for the instruction.
/// @returns Iterator to the newly created instruction.
Block::iterator Block::PrependNewInst(Block::const_iterator insertion_point, Opcode opcode, std::initializer_list<Value> args) noexcept {
Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode opcode, std::initializer_list<Value> args) noexcept {
// First try using the "inline" buffer, otherwise fallback to a slower slab-like allocation scheme
// purpouse is to avoid many calls to new/delete which invoke malloc which invokes mmap
// just pool it!!! - reason why there is an inline buffer is because many small blocks are created
// with few instructions due to subpar optimisations on other passes... plus branch-heavy code will
// hugely benefit from the coherency of faster allocations...
auto it = instructions.insert(insertion_point, Inst(opcode));
DEBUG_ASSERT(args.size() == it->NumArgs());
std::for_each(args.begin(), args.end(), [&it, index = size_t(0)](const auto& arg) mutable {
it->SetArg(index, arg);
IR::Inst* inst;
if (inlined_inst.size() < inlined_inst.max_size()) {
inlined_inst.emplace_back(opcode);
inst = &inlined_inst[inlined_inst.size() - 1];
} else {
if (pooled_inst.empty() || pooled_inst.back().size() == pooled_inst.back().max_size())
pooled_inst.emplace_back();
pooled_inst.back().emplace_back(opcode);
inst = &pooled_inst.back()[pooled_inst.back().size() - 1];
}
DEBUG_ASSERT(args.size() == inst->NumArgs());
std::for_each(args.begin(), args.end(), [&inst, index = size_t(0)](const auto& arg) mutable {
inst->SetArg(index, arg);
index++;
});
return it;
return instructions.insert_before(insertion_point, inst);
}
void Block::Reset(LocationDescriptor location_) noexcept {
instructions.clear();
mcl::intrusive_list<IR::Inst> tmp = {};
instructions.swap(tmp);
inlined_inst.clear();
pooled_inst.clear();
cond_failed.reset();
location = location_;
end_location = location_;
@@ -65,6 +77,9 @@ static std::string TerminalToString(const Terminal& terminal_variant) noexcept {
std::string operator()(const Term::Invalid&) const {
return "<invalid terminal>";
}
std::string operator()(const Term::Interpret& terminal) const {
return fmt::format("Interpret{{{}}}", terminal.next);
}
std::string operator()(const Term::ReturnToDispatch&) const {
return "ReturnToDispatch{}";
}
@@ -95,7 +110,7 @@ static std::string TerminalToString(const Terminal& terminal_variant) noexcept {
std::string DumpBlock(const IR::Block& block) noexcept {
std::string ret = fmt::format("Block: location={}-{}\n", block.Location(), block.EndLocation())
+ fmt::format("cycles={}", block.cycle_count)
+ fmt::format("cycles={}", block.CycleCount())
+ fmt::format(", entry_cond={}", A64::CondToString(block.GetCondition()));
if (block.GetCondition() != Cond::AL)
ret += fmt::format(", cond_fail={}", block.ConditionFailedLocation());
+43 -4
View File
@@ -16,6 +16,7 @@
#include <boost/container/container_fwd.hpp>
#include <boost/container/static_vector.hpp>
#include <boost/container/stable_vector.hpp>
#include <mcl/container/intrusive_list.hpp>
#include "dynarmic/common/common_types.h"
#include "dynarmic/ir/location_descriptor.h"
@@ -32,10 +33,10 @@ enum class Opcode;
/// Note that this is a linear IR and not a pure tree-based IR: i.e.: there is an ordering to
/// the microinstructions. This only matters before chaining is done in order to correctly
/// order memory accesses.
class Block final {
class alignas(4096) Block final {
public:
//using instruction_list_type = dense_list<Inst>;
using instruction_list_type = boost::container::stable_vector<Inst>;
using instruction_list_type = mcl::intrusive_list<Inst>;
using size_type = instruction_list_type::size_type;
using iterator = instruction_list_type::iterator;
using const_iterator = instruction_list_type::const_iterator;
@@ -49,9 +50,25 @@ public:
Block(Block&&) = default;
Block& operator=(Block&&) = default;
iterator PrependNewInst(const_iterator insertion_point, Opcode op, std::initializer_list<Value> args) noexcept;
/// Appends a new instruction to the end of this basic block,
/// handling any allocations necessary to do so.
/// @param op Opcode representing the instruction to add.
/// @param args A sequence of Value instances used as arguments for the instruction.
inline iterator AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
return PrependNewInst(instructions.end(), opcode, args);
}
iterator PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args) noexcept;
void Reset(LocationDescriptor location_) noexcept;
/// Gets a mutable reference to the instruction list for this basic block.
inline instruction_list_type& Instructions() noexcept {
return instructions;
}
/// Gets an immutable reference to the instruction list for this basic block.
inline const instruction_list_type& Instructions() const noexcept {
return instructions;
}
/// Gets the starting location for this basic block.
inline LocationDescriptor Location() const noexcept {
return location;
@@ -87,6 +104,15 @@ public:
return cond_failed.has_value();
}
/// Gets a mutable reference to the condition failed cycle count.
inline size_t& ConditionFailedCycleCount() noexcept {
return cond_failed_cycle_count;
}
/// Gets an immutable reference to the condition failed cycle count.
inline const size_t& ConditionFailedCycleCount() const noexcept {
return cond_failed_cycle_count;
}
/// Gets the terminal instruction for this basic block.
inline Terminal GetTerminal() const noexcept {
return terminal;
@@ -106,8 +132,21 @@ public:
return terminal.which() != 0;
}
/// Gets a mutable reference to the cycle count for this basic block.
inline size_t& CycleCount() noexcept {
return cycle_count;
}
/// Gets an immutable reference to the cycle count for this basic block.
inline const size_t& CycleCount() const noexcept {
return cycle_count;
}
/// "Hot cache" for small blocks so we don't call global allocator
boost::container::static_vector<Inst, 30> inlined_inst;
/// List of instructions in this block.
instruction_list_type instructions;
/// "Long/far" memory pool
boost::container::stable_vector<boost::container::static_vector<Inst, 32>> pooled_inst;
/// Block to execute next if `cond` did not pass.
std::optional<LocationDescriptor> cond_failed = {};
/// Description of the starting location of this block
@@ -123,7 +162,7 @@ public:
/// Number of cycles this block takes to execute.
size_t cycle_count = 0;
};
//static_assert(sizeof(Block) == 120);
static_assert(sizeof(Block) == 4096);
/// Returns a string representation of the contents of block. Intended for debugging.
std::string DumpBlock(const IR::Block& block) noexcept;
+10 -7
View File
@@ -70,10 +70,7 @@ enum class MemOp {
/// The user of this class updates `current_location` as appropriate.
class IREmitter {
public:
explicit IREmitter(Block& block) noexcept
: block(block)
, insertion_point(block.instructions.end())
{}
explicit IREmitter(Block& block) : block(block), insertion_point(block.instructions.end()) {}
Block& block;
@@ -2950,10 +2947,19 @@ public:
block.SetTerminal(terminal);
}
void SetInsertionPointBefore(IR::Inst* new_insertion_point) {
insertion_point = IR::Block::iterator{*new_insertion_point};
}
void SetInsertionPointBefore(IR::Block::iterator new_insertion_point) {
insertion_point = new_insertion_point;
}
void SetInsertionPointAfter(IR::Inst* new_insertion_point) {
insertion_point = IR::Block::iterator{*new_insertion_point};
++insertion_point;
}
void SetInsertionPointAfter(IR::Block::iterator new_insertion_point) {
insertion_point = new_insertion_point;
++insertion_point;
@@ -2964,10 +2970,7 @@ protected:
template<typename T = Value, typename... Args>
T Inst(Opcode op, Args... args) {
auto const offset = std::distance(block.instructions.begin(), insertion_point);
auto const at_end = block.instructions.end() == insertion_point;
auto iter = block.PrependNewInst(insertion_point, op, {Value(args)...});
insertion_point = at_end ? block.instructions.end() : block.instructions.begin() + (offset + 1);
return T(Value(&*iter));
}
};
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -10,7 +10,9 @@
#include <array>
#include <mcl/container/intrusive_list.hpp>
#include "dynarmic/common/common_types.h"
#include "dynarmic/ir/value.h"
#include "dynarmic/ir/opcodes.h"
@@ -24,7 +26,7 @@ constexpr size_t max_arg_count = 4;
/// A representation of a microinstruction. A single ARM/Thumb instruction may be
/// converted into zero or more microinstructions.
//class Inst final {
class Inst final {
class Inst final : public mcl::intrusive_list_node<Inst> {
public:
explicit Inst(Opcode op) : op(op) {}
+66 -31
View File
@@ -86,10 +86,12 @@ static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
}
static void FlagsPass(IR::Block& block) {
using Iterator = typename std::reverse_iterator<IR::Block::iterator>;
struct FlagInfo {
bool set_not_required = false;
bool has_value_request = false;
IR::Block::reverse_iterator value_request = {};
Iterator value_request = {};
};
struct ValuelessFlagInfo {
bool set_not_required = false;
@@ -100,7 +102,7 @@ static void FlagsPass(IR::Block& block) {
FlagInfo c_flag;
FlagInfo ge;
auto do_set = [&](FlagInfo& info, IR::Value value, IR::Block::reverse_iterator inst) {
auto do_set = [&](FlagInfo& info, IR::Value value, Iterator inst) {
if (info.has_value_request) {
info.value_request->ReplaceUsesWith(value);
}
@@ -112,14 +114,14 @@ static void FlagsPass(IR::Block& block) {
info.set_not_required = true;
};
auto do_set_valueless = [](ValuelessFlagInfo& info, IR::Block::reverse_iterator inst) {
auto do_set_valueless = [&](ValuelessFlagInfo& info, Iterator inst) {
if (info.set_not_required) {
inst->Invalidate();
}
info.set_not_required = true;
};
auto do_get = [](FlagInfo& info, IR::Block::reverse_iterator inst) {
auto do_get = [](FlagInfo& info, Iterator inst) {
if (info.has_value_request) {
info.value_request->ReplaceUsesWith(IR::Value{&*inst});
}
@@ -446,8 +448,7 @@ static void A64CallbackConfigPass(IR::Block& block, const A64::UserConfig& conf)
return;
}
for (auto it = block.instructions.begin(); it != block.instructions.end(); it++) {
auto& inst = *it;
for (auto& inst : block.instructions) {
if (inst.GetOpcode() != IR::Opcode::A64DataCacheOperationRaised) {
continue;
}
@@ -456,7 +457,7 @@ static void A64CallbackConfigPass(IR::Block& block, const A64::UserConfig& conf)
if (op == A64::DataCacheOperation::ZeroByVA) {
A64::IREmitter ir{block};
ir.current_location = A64::LocationDescriptor{IR::LocationDescriptor{inst.GetArg(0).GetU64()}};
ir.SetInsertionPointBefore(it);
ir.SetInsertionPointBefore(&inst);
size_t bytes = 4 << static_cast<size_t>(conf.dczid_el0 & 0b1111);
IR::U64 addr{inst.GetArg(2)};
@@ -521,7 +522,7 @@ static void A64GetSetElimination(IR::Block& block) {
const auto do_set = [&block](RegisterInfo& info, IR::Value value, Iterator set_inst, TrackingType tracking_type) {
if (info.set_instruction_present) {
info.last_set_instruction->Invalidate();
block.instructions.erase(info.last_set_instruction);
block.Instructions().erase(info.last_set_instruction);
}
info.register_value = value;
info.tracking_type = tracking_type;
@@ -540,7 +541,7 @@ static void A64GetSetElimination(IR::Block& block) {
ReplaceUsesWith(*get_inst, true, u32(info.register_value.GetImmediateAsU64()));
} else {
A64::IREmitter ir{block};
ir.SetInsertionPointBefore(get_inst);
ir.SetInsertionPointBefore(&*get_inst);
get_inst->ReplaceUsesWith(ir.LeastSignificantWord(IR::U64{info.register_value}));
}
} else {
@@ -637,6 +638,43 @@ static void A64GetSetElimination(IR::Block& block) {
}
}
static void A64MergeInterpretBlocksPass(IR::Block& block, A64::UserCallbacks* cb) {
const auto is_interpret_instruction = [cb](A64::LocationDescriptor location) {
const auto instruction = cb->MemoryReadCode(location.PC());
if (!instruction)
return false;
IR::Block new_block{location};
A64::TranslateSingleInstruction(new_block, location, *instruction);
if (!new_block.Instructions().empty())
return false;
const IR::Terminal terminal = new_block.GetTerminal();
if (auto term = boost::get<IR::Term::Interpret>(&terminal)) {
return term->next == location;
}
return false;
};
IR::Terminal terminal = block.GetTerminal();
auto term = boost::get<IR::Term::Interpret>(&terminal);
if (!term)
return;
A64::LocationDescriptor location{term->next};
size_t num_instructions = 1;
while (is_interpret_instruction(location.AdvancePC(static_cast<int>(num_instructions * 4)))) {
num_instructions++;
}
term->num_instructions = num_instructions;
block.ReplaceTerminal(terminal);
block.CycleCount() += num_instructions - 1;
}
using Op = Dynarmic::IR::Opcode;
static IR::Value Value(bool is_32_bit, u64 value) {
@@ -786,30 +824,24 @@ static void FoldCountLeadingZeros(IR::Inst& inst, bool is_32_bit) {
/// Folds division operations based on the following:
///
/// 1. x / 0 -> 0 (NOTE: This is an ARM-specific behavior defined in the architecture reference manual)
/// 2a. 0x8000_0000 / 0xFFFF_FFFF -> 0x8000_0000 (NOTE: More ARM bullshit)
/// 2b. 0x8000_0000_0000_0000 / 0xFFFF_FFFF_FFFF_FFFF -> 0x8000_0000_0000_0000
/// 3. imm_x / imm_y -> result
/// 4. x / 1 -> x
/// 2. imm_x / imm_y -> result
/// 3. x / 1 -> x
///
static void FoldDivide(IR::Inst& inst, bool is_32_bit, bool is_signed) {
const auto rhs = inst.GetArg(1);
if (rhs.IsZero()) {
ReplaceUsesWith(inst, is_32_bit, 0);
return;
}
const auto lhs = inst.GetArg(0);
if (lhs.IsZero() || rhs.IsZero()) {
ReplaceUsesWith(inst, is_32_bit, u64(0));
} else if (!is_32_bit && lhs.IsUnsignedImmediate(u64(1ULL << 63)) && rhs.IsUnsignedImmediate(u64(-1))) {
ReplaceUsesWith(inst, is_32_bit, u64(1ULL << 63));
} else if (is_32_bit && lhs.IsUnsignedImmediate(u32(1ULL << 31)) && rhs.IsUnsignedImmediate(u32(-1))) {
ReplaceUsesWith(inst, is_32_bit, u64(1ULL << 31));
} else if (lhs.IsImmediate() && rhs.IsImmediate()) {
if (lhs.IsImmediate() && rhs.IsImmediate()) {
if (is_signed) {
auto const dl = lhs.GetImmediateAsS64();
auto const dr = rhs.GetImmediateAsS64();
const s64 result = dl / dr;
ReplaceUsesWith(inst, is_32_bit, u64(result));
const s64 result = lhs.GetImmediateAsS64() / rhs.GetImmediateAsS64();
ReplaceUsesWith(inst, is_32_bit, static_cast<u64>(result));
} else {
auto const dl = lhs.GetImmediateAsU64();
auto const dr = rhs.GetImmediateAsU64();
const u64 result = dl / dr;
const u64 result = lhs.GetImmediateAsU64() / rhs.GetImmediateAsU64();
ReplaceUsesWith(inst, is_32_bit, result);
}
} else if (rhs.IsUnsignedImmediate(1)) {
@@ -1209,7 +1241,7 @@ static void IdentityRemovalPass(IR::Block& block) {
}
if (it->GetOpcode() == IR::Opcode::Identity || it->GetOpcode() == IR::Opcode::Void) {
to_invalidate.push_back(&*it);
it = block.instructions.erase(it);
it = block.Instructions().erase(it);
} else {
++it;
}
@@ -1369,9 +1401,9 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
IR::IREmitter ir{block};
for (auto it = block.instructions.begin(); it != block.instructions.end(); it++) {
auto& inst = *it;
ir.SetInsertionPointBefore(it);
for (auto& inst : block.instructions) {
ir.SetInsertionPointBefore(&inst);
switch (inst.GetOpcode()) {
case IR::Opcode::SHA256MessageSchedule0:
if (polyfill.sha256) {
@@ -1472,6 +1504,9 @@ void Optimize(IR::Block& block, const A64::UserConfig& conf, const Optimization:
Optimization::ConstantPropagation(block);
Optimization::DeadCodeElimination(block);
}
if (conf.HasOptimization(OptimizationFlag::MiscIROpt)) {
Optimization::A64MergeInterpretBlocksPass(block, conf.callbacks);
}
Optimization::IdentityRemovalPass(block);
if (!conf.HasOptimization(OptimizationFlag::DisableVerification)) {
Optimization::VerificationPass(block);
+13 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -19,6 +19,17 @@ namespace Term {
struct Invalid {};
/**
* This terminal instruction calls the interpreter, starting at `next`.
* The interpreter must interpret exactly `num_instructions` instructions.
*/
struct Interpret {
explicit Interpret(const LocationDescriptor& next_)
: next(next_) {}
LocationDescriptor next; ///< Location at which interpretation starts.
size_t num_instructions = 1;
};
/**
* This terminal instruction returns control to the dispatcher.
* The dispatcher will use the current cpu state to determine what comes next.
@@ -72,6 +83,7 @@ struct CheckHalt;
/// A Terminal is the terminal instruction in a MicroBlock.
using Terminal = boost::variant<
Invalid,
Interpret,
ReturnToDispatch,
LinkBlock,
LinkBlockFast,
+6
View File
@@ -59,6 +59,8 @@ bool AnyLocationDescriptorForTerminalHas(IR::Terminal terminal, Fn fn) {
return fn(t.next);
} else if constexpr (std::is_same_v<T, IR::Term::PopRSBHint>) {
return false;
} else if constexpr (std::is_same_v<T, IR::Term::Interpret>) {
return fn(t.next);
} else if constexpr (std::is_same_v<T, IR::Term::FastDispatchHint>) {
return false;
} else if constexpr (std::is_same_v<T, IR::Term::If>) {
@@ -83,6 +85,10 @@ bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A
return false;
}
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal)) {
return false;
}
if (AnyLocationDescriptorForTerminalHas(block.GetTerminal(), [&](IR::LocationDescriptor ld) { return A32::LocationDescriptor{ld}.PC() <= pc; })) {
return false;
}
+9 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -97,6 +97,10 @@ public:
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
}
void InterpreterFallback(u32 pc, size_t num_instructions) override {
UNREACHABLE(); //ASSERT(false && "InterpreterFallback({:08x} && {}) code = {:08x}", pc, num_instructions, *MemoryReadCode(pc));
}
void CallSVC(std::uint32_t swi) override {
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
}
@@ -186,6 +190,10 @@ public:
return true;
}
void InterpreterFallback(std::uint32_t pc, size_t num_instructions) override {
UNREACHABLE(); //ASSERT(false && "InterpreterFallback({:016x} && {})", pc, num_instructions);
}
void CallSVC(std::uint32_t swi) override {
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
}
+19 -85
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -13,7 +13,6 @@
#include "../native/testenv.h"
#include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/interface/exclusive_monitor.h"
#include "dynarmic/interface/optimization_flags.h"
using namespace Dynarmic;
using namespace oaknut::util;
@@ -1461,91 +1460,26 @@ TEST_CASE("A64: SQRDMULH QC flag when output invalidated", "[a64]") {
}
TEST_CASE("A64: SDIV maximally", "[a64]") {
// No indication of this overflow case is produced, and the 32-bit result written to
// R[d] must be the bottom 32 bits of the binary representation of +231.
// So the result of the division is 0x80000000.
A64TestEnv env;
A64::UserConfig jit_user_config{};
jit_user_config.callbacks = &env;
A64::Jit jit{jit_user_config};
oaknut::VectorCodeGenerator code{env.code_mem, nullptr};
code.SDIV(X2, X1, X0);
code.SDIV(W5, W4, W3);
env.code_mem.emplace_back(0x9ac00c22); // SDIV X2, X1, X0
env.code_mem.emplace_back(0x14000000); // B .
jit.SetRegister(0, 0xffffffffffffffff);
jit.SetRegister(1, 0x8000000000000000);
jit.SetRegister(2, 0xffffffffffffffff);
jit.SetRegister(3, 0xffffffff);
jit.SetRegister(4, 0x80000000);
jit.SetRegister(5, 0xffffffff);
jit.SetPC(0);
env.ticks_left = env.code_mem.size();
env.ticks_left = 2;
CheckedRun([&]() { jit.Run(); });
REQUIRE(jit.GetRegister(0) == 0xffffffffffffffff);
REQUIRE(jit.GetRegister(1) == 0x8000000000000000);
REQUIRE(jit.GetRegister(2) == 0x8000000000000000);
REQUIRE(jit.GetRegister(5) == 0x80000000);
REQUIRE(jit.GetPC() == 8);
}
TEST_CASE("A64: SDIV maximally (Immediate)", "[a64]") {
A64TestEnv env;
A64::UserConfig jit_user_config{};
jit_user_config.callbacks = &env;
auto const do_sdiv_code = [&] {
A64::Jit jit{jit_user_config};
oaknut::VectorCodeGenerator code{env.code_mem, nullptr};
code.MOVZ(X12, 0xffff);
code.MOVZ(X11, 0x8000);
code.MOVZ(X10, 0x0000);
// 0xffff_ffff
code.MOV(X0, X12);
code.LSL(X0, X0, 16);
code.ORR(X0, X0, X12);
code.LSL(X0, X0, 16);
code.ORR(X0, X0, X12);
code.LSL(X0, X0, 16);
code.ORR(X0, X0, X12);
// 0x8000_0000
code.MOV(X1, X11);
code.LSL(X1, X1, 16);
code.ORR(X1, X1, X10);
code.LSL(X1, X1, 16);
code.ORR(X1, X1, X10);
code.LSL(X1, X1, 16);
code.ORR(X1, X1, X10);
// 0xffff_ffff
code.MOV(X3, X12);
code.LSL(X3, X3, 16);
code.ORR(X3, X3, X12);
// 0x8000_0000
code.MOV(X4, X11);
code.LSL(X4, X4, 16);
code.ORR(X4, X4, X10);
code.SDIV(X2, X1, X0);
code.SDIV(W5, W4, W3);
jit.SetPC(0);
env.ticks_left = env.code_mem.size();
CheckedRun([&]() { jit.Run(); });
REQUIRE(jit.GetRegister(5) == 0x80000000);
};
SECTION("With no opts") {
jit_user_config.optimizations = no_optimizations;
do_sdiv_code();
}
SECTION("With opts + constant folding") {
jit_user_config.optimizations = all_safe_optimizations;
do_sdiv_code();
}
REQUIRE(jit.GetPC() == 4);
}
// Restricted register set required to trigger:
@@ -2425,12 +2359,12 @@ TEST_CASE("A64: RBIT{16b}", "[a64]") {
A64::UserConfig conf{};
conf.callbacks = &env;
A64::Jit jit{conf};
oaknut::VectorCodeGenerator code{env.code_mem, nullptr};
code.RBIT(V1.B16(), V2.B16());
code.RBIT(V2.B16(), V1.B16());
env.code_mem.emplace_back(0x6e605841); // rbit v1.16b, v2.16b
env.code_mem.emplace_back(0x6e605822); // rbit v2.16b, v1.16b
env.code_mem.emplace_back(0x14000000); // b .
jit.SetVector(2, { 0xcafedead, 0xbabebeef });
jit.SetPC(0); // at _start
env.ticks_left = env.code_mem.size();
env.ticks_left = 4;
CheckedRun([&]() { jit.Run(); });
REQUIRE(jit.GetVector(1)[0] == 0x537f7bb5);
REQUIRE(jit.GetVector(1)[1] == 0x5d7d7df7);
@@ -2443,15 +2377,15 @@ TEST_CASE("A64: CLZ{X}", "[a64]") {
A64::UserConfig conf{};
conf.callbacks = &env;
A64::Jit jit{conf};
oaknut::VectorCodeGenerator code{env.code_mem, nullptr};
code.CLZ(X0, X3);
code.CLZ(X1, X4);
code.CLZ(X2, X5);
env.code_mem.emplace_back(0xdac01060); // clz x0, x3
env.code_mem.emplace_back(0xdac01081); // clz x1, x4
env.code_mem.emplace_back(0xdac010a2); // clz x2, x5
env.code_mem.emplace_back(0x14000000); // b .
jit.SetRegister(3, 0xfffffffffffffff0);
jit.SetRegister(4, 0x0fffffff0ffffff0);
jit.SetRegister(5, 0x07fffffeffeffef0);
jit.SetPC(0); // at _start
env.ticks_left = env.code_mem.size();
env.ticks_left = 4;
CheckedRun([&]() { jit.Run(); });
REQUIRE(jit.GetRegister(0) == 0);
REQUIRE(jit.GetRegister(1) == 4);
@@ -2463,15 +2397,15 @@ TEST_CASE("A64: CLZ{W}", "[a64]") {
A64::UserConfig conf{};
conf.callbacks = &env;
A64::Jit jit{conf};
oaknut::VectorCodeGenerator code{env.code_mem, nullptr};
code.CLZ(W0, W3);
code.CLZ(W1, W4);
code.CLZ(W2, W5);
env.code_mem.emplace_back(0x5ac01060); // clz w0, w3
env.code_mem.emplace_back(0x5ac01081); // clz w1, w4
env.code_mem.emplace_back(0x5ac010a2); // clz w2, w5
env.code_mem.emplace_back(0x14000000); // b .
jit.SetRegister(3, 0xffff1110);
jit.SetRegister(4, 0x0fff1110);
jit.SetRegister(5, 0x07fffffe);
jit.SetPC(0); // at _start
env.ticks_left = env.code_mem.size();
env.ticks_left = 4;
CheckedRun([&]() { jit.Run(); });
REQUIRE(jit.GetRegister(0) == 0);
REQUIRE(jit.GetRegister(1) == 4);
+5
View File
@@ -69,6 +69,11 @@ public:
MemoryWrite64(vaddr + 8, value[1]);
}
void InterpreterFallback(u64, size_t) override {
// This is never called in practice.
std::terminate();
}
void CallSVC(u32) override {
// Do something.
}
@@ -43,6 +43,8 @@ static bool ShouldTestInst(u32 instruction, u64 pc, bool is_last_inst) {
bool should_continue = A64::TranslateSingleInstruction(block, location, instruction);
if (!should_continue && !is_last_inst)
return false;
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal))
return false;
for (const auto& ir_inst : block.instructions) {
switch (ir_inst.GetOpcode()) {
case IR::Opcode::A64ExceptionRaised:
+8
View File
@@ -105,6 +105,10 @@ public:
return true;
}
void InterpreterFallback(u64 pc, size_t num_instructions) override {
UNREACHABLE(); // ASSERT(false&& "InterpreterFallback({:016x} && {})", pc, num_instructions);
}
void CallSVC(std::uint32_t swi) override {
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
}
@@ -204,6 +208,10 @@ public:
return true;
}
void InterpreterFallback(u64 pc, size_t num_instructions) override {
ASSERT(ignore_invalid_insn && "InterpreterFallback");
}
void CallSVC(std::uint32_t swi) override {
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
}
+6 -1
View File
@@ -53,7 +53,9 @@ std::string_view GetNameOfA32Instruction(u32 instruction) {
}
std::string_view GetNameOfA64Instruction(u32 instruction) {
return *A64::GetName<A64::TranslatorVisitor>(instruction);
if (auto const decoder = A64::Decode<A64::TranslatorVisitor>(instruction))
return *A64::GetName<A64::TranslatorVisitor>(instruction);
return "<null>";
}
void PrintA32Instruction(u32 instruction) {
@@ -150,6 +152,9 @@ public:
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
}
void InterpreterFallback(u32 pc, size_t num_instructions) override {
fmt::print("> InterpreterFallback({:08x}, {}) code = {:08x}\n", pc, num_instructions, *MemoryReadCode(pc));
}
void CallSVC(std::uint32_t swi) override {
fmt::print("> CallSVC({})\n", swi);
}
+4
View File
@@ -50,6 +50,10 @@ namespace {
using namespace Dynarmic;
bool ShouldTestInst(IR::Block& block) {
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal)) {
return false;
}
for (const auto& ir_inst : block.instructions) {
switch (ir_inst.GetOpcode()) {
// A32
+5 -2
View File
@@ -23,8 +23,11 @@ if (ENABLE_UPDATE_CHECKER)
update_checker.cpp
update_checker.h)
target_compile_definitions(frontend_common PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(frontend_common PRIVATE OpenSSL::SSL OpenSSL::Crypto)
if (ENABLE_OPENSSL)
target_compile_definitions(frontend_common PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(frontend_common PRIVATE OpenSSL::SSL OpenSSL::Crypto)
endif()
endif()
create_target_directory_groups(frontend_common)
+2 -3
View File
@@ -19,10 +19,9 @@ std::vector<std::filesystem::path> GetModFolder(const std::string& root) {
auto callback = [&paths](const std::filesystem::directory_entry& entry) -> bool {
const auto name = entry.path().filename().string();
static const std::array<std::string, 5> valid_names = {"exefs",
"romfs",
"romfs"
"romfs_ext",
"cheats",
"romfslite"};
"cheats", "romfslite"};
if (std::ranges::find(valid_names, name) != valid_names.end()) {
paths.emplace_back(entry.path().parent_path());
+9 -11
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -78,19 +78,17 @@ bool DmaPusher::Step() {
synced = false;
}
if (header.size > 0 && dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) {
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32));
}
if (header.size > 0) {
if (dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) {
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32));
}
auto run = [&]<Tegra::Memory::GuestMemoryFlags Flags>() {
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Flags>
headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
if (Settings::IsDMALevelDefault() ? (Settings::IsGPULevelMedium() || Settings::IsGPULevelHigh()) : Settings::IsDMALevelSafe()) {
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
ProcessCommands(headers);
};
if (Settings::IsDMALevelSafe() || (Settings::IsDMALevelDefault() && !Settings::IsGPULevelLow())) {
run.template operator()<Tegra::Memory::GuestMemoryFlags::SafeRead>();
} else {
run.template operator()<Tegra::Memory::GuestMemoryFlags::UnsafeRead>();
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::UnsafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
ProcessCommands(headers);
}
}
@@ -12,12 +12,14 @@
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/types.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS;
using VideoCore::Surface::IsPixelFormatInteger;
class DescriptorLayoutBuilder {
public:
@@ -191,10 +193,14 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
const VkImageView vk_image_view{image_view.Handle(desc.type)};
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
// Integer formats don't support VK_FILTER_LINEAR in Vulkan.
const bool needs_nearest{sampler.HasLinearFilter() &&
IsPixelFormatInteger(image_view.format)};
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
!image_view.SupportsAnisotropy()};
const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
: sampler.Handle()};
const VkSampler vk_sampler{needs_nearest ? sampler.HandleWithNearestFilter()
: use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
: sampler.Handle()};
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
rescaling.PushTexture(texture_cache.IsRescaling(image_view));
}
@@ -38,6 +38,7 @@ namespace Vulkan {
using Tegra::Engines::Fermi2D;
using Tegra::Texture::SwizzleSource;
using Tegra::Texture::TextureFilter;
using Tegra::Texture::TextureMipmapFilter;
using VideoCommon::BufferImageCopy;
using VideoCommon::ImageFlagBits;
@@ -2356,6 +2357,35 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
if (max_anisotropy > max_anisotropy_default) {
sampler_default_anisotropy = create_sampler(max_anisotropy_default);
}
// Create a nearest-filter fallback for integer format image views.
// but Vulkan requires the format to support linear filtering.
const bool is_mag_linear = tsc.mag_filter == TextureFilter::Linear;
const bool is_min_linear = tsc.min_filter == TextureFilter::Linear;
has_linear_filter = is_mag_linear || is_min_linear;
if (has_linear_filter) {
sampler_nearest = device.GetLogical().CreateSampler(VkSamplerCreateInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = pnext,
.flags = 0,
.magFilter = VK_FILTER_NEAREST,
.minFilter = VK_FILTER_NEAREST,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
.addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter),
.addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter),
.addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter),
.mipLodBias = tsc.LodBias(),
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 1.0f,
.compareEnable = tsc.depth_compare_enabled,
.compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
.minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(),
.maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(),
.borderColor = has_custom_border_colors ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT
: ConvertBorderColor(color),
.unnormalizedCoordinates = VK_FALSE,
});
}
}
Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers,
@@ -408,9 +408,19 @@ public:
return static_cast<bool>(sampler_default_anisotropy);
}
[[nodiscard]] bool HasLinearFilter() const noexcept {
return has_linear_filter;
}
[[nodiscard]] VkSampler HandleWithNearestFilter() const noexcept {
return sampler_nearest ? *sampler_nearest : *sampler;
}
private:
vk::Sampler sampler;
vk::Sampler sampler_default_anisotropy;
vk::Sampler sampler_nearest;
bool has_linear_filter = false;
};
struct TextureCacheParams {

Some files were not shown because too many files have changed in this diff Show More