Compare commits

..

15 Commits

Author SHA1 Message Date
27b3ac58c5 simplify naming and upgrade release upload with gitea native solution
Some checks failed
Build Linux AppImage / build-appimage (push) Has been cancelled
Build Linux AppImage / build-appimage (release) Successful in 43m8s
2026-01-15 02:25:27 -03:00
6609c6ddcd fix name build
Some checks failed
Build Linux AppImage / build-appimage (push) Has been cancelled
Build Linux AppImage / build-appimage (release) Failing after 43m39s
2026-01-15 01:32:23 -03:00
5fa81dae7a Linux build pipeline (#1)
Some checks failed
Build Linux AppImage / build-appimage (push) Has been cancelled
Build Linux AppImage / build-appimage (release) Failing after 44m19s
Reviewed-on: #1
2026-01-15 00:44:24 -03:00
b3eba6a275 ffmpeg downgrade? 2026-01-08 16:42:37 -03:00
c486334c78 lol 2026-01-08 16:20:36 -03:00
7dc30a5e2d salva isso 2026-01-08 16:18:11 -03:00
b16cece0a6 Merge branch 'master' of https://code.ovosimpatico.com/JDLO/eden 2026-01-08 14:33:52 -03:00
19bbd9894e custom auth support 2026-01-08 14:32:59 -03:00
3bb0d29aa1 fix: Update CMake configuration for Apple support and improve legacy online service command handling 2026-01-07 12:25:00 -03:00
cf4f813145 new update? 2026-01-05 23:49:04 -03:00
3290ed80d8 fix macos build 2026-01-02 09:00:48 -03:00
a71a82d3b7 feat: Add SSL service with OpenSSL, Schannel, and SecureTransport backends, and related internal network and video core components. 2025-12-30 00:55:29 -03:00
bd1d270e97 feat: Introduce new shader environment, OpenGL renderer components, and HLE socket/DNS services with internal network support. 2025-12-30 00:13:48 -03:00
664ff7cc85 2023 Support 2025-12-29 18:47:07 -03:00
b5c86787ab jdlo modifications
Some checks failed
Check Strings / check-strings (push) Has been cancelled
tx-src / sources (push) Has been cancelled
2025-12-26 13:28:47 -03:00
88 changed files with 2325 additions and 1449 deletions

View File

@@ -1,54 +0,0 @@
#!/usr/local/bin/bash -ex
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
[ -f "ps4-toolchain.cmake" ] || cat << EOF >"ps4-toolchain.cmake"
set(CMAKE_SYSROOT "$OO_PS4_TOOLCHAIN")
set(CMAKE_STAGING_PREFIX "$OO_PS4_TOOLCHAIN")
set(CMAKE_SYSTEM_NAME "OpenOrbis")
set(CMAKE_C_FLAGS " -D__OPENORBIS__ -D_LIBCPP_HAS_MUSL_LIBC=1 -D_GNU_SOURCE=1 --target=x86_64-pc-freebsd12-elf -mtune=x86-64 -march=x86-64 -fPIC -funwind-tables")
set(CMAKE_CXX_FLAGS " -D__OPENORBIS__ -D_LIBCPP_HAS_MUSL_LIBC=1 -D_GNU_SOURCE=1 --target=x86_64-pc-freebsd12-elf -mtune=x86-64 -march=x86-64 -fPIC -funwind-tables")
set(CMAKE_EXE_LINKER_FLAGS "-m elf_x86_64 -pie -T $OO_PS4_TOOLCHAIN/link.x --eh-frame-hdr -L$OO_PS4_TOOLCHAIN/lib")
set(CMAKE_C_LINK_FLAGS "-m elf_x86_64 -pie -T $OO_PS4_TOOLCHAIN/link.x --eh-frame-hdr -L$OO_PS4_TOOLCHAIN/lib")
set(CMAKE_CXX_LINK_FLAGS "-m elf_x86_64 -pie -T $OO_PS4_TOOLCHAIN/link.x --eh-frame-hdr -L$OO_PS4_TOOLCHAIN/lib")
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_LINKER ld.lld)
set(CMAKE_C_LINK_EXECUTABLE "<CMAKE_LINKER> <CMAKE_C_LINK_FLAGS> <OBJECTS> -o <TARGET> -lc -lkernel -lSceUserService -lSceSysmodule -lSceNet $OO_PS4_TOOLCHAIN/lib/crt1.o <LINK_LIBRARIES>")
set(CMAKE_CXX_LINK_EXECUTABLE "<CMAKE_LINKER> <CMAKE_CXX_LINK_FLAGS> <OBJECTS> -o <TARGET> -lc -lkernel -lc++ -lSceUserService -lSceSysmodule -lSceNet $OO_PS4_TOOLCHAIN/lib/crt1.o <LINK_LIBRARIES>")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# TODO: Why does cmake not set this?
set(CMAKE_SIZEOF_VOID_P 8)
EOF
# Normally a platform has a package manager
# PS4 does not, atleast not in the normal sense
export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" $@)
cmake -S . -B build -G "Unix Makefiles" \
-DCMAKE_TOOLCHAIN_FILE="ps4-toolchain.cmake" \
-DENABLE_QT_TRANSLATION=OFF \
-DENABLE_CUBEB=OFF \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS="$ARCH_FLAGS" \
-DCMAKE_C_FLAGS="$ARCH_FLAGS" \
-DENABLE_SDL2=ON \
-DENABLE_LIBUSB=OFF \
-DENABLE_UPDATE_CHECKER=OFF \
-DENABLE_QT=OFF \
-DENABLE_OPENSSL=OFF \
-DENABLE_WEB_SERVICE=OFF \
-DUSE_DISCORD_PRESENCE=OFF \
-DCPMUTIL_FORCE_BUNDLED=ON \
-DYUZU_USE_EXTERNAL_FFMPEG=ON \
-DYUZU_USE_CPM=ON \
"${EXTRA_CMAKE_FLAGS[@]}" || exit
cmake --build build -t yuzu-cmd_pkg -- -j8

View File

@@ -1,176 +0,0 @@
#!/usr/local/bin/bash -ex
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# Define global vars
# These flags are used everywhere, so let's reuse them.
export OO_PS4_TOOLCHAIN="$PWD/prefix"
export PREFIX="$OO_PS4_TOOLCHAIN"
export CC="clang"
export CXX="clang++"
export AR="llvm-ar"
export CFLAGS="-fPIC -DPS4 -D_LIBUNWIND_IS_BAREMETAL=1"
export CXXFLAGS="$CFLAGS -D__STDC_VERSION__=0"
export TARGET="x86_64-scei-ps4"
export LLVM_ROOT="$PWD/llvm-project"
export LLVM_PATH="$PWD/llvm-project/llvm"
export WORK_PATH="$PWD"
prepare_prefix() {
[ -d OpenOrbis-PS4-Toolchain ] || git clone --depth=1 https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain
[ -d musl ] || git clone --depth=1 https://github.com/OpenOrbis/musl
[ -d llvm-project ] || git clone --depth=1 --branch openorbis/20.x https://github.com/seuros/llvm-project
[ -d create-fself ] || git clone --depth=1 https://github.com/OpenOrbis/create-fself
[ -d create-gp4 ] || git clone --depth=1 https://github.com/OpenOrbis/create-gp4
[ -d readoelf ] || git clone --depth=1 https://github.com/OpenOrbis/readoelf
[ -d LibOrbisPkg ] || git clone --depth=1 https://github.com/maxton/LibOrbisPkg
mkdir -p $PREFIX "$PREFIX/bin" "$PREFIX/include"
[ -f "$PREFIX/include/orbis/libkernel.h" ] || cp -r OpenOrbis-PS4-Toolchain/include/* "$PREFIX/include/"
mkdir -p $PREFIX/usr
[ -L "$PREFIX/usr/include" ] || ln -s $PREFIX/include $PREFIX/usr/include || echo 1
[ -L "$PREFIX/usr/share" ] || ln -s $PREFIX/share $PREFIX/usr/share || echo 1
[ -L "$PREFIX/usr/lib" ] || ln -s $PREFIX/lib $PREFIX/usr/lib || echo 1
[ -L "$PREFIX/usr/bin" ] || ln -s $PREFIX/bin $PREFIX/usr/bin || echo 1
}
build_musl() {
mkdir -p musl-build
cd musl-build
../musl/configure --target=$TARGET --disable-shared CC="$CC" CFLAGS="$CFLAGS" --prefix=$PREFIX
gmake -j8 && gmake install
cd ..
}
build_llvm() {
# Build compiler-rt
cmake "$LLVM_ROOT/compiler-rt" -B "$WORK_PATH/llvm-build/compiler-rt" \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS" \
-DCMAKE_ASM_COMPILER="$CC" -DCMAKE_ASM_FLAGS="$CFLAGS -x assembler-with-cpp" \
-DLLVM_PATH="$LLVM_PATH" -DCOMPILER_RT_DEFAULT_TARGET_TRIPLE="$TARGET" \
-DCOMPILER_RT_BAREMETAL_BUILD=YES -DCOMPILER_RT_BUILD_BUILTINS=ON \
-DCOMPILER_RT_BUILD_CRT=OFF -DCOMPILER_RT_BUILD_SANITIZERS=OFF \
-DCOMPILER_RT_BUILD_XRAY=OFF -DCOMPILER_RT_BUILD_LIBFUZZER=OFF \
-DCOMPILER_RT_BUILD_PROFILE=OFF -DCOMPILER_RT_STANDALONE_BUILD=ON
# Build libunwind
cmake "$LLVM_ROOT/libunwind" -B "$WORK_PATH/llvm-build/libunwind" \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_C_FLAGS="$CFLAGS -fcxx-exceptions" -DCMAKE_CXX_FLAGS="$CXXFLAGS -fcxx-exceptions" \
-DCMAKE_ASM_COMPILER="$CC" -DCMAKE_ASM_FLAGS="$CFLAGS -x assembler-with-cpp" \
-DLLVM_PATH="$LLVM_PATH" -DLIBUNWIND_USE_COMPILER_RT=YES \
-DLIBUNWIND_BUILD_32_BITS=NO -DLIBUNWIND_ENABLE_STATIC=ON \
-DLIBUNWIND_ENABLE_SHARED=OFF -DLIBUNWIND_IS_BAREMETAL=ON
# Build libcxxabi
cmake "$LLVM_ROOT/libcxxabi" -B "$WORK_PATH/llvm-build/libcxxabi" \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_C_FLAGS="$CFLAGS -D_GNU_SOURCE=1 -isysroot $PREFIX -isystem $LLVM_ROOT/libcxx/include -isystem $PREFIX/include -isystem $WORK_PATH/llvm-build/libcxx/include/c++/v1" \
-DCMAKE_CXX_FLAGS="$CXXFLAGS -D_GNU_SOURCE=1 -isysroot $PREFIX -isystem $LLVM_ROOT/libcxx/include -isystem $PREFIX/include -isystem $WORK_PATH/llvm-build/libcxx/include/c++/v1" \
-DCMAKE_ASM_COMPILER="$CC" -DCMAKE_ASM_FLAGS="$CFLAGS -x assembler-with-cpp" \
-DLLVM_PATH="$LLVM_PATH" -DLIBCXXABI_ENABLE_SHARED=NO \
-DLLVM_ENABLE_RUNTIMES="rt;libunwind" \
-DLIBCXXABI_ENABLE_STATIC=YES -DLIBCXXABI_ENABLE_EXCEPTIONS=YES \
-DLIBCXXABI_USE_COMPILER_RT=YES -DLIBCXXABI_USE_LLVM_UNWINDER=YES \
-DLIBCXXABI_LIBUNWIND_PATH="$LLVM_ROOT/libunwind" \
-DLIBCXXABI_LIBCXX_INCLUDES="$LLVM_ROOT/libcxx/include" \
-DLIBCXXABI_ENABLE_PIC=YES
# Build libcxx
cmake "$LLVM_ROOT/libcxx" -B "$WORK_PATH/llvm-build/libcxx" \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_C_FLAGS="$CFLAGS -D_LIBCPP_HAS_MUSL_LIBC=1 -D_GNU_SOURCE=1 -isysroot $PREFIX -isystem $PREFIX/include/c++/v1 -isystem $PREFIX/include" \
-DCMAKE_CXX_FLAGS="$CXXFLAGS -D_LIBCPP_HAS_MUSL_LIBC=1 -D_GNU_SOURCE=1 -isysroot $PREFIX -isystem $PREFIX/include/c++/v1 -isystem $PREFIX/include" \
-DCMAKE_ASM_COMPILER="$CC" -DCMAKE_ASM_FLAGS="$CFLAGS -x assembler-with-cpp" \
-DLLVM_PATH="$LLVM_PATH" -DLIBCXX_ENABLE_RTTI=YES \
-DLIBCXX_HAS_MUSL_LIBC=YES -DLIBCXX_ENABLE_SHARED=NO \
-DLIBCXX_CXX_ABI=libcxxabi -DLIBCXX_CXX_ABI_INCLUDE_PATHS="$LLVM_ROOT/libcxxabi/include" \
-DLIBCXX_CXX_ABI_LIBRARY_PATH="$LLVM_ROOT/libcxxabi/build/lib"
cmake --build "$WORK_PATH/llvm-build/compiler-rt" --parallel
cmake --install "$WORK_PATH/llvm-build/compiler-rt"
cmake --build "$WORK_PATH/llvm-build/libunwind" --parallel
cmake --install "$WORK_PATH/llvm-build/libunwind"
cmake --build "$WORK_PATH/llvm-build/libcxxabi" --parallel
cmake --install "$WORK_PATH/llvm-build/libcxxabi"
touch "$WORK_PATH/llvm-build/libcxx/include/c++/v1/libcxx.imp"
cmake --build "$WORK_PATH/llvm-build/libcxx" --parallel
cmake --install "$WORK_PATH/llvm-build/libcxx"
}
build_tools() {
# Build create-fself
cd create-fself/cmd/create-fself
cp go-linux.mod go.mod
go build -ldflags "-linkmode external -extldflags -static" -o create-fself
mv ./create-fself $PREFIX/bin/create-fself
cd ../../../
# Build create-gp4
cd create-gp4/cmd/create-gp4
go build -ldflags "-linkmode external -extldflags -static" -o create-gp4
mv ./create-gp4 $PREFIX/bin/create-gp4
cd ../../../
# Build readoelf
cd readoelf/cmd/readoelf
go build -ldflags "-linkmode external -extldflags -static" -o readoelf
mv ./readoelf $PREFIX/bin/readoelf
cd ../../../
# # Pull maxton's publishing tools (<3)
# # Sadly maxton has passed on, we have forked the repository and will continue to update it in the future. RIP <3
# cd $PREFIX/bin
# [ -f PkgTool.Core-linux-x64-0.2.231.zip ] || wget https://github.com/maxton/LibOrbisPkg/releases/download/v0.2/PkgTool.Core-linux-x64-0.2.231.zip
# [ -f PkgTool.Core ] || unzip PkgTool.Core-linux-x64-0.2.231.zip
# chmod +x PkgTool.Core
}
finish_prefix() {
as $WORK_PATH/OpenOrbis-PS4-Toolchain/src/crt/crtlib.S -o $PREFIX/lib/crtlib.o
cp -a $WORK_PATH/OpenOrbis-PS4-Toolchain/link.x $PREFIX/
cp -a ~/OpenOrbis/PS4Toolchain/lib/libkernel* $PREFIX/lib/
cp -a ~/OpenOrbis/PS4Toolchain/lib/libSce* $PREFIX/lib/
cp -a ~/OpenOrbis/PS4Toolchain/lib/libSDL* $PREFIX/lib/
cp -r ~/OpenOrbis/PS4Toolchain/include/SDL2 $PREFIX/include/SDL2
cp $WORK_PATH/llvm-build/compiler-rt/lib/freebsd/libclang_rt.builtins-x86_64.a $PREFIX/lib/
# Combine libc++, libc++abi and libunwind into a single archive
cat << EOF >"mri.txt"
CREATE $PREFIX/lib/libc++M.a
ADDLIB $PREFIX/lib/libunwind.a
ADDLIB $PREFIX/lib/libc++abi.a
ADDLIB $PREFIX/lib/libc++.a
SAVE
END
EOF
$AR -M < mri.txt
cp $PREFIX/lib/libc++M.a $PREFIX/lib/libc++.a
# Merge compiler-rt into libc
cat << EOF >"mri.txt"
CREATE $PREFIX/lib/libcM.a
ADDLIB $PREFIX/lib/libc.a
ADDLIB $PREFIX/lib/libclang_rt.builtins-x86_64.a
SAVE
END
EOF
$AR -M < mri.txt
cp $PREFIX/lib/libcM.a $PREFIX/lib/libc.a
rm mri.txt
}
prepare_prefix
build_musl
build_llvm
build_tools
finish_prefix

View File

@@ -0,0 +1,159 @@
name: Build Linux AppImage
on:
push:
branches:
- '*'
pull_request:
branches:
- '*'
release:
types: [created, published]
jobs:
build-appimage:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
ninja-build \
pkg-config \
git \
curl \
zip \
unzip \
xvfb \
qt6-base-dev \
qt6-base-private-dev \
qt6-svg-dev \
qt6-multimedia-dev \
qt6-tools-dev \
libglew-dev \
libglfw3-dev \
libopus-dev \
libssl-dev \
libudev-dev \
libavcodec-dev \
libavformat-dev \
libswscale-dev \
libavfilter-dev \
libfmt-dev \
libgl1-mesa-dev \
libxrandr-dev \
libxi-dev \
libxcursor-dev \
libxinerama-dev \
libsamplerate-dev \
libasound2-dev \
libpulse-dev \
libsndio-dev \
nlohmann-json3-dev \
libboost-context-dev \
libboost-filesystem-dev \
libzstd-dev \
liblz4-dev \
libsdl2-dev \
catch2 \
libvulkan-dev \
glslang-tools \
spirv-tools \
zsync \
wget
- name: Clean build directory
run: rm -rf build
- name: Build Eden
run: |
export TARGET=appimage
export USE_MULTIMEDIA=false
.ci/linux/build.sh amd64
env:
NPROC: 2
- name: Package AppImage
run: |
export USE_MULTIMEDIA=false
.ci/linux/package.sh amd64
- name: Get version info
id: version
run: |
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")
COMMIT=$(git rev-parse --short HEAD)
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "commit=${COMMIT}" >> $GITHUB_OUTPUT
echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload AppImage artifact
uses: actions/upload-artifact@v3
with:
name: jdlo-eden-linux
path: |
Eden-*.AppImage
Eden-*.AppImage.zsync
retention-days: 30
if-no-files-found: error
- name: Create release info
run: |
echo "# Build Information" > build-info.txt
echo "Version: ${{ steps.version.outputs.version }}" >> build-info.txt
echo "Commit: ${{ steps.version.outputs.commit }}" >> build-info.txt
echo "Build Date: ${{ steps.version.outputs.date }}" >> build-info.txt
echo "Architecture: amd64-v3" >> build-info.txt
echo "Multimedia: disabled" >> build-info.txt
ls -lh Eden-*.AppImage >> build-info.txt
cat build-info.txt
- name: Upload build info
uses: actions/upload-artifact@v3
with:
name: build-info
path: build-info.txt
retention-days: 30
- name: Prepare release files
if: github.event_name == 'release'
id: prepare
run: |
# Find and rename files to simpler names
APPIMAGE_FILE=$(ls Eden-*.AppImage | head -n 1)
ZSYNC_FILE=$(ls Eden-*.AppImage.zsync | head -n 1)
cp "$APPIMAGE_FILE" jdlo-eden.AppImage
cp "$ZSYNC_FILE" jdlo-eden.AppImage.zsync
# Generate MD5 hash
MD5_HASH=$(md5sum "jdlo-eden.AppImage" | awk '{print $1}')
# Create release body with MD5 info
cat > release-body.md <<EOF
Thanks @arknost for the Linux version
\`jdlo-eden.AppImage\` MD5: \`$MD5_HASH\`
EOF
cat release-body.md
echo "md5_hash=$MD5_HASH" >> $GITHUB_OUTPUT
- name: Upload files to Release
if: github.event_name == 'release'
uses: akkuman/gitea-release-action@v1
with:
files: |-
jdlo-eden.AppImage
jdlo-eden.AppImage.zsync
body_path: release-body.md
token: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@@ -63,6 +63,3 @@ artifacts
*.AppImage*
/install*
vulkansdk*.exe
# PS4 toolchain stuff
ps4-toolchain.cmake

View File

@@ -1,17 +0,0 @@
diff --git a/libs/asio/include/boost/asio/detail/impl/socket_ops.ipp b/libs/asio/include/boost/asio/detail/impl/socket_ops.ipp
index 0129511c..10fc9b04 100644
--- a/libs/asio/include/boost/asio/detail/impl/socket_ops.ipp
+++ b/libs/asio/include/boost/asio/detail/impl/socket_ops.ipp
@@ -15,6 +15,12 @@
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
+// hacky fix for ps4
+#if defined(__OPENORBIS__)
+# define FIONBIO 0
+# define FIONREAD 1
+#endif
+
#include <boost/asio/detail/config.hpp>
#include <cctype>

View File

@@ -1,13 +0,0 @@
diff --git a/unix.c b/unix.c
index 6669216..86a2faa 100644
--- a/unix.c
+++ b/unix.c
@@ -53,7 +53,7 @@
#include <poll.h>
#endif
-#if !defined(HAS_SOCKLEN_T) && !defined(__socklen_t_defined)
+#if !defined(__OPENORBIS__) && !defined(HAS_SOCKLEN_T) && !defined(__socklen_t_defined)
typedef int socklen_t;
#endif

View File

@@ -7,7 +7,7 @@ index 754c984c79..59e27afd3e 100644
* target flag is enabled when building the library (e.g. `gcc -mpclmul -msse2`
* or `clang -maes -mpclmul`). */
-#if (defined(__GNUC__) || defined(__clang__)) && defined(__AES__) && defined(__PCLMUL__)
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(_LIBCPP_HAS_MUSL_LIBC)
+#if defined(__GNUC__) || defined(__clang__)
#define MBEDTLS_AESNI_HAVE_INTRINSICS
#endif
/* For 32-bit, we only support intrinsics */
@@ -25,7 +25,7 @@ index 2857068..3e104ab 100644
#pragma GCC target ("pclmul,sse2,aes")
#define MBEDTLS_POP_TARGET_PRAGMA
-#elif defined(__clang__) && (__clang_major__ >= 5)
+#elif defined(__clang__) && !defined(__OPENORBIS__)
+#elif defined(__clang__)
#pragma clang attribute push (__attribute__((target("pclmul,sse2,aes"))), apply_to=function)
#define MBEDTLS_POP_TARGET_PRAGMA
#endif

View File

@@ -1,13 +0,0 @@
diff --git a/library/entropy_poll.c b/library/entropy_poll.c
index 611768c..8950ee4 100644
--- a/library/entropy_poll.c
+++ b/library/entropy_poll.c
@@ -118,7 +118,7 @@ static int getrandom_wrapper(void *buf, size_t buflen, unsigned int flags)
*
* Documentation: https://netbsd.gw.com/cgi-bin/man-cgi?sysctl+7
*/
-#if (defined(__FreeBSD__) || defined(__NetBSD__)) && !defined(HAVE_GETRANDOM)
+#if (defined(__FreeBSD__) || defined(__NetBSD__)) && !defined(HAVE_GETRANDOM) && !defined(__OPENORBIS__)
#include <sys/param.h>
#include <sys/sysctl.h>
#if defined(KERN_ARND)

View File

@@ -1,25 +0,0 @@
diff --git a/source/opt/loop_dependence.cpp b/source/opt/loop_dependence.cpp
index e41c044..a51b53b 100644
--- a/source/opt/loop_dependence.cpp
+++ b/source/opt/loop_dependence.cpp
@@ -12,6 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// PS4: issue?
+#ifdef __PS4__
+#pragma clang diagnostic ignored "-Wabsolute-value"
+#pragma clang diagnostic ignored "-Wshorten-64-to-32"
+#endif
+
#include "source/opt/loop_dependence.h"
#include <functional>
@@ -19,6 +25,7 @@
#include <string>
#include <utility>
#include <vector>
+#include <cstdlib>
#include "source/opt/instruction.h"
#include "source/opt/scalar_analysis_nodes.h"

View File

@@ -17,7 +17,6 @@ include(DownloadExternals)
include(CMakeDependentOption)
include(CTest)
include(CPMUtil)
include(OpenOrbis)
DetectArchitecture()
@@ -159,7 +158,7 @@ set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundl
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
set(EXT_DEFAULT OFF)
if (MSVC OR ANDROID)
if (MSVC OR ANDROID OR APPLE)
set(EXT_DEFAULT ON)
endif()
option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT})
@@ -426,7 +425,7 @@ if (YUZU_USE_CPM)
endif()
# fmt
AddJsonPackage(fmt)
AddJsonPackage(NAME fmt BUNDLED_PACKAGE ON)
# lz4
AddJsonPackage(lz4)
@@ -531,7 +530,9 @@ if (APPLE)
# Umbrella framework for everything GUI-related
find_library(COCOA_LIBRARY Cocoa REQUIRED)
find_library(IOKIT_LIBRARY IOKit REQUIRED)
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
find_library(COREVIDEO_LIBRARY CoreVideo REQUIRED)
find_library(VIDEOTOOLBOX_LIBRARY VideoToolbox REQUIRED)
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY} ${VIDEOTOOLBOX_LIBRARY})
elseif (WIN32)
# Target Windows 10
add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00)
@@ -564,7 +565,7 @@ find_package(VulkanUtilityLibraries)
find_package(SimpleIni)
find_package(SPIRV-Tools)
find_package(sirit)
find_package(gamemode)
find_package(gamemode)
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
find_package(xbyak)

