Compare commits
18 Commits
fs_externa
...
v0.0.4-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
612da00d1b | ||
|
|
4b34a5c9fa | ||
|
|
504df4856d | ||
|
|
6abaee94a6 | ||
|
|
4bf2e0a7aa | ||
|
|
82a476d458 | ||
|
|
ac2287f261 | ||
|
|
dd24ef244d | ||
|
|
5cc218084b | ||
|
|
c0663ccd6b | ||
|
|
8a7fe32a2c | ||
|
|
4b0bcfb0f7 | ||
|
|
82eb5a03f4 | ||
|
|
0e6ea2d9d6 | ||
|
|
6a9ad5e1ea | ||
|
|
903106c9b2 | ||
|
|
c70abc8e43 | ||
|
|
8ae7974092 |
@@ -37,17 +37,23 @@ endif()
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||
|
||||
# https://gitlab.kitware.com/cmake/cmake/-/merge_requests/11112
|
||||
# This works totally fine on MinGW64, but not CLANG{,ARM}64
|
||||
if(MINGW AND CXX_CLANG)
|
||||
set(CMAKE_SYSTEM_VERSION 10.0.0)
|
||||
endif()
|
||||
|
||||
# NB: this does not account for SPARC
|
||||
# If you get Eden working on SPARC, please shoot crueter@crueter.xyz multiple emails
|
||||
# and you will be hailed for eternity
|
||||
if (PLATFORM_SUN)
|
||||
# Terrific Solaris pkg shenanigans
|
||||
list(APPEND CMAKE_PREFIX_PATH "/usr/lib/qt/6.6/lib/amd64/cmake")
|
||||
list(APPEND CMAKE_MODULE_PATH "/usr/lib/qt/6.6/lib/amd64/cmake")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake")
|
||||
|
||||
# Amazing - absolutely incredible
|
||||
list(APPEND CMAKE_PREFIX_PATH "/usr/lib/amd64/cmake")
|
||||
list(APPEND CMAKE_MODULE_PATH "/usr/lib/amd64/cmake")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake")
|
||||
|
||||
# For some mighty reason, doing a normal release build sometimes may not trigger
|
||||
# the proper -O3 switch to materialize
|
||||
@@ -63,18 +69,18 @@ endif()
|
||||
|
||||
# Needed for FFmpeg w/ VAAPI and DRM
|
||||
if (PLATFORM_OPENBSD)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/X11R6/include")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I/usr/X11R6/include")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/X11R6/lib")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R6/lib")
|
||||
elseif (PLATFORM_NETBSD)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/X11R7/include")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I/usr/X11R7/include")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/X11R7/lib")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R7/lib")
|
||||
endif()
|
||||
|
||||
# NetBSD: Fun for the whole family!
|
||||
if (PLATFORM_NETBSD)
|
||||
set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:/usr/pkg/lib/ffmpeg7/pkgconfig")
|
||||
set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:${CMAKE_SYSROOT}/usr/pkg/lib/ffmpeg7/pkgconfig")
|
||||
endif()
|
||||
|
||||
# Detect current compilation architecture and create standard definitions
|
||||
@@ -92,12 +98,14 @@ function(detect_architecture symbol arch)
|
||||
if (ARCHITECTURE_${arch})
|
||||
set(ARCHITECTURE "${arch}" PARENT_SCOPE)
|
||||
set(ARCHITECTURE_${arch} 1 PARENT_SCOPE)
|
||||
add_definitions(-DARCHITECTURE_${arch}=1)
|
||||
add_definitions("-DARCHITECTURE_${arch}=1")
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if (NOT ENABLE_GENERIC)
|
||||
# https://sourceforge.net/p/predef/wiki/Architectures/
|
||||
# TODO: THIS IS FUCKING FLAWED ONLY THE FIRST SYMBOL THAT APPEARS WILL BE CONSIDERED :(
|
||||
if (MSVC)
|
||||
detect_architecture("_M_AMD64" x86_64)
|
||||
detect_architecture("_M_IX86" x86)
|
||||
@@ -109,6 +117,48 @@ if (NOT ENABLE_GENERIC)
|
||||
detect_architecture("__arm__" arm)
|
||||
detect_architecture("__aarch64__" arm64)
|
||||
endif()
|
||||
detect_architecture("__ARM64__" arm64)
|
||||
detect_architecture("__aarch64__" arm64)
|
||||
detect_architecture("_M_ARM64" arm64)
|
||||
|
||||
detect_architecture("__arm__" arm)
|
||||
detect_architecture("__TARGET_ARCH_ARM" arm)
|
||||
detect_architecture("_M_ARM" arm)
|
||||
|
||||
detect_architecture("__x86_64" x86_64)
|
||||
detect_architecture("__x86_64__" x86_64)
|
||||
detect_architecture("__amd64" x86_64)
|
||||
detect_architecture("_M_X64" x86_64)
|
||||
|
||||
detect_architecture("__i386" x86)
|
||||
detect_architecture("__i386__" x86)
|
||||
detect_architecture("_M_IX86" x86)
|
||||
|
||||
detect_architecture("__ia64" ia64)
|
||||
detect_architecture("__ia64__" ia64)
|
||||
detect_architecture("_M_IA64" ia64)
|
||||
|
||||
detect_architecture("__mips" mips)
|
||||
detect_architecture("__mips__" mips)
|
||||
detect_architecture("_M_MRX000" mips)
|
||||
|
||||
detect_architecture("__powerpc64__" ppc64)
|
||||
detect_architecture("__ppc64__" ppc64)
|
||||
detect_architecture("__PPC64__" ppc64)
|
||||
detect_architecture("_ARCH_PPC64" ppc64)
|
||||
|
||||
detect_architecture("__ppc__" ppc)
|
||||
detect_architecture("__ppc" ppc)
|
||||
detect_architecture("__powerpc__" ppc)
|
||||
detect_architecture("_ARCH_COM" ppc)
|
||||
detect_architecture("_ARCH_PWR" ppc)
|
||||
detect_architecture("_ARCH_PPC" ppc)
|
||||
detect_architecture("_M_MPPC" ppc)
|
||||
detect_architecture("_M_PPC" ppc)
|
||||
|
||||
detect_architecture("__riscv" riscv)
|
||||
|
||||
detect_architecture("__EMSCRIPTEN__" wasm)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED ARCHITECTURE)
|
||||
@@ -160,7 +210,7 @@ if (MSVC AND NOT CXX_CLANG)
|
||||
endif()
|
||||
|
||||
if (PLATFORM_FREEBSD)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/local/lib")
|
||||
|
||||
endif()
|
||||
|
||||
@@ -221,6 +271,7 @@ if(YUZU_ENABLE_LTO)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${COMPILER_SUPPORTS_LTO})
|
||||
endif()
|
||||
|
||||
option(USE_CCACHE "Use ccache for compilation" OFF)
|
||||
set(CCACHE_PATH "ccache" CACHE STRING "Path to ccache binary")
|
||||
if(USE_CCACHE)
|
||||
@@ -265,9 +316,11 @@ if (ANDROID OR WIN32 OR APPLE OR PLATFORM_SUN)
|
||||
# your own copy of it.
|
||||
set(DEFAULT_ENABLE_OPENSSL OFF)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
set(DEFAULT_ENABLE_OPENSSL ON)
|
||||
endif()
|
||||
|
||||
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
|
||||
if (ENABLE_OPENSSL)
|
||||
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL OFF)
|
||||
@@ -894,6 +947,14 @@ if(MSVC)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (MINGW)
|
||||
# This saves a truly ridiculous amount of time during linking
|
||||
# In my tests, without this it takes 2 mins, with it takes 3-5 seconds
|
||||
# or on GitHub Actions, 10 minutes -> 3 seconds
|
||||
set(MINGW_FLAGS "-Wl,--strip-all -Wl,--gc-sections")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_FLAGS}")
|
||||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
# Set yuzu project or yuzu-cmd project as default StartUp Project in Visual Studio depending on whether QT is enabled or not
|
||||
|
||||
@@ -594,6 +594,14 @@ function(AddCIPackage)
|
||||
add_ci_package(windows-arm64)
|
||||
endif()
|
||||
|
||||
if ((MINGW AND ARCHITECTURE_x86_64) AND NOT "mingw-amd64" IN_LIST DISABLED_PLATFORMS)
|
||||
add_ci_package(mingw-amd64)
|
||||
endif()
|
||||
|
||||
if ((MINGW AND ARCHITECTURE_arm64) AND NOT "mingw-arm64" IN_LIST DISABLED_PLATFORMS)
|
||||
add_ci_package(mingw-arm64)
|
||||
endif()
|
||||
|
||||
if (ANDROID AND NOT "android" IN_LIST DISABLED_PLATFORMS)
|
||||
add_ci_package(android)
|
||||
endif()
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2020 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
function(copy_yuzu_FFmpeg_deps target_dir)
|
||||
include(WindowsCopyFiles)
|
||||
set(DLL_DEST "$<TARGET_FILE_DIR:${target_dir}>/")
|
||||
file(READ "${FFmpeg_PATH}/requirements.txt" FFmpeg_REQUIRED_DLLS)
|
||||
string(STRIP "${FFmpeg_REQUIRED_DLLS}" FFmpeg_REQUIRED_DLLS)
|
||||
windows_copy_files(${target_dir} ${FFmpeg_LIBRARY_DIR} ${DLL_DEST} ${FFmpeg_REQUIRED_DLLS})
|
||||
endfunction(copy_yuzu_FFmpeg_deps)
|
||||
@@ -1,8 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
function(copy_yuzu_SDL_deps target_dir)
|
||||
include(WindowsCopyFiles)
|
||||
set(DLL_DEST "$<TARGET_FILE_DIR:${target_dir}>/")
|
||||
windows_copy_files(${target_dir} ${SDL2_DLL_DIR} ${DLL_DEST} SDL2.dll)
|
||||
endfunction(copy_yuzu_SDL_deps)
|
||||
@@ -1,3 +1,6 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# SPDX-FileCopyrightText: 2019 Citra Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -75,16 +78,16 @@ function(find_ffmpeg LIBNAME)
|
||||
)
|
||||
else()
|
||||
list(APPEND INCLUDE_PATHS
|
||||
/usr/local/include/ffmpeg
|
||||
/usr/local/include/lib${LIBNAME}
|
||||
/usr/include/ffmpeg
|
||||
/usr/include/lib${LIBNAME}
|
||||
/usr/include/ffmpeg/lib${LIBNAME}
|
||||
${CMAKE_SYSROOT}/usr/local/include/ffmpeg
|
||||
${CMAKE_SYSROOT}/usr/local/include/lib${LIBNAME}
|
||||
${CMAKE_SYSROOT}/usr/include/ffmpeg
|
||||
${CMAKE_SYSROOT}/usr/include/lib${LIBNAME}
|
||||
${CMAKE_SYSROOT}/usr/include/ffmpeg/lib${LIBNAME}
|
||||
)
|
||||
|
||||
list(APPEND LIB_PATHS
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
${CMAKE_SYSROOT}/usr/local/lib
|
||||
${CMAKE_SYSROOT}/usr/lib
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
"version": "3.6.0",
|
||||
"min_version": "1.1.1",
|
||||
"disabled_platforms": [
|
||||
"macos-universal"
|
||||
"macos-universal",
|
||||
"mingw-amd64",
|
||||
"mingw-arm64"
|
||||
]
|
||||
},
|
||||
"boost": {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
- `DISABLED_PLATFORMS`: List of platforms that lack artifacts for this package. Options:
|
||||
* `windows-amd64`
|
||||
* `windows-arm64`
|
||||
* `mingw-amd64`
|
||||
* `mingw-arm64`
|
||||
* `android`
|
||||
* `solaris-amd64`
|
||||
* `freebsd-amd64`
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
# Caveats
|
||||
|
||||
<!-- TOC -->
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [Gentoo Linux](#gentoo-linux)
|
||||
- [macOS](#macos)
|
||||
- [Solaris](#solaris)
|
||||
- [HaikuOS](#haikuos)
|
||||
- [OpenBSD](#openbsd)
|
||||
- [FreeBSD](#freebsd)
|
||||
- [NetBSD](#netbsd)
|
||||
- [Caveats](#caveats)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [Gentoo Linux](#gentoo-linux)
|
||||
- [macOS](#macos)
|
||||
- [Solaris](#solaris)
|
||||
- [HaikuOS](#haikuos)
|
||||
- [OpenBSD](#openbsd)
|
||||
- [FreeBSD](#freebsd)
|
||||
- [NetBSD](#netbsd)
|
||||
- [MSYS2](#msys2)
|
||||
- [Windows 8.1 and below](#windows-81-and-below)
|
||||
<!-- /TOC -->
|
||||
|
||||
## Arch Linux
|
||||
|
||||
- httplib AUR package is broken. Set `httplib_FORCE_BUNDLED=ON` if you have it installed.
|
||||
- Eden is also available as an [AUR package](https://aur.archlinux.org/packages/eden-git). If you are unable to build, either use that or compare your process to the PKGBUILD.
|
||||
Eden is also available as an [AUR package](https://aur.archlinux.org/packages/eden-git). If you are unable to build, either use that or compare your process to the PKGBUILD.
|
||||
|
||||
## Gentoo Linux
|
||||
|
||||
Do not use the system sirit or xbyak packages.
|
||||
[`games-emulation/eden`](https://gitweb.gentoo.org/repo/proj/guru.git/tree/games-emulation/eden) is available in the GURU. This repository also contains some additional dependencies, such as mcl, sirit, oaknut, etc.
|
||||
|
||||
If you're having issues with building, always consult that ebuild.
|
||||
|
||||
## macOS
|
||||
|
||||
@@ -102,3 +106,66 @@ cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -- -j`nproc`
|
||||
cmake --install build
|
||||
```
|
||||
|
||||
## MSYS2
|
||||
|
||||
`qt6-static` isn't supported yet.
|
||||
|
||||
Only the `MINGW64` environment is tested; however, all of the others should work (in theory) sans `MINGW32`.
|
||||
|
||||
Currently, only FFmpeg can be used as a system dependency; the others will result in linker errors.
|
||||
|
||||
When packaging an MSYS2 build, you will need to copy all dependent DLLs recursively alongside the `windeployqt6`; for example:
|
||||
|
||||
```sh
|
||||
# MSYS_TOOLCHAIN is typically just mingw64
|
||||
# since Windows is case-insensitive, you can set this to $MSYSTEM
|
||||
# or, if cross-compiling from Linux, set it to usr/x86_64-w64-mingw32
|
||||
export PATH="/${MSYS_TOOLCHAIN}/bin:$PATH"
|
||||
|
||||
# grab deps of a dll or exe and place them in the current dir
|
||||
deps() {
|
||||
# string parsing is fun
|
||||
objdump -p "$1" | awk '/DLL Name:/ {print $3}' | while read -r dll; do
|
||||
[ -z "$dll" ] && continue
|
||||
|
||||
# bin directory is used for DLLs, so we can do a quick "hack"
|
||||
# and use command to find the path of the DLL
|
||||
dllpath=$(command -v "$dll" 2>/dev/null || true)
|
||||
|
||||
[ -z "$dllpath" ] && continue
|
||||
|
||||
# explicitly exclude system32/syswow64 deps
|
||||
# these aren't needed to be bundled, as all systems already have them
|
||||
case "$dllpath" in
|
||||
*System32* | *SysWOW64*) continue ;;
|
||||
esac
|
||||
|
||||
# avoid copying deps multiple times
|
||||
if [ ! -f "$dll" ]; then
|
||||
echo "$dllpath"
|
||||
cp "$dllpath" "$dll"
|
||||
|
||||
# also grab the dependencies of the dependent DLL; e.g.
|
||||
# double-conversion is a dep of Qt6Core.dll but NOT eden.exe
|
||||
deps "$dllpath"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# NB: must be done in a directory containing eden.exe
|
||||
deps eden.exe
|
||||
|
||||
# deploy Qt plugins and such
|
||||
windeployqt6 --no-compiler-runtime --no-opengl-sw --no-system-dxc-compiler \
|
||||
--no-system-d3d-compiler eden.exe
|
||||
|
||||
# grab deps for Qt plugins
|
||||
find ./*/ -name "*.dll" | while read -r dll; do deps "$dll"; done
|
||||
```
|
||||
|
||||
## Windows 8.1 and below
|
||||
|
||||
DirectX 12 is not available - simply copy and paste a random DLL and name it `d3d12.dll`.
|
||||
|
||||
Install [Qt6 compatibility libraries](github.com/ANightly/qt6windows7) specifically Qt 6.9.5.
|
||||
|
||||
61
docs/Deps.md
61
docs/Deps.md
@@ -87,6 +87,53 @@ Notes for writers: Include build tools as well, assume user has NOTHING installe
|
||||
|
||||
Click on the arrows to expand.
|
||||
|
||||
<details>
|
||||
<summary>Gentoo Linux</summary>
|
||||
|
||||
GURU must be enabled:
|
||||
|
||||
```
|
||||
sudo emerge -a app-eselect/eselect-repository
|
||||
sudo eselect repository enable guru
|
||||
sudo emaint sync -r guru
|
||||
```
|
||||
|
||||
Now, install all deps:
|
||||
|
||||
```sh
|
||||
sudo emerge -a \
|
||||
app-arch/lz4 app-arch/zstd app-arch/unzip \
|
||||
dev-libs/libfmt dev-libs/libusb dev-libs/mcl dev-libs/sirit \
|
||||
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-libs/opus media-video/ffmpeg \
|
||||
media-libs/VulkanMemoryAllocator media-libs/libsdl2 media-libs/cubeb \
|
||||
net-libs/enet net-libs/mbedtls \
|
||||
sys-libs/zlib \
|
||||
dev-cpp/nlohmann_json dev-cpp/simpleini dev-cpp/cpp-httplib dev-cpp/cpp-jwt \
|
||||
games-util/gamemode \
|
||||
net-wireless/wireless-tools \
|
||||
dev-qt/qtbase:6 dev-libs/quazip \
|
||||
virtual/pkgconfig
|
||||
```
|
||||
|
||||
- On `amd64`, also add `dev-libs/xbyak`
|
||||
- On `riscv64`, also add `dev-libs/biscuit` (currently unavailable)
|
||||
- On `aarch64`, also add `dev-libs/oaknut`
|
||||
- If tests are enabled, also add `dev-libs/oaknut` and `dev-cpp/catch`
|
||||
|
||||
Required USE flags:
|
||||
- `dev-qt/qtbase network concurrent dbus gui widgets`
|
||||
- `dev-libs/quazip qt6`
|
||||
- `net-libs/mbedtls cmac`
|
||||
- `media-libs/libsdl2 haptic joystick sound video`
|
||||
- `dev-cpp/cpp-httplib ssl`
|
||||
|
||||
[Caveats](./Caveats.md#gentoo-linux)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Arch Linux</summary>
|
||||
|
||||
@@ -149,6 +196,8 @@ apk add g++ git cmake make mbedtls-dev mbedtls-static mesa-dev qt6-qtbase-dev qt
|
||||
`mbedtls-static` has to be specified otherwise `libeverest.a` and `libp256m.a` will fail to be found.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Void Linux</summary>
|
||||
|
||||
```sh
|
||||
@@ -158,6 +207,7 @@ xbps-install -Su git make cmake clang pkg-config patch mbedtls-devel SPIRV-Tools
|
||||
Yes, `nlohmann-json` is just named `json-c++`. Why?
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>NixOS</summary>
|
||||
|
||||
@@ -234,24 +284,27 @@ Then install the libraries: `sudo pkg install qt6 boost glslang libzip library/l
|
||||
* Download and install all dependencies:
|
||||
```
|
||||
BASE="git make autoconf libtool automake-wrapper jq patch"
|
||||
MINGW="SDL2 cmake python-pip qt6-base toolchain ffmpeg boost catch fmt lz4 nlohmann-json openssl zlib zstd enet opus mbedtls vulkan-devel libusb vulkan-memory-allocator unordered_dense clang ccache"
|
||||
|
||||
MINGW="qt6-base qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus mbedtls libusb unordered_dense"
|
||||
|
||||
packages="$BASE"
|
||||
for pkg in $MINGW; do
|
||||
packages="$packages mingw-w64-x86_64-$pkg"
|
||||
done
|
||||
|
||||
pacman -Syu --needed --noconfirm $packages
|
||||
pacman -Syuu --needed --noconfirm $packages
|
||||
```
|
||||
* Notes:
|
||||
- Using `qt6-static` is possible but currently untested.
|
||||
- Other environments are entirely untested, but should theoretically work provided you install all the necessary packages.
|
||||
- Clang is installed as it generally works better here. You can compile with GCC just fine, however.
|
||||
- GCC is proven to work better with the MinGW environment. If you choose to use Clang, you *may* be better off using the clang64 environment.
|
||||
- Add `qt-creator` to the `MINGW` variable to install Qt Creator. You can then create a Start Menu shortcut to the MinGW Qt Creator by running `powershell "\$s=(New-Object -COM WScript.Shell).CreateShortcut('C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Qt Creator.lnk');\$s.TargetPath='C:\\msys64\\mingw64\\bin\\qtcreator.exe';\$s.Save()"` in Git Bash or MSYS2.
|
||||
* Add MinGW binaries to the PATH if they aren't already:
|
||||
* `echo 'PATH=/mingw64/bin:$PATH' >> ~/.bashrc`
|
||||
* or `echo 'PATH=/mingw64/bin:$PATH' >> ~/.zshrc`
|
||||
|
||||
[Caveats](./Caveats.md#msys2).
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>HaikuOS</summary>
|
||||
@@ -263,6 +316,8 @@ pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel b
|
||||
[Caveats](./Caveats.md#haikuos).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>RedoxOS</summary>
|
||||
|
||||
TODO: Fix syscall crashes (heavy IO stalls and hangup due to net mutexes?)
|
||||
|
||||
6
externals/cpmfile.json
vendored
6
externals/cpmfile.json
vendored
@@ -23,7 +23,11 @@
|
||||
"package": "sirit",
|
||||
"name": "sirit",
|
||||
"repo": "eden-emulator/sirit",
|
||||
"version": "1.0.2"
|
||||
"version": "1.0.2",
|
||||
"disabled_platforms": [
|
||||
"mingw-amd64",
|
||||
"mingw-arm64"
|
||||
]
|
||||
},
|
||||
"httplib": {
|
||||
"repo": "yhirose/cpp-httplib",
|
||||
|
||||
3
externals/ffmpeg/CMakeLists.txt
vendored
3
externals/ffmpeg/CMakeLists.txt
vendored
@@ -107,9 +107,10 @@ endif()
|
||||
|
||||
if (YUZU_USE_BUNDLED_FFMPEG)
|
||||
# MSVC conflicts with ksuser otherwise
|
||||
# MinGW has the funny quirk of requiring avutil after avcodec
|
||||
# Android needs some deps to be compiled with PIC (TODO)
|
||||
# TODO(crueter) fix
|
||||
if (MSVC OR ANDROID)
|
||||
if (ANDROID)
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
else()
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
1
externals/ffmpeg/cpmfile.json
vendored
1
externals/ffmpeg/cpmfile.json
vendored
@@ -15,6 +15,7 @@
|
||||
"disabled_platforms": [
|
||||
"freebsd-amd64",
|
||||
"solaris-amd64",
|
||||
"openbsd-amd64",
|
||||
"macos-universal"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
||||
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
||||
RENDERER_DEBUG("debug"),
|
||||
RENDERER_FORCE_UNSUPPORTED_EXTENSIONS("force_unsupported_extensions"),
|
||||
RENDERER_PROVOKING_VERTEX("provoking_vertex"),
|
||||
RENDERER_DESCRIPTOR_INDEXING("descriptor_indexing"),
|
||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||
|
||||
@@ -139,6 +139,13 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.dynaStateValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FORCE_UNSUPPORTED_EXTENSIONS,
|
||||
titleId = R.string.force_unsupported_extensions,
|
||||
descriptionId = R.string.force_unsupported_extensions_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_PROVOKING_VERTEX,
|
||||
|
||||
@@ -453,6 +453,7 @@ class SettingsFragmentPresenter(
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.veil_extensions))
|
||||
add(ByteSetting.RENDERER_DYNA_STATE.key)
|
||||
add(BooleanSetting.RENDERER_FORCE_UNSUPPORTED_EXTENSIONS.key)
|
||||
add(BooleanSetting.RENDERER_PROVOKING_VERTEX.key)
|
||||
add(BooleanSetting.RENDERER_DESCRIPTOR_INDEXING.key)
|
||||
add(BooleanSetting.RENDERER_SAMPLE_SHADING.key)
|
||||
|
||||
@@ -1511,7 +1511,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
emulationState.newSurface(holder.surface)
|
||||
} else {
|
||||
emulationState.newSurface(holder.surface)
|
||||
// Surface changed due to rotation/config change
|
||||
// Only update surface reference, don't trigger state changes
|
||||
emulationState.updateSurfaceReference(holder.surface)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1842,6 +1844,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun updateSurfaceReference(surface: Surface?) {
|
||||
this.surface = surface
|
||||
if (this.surface != null && state == State.RUNNING) {
|
||||
NativeLibrary.surfaceChanged(this.surface)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun clearSurface() {
|
||||
if (surface == null) {
|
||||
|
||||
@@ -94,6 +94,8 @@
|
||||
<string name="dyna_state">Extended Dynamic State</string>
|
||||
<string name="dyna_state_description">Controls the number of features that can be used in Extended Dynamic State. Higher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors. The default value may vary depending on your system and hardware capabilities. This value can be changed until stability and a better visual quality are achieved.</string>
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="force_unsupported_extensions">Force Unsupported Extensions</string>
|
||||
<string name="force_unsupported_extensions_description">Bypasses all driver workarounds and safety checks. May cause crashes, graphical glitches, or instability. Only enable for testing purposes.</string>
|
||||
<string name="provoking_vertex">Provoking Vertex</string>
|
||||
<string name="provoking_vertex_description">Improves lighting and vertex handling in certain games. Only supported on Vulkan 1.0+ GPUs.</string>
|
||||
<string name="descriptor_indexing">Descriptor Indexing</string>
|
||||
|
||||
@@ -220,8 +220,8 @@ u64 SinkStream::GetExpectedPlayedSampleCount() {
|
||||
auto time_delta{cur_time - last_sample_count_update_time};
|
||||
auto exp_played_sample_count{min_played_sample_count + (TargetSampleRate * time_delta) / std::chrono::seconds{1}};
|
||||
|
||||
// Add 15ms of latency in sample reporting to allow for some leeway in scheduler timings
|
||||
return std::min<u64>(exp_played_sample_count, max_played_sample_count) + TargetSampleCount * 3;
|
||||
// Add 25ms of latency in sample reporting to allow for some leeway in scheduler timings
|
||||
return std::min<u64>(exp_played_sample_count, max_played_sample_count) + TargetSampleCount * 5;
|
||||
}
|
||||
|
||||
void SinkStream::WaitFreeSpace(std::stop_token stop_token) {
|
||||
@@ -231,9 +231,9 @@ void SinkStream::WaitFreeSpace(std::stop_token stop_token) {
|
||||
return paused || queued_buffers < max_queue_size;
|
||||
};
|
||||
|
||||
release_cv.wait_for(lk, std::chrono::milliseconds(10), can_continue);
|
||||
release_cv.wait_for(lk, std::chrono::milliseconds(7), can_continue);
|
||||
|
||||
if (queued_buffers > max_queue_size + 10) {
|
||||
if (queued_buffers > max_queue_size + 3) {
|
||||
release_cv.wait(lk, stop_token, can_continue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/backend.h"
|
||||
|
||||
#include "common/settings.h"
|
||||
|
||||
void assert_fail_impl() {
|
||||
#ifdef _MSC_VER
|
||||
extern "C" {
|
||||
__declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
}
|
||||
#endif
|
||||
void AssertFailSoftImpl() {
|
||||
if (Settings::values.use_debug_asserts) {
|
||||
Common::Log::Stop();
|
||||
Crash();
|
||||
#ifndef _MSC_VER
|
||||
# if defined(ARCHITECTURE_x86_64)
|
||||
__asm__ __volatile__("int $3");
|
||||
# elif defined(ARCHITECTURE_arm64)
|
||||
__asm__ __volatile__("brk #0");
|
||||
# else
|
||||
exit(1);
|
||||
# endif
|
||||
#else // POSIX ^^^ _MSC_VER vvv
|
||||
DebugBreak();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void unreachable_impl() {
|
||||
void AssertFatalImpl() {
|
||||
Common::Log::Stop();
|
||||
Crash();
|
||||
throw std::runtime_error("Unreachable code");
|
||||
std::abort();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
@@ -10,8 +13,8 @@
|
||||
// However touching this file yields a global recompilation as this header is included almost
|
||||
// everywhere. So let's just move the handling of the failed assert to a single cpp file.
|
||||
|
||||
void assert_fail_impl();
|
||||
[[noreturn]] void unreachable_impl();
|
||||
void AssertFailSoftImpl();
|
||||
[[noreturn]] void AssertFatalImpl();
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define YUZU_NO_INLINE __declspec(noinline)
|
||||
@@ -22,29 +25,29 @@ void assert_fail_impl();
|
||||
#define ASSERT(_a_) \
|
||||
([&]() YUZU_NO_INLINE { \
|
||||
if (!(_a_)) [[unlikely]] { \
|
||||
LOG_CRITICAL(Debug, "Assertion Failed!"); \
|
||||
assert_fail_impl(); \
|
||||
LOG_CRITICAL(Debug, "Assert"); \
|
||||
AssertFailSoftImpl(); \
|
||||
} \
|
||||
}())
|
||||
|
||||
#define ASSERT_MSG(_a_, ...) \
|
||||
([&]() YUZU_NO_INLINE { \
|
||||
if (!(_a_)) [[unlikely]] { \
|
||||
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
|
||||
assert_fail_impl(); \
|
||||
LOG_CRITICAL(Debug, "Assert\n" __VA_ARGS__); \
|
||||
AssertFailSoftImpl(); \
|
||||
} \
|
||||
}())
|
||||
|
||||
#define UNREACHABLE() \
|
||||
do { \
|
||||
LOG_CRITICAL(Debug, "Unreachable code!"); \
|
||||
unreachable_impl(); \
|
||||
LOG_CRITICAL(Debug, "Unreachable"); \
|
||||
AssertFatalImpl(); \
|
||||
} while (0)
|
||||
|
||||
#define UNREACHABLE_MSG(...) \
|
||||
do { \
|
||||
LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \
|
||||
unreachable_impl(); \
|
||||
LOG_CRITICAL(Debug, "Unreachable\n" __VA_ARGS__); \
|
||||
AssertFatalImpl(); \
|
||||
} while (0)
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
@@ -32,27 +32,9 @@
|
||||
#define INSERT_PADDING_WORDS_NOINIT(num_words) \
|
||||
[[maybe_unused]] std::array<u32, num_words> CONCAT2(pad, __LINE__)
|
||||
|
||||
#ifndef _MSC_VER
|
||||
|
||||
#if defined(ARCHITECTURE_x86_64)
|
||||
#define Crash() __asm__ __volatile__("int $3")
|
||||
#elif defined(ARCHITECTURE_arm64)
|
||||
#define Crash() __asm__ __volatile__("brk #0")
|
||||
#else
|
||||
#define Crash() exit(1)
|
||||
#endif
|
||||
|
||||
#else // _MSC_VER
|
||||
|
||||
// Locale Cross-Compatibility
|
||||
#define locale_t _locale_t
|
||||
|
||||
extern "C" {
|
||||
__declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
}
|
||||
#define Crash() DebugBreak()
|
||||
|
||||
#endif // _MSC_VER ndef
|
||||
#ifdef _MSC_VER
|
||||
# define locale_t _locale_t // Locale Cross-Compatibility
|
||||
#endif // _MSC_VER
|
||||
|
||||
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
|
||||
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
|
||||
|
||||
@@ -240,7 +240,7 @@ struct Values {
|
||||
Category::Cpu};
|
||||
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
|
||||
"cpu_accuracy", Category::Cpu};
|
||||
|
||||
SwitchableSetting<bool> vtable_bouncing{linkage, true, "vtable_bouncing", Category::Cpu};
|
||||
SwitchableSetting<bool> use_fast_cpu_time{linkage,
|
||||
false,
|
||||
"use_fast_cpu_time",
|
||||
@@ -546,6 +546,7 @@ struct Values {
|
||||
Category::RendererExtensions,
|
||||
Specialization::Scalar};
|
||||
|
||||
SwitchableSetting<bool> force_unsupported_extensions{linkage, false, "force_unsupported_extensions", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> provoking_vertex{linkage, false, "provoking_vertex", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> descriptor_indexing{linkage, false, "descriptor_indexing", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> sample_shading{linkage, false, "sample_shading", Category::RendererExtensions, Specialization::Paired};
|
||||
@@ -759,7 +760,6 @@ struct Values {
|
||||
|
||||
// Add-Ons
|
||||
std::map<u64, std::vector<std::string>> disabled_addons;
|
||||
std::vector<std::string> external_dirs;
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
@@ -81,7 +81,10 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
|
||||
if (auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription"); pf)
|
||||
pf(GetCurrentThread(), UTF8ToUTF16W(name).data()); // Windows 10+
|
||||
else
|
||||
; // No-op
|
||||
}
|
||||
|
||||
#else // !MSVC_VER, so must be POSIX threads
|
||||
@@ -109,7 +112,7 @@ void SetCurrentThreadName(const char* name) {
|
||||
buf[len] = '\0';
|
||||
pthread_setname_np(pthread_self(), buf);
|
||||
}
|
||||
#elif !defined(_WIN32) || defined(_MSC_VER)
|
||||
#elif defined(_WIN32)
|
||||
// mingw stub
|
||||
(void)name;
|
||||
#else
|
||||
|
||||
@@ -54,8 +54,6 @@ add_library(core STATIC
|
||||
file_sys/control_metadata.cpp
|
||||
file_sys/control_metadata.h
|
||||
file_sys/errors.h
|
||||
file_sys/external_content_index.cpp
|
||||
file_sys/external_content_index.h
|
||||
file_sys/fs_directory.h
|
||||
file_sys/fs_file.h
|
||||
file_sys/fs_filesystem.h
|
||||
|
||||
@@ -14,11 +14,33 @@ namespace Core {
|
||||
void ArmInterface::LogBacktrace(Kernel::KProcess* process) const {
|
||||
Kernel::Svc::ThreadContext ctx;
|
||||
this->GetContext(ctx);
|
||||
LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", ctx.sp, ctx.pc);
|
||||
LOG_ERROR(Core_ARM, "{:20}{:20}{:20}{:20}{}", "Module Name", "Address", "Original Address", "Offset", "Symbol");
|
||||
|
||||
std::array<u64, 32> xreg{
|
||||
ctx.r[0], ctx.r[1], ctx.r[2], ctx.r[3],
|
||||
ctx.r[4], ctx.r[5], ctx.r[6], ctx.r[7],
|
||||
ctx.r[8], ctx.r[9], ctx.r[10], ctx.r[11],
|
||||
ctx.r[12], ctx.r[13], ctx.r[14], ctx.r[15],
|
||||
ctx.r[16], ctx.r[17], ctx.r[18], ctx.r[19],
|
||||
ctx.r[20], ctx.r[21], ctx.r[22], ctx.r[23],
|
||||
ctx.r[24], ctx.r[25], ctx.r[26], ctx.r[27],
|
||||
ctx.r[28], ctx.fp, ctx.lr, ctx.sp,
|
||||
};
|
||||
|
||||
std::string msg = fmt::format("Backtrace @ PC={:016X}\n", ctx.pc);
|
||||
for (size_t i = 0; i < 32; i += 4)
|
||||
msg += fmt::format("R{:02}={:016X} R{:02}={:016X} R{:02}={:016X} R{:02}={:016X}\n",
|
||||
i + 0, xreg[i + 0], i + 1, xreg[i + 1],
|
||||
i + 2, xreg[i + 2], i + 3, xreg[i + 3]);
|
||||
for (size_t i = 0; i < 32; i += 2)
|
||||
msg += fmt::format("V{:02}={:016X}_{:016X} V{:02}={:016X}_{:016X}\n",
|
||||
i + 0, ctx.v[i + 0][0], ctx.v[i + 0][1],
|
||||
i + 1, ctx.v[i + 1][0], ctx.v[i + 1][1]);
|
||||
msg += fmt::format("PSTATE={:08X} FPCR={:08X} FPSR={:08X} TPIDR={:016X}\n", ctx.pstate, ctx.fpcr, ctx.fpsr, ctx.tpidr);
|
||||
msg += fmt::format("{:20}{:20}{:20}{:20}{}\n", "Module", "Address", "Original Address", "Offset", "Symbol");
|
||||
auto const backtrace = GetBacktraceFromContext(process, ctx);
|
||||
for (auto const& entry : backtrace)
|
||||
LOG_ERROR(Core_ARM, "{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address, entry.original_address, entry.offset, entry.name);
|
||||
msg += fmt::format("{:20}{:016X} {:016X} {:016X} {}\n", entry.module, entry.address, entry.original_address, entry.offset, entry.name);
|
||||
LOG_ERROR(Core_ARM, "{}", msg);
|
||||
}
|
||||
|
||||
const Kernel::DebugWatchpoint* ArmInterface::MatchingWatchpoint(
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/file_sys/external_content_index.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include "common/fs/fs_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.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/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
ExternalContentIndexer::ExternalContentIndexer(VirtualFilesystem vfs,
|
||||
ManualContentProvider& provider,
|
||||
ExternalContentPaths paths)
|
||||
: m_vfs(std::move(vfs)), m_provider(provider), m_paths(std::move(paths)) {}
|
||||
|
||||
void ExternalContentIndexer::Rebuild() {
|
||||
m_provider.ClearAllEntries();
|
||||
m_updates_by_title.clear();
|
||||
m_all_dlc.clear();
|
||||
|
||||
for (const auto& dir : m_paths.update_dirs) {
|
||||
IndexUpdatesDir(dir);
|
||||
}
|
||||
for (const auto& dir : m_paths.dlc_dirs) {
|
||||
IndexDlcDir(dir);
|
||||
}
|
||||
|
||||
Commit();
|
||||
}
|
||||
|
||||
static std::string ToLowerCopy(const std::string& s) {
|
||||
std::string out;
|
||||
out.resize(s.size());
|
||||
std::transform(s.begin(), s.end(), out.begin(),
|
||||
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
|
||||
return out;
|
||||
}
|
||||
|
||||
void ExternalContentIndexer::IndexUpdatesDir(const std::string& dir) {
|
||||
try {
|
||||
const fs::path p = Common::FS::ToU8String(dir);
|
||||
std::error_code ec;
|
||||
if (!fs::exists(p, ec) || ec)
|
||||
return;
|
||||
|
||||
if (fs::is_directory(p, ec) && !ec) {
|
||||
for (const auto& entry : fs::recursive_directory_iterator(
|
||||
p, fs::directory_options::skip_permission_denied, ec)) {
|
||||
if (entry.is_directory(ec))
|
||||
continue;
|
||||
TryIndexFileAsContainer(Common::FS::ToUTF8String(entry.path().u8string()), true);
|
||||
}
|
||||
TryIndexLooseDir(Common::FS::ToUTF8String(p.u8string()), true);
|
||||
} else {
|
||||
TryIndexFileAsContainer(Common::FS::ToUTF8String(p.u8string()), true);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Loader, "Error accessing update directory '{}': {}", dir, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalContentIndexer::IndexDlcDir(const std::string& dir) {
|
||||
try {
|
||||
const fs::path p = Common::FS::ToU8String(dir);
|
||||
std::error_code ec;
|
||||
if (!fs::exists(p, ec) || ec)
|
||||
return;
|
||||
|
||||
if (fs::is_directory(p, ec) && !ec) {
|
||||
for (const auto& entry : fs::recursive_directory_iterator(
|
||||
p, fs::directory_options::skip_permission_denied, ec)) {
|
||||
if (entry.is_directory(ec))
|
||||
continue;
|
||||
TryIndexFileAsContainer(Common::FS::ToUTF8String(entry.path().u8string()), false);
|
||||
}
|
||||
TryIndexLooseDir(Common::FS::ToUTF8String(p.u8string()), false);
|
||||
} else {
|
||||
TryIndexFileAsContainer(Common::FS::ToUTF8String(p.u8string()), false);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Loader, "Error accessing DLC directory '{}': {}", dir, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalContentIndexer::TryIndexFileAsContainer(const std::string& path, bool is_update) {
|
||||
const auto lower = ToLowerCopy(path);
|
||||
if (lower.size() >= 4 && lower.rfind(".nsp") == lower.size() - 4) {
|
||||
if (auto vf = m_vfs->OpenFile(path, OpenMode::Read)) {
|
||||
ParseContainerNSP(vf, is_update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalContentIndexer::TryIndexLooseDir(const std::string& dir, bool is_update) {
|
||||
fs::path p = Common::FS::ToU8String(dir);
|
||||
std::error_code ec;
|
||||
if (!fs::is_directory(p, ec) || ec)
|
||||
return;
|
||||
|
||||
for (const auto& entry :
|
||||
fs::recursive_directory_iterator(p, fs::directory_options::skip_permission_denied, ec)) {
|
||||
if (ec)
|
||||
break;
|
||||
if (!entry.is_regular_file(ec))
|
||||
continue;
|
||||
const auto path = Common::FS::ToUTF8String(entry.path().u8string());
|
||||
const auto lower = ToLowerCopy(path);
|
||||
if (lower.size() >= 9 && lower.rfind(".cnmt.nca") == lower.size() - 9) {
|
||||
if (auto vf = m_vfs->OpenFile(path, OpenMode::Read)) {
|
||||
ParseLooseCnmtNca(
|
||||
vf, Common::FS::ToUTF8String(entry.path().parent_path().u8string()), is_update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalContentIndexer::ParseContainerNSP(VirtualFile file, bool is_update) {
|
||||
if (file == nullptr)
|
||||
return;
|
||||
NSP nsp(file);
|
||||
if (nsp.GetStatus() != Loader::ResultStatus::Success) {
|
||||
LOG_WARNING(Loader, "ExternalContent: NSP parse failed");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto title_map = nsp.GetNCAs();
|
||||
if (title_map.empty())
|
||||
return;
|
||||
|
||||
for (const auto& [title_id, nca_map] : title_map) {
|
||||
std::shared_ptr<NCA> meta_nca;
|
||||
for (const auto& [key, nca_ptr] : nca_map) {
|
||||
if (nca_ptr && nca_ptr->GetType() == NCAContentType::Meta) {
|
||||
meta_nca = nca_ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!meta_nca)
|
||||
continue;
|
||||
|
||||
auto cnmt_opt = ExtractCnmtFromMetaNca(*meta_nca);
|
||||
if (!cnmt_opt)
|
||||
continue;
|
||||
const auto& cnmt = *cnmt_opt;
|
||||
|
||||
const auto base_id = BaseTitleId(title_id);
|
||||
|
||||
if (is_update && cnmt.GetType() == TitleType::Update) {
|
||||
ParsedUpdate candidate{};
|
||||
// Register updates under their Update TID so PatchManager can find/apply them
|
||||
candidate.title_id = FileSys::GetUpdateTitleID(base_id);
|
||||
candidate.version = cnmt.GetTitleVersion();
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto it = nca_map.find({cnmt.GetType(), rec.type});
|
||||
if (it != nca_map.end() && it->second) {
|
||||
candidate.ncas[rec.type] = it->second->GetBaseFile();
|
||||
}
|
||||
}
|
||||
auto& vec = m_updates_by_title[base_id];
|
||||
vec.emplace_back(std::move(candidate));
|
||||
} else if (cnmt.GetType() == TitleType::AOC) {
|
||||
const auto dlc_title_id = cnmt.GetTitleID();
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto it = nca_map.find({cnmt.GetType(), rec.type});
|
||||
if (it != nca_map.end() && it->second) {
|
||||
m_all_dlc.push_back(
|
||||
ParsedDlcRecord{dlc_title_id, {}, it->second->GetBaseFile()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalContentIndexer::ParseLooseCnmtNca(VirtualFile meta_nca_file, const std::string& folder,
|
||||
bool is_update) {
|
||||
if (meta_nca_file == nullptr)
|
||||
return;
|
||||
|
||||
NCA meta(meta_nca_file);
|
||||
|
||||
if (!IsMeta(meta))
|
||||
return;
|
||||
|
||||
auto cnmt_opt = ExtractCnmtFromMetaNca(meta);
|
||||
if (!cnmt_opt)
|
||||
return;
|
||||
const auto& cnmt = *cnmt_opt;
|
||||
|
||||
const auto base_id = BaseTitleId(cnmt.GetTitleID());
|
||||
|
||||
if (is_update && cnmt.GetType() == TitleType::Update) {
|
||||
ParsedUpdate candidate{};
|
||||
// Register updates under their Update TID so PatchManager can find/apply them
|
||||
candidate.title_id = FileSys::GetUpdateTitleID(base_id);
|
||||
candidate.version = cnmt.GetTitleVersion();
|
||||
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto file_name = Common::HexToString(rec.nca_id) + ".nca";
|
||||
const auto full = Common::FS::ToUTF8String(
|
||||
(fs::path(Common::FS::ToU8String(folder)) / fs::path(file_name)).u8string());
|
||||
if (auto vf = m_vfs->OpenFile(full, OpenMode::Read)) {
|
||||
candidate.ncas[rec.type] = vf;
|
||||
}
|
||||
}
|
||||
|
||||
auto& vec = m_updates_by_title[base_id];
|
||||
vec.emplace_back(std::move(candidate));
|
||||
} else if (cnmt.GetType() == TitleType::AOC) {
|
||||
const auto dlc_title_id = cnmt.GetTitleID();
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto file_name = Common::HexToString(rec.nca_id) + ".nca";
|
||||
const auto full = Common::FS::ToUTF8String(
|
||||
(fs::path(Common::FS::ToU8String(folder)) / fs::path(file_name)).u8string());
|
||||
if (auto vf = m_vfs->OpenFile(full, OpenMode::Read)) {
|
||||
ParsedDlcRecord dl{dlc_title_id, {}, vf};
|
||||
m_all_dlc.push_back(std::move(dl));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<CNMT> ExternalContentIndexer::ExtractCnmtFromMetaNca(const NCA& meta_nca) {
|
||||
if (meta_nca.GetStatus() != Loader::ResultStatus::Success)
|
||||
return std::nullopt;
|
||||
|
||||
const auto subs = meta_nca.GetSubdirectories();
|
||||
if (subs.empty() || !subs[0])
|
||||
return std::nullopt;
|
||||
|
||||
const auto files = subs[0]->GetFiles();
|
||||
if (files.empty() || !files[0])
|
||||
return std::nullopt;
|
||||
|
||||
CNMT cnmt(files[0]);
|
||||
return cnmt;
|
||||
}
|
||||
|
||||
ExternalContentIndexer::TitleID ExternalContentIndexer::BaseTitleId(TitleID id) {
|
||||
return FileSys::GetBaseTitleID(id);
|
||||
}
|
||||
|
||||
bool ExternalContentIndexer::IsMeta(const NCA& nca) {
|
||||
return nca.GetType() == NCAContentType::Meta;
|
||||
}
|
||||
|
||||
void ExternalContentIndexer::Commit() {
|
||||
// Updates: register all discovered versions per base title under unique variant TIDs,
|
||||
// and additionally register the highest version under the canonical update TID for default
|
||||
// usage.
|
||||
size_t update_variants_count = 0;
|
||||
for (auto& [base_title, vec] : m_updates_by_title) {
|
||||
if (vec.empty())
|
||||
continue;
|
||||
// sort ascending by version, dedupe identical versions (for NAND overlap, for example)
|
||||
std::stable_sort(vec.begin(), vec.end(), [](const ParsedUpdate& a, const ParsedUpdate& b) {
|
||||
return a.version < b.version;
|
||||
});
|
||||
vec.erase(std::unique(vec.begin(), vec.end(),
|
||||
[](const ParsedUpdate& a, const ParsedUpdate& b) {
|
||||
return a.version == b.version;
|
||||
}),
|
||||
vec.end());
|
||||
|
||||
// highest version for canonical TID
|
||||
const auto& latest = vec.back();
|
||||
for (const auto& [rtype, file] : latest.ncas) {
|
||||
if (!file)
|
||||
continue;
|
||||
const auto canonical_tid = FileSys::GetUpdateTitleID(base_title);
|
||||
m_provider.AddEntry(TitleType::Update, rtype, canonical_tid, file);
|
||||
}
|
||||
|
||||
// variants under update_tid + i (i starts at1 to avoid colliding with canonical)
|
||||
for (size_t i = 0; i < vec.size(); ++i) {
|
||||
const auto& upd = vec[i];
|
||||
const u64 variant_tid = FileSys::GetUpdateTitleID(base_title) + static_cast<u64>(i + 1);
|
||||
for (const auto& [rtype, file] : upd.ncas) {
|
||||
if (!file)
|
||||
continue;
|
||||
m_provider.AddEntry(TitleType::Update, rtype, variant_tid, file);
|
||||
}
|
||||
}
|
||||
update_variants_count += vec.size();
|
||||
}
|
||||
|
||||
// DLC: additive
|
||||
for (const auto& dlc : m_all_dlc) {
|
||||
if (!dlc.file)
|
||||
continue;
|
||||
m_provider.AddEntry(TitleType::AOC, ContentRecordType::Data, dlc.title_id, dlc.file);
|
||||
}
|
||||
|
||||
LOG_INFO(Loader,
|
||||
"ExternalContent: registered updates for {} titles ({} variants), {} DLC records",
|
||||
m_updates_by_title.size(), update_variants_count, m_all_dlc.size());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -1,74 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class ManualContentProvider;
|
||||
class NCA;
|
||||
|
||||
struct ExternalContentPaths {
|
||||
std::vector<std::string> update_dirs;
|
||||
std::vector<std::string> dlc_dirs;
|
||||
};
|
||||
|
||||
class ExternalContentIndexer {
|
||||
public:
|
||||
ExternalContentIndexer(VirtualFilesystem vfs,
|
||||
ManualContentProvider& provider,
|
||||
ExternalContentPaths paths);
|
||||
|
||||
void Rebuild();
|
||||
|
||||
private:
|
||||
using TitleID = u64;
|
||||
|
||||
struct ParsedUpdate {
|
||||
TitleID title_id{};
|
||||
u32 version{};
|
||||
std::unordered_map<ContentRecordType, VirtualFile> ncas;
|
||||
};
|
||||
|
||||
struct ParsedDlcRecord {
|
||||
TitleID title_id{};
|
||||
NcaID nca_id{};
|
||||
VirtualFile file{};
|
||||
};
|
||||
|
||||
void IndexUpdatesDir(const std::string& dir);
|
||||
void IndexDlcDir(const std::string& dir);
|
||||
|
||||
void TryIndexFileAsContainer(const std::string& path, bool is_update);
|
||||
void TryIndexLooseDir(const std::string& dir, bool is_update);
|
||||
|
||||
void ParseContainerNSP(VirtualFile file, bool is_update);
|
||||
void ParseLooseCnmtNca(VirtualFile meta_nca_file, const std::string& folder, bool is_update);
|
||||
|
||||
static std::optional<CNMT> ExtractCnmtFromMetaNca(const NCA& meta_nca);
|
||||
static TitleID BaseTitleId(TitleID id);
|
||||
static bool IsMeta(const NCA& nca);
|
||||
|
||||
void Commit();
|
||||
|
||||
private:
|
||||
VirtualFilesystem m_vfs;
|
||||
ManualContentProvider& m_provider;
|
||||
ExternalContentPaths m_paths;
|
||||
|
||||
std::unordered_map<TitleID, std::vector<ParsedUpdate>> m_updates_by_title;
|
||||
|
||||
std::vector<ParsedDlcRecord> m_all_dlc;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -8,9 +8,6 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -67,30 +64,6 @@ std::string FormatTitleVersion(u32 version,
|
||||
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
||||
}
|
||||
|
||||
static std::array<int, 4> ParseVersionComponents(std::string_view label) {
|
||||
std::array<int, 4> out{0, 0, 0, 0};
|
||||
if (!label.empty() && (label.front() == 'v' || label.front() == 'V')) {
|
||||
label.remove_prefix(1);
|
||||
}
|
||||
size_t part = 0;
|
||||
size_t start = 0;
|
||||
std::string s(label);
|
||||
while (part < out.size() && start < s.size()) {
|
||||
size_t dot = s.find('.', start);
|
||||
auto token = s.substr(start, dot == std::string::npos ? std::string::npos : dot - start);
|
||||
try {
|
||||
out[part] = std::stoi(token);
|
||||
} catch (...) {
|
||||
out[part] = 0;
|
||||
}
|
||||
++part;
|
||||
if (dot == std::string::npos)
|
||||
break;
|
||||
start = dot + 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
|
||||
// doesn't have a directory with name.
|
||||
VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
|
||||
@@ -144,83 +117,6 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
|
||||
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
||||
}
|
||||
|
||||
static std::vector<u64> EnumerateUpdateVariants(const ContentProvider& provider, u64 base_title_id,
|
||||
ContentRecordType type) {
|
||||
std::vector<u64> tids;
|
||||
const auto entries = provider.ListEntriesFilter(TitleType::Update, type);
|
||||
for (const auto& e : entries) {
|
||||
if (GetBaseTitleID(e.title_id) == base_title_id) {
|
||||
tids.push_back(e.title_id);
|
||||
}
|
||||
}
|
||||
std::sort(tids.begin(), tids.end());
|
||||
tids.erase(std::unique(tids.begin(), tids.end()), tids.end());
|
||||
return tids;
|
||||
}
|
||||
|
||||
static std::string GetUpdateVersionLabel(u64 update_tid,
|
||||
const Service::FileSystem::FileSystemController& fs,
|
||||
const ContentProvider& provider) {
|
||||
PatchManager pm{update_tid, fs, provider};
|
||||
const auto meta = pm.GetControlMetadata();
|
||||
if (meta.first != nullptr) {
|
||||
auto str = meta.first->GetVersionString();
|
||||
if (!str.empty()) {
|
||||
if (str.front() != 'v' && str.front() != 'V') {
|
||||
str.insert(str.begin(), 'v');
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
const auto ver = provider.GetEntryVersion(update_tid).value_or(0);
|
||||
return FormatTitleVersion(ver);
|
||||
}
|
||||
|
||||
static std::optional<u64> ChooseUpdateVariant(const ContentProvider& provider, u64 base_title_id,
|
||||
ContentRecordType type,
|
||||
const Service::FileSystem::FileSystemController& fs) {
|
||||
const auto& disabled = Settings::values.disabled_addons[base_title_id];
|
||||
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto candidates = EnumerateUpdateVariants(provider, base_title_id, type);
|
||||
if (candidates.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Sort candidates by numeric version descending, using meta version; fallback to0
|
||||
std::vector<std::pair<u32, u64>> ordered; // (version,uTid)
|
||||
ordered.reserve(candidates.size());
|
||||
for (const auto tid : candidates) {
|
||||
const u32 ver = provider.GetEntryVersion(tid).value_or(0);
|
||||
ordered.emplace_back(ver, tid);
|
||||
}
|
||||
std::sort(ordered.begin(), ordered.end(), [](auto const& a, auto const& b) {
|
||||
return a.first > b.first; // highest version first
|
||||
});
|
||||
|
||||
// Pick the first candidate that is not specifically disabled via "Update vX.Y.Z"
|
||||
for (const auto& [ver, tid] : ordered) {
|
||||
const auto label = GetUpdateVersionLabel(tid, fs, provider);
|
||||
const auto toggle_name = fmt::format("Update {}", label);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), toggle_name) == disabled.cend()) {
|
||||
return tid;
|
||||
}
|
||||
}
|
||||
|
||||
// All variants disabled, do not apply any update
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static bool HasVariantPreference(const std::vector<std::string>& disabled) {
|
||||
return std::any_of(disabled.begin(), disabled.end(), [](const std::string& s) {
|
||||
return s.rfind("Update v", 0) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
PatchManager::PatchManager(u64 title_id_,
|
||||
@@ -245,22 +141,13 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
|
||||
// Game Updates
|
||||
std::optional<u64> selected_update_tid;
|
||||
if (!update_disabled) {
|
||||
selected_update_tid = ChooseUpdateVariant(content_provider, title_id,
|
||||
ContentRecordType::Program, fs_controller);
|
||||
if (!selected_update_tid.has_value()) {
|
||||
selected_update_tid = GetUpdateTitleID(title_id);
|
||||
}
|
||||
}
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
|
||||
|
||||
if (selected_update_tid.has_value()) {
|
||||
const auto update =
|
||||
content_provider.GetEntry(*selected_update_tid, ContentRecordType::Program);
|
||||
if (update != nullptr && update->GetExeFS() != nullptr) {
|
||||
LOG_INFO(Loader, " ExeFS: Update applied successfully");
|
||||
exefs = update->GetExeFS();
|
||||
}
|
||||
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
|
||||
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
|
||||
exefs = update->GetExeFS();
|
||||
}
|
||||
|
||||
// LayeredExeFS
|
||||
@@ -429,8 +316,7 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name)
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
|
||||
const BuildID& build_id_) const {
|
||||
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) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
@@ -439,19 +325,16 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
|
||||
|
||||
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(); });
|
||||
std::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;
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) == disabled.cend()) {
|
||||
if (auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats");
|
||||
cheats_dir != nullptr) {
|
||||
if (auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); cheats_dir != nullptr) {
|
||||
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true))
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
if (auto const res =
|
||||
ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false))
|
||||
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false))
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
}
|
||||
}
|
||||
@@ -461,17 +344,14 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
|
||||
auto const patch_files = load_dir->GetFiles();
|
||||
for (auto const& f : patch_files) {
|
||||
auto const name = f->GetName();
|
||||
if (name.starts_with("cheat_") &&
|
||||
std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
|
||||
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()));
|
||||
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);
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,12 +447,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
auto romfs = base_romfs;
|
||||
|
||||
// Game Updates
|
||||
std::optional<u64> selected_update_tid =
|
||||
ChooseUpdateVariant(content_provider, title_id, type, fs_controller);
|
||||
if (!selected_update_tid.has_value()) {
|
||||
selected_update_tid = GetUpdateTitleID(title_id);
|
||||
}
|
||||
const auto update_raw = content_provider.GetEntryRaw(*selected_update_tid, type);
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update_raw = content_provider.GetEntryRaw(update_tid, type);
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto update_disabled =
|
||||
@@ -582,45 +458,18 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
const auto new_nca = std::make_shared<NCA>(update_raw, base_nca);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
const auto ver_num = content_provider.GetEntryVersion(*selected_update_tid).value_or(0);
|
||||
LOG_DEBUG(Loader, " RomFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(ver_num));
|
||||
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
|
||||
romfs = new_nca->GetRomFS();
|
||||
} else {
|
||||
LOG_WARNING(Loader, " RomFS: Update NCA is not valid");
|
||||
}
|
||||
} else if (!update_disabled && base_nca != nullptr) {
|
||||
ContentRecordType alt_type = type;
|
||||
|
||||
if (type == ContentRecordType::Program) {
|
||||
alt_type = ContentRecordType::Data;
|
||||
} else if (type == ContentRecordType::Data) {
|
||||
alt_type = ContentRecordType::Program;
|
||||
}
|
||||
|
||||
if (alt_type != type) {
|
||||
const auto alt_update_raw =
|
||||
content_provider.GetEntryRaw(*selected_update_tid, alt_type);
|
||||
if (alt_update_raw != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(alt_update_raw, base_nca);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
LOG_DEBUG(Loader, " RomFS: Update (fallback {}) applied successfully",
|
||||
alt_type == ContentRecordType::Data ? "DATA" : "PROGRAM");
|
||||
romfs = new_nca->GetRomFS();
|
||||
} else {
|
||||
LOG_WARNING(Loader, " RomFS: Update (fallback) NCA is not valid");
|
||||
}
|
||||
}
|
||||
const auto version =
|
||||
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0));
|
||||
}
|
||||
} else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
LOG_DEBUG(Loader, " RomFS: Update (PACKED) applied successfully");
|
||||
LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
|
||||
romfs = new_nca->GetRomFS();
|
||||
} else {
|
||||
LOG_WARNING(Loader, " RomFS: Update (PACKED) NCA is not valid");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,86 +489,36 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
std::vector<Patch> out;
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
auto variant_tids =
|
||||
EnumerateUpdateVariants(content_provider, title_id, ContentRecordType::Program);
|
||||
{
|
||||
auto data_tids =
|
||||
EnumerateUpdateVariants(content_provider, title_id, ContentRecordType::Data);
|
||||
variant_tids.insert(variant_tids.end(), data_tids.begin(), data_tids.end());
|
||||
auto control_tids =
|
||||
EnumerateUpdateVariants(content_provider, title_id, ContentRecordType::Control);
|
||||
variant_tids.insert(variant_tids.end(), control_tids.begin(), control_tids.end());
|
||||
std::sort(variant_tids.begin(), variant_tids.end());
|
||||
variant_tids.erase(std::unique(variant_tids.begin(), variant_tids.end()),
|
||||
variant_tids.end());
|
||||
}
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
PatchManager update{update_tid, fs_controller, content_provider};
|
||||
const auto metadata = update.GetControlMetadata();
|
||||
const auto& nacp = metadata.first;
|
||||
|
||||
if (!variant_tids.empty()) {
|
||||
const auto update_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
const auto update_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
Patch update_patch = {.enabled = !update_disabled,
|
||||
.name = "Update",
|
||||
.version = "",
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id};
|
||||
|
||||
out.push_back({.enabled = !update_disabled,
|
||||
.name = "Update",
|
||||
.version = "",
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id});
|
||||
|
||||
std::optional<u64> selected_variant_tid;
|
||||
if (!update_disabled) {
|
||||
const bool has_pref = HasVariantPreference(Settings::values.disabled_addons[title_id]);
|
||||
if (has_pref) {
|
||||
selected_variant_tid = ChooseUpdateVariant(
|
||||
content_provider, title_id, ContentRecordType::Program, fs_controller);
|
||||
if (nacp != nullptr) {
|
||||
update_patch.version = nacp->GetVersionString();
|
||||
out.push_back(update_patch);
|
||||
} else {
|
||||
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
const auto meta_ver = content_provider.GetEntryVersion(update_tid);
|
||||
if (meta_ver.value_or(0) == 0) {
|
||||
out.push_back(update_patch);
|
||||
} else {
|
||||
selected_variant_tid = GetUpdateTitleID(title_id);
|
||||
update_patch.version = FormatTitleVersion(*meta_ver);
|
||||
out.push_back(update_patch);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, u64>> variant_labels;
|
||||
variant_labels.reserve(variant_tids.size());
|
||||
|
||||
for (const auto tid : variant_tids) {
|
||||
variant_labels.emplace_back(GetUpdateVersionLabel(tid, fs_controller, content_provider),
|
||||
tid);
|
||||
}
|
||||
|
||||
std::sort(variant_labels.begin(), variant_labels.end(),
|
||||
[this](auto const& a, auto const& b) {
|
||||
const auto va = content_provider.GetEntryVersion(a.second).value_or(0);
|
||||
const auto vb = content_provider.GetEntryVersion(b.second).value_or(0);
|
||||
|
||||
if (va != vb)
|
||||
return va > vb;
|
||||
|
||||
const auto ca = ParseVersionComponents(a.first);
|
||||
const auto cb = ParseVersionComponents(b.first);
|
||||
|
||||
if (ca != cb)
|
||||
return ca > cb;
|
||||
|
||||
return a.first > b.first;
|
||||
});
|
||||
|
||||
std::set<std::string> seen_versions;
|
||||
for (const auto& [label, tid] : variant_labels) {
|
||||
std::string version = label;
|
||||
if (!version.empty() && (version.front() == 'v' || version.front() == 'V')) {
|
||||
version.erase(version.begin());
|
||||
}
|
||||
if (seen_versions.find(version) != seen_versions.end()) {
|
||||
continue;
|
||||
}
|
||||
const bool is_selected =
|
||||
selected_variant_tid.has_value() && tid == *selected_variant_tid;
|
||||
const bool variant_disabled = update_disabled || !is_selected;
|
||||
out.push_back({.enabled = !variant_disabled,
|
||||
.name = "Update",
|
||||
.version = version,
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = tid});
|
||||
seen_versions.insert(version);
|
||||
} else if (update_raw != nullptr) {
|
||||
update_patch.version = "PACKED";
|
||||
out.push_back(update_patch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -211,7 +208,6 @@ enum class ContentProviderUnionSlot {
|
||||
UserNAND, ///< User NAND
|
||||
SDMC, ///< SD Card
|
||||
FrontendManual, ///< Frontend-defined game list or similar
|
||||
External ///< External Updates/DLCs (not installed to NAND)
|
||||
};
|
||||
|
||||
// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
|
||||
@@ -203,7 +203,7 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri
|
||||
FileReference& reference) {
|
||||
std::unique_lock lk{list_lock};
|
||||
|
||||
// Temporarily remove from list (regardless of the current list).
|
||||
// Temporarily remove from list.
|
||||
this->RemoveReferenceFromListLocked(reference);
|
||||
|
||||
// Restore file if needed.
|
||||
@@ -211,8 +211,7 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri
|
||||
this->EvictSingleReferenceLocked();
|
||||
|
||||
reference.file =
|
||||
FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile,
|
||||
FS::FileShareFlag::ShareReadWrite);
|
||||
FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
|
||||
if (reference.file) {
|
||||
num_open_files++;
|
||||
}
|
||||
@@ -227,7 +226,7 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri
|
||||
void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) {
|
||||
std::scoped_lock lk{list_lock};
|
||||
|
||||
// Remove from list if present.
|
||||
// Remove from list.
|
||||
this->RemoveReferenceFromListLocked(*reference);
|
||||
|
||||
// Close the file.
|
||||
@@ -237,19 +236,6 @@ void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference
|
||||
}
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::CloseReference(FileReference& reference) {
|
||||
std::scoped_lock lk{list_lock};
|
||||
if (!reference.file) {
|
||||
return;
|
||||
}
|
||||
this->RemoveReferenceFromListLocked(reference);
|
||||
reference.file.reset();
|
||||
if (num_open_files > 0) {
|
||||
num_open_files--;
|
||||
}
|
||||
this->InsertReferenceIntoListLocked(reference);
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::EvictSingleReferenceLocked() {
|
||||
if (num_open_files < MaxOpenFiles || open_references.empty()) {
|
||||
return;
|
||||
@@ -270,18 +256,6 @@ void RealVfsFilesystem::EvictSingleReferenceLocked() {
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::InsertReferenceIntoListLocked(FileReference& reference) {
|
||||
// Ensure the node is not already linked to any list before inserting.
|
||||
if (reference.IsLinked()) {
|
||||
// Unlink from the list it currently belongs to.
|
||||
if (reference.file) {
|
||||
open_references.erase(open_references.iterator_to(reference));
|
||||
}
|
||||
|
||||
if (reference.IsLinked()) {
|
||||
closed_references.erase(closed_references.iterator_to(reference));
|
||||
}
|
||||
}
|
||||
|
||||
if (reference.file) {
|
||||
open_references.push_front(reference);
|
||||
} else {
|
||||
@@ -290,17 +264,9 @@ void RealVfsFilesystem::InsertReferenceIntoListLocked(FileReference& reference)
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference) {
|
||||
// Unlink from whichever list the node currently belongs to, if any.
|
||||
if (!reference.IsLinked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Erase from the correct list to avoid cross-list corruption.
|
||||
if (reference.file) {
|
||||
open_references.erase(open_references.iterator_to(reference));
|
||||
}
|
||||
|
||||
if(reference.IsLinked()) {
|
||||
} else {
|
||||
closed_references.erase(closed_references.iterator_to(reference));
|
||||
}
|
||||
}
|
||||
@@ -330,19 +296,13 @@ std::size_t RealVfsFile::GetSize() const {
|
||||
return *size;
|
||||
}
|
||||
auto lk = base.RefreshReference(path, perms, *reference);
|
||||
const auto result = reference->file ? reference->file->GetSize() : 0;
|
||||
lk.unlock();
|
||||
base.CloseReference(*reference);
|
||||
return result;
|
||||
return reference->file ? reference->file->GetSize() : 0;
|
||||
}
|
||||
|
||||
bool RealVfsFile::Resize(std::size_t new_size) {
|
||||
size.reset();
|
||||
auto lk = base.RefreshReference(path, perms, *reference);
|
||||
const bool ok = reference->file ? reference->file->SetSize(new_size) : false;
|
||||
lk.unlock();
|
||||
base.CloseReference(*reference);
|
||||
return ok;
|
||||
return reference->file ? reference->file->SetSize(new_size) : false;
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFile::GetContainingDirectory() const {
|
||||
@@ -358,37 +318,20 @@ bool RealVfsFile::IsReadable() const {
|
||||
}
|
||||
|
||||
std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
|
||||
if (length != 0 && data == nullptr) {
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"RealVfsFile::Read called with null buffer (len={}, off={}, path={})",
|
||||
length, offset, path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto lk = base.RefreshReference(path, perms, *reference);
|
||||
if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
|
||||
lk.unlock();
|
||||
base.CloseReference(*reference);
|
||||
return 0;
|
||||
}
|
||||
const auto read = reference->file->ReadSpan(std::span{data, length});
|
||||
lk.unlock();
|
||||
base.CloseReference(*reference);
|
||||
return read;
|
||||
return reference->file->ReadSpan(std::span{data, length});
|
||||
}
|
||||
|
||||
std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
|
||||
size.reset();
|
||||
auto lk = base.RefreshReference(path, perms, *reference);
|
||||
if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
|
||||
lk.unlock();
|
||||
base.CloseReference(*reference);
|
||||
return 0;
|
||||
}
|
||||
const auto written = reference->file->WriteSpan(std::span{data, length});
|
||||
lk.unlock();
|
||||
base.CloseReference(*reference);
|
||||
return written;
|
||||
return reference->file->WriteSpan(std::span{data, length});
|
||||
}
|
||||
|
||||
bool RealVfsFile::Rename(std::string_view name) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -61,7 +58,6 @@ private:
|
||||
std::unique_lock<std::mutex> RefreshReference(const std::string& path, OpenMode perms,
|
||||
FileReference& reference);
|
||||
void DropReference(std::unique_ptr<FileReference>&& reference);
|
||||
void CloseReference(FileReference& reference);
|
||||
|
||||
private:
|
||||
friend class RealVfsDirectory;
|
||||
|
||||
@@ -28,9 +28,6 @@ namespace Kernel {
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO: Remove this workaround when proper ASLR is implemented for all address spaces.
|
||||
constexpr u64 CodeStartOffset = 0x500000UL;
|
||||
|
||||
Result TerminateChildren(KernelCore& kernel, KProcess* process,
|
||||
const KThread* thread_to_not_terminate) {
|
||||
// Request that all children threads terminate.
|
||||
@@ -1157,7 +1154,7 @@ KProcess::KProcess(KernelCore& kernel)
|
||||
KProcess::~KProcess() = default;
|
||||
|
||||
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
|
||||
KProcessAddress aslr_space_start, bool is_hbl) {
|
||||
KProcessAddress aslr_space_start, size_t aslr_space_offset, bool is_hbl) {
|
||||
// Create a resource limit for the process.
|
||||
const auto pool = static_cast<KMemoryManager::Pool>(metadata.GetPoolPartition());
|
||||
const auto physical_memory_size = m_kernel.MemoryManager().GetSize(pool);
|
||||
@@ -1187,25 +1184,24 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
|
||||
// Set the address space type and code address.
|
||||
switch (metadata.GetAddressSpaceType()) {
|
||||
case FileSys::ProgramAddressSpaceType::Is39Bit:
|
||||
flag |= Svc::CreateProcessFlag::AddressSpace64Bit;
|
||||
|
||||
// For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
|
||||
// However, some (buggy) programs/libraries like skyline incorrectly depend on the
|
||||
// existence of ASLR pages before the entry point, so we will adjust the load address
|
||||
// to point to about 2GiB into the ASLR region.
|
||||
code_address = 0x8000'0000;
|
||||
flag |= Svc::CreateProcessFlag::AddressSpace64Bit;
|
||||
code_address = 0x8000'0000 + aslr_space_offset;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is36Bit:
|
||||
flag |= Svc::CreateProcessFlag::AddressSpace64BitDeprecated;
|
||||
code_address = 0x800'0000;
|
||||
code_address = 0x800'0000 + aslr_space_offset;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is32Bit:
|
||||
flag |= Svc::CreateProcessFlag::AddressSpace32Bit;
|
||||
code_address = 0x20'0000 + CodeStartOffset;
|
||||
code_address = 0x20'0000 + aslr_space_offset;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
|
||||
flag |= Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias;
|
||||
code_address = 0x20'0000 + CodeStartOffset;
|
||||
code_address = 0x20'0000 + aslr_space_offset;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -511,7 +511,7 @@ public:
|
||||
|
||||
public:
|
||||
Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
|
||||
KProcessAddress aslr_space_start, bool is_hbl);
|
||||
KProcessAddress aslr_space_start, size_t aslr_space_offset, bool is_hbl);
|
||||
|
||||
void LoadModule(CodeSet code_set, KProcessAddress base_addr);
|
||||
|
||||
|
||||
@@ -103,12 +103,26 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
|
||||
const bool data_abort = True(hr & Core::HaltReason::DataAbort);
|
||||
const bool interrupt = True(hr & Core::HaltReason::BreakLoop);
|
||||
|
||||
bool may_abort = true; // Ignore aborting virtual functions (for debugging)
|
||||
if (prefetch_abort && ::Settings::values.vtable_bouncing) {
|
||||
auto& lock = m_kernel.GlobalSchedulerContext().SchedulerLock();
|
||||
lock.Lock();
|
||||
Kernel::Svc::ThreadContext ctx;
|
||||
interface->GetContext(ctx);
|
||||
LOG_WARNING(Core_ARM, "vtable bouncing {:016X}", ctx.lr);
|
||||
ctx.pc = ctx.lr;
|
||||
ctx.r[0] = 0;
|
||||
interface->SetContext(ctx);
|
||||
lock.Unlock();
|
||||
may_abort = false;
|
||||
}
|
||||
|
||||
// Since scheduling may occur here, we cannot use any cached
|
||||
// state after returning from calls we make.
|
||||
|
||||
// Notify the debugger and go to sleep if a breakpoint was hit,
|
||||
// or if the thread is unable to continue for any reason.
|
||||
if (breakpoint || prefetch_abort) {
|
||||
if (breakpoint || (prefetch_abort && may_abort)) {
|
||||
if (breakpoint) {
|
||||
interface->RewindBreakpointInstruction();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -30,8 +33,8 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr<Ap
|
||||
{8, D<&ICommonStateGetter::GetBootMode>, "GetBootMode"},
|
||||
{9, D<&ICommonStateGetter::GetCurrentFocusState>, "GetCurrentFocusState"},
|
||||
{10, D<&ICommonStateGetter::RequestToAcquireSleepLock>, "RequestToAcquireSleepLock"},
|
||||
{11, nullptr, "ReleaseSleepLock"},
|
||||
{12, nullptr, "ReleaseSleepLockTransiently"},
|
||||
{11, D<&ICommonStateGetter::ReleaseSleepLock>, "ReleaseSleepLock"},
|
||||
{12, D<&ICommonStateGetter::ReleaseSleepLockTransiently>, "ReleaseSleepLockTransiently"},
|
||||
{13, D<&ICommonStateGetter::GetAcquiredSleepLockEvent>, "GetAcquiredSleepLockEvent"},
|
||||
{14, nullptr, "GetWakeupCount"},
|
||||
{20, nullptr, "PushToGeneralChannel"},
|
||||
@@ -112,6 +115,20 @@ Result ICommonStateGetter::RequestToAcquireSleepLock() {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICommonStateGetter::ReleaseSleepLock() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
m_applet->sleep_lock_event.Clear();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICommonStateGetter::ReleaseSleepLockTransiently() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
m_applet->sleep_lock_event.Clear();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICommonStateGetter::GetAcquiredSleepLockEvent(
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_WARNING(Service_AM, "called");
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -29,6 +32,8 @@ private:
|
||||
Result ReceiveMessage(Out<AppletMessage> out_applet_message);
|
||||
Result GetCurrentFocusState(Out<FocusState> out_focus_state);
|
||||
Result RequestToAcquireSleepLock();
|
||||
Result ReleaseSleepLock();
|
||||
Result ReleaseSleepLockTransiently();
|
||||
Result GetAcquiredSleepLockEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result GetReaderLockAccessorEx(Out<SharedPointer<ILockAccessor>> out_lock_accessor,
|
||||
u32 button_type);
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <utility>
|
||||
#include <filesystem>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/fs/fs.h"
|
||||
@@ -16,7 +12,6 @@
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/external_content_index.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
@@ -718,36 +713,6 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
||||
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
|
||||
sdmc_factory->GetSDMCContents());
|
||||
}
|
||||
|
||||
if (external_provider == nullptr) {
|
||||
external_provider = std::make_unique<FileSys::ManualContentProvider>();
|
||||
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::External,
|
||||
external_provider.get());
|
||||
}
|
||||
|
||||
RebuildExternalContentIndex();
|
||||
}
|
||||
|
||||
void FileSystemController::RebuildExternalContentIndex() {
|
||||
if (external_provider == nullptr) {
|
||||
LOG_WARNING(Service_FS, "External provider not initialized, skipping re-index.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Settings::values.external_dirs.empty()) {
|
||||
FileSys::ExternalContentPaths paths{};
|
||||
for (const auto& dir : Settings::values.external_dirs) {
|
||||
if (dir.empty())
|
||||
continue;
|
||||
paths.update_dirs.push_back(dir);
|
||||
paths.dlc_dirs.push_back(dir);
|
||||
}
|
||||
FileSys::ExternalContentIndexer indexer{system.GetFilesystem(), *this->external_provider,
|
||||
std::move(paths)};
|
||||
indexer.Rebuild();
|
||||
} else {
|
||||
external_provider->ClearAllEntries();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystemController::Reset() {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -8,7 +5,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <core/file_sys/registered_cache.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/fs_directory.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
@@ -125,8 +121,6 @@ public:
|
||||
// above is called.
|
||||
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
|
||||
|
||||
void RebuildExternalContentIndex();
|
||||
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
@@ -147,7 +141,6 @@ private:
|
||||
std::unique_ptr<FileSys::XCI> gamecard;
|
||||
std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;
|
||||
std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> external_provider;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
#include <openssl/cert.h>
|
||||
#endif
|
||||
|
||||
using namespace Common::FS;
|
||||
|
||||
namespace Service::SSL {
|
||||
@@ -41,11 +45,85 @@ void OneTimeInit();
|
||||
void OneTimeInitLogFile();
|
||||
bool OneTimeInitBIO();
|
||||
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
// This is ported from httplib
|
||||
struct scope_exit {
|
||||
explicit scope_exit(std::function<void(void)> &&f)
|
||||
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
||||
|
||||
scope_exit(scope_exit &&rhs) noexcept
|
||||
: exit_function(std::move(rhs.exit_function)),
|
||||
execute_on_destruction{rhs.execute_on_destruction} {
|
||||
rhs.release();
|
||||
}
|
||||
|
||||
~scope_exit() {
|
||||
if (execute_on_destruction) { this->exit_function(); }
|
||||
}
|
||||
|
||||
void release() { this->execute_on_destruction = false; }
|
||||
|
||||
private:
|
||||
scope_exit(const scope_exit &) = delete;
|
||||
void operator=(const scope_exit &) = delete;
|
||||
scope_exit &operator=(scope_exit &&) = delete;
|
||||
|
||||
std::function<void(void)> exit_function;
|
||||
bool execute_on_destruction;
|
||||
};
|
||||
|
||||
inline X509_STORE *CreateCaCertStore(const char *ca_cert,
|
||||
std::size_t size) {
|
||||
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
|
||||
auto se = scope_exit([&] { BIO_free_all(mem); });
|
||||
if (!mem) { return nullptr; }
|
||||
|
||||
auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
|
||||
if (!inf) { return nullptr; }
|
||||
|
||||
auto cts = X509_STORE_new();
|
||||
if (cts) {
|
||||
for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
|
||||
auto itmp = sk_X509_INFO_value(inf, i);
|
||||
if (!itmp) { continue; }
|
||||
|
||||
if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
|
||||
if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
|
||||
}
|
||||
}
|
||||
|
||||
sk_X509_INFO_pop_free(inf, X509_INFO_free);
|
||||
return cts;
|
||||
}
|
||||
|
||||
inline void SetCaCertStore(SSL_CTX *ctx, X509_STORE *ca_cert_store) {
|
||||
if (ca_cert_store) {
|
||||
if (ctx) {
|
||||
if (SSL_CTX_get_cert_store(ctx) != ca_cert_store) {
|
||||
// Free memory allocated for old cert and use new store `ca_cert_store`
|
||||
SSL_CTX_set_cert_store(ctx, ca_cert_store);
|
||||
}
|
||||
} else {
|
||||
X509_STORE_free(ca_cert_store);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
|
||||
{
|
||||
SetCaCertStore(ctx, CreateCaCertStore(ca_cert, size));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend {
|
||||
public:
|
||||
Result Init() {
|
||||
// on bundled OpenSSL, load ca cert store
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
LoadCaCertStore(ssl_ctx, kCert, sizeof(kCert));
|
||||
#endif
|
||||
std::call_once(one_time_init_flag, OneTimeInit);
|
||||
|
||||
if (!one_time_init_success) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -223,8 +226,13 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
||||
// Add patch size to the total module size
|
||||
code_size += patch_ctx.GetTotalPatchSize();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue()
|
||||
: std::rand()) * 0x734287f27) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process.LoadFromMetadata(metadata, code_size, fastmem_base, is_hbl).IsError()) {
|
||||
if (process.LoadFromMetadata(metadata, code_size, fastmem_base, aslr_offset, is_hbl).IsError()) {
|
||||
return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include "common/settings.h"
|
||||
#include "core/file_sys/kernel_executable.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
@@ -76,11 +77,10 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
|
||||
Kernel::CodeSet codeset;
|
||||
Kernel::PhysicalMemory program_image;
|
||||
|
||||
const auto load_segment = [&program_image](Kernel::CodeSet::Segment& segment,
|
||||
const std::vector<u8>& data, u32 offset) {
|
||||
const auto load_segment = [&program_image](Kernel::CodeSet::Segment& segment, const std::vector<u8>& data, u32 offset) {
|
||||
segment.addr = offset;
|
||||
segment.offset = offset;
|
||||
segment.size = PageAlignSize(static_cast<u32>(data.size()));
|
||||
segment.size = PageAlignSize(u32(data.size()));
|
||||
program_image.resize(offset + data.size());
|
||||
std::memcpy(program_image.data() + offset, data.data(), data.size());
|
||||
};
|
||||
@@ -92,10 +92,14 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
|
||||
program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
|
||||
codeset.DataSegment().size += kip->GetBSSSize();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue()
|
||||
: std::rand()) * 0x734287f27) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process
|
||||
.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), 0,
|
||||
false)
|
||||
.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), 0, aslr_offset, false)
|
||||
.IsError()) {
|
||||
return {ResultStatus::ErrorNotInitialized, {}};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -237,10 +240,14 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
return 0;
|
||||
}();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue()
|
||||
: std::rand()) * 0x734287f27) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process
|
||||
.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), image_size, fastmem_base,
|
||||
false)
|
||||
.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), image_size, fastmem_base, aslr_offset, false)
|
||||
.IsError()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ endif()
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules")
|
||||
|
||||
# Arch detection
|
||||
include(DetectArchitecture)
|
||||
if (NOT DEFINED ARCHITECTURE)
|
||||
message(FATAL_ERROR "Unsupported architecture encountered. Ending CMake generation.")
|
||||
endif()
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
include(CheckSymbolExists)
|
||||
|
||||
if (CMAKE_OSX_ARCHITECTURES)
|
||||
set(DYNARMIC_MULTIARCH_BUILD 1)
|
||||
set(ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}")
|
||||
return()
|
||||
endif()
|
||||
|
||||
function(detect_architecture symbol arch)
|
||||
if (NOT DEFINED ARCHITECTURE)
|
||||
set(CMAKE_REQUIRED_QUIET YES)
|
||||
check_symbol_exists("${symbol}" "" DETECT_ARCHITECTURE_${arch})
|
||||
unset(CMAKE_REQUIRED_QUIET)
|
||||
|
||||
if (DETECT_ARCHITECTURE_${arch})
|
||||
set(ARCHITECTURE "${arch}" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
unset(DETECT_ARCHITECTURE_${arch} CACHE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
detect_architecture("__ARM64__" arm64)
|
||||
detect_architecture("__aarch64__" arm64)
|
||||
detect_architecture("_M_ARM64" arm64)
|
||||
|
||||
detect_architecture("__arm__" arm)
|
||||
detect_architecture("__TARGET_ARCH_ARM" arm)
|
||||
detect_architecture("_M_ARM" arm)
|
||||
|
||||
detect_architecture("__x86_64" x86_64)
|
||||
detect_architecture("__x86_64__" x86_64)
|
||||
detect_architecture("__amd64" x86_64)
|
||||
detect_architecture("_M_X64" x86_64)
|
||||
|
||||
detect_architecture("__i386" x86)
|
||||
detect_architecture("__i386__" x86)
|
||||
detect_architecture("_M_IX86" x86)
|
||||
|
||||
detect_architecture("__ia64" ia64)
|
||||
detect_architecture("__ia64__" ia64)
|
||||
detect_architecture("_M_IA64" ia64)
|
||||
|
||||
detect_architecture("__mips" mips)
|
||||
detect_architecture("__mips__" mips)
|
||||
detect_architecture("_M_MRX000" mips)
|
||||
|
||||
detect_architecture("__ppc64__" ppc64)
|
||||
detect_architecture("__powerpc64__" ppc64)
|
||||
|
||||
detect_architecture("__ppc__" ppc)
|
||||
detect_architecture("__ppc" ppc)
|
||||
detect_architecture("__powerpc__" ppc)
|
||||
detect_architecture("_ARCH_COM" ppc)
|
||||
detect_architecture("_ARCH_PWR" ppc)
|
||||
detect_architecture("_ARCH_PPC" ppc)
|
||||
detect_architecture("_M_MPPC" ppc)
|
||||
detect_architecture("_M_PPC" ppc)
|
||||
|
||||
detect_architecture("__riscv" riscv)
|
||||
|
||||
detect_architecture("__EMSCRIPTEN__" wasm)
|
||||
@@ -44,13 +44,13 @@ Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode opcode, s
|
||||
// hugely benefit from the coherency of faster allocations...
|
||||
IR::Inst* inst;
|
||||
if (inlined_inst.size() < inlined_inst.max_size()) {
|
||||
inst = &inlined_inst[inlined_inst.size()];
|
||||
inlined_inst.emplace_back(opcode);
|
||||
inst = &inlined_inst[inlined_inst.size() - 1];
|
||||
} else {
|
||||
if (pooled_inst.empty() || pooled_inst.back().size() == pooled_inst.back().max_size())
|
||||
pooled_inst.emplace_back();
|
||||
inst = &pooled_inst.back()[pooled_inst.back().size()];
|
||||
pooled_inst.back().emplace_back(opcode);
|
||||
inst = &pooled_inst.back()[pooled_inst.back().size() - 1];
|
||||
}
|
||||
DEBUG_ASSERT(args.size() == inst->NumArgs());
|
||||
std::for_each(args.begin(), args.end(), [&inst, index = size_t(0)](const auto& arg) mutable {
|
||||
|
||||
@@ -654,3 +654,11 @@ constexpr bool MayGetNZCVFromOp(const Opcode op) noexcept {
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::IR
|
||||
|
||||
template<>
|
||||
struct fmt::formatter<Dynarmic::IR::Opcode> : fmt::formatter<std::string_view> {
|
||||
template<typename FormatContext>
|
||||
auto format(Dynarmic::IR::Opcode op, FormatContext& ctx) const {
|
||||
return formatter<std::string_view>::format(GetNameOf(op), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,7 +29,6 @@ target_link_libraries(dynarmic_tests PRIVATE merry::oaknut)
|
||||
|
||||
if (DYNARMIC_TESTS_USE_UNICORN)
|
||||
target_link_libraries(dynarmic_tests PRIVATE Unicorn::Unicorn)
|
||||
|
||||
target_sources(dynarmic_tests PRIVATE
|
||||
fuzz_util.cpp
|
||||
fuzz_util.h
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -286,19 +283,6 @@ void Config::ReadDataStorageValues() {
|
||||
|
||||
ReadCategory(Settings::Category::DataStorage);
|
||||
|
||||
Settings::values.external_dirs.clear();
|
||||
const int num_dirs = BeginArray(std::string("external_dirs"));
|
||||
Settings::values.external_dirs.reserve(num_dirs);
|
||||
for (int i = 0; i < num_dirs; ++i) {
|
||||
SetArrayIndex(i);
|
||||
std::string dir = ReadStringSetting(std::string("path"), std::string(""));
|
||||
if (!dir.empty()) {
|
||||
Settings::values.external_dirs.emplace_back(std::move(dir));
|
||||
}
|
||||
}
|
||||
|
||||
EndArray();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
@@ -607,14 +591,6 @@ void Config::SaveDataStorageValues() {
|
||||
|
||||
WriteCategory(Settings::Category::DataStorage);
|
||||
|
||||
BeginArray(std::string("external_dirs"));
|
||||
for (std::size_t i = 0; i < Settings::values.external_dirs.size(); ++i) {
|
||||
SetArrayIndex(static_cast<int>(i));
|
||||
WriteStringSetting(std::string("path"), Settings::values.external_dirs[i],
|
||||
std::make_optional(std::string("")));
|
||||
}
|
||||
EndArray();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
"cause deadlocks. A range of 77-21000 is recommended."));
|
||||
INSERT(Settings, cpu_backend, tr("Backend:"), QString());
|
||||
|
||||
INSERT(Settings, vtable_bouncing,
|
||||
tr("Virtual Table Bouncing"),
|
||||
tr("Bounces (by emulating a 0-valued return) any functions that triggers a prefetch abort"));
|
||||
|
||||
// Cpu Debug
|
||||
|
||||
// Cpu Unsafe
|
||||
@@ -325,6 +329,13 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
tr("Extended Dynamic State"),
|
||||
tr("Controls the number of features that can be used in Extended Dynamic State.\nHigher numbers allow for more features and can increase performance, but may cause issues.\nThe default value is per-system."));
|
||||
|
||||
INSERT(Settings,
|
||||
force_unsupported_extensions,
|
||||
tr("Force Unsupported Extensions"),
|
||||
tr("Bypasses all driver workarounds and safety checks.\n"
|
||||
"May cause crashes, graphical glitches, or instability.\n"
|
||||
"Only enable for testing purposes."));
|
||||
|
||||
INSERT(Settings,
|
||||
provoking_vertex,
|
||||
tr("Provoking Vertex"),
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -313,6 +316,7 @@ void EmitUDiv32(EmitContext& ctx, IR::Inst& inst, ScalarU32 a, ScalarU32 b);
|
||||
void EmitINeg32(EmitContext& ctx, IR::Inst& inst, ScalarS32 value);
|
||||
void EmitINeg64(EmitContext& ctx, IR::Inst& inst, Register value);
|
||||
void EmitIAbs32(EmitContext& ctx, IR::Inst& inst, ScalarS32 value);
|
||||
void EmitIAbs64(EmitContext& ctx, IR::Inst& inst, ScalarS32 value);
|
||||
void EmitShiftLeftLogical32(EmitContext& ctx, IR::Inst& inst, ScalarU32 base, ScalarU32 shift);
|
||||
void EmitShiftLeftLogical64(EmitContext& ctx, IR::Inst& inst, ScalarRegister base, ScalarU32 shift);
|
||||
void EmitShiftRightLogical32(EmitContext& ctx, IR::Inst& inst, ScalarU32 base, ScalarU32 shift);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -113,6 +116,10 @@ void EmitIAbs32(EmitContext& ctx, IR::Inst& inst, ScalarS32 value) {
|
||||
ctx.Add("ABS.S {},{};", inst, value);
|
||||
}
|
||||
|
||||
void EmitIAbs64(EmitContext& ctx, IR::Inst& inst, ScalarS32 value) {
|
||||
ctx.Add("ABS.S64 {},{};", inst, value);
|
||||
}
|
||||
|
||||
void EmitShiftLeftLogical32(EmitContext& ctx, IR::Inst& inst, ScalarU32 base, ScalarU32 shift) {
|
||||
ctx.Add("SHL.U {}.x,{},{};", inst, base, shift);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -373,6 +376,7 @@ void EmitUDiv32(EmitContext& ctx, IR::Inst& inst, std::string_view a, std::strin
|
||||
void EmitINeg32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
|
||||
void EmitINeg64(EmitContext& ctx, IR::Inst& inst, std::string_view value);
|
||||
void EmitIAbs32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
|
||||
void EmitIAbs64(EmitContext& ctx, IR::Inst& inst, std::string_view value);
|
||||
void EmitShiftLeftLogical32(EmitContext& ctx, IR::Inst& inst, std::string_view base,
|
||||
std::string_view shift);
|
||||
void EmitShiftLeftLogical64(EmitContext& ctx, IR::Inst& inst, std::string_view base,
|
||||
|
||||
@@ -98,6 +98,11 @@ void EmitIAbs32(EmitContext& ctx, IR::Inst& inst, std::string_view value) {
|
||||
ctx.AddU32("{}=abs(int({}));", inst, value);
|
||||
}
|
||||
|
||||
void EmitIAbs64(EmitContext& ctx, IR::Inst& inst, std::string_view value) {
|
||||
// TODO: Uhm, are you sure? This may crash on some drivers!
|
||||
ctx.AddU32("{}=abs(int64_t({}));", inst, value);
|
||||
}
|
||||
|
||||
void EmitShiftLeftLogical32(EmitContext& ctx, IR::Inst& inst, std::string_view base,
|
||||
std::string_view shift) {
|
||||
ctx.AddU32("{}={}<<{};", inst, base, shift);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -293,6 +296,7 @@ Id EmitUDiv32(EmitContext& ctx, Id a, Id b);
|
||||
Id EmitINeg32(EmitContext& ctx, Id value);
|
||||
Id EmitINeg64(EmitContext& ctx, Id value);
|
||||
Id EmitIAbs32(EmitContext& ctx, Id value);
|
||||
Id EmitIAbs64(EmitContext& ctx, Id value);
|
||||
Id EmitShiftLeftLogical32(EmitContext& ctx, Id base, Id shift);
|
||||
Id EmitShiftLeftLogical64(EmitContext& ctx, Id base, Id shift);
|
||||
Id EmitShiftRightLogical32(EmitContext& ctx, Id base, Id shift);
|
||||
|
||||
@@ -94,6 +94,10 @@ Id EmitIAbs32(EmitContext& ctx, Id value) {
|
||||
return ctx.OpSAbs(ctx.U32[1], value);
|
||||
}
|
||||
|
||||
Id EmitIAbs64(EmitContext& ctx, Id value) {
|
||||
return ctx.OpSAbs(ctx.U64, value);
|
||||
}
|
||||
|
||||
Id EmitShiftLeftLogical32(EmitContext& ctx, Id base, Id shift) {
|
||||
return ctx.OpShiftLeftLogical(ctx.U32[1], base, shift);
|
||||
}
|
||||
|
||||
@@ -1190,8 +1190,15 @@ U32U64 IREmitter::INeg(const U32U64& value) {
|
||||
}
|
||||
}
|
||||
|
||||
U32 IREmitter::IAbs(const U32& value) {
|
||||
return Inst<U32>(Opcode::IAbs32, value);
|
||||
U32U64 IREmitter::IAbs(const U32U64& value) {
|
||||
switch (value.Type()) {
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::IAbs32, value);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::IAbs64, value);
|
||||
default:
|
||||
ThrowInvalidType(value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
U32U64 IREmitter::ShiftLeftLogical(const U32U64& base, const U32& shift) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -218,7 +221,7 @@ public:
|
||||
[[nodiscard]] U32 IMul(const U32& a, const U32& b);
|
||||
[[nodiscard]] U32 IDiv(const U32& a, const U32& b, bool is_signed = false);
|
||||
[[nodiscard]] U32U64 INeg(const U32U64& value);
|
||||
[[nodiscard]] U32 IAbs(const U32& value);
|
||||
[[nodiscard]] U32U64 IAbs(const U32U64& value);
|
||||
[[nodiscard]] U32U64 ShiftLeftLogical(const U32U64& base, const U32& shift);
|
||||
[[nodiscard]] U32U64 ShiftRightLogical(const U32U64& base, const U32& shift);
|
||||
[[nodiscard]] U32U64 ShiftRightArithmetic(const U32U64& base, const U32& shift);
|
||||
|
||||
@@ -296,6 +296,7 @@ OPCODE(UDiv32, U32, U32,
|
||||
OPCODE(INeg32, U32, U32, )
|
||||
OPCODE(INeg64, U64, U64, )
|
||||
OPCODE(IAbs32, U32, U32, )
|
||||
OPCODE(IAbs64, U64, U64, )
|
||||
OPCODE(ShiftLeftLogical32, U32, U32, U32, )
|
||||
OPCODE(ShiftLeftLogical64, U64, U64, U32, )
|
||||
OPCODE(ShiftRightLogical32, U32, U32, U32, )
|
||||
|
||||
@@ -70,32 +70,25 @@ void I2F(TranslatorVisitor& v, u64 insn, IR::U32U64 src) {
|
||||
int src_bitsize{};
|
||||
switch (i2f.int_format) {
|
||||
case IntFormat::U8:
|
||||
src = v.ir.BitFieldExtract(src, v.ir.Imm32(static_cast<u32>(i2f.selector) * 8),
|
||||
v.ir.Imm32(8), is_signed);
|
||||
if (i2f.abs != 0) {
|
||||
src = v.ir.BitFieldExtract(src, v.ir.Imm32(u32(i2f.selector) * 8), v.ir.Imm32(8), is_signed);
|
||||
if (i2f.abs != 0)
|
||||
src = SmallAbs(v, src, 8);
|
||||
}
|
||||
src_bitsize = 8;
|
||||
break;
|
||||
case IntFormat::U16:
|
||||
if (i2f.selector == 1 || i2f.selector == 3) {
|
||||
if (i2f.selector == 1 || i2f.selector == 3)
|
||||
throw NotImplementedException("Invalid U16 selector {}", i2f.selector.Value());
|
||||
}
|
||||
src = v.ir.BitFieldExtract(src, v.ir.Imm32(static_cast<u32>(i2f.selector) * 8),
|
||||
v.ir.Imm32(16), is_signed);
|
||||
if (i2f.abs != 0) {
|
||||
src = v.ir.BitFieldExtract(src, v.ir.Imm32(u32(i2f.selector) * 8), v.ir.Imm32(16), is_signed);
|
||||
if (i2f.abs != 0)
|
||||
src = SmallAbs(v, src, 16);
|
||||
}
|
||||
src_bitsize = 16;
|
||||
break;
|
||||
case IntFormat::U32:
|
||||
case IntFormat::U64:
|
||||
if (i2f.selector != 0) {
|
||||
if (i2f.selector != 0)
|
||||
throw NotImplementedException("Unexpected selector {}", i2f.selector.Value());
|
||||
}
|
||||
if (i2f.abs != 0 && is_signed) {
|
||||
if (i2f.abs != 0 && is_signed)
|
||||
src = v.ir.IAbs(src);
|
||||
}
|
||||
src_bitsize = i2f.int_format == IntFormat::U64 ? 64 : 32;
|
||||
break;
|
||||
}
|
||||
@@ -106,9 +99,7 @@ void I2F(TranslatorVisitor& v, u64 insn, IR::U32U64 src) {
|
||||
.rounding = CastFpRounding(i2f.fp_rounding),
|
||||
.fmz_mode = IR::FmzMode::DontCare,
|
||||
};
|
||||
auto value{v.ir.ConvertIToF(static_cast<size_t>(dst_bitsize),
|
||||
static_cast<size_t>(conversion_src_bitsize), is_signed, src,
|
||||
fp_control)};
|
||||
auto value{v.ir.ConvertIToF(size_t(dst_bitsize), size_t(conversion_src_bitsize), is_signed, src, fp_control)};
|
||||
if (i2f.neg != 0) {
|
||||
if (i2f.abs != 0 || !is_signed) {
|
||||
// We know the value is positive
|
||||
@@ -141,9 +132,8 @@ void I2F(TranslatorVisitor& v, u64 insn, IR::U32U64 src) {
|
||||
throw NotImplementedException("Unaligned destination {}", i2f.dest_reg.Value());
|
||||
}
|
||||
const IR::Value vector{v.ir.UnpackDouble2x32(value)};
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
v.X(i2f.dest_reg + i, IR::U32{v.ir.CompositeExtract(vector, static_cast<size_t>(i))});
|
||||
}
|
||||
for (int i = 0; i < 2; ++i)
|
||||
v.X(i2f.dest_reg + i, IR::U32{v.ir.CompositeExtract(vector, size_t(i))});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -945,7 +945,6 @@ bool AccelerateDMA::BufferToImage(const Tegra::DMA::ImageCopy& copy_info,
|
||||
|
||||
void RasterizerVulkan::UpdateDynamicStates() {
|
||||
auto& regs = maxwell3d->regs;
|
||||
|
||||
UpdateViewportsState(regs);
|
||||
UpdateScissorsState(regs);
|
||||
UpdateDepthBias(regs);
|
||||
@@ -953,19 +952,7 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||
UpdateDepthBounds(regs);
|
||||
UpdateStencilFaces(regs);
|
||||
UpdateLineWidth(regs);
|
||||
|
||||
const u8 dynamic_state = Settings::values.dyna_state.GetValue();
|
||||
|
||||
auto features = DynamicFeatures{
|
||||
.has_extended_dynamic_state = device.IsExtExtendedDynamicStateSupported() && dynamic_state > 0,
|
||||
.has_extended_dynamic_state_2 = device.IsExtExtendedDynamicState2Supported() && dynamic_state > 1,
|
||||
.has_extended_dynamic_state_2_extra = device.IsExtExtendedDynamicState2ExtrasSupported() && dynamic_state > 1,
|
||||
.has_extended_dynamic_state_3_blend = device.IsExtExtendedDynamicState3BlendingSupported() && dynamic_state > 2,
|
||||
.has_extended_dynamic_state_3_enables = device.IsExtExtendedDynamicState3EnablesSupported() && dynamic_state > 2,
|
||||
.has_dynamic_vertex_input = device.IsExtVertexInputDynamicStateSupported(),
|
||||
};
|
||||
|
||||
if (features.has_extended_dynamic_state) {
|
||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||
UpdateCullMode(regs);
|
||||
UpdateDepthCompareOp(regs);
|
||||
UpdateFrontFace(regs);
|
||||
@@ -976,14 +963,13 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||
UpdateDepthTestEnable(regs);
|
||||
UpdateDepthWriteEnable(regs);
|
||||
UpdateStencilTestEnable(regs);
|
||||
|
||||
if (features.has_extended_dynamic_state_2) {
|
||||
if (device.IsExtExtendedDynamicState2Supported()) {
|
||||
UpdatePrimitiveRestartEnable(regs);
|
||||
UpdateRasterizerDiscardEnable(regs);
|
||||
UpdateDepthBiasEnable(regs);
|
||||
}
|
||||
|
||||
if (features.has_extended_dynamic_state_3_enables) {
|
||||
if (device.IsExtExtendedDynamicState3Supported()) {
|
||||
using namespace Tegra::Engines;
|
||||
|
||||
if (device.GetDriverID() == VkDriverIdKHR::VK_DRIVER_ID_AMD_OPEN_SOURCE ||
|
||||
@@ -1010,20 +996,15 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||
UpdateDepthClampEnable(regs);
|
||||
}
|
||||
}
|
||||
if (features.has_extended_dynamic_state_2_extra) {
|
||||
if (device.IsExtExtendedDynamicState2ExtrasSupported()) {
|
||||
UpdateLogicOp(regs);
|
||||
}
|
||||
if (features.has_extended_dynamic_state_3_enables) {
|
||||
if (device.IsExtExtendedDynamicState3Supported()) {
|
||||
UpdateBlending(regs);
|
||||
UpdateLineStippleEnable(regs);
|
||||
UpdateConservativeRasterizationMode(regs);
|
||||
}
|
||||
}
|
||||
if (features.has_dynamic_vertex_input) {
|
||||
if (auto* gp = pipeline_cache.CurrentGraphicsPipeline();
|
||||
gp && gp->HasDynamicVertexInput()) {
|
||||
UpdateVertexInput(regs);
|
||||
}
|
||||
if (device.IsExtVertexInputDynamicStateSupported()) {
|
||||
UpdateVertexInput(regs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -494,17 +494,25 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
CollectPhysicalMemoryInfo();
|
||||
CollectToolingInfo();
|
||||
|
||||
if (is_qualcomm || is_turnip) {
|
||||
const bool force_extensions = Settings::values.force_unsupported_extensions.GetValue();
|
||||
|
||||
if (force_extensions) {
|
||||
LOG_WARNING(Render_Vulkan, "Force Unsupported Extensions is enabled - bypassing ALL driver workarounds and safety checks!");
|
||||
}
|
||||
|
||||
if ((is_qualcomm || is_turnip) && !force_extensions) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color");
|
||||
//RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color,
|
||||
//VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color,
|
||||
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (is_qualcomm) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Qualcomm drivers have a slow VK_KHR_push_descriptor implementation");
|
||||
//RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||
if (!force_extensions) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Qualcomm drivers have a slow VK_KHR_push_descriptor implementation");
|
||||
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Disabling shader float controls and 64-bit integer features on Qualcomm proprietary drivers");
|
||||
@@ -545,9 +553,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
|
||||
features.shader_float16_int8.shaderFloat16 = false;
|
||||
} else if (arch <= NvidiaArchitecture::Arch_Volta) {
|
||||
if (nv_major_version < 527) {
|
||||
if (nv_major_version < 527 && !force_extensions) {
|
||||
LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
|
||||
//RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (nv_major_version >= 510) {
|
||||
@@ -558,79 +566,67 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
if (extensions.extended_dynamic_state && is_radv) {
|
||||
// Mask driver version variant
|
||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||
if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) {
|
||||
if (version < VK_MAKE_API_VERSION(0, 21, 2, 0) && !force_extensions) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state");
|
||||
//RemoveExtensionFeature(extensions.extended_dynamic_state,
|
||||
//features.extended_dynamic_state,
|
||||
//VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state,
|
||||
features.extended_dynamic_state,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (extensions.extended_dynamic_state2 && is_radv) {
|
||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||
if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) {
|
||||
if (version < VK_MAKE_API_VERSION(0, 22, 3, 1) && !force_extensions) {
|
||||
LOG_WARNING(
|
||||
Render_Vulkan,
|
||||
"RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2");
|
||||
// RemoveExtensionFeature(extensions.extended_dynamic_state2,
|
||||
// features.extended_dynamic_state2,
|
||||
// VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state2,
|
||||
features.extended_dynamic_state2,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (extensions.extended_dynamic_state2 && is_qualcomm) {
|
||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||
if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
|
||||
version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
|
||||
version < VK_MAKE_API_VERSION(0, 0, 680, 0) && !force_extensions) {
|
||||
// Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2.
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2");
|
||||
//RemoveExtensionFeature(extensions.extended_dynamic_state2,
|
||||
//features.extended_dynamic_state2,
|
||||
//VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state2,
|
||||
features.extended_dynamic_state2,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (extensions.extended_dynamic_state3 && is_radv) {
|
||||
if (extensions.extended_dynamic_state3 && is_radv && !force_extensions) {
|
||||
LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation");
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = true;
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = true;
|
||||
dynamic_state3_blending = true;
|
||||
dynamic_state3_blending = false;
|
||||
|
||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||
if (version < VK_MAKE_API_VERSION(0, 23, 1, 0)) {
|
||||
if (version < VK_MAKE_API_VERSION(0, 23, 1, 0) && !force_extensions) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"RADV versions older than 23.1.0 have broken depth clamp dynamic state");
|
||||
features.extended_dynamic_state3.extendedDynamicState3DepthClampEnable = true;
|
||||
dynamic_state3_enables = true;
|
||||
dynamic_state3_enables = false;
|
||||
}
|
||||
}
|
||||
if (extensions.extended_dynamic_state3 && (is_amd_driver || driver_id == VK_DRIVER_ID_SAMSUNG_PROPRIETARY)) {
|
||||
if (extensions.extended_dynamic_state3 &&
|
||||
(is_amd_driver || driver_id == VK_DRIVER_ID_SAMSUNG_PROPRIETARY) && !force_extensions) {
|
||||
// AMD and Samsung drivers have broken extendedDynamicState3ColorBlendEquation
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"AMD and Samsung drivers have broken extendedDynamicState3ColorBlendEquation");
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = true;
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = true;
|
||||
dynamic_state3_blending = true;
|
||||
dynamic_state3_blending = false;
|
||||
}
|
||||
if (extensions.vertex_input_dynamic_state && is_radv) {
|
||||
// TODO(ameerj): Blacklist only offending driver versions
|
||||
// TODO(ameerj): Confirm if RDNA1 is affected
|
||||
const bool is_rdna2 =
|
||||
supported_extensions.contains(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
|
||||
if (is_rdna2) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware");
|
||||
// RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
|
||||
// features.vertex_input_dynamic_state,
|
||||
// VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (extensions.vertex_input_dynamic_state && is_qualcomm) {
|
||||
if (extensions.vertex_input_dynamic_state && is_qualcomm && !force_extensions) {
|
||||
// Qualcomm drivers do not properly support vertex_input_dynamic_state.
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Qualcomm drivers have broken VK_EXT_vertex_input_dynamic_state");
|
||||
//RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
|
||||
// features.vertex_input_dynamic_state,
|
||||
// VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
|
||||
features.vertex_input_dynamic_state,
|
||||
VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
sets_per_pool = 64;
|
||||
@@ -660,13 +656,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
}
|
||||
}
|
||||
|
||||
if (extensions.vertex_input_dynamic_state && is_intel_windows) {
|
||||
if (extensions.vertex_input_dynamic_state && is_intel_windows && !force_extensions) {
|
||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||
if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) {
|
||||
LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state");
|
||||
//RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
|
||||
//features.vertex_input_dynamic_state,
|
||||
//VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
|
||||
features.vertex_input_dynamic_state,
|
||||
VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) {
|
||||
@@ -688,19 +684,19 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
if (extensions.push_descriptor && is_intel_anv) {
|
||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||
if (version >= VK_MAKE_API_VERSION(0, 22, 3, 0) &&
|
||||
version < VK_MAKE_API_VERSION(0, 23, 2, 0)) {
|
||||
version < VK_MAKE_API_VERSION(0, 23, 2, 0) && !force_extensions) {
|
||||
// Disable VK_KHR_push_descriptor due to
|
||||
// mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
|
||||
//RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||
}
|
||||
} else if (extensions.push_descriptor && is_nvidia) {
|
||||
const auto arch = GetNvidiaArch();
|
||||
if (arch <= NvidiaArchitecture::Arch_Pascal) {
|
||||
if (arch <= NvidiaArchitecture::Arch_Pascal && !force_extensions) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Pascal and older architectures have broken VK_KHR_push_descriptor");
|
||||
//RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,13 +721,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (!extensions.extended_dynamic_state2 && extensions.extended_dynamic_state3) {
|
||||
if (!extensions.extended_dynamic_state2 && extensions.extended_dynamic_state3 && !force_extensions) {
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"Removing extendedDynamicState3 due to missing extendedDynamicState2");
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
dynamic_state3_blending = true;
|
||||
dynamic_state3_enables = true;
|
||||
dynamic_state3_blending = false;
|
||||
dynamic_state3_enables = false;
|
||||
}
|
||||
|
||||
// Mesa Intel drivers on UHD 620 have broken EDS causing extreme flickering - unknown if it affects other iGPUs
|
||||
@@ -743,25 +739,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
Settings::values.dyna_state.SetValue(0);
|
||||
}
|
||||
|
||||
if (Settings::values.dyna_state.GetValue() == 0) {
|
||||
must_emulate_scaled_formats = true;
|
||||
LOG_INFO(Render_Vulkan, "Dynamic state is disabled (dyna_state = 0), forcing scaled format emulation ON");
|
||||
|
||||
// Disable dynamic state 1-3 and all extensions
|
||||
RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2, VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3, VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
dynamic_state3_blending = false;
|
||||
dynamic_state3_enables = false;
|
||||
|
||||
LOG_INFO(Render_Vulkan, "All dynamic state extensions and features have been disabled");
|
||||
} else {
|
||||
must_emulate_scaled_formats = false;
|
||||
LOG_INFO(Render_Vulkan, "Dynamic state is enabled (dyna_state = 1-3), disabling scaled format emulation");
|
||||
}
|
||||
|
||||
logical = vk::Device::Create(physical, queue_cis, ExtensionListForVulkan(loaded_extensions), first_next, dld);
|
||||
|
||||
graphics_queue = logical.GetQueue(graphics_family);
|
||||
@@ -1226,7 +1203,7 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control,
|
||||
VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
|
||||
|
||||
/* */ // VK_EXT_extended_dynamic_state
|
||||
// VK_EXT_extended_dynamic_state
|
||||
extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state,
|
||||
features.extended_dynamic_state,
|
||||
|
||||
@@ -437,13 +437,6 @@ if (ENABLE_SDL2)
|
||||
target_compile_definitions(yuzu PRIVATE HAVE_SDL2)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
include(CopyYuzuSDLDeps)
|
||||
include(CopyYuzuFFmpegDeps)
|
||||
copy_yuzu_SDL_deps(yuzu)
|
||||
copy_yuzu_FFmpeg_deps(yuzu)
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||
target_link_libraries(yuzu PRIVATE dynarmic::dynarmic)
|
||||
endif()
|
||||
|
||||
@@ -7,18 +7,14 @@
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <QFileDialog>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMessageBox>
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "ui_configure_general.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/configuration/configure_general.h"
|
||||
#include "yuzu/configuration/shared_widget.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/util/game.h"
|
||||
|
||||
ConfigureGeneral::ConfigureGeneral(const Core::System& system_,
|
||||
std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
|
||||
@@ -26,45 +22,21 @@ ConfigureGeneral::ConfigureGeneral(const Core::System& system_,
|
||||
: Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGeneral>()}, system{system_} {
|
||||
ui->setupUi(this);
|
||||
|
||||
apply_funcs.push_back([this](bool) {
|
||||
Settings::values.external_dirs.clear();
|
||||
for (int i = 0; i < ui->external_dirs_list->count(); ++i) {
|
||||
QListWidgetItem* item = ui->external_dirs_list->item(i);
|
||||
if (item) {
|
||||
Settings::values.external_dirs.push_back(item->text().toStdString());
|
||||
}
|
||||
}
|
||||
auto& fs_controller = const_cast<Core::System&>(system).GetFileSystemController();
|
||||
fs_controller.RebuildExternalContentIndex();
|
||||
QtCommon::Game::ResetMetadata(false);
|
||||
UISettings::values.is_game_list_reload_pending.exchange(true);
|
||||
});
|
||||
|
||||
Setup(builder);
|
||||
|
||||
SetConfiguration();
|
||||
|
||||
connect(ui->button_reset_defaults, &QPushButton::clicked, this,
|
||||
&ConfigureGeneral::ResetDefaults);
|
||||
connect(ui->add_dir_button, &QPushButton::clicked, this, &ConfigureGeneral::OnAddDirClicked);
|
||||
connect(ui->remove_dir_button, &QPushButton::clicked, this,
|
||||
&ConfigureGeneral::OnRemoveDirClicked);
|
||||
connect(ui->external_dirs_list, &QListWidget::itemSelectionChanged, this,
|
||||
&ConfigureGeneral::OnDirSelectionChanged);
|
||||
|
||||
ui->remove_dir_button->setEnabled(false);
|
||||
|
||||
if (!Settings::IsConfiguringGlobal()) {
|
||||
ui->button_reset_defaults->setVisible(false);
|
||||
ui->DataDirsGroupBox->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigureGeneral::~ConfigureGeneral() = default;
|
||||
|
||||
void ConfigureGeneral::SetConfiguration() {
|
||||
LoadExternalDirs();
|
||||
}
|
||||
void ConfigureGeneral::SetConfiguration() {}
|
||||
|
||||
void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
|
||||
QLayout& general_layout = *ui->general_widget->layout();
|
||||
@@ -137,7 +109,6 @@ void ConfigureGeneral::ResetDefaults() {
|
||||
UISettings::values.reset_to_defaults = true;
|
||||
UISettings::values.is_game_list_reload_pending.exchange(true);
|
||||
reset_callback();
|
||||
SetConfiguration();
|
||||
}
|
||||
|
||||
void ConfigureGeneral::ApplyConfiguration() {
|
||||
@@ -158,37 +129,3 @@ void ConfigureGeneral::changeEvent(QEvent* event) {
|
||||
void ConfigureGeneral::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::LoadExternalDirs() {
|
||||
ui->external_dirs_list->clear();
|
||||
for (const auto& dir : Settings::values.external_dirs) {
|
||||
ui->external_dirs_list->addItem(QString::fromStdString(dir));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureGeneral::OnAddDirClicked() {
|
||||
QString default_path = QDir::homePath();
|
||||
if (ui->external_dirs_list->count() > 0) {
|
||||
default_path = ui->external_dirs_list->item(ui->external_dirs_list->count() - 1)->text();
|
||||
}
|
||||
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Select Directory"), default_path);
|
||||
if (!dir.isEmpty()) {
|
||||
if (ui->external_dirs_list->findItems(dir, Qt::MatchExactly).isEmpty()) {
|
||||
ui->external_dirs_list->addItem(dir);
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Directory already added"),
|
||||
tr("The directory \"%1\" is already in the list.").arg(dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureGeneral::OnRemoveDirClicked() {
|
||||
for (auto* item : ui->external_dirs_list->selectedItems()) {
|
||||
delete ui->external_dirs_list->takeItem(ui->external_dirs_list->row(item));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureGeneral::OnDirSelectionChanged() {
|
||||
ui->remove_dir_button->setEnabled(!ui->external_dirs_list->selectedItems().isEmpty());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -48,11 +45,6 @@ private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void LoadExternalDirs();
|
||||
void OnAddDirClicked();
|
||||
void OnRemoveDirClicked();
|
||||
void OnDirSelectionChanged();
|
||||
|
||||
std::function<void()> reset_callback;
|
||||
|
||||
std::unique_ptr<Ui::ConfigureGeneral> ui;
|
||||
|
||||
@@ -73,59 +73,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="DataDirsGroupBox">
|
||||
<property name="title">
|
||||
<string>Additional Directories (Updates, DLC, etc.)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="DataDirsVerticalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="external_dirs_list">
|
||||
<property name="toolTip">
|
||||
<string>This list contains directories that will be searched for game updates and DLC.</string>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="DataDirsHorizontalLayout">
|
||||
<item>
|
||||
<spacer name="spacer_dirs_left">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="add_dir_button">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="remove_dir_button">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "ui_configure_per_game_addons.h"
|
||||
#include "yuzu/configuration/configure_input.h"
|
||||
#include "yuzu/configuration/configure_per_game_addons.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
|
||||
ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* parent)
|
||||
: QWidget(parent), ui{std::make_unique<Ui::ConfigurePerGameAddons>()}, system{system_} {
|
||||
@@ -64,8 +64,6 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p
|
||||
|
||||
ui->scrollArea->setEnabled(!system.IsPoweredOn());
|
||||
|
||||
connect(item_model, &QStandardItemModel::itemChanged, this,
|
||||
&ConfigurePerGameAddons::OnItemChanged);
|
||||
connect(item_model, &QStandardItemModel::itemChanged,
|
||||
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
|
||||
}
|
||||
@@ -73,26 +71,12 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p
|
||||
ConfigurePerGameAddons::~ConfigurePerGameAddons() = default;
|
||||
|
||||
void ConfigurePerGameAddons::ApplyConfiguration() {
|
||||
bool any_variant_checked = false;
|
||||
for (auto* v : update_variant_items) {
|
||||
if (v && v->checkState() == Qt::Checked) {
|
||||
any_variant_checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (any_variant_checked && default_update_item &&
|
||||
default_update_item->checkState() == Qt::Unchecked) {
|
||||
default_update_item->setCheckState(Qt::Checked);
|
||||
}
|
||||
|
||||
std::vector<std::string> disabled_addons;
|
||||
|
||||
for (const auto& item : list_items) {
|
||||
const auto disabled = item.front()->checkState() == Qt::Unchecked;
|
||||
if (disabled) {
|
||||
const auto key = item.front()->data(Qt::UserRole).toString();
|
||||
disabled_addons.push_back(key.toStdString());
|
||||
}
|
||||
if (disabled)
|
||||
disabled_addons.push_back(item.front()->text().toStdString());
|
||||
}
|
||||
|
||||
auto current = Settings::values.disabled_addons[title_id];
|
||||
@@ -132,12 +116,6 @@ void ConfigurePerGameAddons::LoadConfiguration() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset model and caches to avoid duplicates
|
||||
item_model->removeRows(0, item_model->rowCount());
|
||||
list_items.clear();
|
||||
default_update_item = nullptr;
|
||||
update_variant_items.clear();
|
||||
|
||||
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
const auto loader = Loader::GetLoader(system, file);
|
||||
@@ -148,86 +126,21 @@ void ConfigurePerGameAddons::LoadConfiguration() {
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
for (const auto& patch : pm.GetPatches(update_raw)) {
|
||||
const auto display_name = QString::fromStdString(patch.name);
|
||||
const auto version_q = QString::fromStdString(patch.version);
|
||||
|
||||
QString toggle_key = display_name;
|
||||
const bool is_update = (patch.type == FileSys::PatchType::Update);
|
||||
const bool is_default_update_row = is_update && patch.version.empty();
|
||||
if (is_update) {
|
||||
if (is_default_update_row) {
|
||||
toggle_key = QStringLiteral("Update");
|
||||
} else if (!patch.version.empty() && patch.version != "PACKED") {
|
||||
toggle_key = QStringLiteral("Update v%1").arg(version_q);
|
||||
} else {
|
||||
toggle_key = QStringLiteral("Update");
|
||||
}
|
||||
}
|
||||
const auto name = QString::fromStdString(patch.name);
|
||||
|
||||
auto* const first_item = new QStandardItem;
|
||||
first_item->setText(display_name);
|
||||
first_item->setText(name);
|
||||
first_item->setCheckable(true);
|
||||
first_item->setData(toggle_key, Qt::UserRole);
|
||||
|
||||
const bool disabled_match_key =
|
||||
std::find(disabled.begin(), disabled.end(), toggle_key.toStdString()) != disabled.end();
|
||||
const bool disabled_all_updates =
|
||||
is_update &&
|
||||
std::find(disabled.begin(), disabled.end(), std::string("Update")) != disabled.end();
|
||||
const auto patch_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
|
||||
|
||||
const bool patch_disabled =
|
||||
disabled_match_key || (is_update && !is_default_update_row && disabled_all_updates);
|
||||
first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
|
||||
|
||||
auto* const second_item = new QStandardItem{version_q};
|
||||
|
||||
if (is_default_update_row) {
|
||||
QList<QStandardItem*> row{first_item, second_item};
|
||||
item_model->appendRow(row);
|
||||
list_items.push_back(row);
|
||||
default_update_item = first_item;
|
||||
tree_view->expand(first_item->index());
|
||||
} else if (is_update && default_update_item != nullptr) {
|
||||
QList<QStandardItem*> row{first_item, second_item};
|
||||
default_update_item->appendRow(row);
|
||||
list_items.push_back(row);
|
||||
update_variant_items.push_back(first_item);
|
||||
} else {
|
||||
QList<QStandardItem*> row{first_item, second_item};
|
||||
item_model->appendRow(row);
|
||||
list_items.push_back(row);
|
||||
}
|
||||
list_items.push_back(QList<QStandardItem*>{
|
||||
first_item, new QStandardItem{QString::fromStdString(patch.version)}});
|
||||
item_model->appendRow(list_items.back());
|
||||
}
|
||||
|
||||
tree_view->expandAll();
|
||||
tree_view->resizeColumnToContents(1);
|
||||
}
|
||||
|
||||
void ConfigurePerGameAddons::OnItemChanged(QStandardItem* item) {
|
||||
if (!item)
|
||||
return;
|
||||
const auto key = item->data(Qt::UserRole).toString();
|
||||
const bool is_update_row = key.startsWith(QStringLiteral("Update"));
|
||||
if (!is_update_row)
|
||||
return;
|
||||
|
||||
if (item == default_update_item) {
|
||||
if (default_update_item->checkState() == Qt::Unchecked) {
|
||||
for (auto* v : update_variant_items) {
|
||||
if (v && v->checkState() != Qt::Unchecked)
|
||||
v->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->checkState() == Qt::Checked) {
|
||||
for (auto* v : update_variant_items) {
|
||||
if (v && v != item && v->checkState() != Qt::Unchecked)
|
||||
v->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
if (default_update_item && default_update_item->checkState() == Qt::Unchecked) {
|
||||
default_update_item->setCheckState(Qt::Checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -47,8 +44,6 @@ private:
|
||||
|
||||
void LoadConfiguration();
|
||||
|
||||
void OnItemChanged(QStandardItem* item);
|
||||
|
||||
std::unique_ptr<Ui::ConfigurePerGameAddons> ui;
|
||||
FileSys::VirtualFile file;
|
||||
u64 title_id;
|
||||
@@ -59,8 +54,5 @@ private:
|
||||
|
||||
std::vector<QList<QStandardItem*>> list_items;
|
||||
|
||||
QStandardItem* default_update_item = nullptr;
|
||||
std::vector<QStandardItem*> update_variant_items;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <regex>
|
||||
#include "yuzu/game_list.h"
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
@@ -13,21 +13,20 @@
|
||||
#include <QMenu>
|
||||
#include <QThreadPool>
|
||||
#include <QToolButton>
|
||||
#include <fmt/ranges.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/util/game.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/game_list.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/game_list_worker.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/util/controller_navigation.h"
|
||||
#include <fmt/ranges.h>
|
||||
#include <regex>
|
||||
|
||||
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent)
|
||||
: QObject(parent), gamelist{gamelist_} {}
|
||||
@@ -319,8 +318,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
|
||||
: QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
|
||||
play_time_manager{play_time_manager_}, system{system_} {
|
||||
watcher = new QFileSystemWatcher(this);
|
||||
connect(watcher, &QFileSystemWatcher::directoryChanged, this,
|
||||
&GameList::OnWatchedDirectoryChanged);
|
||||
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
|
||||
|
||||
this->main_window = parent;
|
||||
layout = new QVBoxLayout;
|
||||
@@ -488,21 +486,14 @@ void GameList::DonePopulating(const QStringList& watch_list) {
|
||||
if (!watch_dirs.isEmpty()) {
|
||||
watcher->removePaths(watch_dirs);
|
||||
}
|
||||
|
||||
QStringList all_watch_paths = watch_list;
|
||||
for (const auto& dir : Settings::values.external_dirs) {
|
||||
all_watch_paths.append(QString::fromStdString(dir));
|
||||
}
|
||||
all_watch_paths.removeDuplicates();
|
||||
|
||||
// Workaround: Add the watch paths in chunks to allow the gui to refresh
|
||||
// This prevents the UI from stalling when a large number of watch paths are added
|
||||
// Also artificially caps the watcher to a certain number of directories
|
||||
constexpr int LIMIT_WATCH_DIRECTORIES = 5000;
|
||||
constexpr int SLICE_SIZE = 25;
|
||||
int len = (std::min)(static_cast<int>(all_watch_paths.size()), LIMIT_WATCH_DIRECTORIES);
|
||||
int len = (std::min)(static_cast<int>(watch_list.size()), LIMIT_WATCH_DIRECTORIES);
|
||||
for (int i = 0; i < len; i += SLICE_SIZE) {
|
||||
watcher->addPaths(all_watch_paths.mid(i, i + SLICE_SIZE));
|
||||
watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE));
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
tree_view->setEnabled(true);
|
||||
@@ -628,32 +619,26 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
|
||||
emit RemoveInstalledEntryRequested(program_id, QtCommon::Game::InstalledEntryType::Update);
|
||||
});
|
||||
connect(remove_dlc, &QAction::triggered, [this, program_id]() {
|
||||
emit RemoveInstalledEntryRequested(program_id,
|
||||
QtCommon::Game::InstalledEntryType::AddOnContent);
|
||||
emit RemoveInstalledEntryRequested(program_id, QtCommon::Game::InstalledEntryType::AddOnContent);
|
||||
});
|
||||
connect(remove_gl_shader_cache, &QAction::triggered, [this, program_id, path]() {
|
||||
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::GlShaderCache,
|
||||
path);
|
||||
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::GlShaderCache, path);
|
||||
});
|
||||
connect(remove_vk_shader_cache, &QAction::triggered, [this, program_id, path]() {
|
||||
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::VkShaderCache,
|
||||
path);
|
||||
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::VkShaderCache, path);
|
||||
});
|
||||
connect(remove_shader_cache, &QAction::triggered, [this, program_id, path]() {
|
||||
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::AllShaderCache,
|
||||
path);
|
||||
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::AllShaderCache, path);
|
||||
});
|
||||
connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
|
||||
emit RemoveFileRequested(program_id,
|
||||
QtCommon::Game::GameListRemoveTarget::CustomConfiguration, path);
|
||||
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::CustomConfiguration, path);
|
||||
});
|
||||
connect(set_play_time, &QAction::triggered,
|
||||
[this, program_id]() { emit SetPlayTimeRequested(program_id); });
|
||||
connect(remove_play_time_data, &QAction::triggered,
|
||||
[this, program_id]() { emit RemovePlayTimeRequested(program_id); });
|
||||
connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
|
||||
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::CacheStorage,
|
||||
path);
|
||||
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::CacheStorage, path);
|
||||
});
|
||||
connect(dump_romfs, &QAction::triggered, [this, program_id, path]() {
|
||||
emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal);
|
||||
@@ -680,8 +665,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
|
||||
connect(properties, &QAction::triggered,
|
||||
[this, path]() { emit OpenPerGameGeneralRequested(path); });
|
||||
|
||||
connect(ryujinx, &QAction::triggered,
|
||||
[this, program_id]() { emit LinkToRyujinxRequested(program_id); });
|
||||
connect(ryujinx, &QAction::triggered, [this, program_id]() { emit LinkToRyujinxRequested(program_id);
|
||||
});
|
||||
};
|
||||
|
||||
void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
|
||||
@@ -845,7 +830,8 @@ QStandardItemModel* GameList::GetModel() const {
|
||||
return item_model;
|
||||
}
|
||||
|
||||
void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
||||
void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs)
|
||||
{
|
||||
tree_view->setEnabled(false);
|
||||
|
||||
// Update the columns in case UISettings has changed
|
||||
@@ -862,8 +848,12 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
||||
item_model->removeRows(0, item_model->rowCount());
|
||||
search_field->clear();
|
||||
|
||||
current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list,
|
||||
play_time_manager, system);
|
||||
current_worker = std::make_unique<GameListWorker>(vfs,
|
||||
provider,
|
||||
game_dirs,
|
||||
compatibility_list,
|
||||
play_time_manager,
|
||||
system);
|
||||
|
||||
// Get events from the worker as data becomes available
|
||||
connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent,
|
||||
@@ -892,7 +882,8 @@ const QStringList GameList::supported_file_extensions = {
|
||||
QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"),
|
||||
QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
|
||||
|
||||
void GameList::RefreshGameDirectory() {
|
||||
void GameList::RefreshGameDirectory()
|
||||
{
|
||||
if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) {
|
||||
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
|
||||
PopulateAsync(UISettings::values.game_dirs);
|
||||
@@ -976,17 +967,6 @@ GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent}
|
||||
|
||||
GameListPlaceholder::~GameListPlaceholder() = default;
|
||||
|
||||
void GameList::OnWatchedDirectoryChanged(const QString& path) {
|
||||
LOG_INFO(Frontend, "Change detected in watched directory {}. Reloading content.",
|
||||
path.toStdString());
|
||||
|
||||
system.GetFileSystemController().RebuildExternalContentIndex();
|
||||
|
||||
QtCommon::Game::ResetMetadata(false);
|
||||
|
||||
RefreshGameDirectory();
|
||||
}
|
||||
|
||||
void GameListPlaceholder::onUpdateThemedIcons() {
|
||||
image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
|
||||
}
|
||||
|
||||
@@ -81,8 +81,6 @@ public:
|
||||
void LoadCompatibilityList();
|
||||
void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
|
||||
|
||||
void OnWatchedDirectoryChanged(const QString& path);
|
||||
|
||||
void SaveInterfaceLayout();
|
||||
void LoadInterfaceLayout();
|
||||
|
||||
|
||||
@@ -173,10 +173,6 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_update && patch.version.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QString type =
|
||||
QString::fromStdString(patch.enabled ? patch.name : "[D] " + patch.name);
|
||||
|
||||
|
||||
@@ -55,9 +55,4 @@ if(WIN32)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
include(CopyYuzuSDLDeps)
|
||||
copy_yuzu_SDL_deps(yuzu-cmd)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(yuzu-cmd)
|
||||
|
||||
@@ -37,11 +37,20 @@ EOF
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
(-uf|--force) UPDATE=true; FORCE=true; continue ;;
|
||||
(-u|--update) UPDATE=true; continue ;;
|
||||
(-h) usage ;;
|
||||
("$0") break ;;
|
||||
("") break ;;
|
||||
-f | --force)
|
||||
UPDATE=true
|
||||
FORCE=true
|
||||
shift
|
||||
continue
|
||||
;;
|
||||
-u | --update)
|
||||
UPDATE=true
|
||||
shift
|
||||
continue
|
||||
;;
|
||||
-h) usage ;;
|
||||
"$0") break ;;
|
||||
"") break ;;
|
||||
esac
|
||||
|
||||
PACKAGE="$1"
|
||||
@@ -52,68 +61,72 @@ while true; do
|
||||
# shellcheck disable=SC1091
|
||||
. tools/cpm/package.sh
|
||||
|
||||
SKIP=$(value "skip_updates")
|
||||
SKIP=$(value "skip_updates")
|
||||
|
||||
[ "$SKIP" = "true" ] && continue
|
||||
[ "$SKIP" = "true" ] && continue
|
||||
|
||||
[ "$REPO" = null ] && continue
|
||||
[ "$GIT_HOST" != "github.com" ] && continue # TODO
|
||||
# shellcheck disable=SC2153
|
||||
[ "$TAG" = null ] && continue
|
||||
[ "$REPO" = null ] && continue
|
||||
[ "$GIT_HOST" != "github.com" ] && continue # TODO
|
||||
# shellcheck disable=SC2153
|
||||
[ "$TAG" = null ] && continue
|
||||
|
||||
echo "-- Package $PACKAGE"
|
||||
echo "-- Package $PACKAGE"
|
||||
|
||||
# TODO(crueter): Support for Forgejo updates w/ forgejo_token
|
||||
# Use gh-cli to avoid ratelimits lmao
|
||||
TAGS=$(gh api --method GET "/repos/$REPO/tags")
|
||||
# Use gh-cli to avoid ratelimits lmao
|
||||
TAGS=$(gh api --method GET "/repos/$REPO/tags")
|
||||
|
||||
# filter out some commonly known annoyances
|
||||
# filter out some commonly known annoyances
|
||||
# TODO add more
|
||||
|
||||
filter vulkan-sdk # vulkan
|
||||
filter yotta # mbedtls
|
||||
filter vulkan-sdk # vulkan
|
||||
filter yotta # mbedtls
|
||||
|
||||
# ignore betas/alphas (remove if needed)
|
||||
# ignore betas/alphas (remove if needed)
|
||||
filter alpha
|
||||
filter beta
|
||||
filter rc
|
||||
|
||||
# Add package-specific overrides here, e.g. here for fmt:
|
||||
[ "$PACKAGE" = fmt ] && filter v0.11
|
||||
[ "$PACKAGE" = fmt ] && filter v0.11
|
||||
|
||||
LATEST=$(echo "$TAGS" | jq -r '.[0].name')
|
||||
LATEST=$(echo "$TAGS" | jq -r '.[0].name')
|
||||
|
||||
[ "$LATEST" = "$TAG" ] && [ "$FORCE" != "true" ] && echo "-- * Up-to-date" && continue
|
||||
[ "$LATEST" = "$TAG" ] && [ "$FORCE" != "true" ] && echo "-- * Up-to-date" && continue
|
||||
|
||||
RETURN=1
|
||||
|
||||
if [ "$HAS_REPLACE" = "true" ]; then
|
||||
# this just extracts the tag prefix
|
||||
VERSION_PREFIX=$(echo "$ORIGINAL_TAG" | cut -d"%" -f1)
|
||||
if [ "$HAS_REPLACE" = "true" ]; then
|
||||
# this just extracts the tag prefix
|
||||
VERSION_PREFIX=$(echo "$ORIGINAL_TAG" | cut -d"%" -f1)
|
||||
|
||||
# then we strip out the prefix from the new tag, and make that our new git_version
|
||||
NEW_GIT_VERSION=$(echo "$LATEST" | sed "s/$VERSION_PREFIX//g")
|
||||
fi
|
||||
# then we strip out the prefix from the new tag, and make that our new git_version
|
||||
if [ -z "$VERSION_PREFIX" ]; then
|
||||
NEW_GIT_VERSION="$LATEST"
|
||||
else
|
||||
NEW_GIT_VERSION=$(echo "$LATEST" | sed "s/$VERSION_PREFIX//g")
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "-- * Version $LATEST available, current is $TAG"
|
||||
echo "-- * Version $LATEST available, current is $TAG"
|
||||
|
||||
HASH=$(tools/cpm/hash.sh "$REPO" "$LATEST")
|
||||
HASH=$(tools/cpm/hash.sh "$REPO" "$LATEST")
|
||||
|
||||
echo "-- * New hash: $HASH"
|
||||
echo "-- * New hash: $HASH"
|
||||
|
||||
if [ "$UPDATE" = "true" ]; then
|
||||
if [ "$UPDATE" = "true" ]; then
|
||||
RETURN=0
|
||||
|
||||
if [ "$HAS_REPLACE" = "true" ]; then
|
||||
NEW_JSON=$(echo "$JSON" | jq ".hash = \"$HASH\" | .git_version = \"$NEW_GIT_VERSION\"")
|
||||
else
|
||||
NEW_JSON=$(echo "$JSON" | jq ".hash = \"$HASH\" | .tag = \"$LATEST\"")
|
||||
fi
|
||||
if [ "$HAS_REPLACE" = "true" ]; then
|
||||
NEW_JSON=$(echo "$JSON" | jq ".hash = \"$HASH\" | .git_version = \"$NEW_GIT_VERSION\"")
|
||||
else
|
||||
NEW_JSON=$(echo "$JSON" | jq ".hash = \"$HASH\" | .tag = \"$LATEST\"")
|
||||
fi
|
||||
|
||||
export NEW_JSON
|
||||
|
||||
tools/cpm/replace.sh
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exit $RETURN
|
||||
exit $RETURN
|
||||
|
||||
@@ -65,7 +65,7 @@ ci_package() {
|
||||
|
||||
echo "-- CI package $PACKAGE_NAME"
|
||||
|
||||
for platform in windows-amd64 windows-arm64 android solaris-amd64 freebsd-amd64 linux-amd64 linux-aarch64 macos-universal; do
|
||||
for platform in windows-amd64 windows-arm64 mingw-amd64 mingw-arm64 android solaris-amd64 freebsd-amd64 openbsd-amd64 linux-amd64 linux-aarch64 macos-universal; do
|
||||
echo "-- * platform $platform"
|
||||
|
||||
case $DISABLED in
|
||||
|
||||
Reference in New Issue
Block a user