mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-29 18:08:08 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa0878b636 | |||
| c0a85d0e53 |
@@ -0,0 +1,28 @@
|
||||
From cc15da16e533b2a801934eab2dfeaf3c3949a1dc Mon Sep 17 00:00:00 2001
|
||||
From: crueter <crueter@eden-emu.dev>
|
||||
Date: Mon, 8 Sep 2025 12:28:55 -0400
|
||||
Subject: [PATCH] [cmake] disable NEON runtime check on clang-cl
|
||||
|
||||
When enabling runtime NEON checking for clang-cl, the linker would error out with `undefined symbol: __emit`, since clang doesn't actually implement this instruction. Therefore it makes sense to disable the runtime check by default on this platform, until either this is fixed or a clang-cl compatible intrinsic check is added (I don't have enough knowledge of MSVC to do this)
|
||||
---
|
||||
cmake/OpusConfig.cmake | 7 ++++++-
|
||||
1 file changed, 6 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/cmake/OpusConfig.cmake b/cmake/OpusConfig.cmake
|
||||
index e9319fbad..d0f459e88 100644
|
||||
--- a/cmake/OpusConfig.cmake
|
||||
+++ b/cmake/OpusConfig.cmake
|
||||
@@ -71,7 +71,12 @@ elseif(OPUS_CPU_ARM AND NOT OPUS_DISABLE_INTRINSICS)
|
||||
opus_detect_neon(COMPILER_SUPPORT_NEON)
|
||||
if(COMPILER_SUPPORT_NEON)
|
||||
option(OPUS_USE_NEON "Option to enable NEON" ON)
|
||||
- option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ON)
|
||||
+ if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
+ set(NEON_RUNTIME_CHECK_DEFAULT OFF)
|
||||
+ else()
|
||||
+ set(NEON_RUNTIME_CHECK_DEFAULT ON)
|
||||
+ endif()
|
||||
+ option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ${NEON_RUNTIME_CHECK_DEFAULT})
|
||||
option(OPUS_PRESUME_NEON "Assume target CPU has NEON support" OFF)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
|
||||
set(OPUS_PRESUME_NEON ON)
|
||||
@@ -0,0 +1,153 @@
|
||||
From bf455b67b4eaa446ffae5d25410b141b7b1b1082 Mon Sep 17 00:00:00 2001
|
||||
From: crueter <crueter@eden-emu.dev>
|
||||
Date: Mon, 8 Sep 2025 12:08:20 -0400
|
||||
Subject: [PATCH] [cmake] `OPUS_INSTALL` option; only default install if root
|
||||
project
|
||||
|
||||
Signed-off-by: crueter <crueter@eden-emu.dev>
|
||||
---
|
||||
CMakeLists.txt | 112 ++++++++++++++++++++++++++++---------------------
|
||||
1 file changed, 64 insertions(+), 48 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index fcf034b19..08b5e16f8 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -4,6 +4,13 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
include(OpusPackageVersion)
|
||||
get_package_version(PACKAGE_VERSION PROJECT_VERSION)
|
||||
|
||||
+# root project detection
|
||||
+if(DEFINED PROJECT_NAME)
|
||||
+ set(root_project OFF)
|
||||
+else()
|
||||
+ set(root_project ON)
|
||||
+endif()
|
||||
+
|
||||
project(Opus LANGUAGES C VERSION ${PROJECT_VERSION})
|
||||
|
||||
include(OpusFunctions)
|
||||
@@ -83,12 +90,16 @@ set(OPUS_DNN_FLOAT_DEBUG_HELP_STR "Run DNN computations as float for debugging p
|
||||
option(OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR} OFF)
|
||||
add_feature_info(OPUS_DNN_FLOAT_DEBUG OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR})
|
||||
|
||||
+set(OPUS_INSTALL_HELP_STR "Install Opus targets")
|
||||
+option(OPUS_INSTALL ${OPUS_INSTALL_HELP_STR} ${root_project})
|
||||
+add_feature_info(OPUS_INSTALL OPUS_INSTALL ${OPUS_INSTALL_HELP_STR})
|
||||
+
|
||||
set(OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR "install pkg-config module.")
|
||||
-option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ON)
|
||||
+option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
|
||||
add_feature_info(OPUS_INSTALL_PKG_CONFIG_MODULE OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR})
|
||||
|
||||
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR "install CMake package config module.")
|
||||
-option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ON)
|
||||
+option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
|
||||
add_feature_info(OPUS_INSTALL_CMAKE_CONFIG_MODULE OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR})
|
||||
|
||||
set(OPUS_DRED_HELP_STR "enable DRED.")
|
||||
@@ -613,53 +624,58 @@ if(OPUS_BUILD_FRAMEWORK)
|
||||
OUTPUT_NAME Opus)
|
||||
endif()
|
||||
|
||||
-install(TARGETS opus
|
||||
- EXPORT OpusTargets
|
||||
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
- FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
|
||||
- PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
|
||||
-
|
||||
-if(OPUS_INSTALL_PKG_CONFIG_MODULE)
|
||||
- set(prefix ${CMAKE_INSTALL_PREFIX})
|
||||
- set(exec_prefix ${CMAKE_INSTALL_PREFIX})
|
||||
- set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
|
||||
- set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
||||
- set(VERSION ${PACKAGE_VERSION})
|
||||
- if(HAVE_LIBM)
|
||||
- set(LIBM "-lm")
|
||||
+if (OPUS_INSTALL)
|
||||
+ install(TARGETS opus
|
||||
+ EXPORT OpusTargets
|
||||
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
+ FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
|
||||
+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
|
||||
+
|
||||
+ if(OPUS_INSTALL_PKG_CONFIG_MODULE)
|
||||
+ set(prefix ${CMAKE_INSTALL_PREFIX})
|
||||
+ set(exec_prefix ${CMAKE_INSTALL_PREFIX})
|
||||
+ set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
|
||||
+ set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
||||
+ set(VERSION ${PACKAGE_VERSION})
|
||||
+ if(HAVE_LIBM)
|
||||
+ set(LIBM "-lm")
|
||||
+ endif()
|
||||
+ configure_file(opus.pc.in opus.pc)
|
||||
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
|
||||
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
+ endif()
|
||||
+
|
||||
+ if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
|
||||
+ set(CPACK_GENERATOR TGZ)
|
||||
+ include(CPack)
|
||||
+ set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||
+ install(EXPORT OpusTargets
|
||||
+ NAMESPACE Opus::
|
||||
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
||||
+
|
||||
+ include(CMakePackageConfigHelpers)
|
||||
+
|
||||
+ set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
+ configure_package_config_file(
|
||||
+ ${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
|
||||
+ OpusConfig.cmake
|
||||
+ INSTALL_DESTINATION
|
||||
+ ${CMAKE_INSTALL_PACKAGEDIR}
|
||||
+ PATH_VARS
|
||||
+ INCLUDE_INSTALL_DIR
|
||||
+ INSTALL_PREFIX
|
||||
+ ${CMAKE_INSTALL_PREFIX})
|
||||
+
|
||||
+ write_basic_package_version_file(OpusConfigVersion.cmake
|
||||
+ VERSION ${PROJECT_VERSION}
|
||||
+ COMPATIBILITY SameMajorVersion)
|
||||
+
|
||||
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
|
||||
+ ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
|
||||
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
||||
endif()
|
||||
- configure_file(opus.pc.in opus.pc)
|
||||
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
|
||||
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
-endif()
|
||||
-
|
||||
-if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
|
||||
- set(CPACK_GENERATOR TGZ)
|
||||
- include(CPack)
|
||||
- set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||
- install(EXPORT OpusTargets
|
||||
- NAMESPACE Opus::
|
||||
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
||||
-
|
||||
- include(CMakePackageConfigHelpers)
|
||||
-
|
||||
- set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
- configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
|
||||
- OpusConfig.cmake
|
||||
- INSTALL_DESTINATION
|
||||
- ${CMAKE_INSTALL_PACKAGEDIR}
|
||||
- PATH_VARS
|
||||
- INCLUDE_INSTALL_DIR
|
||||
- INSTALL_PREFIX
|
||||
- ${CMAKE_INSTALL_PREFIX})
|
||||
- write_basic_package_version_file(OpusConfigVersion.cmake
|
||||
- VERSION ${PROJECT_VERSION}
|
||||
- COMPATIBILITY SameMajorVersion)
|
||||
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
|
||||
- ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
|
||||
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
||||
endif()
|
||||
|
||||
if(OPUS_BUILD_PROGRAMS)
|
||||
@@ -461,6 +461,21 @@ if (NOT YUZU_STATIC_ROOM)
|
||||
if (ZLIB_ADDED)
|
||||
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
||||
endif()
|
||||
|
||||
# Opus
|
||||
AddJsonPackage(opus)
|
||||
|
||||
if (Opus_ADDED)
|
||||
if (MSVC AND CXX_CLANG)
|
||||
target_compile_options(opus PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-function-declaration>
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT TARGET Opus::opus)
|
||||
add_library(Opus::opus ALIAS opus)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT TARGET Boost::headers)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_search_module(OPUS QUIET IMPORTED_TARGET opus)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Opus
|
||||
REQUIRED_VARS OPUS_LINK_LIBRARIES
|
||||
VERSION_VAR OPUS_VERSION
|
||||
)
|
||||
|
||||
if (MSYS2)
|
||||
FixMsysPath(PkgConfig::OPUS)
|
||||
endif()
|
||||
|
||||
if (Opus_FOUND AND NOT TARGET Opus::opus)
|
||||
add_library(Opus::opus ALIAS PkgConfig::OPUS)
|
||||
endif()
|
||||
@@ -218,6 +218,21 @@
|
||||
"repo": "jimmy-park/openssl-cmake",
|
||||
"version": "3.6.2"
|
||||
},
|
||||
"opus": {
|
||||
"find_args": "MODULE",
|
||||
"hash": "9506147b0de35befda8633ff272981cc2575c860874791bd455b752f797fd7dbd1079f0ba42ccdd7bb1fe6773fa5e84b3d75667c2883dd1fb2d0e4a5fa4f8387",
|
||||
"min_version": "1.3",
|
||||
"options": [
|
||||
"OPUS_PRESUME_NEON ON"
|
||||
],
|
||||
"package": "Opus",
|
||||
"patches": [
|
||||
"0001-disable-clang-runtime-neon.patch",
|
||||
"0002-no-install.patch"
|
||||
],
|
||||
"repo": "xiph/opus",
|
||||
"version": "a3f0ec02b3"
|
||||
},
|
||||
"quazip": {
|
||||
"hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8",
|
||||
"min_version": "1.3",
|
||||
|
||||
+10
-9
@@ -60,6 +60,7 @@ All other dependencies will be downloaded and built by [CPM](https://github.com/
|
||||
* [ZLIB](https://www.zlib.net/) 1.2+
|
||||
* [zstd](https://facebook.github.io/zstd/) 1.5+
|
||||
* [enet](http://enet.bespin.org/) 1.3+
|
||||
* [Opus](https://opus-codec.org/) 1.3+
|
||||
|
||||
Vulkan 1.3.274+ is also needed:
|
||||
|
||||
@@ -121,7 +122,7 @@ sudo emerge -a \
|
||||
dev-libs/unordered_dense dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
|
||||
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
|
||||
dev-util/vulkan-utility-libraries dev-util/glslang \
|
||||
media-gfx/renderdoc media-libs/libva media-video/ffmpeg \
|
||||
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
|
||||
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
||||
net-libs/enet \
|
||||
sys-libs/zlib \
|
||||
@@ -153,7 +154,7 @@ Required USE flags:
|
||||
<summary>Arch Linux</summary>
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
|
||||
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
|
||||
```
|
||||
|
||||
* Building with QT Web Engine requires `qt6-webengine` as well.
|
||||
@@ -166,7 +167,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
|
||||
<summary>Ubuntu, Debian, Mint Linux</summary>
|
||||
|
||||
```sh
|
||||
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libasound2t64 vulkan-utility-libraries-dev
|
||||
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
|
||||
```
|
||||
|
||||
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
|
||||
@@ -213,7 +214,7 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/
|
||||
# Enable the community repository
|
||||
setup-apkrepos -c
|
||||
# Install
|
||||
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev jq patch
|
||||
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -259,7 +260,7 @@ brew install molten-vk
|
||||
<details>
|
||||
<summary>FreeBSD</summary>
|
||||
|
||||
As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6`
|
||||
As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6`
|
||||
|
||||
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||
|
||||
@@ -272,7 +273,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||
For NetBSD +10.1:
|
||||
|
||||
```sh
|
||||
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq qt6 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
||||
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#netbsd).
|
||||
@@ -303,7 +304,7 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak
|
||||
<summary>OpenIndiana</summary>
|
||||
|
||||
```sh
|
||||
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
|
||||
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#openindiana).
|
||||
@@ -327,7 +328,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
|
||||
|
||||
```sh
|
||||
BASE="git make autoconf libtool automake-wrapper jq patch"
|
||||
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet libusb unordered_dense openssl SDL3"
|
||||
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb unordered_dense openssl SDL3"
|
||||
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
||||
packages="$BASE"
|
||||
for pkg in $MINGW; do
|
||||
@@ -353,7 +354,7 @@ pacman -Syuu --needed --noconfirm $packages
|
||||
<summary>HaikuOS</summary>
|
||||
|
||||
```sh
|
||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#haikuos).
|
||||
|
||||
@@ -12,7 +12,7 @@ pkgs.mkShellNoCC {
|
||||
git cmake clang gnumake patch jq pkg-config
|
||||
# libraries
|
||||
openssl boost fmt nlohmann_json lz4 zlib zstd
|
||||
enet vulkan-headers vulkan-utility-libraries
|
||||
enet libopus vulkan-headers vulkan-utility-libraries
|
||||
spirv-tools spirv-headers vulkan-loader unzip
|
||||
glslang python3 httplib cpp-jwt ffmpeg-headless
|
||||
libusb1 cubeb
|
||||
|
||||
+4
-1
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -24,6 +27,6 @@ object SettingsFile {
|
||||
|
||||
fun loadCustomConfig(game: Game) {
|
||||
val fileName = FileUtil.getFilename(Uri.parse(game.path))
|
||||
NativeConfig.initializePerGameConfig(game.programId, fileName)
|
||||
NativeConfig.initializePerGameConfig(game.applicationId, fileName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ class AddonsFragment : Fragment() {
|
||||
fragmentManager = parentFragmentManager,
|
||||
addonViewModel = addonViewModel,
|
||||
documents = documents,
|
||||
programId = args.game.programId
|
||||
programId = args.game.applicationId
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
try {
|
||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||
val programIdHex = game!!.programIdHex
|
||||
val programIdHex = game!!.applicationIdHex
|
||||
if (NativeFreedrenoConfig.loadPerGameConfigWithGlobalFallback(programIdHex)) {
|
||||
Log.info("[EmulationFragment] Loaded per-game Freedreno config for $programIdHex")
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -59,7 +59,7 @@ class FreedrenoSettingsFragment : Fragment() {
|
||||
NativeFreedrenoConfig.initializeFreedrenoConfig()
|
||||
|
||||
if (isPerGameConfig) {
|
||||
NativeFreedrenoConfig.loadPerGameConfig(game!!.programIdHex)
|
||||
NativeFreedrenoConfig.loadPerGameConfig(game!!.applicationIdHex)
|
||||
} else {
|
||||
NativeFreedrenoConfig.reloadFreedrenoConfig()
|
||||
}
|
||||
@@ -157,7 +157,7 @@ class FreedrenoSettingsFragment : Fragment() {
|
||||
|
||||
binding.buttonSave.setOnClickListener {
|
||||
if (isPerGameConfig) {
|
||||
NativeFreedrenoConfig.savePerGameConfig(game!!.programIdHex)
|
||||
NativeFreedrenoConfig.savePerGameConfig(game!!.applicationIdHex)
|
||||
showSnackbar(getString(R.string.freedreno_per_game_saved))
|
||||
} else {
|
||||
NativeFreedrenoConfig.saveFreedrenoConfig()
|
||||
|
||||
@@ -455,7 +455,7 @@ class GamePropertiesFragment : Fragment() {
|
||||
|
||||
val shaderCacheDir = File(
|
||||
DirectoryInitialization.userDirectory +
|
||||
"/cache/shader/" + args.game.settingsName.lowercase()
|
||||
"/cache/shader/" + args.game.shaderCacheName.lowercase()
|
||||
)
|
||||
if (shaderCacheDir.exists()) {
|
||||
add(
|
||||
@@ -600,7 +600,7 @@ class GamePropertiesFragment : Fragment() {
|
||||
val files = cacheSaveDir.listFiles()
|
||||
var savesFolderFile: File? = null
|
||||
if (files != null) {
|
||||
val savesFolderName = args.game.programIdHex
|
||||
val savesFolderName = args.game.applicationIdHex
|
||||
for (file in files) {
|
||||
if (file.isDirectory && file.name == savesFolderName) {
|
||||
savesFolderFile = file
|
||||
|
||||
@@ -232,7 +232,7 @@ class InstallableFragment : Fragment() {
|
||||
fragmentManager = parentFragmentManager,
|
||||
addonViewModel = addonViewModel,
|
||||
documents = documents,
|
||||
programId = addonViewModel.game?.programId
|
||||
programId = addonViewModel.game?.applicationId
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,13 @@ class LicensesFragment : Fragment() {
|
||||
R.string.license_ffmpeg_copyright,
|
||||
R.string.license_ffmpeg_text
|
||||
),
|
||||
License(
|
||||
R.string.license_opus,
|
||||
R.string.license_opus_description,
|
||||
R.string.license_opus_link,
|
||||
R.string.license_opus_copyright,
|
||||
R.string.license_opus_text
|
||||
),
|
||||
License(
|
||||
R.string.license_sirit,
|
||||
R.string.license_sirit_description,
|
||||
|
||||
@@ -142,10 +142,11 @@ class AddonViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun onDeleteAddon(patch: Patch) {
|
||||
val currentGame = game ?: return
|
||||
when (PatchType.from(patch.type)) {
|
||||
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
|
||||
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
|
||||
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
|
||||
PatchType.Update -> NativeLibrary.removeUpdate(currentGame.programId)
|
||||
PatchType.DLC -> NativeLibrary.removeDLC(currentGame.applicationId)
|
||||
PatchType.Mod -> NativeLibrary.removeMod(currentGame.programId, patch.name)
|
||||
}
|
||||
refreshAddons(force = true)
|
||||
}
|
||||
@@ -165,7 +166,7 @@ class AddonViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
NativeConfig.setDisabledAddons(
|
||||
currentGame.programId,
|
||||
currentGame.applicationId,
|
||||
currentList.mapNotNull {
|
||||
if (it.enabled) {
|
||||
null
|
||||
@@ -199,6 +200,6 @@ class AddonViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private fun gameKey(game: Game): String {
|
||||
return "${game.programId}|${game.path}"
|
||||
return "${game.applicationId}|${game.path}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ class DriverViewModel : ViewModel() {
|
||||
?: return@withContext
|
||||
val shaderDir = File(
|
||||
externalFilesDir.absolutePath +
|
||||
"/shader/" + game.settingsName.lowercase()
|
||||
"/shader/" + game.shaderCacheName.lowercase()
|
||||
)
|
||||
if (shaderDir.exists()) {
|
||||
shaderDir.deleteRecursively()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -35,19 +35,26 @@ class Game(
|
||||
val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime"
|
||||
val keyLastPlayedTime get() = "${path}_LastPlayed"
|
||||
|
||||
private val programIdLong: Long
|
||||
get() = programId.toLongOrNull() ?: 0L
|
||||
|
||||
private val applicationIdLong: Long
|
||||
get() = programIdLong and -8192L
|
||||
|
||||
val applicationId: String
|
||||
get() = applicationIdLong.toString()
|
||||
|
||||
val settingsName: String
|
||||
get() {
|
||||
val programIdLong = programId.toLong()
|
||||
return if (programIdLong == 0L) {
|
||||
return if (applicationIdLong == 0L) {
|
||||
FileUtil.getFilename(Uri.parse(path))
|
||||
} else {
|
||||
"0" + programIdLong.toString(16).uppercase()
|
||||
"0" + applicationIdLong.toString(16).uppercase()
|
||||
}
|
||||
}
|
||||
|
||||
val programIdHex: String
|
||||
get() {
|
||||
val programIdLong = programId.toLong()
|
||||
return if (programIdLong == 0L) {
|
||||
"0"
|
||||
} else {
|
||||
@@ -55,16 +62,32 @@ class Game(
|
||||
}
|
||||
}
|
||||
|
||||
val shaderCacheName: String
|
||||
get() = if (programIdLong == 0L) {
|
||||
FileUtil.getFilename(Uri.parse(path))
|
||||
} else {
|
||||
"0" + programIdLong.toString(16).uppercase()
|
||||
}
|
||||
|
||||
val applicationIdHex: String
|
||||
get() {
|
||||
return if (applicationIdLong == 0L) {
|
||||
"0"
|
||||
} else {
|
||||
"0" + applicationIdLong.toString(16).uppercase()
|
||||
}
|
||||
}
|
||||
|
||||
val saveZipName: String
|
||||
get() = "$title ${YuzuApplication.appContext.getString(R.string.save_data).lowercase()} - ${
|
||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||
}.zip"
|
||||
|
||||
val saveDir: String
|
||||
get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(programId)
|
||||
get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(applicationId)
|
||||
|
||||
val addonDir: String
|
||||
get() = DirectoryInitialization.userDirectory + "/load/" + programIdHex + "/"
|
||||
get() = DirectoryInitialization.userDirectory + "/load/" + applicationIdHex + "/"
|
||||
|
||||
val launchIntent: Intent
|
||||
get() = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -65,7 +65,7 @@ object CustomSettingsHandler {
|
||||
// Initialize per-game config
|
||||
try {
|
||||
val fileName = FileUtil.getFilename(Uri.parse(game.path))
|
||||
NativeConfig.initializePerGameConfig(game.programId, fileName)
|
||||
NativeConfig.initializePerGameConfig(game.applicationId, fileName)
|
||||
Log.info("[CustomSettingsHandler] Successfully applied custom settings")
|
||||
return game
|
||||
} catch (e: Exception) {
|
||||
@@ -333,20 +333,20 @@ object CustomSettingsHandler {
|
||||
*/
|
||||
fun findGameByTitleId(titleId: String, context: Context): Game? {
|
||||
Log.info("[CustomSettingsHandler] Searching for game with title ID: $titleId")
|
||||
// Convert hex title ID to decimal for comparison with programId
|
||||
val programIdDecimal = try {
|
||||
titleId.toLong(16).toString()
|
||||
// Convert the program ID to the application ID used by per-game settings.
|
||||
val applicationIdLong = try {
|
||||
titleId.toLong(16) and -8192L
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.error("[CustomSettingsHandler] Invalid title ID format: $titleId")
|
||||
return null
|
||||
}
|
||||
val applicationIdDecimal = applicationIdLong.toString()
|
||||
|
||||
// Expected hex format with "0" prefix
|
||||
val expectedHex = "0${titleId.uppercase()}"
|
||||
val expectedHex = "0${applicationIdLong.toString(16).uppercase()}"
|
||||
// First check cached games for fast lookup
|
||||
GameHelper.cachedGameList.find { game ->
|
||||
game.programId == programIdDecimal ||
|
||||
game.programIdHex.equals(expectedHex, ignoreCase = true)
|
||||
game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true)
|
||||
}?.let { foundGame ->
|
||||
Log.info("[CustomSettingsHandler] Found game in cache: ${foundGame.title}")
|
||||
return foundGame
|
||||
@@ -355,8 +355,7 @@ object CustomSettingsHandler {
|
||||
Log.info("[CustomSettingsHandler] Game not in cache, scanning full library...")
|
||||
val allGames = GameHelper.getGames()
|
||||
val foundGame = allGames.find { game ->
|
||||
game.programId == programIdDecimal ||
|
||||
game.programIdHex.equals(expectedHex, ignoreCase = true)
|
||||
game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true)
|
||||
}
|
||||
if (foundGame != null) {
|
||||
Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}")
|
||||
|
||||
@@ -170,12 +170,12 @@ object GameHelper {
|
||||
val game = getGame(it.uri, true, false)
|
||||
if (game != null) {
|
||||
games.add(game)
|
||||
if (game.programId != "0") {
|
||||
gamesByProgramId[game.programId] = game
|
||||
if (game.applicationId != "0") {
|
||||
gamesByProgramId[game.applicationId] = game
|
||||
}
|
||||
} else if (mountedContainer) {
|
||||
GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId ->
|
||||
gamesByProgramId[(programId and 0x800L.inv()).toString()]
|
||||
gamesByProgramId[(programId and -8192L).toString()]
|
||||
}?.let { existingGame ->
|
||||
NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId)
|
||||
existingGame.version = GameMetadata.getVersion(
|
||||
|
||||
@@ -788,7 +788,7 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
|
||||
jstring jprogramId,
|
||||
jstring jupdatePath) {
|
||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const u64 program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
std::string updatePath = Common::Android::GetJString(env, jupdatePath);
|
||||
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
|
||||
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
|
||||
@@ -796,7 +796,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en
|
||||
for (const auto& item : nsp->GetNCAs()) {
|
||||
for (const auto& nca_details : item.second) {
|
||||
if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
|
||||
auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL;
|
||||
const auto update_id = FileSys::GetBaseTitleID(nca_details.second->GetTitleId());
|
||||
if (update_id == program_id) {
|
||||
return true;
|
||||
}
|
||||
@@ -1491,7 +1491,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_firmwareVersion(JNIEnv* env, jclas
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_gameRequiresFirmware(JNIEnv* env, jclass clazz, jstring jprogramId) {
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
|
||||
return FirmwareManager::GameRequiresFirmware(program_id);
|
||||
}
|
||||
@@ -1575,20 +1575,21 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj,
|
||||
jstring jprogramId) {
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(),
|
||||
program_id);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj,
|
||||
jstring jprogramId) {
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const auto program_id = FileSys::GetBaseTitleID(
|
||||
EmulationSession::GetProgramId(env, jprogramId));
|
||||
ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId,
|
||||
jstring jname) {
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(),
|
||||
program_id, Common::Android::GetJString(env, jname));
|
||||
}
|
||||
@@ -1635,7 +1636,7 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyGameContents(JNIEnv* env, jobje
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
|
||||
jstring jprogramId) {
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
if (program_id == 0) {
|
||||
return Common::Android::ToJString(env, "");
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "frontend_common/settings_generator.h"
|
||||
#include "native.h"
|
||||
@@ -56,7 +57,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jo
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId,
|
||||
jstring jfileName) {
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
auto file_name = Common::Android::GetJString(env, jfileName);
|
||||
const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
|
||||
per_game_config =
|
||||
@@ -322,7 +323,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject
|
||||
|
||||
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId) {
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
auto& disabledAddons = Settings::values.disabled_addons[program_id];
|
||||
jobjectArray jdisabledAddonsArray =
|
||||
env->NewObjectArray(disabledAddons.size(), Common::Android::GetStringClass(),
|
||||
@@ -337,7 +338,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId,
|
||||
jobjectArray jdisabledAddons) {
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
Settings::values.disabled_addons[program_id].clear();
|
||||
std::vector<std::string> disabled_addons;
|
||||
const int size = env->GetArrayLength(jdisabledAddons);
|
||||
|
||||
@@ -1760,6 +1760,51 @@ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
</string>
|
||||
<string name="license_opus" translatable="false">Opus</string>
|
||||
<string name="license_opus_description" translatable="false">Modern audio compression for the internet</string>
|
||||
<string name="license_opus_link" translatable="false">https://github.com/xiph/opus</string>
|
||||
<string name="license_opus_copyright" translatable="false">Copyright 2001–2011 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, Erik de Castro Lopo</string>
|
||||
<string name="license_opus_text" translatable="false">
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:\n\n
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.\n\n
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.\n\n
|
||||
|
||||
- Neither the name of Internet Society, IETF or IETF Trust, nor the
|
||||
names of specific contributors, may be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.\n\n
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n
|
||||
|
||||
Opus is subject to the royalty-free patent licenses which are
|
||||
specified at:\n\n
|
||||
|
||||
Xiph.Org Foundation:
|
||||
https://datatracker.ietf.org/ipr/1524/ \n\n
|
||||
|
||||
Microsoft Corporation:
|
||||
https://datatracker.ietf.org/ipr/1914/ \n\n
|
||||
|
||||
Broadcom Corporation:
|
||||
https://datatracker.ietf.org/ipr/1526/
|
||||
</string>
|
||||
<string name="license_sirit" translatable="false">Sirit</string>
|
||||
<string name="license_sirit_description" translatable="false">A runtime SPIR-V assembler</string>
|
||||
|
||||
@@ -226,14 +226,8 @@ else()
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
||||
endif()
|
||||
|
||||
if (YUZU_USE_EXTERNAL_FFMPEG)
|
||||
add_dependencies(audio_core ffmpeg-build)
|
||||
endif()
|
||||
target_include_directories(audio_core PUBLIC ${FFmpeg_INCLUDE_DIR})
|
||||
target_link_libraries(audio_core PRIVATE ${FFmpeg_LIBRARIES})
|
||||
target_link_options(audio_core PRIVATE ${FFmpeg_LDFLAGS})
|
||||
|
||||
target_link_libraries(audio_core PUBLIC common core)
|
||||
target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS})
|
||||
target_link_libraries(audio_core PUBLIC common core Opus::opus)
|
||||
|
||||
if (ENABLE_CUBEB)
|
||||
target_sources(audio_core PRIVATE
|
||||
|
||||
@@ -1,71 +1,107 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/codec.h>
|
||||
#include <libavcodec/packet.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/frame.h>
|
||||
}
|
||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_types.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
namespace {
|
||||
bool IsValidChannelCount(u32 channel_count) {
|
||||
return channel_count == 1 || channel_count == 2;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
|
||||
if (channel_count == 1 || channel_count == 2)
|
||||
return u32(sizeof(OpusDecodeObject)) + 16 * channel_count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
|
||||
if ((codec = avcodec_find_decoder(AV_CODEC_ID_OPUS)) == nullptr)
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
if ((avctx = avcodec_alloc_context3(codec)) == nullptr)
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
avctx->sample_rate = sample_rate;
|
||||
av_channel_layout_default(&avctx->ch_layout, channel_count);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpusDecodeObject::Shutdown() {
|
||||
if (avctx)
|
||||
avcodec_free_context(&avctx);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpusDecodeObject::ResetDecoder() {
|
||||
if (avctx) {
|
||||
avcodec_flush_buffers(avctx);
|
||||
return ResultSuccess;
|
||||
if (!IsValidChannelCount(channel_count)) {
|
||||
return 0;
|
||||
}
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
|
||||
}
|
||||
|
||||
Result OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) {
|
||||
OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
|
||||
auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
|
||||
auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2);
|
||||
|
||||
if (new_decoder->magic == DecodeObjectMagic) {
|
||||
if (!new_decoder->initialized ||
|
||||
(new_decoder->initialized && new_decoder->self == comparison)) {
|
||||
new_decoder->state_valid = true;
|
||||
}
|
||||
} else {
|
||||
new_decoder->initialized = false;
|
||||
new_decoder->state_valid = true;
|
||||
}
|
||||
return *new_decoder;
|
||||
}
|
||||
|
||||
s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
return OPUS_OK;
|
||||
}
|
||||
|
||||
// Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include
|
||||
// it in this class. Nintendo does not allocate memory, which is why we have a workbuffer
|
||||
// provided.
|
||||
// We could use _create and have libopus allocate it for us, but then we have to separately
|
||||
// track which decoder is being used between this and multistream in order to call the correct
|
||||
// destroy from the host side.
|
||||
// This is a bit cringe, but is safe as these objects are only ever initialized inside the given
|
||||
// workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow.
|
||||
decoder = (LibOpusDecoder*)(this + 1);
|
||||
s32 ret = opus_decoder_init(decoder, sample_rate, channel_count);
|
||||
if (ret == OPUS_OK) {
|
||||
magic = DecodeObjectMagic;
|
||||
initialized = true;
|
||||
state_valid = true;
|
||||
self = this;
|
||||
final_range = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 OpusDecodeObject::Shutdown() {
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
magic = 0x0;
|
||||
initialized = false;
|
||||
state_valid = false;
|
||||
self = nullptr;
|
||||
final_range = 0;
|
||||
decoder = nullptr;
|
||||
}
|
||||
return OPUS_OK;
|
||||
}
|
||||
|
||||
s32 OpusDecodeObject::ResetDecoder() {
|
||||
return opus_decoder_ctl(decoder, OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size,
|
||||
u64 input_data, u64 input_data_size) {
|
||||
ASSERT(initialized);
|
||||
out_sample_count = 0;
|
||||
if (avctx) {
|
||||
AVPacket* avpkt = av_packet_alloc();
|
||||
av_new_packet(avpkt, int(input_data_size));
|
||||
std::memcpy(avpkt->data, reinterpret_cast<const u8*>(input_data), input_data_size);
|
||||
avcodec_send_packet(avctx, avpkt);
|
||||
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
avcodec_receive_frame(avctx, frame);
|
||||
std::memcpy(reinterpret_cast<u16*>(output_data), frame->data, output_data_size);
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&avpkt);
|
||||
|
||||
out_sample_count = frame->nb_samples;
|
||||
return ResultSuccess;
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
|
||||
auto ret_code_or_samples = opus_decode(
|
||||
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
|
||||
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
|
||||
|
||||
if (ret_code_or_samples < OPUS_OK) {
|
||||
return ret_code_or_samples;
|
||||
}
|
||||
|
||||
out_sample_count = ret_code_or_samples;
|
||||
return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
||||
|
||||
@@ -1,31 +1,38 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_types.h"
|
||||
#include "core/hle/result.h"
|
||||
#include <opus.h>
|
||||
|
||||
struct AVCodec;
|
||||
struct AVCodecContext;
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
using LibOpusDecoder = ::OpusDecoder;
|
||||
static constexpr u32 DecodeObjectMagic = 0xDEADBEEF;
|
||||
|
||||
class OpusDecodeObject {
|
||||
public:
|
||||
static u32 GetWorkBufferSize(u32 channel_count);
|
||||
static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2);
|
||||
|
||||
Result InitializeDecoder(u32 sample_rate, u32 channel_count);
|
||||
Result Shutdown();
|
||||
Result ResetDecoder();
|
||||
Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size);
|
||||
s32 InitializeDecoder(u32 sample_rate, u32 channel_count);
|
||||
s32 Shutdown();
|
||||
s32 ResetDecoder();
|
||||
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
|
||||
u64 input_data_size);
|
||||
u32 GetFinalRange() const noexcept {
|
||||
return final_range;
|
||||
}
|
||||
|
||||
AVCodec const* codec = nullptr;
|
||||
AVCodecContext* avctx = nullptr;
|
||||
private:
|
||||
u32 magic;
|
||||
bool initialized;
|
||||
bool state_valid;
|
||||
OpusDecodeObject* self;
|
||||
u32 final_range;
|
||||
LibOpusDecoder* decoder;
|
||||
};
|
||||
static_assert(std::is_trivially_constructible_v<OpusDecodeObject>);
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
|
||||
@@ -50,16 +49,11 @@ OpusDecoder::~OpusDecoder() {
|
||||
// Shutdown the thread
|
||||
Send(Direction::DSP, Message::Shutdown);
|
||||
auto msg = Receive(Direction::Host);
|
||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg);
|
||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
|
||||
Message::ShutdownOK, msg);
|
||||
main_thread.request_stop();
|
||||
main_thread.join();
|
||||
running = false;
|
||||
|
||||
// Must shutdown as there are AV allocations which are manual
|
||||
for (auto& e : decode_objects)
|
||||
e.second.Shutdown();
|
||||
for (auto& e : ms_decode_objects)
|
||||
e.second.Shutdown();
|
||||
}
|
||||
|
||||
void OpusDecoder::Send(Direction dir, u32 message) {
|
||||
@@ -74,195 +68,202 @@ void OpusDecoder::Init(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
|
||||
|
||||
if (Receive(Direction::DSP, stop_token) != Message::Start) {
|
||||
LOG_ERROR(Service_Audio, "DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
||||
LOG_ERROR(Service_Audio,
|
||||
"DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
||||
return;
|
||||
}
|
||||
// Main OpusDecoder thread, responsible for processing the incoming Opus packets.
|
||||
main_thread = std::jthread([this](std::stop_token thread_stop_token) {
|
||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
|
||||
while (!thread_stop_token.stop_requested()) {
|
||||
auto msg = Receive(Direction::DSP, thread_stop_token);
|
||||
switch (msg) {
|
||||
case Shutdown:
|
||||
Send(Direction::Host, Message::ShutdownOK);
|
||||
return;
|
||||
case GetWorkBufferSize: {
|
||||
auto channel_count = s32(shared_memory->host_send_data[0]);
|
||||
|
||||
ASSERT(IsValidChannelCount(channel_count));
|
||||
|
||||
shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
|
||||
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
||||
break;
|
||||
}
|
||||
case InitializeDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto buffer_size = shared_memory->host_send_data[1];
|
||||
auto sample_rate = s32(shared_memory->host_send_data[2]);
|
||||
auto channel_count = s32(shared_memory->host_send_data[3]);
|
||||
|
||||
ASSERT(sample_rate >= 0);
|
||||
ASSERT(IsValidChannelCount(channel_count));
|
||||
ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count));
|
||||
|
||||
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||
it->second.Shutdown();
|
||||
shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, channel_count).raw;
|
||||
} else {
|
||||
OpusDecodeObject obj{};
|
||||
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, channel_count).raw;
|
||||
decode_objects.insert_or_assign(buffer, obj);
|
||||
}
|
||||
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
||||
break;
|
||||
}
|
||||
case ShutdownDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
|
||||
} else {
|
||||
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||
}
|
||||
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
||||
break;
|
||||
}
|
||||
case DecodeInterleaved: {
|
||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto input_data = shared_memory->host_send_data[1];
|
||||
auto input_data_size = shared_memory->host_send_data[2];
|
||||
auto output_data = shared_memory->host_send_data[3];
|
||||
auto output_data_size = shared_memory->host_send_data[4];
|
||||
//auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||
auto reset_requested = shared_memory->host_send_data[6];
|
||||
|
||||
u32 decoded_samples{0};
|
||||
|
||||
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||
auto res = ResultSuccess;
|
||||
if (reset_requested)
|
||||
res = it->second.ResetDecoder();
|
||||
if (res == ResultSuccess)
|
||||
res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size);
|
||||
|
||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
shared_memory->dsp_return_data[0] = res.raw;
|
||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||
} else {
|
||||
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||
}
|
||||
Send(Direction::Host, Message::DecodeInterleavedOK);
|
||||
break;
|
||||
}
|
||||
case MapMemory: {
|
||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
Send(Direction::Host, Message::MapMemoryOK);
|
||||
break;
|
||||
}
|
||||
case UnmapMemory: {
|
||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
Send(Direction::Host, Message::UnmapMemoryOK);
|
||||
break;
|
||||
}
|
||||
case GetWorkBufferSizeForMultiStream: {
|
||||
auto total_stream_count = s32(shared_memory->host_send_data[0]);
|
||||
auto stereo_stream_count = s32(shared_memory->host_send_data[1]);
|
||||
|
||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
||||
|
||||
shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize(
|
||||
total_stream_count, stereo_stream_count);
|
||||
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
||||
break;
|
||||
}
|
||||
case InitializeMultiStreamDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto buffer_size = shared_memory->host_send_data[1];
|
||||
auto sample_rate = s32(shared_memory->host_send_data[2]);
|
||||
auto channel_count = s32(shared_memory->host_send_data[3]);
|
||||
auto total_stream_count = s32(shared_memory->host_send_data[4]);
|
||||
auto stereo_stream_count = s32(shared_memory->host_send_data[5]);
|
||||
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
|
||||
// mappings, but [6] is never set, and there is not enough room in the argument data for
|
||||
// more than 40 channels, when 255 are possible.
|
||||
// It also means the mapping values are undefined, though likely always 0,
|
||||
// and the mappings given by the game are ignored. The mappings are copied to this
|
||||
// dedicated buffer host side, so let's do as intended.
|
||||
auto mappings = shared_memory->channel_mapping.data();
|
||||
|
||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
||||
ASSERT(sample_rate >= 0);
|
||||
ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(total_stream_count, stereo_stream_count));
|
||||
|
||||
if (auto const it = ms_decode_objects.find(buffer); it != ms_decode_objects.end()) {
|
||||
it->second.Shutdown();
|
||||
shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
|
||||
} else {
|
||||
OpusMultiStreamDecodeObject obj{};
|
||||
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
|
||||
ms_decode_objects.insert_or_assign(buffer, obj);
|
||||
}
|
||||
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
||||
break;
|
||||
}
|
||||
case ShutdownMultiStreamDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
if (auto const it = ms_decode_objects.find(buffer); it != ms_decode_objects.end()) {
|
||||
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
|
||||
} else {
|
||||
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||
}
|
||||
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
||||
break;
|
||||
}
|
||||
case DecodeInterleavedForMultiStream: {
|
||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto input_data = shared_memory->host_send_data[1];
|
||||
auto input_data_size = shared_memory->host_send_data[2];
|
||||
auto output_data = shared_memory->host_send_data[3];
|
||||
auto output_data_size = shared_memory->host_send_data[4];
|
||||
//auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||
auto reset_requested = shared_memory->host_send_data[6];
|
||||
|
||||
u32 decoded_samples{0};
|
||||
|
||||
if (auto const it = ms_decode_objects.find(buffer); it != ms_decode_objects.end()) {
|
||||
auto res = ResultSuccess;
|
||||
if (reset_requested)
|
||||
res = it->second.ResetDecoder();
|
||||
if (res == ResultSuccess)
|
||||
res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size);
|
||||
|
||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
shared_memory->dsp_return_data[0] = res.raw;
|
||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||
} else {
|
||||
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||
}
|
||||
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Audio_DSP, "Invalid OpusDecoder command {}", msg);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
});
|
||||
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
|
||||
running = true;
|
||||
Send(Direction::Host, Message::StartOK);
|
||||
}
|
||||
|
||||
void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
auto msg = Receive(Direction::DSP, stop_token);
|
||||
switch (msg) {
|
||||
case Shutdown:
|
||||
Send(Direction::Host, Message::ShutdownOK);
|
||||
return;
|
||||
|
||||
case GetWorkBufferSize: {
|
||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
||||
|
||||
ASSERT(IsValidChannelCount(channel_count));
|
||||
|
||||
shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
|
||||
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
||||
} break;
|
||||
|
||||
case InitializeDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto buffer_size = shared_memory->host_send_data[1];
|
||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
|
||||
|
||||
ASSERT(sample_rate >= 0);
|
||||
ASSERT(IsValidChannelCount(channel_count));
|
||||
ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count));
|
||||
|
||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
||||
shared_memory->dsp_return_data[0] =
|
||||
decoder_object.InitializeDecoder(sample_rate, channel_count);
|
||||
|
||||
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case ShutdownDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
|
||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
||||
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
|
||||
|
||||
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case DecodeInterleaved: {
|
||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto input_data = shared_memory->host_send_data[1];
|
||||
auto input_data_size = shared_memory->host_send_data[2];
|
||||
auto output_data = shared_memory->host_send_data[3];
|
||||
auto output_data_size = shared_memory->host_send_data[4];
|
||||
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||
auto reset_requested = shared_memory->host_send_data[6];
|
||||
|
||||
u32 decoded_samples{0};
|
||||
|
||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
||||
s32 error_code{OPUS_OK};
|
||||
if (reset_requested) {
|
||||
error_code = decoder_object.ResetDecoder();
|
||||
}
|
||||
|
||||
if (error_code == OPUS_OK) {
|
||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
||||
input_data, input_data_size);
|
||||
}
|
||||
|
||||
if (error_code == OPUS_OK) {
|
||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
||||
error_code = OPUS_INVALID_PACKET;
|
||||
}
|
||||
}
|
||||
|
||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
shared_memory->dsp_return_data[0] = error_code;
|
||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||
|
||||
Send(Direction::Host, Message::DecodeInterleavedOK);
|
||||
} break;
|
||||
|
||||
case MapMemory: {
|
||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
Send(Direction::Host, Message::MapMemoryOK);
|
||||
} break;
|
||||
|
||||
case UnmapMemory: {
|
||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
Send(Direction::Host, Message::UnmapMemoryOK);
|
||||
} break;
|
||||
|
||||
case GetWorkBufferSizeForMultiStream: {
|
||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
|
||||
|
||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
||||
|
||||
shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize(
|
||||
total_stream_count, stereo_stream_count);
|
||||
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
||||
} break;
|
||||
|
||||
case InitializeMultiStreamDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto buffer_size = shared_memory->host_send_data[1];
|
||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
|
||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]);
|
||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]);
|
||||
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
|
||||
// mappings, but [6] is never set, and there is not enough room in the argument data for
|
||||
// more than 40 channels, when 255 are possible.
|
||||
// It also means the mapping values are undefined, though likely always 0,
|
||||
// and the mappings given by the game are ignored. The mappings are copied to this
|
||||
// dedicated buffer host side, so let's do as intended.
|
||||
auto mappings = shared_memory->channel_mapping.data();
|
||||
|
||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
||||
ASSERT(sample_rate >= 0);
|
||||
ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(
|
||||
total_stream_count, stereo_stream_count));
|
||||
|
||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
||||
shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder(
|
||||
sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings);
|
||||
|
||||
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case ShutdownMultiStreamDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
|
||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
||||
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
|
||||
|
||||
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case DecodeInterleavedForMultiStream: {
|
||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto input_data = shared_memory->host_send_data[1];
|
||||
auto input_data_size = shared_memory->host_send_data[2];
|
||||
auto output_data = shared_memory->host_send_data[3];
|
||||
auto output_data_size = shared_memory->host_send_data[4];
|
||||
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||
auto reset_requested = shared_memory->host_send_data[6];
|
||||
|
||||
u32 decoded_samples{0};
|
||||
|
||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
||||
s32 error_code{OPUS_OK};
|
||||
if (reset_requested) {
|
||||
error_code = decoder_object.ResetDecoder();
|
||||
}
|
||||
|
||||
if (error_code == OPUS_OK) {
|
||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
||||
input_data, input_data_size);
|
||||
}
|
||||
|
||||
if (error_code == OPUS_OK) {
|
||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
||||
error_code = OPUS_INVALID_PACKET;
|
||||
}
|
||||
}
|
||||
|
||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
shared_memory->dsp_return_data[0] = error_code;
|
||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||
|
||||
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "ankerl/unordered_dense.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||
#include "audio_core/adsp/mailbox.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -71,7 +68,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
|
||||
/**
|
||||
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
|
||||
*/
|
||||
void Init(std::stop_token stop_token);
|
||||
/**
|
||||
* Main OpusDecoder thread, responsible for processing the incoming Opus packets.
|
||||
@@ -91,9 +90,6 @@ private:
|
||||
/// Structure shared with the host, input data set by the host before sending a mailbox message,
|
||||
/// and the responses are written back by the OpusDecoder.
|
||||
SharedMemory* shared_memory{};
|
||||
|
||||
ankerl::unordered_dense::map<u64, OpusDecodeObject> decode_objects;
|
||||
ankerl::unordered_dense::map<u64, OpusMultiStreamDecodeObject> ms_decode_objects;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
||||
|
||||
@@ -4,17 +4,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/codec.h>
|
||||
#include <libavcodec/packet.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/frame.h>
|
||||
}
|
||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
|
||||
@@ -22,59 +13,101 @@ namespace {
|
||||
constexpr u32 OpusStreamCountMax = 255;
|
||||
|
||||
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
|
||||
return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax
|
||||
&& s32(stereo_stream_count) >= 0 && stereo_stream_count <= total_stream_count;
|
||||
return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax &&
|
||||
static_cast<s32>(stereo_stream_count) >= 0 &&
|
||||
stereo_stream_count <= total_stream_count;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count) {
|
||||
if (IsValidStreamCounts(total_stream_count, stereo_stream_count))
|
||||
return u32(sizeof(OpusMultiStreamDecodeObject)) + 2556 * (total_stream_count * stereo_stream_count);
|
||||
u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count,
|
||||
u32 stereo_stream_count) {
|
||||
if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) {
|
||||
return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) +
|
||||
opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8* mappings) {
|
||||
if ((codec = avcodec_find_decoder(AV_CODEC_ID_OPUS)) == nullptr)
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
if ((avctx = avcodec_alloc_context3(codec)))
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
avctx->sample_rate = sample_rate;
|
||||
av_channel_layout_default(&avctx->ch_layout, channel_count);
|
||||
return ResultSuccess;
|
||||
}
|
||||
OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
|
||||
auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
|
||||
auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
|
||||
|
||||
Result OpusMultiStreamDecodeObject::Shutdown() {
|
||||
if (avctx)
|
||||
avcodec_free_context(&avctx);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpusMultiStreamDecodeObject::ResetDecoder() {
|
||||
if (avctx) {
|
||||
avcodec_flush_buffers(avctx);
|
||||
return ResultSuccess;
|
||||
if (new_decoder->magic == DecodeMultiStreamObjectMagic) {
|
||||
if (!new_decoder->initialized ||
|
||||
(new_decoder->initialized && new_decoder->self == comparison)) {
|
||||
new_decoder->state_valid = true;
|
||||
}
|
||||
} else {
|
||||
new_decoder->initialized = false;
|
||||
new_decoder->state_valid = true;
|
||||
}
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
return *new_decoder;
|
||||
}
|
||||
|
||||
Result OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) {
|
||||
s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count,
|
||||
u32 channel_count, u32 stereo_stream_count,
|
||||
u8* mappings) {
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
return OPUS_OK;
|
||||
}
|
||||
|
||||
// See OpusDecodeObject::InitializeDecoder for an explanation of this
|
||||
decoder = (LibOpusMSDecoder*)(this + 1);
|
||||
s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count,
|
||||
stereo_stream_count, mappings);
|
||||
if (ret == OPUS_OK) {
|
||||
magic = DecodeMultiStreamObjectMagic;
|
||||
initialized = true;
|
||||
state_valid = true;
|
||||
self = this;
|
||||
final_range = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 OpusMultiStreamDecodeObject::Shutdown() {
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
magic = 0x0;
|
||||
initialized = false;
|
||||
state_valid = false;
|
||||
self = nullptr;
|
||||
final_range = 0;
|
||||
decoder = nullptr;
|
||||
}
|
||||
return OPUS_OK;
|
||||
}
|
||||
|
||||
s32 OpusMultiStreamDecodeObject::ResetDecoder() {
|
||||
return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data,
|
||||
u64 output_data_size, u64 input_data, u64 input_data_size) {
|
||||
ASSERT(initialized);
|
||||
out_sample_count = 0;
|
||||
if (avctx) {
|
||||
AVPacket* avpkt = av_packet_alloc();
|
||||
av_new_packet(avpkt, int(input_data_size));
|
||||
std::memcpy(avpkt->data, reinterpret_cast<const u8*>(input_data), input_data_size);
|
||||
avcodec_send_packet(avctx, avpkt);
|
||||
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
avcodec_receive_frame(avctx, frame);
|
||||
std::memcpy(reinterpret_cast<u16*>(output_data), frame->data, output_data_size);
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&avpkt);
|
||||
|
||||
out_sample_count = frame->nb_samples;
|
||||
return ResultSuccess;
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
|
||||
auto ret_code_or_samples = opus_multistream_decode(
|
||||
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
|
||||
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
|
||||
|
||||
if (ret_code_or_samples < OPUS_OK) {
|
||||
return ret_code_or_samples;
|
||||
}
|
||||
|
||||
out_sample_count = ret_code_or_samples;
|
||||
return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/adsp/apps/opus/opus_types.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/result.h"
|
||||
#include <opus_multistream.h>
|
||||
|
||||
struct AVCodec;
|
||||
struct AVCodecContext;
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
using LibOpusMSDecoder = ::OpusMSDecoder;
|
||||
static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF;
|
||||
|
||||
class OpusMultiStreamDecodeObject {
|
||||
public:
|
||||
static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count);
|
||||
static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2);
|
||||
|
||||
Result InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8* mappings);
|
||||
Result Shutdown();
|
||||
Result ResetDecoder();
|
||||
Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size);
|
||||
s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count,
|
||||
u32 stereo_stream_count, u8* mappings);
|
||||
s32 Shutdown();
|
||||
s32 ResetDecoder();
|
||||
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
|
||||
u64 input_data_size);
|
||||
u32 GetFinalRange() const noexcept {
|
||||
return final_range;
|
||||
}
|
||||
|
||||
AVCodec const* codec = nullptr;
|
||||
AVCodecContext* avctx = nullptr;
|
||||
private:
|
||||
u32 magic;
|
||||
bool initialized;
|
||||
bool state_valid;
|
||||
OpusMultiStreamDecodeObject* self;
|
||||
u32 final_range;
|
||||
LibOpusMSDecoder* decoder;
|
||||
};
|
||||
static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>);
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::ADSP {
|
||||
|
||||
static constexpr u32 DECODE_OBJECT_MAGIC = 0xDEADBEEF;
|
||||
struct LibOpusDecoder {
|
||||
u32 magic;
|
||||
bool initialized;
|
||||
bool state_valid;
|
||||
LibOpusDecoder* self;
|
||||
u32 final_range;
|
||||
void* decoder;
|
||||
};
|
||||
static_assert(sizeof(LibOpusDecoder) == 32);
|
||||
|
||||
static constexpr u32 DECODE_MULTISTREAM_OBJECT_MAGIC = 0xDEADBEEF;
|
||||
struct LibOpusMultistreamDecoder {
|
||||
u32 magic;
|
||||
bool initialized;
|
||||
bool state_valid;
|
||||
LibOpusMultistreamDecoder* self;
|
||||
u32 final_range;
|
||||
void* decoder;
|
||||
};
|
||||
static_assert(sizeof(LibOpusMultistreamDecoder) == 32);
|
||||
|
||||
}
|
||||
@@ -9,18 +9,39 @@
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/opus/hardware_opus.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace AudioCore::OpusDecoder {
|
||||
|
||||
namespace {
|
||||
using namespace Service::Audio;
|
||||
|
||||
static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) {
|
||||
s32 error{static_cast<s32>(error_code)};
|
||||
ASSERT(error <= OPUS_OK);
|
||||
switch (error) {
|
||||
case OPUS_ALLOC_FAIL:
|
||||
R_THROW(ResultLibOpusAllocFail);
|
||||
case OPUS_INVALID_STATE:
|
||||
R_THROW(ResultLibOpusInvalidState);
|
||||
case OPUS_UNIMPLEMENTED:
|
||||
R_THROW(ResultLibOpusUnimplemented);
|
||||
case OPUS_INVALID_PACKET:
|
||||
R_THROW(ResultLibOpusInvalidPacket);
|
||||
case OPUS_INTERNAL_ERROR:
|
||||
R_THROW(ResultLibOpusInternalError);
|
||||
case OPUS_BUFFER_TOO_SMALL:
|
||||
R_THROW(ResultBufferTooSmall);
|
||||
case OPUS_BAD_ARG:
|
||||
R_THROW(ResultLibOpusBadArg);
|
||||
case OPUS_OK:
|
||||
R_RETURN(ResultSuccess);
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HardwareOpus::HardwareOpus(Core::System& system_)
|
||||
: system{system_}
|
||||
, opus_decoder{system.AudioCore().ADSP().OpusDecoder()}
|
||||
{
|
||||
: system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} {
|
||||
opus_decoder.SetSharedMemory(shared_memory);
|
||||
}
|
||||
|
||||
@@ -71,7 +92,7 @@ Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count,
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
||||
@@ -99,7 +120,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
||||
@@ -113,7 +134,7 @@ Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
||||
"Expected Opus shutdown code {}, got {}",
|
||||
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
|
||||
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
|
||||
@@ -128,7 +149,7 @@ Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_si
|
||||
"Expected Opus shutdown code {}, got {}",
|
||||
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
|
||||
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
||||
@@ -152,12 +173,12 @@ Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
auto error_code = s32(shared_memory.dsp_return_data[0]);
|
||||
if (error_code == ResultSuccess.raw) {
|
||||
out_sample_count = u32(shared_memory.dsp_return_data[1]);
|
||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
||||
if (error_code == OPUS_OK) {
|
||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||
}
|
||||
R_RETURN(Result(u32(error_code)));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
||||
}
|
||||
|
||||
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
||||
@@ -166,27 +187,29 @@ Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void
|
||||
void* buffer, u64& out_time_taken,
|
||||
bool reset) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = u64(buffer);
|
||||
shared_memory.host_send_data[1] = u64(input_data);
|
||||
shared_memory.host_send_data[0] = (u64)buffer;
|
||||
shared_memory.host_send_data[1] = (u64)input_data;
|
||||
shared_memory.host_send_data[2] = input_data_size;
|
||||
shared_memory.host_send_data[3] = u64(output_data);
|
||||
shared_memory.host_send_data[3] = (u64)output_data;
|
||||
shared_memory.host_send_data[4] = output_data_size;
|
||||
shared_memory.host_send_data[5] = 0;
|
||||
shared_memory.host_send_data[6] = reset;
|
||||
|
||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
|
||||
opus_decoder.Send(ADSP::Direction::DSP,
|
||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
auto const error_code = shared_memory.dsp_return_data[0];
|
||||
if (error_code == ResultSuccess.raw) {
|
||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
||||
if (error_code == OPUS_OK) {
|
||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||
}
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
||||
}
|
||||
|
||||
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
||||
@@ -197,7 +220,8 @@ Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::MapMemoryOK, msg);
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::MapMemoryOK, msg);
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
R_SUCCEED();
|
||||
@@ -211,7 +235,8 @@ Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
|
||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <opus.h>
|
||||
|
||||
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||
#include "audio_core/adsp/mailbox.h"
|
||||
|
||||
@@ -1205,6 +1205,7 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
||||
|
||||
if (BOOST_NO_HEADERS)
|
||||
|
||||
+36
-7
@@ -4,6 +4,7 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "game_settings.h"
|
||||
@@ -16,6 +17,7 @@
|
||||
#include "common/string_util.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
|
||||
#include "launch_timestamp_cache.h"
|
||||
#include "core/core_timing.h"
|
||||
@@ -248,12 +250,30 @@ struct System::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
void SetNVDECActive(bool is_nvdec_active) {
|
||||
nvdec_active = is_nvdec_active;
|
||||
void NotifyNVDECChannelOpen(u64 process_id) {
|
||||
std::scoped_lock lock{nvdec_active_mutex};
|
||||
++nvdec_active_channels[process_id];
|
||||
}
|
||||
|
||||
void NotifyNVDECChannelClose(u64 process_id) {
|
||||
std::scoped_lock lock{nvdec_active_mutex};
|
||||
const auto it = nvdec_active_channels.find(process_id);
|
||||
if (it == nvdec_active_channels.end()) {
|
||||
return;
|
||||
}
|
||||
if (--it->second == 0) {
|
||||
nvdec_active_channels.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool GetNVDECActive() {
|
||||
return nvdec_active;
|
||||
std::scoped_lock lock{nvdec_active_mutex};
|
||||
return !nvdec_active_channels.empty();
|
||||
}
|
||||
|
||||
bool IsNVDECActiveForProcess(u64 process_id) {
|
||||
std::scoped_lock lock{nvdec_active_mutex};
|
||||
return nvdec_active_channels.contains(process_id);
|
||||
}
|
||||
|
||||
void InitializeDebugger(System& system, u16 port) {
|
||||
@@ -377,7 +397,7 @@ struct System::Impl {
|
||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||
}
|
||||
|
||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||
GameSettings::LoadOverrides(FileSys::GetBaseTitleID(program_id), gpu_core->Renderer());
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
@@ -505,6 +525,8 @@ struct System::Impl {
|
||||
|
||||
mutable std::mutex suspend_guard;
|
||||
std::mutex general_channel_mutex;
|
||||
std::mutex nvdec_active_mutex;
|
||||
std::unordered_map<u64, u32> nvdec_active_channels;
|
||||
std::atomic_bool is_paused{};
|
||||
std::atomic_bool is_shutting_down{};
|
||||
std::atomic_bool is_powered_on{};
|
||||
@@ -512,7 +534,6 @@ struct System::Impl {
|
||||
bool extended_memory_layout : 1 = false;
|
||||
bool exit_locked : 1 = false;
|
||||
bool exit_requested : 1 = false;
|
||||
bool nvdec_active : 1 = false;
|
||||
|
||||
void EnsureGeneralChannelInitialized(System& system) {
|
||||
if (!general_channel_event) {
|
||||
@@ -576,14 +597,22 @@ void System::UnstallApplication() {
|
||||
impl->UnstallApplication();
|
||||
}
|
||||
|
||||
void System::SetNVDECActive(bool is_nvdec_active) {
|
||||
impl->SetNVDECActive(is_nvdec_active);
|
||||
void System::NotifyNVDECChannelOpen(u64 process_id) {
|
||||
impl->NotifyNVDECChannelOpen(process_id);
|
||||
}
|
||||
|
||||
void System::NotifyNVDECChannelClose(u64 process_id) {
|
||||
impl->NotifyNVDECChannelClose(process_id);
|
||||
}
|
||||
|
||||
bool System::GetNVDECActive() {
|
||||
return impl->GetNVDECActive();
|
||||
}
|
||||
|
||||
bool System::IsNVDECActiveForProcess(u64 process_id) {
|
||||
return impl->IsNVDECActiveForProcess(process_id);
|
||||
}
|
||||
|
||||
void System::InitializeDebugger() {
|
||||
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
|
||||
}
|
||||
|
||||
+3
-1
@@ -191,8 +191,10 @@ public:
|
||||
std::unique_lock<std::mutex> StallApplication();
|
||||
void UnstallApplication();
|
||||
|
||||
void SetNVDECActive(bool is_nvdec_active);
|
||||
void NotifyNVDECChannelOpen(u64 process_id);
|
||||
void NotifyNVDECChannelClose(u64 process_id);
|
||||
[[nodiscard]] bool GetNVDECActive();
|
||||
[[nodiscard]] bool IsNVDECActiveForProcess(u64 process_id);
|
||||
|
||||
/**
|
||||
* Initialize the debugger.
|
||||
|
||||
@@ -161,7 +161,7 @@ std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64
|
||||
PatchManager::PatchManager(u64 title_id_,
|
||||
const Service::FileSystem::FileSystemController& fs_controller_,
|
||||
const ContentProvider& content_provider_)
|
||||
: title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {}
|
||||
: title_id{title_id_}, application_id{GetBaseTitleID(title_id_)}, fs_controller{fs_controller_}, content_provider{content_provider_} {}
|
||||
|
||||
PatchManager::~PatchManager() = default;
|
||||
|
||||
@@ -169,13 +169,41 @@ u64 PatchManager::GetTitleID() const {
|
||||
return title_id;
|
||||
}
|
||||
|
||||
u64 PatchManager::GetUpdateTitleIDForContent() const {
|
||||
const auto program_update_id = GetUpdateTitleID(title_id);
|
||||
if (program_update_id == GetUpdateTitleID(application_id) || content_provider.HasEntry(program_update_id, ContentRecordType::Program)) {
|
||||
return program_update_id;
|
||||
}
|
||||
return GetUpdateTitleID(application_id);
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> PatchManager::GetModificationLoadRoots() const {
|
||||
std::vector<VirtualDir> roots;
|
||||
roots.push_back(fs_controller.GetModificationLoadRoot(title_id));
|
||||
if (application_id != title_id) {
|
||||
roots.push_back(fs_controller.GetModificationLoadRoot(application_id));
|
||||
}
|
||||
std::erase(roots, nullptr);
|
||||
return roots;
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> PatchManager::GetSDMCModificationLoadRoots() const {
|
||||
std::vector<VirtualDir> roots;
|
||||
roots.push_back(fs_controller.GetSDMCModificationLoadRoot(title_id));
|
||||
if (application_id != title_id) {
|
||||
roots.push_back(fs_controller.GetSDMCModificationLoadRoot(application_id));
|
||||
}
|
||||
std::erase(roots, nullptr);
|
||||
return roots;
|
||||
}
|
||||
|
||||
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
|
||||
|
||||
if (exefs == nullptr)
|
||||
return exefs;
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
|
||||
bool update_disabled = true;
|
||||
std::optional<u32> enabled_version;
|
||||
@@ -183,7 +211,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
bool checked_manual = false;
|
||||
|
||||
const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider);
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update_tid = GetUpdateTitleIDForContent();
|
||||
|
||||
if (content_union) {
|
||||
// First, check ExternalContentProvider
|
||||
@@ -303,17 +331,21 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
}
|
||||
|
||||
// LayeredExeFS
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
const auto load_dirs = GetModificationLoadRoots();
|
||||
const auto sdmc_load_dirs = GetSDMCModificationLoadRoots();
|
||||
|
||||
std::vector<VirtualDir> patch_dirs = {sdmc_load_dir};
|
||||
if (load_dir != nullptr) {
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
for (const auto& sdmc_load_dir : sdmc_load_dirs) {
|
||||
patch_dirs.push_back(sdmc_load_dir);
|
||||
}
|
||||
for (const auto& load_dir : load_dirs) {
|
||||
const auto load_patch_dirs = load_dir->GetSubdirectories();
|
||||
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
|
||||
}
|
||||
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
|
||||
return l->GetName() < r->GetName();
|
||||
});
|
||||
|
||||
std::vector<VirtualDir> layers;
|
||||
layers.reserve(patch_dirs.size() + 1);
|
||||
@@ -347,7 +379,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
|
||||
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const {
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
const auto nso_build_id = fmt::format("{:0<64}", build_id);
|
||||
|
||||
std::vector<VirtualFile> out;
|
||||
@@ -412,15 +444,20 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
|
||||
|
||||
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
|
||||
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
const auto load_dirs = GetModificationLoadRoots();
|
||||
if (load_dirs.empty()) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return nso;
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
for (const auto& load_dir : load_dirs) {
|
||||
const auto load_patch_dirs = load_dir->GetSubdirectories();
|
||||
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
|
||||
}
|
||||
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
|
||||
return l->GetName() < r->GetName();
|
||||
});
|
||||
const auto patches = CollectPatches(patch_dirs, build_id);
|
||||
|
||||
auto out = nso;
|
||||
@@ -455,29 +492,39 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name)
|
||||
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
|
||||
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
const auto load_dirs = GetModificationLoadRoots();
|
||||
if (load_dirs.empty()) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
for (const auto& load_dir : load_dirs) {
|
||||
const auto load_patch_dirs = load_dir->GetSubdirectories();
|
||||
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
|
||||
}
|
||||
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
|
||||
return l->GetName() < r->GetName();
|
||||
});
|
||||
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
const auto load_dirs = GetModificationLoadRoots();
|
||||
if (load_dirs.empty()) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); });
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
for (const auto& load_dir : load_dirs) {
|
||||
const auto load_patch_dirs = load_dir->GetSubdirectories();
|
||||
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
|
||||
}
|
||||
std::stable_sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](auto const& l, auto const& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
// <mod dir> / <folder> / cheats / <build id>.txt
|
||||
std::vector<Core::Memory::CheatEntry> out;
|
||||
@@ -493,39 +540,52 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildI
|
||||
}
|
||||
// Uncareless user-friendly loading of patches (must start with 'cheat_')
|
||||
// <mod dir> / <cheat file>.txt
|
||||
for (auto const& f : load_dir->GetFiles()) {
|
||||
auto const name = f->GetName();
|
||||
if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
|
||||
std::vector<u8> data(f->GetSize());
|
||||
if (f->Read(data.data(), data.size()) == data.size()) {
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
auto const res = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
std::copy(res.begin(), res.end(), std::back_inserter(out));
|
||||
} else {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id);
|
||||
for (const auto& load_dir : load_dirs) {
|
||||
for (auto const& f : load_dir->GetFiles()) {
|
||||
auto const name = f->GetName();
|
||||
if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
|
||||
std::vector<u8> data(f->GetSize());
|
||||
if (f->Read(data.data(), data.size()) == data.size()) {
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
auto const res = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
std::copy(res.begin(), res.end(), std::back_inserter(out));
|
||||
} else {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, u64 application_id, ContentRecordType type,
|
||||
const Service::FileSystem::FileSystemController& fs_controller) {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
std::vector<VirtualDir> load_dirs{fs_controller.GetModificationLoadRoot(title_id)};
|
||||
std::vector<VirtualDir> sdmc_load_dirs{fs_controller.GetSDMCModificationLoadRoot(title_id)};
|
||||
if (application_id != title_id) {
|
||||
load_dirs.push_back(fs_controller.GetModificationLoadRoot(application_id));
|
||||
sdmc_load_dirs.push_back(fs_controller.GetSDMCModificationLoadRoot(application_id));
|
||||
}
|
||||
std::erase(load_dirs, nullptr);
|
||||
std::erase(sdmc_load_dirs, nullptr);
|
||||
if ((type != ContentRecordType::Program && type != ContentRecordType::Data &&
|
||||
type != ContentRecordType::HtmlDocument) ||
|
||||
(load_dir == nullptr && sdmc_load_dir == nullptr)) {
|
||||
(load_dirs.empty() && sdmc_load_dirs.empty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
|
||||
patch_dirs.push_back(sdmc_load_dir);
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
for (const auto& load_dir : load_dirs) {
|
||||
const auto load_patch_dirs = load_dir->GetSubdirectories();
|
||||
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
|
||||
}
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
|
||||
patch_dirs.insert(patch_dirs.end(), sdmc_load_dirs.begin(), sdmc_load_dirs.end());
|
||||
}
|
||||
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
|
||||
return l->GetName() < r->GetName();
|
||||
});
|
||||
|
||||
std::vector<VirtualDir> layers;
|
||||
std::vector<VirtualDir> layers_ext;
|
||||
@@ -597,8 +657,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
auto romfs = base_romfs;
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto update_tid = GetUpdateTitleIDForContent();
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
|
||||
bool update_disabled = true;
|
||||
std::optional<u32> enabled_version;
|
||||
@@ -705,7 +765,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
|
||||
// LayeredFS
|
||||
if (apply_layeredfs) {
|
||||
ApplyLayeredFS(romfs, title_id, type, fs_controller);
|
||||
ApplyLayeredFS(romfs, title_id, application_id, type, fs_controller);
|
||||
}
|
||||
|
||||
return romfs;
|
||||
@@ -717,10 +777,10 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
}
|
||||
|
||||
std::vector<Patch> out;
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update_tid = GetUpdateTitleIDForContent();
|
||||
|
||||
std::vector<Patch> external_update_patches;
|
||||
|
||||
@@ -869,7 +929,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
.version = "",
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.title_id = update_tid,
|
||||
.source = PatchSource::Unknown,
|
||||
.numeric_version = 0};
|
||||
|
||||
@@ -895,8 +955,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
}
|
||||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr) {
|
||||
for (const auto& mod_dir : GetModificationLoadRoots()) {
|
||||
for (auto const& f : mod_dir->GetFiles())
|
||||
if (auto const name = f->GetName(); name.starts_with("cheat_")) {
|
||||
auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end();
|
||||
@@ -963,8 +1022,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
}
|
||||
|
||||
// SDMC mod directory (RomFS LayeredFS)
|
||||
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
if (sdmc_mod_dir != nullptr) {
|
||||
for (const auto& sdmc_mod_dir : GetSDMCModificationLoadRoots()) {
|
||||
std::string types;
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredExeFS");
|
||||
@@ -999,10 +1057,10 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
dlc_match.reserve(dlc_entries_with_origin.size());
|
||||
for (const auto& [slot, entry] : dlc_entries_with_origin) {
|
||||
const auto base_tid = GetBaseTitleID(entry.title_id);
|
||||
const bool matches_base = base_tid == title_id;
|
||||
const bool matches_base = base_tid == application_id;
|
||||
if (!matches_base) {
|
||||
LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}",
|
||||
entry.title_id, base_tid, title_id);
|
||||
entry.title_id, base_tid, application_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1077,16 +1135,22 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
}
|
||||
|
||||
std::optional<u32> PatchManager::GetGameVersion() const {
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update_tid = GetUpdateTitleIDForContent();
|
||||
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
return content_provider.GetEntryVersion(update_tid);
|
||||
}
|
||||
|
||||
return content_provider.GetEntryVersion(title_id);
|
||||
if (const auto version = content_provider.GetEntryVersion(title_id); version.has_value()) {
|
||||
return version;
|
||||
}
|
||||
return content_provider.GetEntryVersion(application_id);
|
||||
}
|
||||
|
||||
PatchManager::Metadata PatchManager::GetControlMetadata() const {
|
||||
const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control);
|
||||
auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control);
|
||||
if (base_control_nca == nullptr && application_id != title_id) {
|
||||
base_control_nca = content_provider.GetEntry(application_id, ContentRecordType::Control);
|
||||
}
|
||||
if (base_control_nca == nullptr) {
|
||||
return {};
|
||||
}
|
||||
@@ -1162,8 +1226,14 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr)
|
||||
return metadata;
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
const auto update_id = FileSys::GetUpdateTitleID(application_id);
|
||||
const auto application_update_id = FileSys::GetUpdateTitleID(GetBaseTitleID(application_id));
|
||||
const FileSys::PatchManager pm_update{update_id, system.GetFileSystemController(), system.GetContentProvider()};
|
||||
metadata = pm_update.GetControlMetadata();
|
||||
if (metadata.first != nullptr || update_id == application_update_id)
|
||||
return metadata;
|
||||
const FileSys::PatchManager pm_application_update{application_update_id, system.GetFileSystemController(), system.GetContentProvider()};
|
||||
return pm_application_update.GetControlMetadata();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
@@ -109,10 +110,14 @@ public:
|
||||
[[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] u64 GetUpdateTitleIDForContent() const;
|
||||
[[nodiscard]] std::vector<VirtualDir> GetModificationLoadRoots() const;
|
||||
[[nodiscard]] std::vector<VirtualDir> GetSDMCModificationLoadRoots() const;
|
||||
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const;
|
||||
|
||||
u64 title_id;
|
||||
u64 application_id;
|
||||
const Service::FileSystem::FileSystemController& fs_controller;
|
||||
const ContentProvider& content_provider;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <optional>
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -104,8 +105,16 @@ std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
|
||||
|
||||
// TODO(DarkLordZach): When FSController/Game Card Support is added, if
|
||||
// current_process_game_card use correct StorageId
|
||||
launch.base_game_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program));
|
||||
launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
|
||||
auto base_slot = storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program);
|
||||
if (!base_slot) {
|
||||
base_slot = storage.GetSlotForEntry(FileSys::GetBaseTitleID(launch.title_id), FileSys::ContentRecordType::Program);
|
||||
}
|
||||
launch.base_game_storage_id = GetStorageIdForFrontendSlot(base_slot);
|
||||
auto update_slot = storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program);
|
||||
if (!update_slot) {
|
||||
update_slot = storage.GetSlotForEntry(FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(launch.title_id)), FileSys::ContentRecordType::Program);
|
||||
}
|
||||
launch.update_storage_id = GetStorageIdForFrontendSlot(update_slot);
|
||||
|
||||
system.GetARPManager().Register(launch.title_id, launch, out_control);
|
||||
return process;
|
||||
|
||||
@@ -158,7 +158,7 @@ Result IApplicationFunctions::EnsureSaveData(Out<u64> out_size, Common::UUID use
|
||||
LOG_INFO(Service_AM, "called, uid={}", user_id.FormattedString());
|
||||
|
||||
FileSys::SaveDataAttribute attribute{};
|
||||
attribute.program_id = m_applet->program_id;
|
||||
attribute.program_id = FileSys::GetBaseTitleID(m_applet->program_id);
|
||||
attribute.user_id = user_id.AsU128();
|
||||
attribute.type = FileSys::SaveDataType::Account;
|
||||
|
||||
@@ -238,7 +238,7 @@ Result IApplicationFunctions::ExtendSaveData(Out<u64> out_required_size, FileSys
|
||||
static_cast<u8>(type), user_id.FormattedString(), normal_size, journal_size);
|
||||
|
||||
system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize(
|
||||
type, m_applet->program_id, user_id.AsU128(), {normal_size, journal_size});
|
||||
type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128(), {normal_size, journal_size});
|
||||
|
||||
// The following value is used to indicate the amount of space remaining on failure
|
||||
// due to running out of space. Since we always succeed, this should be 0.
|
||||
@@ -252,7 +252,7 @@ Result IApplicationFunctions::GetSaveDataSize(Out<u64> out_normal_size, Out<u64>
|
||||
LOG_DEBUG(Service_AM, "called with type={} user_id={}", type, user_id.FormattedString());
|
||||
|
||||
const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize(
|
||||
type, m_applet->program_id, user_id.AsU128());
|
||||
type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128());
|
||||
|
||||
*out_normal_size = size.normal;
|
||||
*out_journal_size = size.journal;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -339,7 +340,7 @@ Result FileSystemController::RegisterProcess(
|
||||
registrations.emplace(process_id, Registration{
|
||||
.program_id = program_id,
|
||||
.romfs_factory = std::move(romfs_factory),
|
||||
.save_data_factory = CreateSaveDataFactory(program_id),
|
||||
.save_data_factory = CreateSaveDataFactory(FileSys::GetBaseTitleID(program_id)),
|
||||
});
|
||||
|
||||
LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fs_directory.h"
|
||||
@@ -313,7 +314,7 @@ Result FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(OutInterface<IFileSyste
|
||||
FileSys::ResultInvalidArgument);
|
||||
|
||||
if (attribute.program_id == 0) {
|
||||
attribute.program_id = program_id;
|
||||
attribute.program_id = FileSys::GetBaseTitleID(program_id);
|
||||
}
|
||||
|
||||
FileSys::VirtualDir dir{};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
|
||||
@@ -71,17 +72,23 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
|
||||
|
||||
void nvhost_nvdec::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
|
||||
LOG_INFO(Service_NVDRV, "NVDEC video stream started");
|
||||
system.SetNVDECActive(true);
|
||||
sessions[fd] = session_id;
|
||||
if (const auto* session = core.GetSession(session_id);
|
||||
session != nullptr && session->process != nullptr) {
|
||||
system.NotifyNVDECChannelOpen(session->process->GetId());
|
||||
}
|
||||
host1x.StartDevice(fd, Tegra::Host1x::ChannelType::NvDec, channel_syncpoint);
|
||||
}
|
||||
|
||||
void nvhost_nvdec::OnClose(DeviceFD fd) {
|
||||
LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
|
||||
host1x.StopDevice(fd, Tegra::Host1x::ChannelType::NvDec);
|
||||
system.SetNVDECActive(false);
|
||||
auto it = sessions.find(fd);
|
||||
if (it != sessions.end()) {
|
||||
if (const auto* session = core.GetSession(it->second);
|
||||
session != nullptr && session->process != nullptr) {
|
||||
system.NotifyNVDECChannelClose(session->process->GetId());
|
||||
}
|
||||
sessions.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <fmt/ranges.h>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
@@ -33,6 +37,7 @@ ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* se
|
||||
: SessionRequestHandler(system_.Kernel(), service_name_)
|
||||
, system{system_}
|
||||
, service_name{service_name_}
|
||||
, is_i_storage{std::string_view{service_name_} == "IStorage"}
|
||||
, handler_invoker{handler_invoker_}
|
||||
, max_sessions{max_sessions_}
|
||||
{}
|
||||
@@ -77,13 +82,22 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx,
|
||||
}
|
||||
|
||||
void ServiceFrameworkBase::InvokeRequest(HLERequestContext& ctx) {
|
||||
auto it = handlers.find(ctx.GetCommand());
|
||||
const auto command = ctx.GetCommand();
|
||||
auto it = handlers.find(command);
|
||||
const bool is_cmd_read = command == 0;
|
||||
FunctionInfoBase const* info = it == handlers.end() ? nullptr : &it->second;
|
||||
if (info == nullptr || info->handler_callback == nullptr)
|
||||
return ReportUnimplementedFunction(ctx, info);
|
||||
|
||||
LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer()));
|
||||
handler_invoker(this, info->handler_callback, ctx);
|
||||
|
||||
if (is_i_storage && is_cmd_read) {
|
||||
const auto* const process = ctx.GetThread().GetOwnerProcess();
|
||||
if (process != nullptr && system.IsNVDECActiveForProcess(process->GetId())) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds{600});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServiceFrameworkBase::InvokeRequestTipc(HLERequestContext& ctx) {
|
||||
|
||||
@@ -107,6 +107,8 @@ protected:
|
||||
Core::System& system;
|
||||
/// Identifier string used to connect to the service.
|
||||
const char* service_name;
|
||||
/// Whether this is the IStorage service.
|
||||
const bool is_i_storage;
|
||||
/// Function used to safely up-cast pointers to the derived class before invoking a handler.
|
||||
InvokerFn* handler_invoker;
|
||||
/// Maximum number of concurrent sessions that this service can handle.
|
||||
|
||||
@@ -70,6 +70,15 @@ std::optional<IndexedProgram> ResolveIndexedProgram(Core::System& system, u64 pr
|
||||
return IndexedProgram{std::move(update), target_id, true};
|
||||
}
|
||||
|
||||
const auto application_update_id =
|
||||
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(target_id));
|
||||
if (application_update_id != update_id) {
|
||||
if (auto update = provider.GetEntryRaw(application_update_id, FileSys::ContentRecordType::Program)) {
|
||||
LOG_INFO(Loader, "Program index {} has no base program, loading it from application update {:016X}", program_index, application_update_id);
|
||||
return IndexedProgram{std::move(update), target_id, true};
|
||||
}
|
||||
}
|
||||
|
||||
LOG_WARNING(Loader, "No program NCA for {:016X} (index {}), falling back to the container",
|
||||
target_id, program_index);
|
||||
return std::nullopt;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "common/hex_util.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
@@ -76,8 +77,13 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
|
||||
LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update");
|
||||
|
||||
const auto& installed = system.GetContentProvider();
|
||||
const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()),
|
||||
FileSys::ContentRecordType::Program);
|
||||
const auto program_update_id = FileSys::GetUpdateTitleID(nca->GetTitleId());
|
||||
auto update_nca = installed.GetEntry(program_update_id, FileSys::ContentRecordType::Program);
|
||||
if (update_nca == nullptr) {
|
||||
update_nca = installed.GetEntry(
|
||||
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(nca->GetTitleId())),
|
||||
FileSys::ContentRecordType::Program);
|
||||
}
|
||||
|
||||
if (update_nca) {
|
||||
exefs = update_nca->GetExeFS();
|
||||
|
||||
@@ -186,8 +186,13 @@ ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
|
||||
return ResultStatus::ErrorNoPackedUpdate;
|
||||
}
|
||||
|
||||
const auto read = nsp->GetNCAFile(FileSys::GetUpdateTitleID(nsp->GetProgramTitleID()),
|
||||
FileSys::ContentRecordType::Program);
|
||||
const auto program_update_id = FileSys::GetUpdateTitleID(nsp->GetProgramTitleID());
|
||||
auto read = nsp->GetNCAFile(program_update_id, FileSys::ContentRecordType::Program);
|
||||
if (read == nullptr) {
|
||||
read = nsp->GetNCAFile(
|
||||
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(nsp->GetProgramTitleID())),
|
||||
FileSys::ContentRecordType::Program);
|
||||
}
|
||||
|
||||
if (read == nullptr) {
|
||||
return ResultStatus::ErrorNoPackedUpdate;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -137,8 +138,13 @@ ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
|
||||
return ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
}
|
||||
|
||||
const auto read = xci->GetSecurePartitionNSP()->GetNCAFile(
|
||||
FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program);
|
||||
const auto program_update_id = FileSys::GetUpdateTitleID(program_id);
|
||||
auto read = xci->GetSecurePartitionNSP()->GetNCAFile(program_update_id, FileSys::ContentRecordType::Program);
|
||||
if (read == nullptr) {
|
||||
read = xci->GetSecurePartitionNSP()->GetNCAFile(
|
||||
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(program_id)),
|
||||
FileSys::ContentRecordType::Program);
|
||||
}
|
||||
if (read == nullptr) {
|
||||
return ResultStatus::ErrorNoPackedUpdate;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
@@ -56,13 +56,14 @@ inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_contro
|
||||
*/
|
||||
inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
|
||||
size_t count{};
|
||||
const auto application_id = FileSys::GetBaseTitleID(program_id);
|
||||
const auto& fs_controller = system.GetFileSystemController();
|
||||
const auto dlc_entries = system.GetContentProvider().ListEntriesFilter(
|
||||
FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
|
||||
std::vector<u64> program_dlc_entries;
|
||||
|
||||
for (const auto& entry : dlc_entries) {
|
||||
if (FileSys::GetBaseTitleID(entry.title_id) == program_id) {
|
||||
if (FileSys::GetBaseTitleID(entry.title_id) == application_id) {
|
||||
program_dlc_entries.push_back(entry.title_id);
|
||||
}
|
||||
}
|
||||
@@ -83,9 +84,17 @@ inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
|
||||
*/
|
||||
inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller,
|
||||
const u64 program_id) {
|
||||
const auto update_id = program_id | 0x800;
|
||||
return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
|
||||
fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
|
||||
const auto remove_update = [&fs_controller](u64 update_id) {
|
||||
return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
|
||||
fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
|
||||
};
|
||||
const auto update_id = FileSys::GetUpdateTitleID(program_id);
|
||||
const auto application_update_id =
|
||||
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(program_id));
|
||||
if (update_id != application_update_id && remove_update(update_id)) {
|
||||
return true;
|
||||
}
|
||||
return remove_update(application_update_id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,15 +120,26 @@ inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& f
|
||||
inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller,
|
||||
const u64 program_id, const std::string& mod_name) {
|
||||
// Check general Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = fs_controller.GetModificationLoadRoot(program_id);
|
||||
if (mod_dir != nullptr) {
|
||||
return mod_dir->DeleteSubdirectoryRecursive(mod_name);
|
||||
const auto remove_from_root = [&mod_name](const auto& root) {
|
||||
return root != nullptr && root->DeleteSubdirectoryRecursive(mod_name);
|
||||
};
|
||||
if (remove_from_root(fs_controller.GetModificationLoadRoot(program_id))) {
|
||||
return true;
|
||||
}
|
||||
if (FileSys::GetBaseTitleID(program_id) != program_id &&
|
||||
remove_from_root(
|
||||
fs_controller.GetModificationLoadRoot(FileSys::GetBaseTitleID(program_id)))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check SDMC mod directory (RomFS LayeredFS)
|
||||
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(program_id);
|
||||
if (sdmc_mod_dir != nullptr) {
|
||||
return sdmc_mod_dir->DeleteSubdirectoryRecursive(mod_name);
|
||||
if (remove_from_root(fs_controller.GetSDMCModificationLoadRoot(program_id))) {
|
||||
return true;
|
||||
}
|
||||
if (FileSys::GetBaseTitleID(program_id) != program_id &&
|
||||
remove_from_root(
|
||||
fs_controller.GetSDMCModificationLoadRoot(FileSys::GetBaseTitleID(program_id)))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_types.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "frontend_common/data_manager.h"
|
||||
#include "mod_manager.h"
|
||||
|
||||
@@ -40,7 +41,7 @@ std::vector<std::filesystem::path> GetModFolder(const std::string& root) {
|
||||
}
|
||||
|
||||
ModInstallResult InstallMod(const std::filesystem::path& path, const u64 program_id, const bool copy) {
|
||||
const auto program_id_string = fmt::format("{:016X}", program_id);
|
||||
const auto program_id_string = fmt::format("{:016X}", FileSys::GetBaseTitleID(program_id));
|
||||
const auto mod_name = path.filename();
|
||||
const auto mod_dir =
|
||||
DataManager::GetDataDir(DataManager::DataDir::Mods) / program_id_string / mod_name;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/hle/service/am/am_types.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
@@ -305,6 +306,7 @@ void RemoveAllTransferableShaderCaches(u64 program_id) {
|
||||
}
|
||||
|
||||
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) {
|
||||
program_id = FileSys::GetBaseTitleID(program_id);
|
||||
const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path));
|
||||
const auto config_file_name =
|
||||
program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini")
|
||||
|
||||
@@ -367,7 +367,9 @@ endif()
|
||||
if (YUZU_USE_EXTERNAL_FFMPEG)
|
||||
add_dependencies(video_core ffmpeg-build)
|
||||
endif()
|
||||
|
||||
target_include_directories(video_core PUBLIC ${FFmpeg_INCLUDE_DIR})
|
||||
|
||||
target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
|
||||
target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "common/settings_input.h"
|
||||
#include "configuration/shared_widget.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
@@ -50,7 +51,7 @@
|
||||
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
|
||||
std::vector<VkDeviceInfo::Record>& vk_device_records,
|
||||
Core::System& system_)
|
||||
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_},
|
||||
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{FileSys::GetBaseTitleID(title_id_)},
|
||||
system{system_},
|
||||
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
|
||||
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "common/fs/path_util.h"
|
||||
#include "configuration/addon/mod_select_dialog.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "frontend_common/mod_manager.h"
|
||||
@@ -137,7 +138,7 @@ void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file_) {
|
||||
}
|
||||
|
||||
void ConfigurePerGameAddons::SetTitleId(u64 id) {
|
||||
this->title_id = id;
|
||||
this->title_id = FileSys::GetBaseTitleID(id);
|
||||
}
|
||||
|
||||
void ConfigurePerGameAddons::InstallMods(const QStringList& mods) {
|
||||
|
||||
@@ -1922,7 +1922,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa
|
||||
std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())};
|
||||
const auto config_file_name = title_id == 0
|
||||
? Common::FS::PathToUTF8String(file_path.filename())
|
||||
: fmt::format("{:016X}", title_id);
|
||||
: fmt::format("{:016X}", FileSys::GetBaseTitleID(title_id));
|
||||
QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
|
||||
QtCommon::system->HIDCore().ReloadInputDevices();
|
||||
QtCommon::system->ApplySettings();
|
||||
@@ -2544,9 +2544,11 @@ void MainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pat
|
||||
}
|
||||
|
||||
const FileSys::NCA update_nca{packed_update_raw, nullptr};
|
||||
const auto selected_update_id = FileSys::GetUpdateTitleID(title_id);
|
||||
const auto application_update_id = FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(title_id));
|
||||
if (type != FileSys::ContentRecordType::Program ||
|
||||
update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS ||
|
||||
update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) {
|
||||
(update_nca.GetTitleId() != selected_update_id && update_nca.GetTitleId() != application_update_id)) {
|
||||
packed_update_raw = {};
|
||||
}
|
||||
|
||||
@@ -4421,6 +4423,7 @@ void MainWindow::SetFPSSuffix() {
|
||||
|
||||
bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
|
||||
u64* selected_title_id, u8* selected_content_record_type) {
|
||||
program_id = FileSys::GetBaseTitleID(program_id);
|
||||
using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
|
||||
boost::container::flat_set<ContentInfo> available_title_ids;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user