View File

@@ -1,391 +0,0 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
# Copyright 2019 Amine Ben Hassouna <amine.benhassouna@gmail.com>
# Copyright 2000-2019 Kitware, Inc. and Contributors
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of Kitware, Inc. nor the names of Contributors
# may be used to endorse or promote products derived from this
# software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#[=======================================================================[.rst:
FindSDL2
--------
Locate SDL2 library
This module defines the following 'IMPORTED' targets:
::
SDL2::Core
The SDL2 library, if found.
Libraries should link to SDL2::Core
SDL2::Main
The SDL2main library, if found.
Applications should link to SDL2::Main instead of SDL2::Core
This module will set the following variables in your project:
::
SDL2_LIBRARIES, the name of the library to link against
SDL2_INCLUDE_DIRS, where to find SDL.h
SDL2_FOUND, if false, do not try to link to SDL2
SDL2MAIN_FOUND, if false, do not try to link to SDL2main
SDL2_VERSION_STRING, human-readable string containing the version of SDL2
This module responds to the following cache variables:
::
SDL2_PATH
Set a custom SDL2 Library path (default: empty)
SDL2_NO_DEFAULT_PATH
Disable search SDL2 Library in default path.
If SDL2_PATH (default: ON)
Else (default: OFF)
SDL2_INCLUDE_DIR
SDL2 headers path.
SDL2_LIBRARY
SDL2 Library (.dll, .so, .a, etc) path.
SDL2MAIN_LIBRAY
SDL2main Library (.a) path.
SDL2_BUILDING_LIBRARY
This flag is useful only when linking to SDL2_LIBRARIES insead of
SDL2::Main. It is required only when building a library that links to
SDL2_LIBRARIES, because only applications need main() (No need to also
link to SDL2main).
If this flag is defined, then no SDL2main will be added to SDL2_LIBRARIES
and no SDL2::Main target will be created.
Don't forget to include SDLmain.h and SDLmain.m in your project for the
OS X framework based version. (Other versions link to -lSDL2main which
this module will try to find on your behalf.) Also for OS X, this
module will automatically add the -framework Cocoa on your behalf.
Additional Note: If you see an empty SDL2_LIBRARY in your project
configuration, it means CMake did not find your SDL2 library
(SDL2.dll, libsdl2.so, SDL2.framework, etc). Set SDL2_LIBRARY to point
to your SDL2 library, and configure again. Similarly, if you see an
empty SDL2MAIN_LIBRARY, you should set this value as appropriate. These
values are used to generate the final SDL2_LIBRARIES variable and the
SDL2::Core and SDL2::Main targets, but when these values are unset,
SDL2_LIBRARIES, SDL2::Core and SDL2::Main does not get created.
$SDL2DIR is an environment variable that would correspond to the
./configure --prefix=$SDL2DIR used in building SDL2. l.e.galup 9-20-02
Created by Amine Ben Hassouna:
Adapt FindSDL.cmake to SDL2 (FindSDL2.cmake).
Add cache variables for more flexibility:
SDL2_PATH, SDL2_NO_DEFAULT_PATH (for details, see doc above).
Mark 'Threads' as a required dependency for non-OSX systems.
Modernize the FindSDL2.cmake module by creating specific targets:
SDL2::Core and SDL2::Main (for details, see doc above).
Original FindSDL.cmake module:
Modified by Eric Wing. Added code to assist with automated building
by using environmental variables and providing a more
controlled/consistent search behavior. Added new modifications to
recognize OS X frameworks and additional Unix paths (FreeBSD, etc).
Also corrected the header search path to follow "proper" SDL
guidelines. Added a search for SDLmain which is needed by some
platforms. Added a search for threads which is needed by some
platforms. Added needed compile switches for MinGW.
On OSX, this will prefer the Framework version (if found) over others.
People will have to manually change the cache value of SDL2_LIBRARY to
override this selection or set the SDL2_PATH variable or the CMake
environment CMAKE_INCLUDE_PATH to modify the search paths.
Note that the header path has changed from SDL/SDL.h to just SDL.h
This needed to change because "proper" SDL convention is #include
"SDL.h", not <SDL/SDL.h>. This is done for portability reasons
because not all systems place things in SDL/ (see FreeBSD).
#]=======================================================================]
# Define options for searching SDL2 Library in a custom path
set(SDL2_PATH "" CACHE STRING "Custom SDL2 Library path")
set(_SDL2_NO_DEFAULT_PATH OFF)
if(SDL2_PATH)
set(_SDL2_NO_DEFAULT_PATH ON)
endif()
set(SDL2_NO_DEFAULT_PATH ${_SDL2_NO_DEFAULT_PATH}
CACHE BOOL "Disable search SDL2 Library in default path")
unset(_SDL2_NO_DEFAULT_PATH)
set(SDL2_NO_DEFAULT_PATH_CMD)
if(SDL2_NO_DEFAULT_PATH)
set(SDL2_NO_DEFAULT_PATH_CMD NO_DEFAULT_PATH)
endif()
# Search for the SDL2 include directory
find_path(SDL2_INCLUDE_DIR SDL.h
HINTS
ENV SDL2DIR
${SDL2_NO_DEFAULT_PATH_CMD}
PATH_SUFFIXES SDL2
# path suffixes to search inside ENV{SDL2DIR}
include/SDL2 include
PATHS ${SDL2_PATH}
DOC "Where the SDL2 headers can be found"
)
set(SDL2_INCLUDE_DIRS "${SDL2_INCLUDE_DIR}")
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(VC_LIB_PATH_SUFFIX lib/x64)
else()
set(VC_LIB_PATH_SUFFIX lib/x86)
endif()
# SDL-2.0 is the name used by FreeBSD ports...
# don't confuse it for the version number.
find_library(SDL2_LIBRARY
NAMES SDL2 SDL-2.0
HINTS
ENV SDL2DIR
${SDL2_NO_DEFAULT_PATH_CMD}
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
PATHS ${SDL2_PATH}
DOC "Where the SDL2 Library can be found"
)
set(SDL2_LIBRARIES "${SDL2_LIBRARY}")
if(NOT SDL2_BUILDING_LIBRARY)
if(NOT SDL2_INCLUDE_DIR MATCHES ".framework")
# Non-OS X framework versions expect you to also dynamically link to
# SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms
# seem to provide SDL2main for compatibility even though they don't
# necessarily need it.
if(SDL2_PATH)
set(SDL2MAIN_LIBRARY_PATHS "${SDL2_PATH}")
endif()
if(NOT SDL2_NO_DEFAULT_PATH)
set(SDL2MAIN_LIBRARY_PATHS
/sw
/opt/local
/opt/csw
/opt
"${SDL2MAIN_LIBRARY_PATHS}"
)
endif()
find_library(SDL2MAIN_LIBRARY
NAMES SDL2main
HINTS
ENV SDL2DIR
${SDL2_NO_DEFAULT_PATH_CMD}
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
PATHS ${SDL2MAIN_LIBRARY_PATHS}
DOC "Where the SDL2main library can be found"
)
unset(SDL2MAIN_LIBRARY_PATHS)
endif()
endif()
# SDL2 may require threads on your system.
# The Apple build may not need an explicit flag because one of the
# frameworks may already provide it.
# But for non-OSX systems, I will use the CMake Threads package.
if(NOT APPLE)
find_package(Threads QUIET)
if(NOT Threads_FOUND)
set(SDL2_THREADS_NOT_FOUND "Could NOT find Threads (Threads is required by SDL2).")
if(SDL2_FIND_REQUIRED)
message(FATAL_ERROR ${SDL2_THREADS_NOT_FOUND})
else()
if(NOT SDL2_FIND_QUIETLY)
message(STATUS ${SDL2_THREADS_NOT_FOUND})
endif()
return()
endif()
unset(SDL2_THREADS_NOT_FOUND)
endif()
endif()
# MinGW needs an additional link flag, -mwindows
# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -mwindows
if(MINGW)
set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW")
endif()
if(SDL2_LIBRARY)
# For SDL2main
if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY)
list(FIND SDL2_LIBRARIES "${SDL2MAIN_LIBRARY}" _SDL2_MAIN_INDEX)
if(_SDL2_MAIN_INDEX EQUAL -1)
set(SDL2_LIBRARIES "${SDL2MAIN_LIBRARY}" ${SDL2_LIBRARIES})
endif()
unset(_SDL2_MAIN_INDEX)
endif()
# For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
# CMake doesn't display the -framework Cocoa string in the UI even
# though it actually is there if I modify a pre-used variable.
# I think it has something to do with the CACHE STRING.
# So I use a temporary variable until the end so I can set the
# "real" variable in one-shot.
if(APPLE)
set(SDL2_LIBRARIES ${SDL2_LIBRARIES} -framework Cocoa)
endif()
# For threads, as mentioned Apple doesn't need this.
# In fact, there seems to be a problem if I used the Threads package
# and try using this line, so I'm just skipping it entirely for OS X.
if(NOT APPLE)
set(SDL2_LIBRARIES ${SDL2_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
endif()
# For MinGW library
if(MINGW)
set(SDL2_LIBRARIES ${MINGW32_LIBRARY} ${SDL2_LIBRARIES})
endif()
endif()
# Read SDL2 version
if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL_version.h")
file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+[0-9]+$")
file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MINOR_VERSION[ \t]+[0-9]+$")
file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_PATCHLEVEL[ \t]+[0-9]+$")
string(REGEX REPLACE "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}")
string(REGEX REPLACE "^#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}")
string(REGEX REPLACE "^#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}")
set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH})
unset(SDL2_VERSION_MAJOR_LINE)
unset(SDL2_VERSION_MINOR_LINE)
unset(SDL2_VERSION_PATCH_LINE)
unset(SDL2_VERSION_MAJOR)
unset(SDL2_VERSION_MINOR)
unset(SDL2_VERSION_PATCH)
endif()
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2
REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR
VERSION_VAR SDL2_VERSION_STRING)
if(SDL2MAIN_LIBRARY)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2main
REQUIRED_VARS SDL2MAIN_LIBRARY SDL2_INCLUDE_DIR
VERSION_VAR SDL2_VERSION_STRING)
endif()
mark_as_advanced(SDL2_PATH
SDL2_NO_DEFAULT_PATH
SDL2_LIBRARY
SDL2MAIN_LIBRARY
SDL2_INCLUDE_DIR
SDL2_BUILDING_LIBRARY)
# SDL2:: targets (SDL2::Core and SDL2::Main)
if(SDL2_FOUND)
# SDL2::Core target
if(SDL2_LIBRARY AND NOT TARGET SDL2::Core)
add_library(SDL2::Core UNKNOWN IMPORTED)
set_target_properties(SDL2::Core PROPERTIES
IMPORTED_LOCATION "${SDL2_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}")
if(APPLE)
# For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
# For more details, please see above.
set_property(TARGET SDL2::Core APPEND PROPERTY
INTERFACE_LINK_OPTIONS -framework Cocoa)
else()
# For threads, as mentioned Apple doesn't need this.
# For more details, please see above.
set_property(TARGET SDL2::Core APPEND PROPERTY
INTERFACE_LINK_LIBRARIES Threads::Threads)
endif()
endif()
# SDL2::Main target
# Applications should link to SDL2::Main instead of SDL2::Core
# For more details, please see above.
if(NOT SDL2_BUILDING_LIBRARY AND NOT TARGET SDL2::Main)
if(SDL2_INCLUDE_DIR MATCHES ".framework" OR NOT SDL2MAIN_LIBRARY)
add_library(SDL2::Main INTERFACE IMPORTED)
set_property(TARGET SDL2::Main PROPERTY
INTERFACE_LINK_LIBRARIES SDL2::Core)
elseif(SDL2MAIN_LIBRARY)
# MinGW requires that the mingw32 library is specified before the
# libSDL2main.a static library when linking.
# The SDL2::MainInternal target is used internally to make sure that
# CMake respects this condition.
add_library(SDL2::MainInternal UNKNOWN IMPORTED)
set_property(TARGET SDL2::MainInternal PROPERTY
IMPORTED_LOCATION "${SDL2MAIN_LIBRARY}")
set_property(TARGET SDL2::MainInternal PROPERTY
INTERFACE_LINK_LIBRARIES SDL2::Core)
add_library(SDL2::Main INTERFACE IMPORTED)
if(MINGW)
# MinGW needs an additional link flag '-mwindows' and link to mingw32
set_property(TARGET SDL2::Main PROPERTY
INTERFACE_LINK_LIBRARIES "mingw32" "-mwindows")
endif()
set_property(TARGET SDL2::Main APPEND PROPERTY
INTERFACE_LINK_LIBRARIES SDL2::MainInternal)
endif()
endif()
endif()

View File

@@ -18,13 +18,10 @@ if (DEFINED GIT_RELEASE)
set(BUILD_VERSION "${GIT_TAG}")
set(GIT_REFSPEC "${GIT_RELEASE}")
set(IS_DEV_BUILD false)
elseif(DEFINED GIT_COMMIT)
else()
string(SUBSTRING ${GIT_COMMIT} 0 10 BUILD_VERSION)
set(BUILD_VERSION "${BUILD_VERSION}-${GIT_REFSPEC}")
set(IS_DEV_BUILD true)
else()
set(BUILD_VERSION "NoGitInfo")
set(IS_DEV_BUILD true)
endif()
set(GIT_DESC ${BUILD_VERSION})

View File

@@ -1,33 +0,0 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
function(create_ps4_pkg project target content_id)
set(sce_sys_dir sce_sys)
set(sce_sys_param ${sce_sys_dir}/param.sfo)
add_custom_command(
OUTPUT "${target}.pkg"
COMMAND ${CMAKE_SYSROOT}/bin/create-fself -in=bin/${target} -out=${target}.oelf --eboot eboot.bin
COMMAND mkdir -p ${sce_sys_dir}
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_new ${sce_sys_param}
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} APP_TYPE --type Integer --maxsize 4 --value 1
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} APP_VER --type Utf8 --maxsize 8 --value 1.03
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} ATTRIBUTE --type Integer --maxsize 4 --value 0
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} CATEGORY --type Utf8 --maxsize 4 --value gd
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} CONTENT_ID --type Utf8 --maxsize 48 --value ${content_id}
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} DOWNLOAD_DATA_SIZE --type Integer --maxsize 4 --value 0
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} SYSTEM_VER --type Integer --maxsize 4 --value 0
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} TITLE --type Utf8 --maxsize 128 --value ${target}
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} TITLE_ID --type Utf8 --maxsize 12 --value BREW00090
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core sfo_setentry ${sce_sys_param} VERSION --type Utf8 --maxsize 8 --value 1.03
COMMAND ${CMAKE_SYSROOT}/bin/create-gp4 -out ${target}.gp4 --content-id=${content_id} --files "eboot.bin ${sce_sys_param} sce_module/libc.prx sce_module/libSceFios2.prx"
COMMAND ${CMAKE_SYSROOT}/bin/PkgTool.Core pkg_build ${target}.gp4 .
VERBATIM
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS ${project}
)
add_custom_target(${project}_pkg ALL DEPENDS "${target}.pkg")
endfunction()
if (NOT DEFINED ENV{OO_PS4_TOOLCHAIN})
set(ENV{OO_PS4_TOOLCHAIN} ${CMAKE_SYSROOT})
endif ()

View File

