Compare commits
15 Commits
eden-orbis
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
27b3ac58c5
|
|||
|
6609c6ddcd
|
|||
| 5fa81dae7a | |||
| b3eba6a275 | |||
| c486334c78 | |||
| 7dc30a5e2d | |||
| b16cece0a6 | |||
| 19bbd9894e | |||
|
3bb0d29aa1
|
|||
| cf4f813145 | |||
|
3290ed80d8
|
|||
| a71a82d3b7 | |||
| bd1d270e97 | |||
| 664ff7cc85 | |||
| b5c86787ab |
@@ -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
|
||||
@@ -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
|
||||
159
.gitea/workflows/build-appimage.yml
Normal file
159
.gitea/workflows/build-appimage.yml
Normal 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
3
.gitignore
vendored
@@ -63,6 +63,3 @@ artifacts
|
||||
*.AppImage*
|
||||
/install*
|
||||
vulkansdk*.exe
|
||||
|
||||
# PS4 toolchain stuff
|
||||
ps4-toolchain.cmake
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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})
|
||||
|
||||
@@ -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 ()
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
6
externals/CMakeLists.txt
vendored
6
externals/CMakeLists.txt
vendored
@@ -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)
|
||||
|
||||
2
externals/cmake-modules/DetectPlatform.cmake
vendored
2
externals/cmake-modules/DetectPlatform.cmake
vendored
@@ -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")
|
||||
|
||||
11
externals/cpmfile.json
vendored
11
externals/cpmfile.json
vendored
@@ -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": {
|
||||
|
||||
180
externals/ffmpeg/CMakeLists.txt
vendored
180
externals/ffmpeg/CMakeLists.txt
vendored
@@ -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}
|
||||
)
|
||||
|
||||
10
externals/ffmpeg/cpmfile.json
vendored
10
externals/ffmpeg/cpmfile.json
vendored
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "core/internal_network/network_interface.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
|
||||
#include <windows.h>
|
||||
#include <wlanapi.h>
|
||||
#ifdef _MSC_VER
|
||||
|
||||
701
src/core/internal_network/legacy_online.cpp
Normal file
701
src/core/internal_network/legacy_online.cpp
Normal 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
|
||||
36
src/core/internal_network/legacy_online.h
Normal file
36
src/core/internal_network/legacy_online.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)";
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user