Compare commits
31 commits
scripts/bu
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 440ee4916d | |||
| 551f244dfd | |||
| ef14303c48 | |||
| b7021afff6 | |||
| bfc10723bc | |||
| 30482692c7 | |||
| 31463142e1 | |||
| bb836ed6c2 | |||
| f273ac446b | |||
| 3f725c979d | |||
| 2a5e6f98b6 | |||
| fff8e2026f | |||
| 5f9dba40a0 | |||
| 0a54ac63f0 | |||
| 94ca83a6ca | |||
| 2f88463203 | |||
| 11200714e8 | |||
| ac59b6eae5 | |||
| b389a72697 | |||
| b3f28d29c0 | |||
| 053f4e95d4 | |||
| 8ae7cfe96a | |||
| e157b3fa96 | |||
| 56e2dbc619 | |||
| a3ef2cc183 | |||
| 1e1b8ad33f | |||
| 91493fa39b | |||
| 973a65c4c5 | |||
| 8a017951aa | |||
| 776958c79d | |||
| 3656253262 |
|
|
@ -1,63 +0,0 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
# SPDX-FileCopyrightText: 2025 eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
QT_VERSION="6.8.3"
|
||||
QT_SRC_DIR="$HOME/qt-src-$QT_VERSION"
|
||||
QT_BUILD_DIR="$HOME/qt-build-$QT_VERSION"
|
||||
QT_INSTALL_DIR="$HOME/qt-clang-$QT_VERSION"
|
||||
CLANG_BIN="/usr/bin/clang"
|
||||
CLANGPP_BIN="/usr/bin/clang++"
|
||||
|
||||
if [ "${INSTALL_DEPS}" = "ON" ]; then
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential perl python3 git \
|
||||
"^libxcb.*" libx11-dev libx11-xcb-dev libxcb-xinerama0-dev \
|
||||
libxcb-keysyms1-dev libxcb-icccm4-dev libxcb-image0-dev \
|
||||
libxkbcommon-dev libxkbcommon-x11-dev libgl-dev libdbus-1-dev \
|
||||
libasound2-dev libpulse-dev libudev-dev libfontconfig1-dev \
|
||||
libcap-dev libssl-dev
|
||||
fi
|
||||
|
||||
if [ ! -d "$QT_SRC_DIR" ]; then
|
||||
mkdir -p "$QT_SRC_DIR"
|
||||
cd "$QT_SRC_DIR"
|
||||
wget https://download.qt.io/archive/qt/6.8/$QT_VERSION/single/qt-everywhere-src-$QT_VERSION.tar.xz
|
||||
tar xf qt-everywhere-src-$QT_VERSION.tar.xz --strip-components=1
|
||||
fi
|
||||
|
||||
mkdir -p "$QT_BUILD_DIR"
|
||||
cd "$QT_BUILD_DIR"
|
||||
|
||||
"$QT_SRC_DIR/configure" \
|
||||
-prefix "$QT_INSTALL_DIR" \
|
||||
-opensource -confirm-license \
|
||||
-nomake examples -nomake tests \
|
||||
-no-pch \
|
||||
-skip qt3d \
|
||||
-skip qtcanvas3d \
|
||||
-skip qtconnectivity \
|
||||
-skip qtdatavis3d \
|
||||
-skip qtdoc \
|
||||
-skip qtgraphicaleffects \
|
||||
-skip qtgamepad \
|
||||
-skip qtquick3d \
|
||||
-skip qtquicktimeline \
|
||||
-skip qtx11extras \
|
||||
-skip qtwebengine \
|
||||
-skip qtgraphs \
|
||||
-skip qtquick3dphysics \
|
||||
-skip qtspeech \
|
||||
-platform linux-clang \
|
||||
-device-option CXX="$CLANGPP_BIN" \
|
||||
-device-option CC="$CLANG_BIN" \
|
||||
-release \
|
||||
-force-debug-info \
|
||||
"CFLAGS=-march=native -mtune=native -O3 -pipe" \
|
||||
"CXXFLAGS=-march=native -mtune=native -O3 -pipe"
|
||||
|
||||
cmake --build . --parallel $(nproc)
|
||||
|
||||
cmake --install .
|
||||
|
||||
13
.patch/mbedtls/0002-aesni-fix.patch
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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 */
|
||||
22
.patch/mbedtls/0003-aesni-fix.patch
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
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)
|
||||
/*
|
||||
55
.patch/mcl/0001-assert-macro.patch
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
diff --git a/include/mcl/assert.hpp b/include/mcl/assert.hpp
|
||||
index f77dbe7..9ec0b9c 100644
|
||||
--- a/include/mcl/assert.hpp
|
||||
+++ b/include/mcl/assert.hpp
|
||||
@@ -23,8 +23,11 @@ template<typename... Ts>
|
||||
|
||||
} // namespace mcl::detail
|
||||
|
||||
+#ifndef UNREACHABLE
|
||||
#define UNREACHABLE() ASSERT_FALSE("Unreachable code!")
|
||||
+#endif
|
||||
|
||||
+#ifndef ASSERT
|
||||
#define ASSERT(expr) \
|
||||
[&] { \
|
||||
if (std::is_constant_evaluated()) { \
|
||||
@@ -37,7 +40,9 @@ template<typename... Ts>
|
||||
} \
|
||||
} \
|
||||
}()
|
||||
+#endif
|
||||
|
||||
+#ifndef ASSERT_MSG
|
||||
#define ASSERT_MSG(expr, ...) \
|
||||
[&] { \
|
||||
if (std::is_constant_evaluated()) { \
|
||||
@@ -50,13 +55,24 @@ template<typename... Ts>
|
||||
} \
|
||||
} \
|
||||
}()
|
||||
+#endif
|
||||
|
||||
+#ifndef ASSERT_FALSE
|
||||
#define ASSERT_FALSE(...) ::mcl::detail::assert_terminate("false", __VA_ARGS__)
|
||||
+#endif
|
||||
|
||||
#if defined(NDEBUG) || defined(MCL_IGNORE_ASSERTS)
|
||||
-# define DEBUG_ASSERT(expr) ASSUME(expr)
|
||||
-# define DEBUG_ASSERT_MSG(expr, ...) ASSUME(expr)
|
||||
+# ifndef DEBUG_ASSERT
|
||||
+# define DEBUG_ASSERT(expr) ASSUME(expr)
|
||||
+# endif
|
||||
+# ifndef DEBUG_ASSERT_MSG
|
||||
+# define DEBUG_ASSERT_MSG(expr, ...) ASSUME(expr)
|
||||
+# endif
|
||||
#else
|
||||
-# define DEBUG_ASSERT(expr) ASSERT(expr)
|
||||
-# define DEBUG_ASSERT_MSG(expr, ...) ASSERT_MSG(expr, __VA_ARGS__)
|
||||
+# ifndef DEBUG_ASSERT
|
||||
+# define DEBUG_ASSERT(expr) ASSERT(expr)
|
||||
+# endif
|
||||
+# ifndef DEBUG_ASSERT_MSG
|
||||
+# define DEBUG_ASSERT_MSG(expr, ...) ASSERT_MSG(expr, __VA_ARGS__)
|
||||
+# endif
|
||||
#endif
|
||||
45
.reuse/dep5
|
|
@ -13,12 +13,13 @@ Copyright: yuzu Emulator Project
|
|||
License: GPL-2.0-or-later
|
||||
|
||||
Files: dist/qt_themes/default/icons/256x256/eden.png
|
||||
dist/qt_themes/default/icons/256x256/eden_named.png
|
||||
dist/yuzu.bmp
|
||||
dist/yuzu.icns
|
||||
dist/eden.icns
|
||||
dist/eden.ico
|
||||
dist/eden.svg
|
||||
Copyright: yuzu Emulator Project
|
||||
License: GPL-2.0-or-later
|
||||
dist/dev.eden_emu.eden.svg
|
||||
Copyright: 2025 Eden Emulator Project
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: dist/qt_themes/qdarkstyle*/LICENSE.*
|
||||
dist/qt_themes/qdarkstyle*/style.qrc
|
||||
|
|
@ -155,3 +156,39 @@ License: BSD-3-Clause
|
|||
Files: src/android/app/debug.keystore
|
||||
Copyright: 2023 yuzu Emulator Project
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: dist/qt_themes/colorful/icons/48x48/user-trash.png
|
||||
dist/qt_themes/colorful/icons/48x48/upload.png
|
||||
dist/qt_themes/colorful/icons/48x48/download.png
|
||||
Copyright: 2014 Uri Herrera
|
||||
1996-2025 KDE Software Foundation
|
||||
License: LGPL-2.0-or-later
|
||||
|
||||
Files: dist/qt_themes/default/icons/48x48/user-trash.png
|
||||
dist/qt_themes/default/icons/48x48/upload.png
|
||||
dist/qt_themes/default/icons/48x48/download.png
|
||||
dist/qt_themes/default_dark/icons/48x48/user-trash.png
|
||||
dist/qt_themes/default_dark/icons/48x48/upload.png
|
||||
dist/qt_themes/default_dark/icons/48x48/download.png
|
||||
Copyright: 2025 Fonticons, Inc.
|
||||
License: CC-BY-4.0
|
||||
Comment: All of these icons have been modified by crueter <crueter@crueter.xyz>
|
||||
|
||||
Files: CMakeModules/CPM.cmake
|
||||
Copyright: 2019-2023 Lars Melchior
|
||||
License: MIT
|
||||
|
||||
Files: CMakeModules/CPMUtil.cmake
|
||||
CMakeModules/CPM.cmake
|
||||
CMakeModules/GetSCMRev.cmake
|
||||
CMakeModules/DetectArchitecture.cmake
|
||||
tools/cpm/*
|
||||
tools/update-cpm.sh
|
||||
tools/shellcheck.sh
|
||||
docs/CPMUtil.md
|
||||
**cpmfile.json
|
||||
Copyright: 2025 crueter <crueter@crueter.xyz>
|
||||
License: GPL-3.0-or-later
|
||||
Comment: CPM.cmake has had additional modifications from crueter to better work with CPMUtil
|
||||
https://git.crueter.xyz/CMake/CPMUtil
|
||||
https://git.crueter.xyz/CMake/Modules
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ if (PLATFORM_SUN)
|
|||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
|
||||
endif()
|
||||
if (CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Needed for FFmpeg w/ VAAPI and DRM
|
||||
|
|
@ -573,11 +577,12 @@ if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
|
|||
find_package(xbyak)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
find_package(httplib)
|
||||
if (ENABLE_WEB_SERVICE OR ENABLE_QT_UPDATE_CHECKER)
|
||||
# Workaround: httplib will kill itself if you attempt to do a find_package propagation
|
||||
# find_package(httplib CONFIG)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE OR ENABLE_QT_UPDATE_CHECKER)
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
find_package(cpp-jwt)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,33 @@
|
|||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# SPDX-FileCopyrightText: 2022 Alexandre Bouvier <contact@amb.tf>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
find_path(DiscordRPC_INCLUDE_DIR discord_rpc.h)
|
||||
find_package(DiscordRPC CONFIG QUIET)
|
||||
|
||||
find_library(DiscordRPC_LIBRARY discord-rpc)
|
||||
if (NOT DiscordRPC_FOUND)
|
||||
find_path(DiscordRPC_INCLUDE_DIR discord_rpc.h)
|
||||
find_library(DiscordRPC_LIBRARY discord-rpc)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(DiscordRPC
|
||||
REQUIRED_VARS
|
||||
DiscordRPC_LIBRARY
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(DiscordRPC
|
||||
REQUIRED_VARS
|
||||
DiscordRPC_LIBRARY
|
||||
DiscordRPC_INCLUDE_DIR
|
||||
)
|
||||
|
||||
if (DiscordRPC_FOUND AND NOT TARGET DiscordRPC::discord-rpc)
|
||||
add_library(DiscordRPC::discord-rpc UNKNOWN IMPORTED)
|
||||
set_target_properties(DiscordRPC::discord-rpc PROPERTIES
|
||||
IMPORTED_LOCATION "${DiscordRPC_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${DiscordRPC_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
DiscordRPC_INCLUDE_DIR
|
||||
)
|
||||
|
||||
if (DiscordRPC_FOUND AND NOT TARGET DiscordRPC::discord-rpc)
|
||||
add_library(DiscordRPC::discord-rpc UNKNOWN IMPORTED)
|
||||
set_target_properties(DiscordRPC::discord-rpc PROPERTIES
|
||||
IMPORTED_LOCATION "${DiscordRPC_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${DiscordRPC_INCLUDE_DIR}"
|
||||
DiscordRPC_LIBRARY
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
DiscordRPC_INCLUDE_DIR
|
||||
DiscordRPC_LIBRARY
|
||||
)
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
dist/qt_themes/colorful/icons/48x48/download.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
dist/qt_themes/colorful/icons/48x48/upload.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
dist/qt_themes/colorful/icons/48x48/user-trash.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
3
dist/qt_themes/colorful/style.qrc
vendored
|
|
@ -18,6 +18,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
|
||||
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">../colorful/icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">../colorful/icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">../colorful/icons/48x48/upload.png</file>
|
||||
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
|
||||
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
|
||||
|
|
|
|||
3
dist/qt_themes/default/default.qrc
vendored
|
|
@ -14,6 +14,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
|
||||
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
<file alias="48x48/star.png">icons/48x48/star.png</file>
|
||||
|
|
|
|||
BIN
dist/qt_themes/default/icons/256x256/eden.png
vendored
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
BIN
dist/qt_themes/default/icons/48x48/download.png
vendored
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
dist/qt_themes/default/icons/48x48/upload.png
vendored
Normal file
|
After Width: | Height: | Size: 820 B |
BIN
dist/qt_themes/default/icons/48x48/user-trash.png
vendored
Normal file
|
After Width: | Height: | Size: 584 B |
3
dist/qt_themes/default_dark/style.qrc
vendored
|
|
@ -13,6 +13,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">../colorful/icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">../colorful/icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">../colorful/icons/48x48/upload.png</file>
|
||||
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
|
||||
|
|
|
|||
BIN
dist/qt_themes/qdarkstyle/icons/48x48/download.png
vendored
Normal file
|
After Width: | Height: | Size: 883 B |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/upload.png
vendored
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/user-trash.png
vendored
Normal file
|
After Width: | Height: | Size: 584 B |
3
dist/qt_themes/qdarkstyle/style.qrc
vendored
|
|
@ -9,6 +9,9 @@
|
|||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
|
||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
<file alias="48x48/bad_folder.png">../qdarkstyle/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../qdarkstyle/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../qdarkstyle/icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">../qdarkstyle/icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">../qdarkstyle/icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">../qdarkstyle/icons/48x48/upload.png</file>
|
||||
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/list-add.png">../qdarkstyle/icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">../qdarkstyle/icons/48x48/sd_card.png</file>
|
||||
|
|
|
|||
9
externals/cpmfile.json
vendored
|
|
@ -29,7 +29,8 @@
|
|||
"repo": "yhirose/cpp-httplib",
|
||||
"tag": "v%VERSION%",
|
||||
"hash": "b364500f76e2ecb0fe21b032d831272e3f1dfeea71af74e325f8fc4ce9dcdb3c941b97a5b422bdeafb9facd058597b90f8bfc284fb9afe3c33fefa15dd5a010b",
|
||||
"git_version": "0.26.0"
|
||||
"git_version": "0.26.0",
|
||||
"find_args": "MODULE GLOBAL"
|
||||
},
|
||||
"cpp-jwt": {
|
||||
"version": "1.4",
|
||||
|
|
@ -96,7 +97,11 @@
|
|||
"version": "3",
|
||||
"git_version": "3.6.4",
|
||||
"artifact": "%TAG%.tar.bz2",
|
||||
"skip_updates": true
|
||||
"skip_updates": true,
|
||||
"patches": [
|
||||
"0002-aesni-fix.patch",
|
||||
"0003-aesni-fix.patch"
|
||||
]
|
||||
},
|
||||
"enet": {
|
||||
"repo": "lsalzman/enet",
|
||||
|
|
|
|||
18
externals/nx_tzdb/CMakeLists.txt
vendored
|
|
@ -35,17 +35,14 @@ endif()
|
|||
|
||||
if(NOT YUZU_TZDB_PATH STREQUAL "")
|
||||
set(NX_TZDB_BASE_DIR "${YUZU_TZDB_PATH}")
|
||||
set(NX_TZDB_TZ_DIR "${NX_TZDB_BASE_DIR}/zoneinfo")
|
||||
elseif (MSVC)
|
||||
# TODO(crueter): This is a terrible solution, but MSVC fails to link without it
|
||||
# Need to investigate further but I still can't reproduce...
|
||||
elseif (MSVC AND NOT CXX_CLANG AND YUZU_ENABLE_LTO)
|
||||
# TODO(crueter): boot up the windows vm
|
||||
set(NX_TZDB_VERSION "250725")
|
||||
set(NX_TZDB_ARCHIVE "${CPM_SOURCE_CACHE}/nx_tzdb/${NX_TZDB_VERSION}.zip")
|
||||
|
||||
set(NX_TZDB_BASE_DIR "${CPM_SOURCE_CACHE}/nx_tzdb/tz")
|
||||
set(NX_TZDB_TZ_DIR "${NX_TZDB_BASE_DIR}/zoneinfo")
|
||||
|
||||
set(NX_TZDB_DOWNLOAD_URL "https://github.com/crueter/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip")
|
||||
set(NX_TZDB_DOWNLOAD_URL "https://git.crueter.xyz/misc/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip")
|
||||
|
||||
message(STATUS "Downloading time zone data from ${NX_TZDB_DOWNLOAD_URL}...")
|
||||
file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE}
|
||||
|
|
@ -65,14 +62,11 @@ else()
|
|||
message(STATUS "Downloading time zone data...")
|
||||
AddJsonPackage(tzdb)
|
||||
|
||||
target_include_directories(nx_tzdb
|
||||
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
INTERFACE ${NX_TZDB_INCLUDE_DIR})
|
||||
|
||||
set(NX_TZDB_BASE_DIR "${CPM_SOURCE_CACHE}/nx_tzdb")
|
||||
set(NX_TZDB_TZ_DIR "${nx_tzdb_SOURCE_DIR}")
|
||||
set(NX_TZDB_BASE_DIR "${nx_tzdb_SOURCE_DIR}")
|
||||
endif()
|
||||
|
||||
set(NX_TZDB_TZ_DIR "${NX_TZDB_BASE_DIR}/zoneinfo")
|
||||
|
||||
target_include_directories(nx_tzdb
|
||||
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
INTERFACE ${NX_TZDB_INCLUDE_DIR})
|
||||
|
|
|
|||
6
externals/nx_tzdb/cpmfile.json
vendored
|
|
@ -3,9 +3,9 @@
|
|||
"package": "nx_tzdb",
|
||||
"repo": "misc/tzdb_to_nx",
|
||||
"git_host": "git.crueter.xyz",
|
||||
"artifact": "%VERSION%.zip",
|
||||
"artifact": "%VERSION%.tar.gz",
|
||||
"tag": "%VERSION%",
|
||||
"hash": "8f60b4b29f285e39c0443f3d5572a73780f3dbfcfd5b35004451fadad77f3a215b2e2aa8d0fffe7e348e2a7b0660882b35228b6178dda8804a14ce44509fd2ca",
|
||||
"version": "250725"
|
||||
"hash": "87abb2aeca716d5d77b05317086dbc2f8acfc2f3f76ce4778345ee3df19973e6cd8ecbf16cfab5ad94c9636a6c44fd3588f9aadd3cba89403cfd56c8bec645c5",
|
||||
"version": "091025"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId = "dev.eden.eden_emulator"
|
||||
|
||||
minSdk = 28
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionName = getGitVersion()
|
||||
|
||||
|
|
|
|||
|
|
@ -230,7 +230,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
|
||||
) {
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
|
@ -244,7 +246,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
|
||||
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
|
||||
) {
|
||||
return super.dispatchGenericMotionEvent(event)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -149,7 +152,9 @@ class InputDialogFragment : DialogFragment() {
|
|||
|
||||
private fun onKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -173,7 +178,9 @@ class InputDialogFragment : DialogFragment() {
|
|||
|
||||
private fun onMotionEvent(event: MotionEvent): Boolean {
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1039,7 +1039,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
scale /= 100f
|
||||
|
||||
// Apply individual scale
|
||||
scale *= overlayControlData.individualScale
|
||||
scale *= overlayControlData.individualScale.let { if (it > 0f) it else 1f }
|
||||
|
||||
// Initialize the InputOverlayDrawableButton.
|
||||
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
|
||||
|
|
@ -1114,7 +1114,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
|
||||
// Apply individual scale
|
||||
if (dpadData != null) {
|
||||
scale *= dpadData.individualScale
|
||||
scale *= dpadData.individualScale.let { if (it > 0f) it else 1f }
|
||||
}
|
||||
|
||||
// Initialize the InputOverlayDrawableDpad.
|
||||
|
|
@ -1191,7 +1191,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
scale /= 100f
|
||||
|
||||
// Apply individual scale
|
||||
scale *= overlayControlData.individualScale
|
||||
scale *= overlayControlData.individualScale.let { if (it > 0f) it else 1f }
|
||||
|
||||
// Initialize the InputOverlayDrawableJoystick.
|
||||
val bitmapOuter = getBitmap(context, resOuter, scale)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
object AddonUtil {
|
||||
val validAddonDirectories = listOf("cheats", "exefs", "romfs")
|
||||
val validAddonDirectories = listOf("cheats", "exefs", "romfs", "romfslite")
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 438 KiB After Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
BIN
src/android/app/src/main/res/drawable/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -12,7 +15,6 @@
|
|||
#include "audio_core/adsp/mailbox.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/reader_writer_queue.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Core {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
#include <ranges>
|
||||
|
||||
namespace AudioCore {
|
||||
constexpr u32 CurrentRevision = 16;
|
||||
constexpr u32 CurrentRevision = 15;
|
||||
|
||||
enum class SupportTags {
|
||||
CommandProcessingTimeEstimatorVersion4,
|
||||
|
|
@ -47,6 +47,10 @@ enum class SupportTags {
|
|||
DelayChannelMappingChange,
|
||||
ReverbChannelMappingChange,
|
||||
I3dl2ReverbChannelMappingChange,
|
||||
SplitterPrevVolumeReset,
|
||||
SplitterBiquadFilterParameter,
|
||||
SplitterDestinationV2b,
|
||||
VoiceInParameterV2,
|
||||
|
||||
// Not a real tag, just here to get the count.
|
||||
Size
|
||||
|
|
@ -91,6 +95,10 @@ constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) {
|
|||
{SupportTags::DelayChannelMappingChange, 11},
|
||||
{SupportTags::ReverbChannelMappingChange, 11},
|
||||
{SupportTags::I3dl2ReverbChannelMappingChange, 11},
|
||||
{SupportTags::SplitterBiquadFilterParameter, 12},
|
||||
{SupportTags::SplitterPrevVolumeReset, 13},
|
||||
{SupportTags::SplitterDestinationV2b, 15},
|
||||
{SupportTags::VoiceInParameterV2, 15},
|
||||
}};
|
||||
|
||||
const auto& feature =
|
||||
|
|
|
|||
|
|
@ -193,4 +193,20 @@ bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
|
|||
return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterPrevVolumeResetSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::SplitterPrevVolumeReset, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterDestinationV2bSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::SplitterDestinationV2b, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVoiceInParameterV2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::VoiceInParameterV2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsBiquadFilterParameterForSplitterEnabled() const {
|
||||
return CheckFeatureSupported(SupportTags::SplitterBiquadFilterParameter, user_revision);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::Renderer
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -361,6 +364,38 @@ public:
|
|||
*/
|
||||
bool IsI3dl2ReverbChannelMappingChanged() const;
|
||||
|
||||
/**
|
||||
* Check if explicit previous mix volume reset is supported for splitters.
|
||||
* This allows splitters to explicitly reset their previous mix volumes instead of
|
||||
* doing so implicitly on first use.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsSplitterPrevVolumeResetSupported() const;
|
||||
|
||||
/**
|
||||
* Check if splitter destination v2b parameter format is supported (revision 15+).
|
||||
* This uses the extended parameter format with biquad filter fields.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsSplitterDestinationV2bSupported() const;
|
||||
|
||||
/**
|
||||
* Check if voice input parameter v2 format is supported (revision 15+).
|
||||
* This uses the extended parameter format with float biquad filters.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsVoiceInParameterV2Supported() const;
|
||||
|
||||
/**
|
||||
* Check if splitter destinations can carry biquad filter parameters (revision 12+).
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsBiquadFilterParameterForSplitterEnabled() const;
|
||||
|
||||
/// Host version
|
||||
u32 process_revision;
|
||||
/// User version
|
||||
|
|
|
|||
|
|
@ -64,8 +64,6 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
|
|||
const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
|
||||
behaviour.IsMemoryForceMappingEnabled());
|
||||
const auto voice_count{voice_context.GetCount()};
|
||||
std::span<const VoiceInfo::InParameter> in_params{
|
||||
reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count};
|
||||
std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output),
|
||||
voice_count};
|
||||
|
||||
|
|
@ -76,8 +74,104 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
|
|||
|
||||
u32 new_voice_count{0};
|
||||
|
||||
// Two input formats exist: legacy (0x170) and v2 with float biquad (0x188).
|
||||
const bool use_v2 = behaviour.IsVoiceInParameterV2Supported();
|
||||
const u32 in_stride = use_v2 ? 0x188u : static_cast<u32>(sizeof(VoiceInfo::InParameter));
|
||||
|
||||
for (u32 i = 0; i < voice_count; i++) {
|
||||
const auto& in_param{in_params[i]};
|
||||
VoiceInfo::InParameter local_in{};
|
||||
std::array<VoiceInfo::BiquadFilterParameter2, MaxBiquadFilters> float_biquads{};
|
||||
|
||||
if (!use_v2) {
|
||||
const auto* in_param_ptr = reinterpret_cast<const VoiceInfo::InParameter*>(
|
||||
input + i * sizeof(VoiceInfo::InParameter));
|
||||
local_in = *in_param_ptr;
|
||||
} else {
|
||||
struct VoiceInParameterV2 {
|
||||
u32 id;
|
||||
u32 node_id;
|
||||
bool is_new;
|
||||
bool in_use;
|
||||
PlayState play_state;
|
||||
SampleFormat sample_format;
|
||||
u32 sample_rate;
|
||||
u32 priority;
|
||||
u32 sort_order;
|
||||
u32 channel_count;
|
||||
f32 pitch;
|
||||
f32 volume;
|
||||
// Two BiquadFilterParameter2 (0x18 each) -> ignored/converted
|
||||
struct BiquadV2 {
|
||||
bool enable;
|
||||
u8 r1;
|
||||
u8 r2;
|
||||
u8 r3;
|
||||
std::array<f32, 3> b;
|
||||
std::array<f32, 2> a;
|
||||
} biquads[2];
|
||||
u32 wave_buffer_count;
|
||||
u32 wave_buffer_index;
|
||||
u32 reserved1;
|
||||
u64 src_data_address;
|
||||
u64 src_data_size;
|
||||
s32 mix_id;
|
||||
u32 splitter_id;
|
||||
std::array<VoiceInfo::WaveBufferInternal, MaxWaveBuffers> wavebuffers;
|
||||
std::array<u32, MaxChannels> channel_resource_ids;
|
||||
bool clear_voice_drop;
|
||||
u8 flush_wave_buffer_count;
|
||||
u16 reserved2;
|
||||
VoiceInfo::Flags flags;
|
||||
SrcQuality src_quality;
|
||||
u32 external_ctx;
|
||||
u32 external_ctx_size;
|
||||
u32 reserved3[2];
|
||||
};
|
||||
const auto* vin = reinterpret_cast<const VoiceInParameterV2*>(input + i * in_stride);
|
||||
local_in.id = vin->id;
|
||||
local_in.node_id = vin->node_id;
|
||||
local_in.is_new = vin->is_new;
|
||||
local_in.in_use = vin->in_use;
|
||||
local_in.play_state = vin->play_state;
|
||||
local_in.sample_format = vin->sample_format;
|
||||
local_in.sample_rate = vin->sample_rate;
|
||||
local_in.priority = static_cast<s32>(vin->priority);
|
||||
local_in.sort_order = static_cast<s32>(vin->sort_order);
|
||||
local_in.channel_count = vin->channel_count;
|
||||
local_in.pitch = vin->pitch;
|
||||
local_in.volume = vin->volume;
|
||||
|
||||
// For REV15+, we keep float coefficients separate and only convert for compatibility
|
||||
for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) {
|
||||
const auto& src = vin->biquads[filter_idx];
|
||||
auto& dst = local_in.biquads[filter_idx];
|
||||
dst.enabled = src.enable;
|
||||
// Convert float coefficients to fixed-point Q2.14 for legacy path
|
||||
dst.b[0] = static_cast<s16>(std::clamp(src.b[0] * 16384.0f, -32768.0f, 32767.0f));
|
||||
dst.b[1] = static_cast<s16>(std::clamp(src.b[1] * 16384.0f, -32768.0f, 32767.0f));
|
||||
dst.b[2] = static_cast<s16>(std::clamp(src.b[2] * 16384.0f, -32768.0f, 32767.0f));
|
||||
dst.a[0] = static_cast<s16>(std::clamp(src.a[0] * 16384.0f, -32768.0f, 32767.0f));
|
||||
dst.a[1] = static_cast<s16>(std::clamp(src.a[1] * 16384.0f, -32768.0f, 32767.0f));
|
||||
|
||||
// Also store the native float version
|
||||
float_biquads[filter_idx].enabled = src.enable;
|
||||
float_biquads[filter_idx].numerator = src.b;
|
||||
float_biquads[filter_idx].denominator = src.a;
|
||||
}
|
||||
local_in.wave_buffer_count = vin->wave_buffer_count;
|
||||
local_in.wave_buffer_index = static_cast<u16>(vin->wave_buffer_index);
|
||||
local_in.src_data_address = static_cast<CpuAddr>(vin->src_data_address);
|
||||
local_in.src_data_size = vin->src_data_size;
|
||||
local_in.mix_id = static_cast<u32>(vin->mix_id);
|
||||
local_in.splitter_id = vin->splitter_id;
|
||||
local_in.wave_buffer_internal = vin->wavebuffers;
|
||||
local_in.channel_resource_ids = vin->channel_resource_ids;
|
||||
local_in.clear_voice_drop = vin->clear_voice_drop;
|
||||
local_in.flush_buffer_count = vin->flush_wave_buffer_count;
|
||||
local_in.flags = vin->flags;
|
||||
local_in.src_quality = vin->src_quality;
|
||||
}
|
||||
const auto& in_param = local_in;
|
||||
std::array<VoiceState*, MaxChannels> voice_states{};
|
||||
|
||||
if (!in_param.in_use) {
|
||||
|
|
@ -101,6 +195,14 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
|
|||
BehaviorInfo::ErrorInfo update_error{};
|
||||
voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour);
|
||||
|
||||
// For REV15+, store the native float biquad coefficients
|
||||
if (use_v2) {
|
||||
voice_info.use_float_biquads = true;
|
||||
voice_info.biquads_float = float_biquads;
|
||||
} else {
|
||||
voice_info.use_float_biquads = false;
|
||||
}
|
||||
|
||||
if (!update_error.error_code.IsSuccess()) {
|
||||
behaviour.AppendError(update_error);
|
||||
}
|
||||
|
|
@ -121,7 +223,7 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
|
|||
new_voice_count += in_param.channel_count;
|
||||
}
|
||||
|
||||
auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))};
|
||||
auto consumed_input_size{voice_count * in_stride};
|
||||
auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))};
|
||||
if (consumed_input_size != in_header->voices_size) {
|
||||
LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}",
|
||||
|
|
@ -257,18 +359,31 @@ Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_co
|
|||
EffectContext& effect_context, SplitterContext& splitter_context) {
|
||||
s32 mix_count{0};
|
||||
u32 consumed_input_size{0};
|
||||
u32 input_mix_size{0};
|
||||
|
||||
if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
|
||||
auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)};
|
||||
mix_count = in_dirty_params->count;
|
||||
|
||||
// Validate against expected header size to ensure structure is correct
|
||||
if (mix_count < 0 || mix_count > 0x100) {
|
||||
LOG_ERROR(
|
||||
Service_Audio,
|
||||
"Invalid mix count from dirty parameter: count={}, magic=0x{:X}, expected_size={}",
|
||||
mix_count, in_dirty_params->magic, in_header->mix_size);
|
||||
return Service::Audio::ResultInvalidUpdateInfo;
|
||||
}
|
||||
|
||||
consumed_input_size += static_cast<u32>(sizeof(MixInfo::InDirtyParameter));
|
||||
input += sizeof(MixInfo::InDirtyParameter);
|
||||
consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) +
|
||||
mix_count * sizeof(MixInfo::InParameter));
|
||||
} else {
|
||||
mix_count = mix_context.GetCount();
|
||||
consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
|
||||
}
|
||||
|
||||
input_mix_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
|
||||
consumed_input_size += input_mix_size;
|
||||
|
||||
|
||||
if (mix_buffer_count == 0) {
|
||||
return Service::Audio::ResultInvalidUpdateInfo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,6 +237,13 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& vo
|
|||
|
||||
cmd.biquad = voice_info.biquads[biquad_index];
|
||||
|
||||
if (voice_info.use_float_biquads) {
|
||||
cmd.biquad_float = voice_info.biquads_float[biquad_index];
|
||||
cmd.use_float_coefficients = true;
|
||||
} else {
|
||||
cmd.use_float_coefficients = false;
|
||||
}
|
||||
|
||||
cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()),
|
||||
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
|
||||
|
||||
|
|
@ -263,6 +270,9 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas
|
|||
cmd.biquad.b = parameter.b;
|
||||
cmd.biquad.a = parameter.a;
|
||||
|
||||
// Effects use legacy fixed-point format
|
||||
cmd.use_float_coefficients = false;
|
||||
|
||||
cmd.state = memory_pool->Translate(CpuAddr(state),
|
||||
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
|
||||
|
||||
|
|
@ -658,6 +668,13 @@ void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, Voice
|
|||
cmd.output = buffer_count + channel;
|
||||
cmd.biquads = voice_info.biquads;
|
||||
|
||||
if (voice_info.use_float_biquads) {
|
||||
cmd.biquads_float = voice_info.biquads_float;
|
||||
cmd.use_float_coefficients = true;
|
||||
} else {
|
||||
cmd.use_float_coefficients = false;
|
||||
}
|
||||
|
||||
cmd.states[0] =
|
||||
memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()),
|
||||
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
|
||||
|
|
|
|||
|
|
@ -51,6 +51,40 @@ void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
|
|||
state.s3 = Common::BitCast<s64>(s[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Biquad filter float implementation with native float coefficients.
|
||||
*/
|
||||
void ApplyBiquadFilterFloat2(std::span<s32> output, std::span<const s32> input,
|
||||
std::array<f32, 3>& b, std::array<f32, 2>& a,
|
||||
VoiceState::BiquadFilterState& state, const u32 sample_count) {
|
||||
constexpr f64 min{std::numeric_limits<s32>::min()};
|
||||
constexpr f64 max{std::numeric_limits<s32>::max()};
|
||||
|
||||
std::array<f64, 3> b_double{static_cast<f64>(b[0]), static_cast<f64>(b[1]),
|
||||
static_cast<f64>(b[2])};
|
||||
std::array<f64, 2> a_double{static_cast<f64>(a[0]), static_cast<f64>(a[1])};
|
||||
std::array<f64, 4> s{Common::BitCast<f64>(state.s0), Common::BitCast<f64>(state.s1),
|
||||
Common::BitCast<f64>(state.s2), Common::BitCast<f64>(state.s3)};
|
||||
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
f64 in_sample{static_cast<f64>(input[i])};
|
||||
auto sample{in_sample * b_double[0] + s[0] * b_double[1] + s[1] * b_double[2] +
|
||||
s[2] * a_double[0] + s[3] * a_double[1]};
|
||||
|
||||
output[i] = static_cast<s32>(std::clamp(sample, min, max));
|
||||
|
||||
s[1] = s[0];
|
||||
s[0] = in_sample;
|
||||
s[3] = s[2];
|
||||
s[2] = sample;
|
||||
}
|
||||
|
||||
state.s0 = Common::BitCast<s64>(s[0]);
|
||||
state.s1 = Common::BitCast<s64>(s[1]);
|
||||
state.s2 = Common::BitCast<s64>(s[2]);
|
||||
state.s3 = Common::BitCast<s64>(s[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Biquad filter s32 implementation.
|
||||
*
|
||||
|
|
@ -98,8 +132,14 @@ void BiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& pro
|
|||
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
|
||||
|
||||
if (use_float_processing) {
|
||||
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
|
||||
processor.sample_count);
|
||||
// REV15+: Use native float coefficients if available
|
||||
if (use_float_coefficients) {
|
||||
ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquad_float.numerator,
|
||||
biquad_float.denominator, *state_, processor.sample_count);
|
||||
} else {
|
||||
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
|
||||
processor.sample_count);
|
||||
}
|
||||
} else {
|
||||
ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
|
||||
processor.sample_count);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -50,12 +53,16 @@ struct BiquadFilterCommand : ICommand {
|
|||
s16 output;
|
||||
/// Input parameters for biquad
|
||||
VoiceInfo::BiquadFilterParameter biquad;
|
||||
/// Input parameters for biquad (REV15+ native float)
|
||||
VoiceInfo::BiquadFilterParameter2 biquad_float;
|
||||
/// Biquad state, updated each call
|
||||
CpuAddr state;
|
||||
/// If true, reset the state
|
||||
bool needs_init;
|
||||
/// If true, use float processing rather than int
|
||||
bool use_float_processing;
|
||||
/// If true, use native float coefficients (REV15+)
|
||||
bool use_float_coefficients;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -72,4 +79,18 @@ void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
|
|||
std::array<s16, 3>& b, std::array<s16, 2>& a,
|
||||
VoiceState::BiquadFilterState& state, const u32 sample_count);
|
||||
|
||||
/**
|
||||
* Biquad filter float implementation with native float coefficients (SDK REV15+).
|
||||
*
|
||||
* @param output - Output container for filtered samples.
|
||||
* @param input - Input container for samples to be filtered.
|
||||
* @param b - Feedforward coefficients (float).
|
||||
* @param a - Feedback coefficients (float).
|
||||
* @param state - State to track previous samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
void ApplyBiquadFilterFloat2(std::span<s32> output, std::span<const s32> input,
|
||||
std::array<f32, 3>& b, std::array<f32, 2>& a,
|
||||
VoiceState::BiquadFilterState& state, const u32 sample_count);
|
||||
|
||||
} // namespace AudioCore::Renderer
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -33,8 +36,14 @@ void MultiTapBiquadFilterCommand::Process(const AudioRenderer::CommandListProces
|
|||
*state = {};
|
||||
}
|
||||
|
||||
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
|
||||
processor.sample_count);
|
||||
// REV15+: Use native float coefficients if available
|
||||
if (use_float_coefficients) {
|
||||
ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquads_float[i].numerator,
|
||||
biquads_float[i].denominator, *state, processor.sample_count);
|
||||
} else {
|
||||
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
|
||||
processor.sample_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -49,12 +52,16 @@ struct MultiTapBiquadFilterCommand : ICommand {
|
|||
s16 output;
|
||||
/// Biquad parameters
|
||||
std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
|
||||
/// Biquad parameters (REV15+ native float)
|
||||
std::array<VoiceInfo::BiquadFilterParameter2, MaxBiquadFilters> biquads_float;
|
||||
/// Biquad states, updated each call
|
||||
std::array<CpuAddr, MaxBiquadFilters> states;
|
||||
/// If each biquad needs initialisation
|
||||
std::array<bool, MaxBiquadFilters> needs_init;
|
||||
/// Number of active biquads
|
||||
u8 filter_tap_count;
|
||||
/// If true, use native float coefficients (REV15+)
|
||||
bool use_float_coefficients;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::Renderer
|
||||
|
|
|
|||
|
|
@ -35,12 +35,16 @@ SplitterDestinationData& SplitterContext::GetData(const u32 index) {
|
|||
|
||||
void SplitterContext::Setup(std::span<SplitterInfo> splitter_infos_, const u32 splitter_info_count_,
|
||||
SplitterDestinationData* splitter_destinations_,
|
||||
const u32 destination_count_, const bool splitter_bug_fixed_) {
|
||||
const u32 destination_count_, const bool splitter_bug_fixed_,
|
||||
const BehaviorInfo& behavior) {
|
||||
splitter_infos = splitter_infos_;
|
||||
info_count = splitter_info_count_;
|
||||
splitter_destinations = splitter_destinations_;
|
||||
destinations_count = destination_count_;
|
||||
splitter_bug_fixed = splitter_bug_fixed_;
|
||||
splitter_prev_volume_reset_supported = behavior.IsSplitterPrevVolumeResetSupported();
|
||||
splitter_biquad_param_supported = behavior.IsBiquadFilterParameterForSplitterEnabled();
|
||||
splitter_float_coeff_supported = behavior.IsSplitterDestinationV2bSupported();
|
||||
}
|
||||
|
||||
bool SplitterContext::UsingSplitter() const {
|
||||
|
|
@ -84,7 +88,7 @@ bool SplitterContext::Initialize(const BehaviorInfo& behavior,
|
|||
}
|
||||
|
||||
Setup(splitter_infos, params.splitter_infos, splitter_destinations,
|
||||
params.splitter_destinations, behavior.IsSplitterBugFixed());
|
||||
params.splitter_destinations, behavior.IsSplitterBugFixed(), behavior);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -137,19 +141,104 @@ u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_
|
|||
|
||||
u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) {
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
auto data_header{
|
||||
reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset)};
|
||||
// Version selection based on feature flags:
|
||||
// - REV12: integer biquad params (Version2a)
|
||||
// - REV15: float coeff/biquad v2b
|
||||
// - older: no biquad fields
|
||||
if (!splitter_biquad_param_supported) {
|
||||
const auto* data_header =
|
||||
reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset);
|
||||
|
||||
if (data_header->magic != GetSplitterSendDataMagic()) {
|
||||
continue;
|
||||
if (data_header->magic != GetSplitterSendDataMagic()) {
|
||||
continue;
|
||||
}
|
||||
if (data_header->id < 0 || data_header->id > destinations_count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto modified_params = *data_header;
|
||||
if (!splitter_prev_volume_reset_supported) {
|
||||
modified_params.reset_prev_volume = false;
|
||||
}
|
||||
splitter_destinations[data_header->id].Update(modified_params);
|
||||
offset += sizeof(SplitterDestinationData::InParameter);
|
||||
} else if (!splitter_float_coeff_supported) {
|
||||
// Version 2a: struct contains legacy fixed-point biquad filter fields (REV12+)
|
||||
const auto* data_header_v2a =
|
||||
reinterpret_cast<const SplitterDestinationData::InParameterVersion2a*>(input +
|
||||
offset);
|
||||
|
||||
if (data_header_v2a->magic != GetSplitterSendDataMagic()) {
|
||||
continue;
|
||||
}
|
||||
if (data_header_v2a->id < 0 || data_header_v2a->id > destinations_count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map common fields to the base format
|
||||
SplitterDestinationData::InParameter mapped{};
|
||||
mapped.magic = data_header_v2a->magic;
|
||||
mapped.id = data_header_v2a->id;
|
||||
mapped.mix_volumes = data_header_v2a->mix_volumes;
|
||||
mapped.mix_id = data_header_v2a->mix_id;
|
||||
mapped.in_use = data_header_v2a->in_use;
|
||||
mapped.reset_prev_volume =
|
||||
splitter_prev_volume_reset_supported ? data_header_v2a->reset_prev_volume : false;
|
||||
|
||||
auto& destination = splitter_destinations[data_header_v2a->id];
|
||||
destination.Update(mapped);
|
||||
|
||||
// Convert legacy fixed-point biquad params into float representation
|
||||
auto biquad_filters = destination.GetBiquadFilters();
|
||||
for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) {
|
||||
const auto& legacy = data_header_v2a->biquad_filters[filter_idx];
|
||||
auto& out = biquad_filters[filter_idx];
|
||||
out.enabled = legacy.enabled;
|
||||
// s16 fixed-point scale: use Q14 like voices (b and a are s16, 1.0 ~= 1<<14)
|
||||
constexpr float scale = 1.0f / static_cast<float>(1 << 14);
|
||||
out.numerator[0] = static_cast<float>(legacy.b[0]) * scale;
|
||||
out.numerator[1] = static_cast<float>(legacy.b[1]) * scale;
|
||||
out.numerator[2] = static_cast<float>(legacy.b[2]) * scale;
|
||||
out.denominator[0] = static_cast<float>(legacy.a[0]) * scale;
|
||||
out.denominator[1] = static_cast<float>(legacy.a[1]) * scale;
|
||||
}
|
||||
|
||||
offset += static_cast<u32>(sizeof(SplitterDestinationData::InParameterVersion2a));
|
||||
} else {
|
||||
// Version 2b: struct contains extra biquad filter fields with float coeffs
|
||||
const auto* data_header_v2b =
|
||||
reinterpret_cast<const SplitterDestinationData::InParameterVersion2b*>(input +
|
||||
offset);
|
||||
|
||||
if (data_header_v2b->magic != GetSplitterSendDataMagic()) {
|
||||
continue;
|
||||
}
|
||||
if (data_header_v2b->id < 0 || data_header_v2b->id > destinations_count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map common fields to the old format
|
||||
SplitterDestinationData::InParameter mapped{};
|
||||
mapped.magic = data_header_v2b->magic;
|
||||
mapped.id = data_header_v2b->id;
|
||||
mapped.mix_volumes = data_header_v2b->mix_volumes;
|
||||
mapped.mix_id = data_header_v2b->mix_id;
|
||||
mapped.in_use = data_header_v2b->in_use;
|
||||
mapped.reset_prev_volume =
|
||||
splitter_prev_volume_reset_supported ? data_header_v2b->reset_prev_volume : false;
|
||||
|
||||
// Store biquad filters from V2b (REV15+)
|
||||
auto& destination = splitter_destinations[data_header_v2b->id];
|
||||
destination.Update(mapped);
|
||||
|
||||
// Copy biquad filter parameters
|
||||
auto biquad_filters = destination.GetBiquadFilters();
|
||||
for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) {
|
||||
biquad_filters[filter_idx] = data_header_v2b->biquad_filters[filter_idx];
|
||||
}
|
||||
|
||||
offset += static_cast<u32>(sizeof(SplitterDestinationData::InParameterVersion2b));
|
||||
}
|
||||
|
||||
if (data_header->id < 0 || data_header->id > destinations_count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
splitter_destinations[data_header->id].Update(*data_header);
|
||||
offset += sizeof(SplitterDestinationData::InParameter);
|
||||
}
|
||||
|
||||
return offset;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -168,10 +171,11 @@ private:
|
|||
* @param splitter_destinations - Workbuffer for splitter destinations.
|
||||
* @param destination_count - Number of destinations in the workbuffer.
|
||||
* @param splitter_bug_fixed - Is the splitter bug fixed?
|
||||
* @param behavior - Behavior info for feature support.
|
||||
*/
|
||||
void Setup(std::span<SplitterInfo> splitter_infos, u32 splitter_info_count,
|
||||
SplitterDestinationData* splitter_destinations, u32 destination_count,
|
||||
bool splitter_bug_fixed);
|
||||
bool splitter_bug_fixed, const BehaviorInfo& behavior);
|
||||
|
||||
/// Workbuffer for splitters
|
||||
std::span<SplitterInfo> splitter_infos{};
|
||||
|
|
@ -183,6 +187,12 @@ private:
|
|||
s32 destinations_count{};
|
||||
/// Is the splitter bug fixed?
|
||||
bool splitter_bug_fixed{};
|
||||
/// Is explicit previous mix volume reset supported?
|
||||
bool splitter_prev_volume_reset_supported{};
|
||||
/// Is biquad filter parameter for splitter (REV12) supported?
|
||||
bool splitter_biquad_param_supported{};
|
||||
/// Is float coefficient/biquad filter v2b parameter supported?
|
||||
bool splitter_float_coeff_supported{};
|
||||
};
|
||||
|
||||
} // namespace Renderer
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -84,4 +87,14 @@ void SplitterDestinationData::SetNext(SplitterDestinationData* next_) {
|
|||
next = next_;
|
||||
}
|
||||
|
||||
std::span<SplitterDestinationData::BiquadFilterParameter2>
|
||||
SplitterDestinationData::GetBiquadFilters() {
|
||||
return biquad_filters;
|
||||
}
|
||||
|
||||
std::span<const SplitterDestinationData::BiquadFilterParameter2>
|
||||
SplitterDestinationData::GetBiquadFilters() const {
|
||||
return biquad_filters;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::Renderer
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -16,16 +19,72 @@ namespace AudioCore::Renderer {
|
|||
*/
|
||||
class SplitterDestinationData {
|
||||
public:
|
||||
/**
|
||||
* Biquad filter parameter with float coefficients (SDK REV15+).
|
||||
* Defined here to avoid circular dependency with VoiceInfo.
|
||||
*/
|
||||
struct BiquadFilterParameter2 {
|
||||
/* 0x00 */ bool enabled;
|
||||
/* 0x01 */ u8 reserved1;
|
||||
/* 0x02 */ u8 reserved2;
|
||||
/* 0x03 */ u8 reserved3;
|
||||
/* 0x04 */ std::array<f32, 3> numerator; // b0, b1, b2
|
||||
/* 0x10 */ std::array<f32, 2> denominator; // a1, a2 (a0 = 1)
|
||||
};
|
||||
static_assert(sizeof(BiquadFilterParameter2) == 0x18,
|
||||
"BiquadFilterParameter2 has the wrong size!");
|
||||
|
||||
/**
|
||||
* Legacy biquad filter parameter with fixed-point coefficients (SDK REV12+ for splitters).
|
||||
* Matches the old voice biquad format.
|
||||
*/
|
||||
struct BiquadFilterParameterLegacy {
|
||||
/* 0x00 */ bool enabled;
|
||||
/* 0x02 */ std::array<s16, 3> b; // numerator
|
||||
/* 0x08 */ std::array<s16, 2> a; // denominator (a0 = 1)
|
||||
};
|
||||
static_assert(sizeof(BiquadFilterParameterLegacy) == 0xC,
|
||||
"BiquadFilterParameterLegacy has the wrong size!");
|
||||
|
||||
struct InParameter {
|
||||
/* 0x00 */ u32 magic; // 'SNDD'
|
||||
/* 0x04 */ s32 id;
|
||||
/* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
|
||||
/* 0x68 */ u32 mix_id;
|
||||
/* 0x6C */ bool in_use;
|
||||
/* 0x6D */ bool reset_prev_volume;
|
||||
};
|
||||
static_assert(sizeof(InParameter) == 0x70,
|
||||
"SplitterDestinationData::InParameter has the wrong size!");
|
||||
|
||||
struct InParameterVersion2a {
|
||||
/* 0x00 */ u32 magic; // 'SNDD'
|
||||
/* 0x04 */ s32 id;
|
||||
/* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
|
||||
/* 0x68 */ u32 mix_id;
|
||||
/* 0x6C */ std::array<SplitterDestinationData::BiquadFilterParameterLegacy, MaxBiquadFilters>
|
||||
biquad_filters;
|
||||
/* 0x84 */ bool in_use;
|
||||
/* 0x85 */ bool reset_prev_volume; // only effective if supported
|
||||
/* 0x86 */ u8 reserved[10];
|
||||
};
|
||||
static_assert(sizeof(InParameterVersion2a) == 0x90,
|
||||
"SplitterDestinationData::InParameterVersion2a has the wrong size!");
|
||||
|
||||
struct InParameterVersion2b {
|
||||
/* 0x00 */ u32 magic; // 'SNDD'
|
||||
/* 0x04 */ s32 id;
|
||||
/* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
|
||||
/* 0x68 */ u32 mix_id;
|
||||
/* 0x6C */ std::array<SplitterDestinationData::BiquadFilterParameter2, MaxBiquadFilters>
|
||||
biquad_filters;
|
||||
/* 0x9C */ bool in_use;
|
||||
/* 0x9D */ bool reset_prev_volume;
|
||||
/* 0x9E */ u8 reserved[10];
|
||||
};
|
||||
static_assert(sizeof(InParameterVersion2b) == 0xA8,
|
||||
"SplitterDestinationData::InParameterVersion2b has the wrong size!");
|
||||
|
||||
SplitterDestinationData(s32 id);
|
||||
|
||||
/**
|
||||
|
|
@ -78,7 +137,7 @@ public:
|
|||
f32 GetMixVolumePrev(u32 index) const;
|
||||
|
||||
/**
|
||||
* Get the previous mix volumes for all mix buffers in this destination.
|
||||
* Get the previous mix volumes for all mix buffers.
|
||||
*
|
||||
* @return Span of previous mix buffer volumes.
|
||||
*/
|
||||
|
|
@ -115,6 +174,20 @@ public:
|
|||
*/
|
||||
void SetNext(SplitterDestinationData* next);
|
||||
|
||||
/**
|
||||
* Get biquad filter parameters for this destination (REV15+ or mapped from REV12).
|
||||
*
|
||||
* @return Span of biquad filter parameters.
|
||||
*/
|
||||
std::span<BiquadFilterParameter2> GetBiquadFilters();
|
||||
|
||||
/**
|
||||
* Get const biquad filter parameters for this destination (REV15+ or mapped from REV12).
|
||||
*
|
||||
* @return Const span of biquad filter parameters.
|
||||
*/
|
||||
std::span<const BiquadFilterParameter2> GetBiquadFilters() const;
|
||||
|
||||
private:
|
||||
/// Id of this destination
|
||||
const s32 id;
|
||||
|
|
@ -124,6 +197,8 @@ private:
|
|||
std::array<f32, MaxMixBuffers> mix_volumes{0.0f};
|
||||
/// Previous mix volumes
|
||||
std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f};
|
||||
/// Biquad filter parameters (REV15+ or mapped from REV12)
|
||||
std::array<BiquadFilterParameter2, MaxBiquadFilters> biquad_filters{};
|
||||
/// Next destination in the mix chain
|
||||
SplitterDestinationData* next{};
|
||||
/// Is this destination in use?
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -135,6 +138,17 @@ public:
|
|||
static_assert(sizeof(BiquadFilterParameter) == 0xC,
|
||||
"VoiceInfo::BiquadFilterParameter has the wrong size!");
|
||||
|
||||
struct BiquadFilterParameter2 {
|
||||
/* 0x00 */ bool enabled;
|
||||
/* 0x01 */ u8 reserved1;
|
||||
/* 0x02 */ u8 reserved2;
|
||||
/* 0x03 */ u8 reserved3;
|
||||
/* 0x04 */ std::array<f32, 3> numerator; // b0, b1, b2
|
||||
/* 0x10 */ std::array<f32, 2> denominator; // a1, a2 (a0 = 1)
|
||||
};
|
||||
static_assert(sizeof(BiquadFilterParameter2) == 0x18,
|
||||
"VoiceInfo::BiquadFilterParameter2 has the wrong size!");
|
||||
|
||||
struct InParameter {
|
||||
/* 0x000 */ u32 id;
|
||||
/* 0x004 */ u32 node_id;
|
||||
|
|
@ -168,6 +182,43 @@ public:
|
|||
};
|
||||
static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!");
|
||||
|
||||
struct InParameter2 {
|
||||
/* 0x000 */ u32 id;
|
||||
/* 0x004 */ u32 node_id;
|
||||
/* 0x008 */ bool is_new;
|
||||
/* 0x009 */ bool in_use;
|
||||
/* 0x00A */ PlayState play_state;
|
||||
/* 0x00B */ SampleFormat sample_format;
|
||||
/* 0x00C */ u32 sample_rate;
|
||||
/* 0x010 */ s32 priority;
|
||||
/* 0x014 */ s32 sort_order;
|
||||
/* 0x018 */ u32 channel_count;
|
||||
/* 0x01C */ f32 pitch;
|
||||
/* 0x020 */ f32 volume;
|
||||
/* 0x024 */ std::array<BiquadFilterParameter2, MaxBiquadFilters> biquads;
|
||||
/* 0x054 */ u32 wave_buffer_count;
|
||||
/* 0x058 */ u32 wave_buffer_index;
|
||||
/* 0x05C */ u32 reserved1;
|
||||
/* 0x060 */ CpuAddr src_data_address;
|
||||
/* 0x068 */ u64 src_data_size;
|
||||
/* 0x070 */ u32 mix_id;
|
||||
/* 0x074 */ u32 splitter_id;
|
||||
/* 0x078 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
|
||||
/* 0x158 */ std::array<s32, MaxChannels> channel_resource_ids;
|
||||
/* 0x170 */ bool clear_voice_drop;
|
||||
/* 0x171 */ u8 flush_buffer_count;
|
||||
/* 0x172 */ u16 reserved2;
|
||||
/* 0x174 */ Flags flags;
|
||||
/* 0x175 */ u8 reserved3;
|
||||
/* 0x176 */ SrcQuality src_quality;
|
||||
/* 0x177 */ u8 reserved4;
|
||||
/* 0x178 */ u32 external_context;
|
||||
/* 0x17C */ u32 external_context_size;
|
||||
/* 0x180 */ u32 reserved5;
|
||||
/* 0x184 */ u32 reserved6;
|
||||
};
|
||||
static_assert(sizeof(InParameter2) == 0x188, "VoiceInfo::InParameter2 has the wrong size!");
|
||||
|
||||
struct OutStatus {
|
||||
/* 0x00 */ u64 played_sample_count;
|
||||
/* 0x08 */ u32 wave_buffers_consumed;
|
||||
|
|
@ -349,6 +400,10 @@ public:
|
|||
f32 prev_volume{};
|
||||
/// Biquad filters for generating filter commands on this voice
|
||||
std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{};
|
||||
/// Float biquad filters for REV15+ (native float coefficients)
|
||||
std::array<BiquadFilterParameter2, MaxBiquadFilters> biquads_float{};
|
||||
/// Use float biquad coefficients (REV15+)
|
||||
bool use_float_biquads{};
|
||||
/// Number of active wavebuffers
|
||||
u32 wave_buffer_count{};
|
||||
/// Current playing wavebuffer index
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace AudioCore::Sink {
|
|||
|
||||
void SinkStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) {
|
||||
SCOPE_EXIT {
|
||||
queue.enqueue(buffer);
|
||||
queue.EmplaceWait(buffer);
|
||||
++queued_buffers;
|
||||
};
|
||||
|
||||
|
|
@ -147,7 +147,8 @@ std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) {
|
|||
|
||||
void SinkStream::ClearQueue() {
|
||||
samples_buffer.Pop();
|
||||
while (queue.pop()) {
|
||||
SinkBuffer tmp;
|
||||
while (queue.TryPop(tmp)) {
|
||||
}
|
||||
queued_buffers = 0;
|
||||
playing_buffer = {};
|
||||
|
|
@ -169,7 +170,7 @@ void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t n
|
|||
while (frames_written < num_frames) {
|
||||
// If the playing buffer has been consumed or has no frames, we need a new one
|
||||
if (playing_buffer.consumed || playing_buffer.frames == 0) {
|
||||
if (!queue.try_dequeue(playing_buffer)) {
|
||||
if (!queue.TryPop(playing_buffer)) {
|
||||
// If no buffer was available we've underrun, just push the samples and
|
||||
// continue.
|
||||
samples_buffer.Push(&input_buffer[frames_written * frame_size],
|
||||
|
|
@ -230,7 +231,7 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
|
|||
while (frames_written < num_frames) {
|
||||
// If the playing buffer has been consumed or has no frames, we need a new one
|
||||
if (playing_buffer.consumed || playing_buffer.frames == 0) {
|
||||
if (!queue.try_dequeue(playing_buffer)) {
|
||||
if (!queue.TryPop(playing_buffer)) {
|
||||
// If no buffer was available we've underrun, fill the remaining buffer with
|
||||
// the last written frame and continue.
|
||||
for (size_t i = frames_written; i < num_frames; i++) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -14,8 +17,8 @@
|
|||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/reader_writer_queue.h"
|
||||
#include "common/ring_buffer.h"
|
||||
#include "common/bounded_threadsafe_queue.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Core {
|
||||
|
|
@ -237,7 +240,7 @@ private:
|
|||
/// Ring buffer of the samples waiting to be played or consumed
|
||||
Common::RingBuffer<s16, 0x10000> samples_buffer;
|
||||
/// Audio buffers queued and waiting to play
|
||||
Common::ReaderWriterQueue<SinkBuffer> queue;
|
||||
Common::SPSCQueue<SinkBuffer, 0x10000> queue;
|
||||
/// The currently-playing audio buffer
|
||||
SinkBuffer playing_buffer{};
|
||||
/// The last played (or received) frame of audio, used when the callback underruns
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@ add_library(
|
|||
range_mutex.h
|
||||
range_sets.h
|
||||
range_sets.inc
|
||||
reader_writer_queue.h
|
||||
ring_buffer.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp
|
||||
scm_rev.h
|
||||
|
|
|
|||
|
|
@ -40,22 +40,22 @@ void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsig
|
|||
#endif
|
||||
|
||||
#define LOG_DEBUG(log_class, ...) \
|
||||
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Debug, \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Debug, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
#define LOG_INFO(log_class, ...) \
|
||||
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Info, \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Info, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
#define LOG_WARNING(log_class, ...) \
|
||||
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Warning, \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Warning, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
#define LOG_ERROR(log_class, ...) \
|
||||
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Error, \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Error, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
#define LOG_CRITICAL(log_class, ...) \
|
||||
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Critical, \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Critical, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
|
|
|
|||
|
|
@ -1,940 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2013-2020 Cameron Desrochers
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdlib> // For malloc/free/abort & size_t
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "common/atomic_helpers.h"
|
||||
|
||||
#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012
|
||||
#include <chrono>
|
||||
#endif
|
||||
|
||||
// A lock-free queue for a single-consumer, single-producer architecture.
|
||||
// The queue is also wait-free in the common path (except if more memory
|
||||
// needs to be allocated, in which case malloc is called).
|
||||
// Allocates memory sparingly, and only once if the original maximum size
|
||||
// estimate is never exceeded.
|
||||
// Tested on x86/x64 processors, but semantics should be correct for all
|
||||
// architectures (given the right implementations in atomicops.h), provided
|
||||
// that aligned integer and pointer accesses are naturally atomic.
|
||||
// Note that there should only be one consumer thread and producer thread;
|
||||
// Switching roles of the threads, or using multiple consecutive threads for
|
||||
// one role, is not safe unless properly synchronized.
|
||||
// Using the queue exclusively from one thread is fine, though a bit silly.
|
||||
|
||||
#ifndef MOODYCAMEL_CACHE_LINE_SIZE
|
||||
#define MOODYCAMEL_CACHE_LINE_SIZE 64
|
||||
#endif
|
||||
|
||||
#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED
|
||||
#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || \
|
||||
(!defined(_MSC_VER) && !defined(__GNUC__))
|
||||
#define MOODYCAMEL_EXCEPTIONS_ENABLED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MOODYCAMEL_HAS_EMPLACE
|
||||
#if !defined(_MSC_VER) || \
|
||||
_MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013
|
||||
#define MOODYCAMEL_HAS_EMPLACE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE
|
||||
#if defined(__APPLE__) && defined(__MACH__) && __cplusplus >= 201703L
|
||||
// This is required to find out what deployment target we are using
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || \
|
||||
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14
|
||||
// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we
|
||||
// can't support over-alignment in this case
|
||||
#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE
|
||||
#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE)
|
||||
#endif
|
||||
|
||||
#ifdef AE_VCPP
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4324) // structure was padded due to __declspec(align())
|
||||
#pragma warning(disable : 4820) // padding was added
|
||||
#pragma warning(disable : 4127) // conditional expression is constant
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename T, size_t MAX_BLOCK_SIZE = 512>
|
||||
class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue {
|
||||
// Design: Based on a queue-of-queues. The low-level queues are just
|
||||
// circular buffers with front and tail indices indicating where the
|
||||
// next element to dequeue is and where the next element can be enqueued,
|
||||
// respectively. Each low-level queue is called a "block". Each block
|
||||
// wastes exactly one element's worth of space to keep the design simple
|
||||
// (if front == tail then the queue is empty, and can't be full).
|
||||
// The high-level queue is a circular linked list of blocks; again there
|
||||
// is a front and tail, but this time they are pointers to the blocks.
|
||||
// The front block is where the next element to be dequeued is, provided
|
||||
// the block is not empty. The back block is where elements are to be
|
||||
// enqueued, provided the block is not full.
|
||||
// The producer thread owns all the tail indices/pointers. The consumer
|
||||
// thread owns all the front indices/pointers. Both threads read each
|
||||
// other's variables, but only the owning thread updates them. E.g. After
|
||||
// the consumer reads the producer's tail, the tail may change before the
|
||||
// consumer is done dequeuing an object, but the consumer knows the tail
|
||||
// will never go backwards, only forwards.
|
||||
// If there is no room to enqueue an object, an additional block (of
|
||||
// equal size to the last block) is added. Blocks are never removed.
|
||||
|
||||
public:
|
||||
typedef T value_type;
|
||||
|
||||
// Constructs a queue that can hold at least `size` elements without further
|
||||
// allocations. If more than MAX_BLOCK_SIZE elements are requested,
|
||||
// then several blocks of MAX_BLOCK_SIZE each are reserved (including
|
||||
// at least one extra buffer block).
|
||||
AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15)
|
||||
#ifndef NDEBUG
|
||||
: enqueuing(false), dequeuing(false)
|
||||
#endif
|
||||
{
|
||||
assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) &&
|
||||
"MAX_BLOCK_SIZE must be a power of 2");
|
||||
assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2");
|
||||
|
||||
Block* firstBlock = nullptr;
|
||||
|
||||
largestBlockSize =
|
||||
ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block
|
||||
if (largestBlockSize > MAX_BLOCK_SIZE * 2) {
|
||||
// We need a spare block in case the producer is writing to a different block the
|
||||
// consumer is reading from, and wants to enqueue the maximum number of elements. We
|
||||
// also need a spare element in each block to avoid the ambiguity between front == tail
|
||||
// meaning "empty" and "full". So the effective number of slots that are guaranteed to
|
||||
// be usable at any time is the block size - 1 times the number of blocks - 1. Solving
|
||||
// for size and applying a ceiling to the division gives us (after simplifying):
|
||||
size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1);
|
||||
largestBlockSize = MAX_BLOCK_SIZE;
|
||||
Block* lastBlock = nullptr;
|
||||
for (size_t i = 0; i != initialBlockCount; ++i) {
|
||||
auto block = make_block(largestBlockSize);
|
||||
if (block == nullptr) {
|
||||
#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
|
||||
throw std::bad_alloc();
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
if (firstBlock == nullptr) {
|
||||
firstBlock = block;
|
||||
} else {
|
||||
lastBlock->next = block;
|
||||
}
|
||||
lastBlock = block;
|
||||
block->next = firstBlock;
|
||||
}
|
||||
} else {
|
||||
firstBlock = make_block(largestBlockSize);
|
||||
if (firstBlock == nullptr) {
|
||||
#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
|
||||
throw std::bad_alloc();
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
firstBlock->next = firstBlock;
|
||||
}
|
||||
frontBlock = firstBlock;
|
||||
tailBlock = firstBlock;
|
||||
|
||||
// Make sure the reader/writer threads will have the initialized memory setup above:
|
||||
fence(memory_order_sync);
|
||||
}
|
||||
|
||||
// Note: The queue should not be accessed concurrently while it's
|
||||
// being moved. It's up to the user to synchronize this.
|
||||
AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other)
|
||||
: frontBlock(other.frontBlock.load()), tailBlock(other.tailBlock.load()),
|
||||
largestBlockSize(other.largestBlockSize)
|
||||
#ifndef NDEBUG
|
||||
,
|
||||
enqueuing(false), dequeuing(false)
|
||||
#endif
|
||||
{
|
||||
other.largestBlockSize = 32;
|
||||
Block* b = other.make_block(other.largestBlockSize);
|
||||
if (b == nullptr) {
|
||||
#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
|
||||
throw std::bad_alloc();
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
b->next = b;
|
||||
other.frontBlock = b;
|
||||
other.tailBlock = b;
|
||||
}
|
||||
|
||||
// Note: The queue should not be accessed concurrently while it's
|
||||
// being moved. It's up to the user to synchronize this.
|
||||
ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN {
|
||||
Block* b = frontBlock.load();
|
||||
frontBlock = other.frontBlock.load();
|
||||
other.frontBlock = b;
|
||||
b = tailBlock.load();
|
||||
tailBlock = other.tailBlock.load();
|
||||
other.tailBlock = b;
|
||||
std::swap(largestBlockSize, other.largestBlockSize);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Note: The queue should not be accessed concurrently while it's
|
||||
// being deleted. It's up to the user to synchronize this.
|
||||
AE_NO_TSAN ~ReaderWriterQueue() {
|
||||
// Make sure we get the latest version of all variables from other CPUs:
|
||||
fence(memory_order_sync);
|
||||
|
||||
// Destroy any remaining objects in queue and free memory
|
||||
Block* frontBlock_ = frontBlock;
|
||||
Block* block = frontBlock_;
|
||||
do {
|
||||
Block* nextBlock = block->next;
|
||||
size_t blockFront = block->front;
|
||||
size_t blockTail = block->tail;
|
||||
|
||||
for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) {
|
||||
auto element = reinterpret_cast<T*>(block->data + i * sizeof(T));
|
||||
element->~T();
|
||||
(void)element;
|
||||
}
|
||||
|
||||
auto rawBlock = block->rawThis;
|
||||
block->~Block();
|
||||
std::free(rawBlock);
|
||||
block = nextBlock;
|
||||
} while (block != frontBlock_);
|
||||
}
|
||||
|
||||
// Enqueues a copy of element if there is room in the queue.
|
||||
// Returns true if the element was enqueued, false otherwise.
|
||||
// Does not allocate memory.
|
||||
AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN {
|
||||
return inner_enqueue<CannotAlloc>(element);
|
||||
}
|
||||
|
||||
// Enqueues a moved copy of element if there is room in the queue.
|
||||
// Returns true if the element was enqueued, false otherwise.
|
||||
// Does not allocate memory.
|
||||
AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN {
|
||||
return inner_enqueue<CannotAlloc>(std::forward<T>(element));
|
||||
}
|
||||
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
// Like try_enqueue() but with emplace semantics (i.e. construct-in-place).
|
||||
template <typename... Args>
|
||||
AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN {
|
||||
return inner_enqueue<CannotAlloc>(std::forward<Args>(args)...);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Enqueues a copy of element on the queue.
|
||||
// Allocates an additional block of memory if needed.
|
||||
// Only fails (returns false) if memory allocation fails.
|
||||
AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN {
|
||||
return inner_enqueue<CanAlloc>(element);
|
||||
}
|
||||
|
||||
// Enqueues a moved copy of element on the queue.
|
||||
// Allocates an additional block of memory if needed.
|
||||
// Only fails (returns false) if memory allocation fails.
|
||||
AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN {
|
||||
return inner_enqueue<CanAlloc>(std::forward<T>(element));
|
||||
}
|
||||
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
// Like enqueue() but with emplace semantics (i.e. construct-in-place).
|
||||
template <typename... Args>
|
||||
AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN {
|
||||
return inner_enqueue<CanAlloc>(std::forward<Args>(args)...);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Attempts to dequeue an element; if the queue is empty,
|
||||
// returns false instead. If the queue has at least one element,
|
||||
// moves front to result using operator=, then returns true.
|
||||
template <typename U>
|
||||
bool try_dequeue(U& result) AE_NO_TSAN {
|
||||
#ifndef NDEBUG
|
||||
ReentrantGuard guard(this->dequeuing);
|
||||
#endif
|
||||
|
||||
// High-level pseudocode:
|
||||
// Remember where the tail block is
|
||||
// If the front block has an element in it, dequeue it
|
||||
// Else
|
||||
// If front block was the tail block when we entered the function, return false
|
||||
// Else advance to next block and dequeue the item there
|
||||
|
||||
// Note that we have to use the value of the tail block from before we check if the front
|
||||
// block is full or not, in case the front block is empty and then, before we check if the
|
||||
// tail block is at the front block or not, the producer fills up the front block *and
|
||||
// moves on*, which would make us skip a filled block. Seems unlikely, but was consistently
|
||||
// reproducible in practice.
|
||||
// In order to avoid overhead in the common case, though, we do a double-checked pattern
|
||||
// where we have the fast path if the front block is not empty, then read the tail block,
|
||||
// then re-read the front block and check if it's not empty again, then check if the tail
|
||||
// block has advanced.
|
||||
|
||||
Block* frontBlock_ = frontBlock.load();
|
||||
size_t blockTail = frontBlock_->localTail;
|
||||
size_t blockFront = frontBlock_->front.load();
|
||||
|
||||
if (blockFront != blockTail ||
|
||||
blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
|
||||
fence(memory_order_acquire);
|
||||
|
||||
non_empty_front_block:
|
||||
// Front block not empty, dequeue from here
|
||||
auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
|
||||
result = std::move(*element);
|
||||
element->~T();
|
||||
|
||||
blockFront = (blockFront + 1) & frontBlock_->sizeMask;
|
||||
|
||||
fence(memory_order_release);
|
||||
frontBlock_->front = blockFront;
|
||||
} else if (frontBlock_ != tailBlock.load()) {
|
||||
fence(memory_order_acquire);
|
||||
|
||||
frontBlock_ = frontBlock.load();
|
||||
blockTail = frontBlock_->localTail = frontBlock_->tail.load();
|
||||
blockFront = frontBlock_->front.load();
|
||||
fence(memory_order_acquire);
|
||||
|
||||
if (blockFront != blockTail) {
|
||||
// Oh look, the front block isn't empty after all
|
||||
goto non_empty_front_block;
|
||||
}
|
||||
|
||||
// Front block is empty but there's another block ahead, advance to it
|
||||
Block* nextBlock = frontBlock_->next;
|
||||
// Don't need an acquire fence here since next can only ever be set on the tailBlock,
|
||||
// and we're not the tailBlock, and we did an acquire earlier after reading tailBlock
|
||||
// which ensures next is up-to-date on this CPU in case we recently were at tailBlock.
|
||||
|
||||
size_t nextBlockFront = nextBlock->front.load();
|
||||
size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();
|
||||
fence(memory_order_acquire);
|
||||
|
||||
// Since the tailBlock is only ever advanced after being written to,
|
||||
// we know there's for sure an element to dequeue on it
|
||||
assert(nextBlockFront != nextBlockTail);
|
||||
AE_UNUSED(nextBlockTail);
|
||||
|
||||
// We're done with this block, let the producer use it if it needs
|
||||
fence(memory_order_release); // Expose possibly pending changes to frontBlock->front
|
||||
// from last dequeue
|
||||
frontBlock = frontBlock_ = nextBlock;
|
||||
|
||||
compiler_fence(memory_order_release); // Not strictly needed
|
||||
|
||||
auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
|
||||
|
||||
result = std::move(*element);
|
||||
element->~T();
|
||||
|
||||
nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
|
||||
|
||||
fence(memory_order_release);
|
||||
frontBlock_->front = nextBlockFront;
|
||||
} else {
|
||||
// No elements in current block and no other block to advance to
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns a pointer to the front element in the queue (the one that
|
||||
// would be removed next by a call to `try_dequeue` or `pop`). If the
|
||||
// queue appears empty at the time the method is called, nullptr is
|
||||
// returned instead.
|
||||
// Must be called only from the consumer thread.
|
||||
T* peek() const AE_NO_TSAN {
|
||||
#ifndef NDEBUG
|
||||
ReentrantGuard guard(this->dequeuing);
|
||||
#endif
|
||||
// See try_dequeue() for reasoning
|
||||
|
||||
Block* frontBlock_ = frontBlock.load();
|
||||
size_t blockTail = frontBlock_->localTail;
|
||||
size_t blockFront = frontBlock_->front.load();
|
||||
|
||||
if (blockFront != blockTail ||
|
||||
blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
|
||||
fence(memory_order_acquire);
|
||||
non_empty_front_block:
|
||||
return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
|
||||
} else if (frontBlock_ != tailBlock.load()) {
|
||||
fence(memory_order_acquire);
|
||||
frontBlock_ = frontBlock.load();
|
||||
blockTail = frontBlock_->localTail = frontBlock_->tail.load();
|
||||
blockFront = frontBlock_->front.load();
|
||||
fence(memory_order_acquire);
|
||||
|
||||
if (blockFront != blockTail) {
|
||||
goto non_empty_front_block;
|
||||
}
|
||||
|
||||
Block* nextBlock = frontBlock_->next;
|
||||
|
||||
size_t nextBlockFront = nextBlock->front.load();
|
||||
fence(memory_order_acquire);
|
||||
|
||||
assert(nextBlockFront != nextBlock->tail.load());
|
||||
return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Removes the front element from the queue, if any, without returning it.
|
||||
// Returns true on success, or false if the queue appeared empty at the time
|
||||
// `pop` was called.
|
||||
bool pop() AE_NO_TSAN {
|
||||
#ifndef NDEBUG
|
||||
ReentrantGuard guard(this->dequeuing);
|
||||
#endif
|
||||
// See try_dequeue() for reasoning
|
||||
|
||||
Block* frontBlock_ = frontBlock.load();
|
||||
size_t blockTail = frontBlock_->localTail;
|
||||
size_t blockFront = frontBlock_->front.load();
|
||||
|
||||
if (blockFront != blockTail ||
|
||||
blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
|
||||
fence(memory_order_acquire);
|
||||
|
||||
non_empty_front_block:
|
||||
auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
|
||||
element->~T();
|
||||
|
||||
blockFront = (blockFront + 1) & frontBlock_->sizeMask;
|
||||
|
||||
fence(memory_order_release);
|
||||
frontBlock_->front = blockFront;
|
||||
} else if (frontBlock_ != tailBlock.load()) {
|
||||
fence(memory_order_acquire);
|
||||
frontBlock_ = frontBlock.load();
|
||||
blockTail = frontBlock_->localTail = frontBlock_->tail.load();
|
||||
blockFront = frontBlock_->front.load();
|
||||
fence(memory_order_acquire);
|
||||
|
||||
if (blockFront != blockTail) {
|
||||
goto non_empty_front_block;
|
||||
}
|
||||
|
||||
// Front block is empty but there's another block ahead, advance to it
|
||||
Block* nextBlock = frontBlock_->next;
|
||||
|
||||
size_t nextBlockFront = nextBlock->front.load();
|
||||
size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();
|
||||
fence(memory_order_acquire);
|
||||
|
||||
assert(nextBlockFront != nextBlockTail);
|
||||
AE_UNUSED(nextBlockTail);
|
||||
|
||||
fence(memory_order_release);
|
||||
frontBlock = frontBlock_ = nextBlock;
|
||||
|
||||
compiler_fence(memory_order_release);
|
||||
|
||||
auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
|
||||
element->~T();
|
||||
|
||||
nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
|
||||
|
||||
fence(memory_order_release);
|
||||
frontBlock_->front = nextBlockFront;
|
||||
} else {
|
||||
// No elements in current block and no other block to advance to
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns the approximate number of items currently in the queue.
|
||||
// Safe to call from both the producer and consumer threads.
|
||||
inline size_t size_approx() const AE_NO_TSAN {
|
||||
size_t result = 0;
|
||||
Block* frontBlock_ = frontBlock.load();
|
||||
Block* block = frontBlock_;
|
||||
do {
|
||||
fence(memory_order_acquire);
|
||||
size_t blockFront = block->front.load();
|
||||
size_t blockTail = block->tail.load();
|
||||
result += (blockTail - blockFront) & block->sizeMask;
|
||||
block = block->next.load();
|
||||
} while (block != frontBlock_);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the total number of items that could be enqueued without incurring
|
||||
// an allocation when this queue is empty.
|
||||
// Safe to call from both the producer and consumer threads.
|
||||
//
|
||||
// NOTE: The actual capacity during usage may be different depending on the consumer.
|
||||
// If the consumer is removing elements concurrently, the producer cannot add to
|
||||
// the block the consumer is removing from until it's completely empty, except in
|
||||
// the case where the producer was writing to the same block the consumer was
|
||||
// reading from the whole time.
|
||||
inline size_t max_capacity() const {
|
||||
size_t result = 0;
|
||||
Block* frontBlock_ = frontBlock.load();
|
||||
Block* block = frontBlock_;
|
||||
do {
|
||||
fence(memory_order_acquire);
|
||||
result += block->sizeMask;
|
||||
block = block->next.load();
|
||||
} while (block != frontBlock_);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
enum AllocationMode { CanAlloc, CannotAlloc };
|
||||
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
template <AllocationMode canAlloc, typename... Args>
|
||||
bool inner_enqueue(Args&&... args) AE_NO_TSAN
|
||||
#else
|
||||
template <AllocationMode canAlloc, typename U>
|
||||
bool inner_enqueue(U&& element) AE_NO_TSAN
|
||||
#endif
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
ReentrantGuard guard(this->enqueuing);
|
||||
#endif
|
||||
|
||||
// High-level pseudocode (assuming we're allowed to alloc a new block):
|
||||
// If room in tail block, add to tail
|
||||
// Else check next block
|
||||
// If next block is not the head block, enqueue on next block
|
||||
// Else create a new block and enqueue there
|
||||
// Advance tail to the block we just enqueued to
|
||||
|
||||
Block* tailBlock_ = tailBlock.load();
|
||||
size_t blockFront = tailBlock_->localFront;
|
||||
size_t blockTail = tailBlock_->tail.load();
|
||||
|
||||
size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask;
|
||||
if (nextBlockTail != blockFront ||
|
||||
nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) {
|
||||
fence(memory_order_acquire);
|
||||
// This block has room for at least one more element
|
||||
char* location = tailBlock_->data + blockTail * sizeof(T);
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
new (location) T(std::forward<Args>(args)...);
|
||||
#else
|
||||
new (location) T(std::forward<U>(element));
|
||||
#endif
|
||||
|
||||
fence(memory_order_release);
|
||||
tailBlock_->tail = nextBlockTail;
|
||||
} else {
|
||||
fence(memory_order_acquire);
|
||||
if (tailBlock_->next.load() != frontBlock) {
|
||||
// Note that the reason we can't advance to the frontBlock and start adding new
|
||||
// entries there is because if we did, then dequeue would stay in that block,
|
||||
// eventually reading the new values, instead of advancing to the next full block
|
||||
// (whose values were enqueued first and so should be consumed first).
|
||||
|
||||
fence(memory_order_acquire); // Ensure we get latest writes if we got the latest
|
||||
// frontBlock
|
||||
|
||||
// tailBlock is full, but there's a free block ahead, use it
|
||||
Block* tailBlockNext = tailBlock_->next.load();
|
||||
size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load();
|
||||
nextBlockTail = tailBlockNext->tail.load();
|
||||
fence(memory_order_acquire);
|
||||
|
||||
// This block must be empty since it's not the head block and we
|
||||
// go through the blocks in a circle
|
||||
assert(nextBlockFront == nextBlockTail);
|
||||
tailBlockNext->localFront = nextBlockFront;
|
||||
|
||||
char* location = tailBlockNext->data + nextBlockTail * sizeof(T);
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
new (location) T(std::forward<Args>(args)...);
|
||||
#else
|
||||
new (location) T(std::forward<U>(element));
|
||||
#endif
|
||||
|
||||
tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask;
|
||||
|
||||
fence(memory_order_release);
|
||||
tailBlock = tailBlockNext;
|
||||
} else if (canAlloc == CanAlloc) {
|
||||
// tailBlock is full and there's no free block ahead; create a new block
|
||||
auto newBlockSize =
|
||||
largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2;
|
||||
auto newBlock = make_block(newBlockSize);
|
||||
if (newBlock == nullptr) {
|
||||
// Could not allocate a block!
|
||||
return false;
|
||||
}
|
||||
largestBlockSize = newBlockSize;
|
||||
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
new (newBlock->data) T(std::forward<Args>(args)...);
|
||||
#else
|
||||
new (newBlock->data) T(std::forward<U>(element));
|
||||
#endif
|
||||
assert(newBlock->front == 0);
|
||||
newBlock->tail = newBlock->localTail = 1;
|
||||
|
||||
newBlock->next = tailBlock_->next.load();
|
||||
tailBlock_->next = newBlock;
|
||||
|
||||
// Might be possible for the dequeue thread to see the new tailBlock->next
|
||||
// *without* seeing the new tailBlock value, but this is OK since it can't
|
||||
// advance to the next block until tailBlock is set anyway (because the only
|
||||
// case where it could try to read the next is if it's already at the tailBlock,
|
||||
// and it won't advance past tailBlock in any circumstance).
|
||||
|
||||
fence(memory_order_release);
|
||||
tailBlock = newBlock;
|
||||
} else if (canAlloc == CannotAlloc) {
|
||||
// Would have had to allocate a new block to enqueue, but not allowed
|
||||
return false;
|
||||
} else {
|
||||
assert(false && "Should be unreachable code");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Disable copying
|
||||
ReaderWriterQueue(ReaderWriterQueue const&) {}
|
||||
|
||||
// Disable assignment
|
||||
ReaderWriterQueue& operator=(ReaderWriterQueue const&) {}
|
||||
|
||||
AE_FORCEINLINE static size_t ceilToPow2(size_t x) {
|
||||
// From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
--x;
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
x |= x >> 4;
|
||||
for (size_t i = 1; i < sizeof(size_t); i <<= 1) {
|
||||
x |= x >> (i << 3);
|
||||
}
|
||||
++x;
|
||||
return x;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN {
|
||||
const std::size_t alignment = std::alignment_of<U>::value;
|
||||
return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;
|
||||
}
|
||||
|
||||
private:
|
||||
#ifndef NDEBUG
|
||||
struct ReentrantGuard {
|
||||
AE_NO_TSAN ReentrantGuard(weak_atomic<bool>& _inSection) : inSection(_inSection) {
|
||||
assert(!inSection &&
|
||||
"Concurrent (or re-entrant) enqueue or dequeue operation detected (only one "
|
||||
"thread at a time may hold the producer or consumer role)");
|
||||
inSection = true;
|
||||
}
|
||||
|
||||
AE_NO_TSAN ~ReentrantGuard() {
|
||||
inSection = false;
|
||||
}
|
||||
|
||||
private:
|
||||
ReentrantGuard& operator=(ReentrantGuard const&);
|
||||
|
||||
private:
|
||||
weak_atomic<bool>& inSection;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct Block {
|
||||
// Avoid false-sharing by putting highly contended variables on their own cache lines
|
||||
weak_atomic<size_t> front; // (Atomic) Elements are read from here
|
||||
size_t localTail; // An uncontended shadow copy of tail, owned by the consumer
|
||||
|
||||
char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) -
|
||||
sizeof(size_t)];
|
||||
weak_atomic<size_t> tail; // (Atomic) Elements are enqueued here
|
||||
size_t localFront;
|
||||
|
||||
char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) -
|
||||
sizeof(size_t)]; // next isn't very contended, but we don't want it on
|
||||
// the same cache line as tail (which is)
|
||||
weak_atomic<Block*> next; // (Atomic)
|
||||
|
||||
char* data; // Contents (on heap) are aligned to T's alignment
|
||||
|
||||
const size_t sizeMask;
|
||||
|
||||
// size must be a power of two (and greater than 0)
|
||||
AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data)
|
||||
: front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data),
|
||||
sizeMask(_size - 1), rawThis(_rawThis) {}
|
||||
|
||||
private:
|
||||
// C4512 - Assignment operator could not be generated
|
||||
Block& operator=(Block const&);
|
||||
|
||||
public:
|
||||
char* rawThis;
|
||||
};
|
||||
|
||||
static Block* make_block(size_t capacity) AE_NO_TSAN {
|
||||
// Allocate enough memory for the block itself, as well as all the elements it will contain
|
||||
auto size = sizeof(Block) + std::alignment_of<Block>::value - 1;
|
||||
size += sizeof(T) * capacity + std::alignment_of<T>::value - 1;
|
||||
auto newBlockRaw = static_cast<char*>(std::malloc(size));
|
||||
if (newBlockRaw == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto newBlockAligned = align_for<Block>(newBlockRaw);
|
||||
auto newBlockData = align_for<T>(newBlockAligned + sizeof(Block));
|
||||
return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData);
|
||||
}
|
||||
|
||||
private:
|
||||
weak_atomic<Block*> frontBlock; // (Atomic) Elements are dequeued from this block
|
||||
|
||||
char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)];
|
||||
weak_atomic<Block*> tailBlock; // (Atomic) Elements are enqueued to this block
|
||||
|
||||
size_t largestBlockSize;
|
||||
|
||||
#ifndef NDEBUG
|
||||
weak_atomic<bool> enqueuing;
|
||||
mutable weak_atomic<bool> dequeuing;
|
||||
#endif
|
||||
};
|
||||
|
||||
// Like ReaderWriterQueue, but also providees blocking operations
|
||||
template <typename T, size_t MAX_BLOCK_SIZE = 512>
|
||||
class BlockingReaderWriterQueue {
|
||||
private:
|
||||
typedef ::Common::ReaderWriterQueue<T, MAX_BLOCK_SIZE> ReaderWriterQueue;
|
||||
|
||||
public:
|
||||
explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN
|
||||
: inner(size),
|
||||
sema(new spsc_sema::LightweightSemaphore()) {}
|
||||
|
||||
BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN
|
||||
: inner(std::move(other.inner)),
|
||||
sema(std::move(other.sema)) {}
|
||||
|
||||
BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN {
|
||||
std::swap(sema, other.sema);
|
||||
std::swap(inner, other.inner);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Enqueues a copy of element if there is room in the queue.
|
||||
// Returns true if the element was enqueued, false otherwise.
|
||||
// Does not allocate memory.
|
||||
AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN {
|
||||
if (inner.try_enqueue(element)) {
|
||||
sema->signal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enqueues a moved copy of element if there is room in the queue.
|
||||
// Returns true if the element was enqueued, false otherwise.
|
||||
// Does not allocate memory.
|
||||
AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN {
|
||||
if (inner.try_enqueue(std::forward<T>(element))) {
|
||||
sema->signal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
// Like try_enqueue() but with emplace semantics (i.e. construct-in-place).
|
||||
template <typename... Args>
|
||||
AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN {
|
||||
if (inner.try_emplace(std::forward<Args>(args)...)) {
|
||||
sema->signal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Enqueues a copy of element on the queue.
|
||||
// Allocates an additional block of memory if needed.
|
||||
// Only fails (returns false) if memory allocation fails.
|
||||
AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN {
|
||||
if (inner.enqueue(element)) {
|
||||
sema->signal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enqueues a moved copy of element on the queue.
|
||||
// Allocates an additional block of memory if needed.
|
||||
// Only fails (returns false) if memory allocation fails.
|
||||
AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN {
|
||||
if (inner.enqueue(std::forward<T>(element))) {
|
||||
sema->signal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
// Like enqueue() but with emplace semantics (i.e. construct-in-place).
|
||||
template <typename... Args>
|
||||
AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN {
|
||||
if (inner.emplace(std::forward<Args>(args)...)) {
|
||||
sema->signal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Attempts to dequeue an element; if the queue is empty,
|
||||
// returns false instead. If the queue has at least one element,
|
||||
// moves front to result using operator=, then returns true.
|
||||
template <typename U>
|
||||
bool try_dequeue(U& result) AE_NO_TSAN {
|
||||
if (sema->tryWait()) {
|
||||
bool success = inner.try_dequeue(result);
|
||||
assert(success);
|
||||
AE_UNUSED(success);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempts to dequeue an element; if the queue is empty,
|
||||
// waits until an element is available, then dequeues it.
|
||||
template <typename U>
|
||||
void wait_dequeue(U& result) AE_NO_TSAN {
|
||||
while (!sema->wait())
|
||||
;
|
||||
bool success = inner.try_dequeue(result);
|
||||
AE_UNUSED(result);
|
||||
assert(success);
|
||||
AE_UNUSED(success);
|
||||
}
|
||||
|
||||
// Attempts to dequeue an element; if the queue is empty,
|
||||
// waits until an element is available up to the specified timeout,
|
||||
// then dequeues it and returns true, or returns false if the timeout
|
||||
// expires before an element can be dequeued.
|
||||
// Using a negative timeout indicates an indefinite timeout,
|
||||
// and is thus functionally equivalent to calling wait_dequeue.
|
||||
template <typename U>
|
||||
bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN {
|
||||
if (!sema->wait(timeout_usecs)) {
|
||||
return false;
|
||||
}
|
||||
bool success = inner.try_dequeue(result);
|
||||
AE_UNUSED(result);
|
||||
assert(success);
|
||||
AE_UNUSED(success);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if __cplusplus > 199711L || _MSC_VER >= 1700
|
||||
// Attempts to dequeue an element; if the queue is empty,
|
||||
// waits until an element is available up to the specified timeout,
|
||||
// then dequeues it and returns true, or returns false if the timeout
|
||||
// expires before an element can be dequeued.
|
||||
// Using a negative timeout indicates an indefinite timeout,
|
||||
// and is thus functionally equivalent to calling wait_dequeue.
|
||||
template <typename U, typename Rep, typename Period>
|
||||
inline bool wait_dequeue_timed(U& result,
|
||||
std::chrono::duration<Rep, Period> const& timeout) AE_NO_TSAN {
|
||||
return wait_dequeue_timed(
|
||||
result, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Returns a pointer to the front element in the queue (the one that
|
||||
// would be removed next by a call to `try_dequeue` or `pop`). If the
|
||||
// queue appears empty at the time the method is called, nullptr is
|
||||
// returned instead.
|
||||
// Must be called only from the consumer thread.
|
||||
AE_FORCEINLINE T* peek() const AE_NO_TSAN {
|
||||
return inner.peek();
|
||||
}
|
||||
|
||||
// Removes the front element from the queue, if any, without returning it.
|
||||
// Returns true on success, or false if the queue appeared empty at the time
|
||||
// `pop` was called.
|
||||
AE_FORCEINLINE bool pop() AE_NO_TSAN {
|
||||
if (sema->tryWait()) {
|
||||
bool result = inner.pop();
|
||||
assert(result);
|
||||
AE_UNUSED(result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the approximate number of items currently in the queue.
|
||||
// Safe to call from both the producer and consumer threads.
|
||||
AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN {
|
||||
return sema->availableApprox();
|
||||
}
|
||||
|
||||
// Returns the total number of items that could be enqueued without incurring
|
||||
// an allocation when this queue is empty.
|
||||
// Safe to call from both the producer and consumer threads.
|
||||
//
|
||||
// NOTE: The actual capacity during usage may be different depending on the consumer.
|
||||
// If the consumer is removing elements concurrently, the producer cannot add to
|
||||
// the block the consumer is removing from until it's completely empty, except in
|
||||
// the case where the producer was writing to the same block the consumer was
|
||||
// reading from the whole time.
|
||||
AE_FORCEINLINE size_t max_capacity() const {
|
||||
return inner.max_capacity();
|
||||
}
|
||||
|
||||
private:
|
||||
// Disable copying & assignment
|
||||
BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) {}
|
||||
BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) {}
|
||||
|
||||
private:
|
||||
ReaderWriterQueue inner;
|
||||
std::unique_ptr<spsc_sema::LightweightSemaphore> sema;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#ifdef AE_VCPP
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
|
@ -161,7 +161,7 @@ struct Values {
|
|||
Category::LibraryApplet};
|
||||
Setting<AppletMode> photo_viewer_applet_mode{
|
||||
linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet};
|
||||
Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::HLE, "offline_web_applet_mode",
|
||||
Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode",
|
||||
Category::LibraryApplet};
|
||||
Setting<AppletMode> login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode",
|
||||
Category::LibraryApplet};
|
||||
|
|
@ -735,6 +735,7 @@ struct Values {
|
|||
Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers",
|
||||
Category::Debugging};
|
||||
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
|
||||
Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging};
|
||||
|
||||
// Miscellaneous
|
||||
Setting<std::string> log_filter{linkage, "*:Info", "log_filter", Category::Miscellaneous};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
// 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 <array>
|
||||
#include <vector>
|
||||
#include <mbedtls/cipher.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
|
|
@ -71,37 +75,37 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* des
|
|||
|
||||
mbedtls_cipher_reset(context);
|
||||
|
||||
// Only ECB strictly requires block sized chunks.
|
||||
std::size_t written = 0;
|
||||
if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) {
|
||||
if (mbedtls_cipher_get_cipher_mode(context) != MBEDTLS_MODE_ECB) {
|
||||
mbedtls_cipher_update(context, src, size, dest, &written);
|
||||
if (written != size) {
|
||||
LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
|
||||
size, written);
|
||||
}
|
||||
} else {
|
||||
const auto block_size = mbedtls_cipher_get_block_size(context);
|
||||
if (size < block_size) {
|
||||
std::vector<u8> block(block_size);
|
||||
std::memcpy(block.data(), src, size);
|
||||
Transcode(block.data(), block.size(), block.data(), op);
|
||||
std::memcpy(dest, block.data(), size);
|
||||
return;
|
||||
}
|
||||
if (written != size)
|
||||
LOG_WARNING(Crypto, "Not all data was processed requested={:016X}, actual={:016X}.", size, written);
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t offset = 0; offset < size; offset += block_size) {
|
||||
auto length = std::min<std::size_t>(block_size, size - offset);
|
||||
mbedtls_cipher_update(context, src + offset, length, dest + offset, &written);
|
||||
if (written != length) {
|
||||
if (length < block_size) {
|
||||
std::vector<u8> block(block_size);
|
||||
std::memcpy(block.data(), src + offset, length);
|
||||
Transcode(block.data(), block.size(), block.data(), op);
|
||||
std::memcpy(dest + offset, block.data(), length);
|
||||
return;
|
||||
}
|
||||
LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
|
||||
length, written);
|
||||
// ECB path: operate in block sized chunks and mirror previous behavior.
|
||||
const auto block_size = mbedtls_cipher_get_block_size(context);
|
||||
if (size < block_size) {
|
||||
std::vector<u8> block(block_size);
|
||||
std::memcpy(block.data(), src, size);
|
||||
Transcode(block.data(), block.size(), block.data(), op);
|
||||
std::memcpy(dest, block.data(), size);
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t offset = 0; offset < size; offset += block_size) {
|
||||
const auto length = std::min<std::size_t>(block_size, size - offset);
|
||||
mbedtls_cipher_update(context, src + offset, length, dest + offset, &written);
|
||||
if (written != length) {
|
||||
if (length < block_size) {
|
||||
std::vector<u8> block(block_size);
|
||||
std::memcpy(block.data(), src + offset, length);
|
||||
Transcode(block.data(), block.size(), block.data(), op);
|
||||
std::memcpy(dest + offset, block.data(), length);
|
||||
return;
|
||||
}
|
||||
LOG_WARNING(Crypto, "Not all data was processed requested={:016X}, actual={:016X}.", length, written);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -15,26 +18,36 @@ std::size_t CTREncryptionLayer::Read(u8* data, std::size_t length, std::size_t o
|
|||
if (length == 0)
|
||||
return 0;
|
||||
|
||||
const auto sector_offset = offset & 0xF;
|
||||
if (sector_offset == 0) {
|
||||
std::size_t total_read = 0;
|
||||
// Handle an initial misaligned portion if needed.
|
||||
if (auto const sector_offset = offset & 0xF; sector_offset != 0) {
|
||||
const std::size_t aligned_off = offset - sector_offset;
|
||||
std::array<u8, 0x10> block{};
|
||||
if (auto const got = base->Read(block.data(), block.size(), aligned_off); got != 0) {
|
||||
UpdateIV(base_offset + aligned_off);
|
||||
cipher.Transcode(block.data(), got, block.data(), Op::Decrypt);
|
||||
auto const to_copy = std::min<std::size_t>(length, got > sector_offset ? got - sector_offset : 0);
|
||||
if (to_copy > 0) {
|
||||
std::memcpy(data, block.data() + sector_offset, to_copy);
|
||||
data += to_copy;
|
||||
offset += to_copy;
|
||||
length -= to_copy;
|
||||
total_read += to_copy;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (length > 0) {
|
||||
// Now aligned to 0x10
|
||||
UpdateIV(base_offset + offset);
|
||||
std::vector<u8> raw = base->ReadBytes(length, offset);
|
||||
cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt);
|
||||
return length;
|
||||
const std::size_t got = base->Read(data, length, offset);
|
||||
if (got > 0) {
|
||||
cipher.Transcode(data, got, data, Op::Decrypt);
|
||||
total_read += got;
|
||||
}
|
||||
}
|
||||
|
||||
// offset does not fall on block boundary (0x10)
|
||||
std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset);
|
||||
UpdateIV(base_offset + offset - sector_offset);
|
||||
cipher.Transcode(block.data(), block.size(), block.data(), Op::Decrypt);
|
||||
std::size_t read = 0x10 - sector_offset;
|
||||
|
||||
if (length + sector_offset < 0x10) {
|
||||
std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
|
||||
return std::min<u64>(length, read);
|
||||
}
|
||||
std::memcpy(data, block.data() + sector_offset, read);
|
||||
return read + Read(data + read, length - read, offset + read);
|
||||
return total_read;
|
||||
}
|
||||
|
||||
void CTREncryptionLayer::SetIV(const IVData& iv_) {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include "core/crypto/xts_encryption_layer.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
constexpr u64 XTS_SECTOR_SIZE = 0x4000;
|
||||
constexpr std::size_t XTS_SECTOR_SIZE = 0x4000;
|
||||
|
||||
XTSEncryptionLayer::XTSEncryptionLayer(FileSys::VirtualFile base_, Key256 key_)
|
||||
: EncryptionLayer(std::move(base_)), cipher(key_, Mode::XTS) {}
|
||||
|
|
@ -19,41 +20,67 @@ std::size_t XTSEncryptionLayer::Read(u8* data, std::size_t length, std::size_t o
|
|||
if (length == 0)
|
||||
return 0;
|
||||
|
||||
const auto sector_offset = offset & 0x3FFF;
|
||||
if (sector_offset == 0) {
|
||||
if (length % XTS_SECTOR_SIZE == 0) {
|
||||
std::vector<u8> raw = base->ReadBytes(length, offset);
|
||||
cipher.XTSTranscode(raw.data(), raw.size(), data, offset / XTS_SECTOR_SIZE,
|
||||
std::size_t total_read = 0;
|
||||
// Handle initial unaligned part within a sector.
|
||||
if (auto const sector_offset = offset % XTS_SECTOR_SIZE; sector_offset != 0) {
|
||||
const std::size_t aligned_off = offset - sector_offset;
|
||||
std::array<u8, XTS_SECTOR_SIZE> block{};
|
||||
if (auto const got = base->Read(block.data(), XTS_SECTOR_SIZE, aligned_off); got > 0) {
|
||||
if (got < XTS_SECTOR_SIZE)
|
||||
std::memset(block.data() + got, 0, XTS_SECTOR_SIZE - got);
|
||||
cipher.XTSTranscode(block.data(), XTS_SECTOR_SIZE, block.data(), aligned_off / XTS_SECTOR_SIZE,
|
||||
XTS_SECTOR_SIZE, Op::Decrypt);
|
||||
return raw.size();
|
||||
|
||||
auto const to_copy = std::min<std::size_t>(length, got > sector_offset ? got - sector_offset : 0);
|
||||
if (to_copy > 0) {
|
||||
std::memcpy(data, block.data() + sector_offset, to_copy);
|
||||
data += to_copy;
|
||||
offset += to_copy;
|
||||
length -= to_copy;
|
||||
total_read += to_copy;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
if (length > XTS_SECTOR_SIZE) {
|
||||
const auto rem = length % XTS_SECTOR_SIZE;
|
||||
const auto read = length - rem;
|
||||
return Read(data, read, offset) + Read(data + read, rem, offset + read);
|
||||
}
|
||||
std::vector<u8> buffer = base->ReadBytes(XTS_SECTOR_SIZE, offset);
|
||||
if (buffer.size() < XTS_SECTOR_SIZE)
|
||||
buffer.resize(XTS_SECTOR_SIZE);
|
||||
cipher.XTSTranscode(buffer.data(), buffer.size(), buffer.data(), offset / XTS_SECTOR_SIZE,
|
||||
XTS_SECTOR_SIZE, Op::Decrypt);
|
||||
std::memcpy(data, buffer.data(), (std::min)(buffer.size(), length));
|
||||
return (std::min)(buffer.size(), length);
|
||||
}
|
||||
|
||||
// offset does not fall on block boundary (0x4000)
|
||||
std::vector<u8> block = base->ReadBytes(0x4000, offset - sector_offset);
|
||||
if (block.size() < XTS_SECTOR_SIZE)
|
||||
block.resize(XTS_SECTOR_SIZE);
|
||||
cipher.XTSTranscode(block.data(), block.size(), block.data(),
|
||||
(offset - sector_offset) / XTS_SECTOR_SIZE, XTS_SECTOR_SIZE, Op::Decrypt);
|
||||
const std::size_t read = XTS_SECTOR_SIZE - sector_offset;
|
||||
|
||||
if (length + sector_offset < XTS_SECTOR_SIZE) {
|
||||
std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
|
||||
return std::min<u64>(length, read);
|
||||
if (length > 0) {
|
||||
// Process aligned middle inplace, in sector sized multiples.
|
||||
while (length >= XTS_SECTOR_SIZE) {
|
||||
const std::size_t req = (length / XTS_SECTOR_SIZE) * XTS_SECTOR_SIZE;
|
||||
const std::size_t got = base->Read(data, req, offset);
|
||||
if (got == 0) {
|
||||
return total_read;
|
||||
}
|
||||
const std::size_t got_rounded = got - (got % XTS_SECTOR_SIZE);
|
||||
if (got_rounded > 0) {
|
||||
cipher.XTSTranscode(data, got_rounded, data, offset / XTS_SECTOR_SIZE, XTS_SECTOR_SIZE, Op::Decrypt);
|
||||
data += got_rounded;
|
||||
offset += got_rounded;
|
||||
length -= got_rounded;
|
||||
total_read += got_rounded;
|
||||
}
|
||||
// If we didn't get a full sector next, break to handle tail.
|
||||
if (got_rounded != got) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Handle tail within a sector, if any.
|
||||
if (length > 0) {
|
||||
std::array<u8, XTS_SECTOR_SIZE> block{};
|
||||
const std::size_t got = base->Read(block.data(), XTS_SECTOR_SIZE, offset);
|
||||
if (got > 0) {
|
||||
if (got < XTS_SECTOR_SIZE) {
|
||||
std::memset(block.data() + got, 0, XTS_SECTOR_SIZE - got);
|
||||
}
|
||||
cipher.XTSTranscode(block.data(), XTS_SECTOR_SIZE, block.data(),
|
||||
offset / XTS_SECTOR_SIZE, XTS_SECTOR_SIZE, Op::Decrypt);
|
||||
const std::size_t to_copy = std::min<std::size_t>(length, got);
|
||||
std::memcpy(data, block.data(), to_copy);
|
||||
total_read += to_copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::memcpy(data, block.data() + sector_offset, read);
|
||||
return read + Read(data + read, length - read, offset + read);
|
||||
return total_read;
|
||||
}
|
||||
} // namespace Core::Crypto
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include "common/alignment.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
|
||||
|
|
@ -83,32 +84,24 @@ size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) {
|
|||
std::memcpy(ctr.data(), m_iv.data(), IvSize);
|
||||
AddCounter(ctr.data(), IvSize, offset / BlockSize);
|
||||
|
||||
// Loop until all data is written.
|
||||
size_t remaining = size;
|
||||
s64 cur_offset = 0;
|
||||
|
||||
// Get a pooled buffer.
|
||||
std::vector<char> pooled_buffer(BlockSize);
|
||||
while (remaining > 0) {
|
||||
// Loop until all data is written using a pooled buffer residing on the stack (blocksize = 0x10)
|
||||
boost::container::static_vector<u8, BlockSize> pooled_buffer;
|
||||
for (size_t remaining = size; remaining > 0; ) {
|
||||
// Determine data we're writing and where.
|
||||
const size_t write_size = std::min(pooled_buffer.size(), remaining);
|
||||
u8* write_buf = reinterpret_cast<u8*>(pooled_buffer.data());
|
||||
auto const write_size = (std::min)(pooled_buffer.size(), remaining);
|
||||
u8* write_buf = pooled_buffer.data();
|
||||
|
||||
// Encrypt the data.
|
||||
// Encrypt the data and then write it.
|
||||
m_cipher->SetIV(ctr);
|
||||
m_cipher->Transcode(buffer, write_size, write_buf, Core::Crypto::Op::Encrypt);
|
||||
m_base_storage->Write(write_buf, write_size, offset);
|
||||
|
||||
// Write the encrypted data.
|
||||
m_base_storage->Write(write_buf, write_size, offset + cur_offset);
|
||||
|
||||
// Advance.
|
||||
cur_offset += write_size;
|
||||
// Advance next write chunk
|
||||
offset += write_size;
|
||||
remaining -= write_size;
|
||||
if (remaining > 0) {
|
||||
if (remaining > 0)
|
||||
AddCounter(ctr.data(), IvSize, write_size / BlockSize);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,13 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include "common/alignment.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_nca_header.h"
|
||||
#include "core/file_sys/fssystem/fssystem_utility.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
|
@ -41,18 +45,12 @@ AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key
|
|||
|
||||
size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
||||
// Allow zero-size reads.
|
||||
if (size == 0) {
|
||||
if (size == 0)
|
||||
return size;
|
||||
}
|
||||
|
||||
// Ensure buffer is valid.
|
||||
// Ensure buffer is valid and we can only read at block aligned offsets.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// We can only read at block aligned offsets.
|
||||
ASSERT(Common::IsAligned(offset, AesBlockSize));
|
||||
ASSERT(Common::IsAligned(size, AesBlockSize));
|
||||
|
||||
// Read the data.
|
||||
ASSERT(Common::IsAligned(offset, AesBlockSize) && Common::IsAligned(size, AesBlockSize));
|
||||
m_base_storage->Read(buffer, size, offset);
|
||||
|
||||
// Setup the counter.
|
||||
|
|
@ -60,25 +58,21 @@ size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
|||
std::memcpy(ctr.data(), m_iv.data(), IvSize);
|
||||
AddCounter(ctr.data(), IvSize, offset / m_block_size);
|
||||
|
||||
// Handle any unaligned data before the start.
|
||||
// Handle any unaligned data before the start; then read said data into a local pooled
|
||||
// buffer that resides on the stack, do not use the global memory allocator this is a
|
||||
// very tiny (512 bytes) buffer so should be fine to keep on the stack (Nca::XtsBlockSize wide buffer)
|
||||
size_t processed_size = 0;
|
||||
if ((offset % m_block_size) != 0) {
|
||||
// Decrypt into our pooled stack buffer (max bound = NCA::XtsBlockSize)
|
||||
boost::container::static_vector<u8, NcaHeader::XtsBlockSize> tmp_buf;
|
||||
// Determine the size of the pre-data read.
|
||||
const size_t skip_size =
|
||||
static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size));
|
||||
const size_t data_size = (std::min)(size, m_block_size - skip_size);
|
||||
|
||||
// Decrypt into a pooled buffer.
|
||||
{
|
||||
std::vector<char> tmp_buf(m_block_size, 0);
|
||||
std::memcpy(tmp_buf.data() + skip_size, buffer, data_size);
|
||||
|
||||
m_cipher->SetIV(ctr);
|
||||
m_cipher->Transcode(tmp_buf.data(), m_block_size, tmp_buf.data(),
|
||||
Core::Crypto::Op::Decrypt);
|
||||
|
||||
std::memcpy(buffer, tmp_buf.data() + skip_size, data_size);
|
||||
}
|
||||
auto const skip_size = size_t(offset - Common::AlignDown(offset, m_block_size));
|
||||
auto const data_size = (std::min)(size, m_block_size - skip_size);
|
||||
std::fill_n(tmp_buf.begin(), skip_size, u8{0});
|
||||
std::memcpy(tmp_buf.data() + skip_size, buffer, data_size);
|
||||
m_cipher->SetIV(ctr);
|
||||
m_cipher->Transcode(tmp_buf.data(), m_block_size, tmp_buf.data(), Core::Crypto::Op::Decrypt);
|
||||
std::memcpy(buffer, tmp_buf.data() + skip_size, data_size);
|
||||
|
||||
AddCounter(ctr.data(), IvSize, 1);
|
||||
processed_size += data_size;
|
||||
|
|
@ -86,20 +80,16 @@ size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
|||
}
|
||||
|
||||
// Decrypt aligned chunks.
|
||||
char* cur = reinterpret_cast<char*>(buffer) + processed_size;
|
||||
size_t remaining = size - processed_size;
|
||||
while (remaining > 0) {
|
||||
const size_t cur_size = (std::min)(m_block_size, remaining);
|
||||
|
||||
auto* cur = buffer + processed_size;
|
||||
for (size_t remaining = size - processed_size; remaining > 0; ) {
|
||||
auto const cur_size = (std::min)(m_block_size, remaining);
|
||||
m_cipher->SetIV(ctr);
|
||||
m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt);
|
||||
|
||||
auto* char_cur = reinterpret_cast<char*>(cur); //same repr cur - diff signedness
|
||||
m_cipher->Transcode(char_cur, cur_size, char_cur, Core::Crypto::Op::Decrypt);
|
||||
remaining -= cur_size;
|
||||
cur += cur_size;
|
||||
|
||||
AddCounter(ctr.data(), IvSize, 1);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -379,6 +382,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
|||
if (romfs_dir != nullptr)
|
||||
layers.emplace_back(std::make_shared<CachedVfsDirectory>(std::move(romfs_dir)));
|
||||
|
||||
// Support for romfslite introduced in Atmosphere 1.9.5
|
||||
auto romfslite_dir = FindSubdirectoryCaseless(subdir, "romfslite");
|
||||
if (romfslite_dir != nullptr)
|
||||
layers.emplace_back(std::make_shared<CachedVfsDirectory>(std::move(romfslite_dir)));
|
||||
|
||||
auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext");
|
||||
if (ext_dir != nullptr)
|
||||
layers_ext.emplace_back(std::make_shared<CachedVfsDirectory>(std::move(ext_dir)));
|
||||
|
|
@ -537,7 +545,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
if (layeredfs)
|
||||
AppendCommaIfNotEmpty(types, "LayeredExeFS");
|
||||
}
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")))
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")) ||
|
||||
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfslite")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "cheats")))
|
||||
AppendCommaIfNotEmpty(types, "Cheats");
|
||||
|
|
@ -563,7 +572,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) {
|
||||
AppendCommaIfNotEmpty(types, "LayeredExeFS");
|
||||
}
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs"))) {
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs")) ||
|
||||
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) {
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -509,6 +509,9 @@ std::vector<std::string> ProfileManager::FindOrphanedProfiles()
|
|||
good_uuids.emplace_back(uuid_string);
|
||||
}
|
||||
|
||||
// used for acnh, etc
|
||||
good_uuids.emplace_back("00000000000000000000000000000000");
|
||||
|
||||
// TODO: fetch save_id programmatically
|
||||
const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir)
|
||||
/ "user/save/0000000000000000";
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ void Controller::Initialize() {
|
|||
break;
|
||||
case ControllerAppletVersion::Version7:
|
||||
case ControllerAppletVersion::Version8:
|
||||
case ControllerAppletVersion::Version9:
|
||||
ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew));
|
||||
std::memcpy(&controller_user_arg_new, user_arg.data(), user_arg.size());
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -30,6 +33,7 @@ enum class ControllerAppletVersion : u32_le {
|
|||
Version5 = 0x5, // 6.0.0 - 7.0.1
|
||||
Version7 = 0x7, // 8.0.0 - 10.2.0
|
||||
Version8 = 0x8, // 11.0.0+
|
||||
Version9 = 0x9,
|
||||
};
|
||||
|
||||
enum class ControllerSupportMode : u8 {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -6,6 +9,7 @@
|
|||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
|
|
@ -232,6 +236,11 @@ WebBrowser::WebBrowser(Core::System& system_, std::shared_ptr<Applet> applet_,
|
|||
WebBrowser::~WebBrowser() = default;
|
||||
|
||||
void WebBrowser::Initialize() {
|
||||
if (Settings::values.disable_web_applet) {
|
||||
LOG_INFO(Service_AM, "Web Browser Applet disabled, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
FrontendApplet::Initialize();
|
||||
|
||||
LOG_INFO(Service_AM, "Initializing Web Browser Applet.");
|
||||
|
|
@ -295,6 +304,11 @@ void WebBrowser::ExecuteInteractive() {
|
|||
}
|
||||
|
||||
void WebBrowser::Execute() {
|
||||
if (Settings::values.disable_web_applet) {
|
||||
WebBrowserExit(WebExitReason::EndButtonPressed);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (web_arg_header.shim_kind) {
|
||||
case ShimKind::Shop:
|
||||
ExecuteShop();
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ ILibraryAppletAccessor::ILibraryAppletAccessor(Core::System& system_,
|
|||
{30, D<&ILibraryAppletAccessor::GetResult>, "GetResult"},
|
||||
{50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"},
|
||||
{60, D<&ILibraryAppletAccessor::PresetLibraryAppletGpuTimeSliceZero>, "PresetLibraryAppletGpuTimeSliceZero"},
|
||||
{90, D<&ILibraryAppletAccessor::Unknown90>, "Unknown90"},
|
||||
{100, D<&ILibraryAppletAccessor::PushInData>, "PushInData"},
|
||||
{101, D<&ILibraryAppletAccessor::PopOutData>, "PopOutData"},
|
||||
{102, nullptr, "PushExtraStorage"},
|
||||
|
|
@ -96,6 +97,11 @@ Result ILibraryAppletAccessor::Terminate() {
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ILibraryAppletAccessor::Unknown90() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ILibraryAppletAccessor::PushInData(SharedPointer<IStorage> storage) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
m_broker->GetInData().Push(storage);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -26,6 +29,7 @@ private:
|
|||
Result Start();
|
||||
Result RequestExit();
|
||||
Result Terminate();
|
||||
Result Unknown90();
|
||||
Result PushInData(SharedPointer<IStorage> storage);
|
||||
Result PopOutData(Out<SharedPointer<IStorage>> out_storage);
|
||||
Result PushInteractiveInData(SharedPointer<IStorage> storage);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -175,6 +178,7 @@ ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_, std::shared_
|
|||
{0, D<&ILibraryAppletCreator::CreateLibraryApplet>, "CreateLibraryApplet"},
|
||||
{1, nullptr, "TerminateAllLibraryApplets"},
|
||||
{2, nullptr, "AreAnyLibraryAppletsLeft"},
|
||||
{3, D<&ILibraryAppletCreator::CreateLibraryAppletEx>, "CreateLibraryAppletEx"},
|
||||
{10, D<&ILibraryAppletCreator::CreateStorage>, "CreateStorage"},
|
||||
{11, D<&ILibraryAppletCreator::CreateTransferMemoryStorage>, "CreateTransferMemoryStorage"},
|
||||
{12, D<&ILibraryAppletCreator::CreateHandleStorage>, "CreateHandleStorage"},
|
||||
|
|
@ -210,6 +214,32 @@ Result ILibraryAppletCreator::CreateLibraryApplet(
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ILibraryAppletCreator::CreateLibraryAppletEx(
|
||||
Out<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
|
||||
LibraryAppletMode library_applet_mode, u64 thread_id) {
|
||||
LOG_DEBUG(Service_AM, "called with applet_id={} applet_mode={} thread_id={}", applet_id,
|
||||
library_applet_mode, thread_id);
|
||||
|
||||
std::shared_ptr<ILibraryAppletAccessor> library_applet;
|
||||
if (ShouldCreateGuestApplet(applet_id)) {
|
||||
library_applet =
|
||||
CreateGuestApplet(system, m_window_system, m_applet, applet_id, library_applet_mode);
|
||||
}
|
||||
if (!library_applet) {
|
||||
library_applet =
|
||||
CreateFrontendApplet(system, m_window_system, m_applet, applet_id, library_applet_mode);
|
||||
}
|
||||
if (!library_applet) {
|
||||
LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
|
||||
R_THROW(ResultUnknown);
|
||||
}
|
||||
|
||||
// Applet is created, can now be launched.
|
||||
m_applet->library_applet_launchable_event.Signal();
|
||||
*out_library_applet_accessor = library_applet;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ILibraryAppletCreator::CreateStorage(Out<SharedPointer<IStorage>> out_storage, s64 size) {
|
||||
LOG_DEBUG(Service_AM, "called, size={}", size);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -24,6 +27,9 @@ private:
|
|||
Result CreateLibraryApplet(
|
||||
Out<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
|
||||
LibraryAppletMode library_applet_mode);
|
||||
Result CreateLibraryAppletEx(
|
||||
Out<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
|
||||
LibraryAppletMode library_applet_mode, u64 thread_id);
|
||||
Result CreateStorage(Out<SharedPointer<IStorage>> out_storage, s64 size);
|
||||
Result CreateTransferMemoryStorage(
|
||||
Out<SharedPointer<IStorage>> out_storage, bool is_writable, s64 size,
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
|||
{406, nullptr, "GetApplicationControlProperty"},
|
||||
{407, nullptr, "ListApplicationTitle"},
|
||||
{408, nullptr, "ListApplicationIcon"},
|
||||
{419, D<&IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground>, "RequestDownloadApplicationControlDataInBackground"},
|
||||
{502, nullptr, "RequestCheckGameCardRegistration"},
|
||||
{503, nullptr, "RequestGameCardRegistrationGoldPoint"},
|
||||
{504, nullptr, "RequestRegisterGameCard"},
|
||||
|
|
@ -210,6 +211,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
|||
{1703, nullptr, "GetApplicationViewDownloadErrorContext"},
|
||||
{1704, D<&IApplicationManagerInterface::GetApplicationViewWithPromotionInfo>, "GetApplicationViewWithPromotionInfo"},
|
||||
{1705, nullptr, "IsPatchAutoDeletableApplication"},
|
||||
{1706, D<&IApplicationManagerInterface::Unknown1706>, "Unknown1706"},
|
||||
{1800, nullptr, "IsNotificationSetupCompleted"},
|
||||
{1801, nullptr, "GetLastNotificationInfoCount"},
|
||||
{1802, nullptr, "ListLastNotificationInfo"},
|
||||
|
|
@ -309,6 +311,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
|||
{4022, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4022"},
|
||||
{4023, D<&IApplicationManagerInterface::Unknown4023>, "Unknown4023"},
|
||||
{4088, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4088"},
|
||||
{4053, D<&IApplicationManagerInterface::Unknown4053>, "Unknown4053"},
|
||||
{9999, nullptr, "GetApplicationCertificate"},
|
||||
};
|
||||
// clang-format on
|
||||
|
|
@ -526,6 +529,37 @@ Result IApplicationManagerInterface::GetApplicationTerminateResult(Out<Result> o
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground(
|
||||
u64 unk, u64 application_id) {
|
||||
LOG_WARNING(Service_NS, "(STUBBED), app={:016X} unk={}", application_id, unk);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::Unknown1706(
|
||||
OutBuffer<BufferAttr_HipcAutoSelect> out_buffer_58,
|
||||
InBuffer<BufferAttr_HipcMapAlias> in_buffer_8) {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) Unknown1706 called: out_size={} in_size={}",
|
||||
out_buffer_58.size(), in_buffer_8.size());
|
||||
|
||||
if (out_buffer_58.size() < 0x58 || in_buffer_8.size() < 0x8) {
|
||||
R_THROW(ResultUnknown);
|
||||
}
|
||||
|
||||
u64 application_id = 0;
|
||||
std::memcpy(&application_id, in_buffer_8.data(), sizeof(u64));
|
||||
|
||||
ApplicationView view{};
|
||||
view.application_id = application_id;
|
||||
view.unk = 0x70000;
|
||||
view.flags = 0x401f17;
|
||||
|
||||
std::memset(out_buffer_58.data(), 0, out_buffer_58.size());
|
||||
std::memcpy(out_buffer_58.data(), &view, sizeof(ApplicationView));
|
||||
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::Unknown4022(
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) called");
|
||||
|
|
@ -539,4 +573,9 @@ Result IApplicationManagerInterface::Unknown4023(Out<u64> out_result) {
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::Unknown4053() {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) called.");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace Service::NS
|
||||
|
|
|
|||
|
|
@ -55,6 +55,12 @@ public:
|
|||
Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id);
|
||||
Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result Unknown4023(Out<u64> out_result);
|
||||
Result Unknown4053();
|
||||
|
||||
Result RequestDownloadApplicationControlDataInBackground(u64 unk,
|
||||
u64 application_id);
|
||||
Result Unknown1706(OutBuffer<BufferAttr_HipcAutoSelect> out_buffer_58,
|
||||
InBuffer<BufferAttr_HipcMapAlias> in_buffer_8);
|
||||
|
||||
private:
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -81,10 +84,11 @@ static_assert(sizeof(PromotionInfo) == 0x20, "PromotionInfo has incorrect size."
|
|||
|
||||
/// NsApplicationViewWithPromotionInfo
|
||||
struct ApplicationViewWithPromotionInfo {
|
||||
ApplicationView view; ///< \ref NsApplicationView
|
||||
PromotionInfo promotion; ///< \ref NsPromotionInfo
|
||||
ApplicationView view; ///< \ref NsApplicationView
|
||||
PromotionInfo promotion; ///< \ref NsPromotionInfo
|
||||
std::array<u8, 0x8> padding{}; ///< Extra padding for newer HOS versions
|
||||
};
|
||||
static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x70,
|
||||
static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x78,
|
||||
"ApplicationViewWithPromotionInfo has incorrect size.");
|
||||
|
||||
struct ApplicationOccupiedSizeEntity {
|
||||
|
|
@ -113,4 +117,10 @@ struct Uid {
|
|||
};
|
||||
static_assert(sizeof(Uid) == 0x10, "Uid has incorrect size.");
|
||||
|
||||
struct ApplicationDisplayData {
|
||||
std::array<char, 0x200> application_name;
|
||||
std::array<char, 0x100> developer_name;
|
||||
};
|
||||
static_assert(sizeof(ApplicationDisplayData) == 0x300, "ApplicationDisplayData has incorrect size.");
|
||||
|
||||
} // namespace Service::NS
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -7,6 +10,7 @@
|
|||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/ns/language.h"
|
||||
#include "core/hle/service/ns/ns_types.h"
|
||||
#include "core/hle/service/ns/ns_results.h"
|
||||
#include "core/hle/service/ns/read_only_application_control_data_interface.h"
|
||||
#include "core/hle/service/set/settings_server.h"
|
||||
|
|
@ -23,6 +27,7 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
|
|||
{2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"},
|
||||
{3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
|
||||
{4, nullptr, "SelectApplicationDesiredLanguage"},
|
||||
{5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationDisplayData>, "GetApplicationDisplayData"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
|
@ -119,4 +124,33 @@ Result IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLan
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IReadOnlyApplicationControlDataInterface::GetApplicationDisplayData(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u64> out_size, u64 language_code,
|
||||
u64 application_id) {
|
||||
LOG_INFO(Service_NS, "called with application_id={:016X}, language_code={:016X}",
|
||||
application_id, language_code);
|
||||
|
||||
constexpr u64 payload_size = sizeof(ApplicationDisplayData);
|
||||
|
||||
if (out_buffer.size() < payload_size) {
|
||||
LOG_ERROR(Service_NS, "output buffer is too small! (actual={}, expected_min={})",
|
||||
out_buffer.size(), payload_size);
|
||||
R_THROW(ResultUnknown);
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
ApplicationDisplayData display_data{};
|
||||
|
||||
std::memset(display_data.application_name.data(), 0, display_data.application_name.size());
|
||||
std::memset(display_data.developer_name.data(), 0, display_data.developer_name.size());
|
||||
|
||||
std::memcpy(out_buffer.data(), &display_data, payload_size);
|
||||
*out_size = payload_size;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace Service::NS
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/ns/language.h"
|
||||
#include "core/hle/service/ns/ns_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
|
|
@ -16,7 +19,6 @@ public:
|
|||
explicit IReadOnlyApplicationControlDataInterface(Core::System& system_);
|
||||
~IReadOnlyApplicationControlDataInterface() override;
|
||||
|
||||
public:
|
||||
Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
Out<u32> out_actual_size,
|
||||
ApplicationControlSource application_control_source,
|
||||
|
|
@ -25,6 +27,10 @@ public:
|
|||
u32 supported_languages);
|
||||
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
|
||||
ApplicationLanguage application_language);
|
||||
|
||||
Result GetApplicationDisplayData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
Out<u64> out_size, u64 language_code,
|
||||
u64 application_id);
|
||||
};
|
||||
|
||||
} // namespace Service::NS
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/ns/read_only_application_record_interface.h"
|
||||
#include "core/hle/service/ns/ns_types.h"
|
||||
#include "core/hle/service/ns/application_manager_interface.h"
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
|
|
@ -13,6 +18,8 @@ IReadOnlyApplicationRecordInterface::IReadOnlyApplicationRecordInterface(Core::S
|
|||
{1, nullptr, "NotifyApplicationFailure"},
|
||||
{2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>,
|
||||
"IsDataCorruptedResult"},
|
||||
{3, D<&IReadOnlyApplicationRecordInterface::ListApplicationRecord>,
|
||||
"ListApplicationRecord"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
|
@ -35,4 +42,14 @@ Result IReadOnlyApplicationRecordInterface::IsDataCorruptedResult(
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IReadOnlyApplicationRecordInterface::ListApplicationRecord(
|
||||
OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> out_count,
|
||||
s32 entry_offset) {
|
||||
LOG_DEBUG(Service_NS, "delegating to IApplicationManagerInterface::ListApplicationRecord, offset={} limit={}",
|
||||
entry_offset, out_records.size());
|
||||
|
||||
R_RETURN(IApplicationManagerInterface(system).ListApplicationRecord(out_records, out_count,
|
||||
entry_offset));
|
||||
}
|
||||
|
||||
} // namespace Service::NS
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -5,6 +8,7 @@
|
|||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/ns/ns_types.h"
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
|
|
@ -17,6 +21,9 @@ public:
|
|||
private:
|
||||
Result HasApplicationRecord(Out<bool> out_has_application_record, u64 program_id);
|
||||
Result IsDataCorruptedResult(Out<bool> out_is_data_corrupted_result, Result result);
|
||||
Result ListApplicationRecord(
|
||||
OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> out_count,
|
||||
s32 entry_offset);
|
||||
};
|
||||
|
||||
} // namespace Service::NS
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili
|
|||
{1016, nullptr, "ConfirmShowNewsPermission"},
|
||||
{1017, D<&IParentalControlService::EndFreeCommunication>, "EndFreeCommunication"},
|
||||
{1018, D<&IParentalControlService::IsFreeCommunicationAvailable>, "IsFreeCommunicationAvailable"},
|
||||
{1019, D<&IParentalControlService::ConfirmLaunchApplicationPermission>, "ConfirmLaunchApplicationPermission"},
|
||||
{1031, D<&IParentalControlService::IsRestrictionEnabled>, "IsRestrictionEnabled"},
|
||||
{1032, D<&IParentalControlService::GetSafetyLevel>, "GetSafetyLevel"},
|
||||
{1033, nullptr, "SetSafetyLevel"},
|
||||
|
|
|
|||
3
src/dynarmic/externals/cpmfile.json
vendored
|
|
@ -13,6 +13,9 @@
|
|||
"hash": "f943bac39c1879986decad7a442ff4288eaeca4a2907684c7914e115a55ecc43c2782ded85c0835763fe04e40d5c82220ce864423e489e648e408a84f54dc4f3",
|
||||
"options": [
|
||||
"MCL_INSTALL OFF"
|
||||
],
|
||||
"patches": [
|
||||
"0001-assert-macro.patch"
|
||||
]
|
||||
},
|
||||
"zycore": {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2022 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
|
|
@ -238,7 +241,7 @@ EmittedBlockInfo EmitArm64(oaknut::CodeGenerator& code, IR::Block block, const E
|
|||
#undef A32OPC
|
||||
#undef A64OPC
|
||||
default:
|
||||
ASSERT_FALSE("Invalid opcode: {}", inst->GetOpcode());
|
||||
ASSERT_FALSE("Invalid opcode: {:x}", std::size_t(inst->GetOpcode()));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2024 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
|
|
@ -140,7 +143,7 @@ EmittedBlockInfo EmitRV64(biscuit::Assembler& as, IR::Block block, const EmitCon
|
|||
#undef A32OPC
|
||||
#undef A64OPC
|
||||
default:
|
||||
ASSERT_FALSE("Invalid opcode: {}", inst->GetOpcode());
|
||||
ASSERT_FALSE("Invalid opcode: {:x}", std::size_t(inst->GetOpcode()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
|
|||
#undef OPCODE
|
||||
#undef A32OPC
|
||||
#undef A64OPC
|
||||
default: [[unlikely]] ASSERT_FALSE("Invalid opcode: {}", inst->GetOpcode());
|
||||
default: [[unlikely]] ASSERT_FALSE("Invalid opcode: {:x}", std::size_t(inst->GetOpcode()));
|
||||
}
|
||||
reg_alloc.EndOfAllocScope();
|
||||
func(reg_alloc);
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ A64EmitX64::BlockDescriptor A64EmitX64::Emit(IR::Block& block) noexcept {
|
|||
#undef A32OPC
|
||||
#undef A64OPC
|
||||
default: [[unlikely]] {
|
||||
ASSERT_MSG(false, "Invalid opcode: {}", opcode);
|
||||
ASSERT_MSG(false, "Invalid opcode: {:x}", std::size_t(opcode));
|
||||
goto finish_this_inst;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ std::optional<EmitX64::BlockDescriptor> EmitX64::GetBasicBlock(IR::LocationDescr
|
|||
}
|
||||
|
||||
void EmitX64::EmitInvalid(EmitContext&, IR::Inst* inst) {
|
||||
ASSERT_MSG(false, "Invalid opcode: {}", inst->GetOpcode());
|
||||
ASSERT_MSG(false, "Invalid opcode: {:x}", std::size_t(inst->GetOpcode()));
|
||||
}
|
||||
|
||||
void EmitX64::EmitVoid(EmitContext&, IR::Inst*) {
|
||||
|
|
|
|||
|
|
@ -654,11 +654,3 @@ constexpr bool MayGetNZCVFromOp(const Opcode op) noexcept {
|
|||
}
|
||||
|
||||
} // namespace Dynarmic::IR
|
||||
|
||||
template<>
|
||||
struct fmt::formatter<Dynarmic::IR::Opcode> : fmt::formatter<std::string> {
|
||||
template<typename FormatContext>
|
||||
auto format(Dynarmic::IR::Opcode op, FormatContext& ctx) const {
|
||||
return formatter<std::string>::format(Dynarmic::IR::GetNameOf(op), ctx);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -7,6 +10,7 @@ add_library(frontend_common STATIC
|
|||
content_manager.h
|
||||
firmware_manager.h
|
||||
firmware_manager.cpp
|
||||
data_manager.h data_manager.cpp
|
||||
)
|
||||
|
||||
create_target_directory_groups(frontend_common)
|
||||
|
|
|
|||
77
src/frontend_common/data_manager.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "data_manager.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace FrontendCommon::DataManager {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const std::string GetDataDir(DataDir dir, const std::string &user_id)
|
||||
{
|
||||
const fs::path nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir);
|
||||
|
||||
switch (dir) {
|
||||
case DataDir::Saves:
|
||||
return (nand_dir / "user" / "save" / "0000000000000000" / user_id).string();
|
||||
case DataDir::UserNand:
|
||||
return (nand_dir / "user" / "Contents" / "registered").string();
|
||||
case DataDir::SysNand:
|
||||
// NB: do NOT delete save
|
||||
// that contains profile data and other stuff
|
||||
return (nand_dir / "system" / "Contents" / "registered").string();
|
||||
case DataDir::Mods:
|
||||
return Common::FS::GetEdenPathString(Common::FS::EdenPath::LoadDir);
|
||||
case DataDir::Shaders:
|
||||
return Common::FS::GetEdenPathString(Common::FS::EdenPath::ShaderDir);
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
u64 ClearDir(DataDir dir, const std::string &user_id)
|
||||
{
|
||||
fs::path data_dir = GetDataDir(dir, user_id);
|
||||
u64 result = fs::remove_all(data_dir);
|
||||
|
||||
// mkpath at the end just so it actually exists
|
||||
fs::create_directories(data_dir);
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::string ReadableBytesSize(u64 size)
|
||||
{
|
||||
static constexpr std::array units{"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
|
||||
if (size == 0) {
|
||||
return "0 B";
|
||||
}
|
||||
|
||||
const int digit_groups = (std::min) (static_cast<int>(std::log10(size) / std::log10(1024)),
|
||||
static_cast<int>(units.size()));
|
||||
return fmt::format("{:.1f} {}", size / std::pow(1024, digit_groups), units[digit_groups]);
|
||||
}
|
||||
|
||||
u64 DataDirSize(DataDir dir)
|
||||
{
|
||||
fs::path data_dir = GetDataDir(dir);
|
||||
u64 size = 0;
|
||||
|
||||
if (!fs::exists(data_dir))
|
||||
return 0;
|
||||
|
||||
for (const auto& entry : fs::recursive_directory_iterator(data_dir)) {
|
||||
if (!entry.is_directory()) {
|
||||
size += entry.file_size();
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
24
src/frontend_common/data_manager.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef DATA_MANAGER_H
|
||||
#define DATA_MANAGER_H
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include <string>
|
||||
|
||||
namespace FrontendCommon::DataManager {
|
||||
|
||||
enum class DataDir { Saves, UserNand, SysNand, Mods, Shaders };
|
||||
|
||||
const std::string GetDataDir(DataDir dir, const std::string &user_id = "");
|
||||
|
||||
u64 ClearDir(DataDir dir, const std::string &user_id = "");
|
||||
|
||||
const std::string ReadableBytesSize(u64 size);
|
||||
|
||||
u64 DataDirSize(DataDir dir);
|
||||
|
||||
}; // namespace FrontendCommon::DataManager
|
||||
|
||||
#endif // DATA_MANAGER_H
|
||||
|
|
@ -763,14 +763,15 @@ void EmulatedController::StartMotionCalibration() {
|
|||
}
|
||||
}
|
||||
|
||||
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) {
|
||||
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
|
||||
Common::UUID uuid) {
|
||||
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
|
||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
||||
|
||||
if (index >= controller.button_values.size()) {
|
||||
|
||||
if (index >= controller.button_values.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
std::unique_lock lock{mutex};
|
||||
bool value_changed = false;
|
||||
const auto new_status = TransformToButton(callback);
|
||||
|
|
@ -923,9 +924,13 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
|
|||
|
||||
lock.unlock();
|
||||
|
||||
if (player.connected) {
|
||||
Connect();
|
||||
if (!is_connected && !controller_connected[player_index]) {
|
||||
if (player.connected) {
|
||||
Connect();
|
||||
controller_connected[player_index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
TriggerOnChange(ControllerTriggerType::Button, true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include "common/settings.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "hid_core/frontend/motion_input.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/hid_types.h"
|
||||
#include "hid_core/irsensor/irs_types.h"
|
||||
|
||||
|
|
@ -588,6 +589,7 @@ private:
|
|||
std::array<VibrationValue, 2> last_vibration_value{DEFAULT_VIBRATION_VALUE,
|
||||
DEFAULT_VIBRATION_VALUE};
|
||||
std::array<std::chrono::steady_clock::time_point, 2> last_vibration_timepoint{};
|
||||
std::array<bool, HIDCore::available_controllers - 2> controller_connected{};
|
||||
|
||||
// Temporary values to avoid doing changes while the controller is in configuring mode
|
||||
NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
|
@ -159,6 +162,17 @@ struct NpadGcTriggerState {
|
|||
};
|
||||
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
|
||||
|
||||
// This is nn::hid::NpadCondition (global controller condition structure)
|
||||
struct NpadCondition {
|
||||
u32 _00{};
|
||||
u32 is_initialized{1};
|
||||
u32 hold_type{static_cast<u32>(NpadJoyHoldType::Horizontal)};
|
||||
u32 is_valid{1};
|
||||
};
|
||||
static_assert(sizeof(NpadCondition) == 0x10, "NpadCondition is an invalid size");
|
||||
|
||||
|
||||
|
||||
// This is nn::hid::NpadSystemProperties
|
||||
struct NPadSystemProperties {
|
||||
union {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -44,8 +47,7 @@ struct Lifo {
|
|||
buffer_count++;
|
||||
}
|
||||
buffer_tail = GetNextEntryIndex();
|
||||
const auto& previous_entry = ReadPreviousEntry();
|
||||
entries[buffer_tail].sampling_number = previous_entry.sampling_number + 1;
|
||||
entries[buffer_tail].sampling_number = new_state.sampling_number << 1;
|
||||
entries[buffer_tail].state = new_state;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
|
@ -201,7 +204,9 @@ static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20,
|
|||
|
||||
// This is nn::hid::detail::SharedMemoryFormat
|
||||
struct SharedMemoryFormat {
|
||||
void Initialize() {}
|
||||
void Initialize() {
|
||||
npad_condition = NpadCondition{};
|
||||
}
|
||||
|
||||
DebugPadSharedMemoryFormat debug_pad;
|
||||
TouchScreenSharedMemoryFormat touch_screen;
|
||||
|
|
@ -218,7 +223,9 @@ struct SharedMemoryFormat {
|
|||
ConsoleSixAxisSensorSharedMemoryFormat console;
|
||||
INSERT_PADDING_BYTES(0x19E0);
|
||||
MouseSharedMemoryFormat debug_mouse;
|
||||
INSERT_PADDING_BYTES(0x2000);
|
||||
INSERT_PADDING_BYTES(0x200);
|
||||
NpadCondition npad_condition;
|
||||
INSERT_PADDING_BYTES(0x1DF0);
|
||||
};
|
||||
static_assert(offsetof(SharedMemoryFormat, debug_pad) == 0x0, "debug_pad has wrong offset");
|
||||
static_assert(offsetof(SharedMemoryFormat, touch_screen) == 0x400, "touch_screen has wrong offset");
|
||||
|
|
@ -236,6 +243,8 @@ static_assert(offsetof(SharedMemoryFormat, npad) == 0x9A00, "npad has wrong offs
|
|||
static_assert(offsetof(SharedMemoryFormat, gesture) == 0x3BA00, "gesture has wrong offset");
|
||||
static_assert(offsetof(SharedMemoryFormat, console) == 0x3C200, "console has wrong offset");
|
||||
static_assert(offsetof(SharedMemoryFormat, debug_mouse) == 0x3DC00, "debug_mouse has wrong offset");
|
||||
static_assert(offsetof(SharedMemoryFormat, npad_condition) == 0x3E200,
|
||||
"npad_condition has wrong offset");
|
||||
static_assert(sizeof(SharedMemoryFormat) == 0x40000, "SharedMemoryFormat is an invalid size");
|
||||
|
||||
} // namespace Service::HID
|
||||
|
|
|
|||
|
|
@ -1,50 +1,82 @@
|
|||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
||||
|
||||
add_library(qt_common STATIC
|
||||
qt_common.h
|
||||
qt_common.cpp
|
||||
|
||||
uisettings.cpp
|
||||
uisettings.h
|
||||
config/uisettings.cpp
|
||||
config/uisettings.h
|
||||
config/qt_config.cpp
|
||||
config/qt_config.h
|
||||
config/shared_translation.cpp
|
||||
config/shared_translation.h
|
||||
|
||||
qt_config.cpp
|
||||
qt_config.h
|
||||
util/path.h util/path.cpp
|
||||
util/game.h util/game.cpp
|
||||
util/meta.h util/meta.cpp
|
||||
util/content.h util/content.cpp
|
||||
util/rom.h util/rom.cpp
|
||||
util/applet.h util/applet.cpp
|
||||
util/compress.h util/compress.cpp
|
||||
|
||||
shared_translation.cpp
|
||||
shared_translation.h
|
||||
qt_path_util.h qt_path_util.cpp
|
||||
qt_game_util.h qt_game_util.cpp
|
||||
qt_frontend_util.h qt_frontend_util.cpp
|
||||
qt_meta.h qt_meta.cpp
|
||||
qt_content_util.h qt_content_util.cpp
|
||||
qt_rom_util.h qt_rom_util.cpp
|
||||
qt_applet_util.h qt_applet_util.cpp
|
||||
qt_progress_dialog.h qt_progress_dialog.cpp
|
||||
abstract/qt_frontend_util.h abstract/qt_frontend_util.cpp
|
||||
abstract/qt_progress_dialog.h abstract/qt_progress_dialog.cpp
|
||||
|
||||
qt_string_lookup.h
|
||||
qt_compat.h
|
||||
|
||||
discord/discord.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(qt_common)
|
||||
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
target_sources(qt_common PRIVATE
|
||||
discord/discord_impl.cpp
|
||||
discord/discord_impl.h
|
||||
)
|
||||
target_link_libraries(qt_common PUBLIC DiscordRPC::discord-rpc Qt6::Network)
|
||||
target_compile_definitions(qt_common PUBLIC USE_DISCORD_PRESENCE)
|
||||
endif()
|
||||
|
||||
# TODO(crueter)
|
||||
if (ENABLE_QT)
|
||||
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(qt_common PUBLIC
|
||||
# Use QStringBuilder for string concatenation to reduce
|
||||
# the overall number of temporary strings created.
|
||||
QT_USE_QSTRINGBUILDER
|
||||
|
||||
# Disable implicit conversions from/to C strings
|
||||
QT_NO_CAST_FROM_ASCII
|
||||
QT_NO_CAST_TO_ASCII
|
||||
|
||||
# Disable implicit type narrowing in signal/slot connect() calls.
|
||||
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
|
||||
|
||||
# Disable unsafe overloads of QProcess' start() function.
|
||||
QT_NO_PROCESS_COMBINED_ARGUMENT_START
|
||||
|
||||
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
|
||||
QT_NO_URL_CAST_FROM_STRING
|
||||
)
|
||||
|
||||
add_subdirectory(externals)
|
||||
|
||||
target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip)
|
||||
target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip)
|
||||
target_link_libraries(qt_common PUBLIC frozen::frozen)
|
||||
|
||||
if (NOT APPLE AND ENABLE_OPENGL)
|
||||
target_compile_definitions(qt_common PUBLIC HAS_OPENGL)
|
||||
endif()
|
||||
|
||||
if (NOT WIN32)
|
||||
target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
if (UNIX AND NOT APPLE)
|
||||
if (TARGET Qt6::GuiPrivate)
|
||||
target_link_libraries(qt_common PRIVATE Qt6::GuiPrivate)
|
||||
else()
|
||||
target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -32,4 +32,15 @@ const QString GetOpenFileName(const QString &title,
|
|||
#endif
|
||||
}
|
||||
|
||||
const QString GetSaveFileName(const QString &title,
|
||||
const QString &dir,
|
||||
const QString &filter,
|
||||
QString *selectedFilter,
|
||||
Options options)
|
||||
{
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
return QFileDialog::getSaveFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
|
|
@ -97,10 +97,10 @@ Q_ENUM_NS(Icon)
|
|||
|
||||
// TODO(crueter) widgets-less impl, choices et al.
|
||||
StandardButton ShowMessage(Icon icon,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
StandardButtons buttons = StandardButton::NoButton,
|
||||
QObject *parent = nullptr);
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
StandardButtons buttons = StandardButton::NoButton,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
#define UTIL_OVERRIDES(level) \
|
||||
inline StandardButton level(QObject *parent, \
|
||||
|
|
@ -110,21 +110,6 @@ StandardButton ShowMessage(Icon icon,
|
|||
{ \
|
||||
return ShowMessage(Icon::level, title, text, buttons, parent); \
|
||||
} \
|
||||
inline StandardButton level(QObject *parent, \
|
||||
const char *title, \
|
||||
const char *text, \
|
||||
StandardButtons buttons \
|
||||
= StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, tr(title), tr(text), buttons, parent); \
|
||||
} \
|
||||
inline StandardButton level(const char *title, \
|
||||
const char *text, \
|
||||
StandardButtons buttons \
|
||||
= StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, tr(title), tr(text), buttons, rootObject); \
|
||||
} \
|
||||
inline StandardButton level(const QString title, \
|
||||
const QString &text, \
|
||||
StandardButtons buttons \
|
||||
|
|
@ -144,5 +129,11 @@ const QString GetOpenFileName(const QString &title,
|
|||
QString *selectedFilter = nullptr,
|
||||
Options options = Options());
|
||||
|
||||
const QString GetSaveFileName(const QString &title,
|
||||
const QString &dir,
|
||||
const QString &filter,
|
||||
QString *selectedFilter = nullptr,
|
||||
Options options = Options());
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
#endif // QT_FRONTEND_UTIL_H
|
||||