@@ -19,8 +19,7 @@
"patches": [
"0001-clang-cl.patch",
"0002-use-marmasm.patch",
"0003-armasm-options.patch",
"0004-openorbis.patch"
"0003-armasm-options.patch"
]
},
"fmt": {

View File

@@ -207,10 +207,3 @@ Install [Qt6 compatibility libraries](github.com/ANightly/qt6windows7) specifica
The package install may randomly hang at times, in which case it has to be restarted. ALWAYS do a `sudo pkg update` or the chances of it hanging will be close to 90%. If "multiple" installs fail at once, try installing 1 by 1 the packages.
When CMake invokes certain file syscalls - it may sometimes cause crashes or corruptions on the (kernel?) address space - so reboot the system if there is a "hang" in CMake.
## PlayStation 4 (OpenOrbis cross compile)
```sh
export OO_PS4_TOOLCHAIN="$HOME/OpenOrbis/PS4Toolchain"
export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
```

View File

@@ -26,7 +26,6 @@ Eden will store configuration files in the following directories:
- **Android**: Data is stored internally.
- **Linux, macOS, FreeBSD, Solaris, OpenBSD**: `$XDG_DATA_HOME`, `$XDG_CACHE_HOME`, `$XDG_CONFIG_HOME`.
- **HaikuOS**: `/boot/home/config/settings/eden`
- **PlayStation 4**: `/data/eden`
If a `user` directory is present in the current working directory, that will override all global configuration directories and the emulator will use that instead.

View File

@@ -147,11 +147,7 @@ if (ENABLE_SDL2)
AddJsonPackage(sdl2)
endif()
if (PLATFORM_PS4)
set(SDL2_LIBRARY ${CMAKE_SYSROOT}/lib/libSDL2.a)
set(SDL2_INCLUDE_DIR ${CMAKE_SYSROOT}/include/SDL2)
endif()
find_package(SDL2 REQUIRED)
find_package(SDL2 2.26.4 REQUIRED)
endif()
set(BUILD_SHARED_LIBS OFF)

View File

@@ -17,8 +17,6 @@
if (${CMAKE_SYSTEM_NAME} STREQUAL "SunOS")
set(PLATFORM_SUN ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenOrbis")
set(PLATFORM_PS4 ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
set(PLATFORM_FREEBSD ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")

View File

@@ -104,8 +104,7 @@
"skip_updates": true,
"patches": [
"0001-aesni-fix.patch",
"0002-arm64-aes-fix.patch",
"0003-openorbis.patch"
"0002-arm64-aes-fix.patch"
]
},
"enet": {
@@ -114,10 +113,7 @@
"hash": "a0d2fa8c957704dd49e00a726284ac5ca034b50b00d2b20a94fa1bbfbb80841467834bfdc84aa0ed0d6aab894608fd6c86c3b94eee46343f0e6d9c22e391dbf9",
"version": "1.3",
"git_version": "1.3.18",
"find_args": "MODULE",
"patches": [
"0001-openorbis.patch"
]
"find_args": "MODULE"
},
"vulkan-utility-headers": {
"package": "VulkanUtilityLibraries",
@@ -138,8 +134,7 @@
"SPIRV_SKIP_EXECUTABLES ON"
],
"patches": [
"0001-netbsd-fix.patch",
"0002-openorbis.patch"
"0001-netbsd-fix.patch"
]
},
"spirv-headers": {

View File

@@ -13,106 +13,63 @@ set(FFmpeg_HWACCEL_FLAGS)
set(FFmpeg_HWACCEL_INCLUDE_DIRS)
set(FFmpeg_HWACCEL_LDFLAGS)
if (NOT YUZU_USE_BUNDLED_FFMPEG)
set(FFmpeg_CROSS_COMPILE_FLAGS "")
if (ANDROID)
# TODO: Maybe use CMAKE_SYSROOT? and probably provide a toolchain file for android
# I mean isn't that the "proper" way anyways?
string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" FFmpeg_HOST_SYSTEM_NAME)
set(TOOLCHAIN "${ANDROID_NDK}/toolchains/llvm/prebuilt/${FFmpeg_HOST_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}")
set(SYSROOT "${TOOLCHAIN}/sysroot")
set(FFmpeg_CPU "armv8-a")
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--enable-cross-compile
--arch=arm64
#--cpu=${FFmpeg_CPU}
--cross-prefix="${TOOLCHAIN}/bin/aarch64-linux-android-"
--sysroot="${SYSROOT}"
--target-os=android
--extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld"
--extra-ldflags="-nostdlib"
)
set(FFmpeg_IS_CROSS_COMPILING TRUE)
# User attempts to do a FFmpeg cross compilation because...
# Here we just quickly test against host/system processors not matching
# TODO: Test for versions not matching as well?
elseif (NOT (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES CMAKE_SYSTEM_PROCESSOR
AND CMAKE_HOST_SYSTEM_NAME MATCHES CMAKE_SYSTEM_NAME))
string(TOLOWER "${CMAKE_SYSTEM_NAME}" FFmpeg_SYSTEM_NAME)
if (FFmpeg_SYSTEM_NAME STREQUAL "openorbis")
set(FFmpeg_SYSTEM_NAME "freebsd") # Emulates FBSD :)
endif()
# TODO: Can we really do better? Auto-detection? Something clever?
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--enable-cross-compile
--arch="${CMAKE_SYSTEM_PROCESSOR}"
--target-os="${FFmpeg_SYSTEM_NAME}"
--sysroot="${CMAKE_SYSROOT}"
)
if (DEFINED FFmpeg_CROSS_PREFIX)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --cross-prefix="${FFmpeg_CROSS_PREFIX}")
else()
message(WARNING "Please set FFmpeg_CROSS_PREFIX to your cross toolchain prefix, for example: \${CMAKE_STAGING_PREFIX}/bin/${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}-")
endif()
set(FFmpeg_IS_CROSS_COMPILING TRUE)
endif()
endif()
if (PLATFORM_PS4)
list(APPEND FFmpeg_HWACCEL_FLAGS
--disable-vaapi
)
elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING)
if (UNIX AND NOT ANDROID)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBVA libva)
pkg_check_modules(CUDA cuda)
pkg_check_modules(FFNVCODEC ffnvcodec)
pkg_check_modules(VDPAU vdpau)
if (NOT ANDROID)
pkg_check_modules(LIBVA libva)
pkg_check_modules(CUDA cuda)
pkg_check_modules(FFNVCODEC ffnvcodec)
pkg_check_modules(VDPAU vdpau)
endif()
find_package(X11)
if(X11_FOUND)
if (NOT APPLE)
# In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so
if(PLATFORM_SUN)
if (NOT APPLE)
# In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so
if(PLATFORM_SUN)
find_library(LIBDRM_LIB libdrm PATHS /usr/lib/64 /usr/lib/amd64 /usr/lib)
if(LIBDRM_LIB)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
X11
"${CMAKE_SYSROOT}/usr/lib/xorg/amd64/libdrm.so")
"${LIBDRM_LIB}")
message(STATUS "Found libdrm at: ${LIBDRM_LIB}")
else()
pkg_check_modules(LIBDRM libdrm REQUIRED)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
${LIBDRM_LIBRARIES})
list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
${LIBDRM_INCLUDE_DIRS})
message(WARNING "libdrm not found, disabling libdrm support")
list(APPEND FFmpeg_HWACCEL_FLAGS
--disable-libdrm)
endif()
else()
pkg_check_modules(LIBDRM libdrm REQUIRED)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
${LIBDRM_LIBRARIES})
list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
${LIBDRM_INCLUDE_DIRS})
list(APPEND FFmpeg_HWACCEL_FLAGS
--enable-libdrm)
endif()
if(LIBVA_FOUND)
pkg_check_modules(LIBVA-DRM libva-drm REQUIRED)
pkg_check_modules(LIBVA-X11 libva-x11 REQUIRED)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
${X11_LIBRARIES}
${LIBVA-DRM_LIBRARIES}
${LIBVA-X11_LIBRARIES}
${LIBVA_LIBRARIES})
list(APPEND FFmpeg_HWACCEL_FLAGS
--enable-hwaccel=h264_vaapi
--enable-hwaccel=vp8_vaapi
--enable-hwaccel=vp9_vaapi)
list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
${X11_INCLUDE_DIRS}
${LIBVA-DRM_INCLUDE_DIRS}
${LIBVA-X11_INCLUDE_DIRS}
${LIBVA_INCLUDE_DIRS}
)
message(STATUS "ffmpeg: va-api libraries version ${LIBVA_VERSION} found")
else()
list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi)
message(WARNING "ffmpeg: libva-dev not found, disabling Video Acceleration API (VA-API)...")
endif()
endif()
if(LIBVA_FOUND)
find_package(X11 REQUIRED)
pkg_check_modules(LIBVA-DRM libva-drm REQUIRED)
pkg_check_modules(LIBVA-X11 libva-x11 REQUIRED)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
${X11_LIBRARIES}
${LIBVA-DRM_LIBRARIES}
${LIBVA-X11_LIBRARIES}
${LIBVA_LIBRARIES})
list(APPEND FFmpeg_HWACCEL_FLAGS
--enable-hwaccel=h264_vaapi
--enable-hwaccel=vp8_vaapi
--enable-hwaccel=vp9_vaapi)
list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
${X11_INCLUDE_DIRS}
${LIBVA-DRM_INCLUDE_DIRS}
${LIBVA-X11_INCLUDE_DIRS}
${LIBVA_INCLUDE_DIRS}
)
message(STATUS "ffmpeg: va-api libraries version ${LIBVA_VERSION} found")
else()
list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi)
message(WARNING "ffmpeg: X11 libraries not found, disabling VA-API...")
message(WARNING "ffmpeg: libva-dev not found, disabling Video Acceleration API (VA-API)...")
endif()
if (FFNVCODEC_FOUND)
@@ -156,23 +113,6 @@ elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING)
endif()
endif()
if (PLATFORM_PS4)
list(APPEND FFmpeg_CROSS_COMPILE_LIBS
-lc
-lkernel
-lSceUserService
-lSceSysmodule
-lSceNet
-lSceLibcInternal
)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--disable-pthreads
--extra-cflags=${CMAKE_SYSROOT}/usr/include
--extra-cxxflags=${CMAKE_SYSROOT}/usr/include
--extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}"
)
endif()
if (YUZU_USE_BUNDLED_FFMPEG)
AddJsonPackage(ffmpeg-ci)
@@ -243,6 +183,24 @@ else()
find_program(BASH_PROGRAM bash REQUIRED)
set(FFmpeg_CROSS_COMPILE_FLAGS "")
if (ANDROID)
string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" FFmpeg_HOST_SYSTEM_NAME)
set(TOOLCHAIN "${ANDROID_NDK}/toolchains/llvm/prebuilt/${FFmpeg_HOST_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}")
set(SYSROOT "${TOOLCHAIN}/sysroot")
set(FFmpeg_CPU "armv8-a")
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--arch=arm64
#--cpu=${FFmpeg_CPU}
--enable-cross-compile
--cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android-
--sysroot=${SYSROOT}
--target-os=android
--extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld"
--extra-ldflags="-nostdlib"
)
endif()
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
# `--disable-vdpau` is needed to avoid linking issues
set(FFmpeg_CC ${CMAKE_C_COMPILER_LAUNCHER} ${CMAKE_C_COMPILER})
@@ -265,12 +223,8 @@ else()
--enable-decoder=vp9
--enable-filter=yadif,scale
--enable-pic
--cc=${FFmpeg_CC}
--cxx=${FFmpeg_CXX}
--ld=${CMAKE_LINKER}
--extra-cflags=${CMAKE_C_FLAGS}
--extra-cxxflags=${CMAKE_CXX_FLAGS}
--extra-ldflags=${CMAKE_C_LINK_FLAGS}
--cc="${FFmpeg_CC}"
--cxx="${FFmpeg_CXX}"
${FFmpeg_HWACCEL_FLAGS}
${FFmpeg_CROSS_COMPILE_FLAGS}
WORKING_DIRECTORY
@@ -302,7 +256,7 @@ else()
OUTPUT
${FFmpeg_BUILD_LIBRARIES}
COMMAND
gmake ${FFmpeg_MAKE_ARGS}
make ${FFmpeg_MAKE_ARGS}
WORKING_DIRECTORY
${FFmpeg_BUILD_DIR}
)

View File

@@ -1,16 +1,16 @@
{
"ffmpeg": {
"repo": "FFmpeg/FFmpeg",
"sha": "5e56937b74",
"hash": "9ab0457dcd6ce6359b5053c1662f57910d332f68ca0cca9d4134d858464840917027374de3d97e0863c3a7daaea2fe4f4cd17d1c6d8e7f740f4ad91e71c2932b",
"tag": "n6.1",
"bundled": true
},
"ffmpeg-ci": {
"ci": true,
"package": "FFmpeg",
"name": "ffmpeg",
"repo": "crueter-ci/FFmpeg",
"version": "8.0.1-5e56937b74",
"repo": "FFmpeg/FFmpeg",
"tag": "n6.1",
"version": "6.1",
"min_version": "4.1"
}
}
}

View File

@@ -251,8 +251,8 @@ if (ENABLE_SDL2)
sink/sdl2_sink.cpp
sink/sdl2_sink.h
)
target_include_directories(audio_core PRIVATE ${CMAKE_SYSROOT}/include/SDL2)
target_link_libraries(audio_core PRIVATE SDL2)
target_link_libraries(audio_core PRIVATE SDL2::SDL2)
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
endif()

View File

@@ -1,6 +1,3 @@
// 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
@@ -174,14 +171,11 @@ bool CreateDir(const fs::path& path) {
return false;
}
// TODO: Maybe this is what causes death?
#ifndef __OPENORBIS__
if (!Exists(path.parent_path())) {
LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist",
PathToUTF8String(path));
return false;
}
#endif
if (IsDir(path)) {
LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory",

View File

@@ -130,10 +130,6 @@ public:
ASSERT(!eden_path.empty());
eden_path_cache = eden_path / CACHE_DIR;
eden_path_config = eden_path / CONFIG_DIR;
#elif defined(__OPENORBIS__)
eden_path = "/data/eden";
eden_path_cache = eden_path / CACHE_DIR;
eden_path_config = eden_path / CONFIG_DIR;
#else
eden_path = GetCurrentDir() / PORTABLE_DIR;
if (!Exists(eden_path) || !IsDir(eden_path)) {

View File

@@ -30,21 +30,19 @@
#include <sys/random.h>
#include <mach/vm_map.h>
#include <mach/mach.h>
#elif defined(__OPENORBIS__)
#include <orbis/libkernel.h>
#endif
// FreeBSD
#ifndef MAP_NORESERVE
# define MAP_NORESERVE 0
#define MAP_NORESERVE 0
#endif
// Solaris 11 and illumos
#ifndef MAP_ALIGNED_SUPER
# define MAP_ALIGNED_SUPER 0
#define MAP_ALIGNED_SUPER 0
#endif
// macOS
#ifndef MAP_ANONYMOUS
# define MAP_ANONYMOUS MAP_ANON
#define MAP_ANONYMOUS MAP_ANON
#endif
#endif // ^^^ POSIX ^^^
@@ -70,8 +68,8 @@ static int memfd_create(const char* name, unsigned int flags) {
namespace Common {
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
constexpr size_t PageAlignment = 0x1000;
constexpr size_t HugePageSize = 0x200000;
#ifdef _WIN32
@@ -440,15 +438,13 @@ static void* ChooseVirtualBase(size_t virtual_size) {
#else
static void* ChooseVirtualBase(size_t size) {
static void* ChooseVirtualBase(size_t virtual_size) {
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) || defined(__AIX__)
void* virtual_base = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0);
void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0);
if (virtual_base != MAP_FAILED)
return virtual_base;
return mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
#else
return mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
#endif
return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
}
#endif
@@ -504,12 +500,9 @@ class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_)
: backing_size{backing_size_}, virtual_size{virtual_size_} {
#ifdef __OPENORBIS__
#else
long page_size = sysconf(_SC_PAGESIZE);
ASSERT_MSG(page_size == 0x1000, "page size {:#x} is incompatible with 4K paging", page_size);
#endif
ASSERT_MSG(page_size == 0x1000, "page size {:#x} is incompatible with 4K paging",
page_size);
// Backing memory initialization
#if defined(__sun__) || defined(__HAIKU__) || defined(__NetBSD__) || defined(__DragonFly__)
fd = shm_open_anon(O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
@@ -699,25 +692,31 @@ private:
#endif // ^^^ POSIX ^^^
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) : backing_size(backing_size_), virtual_size(virtual_size_) {
#ifdef __OPENORBIS__
Common::InitSwap();
LOG_WARNING(HW_Memory, "Platform doesn't support fastmem");
fallback_buffer.emplace(backing_size);
backing_base = fallback_buffer->data();
virtual_base = nullptr;
#else
// Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors.
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
backing_base = impl->backing_base;
virtual_base = impl->virtual_base;
if (virtual_base) {
// Ensure the virtual base is aligned to the L2 block size.
virtual_base = reinterpret_cast<u8*>(Common::AlignUp(uintptr_t(virtual_base), HugePageSize));
virtual_base_offset = virtual_base - impl->virtual_base;
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
: backing_size(backing_size_), virtual_size(virtual_size_) {
try {
// Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors.
impl =
std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment),
AlignUp(virtual_size, PageAlignment) + HugePageSize);
backing_base = impl->backing_base;
virtual_base = impl->virtual_base;
if (virtual_base) {
// Ensure the virtual base is aligned to the L2 block size.
virtual_base = reinterpret_cast<u8*>(
Common::AlignUp(reinterpret_cast<uintptr_t>(virtual_base), HugePageSize));
virtual_base_offset = virtual_base - impl->virtual_base;
}
} catch (const std::bad_alloc&) {
LOG_CRITICAL(HW_Memory,
"Fastmem unavailable, falling back to VirtualBuffer for memory allocation");
fallback_buffer = std::make_unique<Common::VirtualBuffer<u8>>(backing_size);
backing_base = fallback_buffer->data();
virtual_base = nullptr;
}
#endif
}
HostMemory::~HostMemory() = default;

View File

@@ -1,13 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <optional>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/virtual_buffer.h"
@@ -85,7 +81,7 @@ private:
size_t virtual_base_offset{};
// Fallback if fastmem is not supported on this platform
std::optional<Common::VirtualBuffer<u8>> fallback_buffer;
std::unique_ptr<Common::VirtualBuffer<u8>> fallback_buffer;
};
} // namespace Common

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -11,7 +8,7 @@
// clang-format on
#else
#include <sys/types.h>
#if defined(__APPLE__) || (defined(__FreeBSD__) && !defined(__OPENORBIS__))
#if defined(__APPLE__) || defined(__FreeBSD__)
#include <sys/sysctl.h>
#elif defined(__linux__)
#include <sys/sysinfo.h>
@@ -46,8 +43,6 @@ static MemoryInfo Detect() {
sysctlbyname("vm.swapusage", &vmusage, &sizeof_vmusage, nullptr, 0);
mem_info.TotalPhysicalMemory = ramsize;
mem_info.TotalSwapMemory = vmusage.xsu_total;
#elif defined(__OPENORBIS__)
mem_info.TotalPhysicalMemory = mem_info.TotalSwapMemory = 0;
#elif defined(__FreeBSD__)
u_long physmem, swap_total;
std::size_t sizeof_u_long = sizeof(u_long);

View File

@@ -196,14 +196,7 @@ struct Values {
linkage, false, "dump_audio_commands", Category::Audio, Specialization::Default, false};
// Core
SwitchableSetting<bool> use_multi_core{linkage,
#ifdef __OPENORBIS__
// Re-enable once proper TLS support is added
false,
#else
true,
#endif
"use_multi_core", Category::Core};
SwitchableSetting<bool> use_multi_core{linkage, true, "use_multi_core", Category::Core};
SwitchableSetting<MemoryLayout, true> memory_layout_mode{linkage,
MemoryLayout::Memory_4Gb,
"memory_layout_mode",
@@ -325,14 +318,14 @@ struct Values {
// Renderer
SwitchableSetting<RendererBackend, true> renderer_backend{linkage,
#if defined(__sun__) || defined(__managarm__) || defined(__OPENORBIS__)
#if defined(__sun__) || defined(__managarm__)
RendererBackend::OpenGL,
#else
RendererBackend::Vulkan,
#endif
"backend", Category::Renderer};
SwitchableSetting<ShaderBackend, true> shader_backend{linkage,
#if defined(__sun__) || defined(__managarm__) || defined(__OPENORBIS__)
#if defined(__sun__) || defined(__managarm__)
ShaderBackend::Glsl,
#else
ShaderBackend::SpirV,

View File

@@ -10,147 +10,30 @@
#include <sys/mman.h>
#endif
#ifdef __OPENORBIS__
#include <ranges>
#include <csignal>
#include <boost/container/static_vector.hpp>
#include <orbis/SystemService.h>
typedef void (*SceKernelExceptionHandler)(int32_t, void*);
extern "C" int32_t sceKernelInstallExceptionHandler(int32_t signum, SceKernelExceptionHandler handler);
#endif
#include "common/assert.h"
#include "common/virtual_buffer.h"
#include "common/logging/log.h"
// PlayStation 4
// Flag needs to be undef-ed on non PS4 since it has different semantics
// on some platforms.
#ifdef __OPENORBIS__
# ifndef MAP_SYSTEM
# define MAP_SYSTEM 0x2000
# endif
# ifndef MAP_VOID
# define MAP_VOID 0x100
# endif
// sigaction(2) has a motherfucking bug on musl where the thing isnt even properly prefixed
# undef sa_sigaction
# define sa_sigaction __sa_handler.__sa_sigaction
#endif
namespace Common {
#ifdef __OPENORBIS__
namespace Orbis {
struct Ucontext {
struct Sigset {
u64 bits[2];
} uc_sigmask;
int field1_0x10[12];
struct Mcontext {
u64 mc_onstack;
u64 mc_rdi;
u64 mc_rsi;
u64 mc_rdx;
u64 mc_rcx;
u64 mc_r8;
u64 mc_r9;
u64 mc_rax;
u64 mc_rbx;
u64 mc_rbp;
u64 mc_r10;
u64 mc_r11;
u64 mc_r12;
u64 mc_r13;
u64 mc_r14;
u64 mc_r15;
int mc_trapno;
u16 mc_fs;
u16 mc_gs;
u64 mc_addr;
int mc_flags;
u16 mc_es;
u16 mc_ds;
u64 mc_err;
u64 mc_rip;
u64 mc_cs;
u64 mc_rflags;
u64 mc_rsp;
u64 mc_ss;
u64 mc_len;
u64 mc_fpformat;
u64 mc_ownedfp;
u64 mc_lbrfrom;
u64 mc_lbrto;
u64 mc_aux1;
u64 mc_aux2;
u64 mc_fpstate[104];
u64 mc_fsbase;
u64 mc_gsbase;
u64 mc_spare[6];
} uc_mcontext;
struct Ucontext* uc_link;
struct ExStack {
void* ss_sp;
std::size_t ss_size;
int ss_flags;
int _align;
} uc_stack;
int uc_flags;
int __spare[4];
int field7_0x4f4[3];
};
}
static boost::container::static_vector<std::pair<void*, size_t>, 16> swap_regions;
extern "C" int sceKernelRemoveExceptionHandler(s32 sig_num);
static void SwapHandler(int sig, void* raw_context) {
auto& mctx = ((Orbis::Ucontext*)raw_context)->uc_mcontext;
if (std::ranges::find_if(swap_regions, [addr = mctx.mc_addr](auto const& e) {
return uintptr_t(addr) >= uintptr_t(e.first) && uintptr_t(addr) < uintptr_t(e.first) + e.second;
}) != swap_regions.end()) {
size_t const page_size = 4096;
size_t const page_mask = ~0xfff;
// should replace the existing mapping... ugh
void* aligned_addr = reinterpret_cast<void*>(uintptr_t(mctx.mc_addr) & page_mask);
void* res = mmap(aligned_addr, page_size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
ASSERT(res != MAP_FAILED);
} else {
LOG_ERROR(HW_Memory, "fault in addr {:#x} at {:#x}", mctx.mc_addr, mctx.mc_rip); // print caller address
sceKernelRemoveExceptionHandler(SIGSEGV); // to not catch the next signal
}
}
void InitSwap() noexcept {
sceKernelInstallExceptionHandler(SIGSEGV, &SwapHandler);
}
#else
void InitSwap() noexcept {}
#endif
void* AllocateMemoryPages(std::size_t size) noexcept {
#ifdef _WIN32
void* addr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
ASSERT(addr != nullptr);
#elif defined(__OPENORBIS__)
void* addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_VOID | MAP_PRIVATE, -1, 0);
ASSERT(addr != MAP_FAILED);
swap_regions.emplace_back(addr, size);
void* base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
#else
void* addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
ASSERT(addr != MAP_FAILED);
void* base = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (base == MAP_FAILED)
base = nullptr;
#endif
return addr;
ASSERT(base);
return base;
}
void FreeMemoryPages(void* addr, [[maybe_unused]] std::size_t size) noexcept {
if (!addr)
void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
if (!base)
return;
#ifdef _WIN32
VirtualFree(addr, 0, MEM_RELEASE);
ASSERT(VirtualFree(base, 0, MEM_RELEASE));
#else
int rc = munmap(addr, size);
ASSERT(rc == 0);
ASSERT(munmap(base, size) == 0);
#endif
}

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -12,7 +9,6 @@ namespace Common {
void* AllocateMemoryPages(std::size_t size) noexcept;
void FreeMemoryPages(void* base, std::size_t size) noexcept;
void InitSwap() noexcept;
template <typename T>
class VirtualBuffer final {
@@ -36,10 +32,9 @@ public:
VirtualBuffer(const VirtualBuffer&) = delete;
VirtualBuffer& operator=(const VirtualBuffer&) = delete;
VirtualBuffer(VirtualBuffer&& other) noexcept {
alloc_size = std::exchange(other.alloc_size, 0);
base_ptr = std::exchange(other.base_ptr, nullptr);
}
VirtualBuffer(VirtualBuffer&& other) noexcept
: alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr),
nullptr} {}
VirtualBuffer& operator=(VirtualBuffer&& other) noexcept {
alloc_size = std::exchange(other.alloc_size, 0);

View File

@@ -17,8 +17,6 @@ add_library(core STATIC
constants.h
core.cpp
core.h
game_settings.cpp
game_settings.h
core_timing.cpp
core_timing.h
cpu_manager.cpp
@@ -45,7 +43,11 @@ add_library(core STATIC
device_memory.cpp
device_memory.h
device_memory_manager.h
device_memory.h
device_memory_manager.h
device_memory_manager.inc
internal_network/legacy_online.cpp
internal_network/legacy_online.h
file_sys/bis_factory.cpp
file_sys/bis_factory.h
file_sys/card_image.cpp

View File

@@ -295,7 +295,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ArmDynarmic32::MakeJit(Common::PageTable* pa
// Curated optimizations
case Settings::CpuAccuracy::Auto:
config.unsafe_optimizations = true;
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OPENORBIS__)
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__DragonFly__) || defined(__NetBSD__)
config.fastmem_pointer = std::nullopt;
config.fastmem_exclusive_access = false;
#endif

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
@@ -49,6 +49,7 @@
#include "core/hle/service/services.h"
#include "core/hle/service/set/system_settings_server.h"
#include "core/hle/service/sm/sm.h"
#include "core/internal_network/legacy_online.h"
#include "core/internal_network/network.h"
#include "core/loader/loader.h"
#include "core/memory.h"
@@ -137,6 +138,14 @@ struct System::Impl {
kernel.SetMulticore(is_multicore);
cpu_manager.SetMulticore(is_multicore);
cpu_manager.SetAsyncGpu(is_async_gpu);
cpu_manager.SetMulticore(is_multicore);
cpu_manager.SetAsyncGpu(is_async_gpu);
// Start Legacy Online Service (UDP port 6000 + HTTP port 8080 for mobile app)
if (!legacy_online) {
legacy_online = std::make_unique<Network::LegacyOnlineService>();
legacy_online->Start();
}
}
void ReinitializeIfNecessary(System& system) {
@@ -293,6 +302,48 @@ struct System::Impl {
return SystemResultStatus::Success;
}
void LoadOverrides(u64 programId) const {
std::string vendor = gpu_core->Renderer().GetDeviceVendor();
LOG_INFO(Core, "GPU Vendor: {}", vendor);
// Reset all per-game flags
Settings::values.use_squashed_iterated_blend = false;
// Insert PC overrides here
#ifdef ANDROID
// Example on how to set a setting based on the program ID and vendor
if (programId == 0x010028600EBDA000 && vendor == "Mali") { // Mario 3d World
// Settings::values.example = true;
}
// Example array of program IDs
const std::array<u64, 10> example_array = {
//0xprogramId
0x0004000000033400, // Game 1
0x0004000000033500 // Game 2
// And so on
};
for (auto id : example_array) {
if (programId == id) {
// Settings::values.example = true;
break;
}
}
#endif
// Ninja Gaiden Ragebound
constexpr u64 ngr = 0x0100781020710000ULL;
if (programId == ngr) {
LOG_INFO(Core, "Enabling game specifc override: use_squashed_iterated_blend");
Settings::values.use_squashed_iterated_blend = true;
}
}
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
const std::string& filepath,
Service::AM::FrontendAppletParameters& params) {
@@ -378,8 +429,7 @@ struct System::Impl {
LOG_ERROR(Core, "Failed to find program id for ROM");
}
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
LoadOverrides(program_id);
if (auto room_member = Network::GetRoomMember().lock()) {
Network::GameInfo game_info;
game_info.name = name;
@@ -428,6 +478,12 @@ struct System::Impl {
stop_event = {};
Network::RestartSocketOperations();
if (legacy_online) {
// Keep legacy_online running for the emulator's lifetime
// legacy_online->Stop();
// legacy_online.reset();
}
if (auto room_member = Network::GetRoomMember().lock()) {
Network::GameInfo game_info{};
room_member->SendGameInfo(game_info);
@@ -517,6 +573,9 @@ struct System::Impl {
/// Network instance
Network::NetworkInstance network_instance;
/// Legacy Online Service
std::unique_ptr<Network::LegacyOnlineService> legacy_online;
/// Debugger
std::unique_ptr<Core::Debugger> debugger;

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -29,22 +26,27 @@ public:
template <typename T>
Common::PhysicalAddress GetPhysicalAddr(const T* ptr) const {
return (uintptr_t(ptr) - uintptr_t(buffer.BackingBasePointer())) + DramMemoryMap::Base;
return (reinterpret_cast<uintptr_t>(ptr) -
reinterpret_cast<uintptr_t>(buffer.BackingBasePointer())) +
DramMemoryMap::Base;
}
template <typename T>
PAddr GetRawPhysicalAddr(const T* ptr) const {
return PAddr(uintptr_t(ptr) - uintptr_t(buffer.BackingBasePointer()));
return static_cast<PAddr>(reinterpret_cast<uintptr_t>(ptr) -
reinterpret_cast<uintptr_t>(buffer.BackingBasePointer()));
}
template <typename T>
T* GetPointer(Common::PhysicalAddress addr) {
return reinterpret_cast<T*>(buffer.BackingBasePointer() + (GetInteger(addr) - DramMemoryMap::Base));
return reinterpret_cast<T*>(buffer.BackingBasePointer() +
(GetInteger(addr) - DramMemoryMap::Base));
}
template <typename T>
const T* GetPointer(Common::PhysicalAddress addr) const {
return reinterpret_cast<T*>(buffer.BackingBasePointer() + (GetInteger(addr) - DramMemoryMap::Base));
return reinterpret_cast<T*>(buffer.BackingBasePointer() +
(GetInteger(addr) - DramMemoryMap::Base));
}
template <typename T>

View File

@@ -161,7 +161,7 @@ struct DeviceMemoryManagerAllocator {
template <typename Traits>
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())},
: physical_base{reinterpret_cast<const uintptr_t>(device_memory_.buffer.BackingBasePointer())},
device_inter{nullptr}, compressed_physical_ptr(device_as_size >> Memory::YUZU_PAGEBITS),
compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() ==
Settings::MemoryLayout::Memory_4Gb

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -11,6 +11,8 @@
#include "core/core.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs/vfs.h"
#include "core/file_sys/sdmc_factory.h"
#include "core/hle/service/filesystem/filesystem.h"
namespace FileSys {
@@ -55,39 +57,114 @@ std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u
} // Anonymous namespace
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
VirtualDir save_directory_)
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
// Delete all temporary storages
// On hardware, it is expected that temporary storage be empty at first use.
dir->DeleteSubdirectoryRecursive("temp");
}
SaveDataFactory::~SaveDataFactory() = default;
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
meta.user_id, meta.system_save_data_id);
return dir->CreateDirectoryRelative(save_directory);
}
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
u64 target_program_id = meta.program_id;
// CRITICAL FIX: If the game requests Cache/Temp with ProgramID 0 (generic),
// we MUST redirect it to the actual running TitleID, otherwise it looks in '.../0000000000000000'.
if ((meta.type == SaveDataType::Cache || meta.type == SaveDataType::Temporary) && target_program_id == 0) {
target_program_id = system.GetApplicationProcessProgramID();
LOG_INFO(Service_FS, "Redirecting generic Cache request (ID 0) to active TitleID: {:016X}", target_program_id);
}
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, target_program_id,
meta.user_id, meta.system_save_data_id);
auto out = dir->GetDirectoryRelative(save_directory);
if (out == nullptr) {
LOG_WARNING(Service_FS, "Cache/Save path NOT FOUND: '{}'. Auto-create={}", save_directory, auto_create);
} else {
LOG_INFO(Service_FS, "Cache/Save path FOUND: '{}'", save_directory);
}
if (out == nullptr && (ShouldSaveDataBeAutomaticallyCreated(space, meta) && auto_create)) {
LOG_INFO(Service_FS, "Auto-creating save directory...");
return Create(space, meta);
}
if (out != nullptr) {
// Some emulators (Ryujinx) or even different firmware versions may rely on the commit
// directories /0 or /1 being present for cache or save data.
// We prioritizing /1 as it usually implies a newer commit if both exist,
// but /0 is what's commonly used by Ryujinx for cache.
// Ryujinx behavior: If 0 exists and 1 does not, copy 0 to 1.
auto dir_0 = out->GetSubdirectory("0");
auto dir_1 = out->GetSubdirectory("1");
if (dir_0) LOG_INFO(Service_FS, "Found subdirectory '0' in save path.");
if (dir_1) LOG_INFO(Service_FS, "Found subdirectory '1' in save path.");
if (dir_0 != nullptr && dir_1 == nullptr) {
// User requested removal of auto-copy 0->1 logic.
// We simply don't create/copy '1' if it's missing. We rely on fallback to '0'.
LOG_INFO(Service_FS, "Ryujinx structure detected: '0' exists, '1' missing. Skipping copy (User Request).");
// VfsRawCopyD(dir_0, dir_1); // REMOVED
}
// Check for 'Addressables' and 'Addressables2' and delete 'json.cache' if present.
// This is a specific workaround for games (e.g. Just Dance) that freeze if they find old cache metadata.
// We force them to regenerate it.
const auto CleanCache = [](VirtualDir root) {
if (root == nullptr) return;
const char* subdirs[] = {"Addressables", "Addressables2"};
for (const char* subdir_name : subdirs) {
auto subdir = root->GetSubdirectory(subdir_name);
if (subdir != nullptr) {
if (subdir->DeleteFile("json.cache")) {
LOG_INFO(Service_FS, "Deleted stale 'json.cache' in '{}'", subdir_name);
}
}
}
};
VirtualDir commit_root = nullptr;
if (dir_1 != nullptr) {
LOG_INFO(Service_FS, "Using subdirectory '1' as Commit Root.");
commit_root = dir_1;
} else if (dir_0 != nullptr) {
LOG_INFO(Service_FS, "Using subdirectory '0' as Commit Root.");
commit_root = dir_0;
} else {
LOG_INFO(Service_FS, "No '0' or '1' subdirectories found. Using parent folder as Commit Root.");
commit_root = out;
}
CleanCache(commit_root);
// Implement SD_Cache.XXXX logic for Cache Storage
if (meta.type == SaveDataType::Cache) {
const std::string sd_cache_name = fmt::format("SD_Cache.{:04X}", meta.index);
auto sd_cache_dir = commit_root->GetSubdirectory(sd_cache_name);
if (sd_cache_dir != nullptr) {
LOG_INFO(Service_FS, "Found SD_Cache directory: '{}'", sd_cache_name);
return sd_cache_dir;
} else if (auto_create) {
LOG_INFO(Service_FS, "Auto-creating SD_Cache directory: '{}'", sd_cache_name);
return commit_root->CreateSubdirectory(sd_cache_name);
} else {
LOG_WARNING(Service_FS, "SD_Cache directory '{}' not found in commit root.", sd_cache_name);
// Fallback? Or return nullptr?
// If the user strictly wants SD_Cache, we should probably return nullptr if not found/created.
// But for now, returning the commit_root might be safer for legacy compatibility IF SD_Cache isn't strictly enforced for existing saves?
// User said "SD_Cache now represents which CacheStorage it will be". This implies correct behavior is to use SD_Cache.
// If we return commit_root, it's CacheStorage_0 (conceptually) or just "everything".
// Let's assume we return nullptr if not found, to trigger creation logic or error.
return nullptr;
}
}
return commit_root;
}
return out;
}
VirtualDir SaveDataFactory::GetSaveDataSpaceDirectory(SaveDataSpaceId space) const {
return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
const auto path = GetSaveDataSpaceIdPath(space);
// Ensure the directory exists, otherwise FindAllSaves fails.
return GetOrCreateDirectoryRelative(dir, path);
// return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
}
std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
@@ -96,12 +173,12 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
return "/system/";
case SaveDataSpaceId::User:
case SaveDataSpaceId::SdUser:
case SaveDataSpaceId::Temporary: // Map into User so we can find the save/ folder
return "/user/";
case SaveDataSpaceId::Temporary:
return "/temp/";
default:
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
return "/unrecognized/"; ///< To prevent corruption when ignoring asserts.
// ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
LOG_WARNING(Service_FS, "Unrecognized SaveDataSpaceId: {:02X}, defaulting to /user/", static_cast<u8>(space));
return "/user/";
}
}
@@ -137,8 +214,9 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
case SaveDataType::Temporary:
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
// Unified Cache/Temporary Path: Always use save/cache/{TitleID}
// This simplifies user instructions and avoids UUID/Permission issues.
return fmt::format("{}save/cache/{:016X}", out, title_id);
case SaveDataType::Cache:
return fmt::format("{}save/cache/{:016X}", out, title_id);
default:
@@ -147,6 +225,40 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
}
}
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
VirtualDir save_directory_)
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
// Delete all temporary storages
// On hardware, it is expected that temporary storage be empty at first use.
dir->DeleteSubdirectoryRecursive("temp");
}
SaveDataFactory::~SaveDataFactory() = default;
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
meta.user_id, meta.system_save_data_id);
auto created_dir = dir->CreateDirectoryRelative(save_directory);
// For Cache storage, enforce the new hierarchy: .../1/SD_Cache.XXXX
if (meta.type == SaveDataType::Cache && created_dir != nullptr) {
// Ensure commit directory '1' exists
auto commit_dir = created_dir->GetSubdirectory("1");
if (commit_dir == nullptr) {
commit_dir = created_dir->CreateSubdirectory("1");
}
if (commit_dir != nullptr) {
// Create SD_Cache.XXXX
const std::string sd_cache_name = fmt::format("SD_Cache.{:04X}", meta.index);
return commit_dir->CreateSubdirectory(sd_cache_name);
}
}
return created_dir;
}
std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future) {
if (future) {
Common::UUID uuid;

View File

@@ -69,9 +69,7 @@ struct KernelCore::Impl {
global_object_list_container = std::make_unique<KAutoObjectWithListContainer>(kernel);
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
#ifndef __OPENORBIS__
is_phantom_mode_for_singlecore = false;
#endif
// Derive the initial memory layout from the emulated board
Init::InitializeSlabResourceCounts(kernel);
@@ -358,11 +356,7 @@ struct KernelCore::Impl {
application_process->Open();
}
#ifdef __OPENORBIS__
static inline u8 host_thread_id = UINT8_MAX;
#else
static inline thread_local u8 host_thread_id = UINT8_MAX;
#endif
/// Sets the host thread ID for the caller.
u32 SetHostThreadId(std::size_t core_id) {
@@ -386,14 +380,8 @@ struct KernelCore::Impl {
ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
return thread;
}};
#ifdef __OPENORBIS__
// No proper TLS yet
static KThread raw_thread{system.Kernel()};
static KThread* thread = existing_thread ? existing_thread : initialize(&raw_thread);
#else
thread_local KThread raw_thread{system.Kernel()};
thread_local KThread* thread = existing_thread ? existing_thread : initialize(&raw_thread);
#endif
return thread;
}
@@ -419,33 +407,22 @@ struct KernelCore::Impl {
return this_id;
}
#ifdef __OPENORBIS__
bool IsPhantomModeForSingleCore() const {
return true;
}
void SetIsPhantomModeForSingleCore(bool value) {}
#else
// Forces singlecore
static inline thread_local bool is_phantom_mode_for_singlecore{false};
bool IsPhantomModeForSingleCore() const {
return is_phantom_mode_for_singlecore;
}
void SetIsPhantomModeForSingleCore(bool value) {
ASSERT(!is_multicore);
is_phantom_mode_for_singlecore = value;
}
#endif
bool IsShuttingDown() const {
return is_shutting_down.load(std::memory_order_relaxed);
}
#ifdef __OPENORBIS__
// PS4 doesn't have proper TLS handling
static inline KThread* current_thread{nullptr};
#else
static inline thread_local KThread* current_thread{nullptr};
#endif
KThread* GetCurrentEmuThread() {
if (!current_thread) {

View File

@@ -686,6 +686,16 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
using EdenPath = Common::FS::EdenPath;
const auto sdmc_dir_path = Common::FS::GetEdenPath(EdenPath::SDMCDir);
const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
// If the NAND user save location doesn't exist but the SDMC contains
// Nintendo/save (common portable save structure), create a host-side
// symlink so the emulator will see those saves under the expected NAND path.
// This helps users who placed saves under `<eden>/user/sdmc/Nintendo/save/...`.
// SDMC to NAND sync logic REMOVED as per user request.
// The emulator will no longer attempt to symlink or copy "Nintendo/save" or "SD_Cache.0000"
// from SDMC to the NAND user save directory.
// Users must ensure their save/cache structure is valid within the NAND directory itself
// if that is what they intend to use, or rely on the game creating it.
const auto rw_mode = FileSys::OpenMode::ReadWrite;
auto nand_directory =

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -14,7 +14,7 @@ namespace Service::FileSystem {
ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
std::shared_ptr<SaveDataController> save_data_controller_,
FileSys::SaveDataSpaceId space)
FileSys::SaveDataSpaceId space, bool cache_only)
: ServiceFramework{system_, "ISaveDataInfoReader"}, save_data_controller{
save_data_controller_} {
static const FunctionInfo functions[] = {
@@ -22,7 +22,7 @@ ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
};
RegisterHandlers(functions);
FindAllSaves(space);
FindAllSaves(space, cache_only);
}
ISaveDataInfoReader::~ISaveDataInfoReader() = default;
@@ -63,7 +63,7 @@ Result ISaveDataInfoReader::ReadSaveDataInfo(
R_SUCCEED();
}
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only) {
FileSys::VirtualDir save_root{};
const auto result = save_data_controller->OpenSaveDataSpace(&save_root, space);
@@ -74,8 +74,12 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
for (const auto& type : save_root->GetSubdirectories()) {
if (type->GetName() == "save") {
FindNormalSaves(space, type);
} else if (space == FileSys::SaveDataSpaceId::Temporary) {
if (cache_only) {
FindCacheSaves(space, type);
} else {
FindNormalSaves(space, type);
}
} else if (space == FileSys::SaveDataSpaceId::Temporary && !cache_only) {
FindTemporaryStorageSaves(space, type);
}
}
@@ -84,6 +88,11 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
const FileSys::VirtualDir& type) {
for (const auto& save_id : type->GetSubdirectories()) {
// Skip cache directory in normal scans
if (save_id->GetName() == "cache") {
continue;
}
for (const auto& user_id : save_id->GetSubdirectories()) {
// Skip non user id subdirectories
if (user_id->GetName().size() != 0x20) {
@@ -132,6 +141,96 @@ void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
}
}
void ISaveDataInfoReader::FindCacheSaves(FileSys::SaveDataSpaceId space,
const FileSys::VirtualDir& type) {
const auto cache_dir = type->GetSubdirectory("cache");
if (cache_dir == nullptr) {
return;
}
for (const auto& title_id_dir : cache_dir->GetSubdirectories()) {
const auto title_id = stoull_be(title_id_dir->GetName());
// Simple validation: TitleID should be non-zero
if (title_id == 0) {
continue;
}
// Determine commit root (priority to "1", then "0", then self)
auto commit_root = title_id_dir->GetSubdirectory("1");
if (commit_root == nullptr) {
commit_root = title_id_dir->GetSubdirectory("0");
}
// If neither exists, we might fall back to title_id_dir itself if users put SD_Cache directly there,
// but based on SaveDataFactory we expect it inside 0 or 1.
if (commit_root == nullptr) {
// Check if SD_Cache exists directly in title_dir (legacy/fallback)
bool has_sd_cache_root = false;
for (const auto& sub : title_id_dir->GetSubdirectories()) {
if (sub->GetName().find("SD_Cache.") == 0) {
has_sd_cache_root = true;
break;
}
}
if (has_sd_cache_root) {
commit_root = title_id_dir;
} else {
continue; // No valid storage found
}
}
bool found_any = false;
for (const auto& sd_cache_dir : commit_root->GetSubdirectories()) {
const std::string& name = sd_cache_dir->GetName();
if (name.find("SD_Cache.") == 0) {
// Parse index from "SD_Cache.XXXX" (hexadecimal)
u64 index = 0;
try {
if (name.size() > 9) {
index = std::stoull(name.substr(9), nullptr, 16); // Base 16 for hex
}
} catch(...) {
continue;
}
info.emplace_back(SaveDataInfo{
0,
space,
FileSys::SaveDataType::Cache,
{}, // padding 0x6
{}, // user_id (empty array match)
0, // save_id
title_id,
sd_cache_dir->GetSize(),
static_cast<u16>(index), // Correct index with cast
FileSys::SaveDataRank::Primary,
{}, // padding 0x25
});
found_any = true;
}
}
// Fallback for legacy "flat" cache if no SD_Cache folders found?
// If the user specific structure IS enforced, maybe we don't fallback.
// But if they have existing cache without the folder, it is effectively index 0.
if (!found_any) {
// Treat the entire commit_root as index 0 (Legacy behavior)
info.emplace_back(SaveDataInfo{
0,
space,
FileSys::SaveDataType::Cache,
{}, // padding 0x6
{}, // user_id (empty array match)
0, // save_id
title_id,
commit_root->GetSize(),
0, // index 0
FileSys::SaveDataRank::Primary,
{}, // padding 0x25
});
}
}
}
void ISaveDataInfoReader::FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space,
const FileSys::VirtualDir& type) {
for (const auto& user_id : type->GetSubdirectories()) {

View File

@@ -16,7 +16,7 @@ class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
public:
explicit ISaveDataInfoReader(Core::System& system_,
std::shared_ptr<SaveDataController> save_data_controller_,
FileSys::SaveDataSpaceId space);
FileSys::SaveDataSpaceId space, bool cache_only = false);
~ISaveDataInfoReader() override;
struct SaveDataInfo {
@@ -38,8 +38,9 @@ public:
OutArray<SaveDataInfo, BufferAttr_HipcMapAlias> out_entries);
private:
void FindAllSaves(FileSys::SaveDataSpaceId space);
void FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only);
void FindNormalSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
void FindCacheSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
void FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
std::shared_ptr<SaveDataController> save_data_controller;

View File

@@ -353,10 +353,10 @@ Result FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(
Result FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(
OutInterface<ISaveDataInfoReader> out_interface) {
LOG_WARNING(Service_FS, "(STUBBED) called");
LOG_DEBUG(Service_FS, "called");
*out_interface = std::make_shared<ISaveDataInfoReader>(system, save_data_controller,
FileSys::SaveDataSpaceId::Temporary);
FileSys::SaveDataSpaceId::User, true);
R_SUCCEED();
}

View File

@@ -42,8 +42,16 @@ bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& co
const auto& message_header = context.GetDomainMessageHeader();
const auto object_id = message_header.object_id;
// Some games send magic numbers in the object_id field, which indicates
// this is not actually a proper domain request
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
if (object_id == sfci_magic || object_id == sfco_magic) {
// This is not a domain request, treat it as a regular session request
return session_handler != nullptr;
}
if (object_id > DomainHandlerCount()) {
LOG_CRITICAL(IPC, "object_id {} is too big!", object_id);
return false;
}
return !DomainHandler(object_id - 1).expired();
@@ -91,7 +99,18 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se
// If there is a DomainMessageHeader, then this is CommandType "Request"
const auto& domain_message_header = context.GetDomainMessageHeader();
const u32 object_id{domain_message_header.object_id};
const u32 object_id = domain_message_header.object_id;
// Some games send magic numbers in the object_id field
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
if (object_id == sfci_magic || object_id == sfco_magic) {
// This is not a domain request, handle as regular session request
LOG_DEBUG(IPC, "Detected magic number 0x{:08X} in object_id, treating as regular session request. Command={}",
object_id, context.GetCommand());
return session_handler->HandleSyncRequest(*server_session, context);
}
switch (domain_message_header.command) {
case IPC::DomainMessageHeader::CommandType::SendMessage:
if (object_id > this->DomainHandlerCount()) {
@@ -200,7 +219,17 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
// If this is an incoming message, only CommandType "Request" has a domain header
// All outgoing domain messages have the domain header, if only incoming has it
if (incoming || domain_message_header) {
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
// Check if the next value is actually a magic number (SFCI/SFCO)
// Some games send these in the object_id field, indicating this is not a domain request
const u32 possible_object_id = src_cmdbuf[rp.GetCurrentOffset() + 1]; // object_id is second field
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
if (possible_object_id != sfci_magic && possible_object_id != sfco_magic) {
// This is a proper domain request
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
}
// If it's a magic number, skip reading the domain header entirely
} else {
if (GetManager()->IsDomain()) {
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
@@ -220,9 +249,15 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
}
if (incoming) {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
// Only check magic if we have a valid data payload header
// When domain header is skipped (SFCI in object_id), the structure is different
if (domain_message_header) {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
}
} else {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
if (domain_message_header) {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
}
}
}

View File

@@ -66,7 +66,9 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
void nvhost_nvdec::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
LOG_INFO(Service_NVDRV, "NVDEC video stream started");
system.SetNVDECActive(true);
if (!system.GetNVDECActive()) {
system.SetNVDECActive(true);
}
sessions[fd] = session_id;
host1x.StartDevice(fd, Tegra::Host1x::ChannelType::NvDec, channel_syncpoint);
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -22,6 +22,9 @@
#include "core/internal_network/sockets.h"
#include "network/network.h"
#include <common/settings.h>
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/string_util.h"
using Common::Expected;
using Common::Unexpected;
@@ -315,7 +318,15 @@ void BSD::Fcntl(HLERequestContext& ctx) {
const u32 cmd = rp.Pop<u32>();
const s32 arg = rp.Pop<s32>();
LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
// Log with more detail to understand non-blocking configuration
if (cmd == 4) { // SETFL
bool is_nonblock = (arg & 0x800) != 0; // O_NONBLOCK
LOG_INFO(Service, "Fcntl SETFL fd={} arg={} (non-blocking={})", fd, arg, is_nonblock);
} else if (cmd == 3) { // GETFL
LOG_INFO(Service, "Fcntl GETFL fd={}", fd);
} else {
LOG_INFO(Service, "Fcntl fd={} cmd={} arg={}", fd, cmd, arg);
}
const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg);
@@ -802,33 +813,40 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8
switch (optname) {
case OptName::REUSEADDR:
LOG_INFO(Service, "SetSockOpt fd={} REUSEADDR={}", fd, value);
if (value != 0 && value != 1) {
LOG_WARNING(Service, "Invalid REUSEADDR value: {}", value);
return Errno::INVAL;
}
return Translate(socket->SetReuseAddr(value != 0));
case OptName::KEEPALIVE:
LOG_INFO(Service, "SetSockOpt fd={} KEEPALIVE={}", fd, value);
if (value != 0 && value != 1) {
LOG_WARNING(Service, "Invalid KEEPALIVE value: {}", value);
return Errno::INVAL;
}
return Translate(socket->SetKeepAlive(value != 0));
case OptName::BROADCAST:
LOG_INFO(Service, "SetSockOpt fd={} BROADCAST={}", fd, value);
if (value != 0 && value != 1) {
LOG_WARNING(Service, "Invalid BROADCAST value: {}", value);
return Errno::INVAL;
}
return Translate(socket->SetBroadcast(value != 0));
case OptName::SNDBUF:
LOG_INFO(Service, "SetSockOpt fd={} SNDBUF={}", fd, value);
return Translate(socket->SetSndBuf(value));
case OptName::RCVBUF:
LOG_INFO(Service, "SetSockOpt fd={} RCVBUF={}", fd, value);
return Translate(socket->SetRcvBuf(value));
case OptName::SNDTIMEO:
LOG_INFO(Service, "SetSockOpt fd={} SNDTIMEO={}", fd, value);
return Translate(socket->SetSndTimeo(value));
case OptName::RCVTIMEO:
LOG_INFO(Service, "SetSockOpt fd={} RCVTIMEO={}", fd, value);
return Translate(socket->SetRcvTimeo(value));
case OptName::NOSIGPIPE:
LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value);
LOG_INFO(Service, "SetSockOpt fd={} NOSIGPIPE={}", fd, value);
return Errno::SUCCESS;
default:
LOG_WARNING(Service, "(STUBBED) Unimplemented optname={} (0x{:x}), returning INVAL",
@@ -919,12 +937,134 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess
return {ret, bsd_errno};
}
std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const u8> message) {
if (!IsFileDescriptorValid(fd)) {
return {-1, Errno::BADF};
std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const u8> message) {
if (!IsFileDescriptorValid(fd)) {
return {-1, Errno::BADF};
}
const size_t original_size = message.size();
// Inspect for Authorization header to inject custom token (HTTP/Localhost support)
const std::string_view data_view(reinterpret_cast<const char*>(message.data()),
message.size());
// Optimized check: only look if it looks like an HTTP request with auth
// We do a case-insensitive search for the specific header pattern
std::string request_str(data_view);
std::string request_lower = Common::ToLower(request_str);
size_t auth_pos = request_lower.find("authorization: switch t=");
if (auth_pos != std::string::npos) {
LOG_INFO(Service,
"BSDJ: Found 'Authorization: switch t=' in SendImpl. Injecting custom auth.");
const auto auth_file_path =
Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir) / "jdlo_auth.ini";
Common::FS::IOFile auth_file(auth_file_path, Common::FS::FileAccessMode::Read,
Common::FS::FileType::TextFile);
if (auth_file.IsOpen()) {
std::vector<u8> file_content_vec(auth_file.GetSize());
if (auth_file.Read(file_content_vec) == file_content_vec.size()) {
std::string encoded_content(file_content_vec.begin(), file_content_vec.end());
// Simple Base64 Decoder
auto DecodeBase64 = [](std::string_view input) -> std::string {
static const int T[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};
std::string out;
int val = 0, valb = -8;
for (unsigned char c : input) {
if (T[c] == -1)
break;
val = (val << 6) + T[c];
valb += 6;
if (valb >= 0) {
out.push_back(char((val >> valb) & 0xFF));
valb -= 8;
}
}
return out;
};
// Decode the INI content
std::string file_content = DecodeBase64(encoded_content);
// Find TickedId
std::string auth_token;
static constexpr std::string_view KeyName = "TickedId=";
size_t key_pos = file_content.find(KeyName);
if (key_pos != std::string::npos) {
size_t value_start = key_pos + KeyName.size();
size_t value_end = file_content.find_first_of("\r\n", value_start);
if (value_end == std::string::npos) {
value_end = file_content.size();
}
auth_token = file_content.substr(value_start, value_end - value_start);
}
if (!auth_token.empty()) {
// Ensure the token has the correct prefix "uplaypc_v1 t="
if (auth_token.find("uplaypc_v1 t=") == std::string::npos) {
auth_token = "uplaypc_v1 t=" + auth_token;
}
// Find end of the line
size_t end_pos = request_str.find("\r\n", auth_pos);
if (end_pos != std::string::npos) {
bool is_header_start = (auth_pos == 0) || (request_str[auth_pos - 1] == '\n');
if (is_header_start) {
LOG_INFO(Service, "BSDJ: Injecting token (TickedId): {}...",
auth_token.substr(0, 20));
std::string new_header = "Authorization: " + auth_token;
request_str.replace(auth_pos, end_pos - auth_pos, new_header);
// Send the MODIFIED message
std::span<const u8> new_message(
reinterpret_cast<const u8*>(request_str.data()),
request_str.size());
auto result = Translate(
file_descriptors[fd]->socket->Send(new_message, flags));
LOG_CRITICAL(
Service,
"SendImpl (Modified) real_sent={} original_size={} errno={}",
result.first, original_size, static_cast<int>(result.second));
// Mask the return size: If we successfully sent the larger buffer,
// tell the guest we sent exactly what they asked for.
if (result.first > 0 && result.second == Errno::SUCCESS) {
// Return original size to prevent guest confusion
return {static_cast<s32>(original_size), Errno::SUCCESS};
}
return result;
}
}
}
}
} else {
// LOG_WARNING(Service, "jdlo_auth.ini not found");
}
}
LOG_CRITICAL(Service, "SendImpl called: fd={}, size={} bytes, flags={}", fd, message.size(),
flags);
auto result = Translate(file_descriptors[fd]->socket->Send(message, flags));
LOG_CRITICAL(Service, "SendImpl result: sent={} bytes, errno={}", result.first,
static_cast<int>(result.second));
return result;
}
return Translate(file_descriptors[fd]->socket->Send(message, flags));
}
std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, std::span<const u8> message,
std::span<const u8> addr) {

View File

@@ -60,12 +60,29 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
RegisterHandlers(functions);
}
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
};
static std::string GetRedirectedHost(const std::string& host) {
for (const auto& rule : redirectionRules) {
if (host.find(rule.first) != std::string::npos) {
LOG_INFO(Service, "Redirecting NSD host '{}' to '{}'", host, rule.second);
return rule.second;
}
}
return host;
}
static std::string ResolveImpl(const std::string& fqdn_in) {
// The real implementation makes various substitutions.
// For now we just return the string as-is, which is good enough when not
// connecting to real Nintendo servers.
LOG_WARNING(Service, "(STUBBED) called, fqdn_in={}", fqdn_in);
return fqdn_in;
return GetRedirectedHost(fqdn_in);
}
static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) {

View File

@@ -29,13 +29,13 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"
{4, nullptr, "GetHostStringErrorRequest"},
{5, &SFDNSRES::GetGaiStringErrorRequest, "GetGaiStringErrorRequest"},
{6, &SFDNSRES::GetAddrInfoRequest, "GetAddrInfoRequest"},
{7, nullptr, "GetNameInfoRequest"},
{8, nullptr, "RequestCancelHandleRequest"},
{7, &SFDNSRES::GetNameInfoRequest, "GetNameInfoRequest"},
{8, &SFDNSRES::RequestCancelHandleRequest, "RequestCancelHandleRequest"},
{9, nullptr, "CancelRequest"},
{10, &SFDNSRES::GetHostByNameRequestWithOptions, "GetHostByNameRequestWithOptions"},
{11, nullptr, "GetHostByAddrRequestWithOptions"},
{12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"},
{13, nullptr, "GetNameInfoRequestWithOptions"},
{13, &SFDNSRES::GetNameInfoRequestWithOptions, "GetNameInfoRequestWithOptions"},
{14, &SFDNSRES::ResolverSetOptionRequest, "ResolverSetOptionRequest"},
{15, nullptr, "ResolverGetOptionRequest"},
};
@@ -66,6 +66,21 @@ static bool IsBlockedHost(const std::string& host) {
[&host](const std::string& domain) { return host.find(domain) != std::string::npos; });
}
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
};
static std::string GetRedirectedHost(const std::string& host) {
for (const auto& rule : redirectionRules) {
if (host.find(rule.first) != std::string::npos) {
LOG_INFO(Service, "Redirecting host '{}' to '{}'", host, rule.second);
return rule.second;
}
}
return host;
}
static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) {
// These combinations have been verified on console (but are not
// exhaustive).
@@ -163,7 +178,8 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id);
const auto host_buffer = ctx.ReadBuffer(0);
const std::string host = Common::StringFromBuffer(host_buffer);
std::string host = Common::StringFromBuffer(host_buffer);
host = GetRedirectedHost(host);
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
// Prevent resolution of Nintendo servers
@@ -281,7 +297,8 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
// before looking up.
const auto host_buffer = ctx.ReadBuffer(0);
const std::string host = Common::StringFromBuffer(host_buffer);
std::string host = Common::StringFromBuffer(host_buffer);
host = GetRedirectedHost(host);
// Prevent resolution of Nintendo servers
if (IsBlockedHost(host)) {
@@ -364,6 +381,120 @@ void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) {
});
}
void SFDNSRES::GetNameInfoRequest(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto addr_in = rp.PopRaw<SockAddrIn>();
const u32 flags = rp.Pop<u32>();
const u32 cancel_handle = rp.Pop<u32>();
const u64 process_id = rp.Pop<u64>();
LOG_DEBUG(Service, "called. flags={}, cancel_handle={}, process_id={}", flags, cancel_handle,
process_id);
struct OutputParameters {
u32 data_size;
GetAddrInfoError gai_error;
NetDbError netdb_error;
Errno bsd_errno;
};
static_assert(sizeof(OutputParameters) == 0x10);
const auto res = Network::GetNameInfo(Translate(addr_in));
if (res.second != 0) {
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
const auto service_error = Translate(network_error);
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
rb.PushRaw(OutputParameters{
.data_size = 0,
.gai_error = service_error,
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
});
return;
}
const std::string& host = res.first;
const u32 data_size = static_cast<u32>(host.size() + 1);
ctx.WriteBuffer(host.data(), data_size, 0);
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
rb.PushRaw(OutputParameters{
.data_size = data_size,
.gai_error = GetAddrInfoError::SUCCESS,
.netdb_error = NetDbError::Success,
.bsd_errno = Errno::SUCCESS,
});
}
void SFDNSRES::GetNameInfoRequestWithOptions(HLERequestContext& ctx) {
struct InputParameters {
u32 flags;
u32 interface_index;
u64 process_id;
u32 padding; // 0x14 + 4 = 0x18? No. 0x14 aligned to 8 bytes?
// Wait, sizeof(InputParameters) == 0x14.
};
// Derived from partial snippets:
// u32 flags, u32 interface_index, u64 process_id.
// 4 + 4 + 8 = 16 bytes (0x10).
// The previous prompt had static_assert size 0x14.
// Maybe a u32 padding?
// Let's rely on standard layout.
// I will use manual popping for safety if struct definition is unknown.
IPC::RequestParser rp{ctx};
const auto addr_in = rp.PopRaw<SockAddrIn>();
const u32 flags = rp.Pop<u32>();
const u32 interface_index = rp.Pop<u32>();
const u64 process_id = rp.Pop<u64>();
(void)flags;
(void)interface_index;
(void)process_id;
// If there was padding, it might be implicitly popped or ignored.
struct OutputParameters {
u32 data_size;
GetAddrInfoError gai_error;
NetDbError netdb_error;
Errno bsd_errno;
};
static_assert(sizeof(OutputParameters) == 0x10);
const auto res = Network::GetNameInfo(Translate(addr_in));
if (res.second != 0) {
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
const auto service_error = Translate(network_error);
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
rb.PushRaw(OutputParameters{
.data_size = 0,
.gai_error = service_error,
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
});
return;
}
const std::string& host = res.first;
const u32 data_size = static_cast<u32>(host.size() + 1);
ctx.WriteBuffer(host.data(), data_size, 0);
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
rb.PushRaw(OutputParameters{
.data_size = data_size,
.gai_error = GetAddrInfoError::SUCCESS,
.netdb_error = NetDbError::Success,
.bsd_errno = Errno::SUCCESS,
});
}
void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
@@ -372,4 +503,24 @@ void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
rb.Push<s32>(0); // bsd errno
}
void SFDNSRES::RequestCancelHandleRequest(HLERequestContext& ctx) {
// This is just a stub for now.
// In a real implementation this would likely cancel a pending request represented by the handle.
LOG_WARNING(Service, "(STUBBED) called");
struct InputParameters {
u32 cancel_handle;
};
IPC::RequestParser rp{ctx};
auto input = rp.PopRaw<InputParameters>();
LOG_DEBUG(Service, "cancel_handle={}", input.cancel_handle);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.Push<u32>(0); // cancel_handle (response seems to echo it or return a new one? usually just an error code or unrelated)
// Actually based on typical patterns, it probably returns an error code (bsd_errno).
rb.Push<s32>(0); // bsd_errno
}
} // namespace Service::Sockets

View File

@@ -22,7 +22,10 @@ private:
void GetHostByNameRequestWithOptions(HLERequestContext& ctx);
void GetAddrInfoRequest(HLERequestContext& ctx);
void GetAddrInfoRequestWithOptions(HLERequestContext& ctx);
void GetNameInfoRequest(HLERequestContext& ctx);
void GetNameInfoRequestWithOptions(HLERequestContext& ctx);
void ResolverSetOptionRequest(HLERequestContext& ctx);
void RequestCancelHandleRequest(HLERequestContext& ctx);
};
} // namespace Service::Sockets

View File

@@ -16,10 +16,12 @@ enum class Errno : u32 {
SUCCESS = 0,
BADF = 9,
AGAIN = 11,
ACCES = 13,
INVAL = 22,
MFILE = 24,
PIPE = 32,
MSGSIZE = 90,
ADDRINUSE = 98,
CONNABORTED = 103,
CONNRESET = 104,
NOTCONN = 107,

View File

@@ -37,6 +37,10 @@ Errno Translate(Network::Errno value) {
return Errno::CONNRESET;
case Network::Errno::INPROGRESS:
return Errno::INPROGRESS;
case Network::Errno::ACCES:
return Errno::ACCES;
case Network::Errno::ADDRINUSE:
return Errno::ADDRINUSE;
default:
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
return Errno::SUCCESS;
@@ -261,7 +265,9 @@ PollEvents Translate(Network::PollEvents flags) {
Network::SockAddrIn Translate(SockAddrIn value) {
// Note: 6 is incorrect, but can be passed by homebrew (because libnx sets
// sin_len to 6 when deserializing getaddrinfo results).
ASSERT(value.len == 0 || value.len == sizeof(value) || value.len == 6);
if (value.len != 0 && value.len != sizeof(value) && value.len != 6) {
LOG_WARNING(Service, "Unexpected SockAddrIn length={}", value.len);
}
return {
.family = Translate(static_cast<Domain>(value.family)),

View File

@@ -117,15 +117,20 @@ public:
RegisterHandlers(functions);
shared_data->connection_count++;
LOG_CRITICAL(Service_SSL, "ISslConnection created! Total connections: {}", shared_data->connection_count);
}
~ISslConnection() {
shared_data->connection_count--;
if (fd_to_close.has_value()) {
const s32 fd = *fd_to_close;
if (!do_not_close_socket) {
LOG_ERROR(Service_SSL,
"do_not_close_socket was changed after setting socket; is this right?");
if (do_not_close_socket) {
// If we aren't supposed to close the socket, but we have an fd_to_close,
// that means the configuration changed after we took ownership.
// This is weird but we should probably honor the flag.
// However, the original valid logic seemed to imply we duped the socket
// and should close our dup... but let's stick to what the flag says.
LOG_INFO(Service_SSL, "do_not_close_socket is true, skipping close of fd {}", fd);
} else {
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
if (bsd) {
@@ -269,16 +274,17 @@ private:
}
Result PendingImpl(s32* out_pending) {
LOG_WARNING(Service_SSL, "(STUBBED) called.");
*out_pending = 0;
return ResultSuccess;
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
return backend->Pending(out_pending);
}
void SetSocketDescriptor(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const s32 in_fd = rp.Pop<s32>();
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor called with fd={}", in_fd);
s32 out_fd{-1};
const Result res = SetSocketDescriptorImpl(&out_fd, in_fd);
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor result: res={}, out_fd={}", res.raw, out_fd);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(res);
rb.Push<s32>(out_fd);
@@ -308,7 +314,9 @@ private:
}
void DoHandshake(HLERequestContext& ctx) {
LOG_INFO(Service_SSL, "DoHandshake called, socket={}", socket != nullptr);
const Result res = DoHandshakeImpl();
LOG_INFO(Service_SSL, "DoHandshake result: {}, did_handshake={}", res.raw, did_handshake);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res);
}

View File

@@ -38,6 +38,7 @@ public:
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
virtual Result Pending(s32* out_pending) = 0;
};
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);

View File

@@ -12,6 +12,7 @@
#include <openssl/x509.h>
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/string_util.h"
@@ -48,28 +49,32 @@ 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} {}
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(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(); }
}
~scope_exit() {
if (execute_on_destruction) {
this->exit_function();
}
}
void release() { this->execute_on_destruction = false; }
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;
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;
std::function<void(void)> exit_function;
bool execute_on_destruction;
};
inline X509_STORE *CreateCaCertStore(const char *ca_cert,
@@ -115,6 +120,22 @@ inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
}
#endif
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
};
static std::string GetRedirectedHost(const std::string& host) {
for (const auto& rule : redirectionRules) {
if (host.find(rule.first) != std::string::npos) {
LOG_INFO(Service_SSL, "Redirecting SSL host '{}' to '{}'", host, rule.second);
return rule.second;
}
}
return host;
}
} // namespace
class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend {
@@ -157,7 +178,8 @@ public:
socket = std::move(socket_in);
}
Result SetHostName(const std::string& hostname) override {
Result SetHostName(const std::string& hostname_in) override {
const std::string hostname = GetRedirectedHost(hostname_in);
if (!SSL_set1_host(ssl, hostname.c_str())) { // hostname for verification
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
return CheckOpenSSLErrors();
@@ -195,6 +217,123 @@ public:
}
Result Write(size_t* out_size, std::span<const u8> data) override {
const size_t original_size = data.size();
const std::string_view data_view(reinterpret_cast<const char*>(data.data()), data.size());
// Log all POST requests for debugging
if (data_view.size() > 5 && data_view.substr(0, 5) == "POST ") {
LOG_INFO(Service_SSL, "Intercepted POST request. Length: {}. Preview: {}", data.size(), data_view.substr(0, std::min(data_view.size(), size_t(200))));
}
std::string request_str(data_view);
std::string request_lower = Common::ToLower(request_str);
// Look for the specific authorization header value we want to replace: "switch t="
// We match "authorization: switch t=" case-insensitively
size_t auth_pos = request_lower.find("authorization: switch t=");
if (auth_pos != std::string::npos) {
LOG_INFO(Service_SSL, "Found 'Authorization: switch t=' header. Injecting custom auth.");
const auto auth_file_path = Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir) / "jdlo_auth.ini";
Common::FS::IOFile auth_file(auth_file_path, Common::FS::FileAccessMode::Read, Common::FS::FileType::TextFile);
if (auth_file.IsOpen()) {
std::vector<u8> file_content_vec(auth_file.GetSize());
if (auth_file.Read(file_content_vec) == file_content_vec.size()) {
std::string encoded_content(file_content_vec.begin(), file_content_vec.end());
// Simple Base64 Decoder
auto DecodeBase64 = [](std::string_view input) -> std::string {
static const int T[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};
std::string out;
int val = 0, valb = -8;
for (unsigned char c : input) {
if (T[c] == -1)
break;
val = (val << 6) + T[c];
valb += 6;
if (valb >= 0) {
out.push_back(char((val >> valb) & 0xFF));
valb -= 8;
}
}
return out;
};
// Decode the INI content
std::string file_content = DecodeBase64(encoded_content);
// Find TickedId
std::string auth_token;
static constexpr std::string_view KeyName = "TickedId=";
size_t key_pos = file_content.find(KeyName);
if (key_pos != std::string::npos) {
size_t value_start = key_pos + KeyName.size();
size_t value_end = file_content.find_first_of("\r\n", value_start);
if (value_end == std::string::npos) {
value_end = file_content.size();
}
auth_token = file_content.substr(value_start, value_end - value_start);
} else {
// Fallback: If not found, maybe it wasn't base64 or format is different?
// Try using raw content if decode failed to produce readable key?
// Actually, let's just stick to the decoded content.
}
if (!auth_token.empty()) {
// Ensure the token has the correct prefix "uplaypc_v1 t="
if (auth_token.find("uplaypc_v1 t=") == std::string::npos) {
auth_token = "uplaypc_v1 t=" + auth_token;
}
LOG_INFO(Service_SSL,
"Injecting custom Authorization from jdlo_auth.ini (decoded "
"TickedId): {}...",
auth_token.substr(0, 20));
// Find existing Authorization header position case-insensitively
size_t header_pos = request_lower.find("\r\nauthorization: ");
if (header_pos != std::string::npos) {
size_t end_pos = request_str.find("\r\n", header_pos + 2);
if (end_pos != std::string::npos) {
LOG_INFO(Service_SSL, "Replacing existing Authorization header.");
request_str.replace(header_pos, end_pos - header_pos,
"\r\nAuthorization: " + auth_token);
}
} else {
LOG_INFO(Service_SSL, "Appending new Authorization header.");
size_t body_pos = request_str.find("\r\n\r\n");
if (body_pos != std::string::npos) {
request_str.insert(body_pos, "\r\nAuthorization: " + auth_token);
}
}
const int ret = SSL_write_ex(ssl, request_str.data(), request_str.size(), out_size);
if (ret == 1) { // Success
*out_size = original_size;
}
return HandleReturn("SSL_write_ex", out_size, ret);
}
} else {
LOG_ERROR(Service_SSL, "Failed to read jdlo_auth.ini content");
}
} else {
LOG_WARNING(Service_SSL, "jdlo_auth.ini not found at {}",
Common::FS::PathToUTF8String(auth_file_path));
}
}
const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size);
return HandleReturn("SSL_write_ex", out_size, ret);
}
@@ -247,6 +386,28 @@ public:
return ResultSuccess;
}
Result Pending(s32* out_pending) override {
if (!ssl) {
return ResultInternalError;
}
int pending = SSL_pending(ssl);
if (pending > 0) {
*out_pending = pending;
return ResultSuccess;
}
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
std::vector<Network::PollFD> poll_fds{poll_fd};
auto [count, err] = Network::Poll(poll_fds, 0);
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
*out_pending = 1;
} else {
*out_pending = 0;
}
return ResultSuccess;
}
~SSLConnectionBackendOpenSSL() {
// this is null-tolerant:
SSL_free(ssl);

View File

@@ -489,6 +489,27 @@ public:
return ResultSuccess;
}
Result Pending(s32* out_pending) override {
*out_pending = static_cast<s32>(cleartext_read_buf.size());
if (*out_pending > 0) {
return ResultSuccess;
}
if (!ciphertext_read_buf.empty()) {
*out_pending = 1;
return ResultSuccess;
}
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
std::vector<Network::PollFD> poll_fds{poll_fd};
auto [count, err] = Network::Poll(poll_fds, 0);
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
*out_pending = 1;
}
return ResultSuccess;
}
~SSLConnectionBackendSchannel() {
if (handshake_state != HandshakeState::Initial) {
DeleteSecurityContext(&ctxt);

View File

@@ -149,6 +149,26 @@ public:
return ResultSuccess;
}
Result Pending(s32* out_pending) override {
size_t bufferSize = 0;
OSStatus status = SSLGetBufferedReadSize(context, &bufferSize);
if (status == 0 && bufferSize > 0) {
*out_pending = static_cast<s32>(bufferSize);
return ResultSuccess;
}
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
std::vector<Network::PollFD> poll_fds{poll_fd};
auto [count, err] = Network::Poll(poll_fds, 0);
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
*out_pending = 1;
} else {
*out_pending = 0;
}
return ResultSuccess;
}
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
return ReadOrWriteCallback(connection, data, dataLength, true);
}

View File

@@ -7,7 +7,7 @@
#include "core/internal_network/network_interface.h"
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
#include <wlanapi.h>
#ifdef _MSC_VER

View File

@@ -0,0 +1,701 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/internal_network/legacy_online.h"
#include <cstring>
#include <iostream>
#include <sstream>
#include <regex>
#include <iomanip>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
// For SHA1 and Base64
#include "common/logging/log.h"
namespace Network {
// Simple SHA1 implementation for WebSocket handshake
namespace {
class SHA1 {
public:
SHA1() { reset(); }
void update(const uint8_t* data, size_t len) {
while (len--) {
buffer[buffer_size++] = *data++;
if (buffer_size == 64) {
process_block();
buffer_size = 0;
}
total_bits += 8;
}
}
void update(const std::string& str) {
update(reinterpret_cast<const uint8_t*>(str.data()), str.size());
}
std::array<uint8_t, 20> finalize() {
// Padding
buffer[buffer_size++] = 0x80;
while (buffer_size != 56) {
if (buffer_size == 64) {
process_block();
buffer_size = 0;
}
buffer[buffer_size++] = 0;
}
// Append length in bits
for (int i = 7; i >= 0; --i) {
buffer[buffer_size++] = static_cast<uint8_t>(total_bits >> (i * 8));
}
process_block();
std::array<uint8_t, 20> result;
for (int i = 0; i < 5; ++i) {
result[i*4+0] = static_cast<uint8_t>(h[i] >> 24);
result[i*4+1] = static_cast<uint8_t>(h[i] >> 16);
result[i*4+2] = static_cast<uint8_t>(h[i] >> 8);
result[i*4+3] = static_cast<uint8_t>(h[i]);
}
return result;
}
private:
void reset() {
h[0] = 0x67452301;
h[1] = 0xEFCDAB89;
h[2] = 0x98BADCFE;
h[3] = 0x10325476;
h[4] = 0xC3D2E1F0;
buffer_size = 0;
total_bits = 0;
}
void process_block() {
uint32_t w[80];
for (int i = 0; i < 16; ++i) {
w[i] = (buffer[i*4+0] << 24) | (buffer[i*4+1] << 16) |
(buffer[i*4+2] << 8) | buffer[i*4+3];
}
for (int i = 16; i < 80; ++i) {
uint32_t t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16];
w[i] = (t << 1) | (t >> 31);
}
uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
for (int i = 0; i < 80; ++i) {
uint32_t f, k;
if (i < 20) {
f = (b & c) | ((~b) & d);
k = 0x5A827999;
} else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32_t temp = ((a << 5) | (a >> 27)) + f + e + k + w[i];
e = d;
d = c;
c = (b << 30) | (b >> 2);
b = a;
a = temp;
}
h[0] += a;
h[1] += b;
h[2] += c;
h[3] += d;
h[4] += e;
}
uint32_t h[5];
uint8_t buffer[64];
size_t buffer_size;
uint64_t total_bits;
};
std::string base64_encode(const uint8_t* data, size_t len) {
static const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string result;
result.reserve((len + 2) / 3 * 4);
for (size_t i = 0; i < len; i += 3) {
uint32_t n = static_cast<uint32_t>(data[i]) << 16;
if (i + 1 < len) n |= static_cast<uint32_t>(data[i + 1]) << 8;
if (i + 2 < len) n |= static_cast<uint32_t>(data[i + 2]);
result += chars[(n >> 18) & 0x3F];
result += chars[(n >> 12) & 0x3F];
result += (i + 1 < len) ? chars[(n >> 6) & 0x3F] : '=';
result += (i + 2 < len) ? chars[n & 0x3F] : '=';
}
return result;
}
std::string compute_websocket_accept(const std::string& key) {
// WebSocket magic GUID
const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
std::string combined = key + magic;
SHA1 sha1;
sha1.update(combined);
auto hash = sha1.finalize();
return base64_encode(hash.data(), hash.size());
}
} // anonymous namespace
LegacyOnlineService::LegacyOnlineService() {
#ifdef _WIN32
WSADATA wsa_data;
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
LOG_ERROR(Network, "WSAStartup failed with error: {}", WSAGetLastError());
winsock_initialized = false;
} else {
winsock_initialized = true;
}
#endif
}
LegacyOnlineService::~LegacyOnlineService() {
Stop();
#ifdef _WIN32
if (winsock_initialized) {
WSACleanup();
}
#endif
}
void LegacyOnlineService::Start() {
if (is_running) {
return;
}
#ifdef _WIN32
if (!winsock_initialized) {
LOG_ERROR(Network, "Cannot start Legacy Online Service: Winsock not initialized");
return;
}
#endif
is_running = true;
udp_worker_thread = std::thread(&LegacyOnlineService::UdpServerLoop, this);
http_worker_thread = std::thread(&LegacyOnlineService::HttpServerLoop, this);
}
void LegacyOnlineService::Stop() {
if (!is_running) {
return;
}
is_running = false;
if (udp_socket_fd != ~0ULL) {
#ifdef _WIN32
closesocket(static_cast<SOCKET>(udp_socket_fd));
#else
close(static_cast<int>(udp_socket_fd));
#endif
udp_socket_fd = ~0ULL;
}
if (http_socket_fd != ~0ULL) {
#ifdef _WIN32
closesocket(static_cast<SOCKET>(http_socket_fd));
#else
close(static_cast<int>(http_socket_fd));
#endif
http_socket_fd = ~0ULL;
}
if (udp_worker_thread.joinable()) {
udp_worker_thread.join();
}
if (http_worker_thread.joinable()) {
http_worker_thread.join();
}
}
void LegacyOnlineService::UdpServerLoop() {
LOG_INFO(Network, "Starting Legacy Online UDP Server on port {}", UDP_PORT);
auto s = socket(AF_INET, SOCK_DGRAM, 0);
#ifdef _WIN32
if (s == INVALID_SOCKET) {
#else
if (s == -1) {
#endif
LOG_ERROR(Network, "Failed to create UDP socket");
return;
}
udp_socket_fd = static_cast<uintptr_t>(s);
int opt = 1;
#ifdef _WIN32
setsockopt(static_cast<SOCKET>(udp_socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
#else
setsockopt(static_cast<int>(udp_socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(UDP_PORT);
int res = -1;
#ifdef _WIN32
res = bind(static_cast<SOCKET>(udp_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
#else
res = bind(static_cast<int>(udp_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
#endif
if (res < 0) {
#ifdef _WIN32
LOG_ERROR(Network, "Failed to bind UDP to port {}: {}", UDP_PORT, WSAGetLastError());
closesocket(static_cast<SOCKET>(udp_socket_fd));
#else
LOG_ERROR(Network, "Failed to bind UDP to port {}: {}", UDP_PORT, strerror(errno));
close(static_cast<int>(udp_socket_fd));
#endif
udp_socket_fd = ~0ULL;
return;
}
LOG_INFO(Network, "Legacy Online UDP Server waiting for messages...");
char buffer[2048];
while (is_running) {
sockaddr_in client_addr{};
#ifdef _WIN32
int client_len = sizeof(client_addr);
int len = recvfrom(static_cast<SOCKET>(udp_socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
#else
socklen_t client_len = sizeof(client_addr);
ssize_t len = recvfrom(static_cast<int>(udp_socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
#endif
if (!is_running) break;
if (len > 0) {
const char* ack_msg = "ACK";
#ifdef _WIN32
sendto(static_cast<SOCKET>(udp_socket_fd), ack_msg, static_cast<int>(strlen(ack_msg)), 0, (sockaddr*)&client_addr, client_len);
#else
sendto(static_cast<int>(udp_socket_fd), ack_msg, strlen(ack_msg), 0, (sockaddr*)&client_addr, client_len);
#endif
} else {
break;
}
}
#ifdef _WIN32
if (udp_socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(udp_socket_fd));
#else
if (udp_socket_fd != ~0ULL) close(static_cast<int>(udp_socket_fd));
#endif
udp_socket_fd = ~0ULL;
LOG_INFO(Network, "Legacy Online UDP Server stopped");
}
void LegacyOnlineService::HttpServerLoop() {
LOG_INFO(Network, "Starting Mobile App HTTP/WebSocket Server on port {}", HTTP_PORT);
auto s = socket(AF_INET, SOCK_STREAM, 0);
#ifdef _WIN32
if (s == INVALID_SOCKET) {
#else
if (s == -1) {
#endif
LOG_ERROR(Network, "Failed to create HTTP socket");
return;
}
http_socket_fd = static_cast<uintptr_t>(s);
int opt = 1;
#ifdef _WIN32
setsockopt(static_cast<SOCKET>(http_socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
#else
setsockopt(static_cast<int>(http_socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(HTTP_PORT);
int res = -1;
#ifdef _WIN32
res = bind(static_cast<SOCKET>(http_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
#else
res = bind(static_cast<int>(http_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
#endif
if (res < 0) {
#ifdef _WIN32
LOG_ERROR(Network, "Failed to bind HTTP to port {}: {}", HTTP_PORT, WSAGetLastError());
closesocket(static_cast<SOCKET>(http_socket_fd));
#else
LOG_ERROR(Network, "Failed to bind HTTP to port {}: {}", HTTP_PORT, strerror(errno));
close(static_cast<int>(http_socket_fd));
#endif
http_socket_fd = ~0ULL;
return;
}
#ifdef _WIN32
res = listen(static_cast<SOCKET>(http_socket_fd), 10);
#else
res = listen(static_cast<int>(http_socket_fd), 10);
#endif
if (res < 0) {
#ifdef _WIN32
LOG_ERROR(Network, "Failed to listen on HTTP port {}: {}", HTTP_PORT, WSAGetLastError());
closesocket(static_cast<SOCKET>(http_socket_fd));
#else
LOG_ERROR(Network, "Failed to listen on HTTP port {}: {}", HTTP_PORT, strerror(errno));
close(static_cast<int>(http_socket_fd));
#endif
http_socket_fd = ~0ULL;
return;
}
LOG_INFO(Network, "Mobile App HTTP/WebSocket Server listening on port {}...", HTTP_PORT);
while (is_running) {
sockaddr_in client_addr{};
#ifdef _WIN32
int client_len = sizeof(client_addr);
SOCKET client_fd = accept(static_cast<SOCKET>(http_socket_fd), (sockaddr*)&client_addr, &client_len);
if (client_fd == INVALID_SOCKET) {
#else
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(static_cast<int>(http_socket_fd), (sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
#endif
if (!is_running) break;
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
LOG_INFO(Network, "HTTP/WebSocket connection from {}:{}", client_ip, ntohs(client_addr.sin_port));
// Read HTTP request
char buffer[4096];
memset(buffer, 0, sizeof(buffer));
#ifdef _WIN32
int bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
#else
ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
#endif
if (bytes_read > 0) {
std::string request(buffer, bytes_read);
LOG_INFO(Network, "Request:\n{}", request);
// Check if this is a WebSocket upgrade request
bool is_websocket = request.find("Upgrade: websocket") != std::string::npos;
if (is_websocket) {
// Extract Sec-WebSocket-Key
std::string ws_key;
std::regex key_regex("Sec-WebSocket-Key: ([^\r\n]+)");
std::smatch match;
if (std::regex_search(request, match, key_regex)) {
ws_key = match[1].str();
}
// Extract Sec-WebSocket-Protocol
std::string ws_protocol;
std::regex protocol_regex("Sec-WebSocket-Protocol: ([^\r\n]+)");
if (std::regex_search(request, match, protocol_regex)) {
ws_protocol = match[1].str();
}
LOG_INFO(Network, "WebSocket upgrade request - Key: {}, Protocol: {}", ws_key, ws_protocol);
// Compute accept key
std::string accept_key = compute_websocket_accept(ws_key);
LOG_INFO(Network, "WebSocket Accept Key: {}", accept_key);
// Build WebSocket handshake response
std::ostringstream ws_response;
ws_response << "HTTP/1.1 101 Switching Protocols\r\n";
ws_response << "Upgrade: websocket\r\n";
ws_response << "Connection: Upgrade\r\n";
ws_response << "Sec-WebSocket-Accept: " << accept_key << "\r\n";
if (!ws_protocol.empty()) {
ws_response << "Sec-WebSocket-Protocol: " << ws_protocol << "\r\n";
}
ws_response << "\r\n";
std::string response_str = ws_response.str();
#ifdef _WIN32
send(client_fd, response_str.c_str(), static_cast<int>(response_str.size()), 0);
#else
send(client_fd, response_str.c_str(), response_str.size(), 0);
#endif
LOG_INFO(Network, "WebSocket handshake completed! Response:\n{}", response_str);
// Now handle WebSocket messages
LOG_INFO(Network, "Mobile app WebSocket connected! Entering message loop...");
while (is_running) {
memset(buffer, 0, sizeof(buffer));
#ifdef _WIN32
bytes_read = recv(client_fd, buffer, sizeof(buffer), 0);
#else
bytes_read = recv(client_fd, buffer, sizeof(buffer), 0);
#endif
if (bytes_read <= 0) {
LOG_INFO(Network, "WebSocket connection closed");
break;
}
// Parse WebSocket frame
uint8_t* frame = reinterpret_cast<uint8_t*>(buffer);
uint8_t opcode = frame[0] & 0x0F;
bool masked = (frame[1] & 0x80) != 0;
uint64_t payload_len = frame[1] & 0x7F;
size_t header_len = 2;
if (payload_len == 126) {
payload_len = (frame[2] << 8) | frame[3];
header_len = 4;
} else if (payload_len == 127) {
header_len = 10;
payload_len = 0;
for (int i = 0; i < 8; ++i) {
payload_len = (payload_len << 8) | frame[2 + i];
}
}
uint8_t mask_key[4] = {0};
if (masked) {
memcpy(mask_key, frame + header_len, 4);
header_len += 4;
}
// Unmask payload
std::string payload;
for (size_t i = 0; i < payload_len && (header_len + i) < static_cast<size_t>(bytes_read); ++i) {
char c = frame[header_len + i];
if (masked) {
c ^= mask_key[i % 4];
}
payload += c;
}
// LOG_INFO(Network, "WebSocket message (opcode={}): {}", opcode, payload);
// Handle different opcodes
if (opcode == 0x08) {
// Close frame
// LOG_INFO(Network, "WebSocket close frame received");
break;
} else if (opcode == 0x09) {
// Ping - send pong. Theoretically should echo payload, but empty pong is usually fine.
// LOG_INFO(Network, "WebSocket PING received. Responding with PONG.");
uint8_t pong[2] = {0x8A, 0x00};
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(pong), 2, 0);
#else
send(client_fd, pong, 2, 0);
#endif
} else if (opcode == 0x0A) {
// Pong (keep-alive response from client)
// LOG_INFO(Network, "WebSocket PONG received from client.");
} else if (opcode == 0x01 || opcode == 0x02) {
// Text or Binary frame - process Just Dance protocol
std::string response;
// Check message type and respond appropriately
// Protocol flow based on JoyDance:
// 1. Phone -> Console: JD_PhoneDataCmdHandshakeHello
// 2. Console -> Phone: JD_PhoneDataCmdHandshakeContinue (with phoneID)
// 3. Phone -> Console: JD_PhoneDataCmdSync (with phoneID)
// 4. Console -> Phone: JD_PhoneDataCmdSyncEnd (with phoneID)
// 5. Connected!
if (payload.find("JD_PhoneDataCmdHandshakeHello") != std::string::npos) {
// Step 2: Respond with HandshakeContinue
// PROTOCOL FIX: No "root" wrapper. Authenticated ID=1 (Int).
// CRITICAL: App sent Freq=0. Providing configuration values.
// CLEANUP: Removed extra status fields to prevent parsing errors.
response = R"({"__class":"JD_PhoneDataCmdHandshakeContinue","phoneID":1,"accelAcquisitionFreqHz":50,"accelAcquisitionLatency":40,"accelMaxRange":8})";
// LOG_INFO(Network, "Sending HandshakeContinue (id=1, cfg=50Hz)");
// Send HandshakeContinue
std::vector<uint8_t> ws_frame;
ws_frame.push_back(0x81);
ws_frame.push_back(static_cast<uint8_t>(response.size()));
for (char c : response) {
ws_frame.push_back(static_cast<uint8_t>(c));
}
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
#else
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
#endif
// LOG_INFO(Network, "WebSocket response sent: {}", response);
// Removed proactive commands to verify if app accepts HandshakeContinue and sends Sync
response.clear();
} else if (payload.find("JD_PhoneDataCmdSync") != std::string::npos) {
// Step 4: Respond with SyncEnd (NO ROOT)
// Using phoneID 1 (Int)
response = R"({"__class":"JD_PhoneDataCmdSyncEnd","phoneID":1,"status":"ok"})";
// LOG_INFO(Network, "Sending SyncEnd (id=1, no root) - Connection complete!");
// Send SyncEnd
std::vector<uint8_t> ws_frame;
ws_frame.push_back(0x81);
ws_frame.push_back(static_cast<uint8_t>(response.size()));
for (char c : response) {
ws_frame.push_back(static_cast<uint8_t>(c));
}
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
#else
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
#endif
// Step 5: Send Activation Commands immediately to authorize input and accel
// 5a. Enable Input
std::string cmd1 = R"({"__class":"InputSetup_ConsoleCommandData","isEnabled":1,"inputSetup":{"isEnabled":1}})";
std::vector<uint8_t> f1; f1.push_back(0x81); f1.push_back(static_cast<uint8_t>(cmd1.size())); for(char c:cmd1) f1.push_back(static_cast<uint8_t>(c));
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(f1.data()), static_cast<int>(f1.size()), 0);
#else
send(client_fd, f1.data(), f1.size(), 0);
#endif
// 5b. Enable Accelerometer
std::string cmd2 = R"({"__class":"JD_EnableAccelValuesSending_ConsoleCommandData","isEnabled":1})";
std::vector<uint8_t> f2; f2.push_back(0x81); f2.push_back(static_cast<uint8_t>(cmd2.size())); for(char c:cmd2) f2.push_back(static_cast<uint8_t>(c));
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(f2.data()), static_cast<int>(f2.size()), 0);
#else
send(client_fd, f2.data(), f2.size(), 0);
#endif
// 5c. UI Setup (optional but good for safety)
std::string cmd3 = R"({"__class":"JD_PhoneUiSetupData","isPopup":0,"inputSetup":{"isEnabled":1}})";
std::vector<uint8_t> f3; f3.push_back(0x81); f3.push_back(static_cast<uint8_t>(cmd3.size())); for(char c:cmd3) f3.push_back(static_cast<uint8_t>(c));
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(f3.data()), static_cast<int>(f3.size()), 0);
#else
send(client_fd, f3.data(), f3.size(), 0);
#endif
// LOG_INFO(Network, "Sent Activation Commands (Input, Accel, UI)");
response.clear();
} else if (payload.find("JD_PhoneScoringData") != std::string::npos) {
// Accelerometer/scoring data - no response needed
// LOG_DEBUG(Network, "Received phone scoring data");
continue;
} else if (payload.find("JD_Input_PhoneCommandData") != std::string::npos) {
// Button input from phone - no response needed
// LOG_INFO(Network, "Received phone input command");
continue;
} else if (payload.find("JD_Pause_PhoneCommandData") != std::string::npos) {
// Pause command from phone
// LOG_INFO(Network, "Received pause command from phone");
continue;
} else if (payload.find("JD_Custom_PhoneCommandData") != std::string::npos) {
// Custom shortcut command
// LOG_INFO(Network, "Received custom command from phone");
continue;
} else if (payload.find("JD_CancelKeyboard_PhoneCommandData") != std::string::npos) {
// Keyboard cancelled
// LOG_INFO(Network, "Phone cancelled keyboard");
continue;
} else {
// Unknown message - log but don't respond
// LOG_INFO(Network, "Unknown phone message type");
continue;
}
if (!response.empty()) {
// Build WebSocket frame
std::vector<uint8_t> ws_frame;
ws_frame.push_back(0x81); // Text frame, FIN
if (response.size() < 126) {
ws_frame.push_back(static_cast<uint8_t>(response.size()));
} else if (response.size() < 65536) {
ws_frame.push_back(126);
ws_frame.push_back(static_cast<uint8_t>((response.size() >> 8) & 0xFF));
ws_frame.push_back(static_cast<uint8_t>(response.size() & 0xFF));
}
for (char c : response) {
ws_frame.push_back(static_cast<uint8_t>(c));
}
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
#else
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
#endif
LOG_INFO(Network, "WebSocket response sent: {}", response);
}
}
}
} else {
// Regular HTTP request
std::string response_body = R"({"status":"ok","message":"Eden Mobile Bridge"})";
std::ostringstream http_response;
http_response << "HTTP/1.1 200 OK\r\n";
http_response << "Content-Type: application/json\r\n";
http_response << "Content-Length: " << response_body.size() << "\r\n";
http_response << "Connection: close\r\n";
http_response << "\r\n";
http_response << response_body;
std::string response_str = http_response.str();
#ifdef _WIN32
send(client_fd, response_str.c_str(), static_cast<int>(response_str.size()), 0);
#else
send(client_fd, response_str.c_str(), response_str.size(), 0);
#endif
}
}
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
}
#ifdef _WIN32
if (http_socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(http_socket_fd));
#else
if (http_socket_fd != ~0ULL) close(static_cast<int>(http_socket_fd));
#endif
http_socket_fd = ~0ULL;
LOG_INFO(Network, "Mobile App HTTP/WebSocket Server stopped");
}
} // namespace Network

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <atomic>
#include <cstdint>
#include <memory>
#include <thread>
namespace Network {
class LegacyOnlineService {
public:
LegacyOnlineService();
~LegacyOnlineService();
void Start();
void Stop();
private:
void UdpServerLoop();
void HttpServerLoop();
std::atomic_bool is_running{false};
std::thread udp_worker_thread;
std::thread http_worker_thread;
uintptr_t udp_socket_fd{~0ULL};
uintptr_t http_socket_fd{~0ULL};
bool winsock_initialized{false};
static constexpr int UDP_PORT = 6000;
static constexpr int HTTP_PORT = 8080;
};
} // namespace Network

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -75,6 +75,8 @@ SOCKET GetInterruptSocket() {
return interrupt_socket;
}
} // namespace
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
sockaddr_in result;
@@ -83,6 +85,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
#endif
switch (static_cast<Domain>(input.family)) {
case Domain::Unspecified:
case Domain::INET:
result.sin_family = AF_INET;
break;
@@ -105,6 +108,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
return addr;
}
namespace {
LINGER MakeLinger(bool enable, u32 linger_value) {
ASSERT(linger_value <= (std::numeric_limits<u_short>::max)());
@@ -124,6 +129,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
case 0:
return Errno::SUCCESS;
case WSAEBADF:
case WSAENOTSOCK:
return Errno::BADF;
case WSAEINVAL:
return Errno::INVAL;
@@ -157,6 +163,10 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
return Errno::TIMEDOUT;
case WSAEINPROGRESS:
return Errno::INPROGRESS;
case WSAEACCES:
return Errno::ACCES;
case WSAEADDRINUSE:
return Errno::ADDRINUSE;
default:
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
return Errno::OTHER;
@@ -212,6 +222,8 @@ SOCKET GetInterruptSocket() {
return interrupt_pipe_fd[0];
}
} // namespace
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
sockaddr_in result;
@@ -234,6 +246,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
return addr;
}
namespace {
int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) {
return poll(fds, static_cast<nfds_t>(nfds), timeout);
}
@@ -320,6 +334,8 @@ Errno GetAndLogLastError(CallType call_type = CallType::Other) {
return err;
}
} // namespace
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
switch (gai_err) {
case 0:
@@ -373,6 +389,8 @@ GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
}
}
namespace {
Domain TranslateDomainFromNative(int domain) {
switch (domain) {
case 0:
@@ -607,6 +625,8 @@ Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
return ret;
}
std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
const size_t num = pollfds.size();
@@ -684,6 +704,10 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
fd = socket(TranslateDomainToNative(domain), TranslateTypeToNative(type),
TranslateProtocolToNative(protocol));
if (fd != INVALID_SOCKET) {
// Enable SO_REUSEADDR to allow port reuse after socket close
// This prevents "Address already in use" errors when rebinding
int reuse = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&reuse), sizeof(reuse));
return Errno::SUCCESS;
}
@@ -801,6 +825,35 @@ std::pair<s32, Errno> Socket::Recv(int flags, std::span<u8> message) {
ASSERT(flags == 0);
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)()));
// If socket is blocking, use poll with interrupt socket to avoid infinite blocking
if (!is_non_blocking) {
std::vector<WSAPOLLFD> host_pollfds{
WSAPOLLFD{fd, POLLIN, 0},
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
};
// Poll with a longer timeout (30 seconds) to wait for data
const int pollres = WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), 30000);
if (host_pollfds[1].revents != 0) {
// Interrupt signaled, return EAGAIN
return {-1, Errno::AGAIN};
}
if (pollres == 0) {
// Timeout - return AGAIN so the game can retry
LOG_DEBUG(Network, "Recv poll timeout after 30 seconds, returning EAGAIN");
return {-1, Errno::AGAIN};
}
if (pollres < 0) {
return {-1, GetAndLogLastError()};
}
// Data is available, proceed with recv
LOG_INFO(Network, "Recv poll detected data available!");
}
const auto result =
recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0);
if (result != SOCKET_ERROR) {
@@ -814,6 +867,35 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::span<u8> message, SockAdd
ASSERT(flags == 0);
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)()));
// If socket is blocking, use poll with interrupt socket to avoid infinite blocking
if (!is_non_blocking) {
std::vector<WSAPOLLFD> host_pollfds{
WSAPOLLFD{fd, POLLIN, 0},
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
};
// Poll with a short timeout (5ms) to emulate Switch non-blocking/interruptible behavior
// This prevents the game from freezing when waiting for UDP packets on the main thread
const int pollres = WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), 5);
if (host_pollfds[1].revents != 0) {
// Interrupt signaled, return EAGAIN
return {-1, Errno::AGAIN};
}
if (pollres == 0) {
// Timeout - return AGAIN so the game can continue its loop (rendering, input, etc.)
return {-1, Errno::AGAIN};
}
if (pollres < 0) {
return {-1, GetAndLogLastError()};
}
// Data is available, proceed with recvfrom
LOG_INFO(Network, "RecvFrom poll detected data available!");
}
sockaddr_in addr_in{};
socklen_t addrlen = sizeof(addr_in);
socklen_t* const p_addrlen = addr ? &addrlen : nullptr;
@@ -824,6 +906,10 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::span<u8> message, SockAdd
if (result != SOCKET_ERROR) {
if (addr) {
*addr = TranslateToSockAddrIn(addr_in, addrlen);
LOG_INFO(Network, "RecvFrom received {} bytes from {}:{}",
result, IPv4AddressToString(addr->ip), addr->portno);
} else {
LOG_INFO(Network, "RecvFrom received {} bytes", result);
}
return {static_cast<s32>(result), Errno::SUCCESS};
}
@@ -859,20 +945,66 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
if (addr) {
host_addr_in = TranslateFromSockAddrIn(*addr);
to = &host_addr_in;
LOG_INFO(Network, "SendTo sending {} bytes to {}:{}",
message.size(), IPv4AddressToString(addr->ip), addr->portno);
} else {
LOG_INFO(Network, "SendTo sending {} bytes (no addr)", message.size());
}
// Log packet content for debugging mobile app connection
if (message.size() > 0 && message.size() <= 200) {
std::string hex_dump;
std::string ascii_dump;
for (size_t i = 0; i < message.size(); ++i) {
char hex[4];
snprintf(hex, sizeof(hex), "%02X ", message[i]);
hex_dump += hex;
// Build ASCII representation
if (message[i] >= 32 && message[i] < 127) {
ascii_dump += static_cast<char>(message[i]);
} else {
ascii_dump += '.';
}
}
LOG_INFO(Network, "SendTo packet HEX: {}", hex_dump);
LOG_INFO(Network, "SendTo packet ASCII: {}", ascii_dump);
} else if (message.size() > 200) {
// Log first 200 bytes for large packets
std::string hex_dump;
std::string ascii_dump;
for (size_t i = 0; i < 200; ++i) {
char hex[4];
snprintf(hex, sizeof(hex), "%02X ", message[i]);
hex_dump += hex;
if (message[i] >= 32 && message[i] < 127) {
ascii_dump += static_cast<char>(message[i]);
} else {
ascii_dump += '.';
}
}
LOG_INFO(Network, "SendTo packet HEX (first 200): {}", hex_dump);
LOG_INFO(Network, "SendTo packet ASCII (first 200): {}", ascii_dump);
}
const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()),
static_cast<int>(message.size()), 0, to, to_len);
if (result != SOCKET_ERROR) {
LOG_INFO(Network, "SendTo success: sent {} bytes", result);
return {static_cast<s32>(result), Errno::SUCCESS};
}
LOG_ERROR(Network, "SendTo failed!");
return {-1, GetAndLogLastError(CallType::Send)};
}
Errno Socket::Close() {
if (fd == INVALID_SOCKET) {
return Errno::SUCCESS;
}
[[maybe_unused]] const int result = closesocket(fd);
ASSERT(result == 0);
if (result != 0) {
GetAndLogLastError();
}
fd = INVALID_SOCKET;
return Errno::SUCCESS;

View File

@@ -48,6 +48,8 @@ enum class Errno {
TIMEDOUT,
MSGSIZE,
INPROGRESS,
ACCES,
ADDRINUSE,
OTHER,
};
@@ -122,8 +124,33 @@ std::optional<IPv4Address> GetHostIPv4Address();
std::string IPv4AddressToString(IPv4Address ip_addr);
u32 IPv4AddressToInteger(IPv4Address ip_addr);
#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <netdb.h>
#endif
// named to avoid name collision with Windows macro
Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
const std::string& host, const std::optional<std::string>& service);
sockaddr TranslateFromSockAddrIn(SockAddrIn input);
inline std::pair<std::string, int> GetNameInfo(const SockAddrIn& addr) {
sockaddr sa = TranslateFromSockAddrIn(addr);
sockaddr_in addr_in{};
std::memcpy(&addr_in, &sa, sizeof(sockaddr_in));
char host[1025]; // NI_MAXHOST
int err = getnameinfo(reinterpret_cast<const sockaddr*>(&addr_in), sizeof(addr_in), host, sizeof(host), nullptr, 0, 0);
if (err != 0) {
return {std::string{}, err};
}
return {std::string(host), 0};
}
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int err);
} // namespace Network

View File

@@ -107,7 +107,7 @@ else()
endif()
find_package(Boost 1.57 REQUIRED)
find_package(fmt 8 CONFIG)
# find_package(fmt 8 CONFIG)
# Pull in externals CMakeLists for libs where available
add_subdirectory(externals)

View File

@@ -59,16 +59,12 @@ public:
signal_stack_size = std::max<size_t>(SIGSTKSZ, 2 * 1024 * 1024);
signal_stack_memory = mmap(nullptr, signal_stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
#ifdef __OPENORBIS__
fmt::print(stderr, "no fastmem on PS4\n");
supports_fast_mem = false;
#else
stack_t signal_stack{};
signal_stack.ss_sp = signal_stack_memory;
signal_stack.ss_size = signal_stack_size;
signal_stack.ss_flags = 0;
if (sigaltstack(&signal_stack, nullptr) != 0) {
fmt::print(stderr, "POSIX SigHandler: init failure at sigaltstack\n");
fmt::print(stderr, "dynarmic: POSIX SigHandler: init failure at sigaltstack\n");
supports_fast_mem = false;
return;
}
@@ -79,17 +75,16 @@ public:
sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSEGV, &sa, &old_sa_segv) != 0) {
fmt::print(stderr, "POSIX SigHandler: could not set SIGSEGV handler\n");
fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGSEGV handler\n");
supports_fast_mem = false;
return;
}
# ifdef __APPLE__
#ifdef __APPLE__
if (sigaction(SIGBUS, &sa, &old_sa_bus) != 0) {
fmt::print(stderr, "POSIX SigHandler: could not set SIGBUS handler\n");
fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGBUS handler\n");
supports_fast_mem = false;
return;
}
# endif
#endif
}
@@ -150,9 +145,6 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) {
# error "Invalid architecture"
#endif
#ifdef __OPENORBIS__
// No fastmem
#else
struct sigaction* retry_sa = sig == SIGSEGV ? &sig_handler->old_sa_segv : &sig_handler->old_sa_bus;
if (retry_sa->sa_flags & SA_SIGINFO) {
retry_sa->sa_sigaction(sig, info, raw_context);
@@ -166,7 +158,6 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) {
return;
}
retry_sa->sa_handler(sig);
#endif
}
} // anonymous namespace

View File

@@ -60,7 +60,7 @@
# elif defined(__linux__)
# define CTX_RIP (mctx.gregs[REG_RIP])
# define CTX_RSP (mctx.gregs[REG_RSP])
# elif defined(__FreeBSD__) || defined(__DragonFly__)
# elif defined(__FreeBSD__)
# define CTX_RIP (mctx.mc_rip)
# define CTX_RSP (mctx.mc_rsp)
# elif defined(__NetBSD__)
@@ -72,9 +72,9 @@
# elif defined(__sun__)
# define CTX_RIP (mctx.gregs[REG_RIP])
# define CTX_RSP (mctx.gregs[REG_RSP])
# elif defined(__OPENORBIS__)
# define CTX_RIP (mctx.gregs[REG_RIP])
# define CTX_RSP (mctx.gregs[REG_RSP])
# elif defined(__DragonFly__)
# define CTX_RIP (mctx.mc_rip)
# define CTX_RSP (mctx.mc_rsp)
# else
# error "Unknown platform"
# endif
@@ -97,7 +97,7 @@
# define CTX_Q(i) (fpctx->vregs[i])
# define CTX_FPSR (fpctx->fpsr)
# define CTX_FPCR (fpctx->fpcr)
# elif defined(__FreeBSD__) || defined(__DragonFly__)
# elif defined(__FreeBSD__)
# define CTX_PC (mctx.mc_gpregs.gp_elr)
# define CTX_SP (mctx.mc_gpregs.gp_sp)
# define CTX_LR (mctx.mc_gpregs.gp_lr)

View File

@@ -97,15 +97,15 @@ public:
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
}
void InterpreterFallback(u32 pc, size_t num_instructions) override {
void InterpreterFallback(u32 /*pc*/, size_t /*num_instructions*/) override {
UNREACHABLE(); //ASSERT(false && "InterpreterFallback({:08x} && {}) code = {:08x}", pc, num_instructions, *MemoryReadCode(pc));
}
void CallSVC(std::uint32_t swi) override {
void CallSVC(std::uint32_t /*swi*/) override {
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
}
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception /*exception*/) override {
void ExceptionRaised(u32 /*pc*/, Dynarmic::A32::Exception /*exception*/) override {
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:08x}) code = {:08x}", pc, *MemoryReadCode(pc));
}
@@ -190,15 +190,15 @@ public:
return true;
}
void InterpreterFallback(std::uint32_t pc, size_t num_instructions) override {
void InterpreterFallback(std::uint32_t /*pc*/, size_t /*num_instructions*/) override {
UNREACHABLE(); //ASSERT(false && "InterpreterFallback({:016x} && {})", pc, num_instructions);
}
void CallSVC(std::uint32_t swi) override {
void CallSVC(std::uint32_t /*swi*/) override {
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
}
void ExceptionRaised(std::uint32_t pc, Dynarmic::A32::Exception) override {
void ExceptionRaised(std::uint32_t /*pc*/, Dynarmic::A32::Exception) override {
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:016x})", pc);
}

View File

@@ -105,15 +105,15 @@ public:
return true;
}
void InterpreterFallback(u64 pc, size_t num_instructions) override {
void InterpreterFallback(u64 /*pc*/, size_t /*num_instructions*/) override {
UNREACHABLE(); // ASSERT(false&& "InterpreterFallback({:016x} && {})", pc, num_instructions);
}
void CallSVC(std::uint32_t swi) override {
void CallSVC(std::uint32_t /*swi*/) override {
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
}
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception /*exception*/) override {
void ExceptionRaised(u64 /*pc*/, Dynarmic::A64::Exception /*exception*/) override {
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:016x})", pc);
}
@@ -208,15 +208,15 @@ public:
return true;
}
void InterpreterFallback(u64 pc, size_t num_instructions) override {
void InterpreterFallback(u64 /*pc*/, size_t /*num_instructions*/) override {
ASSERT(ignore_invalid_insn && "InterpreterFallback");
}
void CallSVC(std::uint32_t swi) override {
void CallSVC(std::uint32_t /*swi*/) override {
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
}
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception) override {
void ExceptionRaised(u64 /*pc*/, Dynarmic::A64::Exception) override {
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:016x})", pc);
}

View File

@@ -75,8 +75,7 @@ if (ENABLE_SDL2)
helpers/joycon_protocol/rumble.cpp
helpers/joycon_protocol/rumble.h
)
target_include_directories(input_common PRIVATE ${CMAKE_SYSROOT}/include/SDL2)
target_link_libraries(input_common PRIVATE SDL2)
target_link_libraries(input_common PRIVATE SDL2::SDL2)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
endif()

View File

@@ -23,14 +23,12 @@ Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_)
return;
}
LOG_INFO(Input, "Joycon driver Initialization started");
#if SDL_VERSION_ATLEAST(2, 26, 4)
int const res = SDL_hid_init();
if (res == 0) {
const int init_res = SDL_hid_init();
if (init_res == 0) {
Setup();
} else {
LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", res);
LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
}
#endif
}
Joycons::~Joycons() {
@@ -57,9 +55,7 @@ void Joycons::Reset() {
}
device->Stop();
}
#if SDL_VERSION_ATLEAST(2, 26, 4)
SDL_hid_exit();
#endif
}
void Joycons::Setup() {
@@ -84,9 +80,9 @@ void Joycons::Setup() {
}
void Joycons::ScanThread(std::stop_token stop_token) {
#if SDL_VERSION_ATLEAST(2, 26, 4)
constexpr u16 nintendo_vendor_id = 0x057e;
Common::SetCurrentThreadName("JoyconScanThread");
do {
SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
SDL_hid_device_info* cur_dev = devs;
@@ -102,7 +98,6 @@ void Joycons::ScanThread(std::stop_token stop_token) {
SDL_hid_free_enumeration(devs);
} while (Common::StoppableTimedWait(stop_token, std::chrono::seconds{5}));
#endif
}
bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -9,14 +6,7 @@
#include <array>
#include <span>
#include <thread>
#include <SDL.h>
#if SDL_VERSION_ATLEAST(2, 26, 4)
# include <SDL_hidapi.h>
#else
struct SDL_hid_device;
struct SDL_hid_device_info;
#endif
#include <SDL_hidapi.h>
#include "input_common/input_engine.h"

View File

@@ -42,7 +42,6 @@ public:
}
void EnableMotion() {
#if SDL_VERSION_ATLEAST(2, 26, 4)
if (!sdl_controller) {
return;
}
@@ -59,14 +58,12 @@ public:
if (has_gyro) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
}
#endif
}
bool HasMotion() const {
return has_gyro || has_accel;
}
#if SDL_VERSION_ATLEAST(2, 26, 4)
bool UpdateMotion(SDL_ControllerSensorEvent event) {
constexpr float gravity_constant = 9.80665f;
std::scoped_lock lock{mutex};
@@ -108,7 +105,6 @@ public:
motion.delta_timestamp = time_difference * 1000;
return true;
}
#endif
const BasicMotion& GetMotion() const {
return motion;
@@ -153,15 +149,13 @@ public:
}
bool HasHDRumble() const {
#if SDL_VERSION_ATLEAST(2, 26, 4)
if (sdl_controller) {
auto const type = SDL_GameControllerGetType(sdl_controller.get());
const auto type = SDL_GameControllerGetType(sdl_controller.get());
return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) ||
(type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) ||
(type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) ||
(type == SDL_CONTROLLER_TYPE_PS5);
}
#endif
return false;
}
@@ -258,21 +252,26 @@ public:
}
std::string GetControllerName() const {
#if SDL_VERSION_ATLEAST(2, 26, 4)
if (sdl_controller) {
switch (SDL_GameControllerGetType(sdl_controller.get())) {
case SDL_CONTROLLER_TYPE_XBOX360: return "Xbox 360 Controller";
case SDL_CONTROLLER_TYPE_XBOXONE: return "Xbox One Controller";
case SDL_CONTROLLER_TYPE_PS3: return "DualShock 3 Controller";
case SDL_CONTROLLER_TYPE_PS4: return "DualShock 4 Controller";
case SDL_CONTROLLER_TYPE_PS5: return "DualSense Controller";
case SDL_CONTROLLER_TYPE_XBOX360:
return "Xbox 360 Controller";
case SDL_CONTROLLER_TYPE_XBOXONE:
return "Xbox One Controller";
case SDL_CONTROLLER_TYPE_PS3:
return "DualShock 3 Controller";
case SDL_CONTROLLER_TYPE_PS4:
return "DualShock 4 Controller";
case SDL_CONTROLLER_TYPE_PS5:
return "DualSense Controller";
default:
if (auto const name = SDL_GameControllerName(sdl_controller.get()); name)
return name;
break;
}
const auto name = SDL_GameControllerName(sdl_controller.get());
if (name) {
return name;
}
}
#endif
if (sdl_joystick) {
const auto name = SDL_JoystickName(sdl_joystick.get());
@@ -457,7 +456,6 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
}
break;
}
#if SDL_VERSION_ATLEAST(2, 26, 4)
case SDL_CONTROLLERSENSORUPDATE: {
if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
if (joystick->UpdateMotion(event.csensor)) {
@@ -474,7 +472,6 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
}
break;
}
#endif
case SDL_JOYDEVICEREMOVED:
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
@@ -492,7 +489,6 @@ void SDLDriver::CloseJoysticks() {
}
SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
#if SDL_VERSION_ATLEAST(2, 26, 4)
// Set our application name. Currently passed to DBus by SDL and visible to the user through
// their desktop environment.
SDL_SetHint(SDL_HINT_APP_NAME, "Eden");
@@ -535,7 +531,6 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
// Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
// driver on Linux.
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
#endif
// If the frontend is going to manage the event loop, then we don't start one here
start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0;
@@ -840,7 +835,6 @@ ButtonBindings SDLDriver::GetDefaultButtonBinding(
auto slr_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
auto srr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
#if SDL_VERSION_ATLEAST(2, 26, 4)
if (joystick->IsJoyconLeft()) {
sll_button = SDL_CONTROLLER_BUTTON_PADDLE2;
srl_button = SDL_CONTROLLER_BUTTON_PADDLE4;
@@ -849,7 +843,6 @@ ButtonBindings SDLDriver::GetDefaultButtonBinding(
slr_button = SDL_CONTROLLER_BUTTON_PADDLE3;
srr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
}
#endif
return {
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
@@ -871,9 +864,7 @@ ButtonBindings SDLDriver::GetDefaultButtonBinding(
{Settings::NativeButton::SLRight, slr_button},
{Settings::NativeButton::SRRight, srr_button},
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
#if SDL_VERSION_ATLEAST(2, 26, 4)
{Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1},
#endif
};
}

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -41,8 +38,8 @@ Common::Input::DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_inf
return Common::Input::DriverResult::UnsupportedControllerType;
}
#if SDL_VERSION_ATLEAST(2, 26, 4)
hidapi_handle->handle = SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
hidapi_handle->handle =
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
std::memcpy(&handle_serial_number, device_info->serial_number, 15);
if (!hidapi_handle->handle) {
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
@@ -51,9 +48,6 @@ Common::Input::DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_inf
}
SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
return Common::Input::DriverResult::Success;
#else
return Common::Input::DriverResult::UnsupportedControllerType;
#endif
}
Common::Input::DriverResult JoyconDriver::InitializeDevice() {
@@ -144,6 +138,8 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
Common::SetCurrentThreadName("JoyconInput");
input_thread_running = true;
// Max update rate is 5ms, ensure we are always able to read a bit faster
constexpr int ThreadDelay = 3;
std::vector<u8> buffer(MaxBufferSize);
while (!stop_token.stop_requested()) {
@@ -154,17 +150,14 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
continue;
}
#if SDL_VERSION_ATLEAST(2, 26, 4)
// Max update rate is 5ms, ensure we are always able to read a bit faster
int constexpr thread_delay = 3;
// By disabling the input thread we can ensure custom commands will succeed as no package is
// skipped
if (!disable_input_thread) {
status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(), thread_delay);
status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
ThreadDelay);
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(thread_delay));
std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
}
#endif
if (IsPayloadCorrect(status, buffer)) {
OnNewData(buffer);
@@ -697,18 +690,19 @@ void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
joycon_poller->SetCallbacks(callbacks);
}
Common::Input::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, ControllerType& controller_type) {
#if SDL_VERSION_ATLEAST(2, 26, 4)
Common::Input::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
ControllerType& controller_type) {
static constexpr std::array<std::pair<u32, ControllerType>, 6> supported_devices{
std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
{0x2007, ControllerType::Right},
{0x2009, ControllerType::Pro},
};
constexpr u16 nintendo_vendor_id = 0x057e;
controller_type = ControllerType::None;
if (device_info->vendor_id != nintendo_vendor_id)
if (device_info->vendor_id != nintendo_vendor_id) {
return Common::Input::DriverResult::UnsupportedControllerType;
}
for (const auto& [product_id, type] : supported_devices) {
if (device_info->product_id == static_cast<u16>(product_id)) {
@@ -716,20 +710,16 @@ Common::Input::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* dev
return Common::Input::DriverResult::Success;
}
}
#endif
return Common::Input::DriverResult::UnsupportedControllerType;
}
Common::Input::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info, SerialNumber& serial_number) {
#if SDL_VERSION_ATLEAST(2, 26, 4)
Common::Input::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
SerialNumber& serial_number) {
if (device_info->serial_number == nullptr) {
return Common::Input::DriverResult::Unknown;
}
std::memcpy(&serial_number, device_info->serial_number, 15);
return Common::Input::DriverResult::Success;
#else
return Common::Input::DriverResult::Unknown;
#endif
}
} // namespace InputCommon::Joycon

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -18,15 +15,11 @@ u8 JoyconCommonProtocol::GetCounter() {
}
void JoyconCommonProtocol::SetBlocking() {
#if SDL_VERSION_ATLEAST(2, 26, 4)
SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
#endif
}
void JoyconCommonProtocol::SetNonBlocking() {
#if SDL_VERSION_ATLEAST(2, 26, 4)
SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
#endif
}
Common::Input::DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
@@ -42,23 +35,26 @@ Common::Input::DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType&
return result;
}
Common::Input::DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
Common::Input::DriverResult JoyconCommonProtocol::CheckDeviceAccess(
SDL_hid_device_info* device_info) {
ControllerType controller_type{ControllerType::None};
const auto result = GetDeviceType(controller_type);
if (result != Common::Input::DriverResult::Success || controller_type == ControllerType::None) {
return Common::Input::DriverResult::UnsupportedControllerType;
}
#if SDL_VERSION_ATLEAST(2, 26, 4)
hidapi_handle->handle = SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
hidapi_handle->handle =
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
if (!hidapi_handle->handle) {
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", device_info->vendor_id, device_info->product_id);
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
device_info->vendor_id, device_info->product_id);
return Common::Input::DriverResult::HandleInUse;
}
SetNonBlocking();
return Common::Input::DriverResult::Success;
#else
return Common::Input::DriverResult::UnsupportedControllerType;
#endif
}
Common::Input::DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
@@ -67,21 +63,21 @@ Common::Input::DriverResult JoyconCommonProtocol::SetReportMode(ReportMode repor
}
Common::Input::DriverResult JoyconCommonProtocol::SendRawData(std::span<const u8> buffer) {
#if SDL_VERSION_ATLEAST(2, 26, 4)
auto const result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
if (result == -1)
const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
if (result == -1) {
return Common::Input::DriverResult::ErrorWritingData;
}
return Common::Input::DriverResult::Success;
#else
return Common::Input::DriverResult::ErrorWritingData;
#endif
}
Common::Input::DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, SubCommandResponse& output) {
#if SDL_VERSION_ATLEAST(2, 26, 4)
Common::Input::DriverResult JoyconCommonProtocol::GetSubCommandResponse(
SubCommand sc, SubCommandResponse& output) {
constexpr int timeout_mili = 66;
constexpr int MaxTries = 10;
int tries = 0;
do {
int result = SDL_hid_read_timeout(hidapi_handle->handle, reinterpret_cast<u8*>(&output),
sizeof(SubCommandResponse), timeout_mili);
@@ -92,8 +88,9 @@ Common::Input::DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubComma
if (tries++ > MaxTries) {
return Common::Input::DriverResult::Timeout;
}
} while (output.input_report.report_mode != ReportMode::SUBCMD_REPLY && output.sub_command != sc);
#endif
} while (output.input_report.report_mode != ReportMode::SUBCMD_REPLY &&
output.sub_command != sc);
return Common::Input::DriverResult::Success;
}
@@ -221,11 +218,12 @@ Common::Input::DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig&
return result;
}
Common::Input::DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode, MCUCommandResponse& output) {
#if SDL_VERSION_ATLEAST(2, 26, 4)
Common::Input::DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode,
MCUCommandResponse& output) {
constexpr int TimeoutMili = 200;
constexpr int MaxTries = 9;
int tries = 0;
do {
int result = SDL_hid_read_timeout(hidapi_handle->handle, reinterpret_cast<u8*>(&output),
sizeof(MCUCommandResponse), TimeoutMili);
@@ -236,8 +234,9 @@ Common::Input::DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode
if (tries++ > MaxTries) {
return Common::Input::DriverResult::Timeout;
}
} while (output.input_report.report_mode != report_mode || output.mcu_report == MCUReport::EmptyAwaitingCmd);
#endif
} while (output.input_report.report_mode != report_mode ||
output.mcu_report == MCUReport::EmptyAwaitingCmd);
return Common::Input::DriverResult::Success;
}

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -13,14 +10,7 @@
#include <array>
#include <functional>
#include <SDL.h>
#if SDL_VERSION_ATLEAST(2, 26, 4)
# include <SDL_hidapi.h>
#else
struct SDL_hid_device;
struct SDL_hid_device_info;
#endif
#include <SDL_hidapi.h>
#include "common/bit_field.h"
#include "common/common_funcs.h"

View File

@@ -82,10 +82,7 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
RegisterEngine("gcpad", gcadapter);
#endif
#ifndef __OPENORBIS__
// TODO: Issue in PS4, crash for UDP_client
RegisterEngine("cemuhookudp", udp_client);
#endif
RegisterEngine("tas", tas_input);
RegisterEngine("camera", camera);
#ifdef ANDROID
@@ -119,9 +116,7 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
UnregisterEngine(gcadapter);
#endif
#ifndef __OPENORBIS__
UnregisterEngine(udp_client);
#endif
UnregisterEngine(tas_input);
UnregisterEngine(camera);
#ifdef ANDROID
@@ -157,10 +152,8 @@ struct InputSubsystem::Impl {
auto gcadapter_devices = gcadapter->GetInputDevices();
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
#endif
#ifndef __OPENORBIS__
auto udp_devices = udp_client->GetInputDevices();
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
#endif
#ifdef HAVE_SDL2
auto joycon_devices = joycon->GetInputDevices();
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
@@ -193,11 +186,9 @@ struct InputSubsystem::Impl {
return gcadapter;
}
#endif
#ifndef __OPENORBIS__
if (engine == udp_client->GetEngineName()) {
return udp_client;
}
#endif
#ifdef HAVE_SDL2
if (engine == sdl->GetEngineName()) {
return sdl;
@@ -280,11 +271,9 @@ struct InputSubsystem::Impl {
return true;
}
#endif
#ifndef __OPENORBIS__
if (engine == udp_client->GetEngineName()) {
return true;
}
#endif
if (engine == tas_input->GetEngineName()) {
return true;
}
@@ -311,9 +300,7 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
gcadapter->BeginConfiguration();
#endif
#ifndef __OPENORBIS__
udp_client->BeginConfiguration();
#endif
#ifdef HAVE_SDL2
sdl->BeginConfiguration();
joycon->BeginConfiguration();
@@ -329,9 +316,7 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
gcadapter->EndConfiguration();
#endif
#ifndef __OPENORBIS__
udp_client->EndConfiguration();
#endif
#ifdef HAVE_SDL2
sdl->EndConfiguration();
joycon->EndConfiguration();
@@ -356,9 +341,7 @@ struct InputSubsystem::Impl {
std::shared_ptr<Mouse> mouse;
std::shared_ptr<TouchScreen> touch_screen;
std::shared_ptr<TasInput::Tas> tas_input;
#ifndef __OPENORBIS__
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
#endif
std::shared_ptr<Camera> camera;
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
std::shared_ptr<VirtualGamepad> virtual_gamepad;
@@ -487,9 +470,7 @@ bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const {
}
void InputSubsystem::ReloadInputDevices() {
#ifndef __OPENORBIS__
impl->udp_client.get()->ReloadSockets();
#endif
}
void InputSubsystem::BeginMapping(Polling::InputType type) {

View File

@@ -159,13 +159,7 @@ struct Values {
Setting<bool> enable_discord_presence{linkage, false, "enable_discord_presence", Category::Ui};
// logging
Setting<bool> show_console{linkage,
#ifdef __OPENORBIS__
true,
#else
false,
#endif
"showConsole", Category::Ui};
Setting<bool> show_console{linkage, false, "showConsole", Category::Ui};
// Screenshots
Setting<bool> enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as",

View File

@@ -446,10 +446,12 @@ void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst) {
void EmitSR_WScaleFactorXY(EmitContext& ctx, IR::Inst& inst) {
LOG_WARNING(Shader, "(STUBBED) called");
ctx.AddU32("{}=0x3c003c00u;", inst);
}
void EmitSR_WScaleFactorZ(EmitContext& ctx, IR::Inst& inst) {
LOG_WARNING(Shader, "(STUBBED) called");
ctx.AddU32("{}=0x3f800000u;", inst);
}
void EmitYDirection(EmitContext& ctx, IR::Inst& inst) {

View File

@@ -58,6 +58,9 @@ std::string FormatFloat(std::string_view value, IR::Type type) {
if (value == "nan") {
return "utof(0x7fc00000)";
}
if (value == "-nan") {
return "utof(0xffc00000)";
}
if (value == "inf") {
return "utof(0x7f800000)";
}

View File

@@ -81,7 +81,8 @@ public:
if constexpr (can_async_check) {
guard.lock();
}
if (Settings::IsGPULevelLow() || (Settings::IsGPULevelMedium() && !should_flush)) {
// if ((Settings::IsGPULevelLow() || Settings::IsGPULevelMedium()) && !should_flush) {
if (false) {
func();
} else {
uncommitted_operations.emplace_back(std::move(func));

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -8,6 +8,7 @@
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include <thread>
#include "core/memory.h"
#include "video_core/host1x/ffmpeg/ffmpeg.h"
#include "video_core/memory_manager.h"

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -7,6 +7,9 @@
#include <array>
#include <tuple>
#include <stdint.h>
#include <thread>
#include <vector>
extern "C" {
#if defined(__GNUC__) || defined(__clang__)
@@ -107,9 +110,19 @@ void Vic::Execute() {
auto output_height{config.output_surface_config.out_surface_height + 1};
output_surface.resize_destructive(output_width * output_height);
// Initialize the surface with the appropriate black pixel
Pixel black_pixel{};
if (config.output_surface_config.out_pixel_format == VideoPixelFormat::Y8__V8U8_N420) {
// Y=0, U=512, V=512 (10-bit), A=0
black_pixel = {0, 512, 512, 0};
} else {
// R=0, G=0, B=0, A=0
black_pixel = {0, 0, 0, 0};
}
std::fill(output_surface.begin(), output_surface.end(), black_pixel);
if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Off) [[unlikely]] {
// Fill the frame with black, as otherwise they can have random data and be very glitchy.
std::fill(output_surface.begin(), output_surface.end(), Pixel{});
} else {
for (size_t i = 0; i < config.slot_structs.size(); i++) {
auto& slot_config{config.slot_structs[i]};
@@ -122,7 +135,18 @@ void Vic::Execute() {
nvdec_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
}
if (auto frame = frame_queue.GetFrame(nvdec_id, luma_offset); frame) {
auto frame = frame_queue.GetFrame(nvdec_id, luma_offset);
if (!frame) {
// We might've failed to find the frame, or the nvdec id is stale/wrong.
// Try to find the nvdec id again.
const s32 new_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
if (new_id != -1) {
nvdec_id = new_id;
frame = frame_queue.GetFrame(nvdec_id, luma_offset);
}
}
if (frame) {
if (frame.get()) {
switch (frame->GetPixelFormat()) {
case AV_PIX_FMT_YUV420P:
@@ -191,20 +215,44 @@ void Vic::ReadProgressiveY8__V8U8_N420(const SlotStruct& slot, std::span<const P
const auto alpha{u16(slot.config.planar_alpha.Value())};
for (s32 y = 0; y < in_luma_height; y++) {
const auto src_luma{y * in_luma_stride};
const auto src_chroma{(y / 2) * in_chroma_stride};
const auto dst{y * out_luma_stride};
for (s32 x = 0; x < in_luma_width; x++) {
slot_surface[dst + x].r = u16(luma_buffer[src_luma + x] << 2);
// Chroma samples are duplicated horizontally and vertically.
if(planar) {
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + x / 2] << 2);
slot_surface[dst + x].b = u16(chroma_v_buffer[src_chroma + x / 2] << 2);
const u8* luma_ptr = luma_buffer + y * in_luma_stride;
const u8* chroma_u_ptr = chroma_u_buffer + (y / 2) * in_chroma_stride;
// For planar, V buffer is separate. For NV12, it is not used directly in the same way.
const u8* chroma_v_ptr = planar ? (chroma_v_buffer + (y / 2) * in_chroma_stride) : nullptr;
Pixel* dst_ptr = &slot_surface[y * out_luma_stride];
for (s32 x = 0; x < in_luma_width; x += 2) {
u16 u_val, v_val;
if (planar) {
// YUV420P: U and V are in separate planes.
// 1 UV pair for 2 horizontal pixels.
u_val = u16(chroma_u_ptr[x / 2] << 2);
v_val = u16(chroma_v_ptr[x / 2] << 2);
} else {
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + (x & ~1) + 0] << 2);
slot_surface[dst + x].b = u16(chroma_u_buffer[src_chroma + (x & ~1) + 1] << 2);
// NV12: UV are interleaved in the second plane.
// U is at even byte, V is at odd byte.
// x is even (0, 2, 4...), so x corresponds to the byte offset in the interleaved buffer.
u_val = u16(chroma_u_ptr[x] << 2);
v_val = u16(chroma_u_ptr[x + 1] << 2);
}
slot_surface[dst + x].a = alpha;
// Pixel 1 (Even x)
dst_ptr[0].r = u16(luma_ptr[x] << 2);
dst_ptr[0].g = u_val;
dst_ptr[0].b = v_val;
dst_ptr[0].a = alpha;
// Pixel 2 (Odd x), check boundary
if (x + 1 < in_luma_width) {
dst_ptr[1].r = u16(luma_ptr[x + 1] << 2);
dst_ptr[1].g = u_val;
dst_ptr[1].b = v_val;
dst_ptr[1].a = alpha;
}
dst_ptr += 2;
}
}
}

View File

@@ -33,6 +33,8 @@ static OGLProgram LinkSeparableProgram(GLuint shader) {
glGetProgramInfoLog(program.handle, log_length, nullptr, log.data());
if (link_status == GL_FALSE) {
LOG_ERROR(Render_OpenGL, "{}", log);
glDeleteProgram(program.handle);
program.handle = 0;
} else {
LOG_WARNING(Render_OpenGL, "{}", log);
}

View File

@@ -116,9 +116,14 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
glBindTextureUnit(0, textures[i]);
glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE,
matrices[i].data());
glProgramUniform2ui(frag.handle, ScreenSizeLocation,
static_cast<GLuint>(layout.screen.GetWidth()),
static_cast<GLuint>(layout.screen.GetHeight()));
if (frag.handle != 0) {
const GLint screen_size_loc = glGetUniformLocation(frag.handle, "screen_size");
if (screen_size_loc != -1) {
glProgramUniform2ui(frag.handle, screen_size_loc,
static_cast<GLuint>(layout.screen.GetWidth()),
static_cast<GLuint>(layout.screen.GetHeight()));
}
}
glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices[i]), std::data(vertices[i]));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

View File

@@ -277,7 +277,10 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
bool via_header_index, u32 raw) {
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
ASSERT(handle.first <= tic_limit);
if (handle.first > tic_limit) {
LOG_WARNING(Shader, "Texture ID {} is out of bounds (limit {})", handle.first, tic_limit);
return {};
}
const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
Tegra::Texture::TICEntry entry;
gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));

View File

@@ -15,8 +15,6 @@
#define VK_USE_PLATFORM_ANDROID_KHR
#elif defined(__HAIKU__)
#define VK_USE_PLATFORM_XCB_KHR
#elif defined(__OPENORBIS__)
// No fucking vulkan on the PlayStation 4
#else
#define VK_USE_PLATFORM_XLIB_KHR
#define VK_USE_PLATFORM_WAYLAND_KHR

View File

@@ -57,8 +57,6 @@ namespace {
case Core::Frontend::WindowSystemType::Xcb:
extensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
break;
#elif defined(__OPENORBIS__)
// No vulkan
#else
case Core::Frontend::WindowSystemType::X11:
extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);

View File

@@ -74,8 +74,6 @@ vk::SurfaceKHR CreateSurface(
throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
}
}
#elif defined(__OPENORBIS__)
// No native
#else
if (window_info.type == Core::Frontend::WindowSystemType::X11) {
const VkXlibSurfaceCreateInfoKHR xlib_ci{

View File

@@ -435,8 +435,7 @@ if (YUZU_USE_BUNDLED_QT)
endif()
if (ENABLE_SDL2)
target_include_directories(yuzu PRIVATE ${CMAKE_SYSROOT}/include/SDL2)
target_link_libraries(yuzu PRIVATE SDL2)
target_link_libraries(yuzu PRIVATE SDL2::SDL2)
target_compile_definitions(yuzu PRIVATE HAVE_SDL2)
endif()

View File

@@ -34,16 +34,13 @@ target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common
target_link_libraries(yuzu-cmd PRIVATE glad)
if (MSVC)
target_link_libraries(yuzu-cmd PRIVATE getopt)
elseif(PLATFORM_PS4)
target_link_libraries(yuzu-cmd PRIVATE SceVideoOut SceAudioOut ScePad SceSystemService)
endif()
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR})
target_include_directories(yuzu-cmd PRIVATE ${CMAKE_SYSROOT}/include/SDL2)
target_link_libraries(yuzu-cmd PRIVATE SDL2)
target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2)
if(UNIX AND NOT APPLE)
install(TARGETS yuzu-cmd)
@@ -68,7 +65,3 @@ if (NOT MSVC)
-Wno-unused-parameter
-Wno-missing-field-initializers)
endif()
if (PLATFORM_PS4)
create_ps4_pkg(yuzu-cmd eden-cli IV0000-BREW00090_00-EDENEMULAT000000)
endif()

View File

@@ -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
@@ -77,12 +74,6 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsyste
window_info.type = Core::Frontend::WindowSystemType::Android;
window_info.render_surface = reinterpret_cast<void*>(wm.info.android.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_DIRECTFB
case SDL_SYSWM_TYPE::SDL_SYSWM_DIRECTFB:
window_info.type = Core::Frontend::WindowSystemType::Headless;
window_info.render_surface = reinterpret_cast<void*>(wm.info.dfb.window);
break;
#endif
default:
LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem);

View File

@@ -51,20 +51,7 @@
#include <unistd.h>
#endif
#ifdef __OPENORBIS__
#include <orbis/SystemService.h>
#include <cxxabi.h>
#include <__thread/support.h>
# define STUB_WEAK(name) extern "C" void name() { printf("called " #name); asm volatile("ud2"); }
void *__cxa_thread_atexit_impl = nullptr;
STUB_WEAK(__assert)
STUB_WEAK(ZSTD_trace_compress_begin)
STUB_WEAK(ZSTD_trace_compress_end)
STUB_WEAK(ZSTD_trace_decompress_begin)
STUB_WEAK(ZSTD_trace_decompress_end)
FILE* __stderrp = stdout;
# undef STUB_WEAK
#elif defined(_WIN32)
#ifdef _WIN32
extern "C" {
// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
// graphics
@@ -237,11 +224,7 @@ int main(int argc, char** argv) {
{0, 0, 0, 0},
// clang-format on
};
#ifdef __OPENORBIS__
// PS4 will use this path by default UNLESS overriden; this is so users
// can quickly launch whatever they want.
filepath = "/data/eden/games/test.nro";
#endif
while (optind < argc) {
int arg = getopt_long(argc, argv, "g:fhvp::c:u:d:", long_options, &option_index);
if (arg != -1) {
@@ -436,20 +419,10 @@ int main(int argc, char** argv) {
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
}
auto const exit_fn = [&] {
#ifdef __OPENORBIS__
sceSystemServiceLoadExec("EXIT", nullptr);
#else
system.RegisterExitCallback([&] {
// Just exit right away.
exit(0);
#endif
};
system.RegisterExitCallback(exit_fn);
#ifdef __linux__
Common::Linux::StartGamemode();
#endif
});
void(system.Run());
if (system.DebuggerEnabled()) {
system.InitializeDebugger();
@@ -461,7 +434,6 @@ int main(int argc, char** argv) {
void(system.Pause());
system.ShutdownMainProcess();
detached_tasks.WaitForAllTasks();
exit_fn();
return 0;
}