diff --git a/.ci/ps4/build.sh b/.ci/ps4/build.sh new file mode 100755 index 0000000000..ae0ffd3aef --- /dev/null +++ b/.ci/ps4/build.sh @@ -0,0 +1,69 @@ +#!/usr/local/bin/bash -ex + +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +[ -z ${OO_PS4_TOOLCHAIN+x} ] && exit + +[ -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=btver2 -march=btver2 -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=btver2 -march=btver2 -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 " -o -lc -lkernel -lSceUserService -lSceSysmodule -lSceNet -lSceLibcInternal $OO_PS4_TOOLCHAIN/lib/crt1.o ") +set(CMAKE_CXX_LINK_EXECUTABLE " -o -lc -lkernel -lc++ -lSceUserService -lSceSysmodule -lSceNet -lSceLibcInternal $OO_PS4_TOOLCHAIN/lib/crt1.o ") + +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 + +[ -z ${NPROC+x} ] && NPROC=$(nproc || 1) + +# Normally a platform has a package manager +# PS4 does not, atleast not in the normal sense +export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" $@) +[ -f "build/Makefile" ] || 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 \ + -DOPENSSL_ROOT_DIR="$OO_PS4_TOOLCHAIN" \ + -DOPENSSL_SSL_LIBRARY="$OO_PS4_TOOLCHAIN/lib/libssl.a" \ + -DOPENSSL_CRYPTO_LIBRARY="$OO_PS4_TOOLCHAIN/lib/libcrypto.a" \ + -DOPENSSL_INCLUDE_DIR="$OO_PS4_TOOLCHAIN/include/openssl" \ + -DYUZU_USE_EXTERNAL_FFMPEG=ON \ + -DYUZU_USE_CPM=ON \ + -DDYNARMIC_ENABLE_NO_EXECUTE_SUPPORT=OFF \ + -DDYNARMIC_TESTS=OFF \ + -DYUZU_TESTS=OFF \ + -DYUZU_USE_EXTERNAL_SDL2=ON \ + "${EXTRA_CMAKE_FLAGS[@]}" || exit + +cmake --build build -t yuzu-cmd_pkg -- -j$NPROC +#cmake --build build -t dynarmic_tests_pkg -- -j$NPROC +#cmake --build build -t testps4_pkg -- -j$NPROC diff --git a/.gitignore b/.gitignore index 67bdd8adf4..b304411646 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,6 @@ artifacts /install* vulkansdk*.exe *.tar.zst + +# PS4 toolchain stuff +ps4-toolchain.cmake diff --git a/.patch/boost/0004-openorbis.patch b/.patch/boost/0004-openorbis.patch new file mode 100644 index 0000000000..09071b599c --- /dev/null +++ b/.patch/boost/0004-openorbis.patch @@ -0,0 +1,17 @@ +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 + + #include diff --git a/.patch/enet/0001-openorbis.patch b/.patch/enet/0001-openorbis.patch new file mode 100644 index 0000000000..744e2f879d --- /dev/null +++ b/.patch/enet/0001-openorbis.patch @@ -0,0 +1,13 @@ +diff --git a/unix.c b/unix.c +index 6669216..86a2faa 100644 +--- a/unix.c ++++ b/unix.c +@@ -53,7 +53,7 @@ + #include + #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 + diff --git a/.patch/mbedtls/0003-openorbis.patch b/.patch/mbedtls/0003-openorbis.patch new file mode 100644 index 0000000000..46cfbd408f --- /dev/null +++ b/.patch/mbedtls/0003-openorbis.patch @@ -0,0 +1,13 @@ +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 + #include + #if defined(KERN_ARND) diff --git a/.patch/sdl3/0001-ps4.patch b/.patch/sdl3/0001-ps4.patch new file mode 100644 index 0000000000..3b94af77d1 --- /dev/null +++ b/.patch/sdl3/0001-ps4.patch @@ -0,0 +1,196 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 8872895..b0eeca8 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -1770,7 +1770,6 @@ elseif(EMSCRIPTEN) + + CheckPTHREAD() + CheckLibUnwind() +- + elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) + + set(SDL_DISABLE_DLOPEN_NOTES TRUE) +@@ -3647,10 +3646,30 @@ if(NOT HAVE_CAMERA) + ) + endif() + ++# Always on? ++if (PLATFORM_PS4) ++ sdl_sources( ++ "${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_systhread.c" ++ "${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_sysmutex.c" # Can be faked, if necessary ++ "${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_syscond.c" # Can be faked, if necessary ++ "${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_sysrwlock.c" # Can be faked, if necessary ++ "${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_systls.c" ++ "${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syssem.c" ++ ) ++ sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c") ++ sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c") ++ set(HAVE_SDL_THREADS TRUE) ++ set(HAVE_SDL_TIMERS TRUE) ++ set(HAVE_SDL_TIME TRUE) ++ set(SDL_TIME_UNIX 1) ++ set(SDL_TIMER_UNIX 1) ++ set(SDL_THREAD_PTHREAD 1) ++endif () ++ + # We always need to have threads and timers around + if(NOT HAVE_SDL_THREADS) + # The Emscripten and N-Gage platform has been carefully vetted to work without threads +- if(EMSCRIPTEN OR NGAGE) ++ if(EMSCRIPTEN OR NGAGE OR PLATFORM_PS4) + set(SDL_THREADS_DISABLED 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/thread/generic/*.c" +diff --git a/build-scripts/rename_macros.py b/build-scripts/rename_macros.py +index 978120c..b6063dd 100755 +--- a/build-scripts/rename_macros.py ++++ b/build-scripts/rename_macros.py +@@ -124,6 +124,7 @@ RENAMED_MACROS = { + "__RISCOS__": "SDL_PLATFORM_RISCOS", + "__SOLARIS__": "SDL_PLATFORM_SOLARIS", + "__PSP__": "SDL_PLATFORM_PSP", ++ "__PS4__": "SDL_PLATFORM_PS4", + "__PS2__": "SDL_PLATFORM_PS2", + "__VITA__": "SDL_PLATFORM_VITA", + "__3DS__": "SDL_PLATFORM_3DS", +diff --git a/include/SDL3/SDL_platform_defines.h b/include/SDL3/SDL_platform_defines.h +index 7963149..6c0dd5d 100644 +--- a/include/SDL3/SDL_platform_defines.h ++++ b/include/SDL3/SDL_platform_defines.h +@@ -442,6 +442,16 @@ + #define SDL_PLATFORM_PSP 1 + #endif + ++#if defined(__PS4__) || defined(__OPENORBIS__) ++ ++/** ++ * A preprocessor macro that is only defined if compiling for Sony PSP. ++ * ++ * \since This macro is available since SDL 3.2.0. ++ */ ++#define SDL_PLATFORM_PS4 1 ++#endif ++ + #if defined(__PS2__) || defined(PS2) + + /** +diff --git a/src/SDL.c b/src/SDL.c +index b1a1a56..0a3fec6 100644 +--- a/src/SDL.c ++++ b/src/SDL.c +@@ -714,7 +714,7 @@ void SDL_Quit(void) + SDL_DBus_Quit(); + #endif + +-#if defined(SDL_PLATFORM_UNIX) && !defined(SDL_PLATFORM_ANDROID) && !defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(SDL_PLATFORM_PRIVATE) ++#if defined(SDL_PLATFORM_UNIX) && !defined(SDL_PLATFORM_ANDROID) && !defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(SDL_PLATFORM_PRIVATE) && !defined(SDL_PLATFORM_PS4) + SDL_Gtk_Quit(); + #endif + +@@ -813,6 +813,8 @@ const char *SDL_GetPlatform(void) + return "tvOS"; + #elif defined(SDL_PLATFORM_PS2) + return "PlayStation 2"; ++#elif defined(SDL_PLATFORM_PS4) ++ return "PlayStation 4"; + #elif defined(SDL_PLATFORM_PSP) + return "PlayStation Portable"; + #elif defined(SDL_PLATFORM_VITA) +diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c +index c1e4241..65b1e6d 100644 +--- a/src/audio/SDL_audio.c ++++ b/src/audio/SDL_audio.c +@@ -80,6 +80,9 @@ static const AudioBootStrap *const bootstrap[] = { + #ifdef SDL_AUDIO_DRIVER_NGAGE + &NGAGEAUDIO_bootstrap, + #endif ++#ifdef SDL_AUDIO_DRIVER_PS4 ++ &PS4AUDIO_bootstrap, ++#endif + #ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN + &EMSCRIPTENAUDIO_bootstrap, + #endif +diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c +index 60c9743..2d12c3a 100644 +--- a/src/joystick/SDL_joystick.c ++++ b/src/joystick/SDL_joystick.c +@@ -106,6 +106,9 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = { + #ifdef SDL_JOYSTICK_N3DS + &SDL_N3DS_JoystickDriver, + #endif ++#ifdef SDL_JOYSTICK_PS4 ++ &SDL_PS4_JoystickDriver, ++#endif + #if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED) + &SDL_DUMMY_JoystickDriver + #endif +diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h +index 47ce42c..42ab9de 100644 +--- a/src/joystick/SDL_sysjoystick.h ++++ b/src/joystick/SDL_sysjoystick.h +@@ -253,6 +253,7 @@ extern SDL_JoystickDriver SDL_HAIKU_JoystickDriver; + extern SDL_JoystickDriver SDL_HIDAPI_JoystickDriver; + extern SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver; + extern SDL_JoystickDriver SDL_IOS_JoystickDriver; ++extern SDL_JoystickDriver SDL_PS4_JoystickDriver; + extern SDL_JoystickDriver SDL_LINUX_JoystickDriver; + extern SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver; + extern SDL_JoystickDriver SDL_WGI_JoystickDriver; +diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h +index a1bb44a..51843c4 100644 +--- a/src/render/SDL_sysrender.h ++++ b/src/render/SDL_sysrender.h +@@ -381,6 +381,7 @@ extern SDL_RenderDriver NGAGE_RenderDriver; + extern SDL_RenderDriver VULKAN_RenderDriver; + extern SDL_RenderDriver PS2_RenderDriver; + extern SDL_RenderDriver PSP_RenderDriver; ++extern SDL_RenderDriver PS4_RenderDriver; + extern SDL_RenderDriver SW_RenderDriver; + extern SDL_RenderDriver VITA_GXM_RenderDriver; + extern SDL_RenderDriver GPU_RenderDriver; +diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c +index 4a870f6..69ca1db 100644 +--- a/src/stdlib/SDL_string.c ++++ b/src/stdlib/SDL_string.c +@@ -32,7 +32,9 @@ + + #include "SDL_casefolding.h" + +-#if defined(__SIZEOF_WCHAR_T__) ++#if defined(SDL_PLATFORM_PS4) ++#define SDL_SIZEOF_WCHAR_T 2 ++#elif defined(__SIZEOF_WCHAR_T__) + #define SDL_SIZEOF_WCHAR_T __SIZEOF_WCHAR_T__ + #elif defined(SDL_PLATFORM_NGAGE) + #define SDL_SIZEOF_WCHAR_T 2 +@@ -43,7 +45,6 @@ + #endif + SDL_COMPILE_TIME_ASSERT(sizeof_wchar_t, sizeof(wchar_t) == SDL_SIZEOF_WCHAR_T); + +- + char *SDL_UCS4ToUTF8(Uint32 codepoint, char *dst) + { + if (!dst) { +diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c +index f650f27..e23424f 100644 +--- a/src/video/SDL_video.c ++++ b/src/video/SDL_video.c +@@ -131,6 +131,9 @@ static VideoBootStrap *bootstrap[] = { + #ifdef SDL_VIDEO_DRIVER_NGAGE + &NGAGE_bootstrap, + #endif ++#ifdef SDL_VIDEO_DRIVER_PS4 ++ &PS4_bootstrap, ++#endif + #ifdef SDL_VIDEO_DRIVER_KMSDRM + &KMSDRM_bootstrap, + #endif +@@ -314,6 +317,7 @@ static bool SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, + SDL_GetWindowSizeInPixels(window, &w, &h); + + if (!data) { ++ + SDL_Renderer *renderer = NULL; + const char *render_driver = NULL; + diff --git a/.patch/spirv-tools/0003-openorbis.patch b/.patch/spirv-tools/0003-openorbis.patch new file mode 100644 index 0000000000..f2c19d893f --- /dev/null +++ b/.patch/spirv-tools/0003-openorbis.patch @@ -0,0 +1,25 @@ +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 +@@ -19,6 +25,7 @@ + #include + #include + #include ++#include + + #include "source/opt/instruction.h" + #include "source/opt/scalar_analysis_nodes.h" diff --git a/.patch/xbyak/0001-macro-stl.patch b/.patch/xbyak/0001-macro-stl.patch new file mode 100644 index 0000000000..5cfb9eeec6 --- /dev/null +++ b/.patch/xbyak/0001-macro-stl.patch @@ -0,0 +1,21 @@ +diff --git a/xbyak/xbyak.h b/xbyak/xbyak.h +index ed7706a..51b520d 100644 +--- a/xbyak/xbyak.h ++++ b/xbyak/xbyak.h +@@ -37,6 +37,7 @@ + #define XBYAK_GNUC_PREREQ(major, minor) 0 + #endif + ++#if !defined(XBYAK_STD_UNORDERED_SET) + // This covers -std=(gnu|c)++(0x|11|1y), -stdlib=libc++, and modern Microsoft. + #if ((defined(_MSC_VER) && (_MSC_VER >= 1600)) || defined(_LIBCPP_VERSION) ||\ + ((__cplusplus >= 201103) || defined(__GXX_EXPERIMENTAL_CXX0X__))) +@@ -71,6 +72,8 @@ + #define XBYAK_STD_UNORDERED_MAP std::map + #define XBYAK_STD_UNORDERED_MULTIMAP std::multimap + #endif ++#endif ++ + #ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN diff --git a/.patch/xbyak_sun/0001-macro-stl.patch b/.patch/xbyak_sun/0001-macro-stl.patch new file mode 100644 index 0000000000..5cfb9eeec6 --- /dev/null +++ b/.patch/xbyak_sun/0001-macro-stl.patch @@ -0,0 +1,21 @@ +diff --git a/xbyak/xbyak.h b/xbyak/xbyak.h +index ed7706a..51b520d 100644 +--- a/xbyak/xbyak.h ++++ b/xbyak/xbyak.h +@@ -37,6 +37,7 @@ + #define XBYAK_GNUC_PREREQ(major, minor) 0 + #endif + ++#if !defined(XBYAK_STD_UNORDERED_SET) + // This covers -std=(gnu|c)++(0x|11|1y), -stdlib=libc++, and modern Microsoft. + #if ((defined(_MSC_VER) && (_MSC_VER >= 1600)) || defined(_LIBCPP_VERSION) ||\ + ((__cplusplus >= 201103) || defined(__GXX_EXPERIMENTAL_CXX0X__))) +@@ -71,6 +72,8 @@ + #define XBYAK_STD_UNORDERED_MAP std::map + #define XBYAK_STD_UNORDERED_MULTIMAP std::multimap + #endif ++#endif ++ + #ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f84dd539c..a86ac82818 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ include(UseCcache) include(CMakeDependentOption) include(CTest) include(CPMUtil) +include(OpenOrbis) if (NOT DEFINED ARCHITECTURE) message(FATAL_ERROR "Architecture didn't make it out of scope, did you delete DetectArchitecture.cmake?") diff --git a/CMakeModules/GenerateSCMRev.cmake b/CMakeModules/GenerateSCMRev.cmake index 40c64fea1e..8f1e55e12c 100644 --- a/CMakeModules/GenerateSCMRev.cmake +++ b/CMakeModules/GenerateSCMRev.cmake @@ -18,10 +18,13 @@ if (DEFINED GIT_RELEASE) set(BUILD_VERSION "${GIT_TAG}") set(GIT_REFSPEC "${GIT_RELEASE}") set(IS_DEV_BUILD false) -else() +elseif(DEFINED GIT_COMMIT) 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() if (NIGHTLY_BUILD) diff --git a/CMakeModules/OpenOrbis.cmake b/CMakeModules/OpenOrbis.cmake new file mode 100644 index 0000000000..6babccdd6d --- /dev/null +++ b/CMakeModules/OpenOrbis.cmake @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +function(create_ps4_eboot 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 ${target}_eboot.bin + VERBATIM + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + DEPENDS ${project} + ) + add_custom_target(${project}_pkg ALL DEPENDS "${target}.pkg") +endfunction() + +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 ${target}_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 "${target}_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 () diff --git a/cpmfile.json b/cpmfile.json index ead839de2e..798e246fcb 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -17,7 +17,8 @@ "version": "1.57", "find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem", "patches": [ - "0001-clang-cl.patch" + "0001-clang-cl.patch", + "0004-openorbis.patch" ] }, "fmt": { diff --git a/dist/icon_variations/ps4.svg b/dist/icon_variations/ps4.svg new file mode 100644 index 0000000000..0ee32c660c --- /dev/null +++ b/dist/icon_variations/ps4.svg @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/Caveats.md b/docs/Caveats.md index fb86437737..abeed7e019 100644 --- a/docs/Caveats.md +++ b/docs/Caveats.md @@ -12,6 +12,7 @@ - [NetBSD](#netbsd) - [MSYS2](#msys2) - [RedoxOS](#redoxos) +- [PlayStation 4](#playstation-4) - [Windows](#windows) - [Windows 7, Windows 8 and Windows 8.1](#windows-7-windows-8-and-windows-81) - [Windows Vista and below](#windows-vista-and-below) @@ -245,6 +246,17 @@ The package install may randomly hang at times, in which case it has to be resta 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 + +```sh +export OO_PS4_TOOLCHAIN="$HOME/OpenOrbis/PS4Toolchain/prefix" +export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +``` + +```sh +cp $OO_PS4_TOOLCHAIN/include/endian.h $OO_PS4_TOOLCHAIN/include/sys/endian.h +``` + ## Windows ### Windows 7, Windows 8 and Windows 8.1 diff --git a/docs/user/Basics.md b/docs/user/Basics.md index 794a0935d5..ce485e91bb 100644 --- a/docs/user/Basics.md +++ b/docs/user/Basics.md @@ -35,6 +35,7 @@ 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. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 13de8f567d..923241d892 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -256,6 +256,11 @@ target_include_directories(tz PUBLIC ./tz) add_library(bc_decoder bc_decoder/bc_decoder.cpp) target_include_directories(bc_decoder PUBLIC ./bc_decoder) +if (PLATFORM_PS4) + add_library(ps4sup ps4sup/emutls.c ps4sup/stub.cpp) + target_include_directories(ps4sup PUBLIC ./ps4sup) +endif() + if (NOT TARGET RenderDoc::API) add_library(renderdoc INTERFACE) target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc) diff --git a/externals/cpmfile.json b/externals/cpmfile.json index 4e9356cb83..35b60e13eb 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -36,8 +36,8 @@ "0002-fix-zstd.patch" ], "options": [ - "HTTPLIB_REQUIRE_OPENSSL ON", - "HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES ON" + "HTTPLIB_REQUIRE_OPENSSL OFF", + "HTTPLIB_USE_OPENSSL_IF_AVAILABLE OFF" ] }, "cpp-jwt": { @@ -99,7 +99,10 @@ "hash": "a0d2fa8c957704dd49e00a726284ac5ca034b50b00d2b20a94fa1bbfbb80841467834bfdc84aa0ed0d6aab894608fd6c86c3b94eee46343f0e6d9c22e391dbf9", "version": "1.3", "git_version": "1.3.18", - "find_args": "MODULE" + "find_args": "MODULE", + "patches": [ + "0001-openorbis.patch" + ] }, "spirv-headers": { "package": "SPIRV-Headers", @@ -162,7 +165,10 @@ "tag": "release-%VERSION%", "hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3", "git_version": "3.4.8", - "version": "3.2.10" + "version": "3.2.10", + "patches": [ + "0001-ps4.patch" + ] }, "moltenvk": { "repo": "V380-Ori/Ryujinx.MoltenVK", diff --git a/externals/ps4sup/emutls.c b/externals/ps4sup/emutls.c new file mode 100644 index 0000000000..3b0a0cc909 --- /dev/null +++ b/externals/ps4sup/emutls.c @@ -0,0 +1,181 @@ +/* ===---------- emutls.c - Implements __emutls_get_address ---------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + */ +#include +#include +#include +#include + +//#include "int_util.h" + +/* Default is not to use posix_memalign, so systems like Android + * can use thread local data without heavier POSIX memory allocators. + */ +#ifndef EMUTLS_USE_POSIX_MEMALIGN +#define EMUTLS_USE_POSIX_MEMALIGN 0 +#endif + +/* For every TLS variable xyz, + * there is one __emutls_control variable named __emutls_v.xyz. + * If xyz has non-zero initial value, __emutls_v.xyz's "value" + * will point to __emutls_t.xyz, which has the initial value. + */ +typedef struct __emutls_control { + size_t size; /* size of the object in bytes */ + size_t align; /* alignment of the object in bytes */ + union { + uintptr_t index; /* data[index-1] is the object address */ + void* address; /* object address, when in single thread env */ + } object; + void* value; /* null or non-zero initial value for the object */ +} __emutls_control; + +static inline void* emutls_memalign_alloc(size_t align, size_t size) { + void *base; +#if EMUTLS_USE_POSIX_MEMALIGN + if (posix_memalign(&base, align, size) != 0) + abort(); +#else + #define EXTRA_ALIGN_PTR_BYTES (align - 1 + sizeof(void*)) + char* object; + if ((object = malloc(EXTRA_ALIGN_PTR_BYTES + size)) == NULL) + abort(); + base = (void*)(((uintptr_t)(object + EXTRA_ALIGN_PTR_BYTES)) + & ~(uintptr_t)(align - 1)); + + ((void**)base)[-1] = object; +#endif + return base; +} + +static inline void emutls_memalign_free(void* base) { +#if EMUTLS_USE_POSIX_MEMALIGN + free(base); +#else + /* The mallocated address is in ((void**)base)[-1] */ + free(((void**)base)[-1]); +#endif +} + +/* Emulated TLS objects are always allocated at run-time. */ +static inline void* emutls_allocate_object(__emutls_control* control) { + /* Use standard C types, check with gcc's emutls.o. */ + //typedef unsigned int gcc_word __attribute__((mode(word))); + //typedef unsigned int gcc_pointer __attribute__((mode(pointer))); + //COMPILE_TIME_ASSERT(sizeof(size_t) == sizeof(gcc_word)); + //COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(gcc_pointer)); + //COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(void*)); + + size_t size = control->size; + size_t align = control->align; + if (align < sizeof(void*)) + align = sizeof(void*); + /* Make sure that align is power of 2. */ + if ((align & (align - 1)) != 0) + abort(); + + void* base = emutls_memalign_alloc(align, size); + if (control->value) + memcpy(base, control->value, size); + else + memset(base, 0, size); + return base; +} + +static pthread_mutex_t emutls_mutex = PTHREAD_MUTEX_INITIALIZER; + +static size_t emutls_num_object = 0; /* number of allocated TLS objects */ + +typedef struct emutls_address_array { + uintptr_t size; /* number of elements in the 'data' array */ + void* data[]; +} emutls_address_array; + +static pthread_key_t emutls_pthread_key; + +static void emutls_key_destructor(void* ptr) { + emutls_address_array* array = (emutls_address_array*)ptr; + uintptr_t i; + for (i = 0; i < array->size; ++i) { + if (array->data[i]) + emutls_memalign_free(array->data[i]); + } + free(ptr); +} + +static void emutls_init(void) { + if (pthread_key_create(&emutls_pthread_key, emutls_key_destructor) != 0) + abort(); +} + +/* Returns control->object.index; set index if not allocated yet. */ +static inline uintptr_t emutls_get_index(__emutls_control* control) { + uintptr_t index = __atomic_load_n(&control->object.index, __ATOMIC_ACQUIRE); + if (!index) { + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, emutls_init); + pthread_mutex_lock(&emutls_mutex); + index = control->object.index; + if (!index) { + index = ++emutls_num_object; + __atomic_store_n(&control->object.index, index, __ATOMIC_RELEASE); + } + pthread_mutex_unlock(&emutls_mutex); + } + return index; +} + +/* Updates newly allocated thread local emutls_address_array. */ +static inline void emutls_check_array_set_size(emutls_address_array* array, + uintptr_t size) { + if (array == NULL) + abort(); + array->size = size; + pthread_setspecific(emutls_pthread_key, (void*)array); +} + +/* Returns the new 'data' array size, number of elements, + * which must be no smaller than the given index. + */ +static inline uintptr_t emutls_new_data_array_size(uintptr_t index) { + /* Need to allocate emutls_address_array with one extra slot + * to store the data array size. + * Round up the emutls_address_array size to multiple of 16. + */ + return ((index + 1 + 15) & ~((uintptr_t)15)) - 1; +} + +/* Returns the thread local emutls_address_array. + * Extends its size if necessary to hold address at index. + */ +static inline emutls_address_array* emutls_get_address_array(uintptr_t index) { + emutls_address_array* array = pthread_getspecific(emutls_pthread_key); + if (array == NULL) { + uintptr_t new_size = emutls_new_data_array_size(index); + array = calloc(new_size + 1, sizeof(void*)); + emutls_check_array_set_size(array, new_size); + } else if (index > array->size) { + uintptr_t orig_size = array->size; + uintptr_t new_size = emutls_new_data_array_size(index); + array = realloc(array, (new_size + 1) * sizeof(void*)); + if (array) + memset(array->data + orig_size, 0, + (new_size - orig_size) * sizeof(void*)); + emutls_check_array_set_size(array, new_size); + } + return array; +} + +void* __emutls_get_address(__emutls_control* control) { + uintptr_t index = emutls_get_index(control); + emutls_address_array* array = emutls_get_address_array(index); + if (array->data[index - 1] == NULL) + array->data[index - 1] = emutls_allocate_object(control); + return array->data[index - 1]; +} diff --git a/externals/ps4sup/stub.cpp b/externals/ps4sup/stub.cpp new file mode 100644 index 0000000000..ebaef27acc --- /dev/null +++ b/externals/ps4sup/stub.cpp @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#define STUB_WEAK(name) \ + extern "C" void name() { \ + printf("called " #name); \ + asm volatile("ud2"); \ + } + +extern "C" int __pthread_cxa_finalize(); +extern "C" void __cxa_thread_atexit_impl() { + //printf("__cxa_thread_atexit_impl called!\n"); + //__pthread_cxa_finalize(); +} + +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; +FILE* __stdinp = stdin; + +#undef STUB_WEAK + +// THIS MAKES STD::COUT AND SUCH WORK :) +#include +std::ios_base::Init init; diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 4d0b130afc..afe7f9cc99 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -254,4 +254,10 @@ else() target_compile_definitions(audio_core PRIVATE HAVE_SDL3) endif() +if(PLATFORM_PS4) + target_sources(audio_core PRIVATE + sink/ps4_sink.cpp + sink/ps4_sink.h) +endif() + create_target_directory_groups(audio_core) diff --git a/src/audio_core/sink/ps4_sink.cpp b/src/audio_core/sink/ps4_sink.cpp new file mode 100644 index 0000000000..99fe636282 --- /dev/null +++ b/src/audio_core/sink/ps4_sink.cpp @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include + +#include "audio_core/common/common.h" +#include "audio_core/sink/ps4_sink.h" +#include "audio_core/sink/sink_stream.h" +#include "common/alignment.h" +#include "common/logging.h" +#include "common/scope_exit.h" +#include "core/core.h" + +namespace AudioCore::Sink { + +/// @brief PS4 sink stream, responsible for sinking samples to hardware. +struct PS4SinkStream final : public SinkStream { + /// @brief Create a new sink stream. + /// @param device_channels_ - Number of channels supported by the hardware. + /// @param system_channels_ - Number of channels the audio systems expect. + /// @param output_device - Name of the output device to use for this stream. + /// @param input_device - Name of the input device to use for this stream. + /// @param type_ - Type of this stream. + /// @param system_ - Core system. + /// @param event - Event used only for audio renderer, signalled on buffer consume. + PS4SinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device, const std::string& input_device, StreamType type_, Core::System& system_) + : SinkStream{system_, type_} + { + system_channels = system_channels_; + device_channels = device_channels_; + + audio_dev = sceAudioOutOpen(ORBIS_USER_SERVICE_USER_ID_SYSTEM, ORBIS_AUDIO_OUT_PORT_TYPE_MAIN, 0, Common::AlignUp(TargetSampleCount, 0x100), TargetSampleRate, device_channels > 1 ? ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_STEREO : ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_MONO); + if (audio_dev > 0) { + audio_thread = std::jthread([=, this](std::stop_token stop_token) { + std::array buffer; + while (!stop_token.stop_requested()) { + if (this->type == StreamType::In) { + this->ProcessAudioIn(buffer, TargetSampleCount); + (void)buffer; // TODO: microphone support + } else { + int err = 0; + std::fill(buffer.begin(), buffer.end(), 0); + this->ProcessAudioOutAndRender(buffer, TargetSampleCount); + sceAudioOutOutput(audio_dev, nullptr); + if ((err = sceAudioOutOutput(audio_dev, buffer.data())) < 0) + LOG_ERROR(Service_Audio, "{}", err); + } + // Wait for pause, we don't really have a native pause + // so this is the best i can do + while (paused && !stop_token.stop_requested()) + ; + } + sceAudioOutClose(audio_dev); + }); + } else { + LOG_ERROR(Service_Audio, "Failed to create audio device! {:#x}", uint32_t(audio_dev)); + } + } + + ~PS4SinkStream() override { + LOG_DEBUG(Service_Audio, "Destroying PS4 stream {}", name); + if (audio_thread.joinable()) { + audio_thread.request_stop(); + audio_thread.join(); + } + } + + void Finalize() override { + if (audio_dev > 0) { + + } + } + + void Start(bool resume = false) override { + if (audio_dev > 0 && paused) { + paused = false; + } + } + + void Stop() override { + if (audio_dev > 0 && !paused) { + SignalPause(); + } + } + + std::jthread audio_thread; + int32_t audio_dev{}; +}; + +PS4Sink::PS4Sink(std::string_view target_device_name) { + int32_t rc = sceAudioOutInit(); + if (rc == 0 || unsigned(rc) == ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT) { + if (target_device_name != auto_device_name && !target_device_name.empty()) { + output_device = target_device_name; + } else { + output_device.clear(); + } + device_channels = 2; + } else { + LOG_ERROR(Service_Audio, "Unable to open audio out! {:#x}", uint32_t(rc)); + } +} + +PS4Sink::~PS4Sink() = default; + +/// @brief Create a new sink stream. +/// @param system - Core system. +/// @param system_channels - Number of channels the audio system expects. May differ from the device's channel count. +/// @param name - Name of this stream. +/// @param type - Type of this stream, render/in/out. +/// @return A pointer to the created SinkStream +SinkStream* PS4Sink::AcquireSinkStream(Core::System& system, u32 system_channels_, const std::string&, StreamType type) { + system_channels = system_channels_; + SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique(device_channels, system_channels, output_device, input_device, type, system)); + return stream.get(); +} + +void PS4Sink::CloseStream(SinkStream* stream) { + for (size_t i = 0; i < sink_streams.size(); i++) { + if (sink_streams[i].get() == stream) { + sink_streams[i].reset(); + sink_streams.erase(sink_streams.begin() + i); + break; + } + } +} + +void PS4Sink::CloseStreams() { + sink_streams.clear(); +} + +f32 PS4Sink::GetDeviceVolume() const { + return sink_streams.size() > 0 ? sink_streams[0]->GetDeviceVolume() : 1.f; +} + +void PS4Sink::SetDeviceVolume(f32 volume) { + for (auto& stream : sink_streams) + stream->SetDeviceVolume(volume); +} + +void PS4Sink::SetSystemVolume(f32 volume) { + for (auto& stream : sink_streams) + stream->SetSystemVolume(volume); +} + +std::vector ListPS4SinkDevices(bool capture) { + return {{"Default"}}; +} + +u32 GetPS4Latency() { + return TargetSampleCount; +} + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/ps4_sink.h b/src/audio_core/sink/ps4_sink.h new file mode 100644 index 0000000000..944e7f9b7a --- /dev/null +++ b/src/audio_core/sink/ps4_sink.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#pragma once + +#include +#include + +#include "audio_core/sink/sink.h" + +namespace Core { +class System; +} + +namespace AudioCore::Sink { +class SinkStream; + +/// @brief PS4 backend sink, holds multiple output streams and is responsible for sinking samples to +/// hardware. Used by Audio Render, Audio In and Audio Out. +struct PS4Sink final : public Sink { + explicit PS4Sink(std::string_view device_id); + ~PS4Sink() override; + SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, const std::string& name, StreamType type) override; + void CloseStream(SinkStream* stream) override; + void CloseStreams() override; + f32 GetDeviceVolume() const override; + void SetDeviceVolume(f32 volume) override; + void SetSystemVolume(f32 volume) override; + /// Name of the output device used by streams + std::string output_device; + /// Name of the input device used by streams + std::string input_device; + /// Vector of streams managed by this sink + std::vector sink_streams; +}; + +std::vector ListPS4SinkDevices(bool capture); +u32 GetPS4Latency(); + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index 1b737eb12e..9fe182d6fa 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp @@ -19,6 +19,9 @@ #ifdef HAVE_SDL3 #include "audio_core/sink/sdl3_sink.h" #endif +#ifdef __OPENORBIS__ +#include "audio_core/sink/ps4_sink.h" +#endif #include "audio_core/sink/null_sink.h" #include "common/logging.h" #include "common/settings_enums.h" @@ -51,6 +54,16 @@ struct SinkDetails { // sink_details is ordered in terms of desirability, with the best choice at the top. constexpr SinkDetails sink_details[] = { +#ifdef __OPENORBIS__ + SinkDetails{ + Settings::AudioEngine::Ps4, + [](std::string_view device_id) -> std::unique_ptr { + return std::make_unique(device_id); + }, + &ListPS4SinkDevices, + &GetPS4Latency, + }, +#endif #ifdef HAVE_OBOE SinkDetails{ Settings::AudioEngine::Oboe, @@ -115,7 +128,9 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { // BEGIN REINTRODUCED FROM 3833 - REPLACED CODE BLOCK ABOVE - DIABLO 3 FIX // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which // causes audio issues, in that case go with SDL. -#if defined(HAVE_CUBEB) && defined(HAVE_SDL3) +#if defined(__OPENORBIS__) + iter = find_backend(Settings::AudioEngine::Ps4); +#elif defined(HAVE_CUBEB) && defined(HAVE_SDL3) iter = find_backend(Settings::AudioEngine::Cubeb); if (iter->latency() > TargetSampleCount * 3) { iter = find_backend(Settings::AudioEngine::Sdl3); diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp index 69eca732eb..ef635aa307 100644 --- a/src/common/fiber.cpp +++ b/src/common/fiber.cpp @@ -16,7 +16,7 @@ namespace Common { #ifdef __OPENORBIS__ -constexpr size_t DEFAULT_STACK_SIZE = 128 * 4096; +constexpr size_t DEFAULT_STACK_SIZE = 64 * 4096; #else constexpr size_t DEFAULT_STACK_SIZE = 512 * 4096; #endif diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 854090e7b2..96fb483dbf 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -20,6 +20,9 @@ #else #include #endif +#ifdef __OPENORBIS__ +#include +#endif #ifdef _MSC_VER #define fileno _fileno @@ -402,28 +405,27 @@ u64 IOFile::GetSize() const { file_size = Android::GetSize(file_path); } else { std::error_code ec; - file_size = fs::file_size(file_path, ec); - if (ec) { - LOG_ERROR(Common_Filesystem, - "Failed to retrieve the file size of path={}, ec_message={}", - PathToUTF8String(file_path), ec.message()); + LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", PathToUTF8String(file_path), ec.message()); return 0; } } +#elif defined(__OPENORBIS__) + // TODO: implementation of fs::file_size() is buggy on PS4, why? + // probably toolchain issue... ugh + OrbisKernelStat st{}; + stat(file_path.c_str(), reinterpret_cast(std::addressof(st))); + auto const file_size = st.st_size; + LOG_DEBUG(Common_Filesystem, "size for {} = {}", file_path.c_str(), file_size); #else std::error_code ec; - const auto file_size = fs::file_size(file_path, ec); - if (ec) { - LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", - PathToUTF8String(file_path), ec.message()); + LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", PathToUTF8String(file_path), ec.message()); return 0; } #endif - return file_size; } diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index 3858b0f0c3..cc88f5f869 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp @@ -174,11 +174,14 @@ 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", diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 714e393293..62e765aa6c 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -130,6 +130,10 @@ 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)) { @@ -169,12 +173,11 @@ public: #ifdef _WIN32 GenerateLegacyPath(EmuPath::RyujinxDir, GetAppDataRoamingDirectory() / RYUJINX_DIR); -#else +#elif !defined(__OPENORBIS__) // In Ryujinx's infinite wisdom, it places EVERYTHING in the config directory on UNIX // This is incredibly stupid and violates a million XDG standards, but whatever GenerateLegacyPath(EmuPath::RyujinxDir, GetDataDirectory("XDG_CONFIG_HOME") / RYUJINX_DIR); #endif - } private: @@ -336,36 +339,33 @@ fs::path GetAppDataRoamingDirectory() { return fs_appdata_roaming_path; } +#elif defined(__OPENORBIS__) + +// Not required/defined + #else fs::path GetHomeDirectory() { const char* home_env_var = getenv("HOME"); - if (home_env_var) { return fs::path{home_env_var}; } - LOG_INFO(Common_Filesystem, - "$HOME is not defined in the environment variables, " - "attempting to query passwd to get the home path of the current user"); - + "$HOME is not defined in the environment variables, " + "attempting to query passwd to get the home path of the current user"); const auto* pw = getpwuid(getuid()); - if (!pw) { LOG_ERROR(Common_Filesystem, "Failed to get the home path of the current user"); return {}; } - return fs::path{pw->pw_dir}; } fs::path GetDataDirectory(const std::string& env_name) { const char* data_env_var = getenv(env_name.c_str()); - if (data_env_var) { return fs::path{data_env_var}; } - if (env_name == "XDG_DATA_HOME") { return GetHomeDirectory() / ".local/share"; } else if (env_name == "XDG_CACHE_HOME") { @@ -373,7 +373,6 @@ fs::path GetDataDirectory(const std::string& env_name) { } else if (env_name == "XDG_CONFIG_HOME") { return GetHomeDirectory() / ".config"; } - return {}; } diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 04f3a65778..d60df0fd5a 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -32,19 +32,21 @@ #include #elif defined(__FreeBSD__) #include +#elif defined(__OPENORBIS__) +#include #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 ^^^ @@ -391,6 +393,8 @@ private: ankerl::unordered_dense::map placeholder_host_pointers; ///< Placeholder backing offset }; +#elif defined(__OPENORBIS__) +// None of the luxuries of POSIX #else // ^^^ Windows ^^^ vvv POSIX vvv #ifdef ARCHITECTURE_arm64 @@ -435,13 +439,15 @@ static void* ChooseVirtualBase(size_t virtual_size) { #else -static void* ChooseVirtualBase(size_t virtual_size) { +static void* ChooseVirtualBase(size_t size) { #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) || defined(__AIX__) - void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0); + void* virtual_base = mmap(nullptr, 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 @@ -497,9 +503,10 @@ class HostMemory::Impl { public: explicit Impl(size_t backing_size_, size_t virtual_size_) : backing_size{backing_size_}, virtual_size{virtual_size_} { +#if !defined(__OPENORBIS__) && !defined(__APPLE__) long page_size = sysconf(_SC_PAGESIZE); - ASSERT_MSG(page_size == 0x1000, "page size {:#x} is incompatible with 4K paging", - page_size); + ASSERT_MSG(page_size == 0x1000, "page size {:#x} is incompatible with 4K paging", page_size); +#endif // 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); @@ -669,7 +676,16 @@ private: #endif // ^^^ POSIX ^^^ -HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) : backing_size(backing_size_), virtual_size(virtual_size_) { +HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) + : backing_size(backing_size_) + , virtual_size(virtual_size_) +{ +#ifdef __OPENORBIS__ + 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(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize); @@ -680,6 +696,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) : backing_siz virtual_base = reinterpret_cast(Common::AlignUp(uintptr_t(virtual_base), HugePageSize)); virtual_base_offset = virtual_base - impl->virtual_base; } +#endif } HostMemory::~HostMemory() = default; @@ -688,30 +705,32 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default; HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default; -void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, - MemoryPermission perms, bool separate_heap) { +void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms, bool separate_heap) { +#ifndef __OPENORBIS__ ASSERT(virtual_offset % PageAlignment == 0); ASSERT(host_offset % PageAlignment == 0); ASSERT(length % PageAlignment == 0); ASSERT(virtual_offset + length <= virtual_size); ASSERT(host_offset + length <= backing_size); - if (length == 0 || !virtual_base || !impl) { + if (length == 0 || !virtual_base || !impl) return; - } impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms); +#endif } void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) { +#ifndef __OPENORBIS__ ASSERT(virtual_offset % PageAlignment == 0); ASSERT(length % PageAlignment == 0); ASSERT(virtual_offset + length <= virtual_size); - if (length == 0 || !virtual_base || !impl) { + if (length == 0 || !virtual_base || !impl) return; - } impl->Unmap(virtual_offset + virtual_base_offset, length); +#endif } void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) { +#ifndef __OPENORBIS__ ASSERT(virtual_offset % PageAlignment == 0); ASSERT(length % PageAlignment == 0); ASSERT(virtual_offset + length <= virtual_size); @@ -722,6 +741,7 @@ void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission const bool write = True(perm & MemoryPermission::Write); const bool execute = True(perm & MemoryPermission::Execute); impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute); +#endif } void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value) { @@ -729,10 +749,12 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f } void HostMemory::EnableDirectMappedAddress() { +#ifndef __OPENORBIS__ if (impl) { impl->EnableDirectMappedAddress(); virtual_size += reinterpret_cast(virtual_base); } +#endif } } // namespace Common diff --git a/src/common/host_memory.h b/src/common/host_memory.h index 8dd30aa9de..2a908e71b4 100644 --- a/src/common/host_memory.h +++ b/src/common/host_memory.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -7,6 +7,7 @@ #pragma once #include +#include #include "common/common_funcs.h" #include "common/common_types.h" #include "common/virtual_buffer.h" @@ -73,15 +74,19 @@ public: } private: - size_t backing_size{}; - size_t virtual_size{}; - // Low level handler for the platform dependent memory routines +#ifndef __OPENORBIS__ class Impl; std::unique_ptr impl; +#endif + size_t backing_size{}; + size_t virtual_size{}; u8* backing_base{}; u8* virtual_base{}; size_t virtual_base_offset{}; +#ifdef __OPENORBIS__ + std::optional> fallback_buffer; +#endif }; } // namespace Common diff --git a/src/common/httplib.h b/src/common/httplib.h index 57bc4eeb93..b600b6739e 100644 --- a/src/common/httplib.h +++ b/src/common/httplib.h @@ -3,8 +3,14 @@ #pragma once +// TODO: httplib port can't use OpenSSL because of package misconfigs on OO toolchain +// right now the solution is to tell httplib that OpenSSL doesn't exist, but that's bad +// and the issue is a bit more of how OpenSSL lacks some essential functionality +// which is a bit annoying to reconfigure atm. +#ifndef __OPENORBIS__ #define CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES 1 #define CPPHTTPLIB_OPENSSL_SUPPORT 1 +#endif #ifdef __GNUC__ #pragma GCC diagnostic push diff --git a/src/common/logging.cpp b/src/common/logging.cpp index 68e033b129..cca2663b51 100644 --- a/src/common/logging.cpp +++ b/src/common/logging.cpp @@ -243,7 +243,6 @@ struct ColorConsoleBackend final : public Backend { #endif }; -#ifndef __OPENORBIS__ /// @brief Backend that writes to a file passed into the constructor struct FileBackend final : public Backend { explicit FileBackend(const std::filesystem::path& filename) noexcept { @@ -307,7 +306,6 @@ private: std::size_t bytes_written = 0; bool enabled = true; }; -#endif #ifdef _WIN32 /// @brief Backend that writes to Visual Studio's output window @@ -352,10 +350,8 @@ struct Impl { template void ForEachBackend(F&& lambda) noexcept { lambda(static_cast(color_console_backend)); -#ifndef __OPENORBIS__ if (file_backend) lambda(static_cast(*file_backend)); -#endif #ifdef _WIN32 lambda(static_cast(debugger_backend)); #endif @@ -366,9 +362,7 @@ struct Impl { Filter filter; ColorConsoleBackend color_console_backend{}; -#ifndef __OPENORBIS__ std::optional file_backend; -#endif #ifdef _WIN32 DebuggerBackend debugger_backend{}; #endif @@ -389,12 +383,10 @@ void Initialize() { } else { logging_instance.emplace(); logging_instance->filter.ParseFilterString(Settings::values.log_filter.GetValue()); -#ifndef __OPENORBIS__ using namespace Common::FS; const auto& log_dir = GetEdenPath(EdenPath::LogDir); void(CreateDir(log_dir)); logging_instance->file_backend.emplace(log_dir / LOG_FILE); -#endif } } diff --git a/src/common/memory_detect.cpp b/src/common/memory_detect.cpp index 86a3abcc6d..65af4c9cc8 100644 --- a/src/common/memory_detect.cpp +++ b/src/common/memory_detect.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -8,7 +11,7 @@ // clang-format on #else #include -#if defined(__APPLE__) || defined(__FreeBSD__) +#if defined(__APPLE__) || (defined(__FreeBSD__) && !defined(__OPENORBIS__)) #include #elif defined(__linux__) #include diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 61f9acb1e7..813bda5713 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -27,7 +27,7 @@ #include "common/settings.h" #include "common/time_zone.h" -#if defined(__linux__ ) && defined(ARCHITECTURE_arm64) +#ifdef __unix__ #include #endif @@ -182,12 +182,9 @@ bool IsFastmemEnabled() { return bool(values.cpuopt_fastmem); else if (values.cpu_accuracy.GetValue() == CpuAccuracy::Unsafe) return bool(values.cpuopt_unsafe_host_mmu); -#if defined(__linux__) && defined(ARCHITECTURE_arm64) - // Only 4kb systems support host MMU right now - // TODO: Support this +#if defined(__unix__) + // TODO: Support this since only 4kb systems support host MMU right now return getpagesize() == 4096; -#elif !defined(__APPLE__) && !defined(__ANDROID__) && !defined(_WIN32) && !defined(__linux__) && !defined(__FreeBSD__) - return false; #else return true; #endif diff --git a/src/common/settings.h b/src/common/settings.h index e8877fce1e..11d3ec1c7e 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -196,7 +196,14 @@ struct Values { linkage, false, "dump_audio_commands", Category::Audio, Specialization::Default, false}; // Core - SwitchableSetting use_multi_core{linkage, true, "use_multi_core", Category::Core}; + SwitchableSetting use_multi_core{linkage, +#ifdef __OPENORBIS__ + // Re-enable once proper TLS support is added + false, +#else + true, +#endif + "use_multi_core", Category::Core}; SwitchableSetting memory_layout_mode{linkage, MemoryLayout::Memory_4Gb, "memory_layout_mode", @@ -330,7 +337,7 @@ struct Values { // Renderer SwitchableSetting renderer_backend{linkage, -#if defined(__sun__) || defined(__managarm__) +#if defined(__sun__) || defined(__managarm__) || defined(__OPENORBIS__) RendererBackend::OpenGL_GLSL, #else RendererBackend::Vulkan, diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 9a406e432b..331b4c51f6 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -92,7 +92,7 @@ struct EnumMetadata { // AudioEngine must be specified discretely due to having existing but slightly different // canonicalizations // TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id -enum class AudioEngine : u32 { Auto, Cubeb, Sdl3, Null, Oboe, }; +enum class AudioEngine : u32 { Auto, Cubeb, Sdl3, Null, Oboe, Ps4, }; template<> inline std::vector> EnumMetadata::Canonicalizations() { return { @@ -100,6 +100,7 @@ inline std::vector> EnumMetadata #endif +#ifdef __OPENORBIS__ +#include +#include +#include +#include +#include +#include +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.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 { -void* AllocateMemoryPages(std::size_t size) noexcept { -#ifdef _WIN32 - void* base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE); -#else - void* base = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); - if (base == MAP_FAILED) - base = nullptr; -#endif - ASSERT(base); - return base; +#ifdef __OPENORBIS__ + +namespace Orbis { +constexpr size_t ORBIS_PAGE_SIZE = 16384; + +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]; +}; } -void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept { - if (!base) +static boost::container::static_vector, 16> swap_regions; +static std::mutex evil_swap_mutex; +extern "C" int sceKernelRemoveExceptionHandler(s32 sig_num); +static void SwapHandler(int sig, void* raw_context) { + std::unique_lock lk{evil_swap_mutex}; + auto& mctx = ((Orbis::Ucontext*)raw_context)->uc_mcontext; + if (auto const it = 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; + }); it != swap_regions.end()) { + size_t const page_size = Orbis::ORBIS_PAGE_SIZE * 128; //128K + size_t const page_mask = ~(page_size - 1); + // should replace the existing mapping... ugh + void* aligned_addr = reinterpret_cast(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); + if (res == MAP_FAILED) { + LOG_ERROR(HW_Memory, "{:#x},{} @ {}:{:#x}", mctx.mc_addr, aligned_addr, it->first, it->second); + sceKernelRemoveExceptionHandler(SIGSEGV); // to not catch the next signal + } else { + LOG_DEBUG(HW_Memory, "{:#x},{} @ {}:{:#x}", mctx.mc_addr, aligned_addr, it->first, it->second); + } + } 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__) + bool use_void_mem = true; + void* addr = nullptr; + if (size < 4294967296ull) { + size_t align = Orbis::ORBIS_PAGE_SIZE; + off_t offset; + int32_t res; + size = (size + align - 1) / align * align; + if ((res = sceKernelAllocateDirectMemory(0, ORBIS_KERNEL_MAIN_DMEM_SIZE, size, align, ORBIS_KERNEL_WB_ONION, &offset)) == 0) { + if ((res = sceKernelMapDirectMemory(&addr, size, ORBIS_KERNEL_PROT_CPU_READ | ORBIS_KERNEL_PROT_CPU_WRITE, 0, offset, size)) == 0) { + if ((res = sceKernelMprotect(addr, size, VM_PROT_ALL)) == 0 && addr != nullptr) { + LOG_WARNING(HW_Memory, "Using DMem for {} bytes area @ {}", size, addr); + use_void_mem = false; //Memory properly mapped + } else { + sceKernelReleaseDirectMemory(offset, size); + LOG_ERROR(HW_Memory, "{} = sceKernelMprotect({}, {})", res, offset, size); + } + } else { + sceKernelReleaseDirectMemory(offset, size); + LOG_ERROR(HW_Memory, "{} = sceKernelMapDirectMemory({}, {})", res, offset, size); + } + } else { + LOG_ERROR(HW_Memory, "{} = sceKernelAllocateDirectMemory({}, {}, {})", res, size, align, offset); + } + } + if (use_void_mem) { + addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_VOID | MAP_PRIVATE, -1, 0); + LOG_WARNING(HW_Memory, "Using VoidMem for {} bytes area @ {}", size, addr); + ASSERT(addr != MAP_FAILED); + swap_regions.emplace_back(addr, size); + } +#else + void* addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + ASSERT(addr != MAP_FAILED); +#endif + return addr; +} + +void FreeMemoryPages(void* addr, [[maybe_unused]] std::size_t size) noexcept { + if (!addr) return; #ifdef _WIN32 - ASSERT(VirtualFree(base, 0, MEM_RELEASE)); + VirtualFree(addr, 0, MEM_RELEASE); +#elif defined(__OPENORBIS__) + if (auto const it = std::ranges::find_if(swap_regions, [=](auto const& e) { + return uintptr_t(addr) >= uintptr_t(e.first) && uintptr_t(addr) < uintptr_t(e.first) + e.second; + }); it != swap_regions.end()) { + int rc = munmap(addr, size); + ASSERT(rc == 0); + } else { + sceKernelCheckedReleaseDirectMemory(off_t(addr), size_t(size)); + } #else - ASSERT(munmap(base, size) == 0); + int rc = munmap(addr, size); + ASSERT(rc == 0); #endif } diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h index af96317288..adfdbb75d4 100644 --- a/src/common/virtual_buffer.h +++ b/src/common/virtual_buffer.h @@ -12,6 +12,7 @@ namespace Common { void* AllocateMemoryPages(std::size_t size) noexcept; void FreeMemoryPages(void* base, std::size_t size) noexcept; +void InitSwap() noexcept; template class VirtualBuffer final { @@ -38,7 +39,8 @@ public: 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} + : alloc_size{std::exchange(other.alloc_size, 0)} + , base_ptr{std::exchange(other.base_ptr, nullptr)} {} VirtualBuffer& operator=(VirtualBuffer&& other) noexcept { diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index e434a31d5d..bf473eb07b 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -199,7 +199,9 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) { config.enable_cycle_counting = !m_uses_wall_clock; // Code cache size -#if defined(ARCHITECTURE_arm64) || defined(__sun__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) +#if defined(__OPENORBIS__) + config.code_cache_size = std::uint32_t(8_MiB); +#elif defined(ARCHITECTURE_arm64) || defined(__sun__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) config.code_cache_size = std::uint32_t(128_MiB); #else config.code_cache_size = std::uint32_t(512_MiB); diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index e6c7e61265..88bd0cfad9 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -252,7 +252,9 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s config.enable_cycle_counting = !m_uses_wall_clock; // Code cache size -#if defined(ARCHITECTURE_arm64) || defined(__sun__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) +#if defined(__OPENORBIS__) + config.code_cache_size = std::uint32_t(8_MiB); +#elif defined(ARCHITECTURE_arm64) || defined(__sun__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) config.code_cache_size = std::uint32_t(128_MiB); #else config.code_cache_size = std::uint32_t(512_MiB); diff --git a/src/core/file_sys/vfs/vfs_real.cpp b/src/core/file_sys/vfs/vfs_real.cpp index 1eafff6c06..60997a8a65 100644 --- a/src/core/file_sys/vfs/vfs_real.cpp +++ b/src/core/file_sys/vfs/vfs_real.cpp @@ -449,8 +449,19 @@ std::vector RealVfsDirectory::GetFiles() const { FileTimeStampRaw RealVfsDirectory::GetFileTimeStamp(std::string_view path_) const { const auto full_path = FS::SanitizePath(path + '/' + std::string(path_)); +#ifdef __OPENORBIS__ + // DO NOT USE NORMAL STAT, it's bugged on OO toolchain + OrbisKernelStat st{}; + const auto stat_result = stat(full_path.c_str(), reinterpret_cast(std::addressof(st))); + if (stat_result != 0) + return {}; + return { + .created{u64(st.st_ctime)}, + .accessed{u64(st.st_atime)}, + .modified{u64(st.st_mtime)}, + }; +#else const auto fs_path = std::filesystem::path{FS::ToU8String(full_path)}; - #ifdef _WIN32 struct _stat64 file_status; const auto stat_result = _wstat64(fs_path.c_str(), &file_status); @@ -458,16 +469,14 @@ FileTimeStampRaw RealVfsDirectory::GetFileTimeStamp(std::string_view path_) cons struct stat file_status; const auto stat_result = stat(fs_path.c_str(), &file_status); #endif - - if (stat_result != 0) { + if (stat_result != 0) return {}; - } - return { - .created{static_cast(file_status.st_ctime)}, - .accessed{static_cast(file_status.st_atime)}, - .modified{static_cast(file_status.st_mtime)}, + .created{u64(file_status.st_ctime)}, + .accessed{u64(file_status.st_atime)}, + .modified{u64(file_status.st_mtime)}, }; +#endif } std::vector RealVfsDirectory::GetSubdirectories() const { diff --git a/src/core/hle/service/ns/platform_service_manager.cpp b/src/core/hle/service/ns/platform_service_manager.cpp index bf93a2edfa..64f0fab454 100644 --- a/src/core/hle/service/ns/platform_service_manager.cpp +++ b/src/core/hle/service/ns/platform_service_manager.cpp @@ -34,12 +34,13 @@ struct FontRegion { // The below data is specific to shared font data dumped from Switch on f/w 2.2 // Virtual address and offsets/sizes likely will vary by dump -[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; -constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be -constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be -constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; +[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR = 0x00000009d3016000ULL; +constexpr u32 EXPECTED_RESULT = 0x7f9a0218; // What we expect the decrypted bfttf first 4 bytes to be +constexpr u32 EXPECTED_MAGIC = 0x36f81a1e; // What we expect the encrypted bfttf first 4 bytes to be +constexpr u64 SHARED_FONT_MEM_SIZE = 0x1100000; constexpr FontRegion EMPTY_REGION{0, 0}; +#ifndef __OPENORBIS__ static void DecryptSharedFont(const std::span input, std::span output, std::size_t& offset) { ASSERT(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE && "Shared fonts exceeds 17mb!"); ASSERT(input[0] == EXPECTED_MAGIC && "Failed to derive key, unexpected magic number"); @@ -51,6 +52,7 @@ static void DecryptSharedFont(const std::span input, std::span ou std::memcpy(output.data() + offset, transformed_font.data(), transformed_font.size() * sizeof(u32)); offset += transformed_font.size() * sizeof(u32); } +#endif void DecryptSharedFontToTTF(const std::vector& input, std::vector& output) { ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); @@ -70,7 +72,7 @@ void EncryptSharedFont(const std::vector& input, std::vector& output, s const auto key = Common::swap32(EXPECTED_RESULT ^ EXPECTED_MAGIC); std::vector transformed_font(input.size() + 2); transformed_font[0] = Common::swap32(EXPECTED_MAGIC); - transformed_font[1] = Common::swap32(static_cast(input.size() * sizeof(u32))) ^ key; + transformed_font[1] = Common::swap32(u32(input.size() * sizeof(u32))) ^ key; std::transform(input.begin(), input.end(), transformed_font.begin() + 2, [key](u32 in) { return in ^ key; }); std::memcpy(output.data() + offset, transformed_font.data(), transformed_font.size() * sizeof(u32)); offset += transformed_font.size() * sizeof(u32); @@ -83,8 +85,10 @@ struct IPlatformServiceManager::Impl { // Automatically populated based on shared_fonts dump or system archives. // 6 builtin fonts + extra 2 for whatever may come after boost::container::static_vector shared_font_regions; +#ifndef __OPENORBIS__ /// Backing memory for the shared font data std::array shared_font; +#endif }; IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const char* service_name_) @@ -111,49 +115,40 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch // clang-format on RegisterHandlers(functions); +#ifndef __OPENORBIS__ auto& fsc = system.GetFileSystemController(); - // Attempt to load shared font data from disk const auto* nand = fsc.GetSystemNANDContents(); std::size_t offset = 0; // Rebuild shared fonts from data ncas or synthesize for (auto& font : SHARED_FONTS) { FileSys::VirtualFile romfs; - const auto nca = - nand->GetEntry(static_cast(font.first), FileSys::ContentRecordType::Data); - if (nca) { + if (auto const nca = nand->GetEntry(u64(font.first), FileSys::ContentRecordType::Data); nca) romfs = nca->GetRomFS(); - } - - if (!romfs) { - romfs = FileSys::SystemArchive::SynthesizeSystemArchive(static_cast(font.first)); - } - - if (!romfs) { + if (!romfs) + romfs = FileSys::SystemArchive::SynthesizeSystemArchive(u64(font.first)); + if (romfs) { + if (auto const extracted_romfs = FileSys::ExtractRomFS(romfs); extracted_romfs) { + if (auto const font_fp = extracted_romfs->GetFile(font.second); font_fp) { + std::vector font_data_u32(font_fp->GetSize() / sizeof(u32)); + font_fp->ReadBytes(font_data_u32.data(), font_fp->GetSize()); + // We need to be BigEndian as u32s for the xor encryption + std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(), Common::swap32); + // Font offset and size do not account for the header + const FontRegion region{u32(offset + 8), u32((font_data_u32.size() * sizeof(u32)) - 8)}; + DecryptSharedFont(font_data_u32, impl->shared_font, offset); + impl->shared_font_regions.push_back(region); + } else { + LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", font.first, font.second); + } + } else { + LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", font.first); + } + } else { LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", font.first); - continue; } - - const auto extracted_romfs = FileSys::ExtractRomFS(romfs); - if (!extracted_romfs) { - LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", font.first); - continue; - } - const auto font_fp = extracted_romfs->GetFile(font.second); - if (!font_fp) { - LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", font.first, font.second); - continue; - } - std::vector font_data_u32(font_fp->GetSize() / sizeof(u32)); - font_fp->ReadBytes(font_data_u32.data(), font_fp->GetSize()); - // We need to be BigEndian as u32s for the xor encryption - std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(), - Common::swap32); - // Font offset and size do not account for the header - const FontRegion region{u32(offset + 8), u32((font_data_u32.size() * sizeof(u32)) - 8)}; - DecryptSharedFont(font_data_u32, impl->shared_font, offset); - impl->shared_font_regions.push_back(region); } +#endif } IPlatformServiceManager::~IPlatformServiceManager() = default; @@ -187,8 +182,10 @@ Result IPlatformServiceManager::GetSharedMemoryNativeHandle(OutCopyHandleshared_font.data(), impl->shared_font.size()); +#endif // FIXME: this shouldn't belong to the kernel *out_shared_memory_native_handle = &kernel.GetFontSharedMem(); diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp index 636f54ad49..bf0a89dfef 100644 --- a/src/core/hle/service/services.cpp +++ b/src/core/hle/service/services.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -73,6 +73,13 @@ Services::Services(std::shared_ptr& sm, Core::System& system system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false); +#ifdef __OPENORBIS__ + // PS4 requires us to run this on a single thread so we don't immediately die + bool const run_on_host = false; +#else + bool const run_on_host = true; +#endif + // Just a quick C++ lesson // Capturing lambdas will silently create new variables for the objects referenced via = // and create a `auto&` sorts of for `&`; with all your usual reference shenanigans. @@ -92,9 +99,12 @@ Services::Services(std::shared_ptr& sm, Core::System& system {"Loader", &LDR::LoopProcess}, {"nvservices", &Nvidia::LoopProcess}, {"bsdsocket", &Sockets::LoopProcess}, - }) - kernel.RunOnHostCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }).detach(); - kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach(); + }) { + if (run_on_host) kernel.RunOnHostCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }).detach(); + else kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }); + } + if (run_on_host) kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach(); + else kernel.RunOnGuestCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }); // Avoid cold clones of lambdas -- succintly for (auto const& e : std::vector>{ {"sm", &SM::LoopProcess}, diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index dd6becc02d..fef83b2d4b 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -60,7 +63,11 @@ private: std::size_t current_index{0}; /// Stores an hour of historical frametime data useful for processing and tracking performance /// regressions with code changes. +#ifdef __OPENORBIS__ + std::array perf_history{}; +#else std::array perf_history{}; +#endif /// Point when the cumulative counters were reset Clock::time_point reset_point = Clock::now(); diff --git a/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp b/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp index 224559133a..c45efc6103 100644 --- a/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp +++ b/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp @@ -60,12 +60,16 @@ public: signal_stack_size = std::max(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, "dynarmic: POSIX SigHandler: init failure at sigaltstack\n"); + fmt::print(stderr, "POSIX SigHandler: init failure at sigaltstack\n"); supports_fast_mem = false; return; } @@ -76,16 +80,17 @@ 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, "dynarmic: POSIX SigHandler: could not set SIGSEGV handler\n"); + fmt::print(stderr, "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, "dynarmic: POSIX SigHandler: could not set SIGBUS handler\n"); + fmt::print(stderr, "POSIX SigHandler: could not set SIGBUS handler\n"); supports_fast_mem = false; return; } +# endif #endif } @@ -154,6 +159,9 @@ 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); @@ -167,6 +175,7 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) { return; } retry_sa->sa_handler(sig); +#endif } } // anonymous namespace diff --git a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.h b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.h index 8e97dc7737..6e6147e855 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.h +++ b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.h @@ -66,8 +66,14 @@ public: const void* code_ptr = nullptr; }; static_assert(sizeof(FastDispatchEntry) == 0x10); +#ifdef __OPENORBIS__ + static constexpr u64 fast_dispatch_table_mask = 0xFF0; + static constexpr size_t fast_dispatch_table_size = 0x100; +#else static constexpr u64 fast_dispatch_table_mask = 0xFFFF0; static constexpr size_t fast_dispatch_table_size = 0x10000; +#endif + void ClearFastDispatchTable(); void GenFastmemFallbacks(); void GenTerminalHandlers(); diff --git a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.h b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.h index d57b1d81b9..8bf73d7bb3 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.h +++ b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.h @@ -59,8 +59,13 @@ public: const void* code_ptr = nullptr; }; static_assert(sizeof(FastDispatchEntry) == 0x10); +#ifdef __OPENORBIS__ + static constexpr u64 fast_dispatch_table_mask = 0xFF0; + static constexpr size_t fast_dispatch_table_size = 0x100; +#else static constexpr u64 fast_dispatch_table_mask = 0xFFFFF0; static constexpr size_t fast_dispatch_table_size = 0x100000; +#endif void ClearFastDispatchTable(); void GenMemory128Accessors(); diff --git a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp index f41bea6c83..96f348955d 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp @@ -7,6 +7,7 @@ */ #include "dynarmic/backend/x64/block_of_code.h" +#include "xbyak/xbyak.h" #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN @@ -67,8 +68,12 @@ public: uint8_t* alloc(size_t size) override { void* p = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); if (p == nullptr) { +#ifndef XBYAK_NO_EXCEPTION using Xbyak::Error; XBYAK_THROW(Xbyak::ERR_CANT_ALLOC); +#else + std::abort(); +#endif } return static_cast(p); } @@ -79,13 +84,12 @@ public: bool useProtect() const override { return false; } #else - static constexpr size_t DYNARMIC_PAGE_SIZE = 4096; - // Can't subclass Xbyak::MmapAllocator because it is not a pure interface // and doesn't expose its construtor uint8_t* alloc(size_t size) override { + auto const page_size = Xbyak::inner::getPageSize(); // Waste a page to store the size - size += DYNARMIC_PAGE_SIZE; + size += page_size; int mode = MAP_PRIVATE; #if defined(MAP_ANONYMOUS) @@ -106,17 +110,22 @@ public: #endif void* p = mmap(nullptr, size, prot, mode, -1, 0); if (p == MAP_FAILED) { +#ifndef XBYAK_NO_EXCEPTION using Xbyak::Error; XBYAK_THROW(Xbyak::ERR_CANT_ALLOC); +#else + std::abort(); +#endif } std::memcpy(p, &size, sizeof(size_t)); - return static_cast(p) + DYNARMIC_PAGE_SIZE; + return static_cast(p) + page_size; } void free(uint8_t* p) override { + auto const page_size = Xbyak::inner::getPageSize(); size_t size; - std::memcpy(&size, p - DYNARMIC_PAGE_SIZE, sizeof(size_t)); - munmap(p - DYNARMIC_PAGE_SIZE, size); + std::memcpy(&size, p - page_size, sizeof(size_t)); + munmap(p - page_size, size); } # ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT @@ -233,18 +242,16 @@ bool IsUnderRosetta() { } // anonymous namespace -#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT -static const auto default_cg_mode = Xbyak::DontSetProtectRWE; +BlockOfCode::BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_code_size, std::function rcp) noexcept +#if defined(DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT) + : Xbyak::CodeGenerator(total_code_size, Xbyak::DontSetProtectRWE, &s_allocator) #else -static const auto default_cg_mode = nullptr; //Allow RWE + : Xbyak::CodeGenerator(total_code_size, nullptr, &s_allocator) #endif - -BlockOfCode::BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_code_size, std::function rcp) - : Xbyak::CodeGenerator(total_code_size, default_cg_mode, &s_allocator) - , cb(std::move(cb)) - , jsi(jsi) - , constant_pool(*this, CONSTANT_POOL_SIZE) - , host_features(GetHostFeatures()) { + , cb(std::move(cb)) + , jsi(jsi) + , constant_pool(*this, CONSTANT_POOL_SIZE) + , host_features(GetHostFeatures()) { EnableWriting(); EnsureMemoryCommitted(PRELUDE_COMMIT_SIZE); GenRunCode(rcp); @@ -533,8 +540,12 @@ size_t BlockOfCode::GetTotalCodeSize() const { void* BlockOfCode::AllocateFromCodeSpace(size_t alloc_size) { if (size_ + alloc_size >= maxSize_) { +#ifndef XBYAK_NO_EXCEPTION using Xbyak::Error; XBYAK_THROW(Xbyak::ERR_CODE_IS_TOO_BIG); +#else + std::abort(); +#endif } EnsureMemoryCommitted(alloc_size); diff --git a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.h b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.h index 857b1a4484..e19b6fac4d 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.h +++ b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.h @@ -38,8 +38,8 @@ struct RunCodeCallbacks { class BlockOfCode final : public Xbyak::CodeGenerator { public: - BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_code_size, std::function rcp); - BlockOfCode(const BlockOfCode&) = delete; + BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_code_size, std::function rcp) noexcept; + BlockOfCode(const BlockOfCode&) noexcept = delete; /// Call when external emitters have finished emitting their preludes. void PreludeComplete(); diff --git a/src/dynarmic/src/dynarmic/backend/x64/emit_x64.cpp b/src/dynarmic/src/dynarmic/backend/x64/emit_x64.cpp index 3d28365b1c..a4a13a4870 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/emit_x64.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/emit_x64.cpp @@ -40,9 +40,10 @@ EmitContext::EmitContext(RegAlloc& reg_alloc, IR::Block& block, boost::container EmitContext::~EmitContext() = default; -EmitX64::EmitX64(BlockOfCode& code) - : code(code) { +EmitX64::EmitX64(BlockOfCode& code) : code(code) { +#ifndef __OPENORBIS__ exception_handler.Register(code); +#endif } EmitX64::~EmitX64() = default; diff --git a/src/dynarmic/src/dynarmic/common/context.h b/src/dynarmic/src/dynarmic/common/context.h index db7af8e0ae..58fc707e9f 100644 --- a/src/dynarmic/src/dynarmic/common/context.h +++ b/src/dynarmic/src/dynarmic/common/context.h @@ -88,7 +88,7 @@ # define CTX_Q(i) (fpctx->vregs[i]) # define CTX_FPSR (fpctx->fpsr) # define CTX_FPCR (fpctx->fpcr) -# elif defined(__FreeBSD__) +# elif defined(__FreeBSD__) || defined(__DragonFly__) # define CTX_PC (mctx.mc_gpregs.gp_elr) # define CTX_SP (mctx.mc_gpregs.gp_sp) # define CTX_LR (mctx.mc_gpregs.gp_lr) @@ -131,6 +131,9 @@ # elif defined(__managarm__) # define CTX_PC (mctx.__pollution(gregs)[REG_RIP]) # define CTX_SP (mctx.__pollution(gregs)[REG_RSP]) +# elif defined(__OPENORBIS__) +# define CTX_RIP (mctx.gregs[REG_RIP]) +# define CTX_RSP (mctx.gregs[REG_RSP]) # else # error "unknown platform" # endif diff --git a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp index 1a80ae638a..00ce444429 100644 --- a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp +++ b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp @@ -13,6 +13,7 @@ #include "dynarmic/backend/x64/abi.h" #include "dynarmic/backend/x64/hostloc.h" #include "dynarmic/common/spin_lock.h" +#include "xbyak/xbyak.h" #ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT static const auto default_cg_mode = Xbyak::DontSetProtectRWE; @@ -78,7 +79,7 @@ namespace { struct SpinLockImpl { void Initialize() noexcept; static void GlobalInitialize() noexcept; - Xbyak::CodeGenerator code = Xbyak::CodeGenerator(4096, default_cg_mode); + Xbyak::CodeGenerator code = Xbyak::CodeGenerator(Xbyak::inner::getPageSize(), default_cg_mode); void (*lock)(volatile int*) = nullptr; void (*unlock)(volatile int*) = nullptr; }; diff --git a/src/dynarmic/tests/CMakeLists.txt b/src/dynarmic/tests/CMakeLists.txt index ca44dce593..3beacaf568 100644 --- a/src/dynarmic/tests/CMakeLists.txt +++ b/src/dynarmic/tests/CMakeLists.txt @@ -134,3 +134,8 @@ target_compile_options(dynarmic_tests PRIVATE ${DYNARMIC_CXX_FLAGS}) target_compile_definitions(dynarmic_tests PRIVATE FMT_USE_USER_DEFINED_LITERALS=1) add_test(NAME dynarmic_tests COMMAND dynarmic_tests --durations yes) + +if (PLATFORM_PS4) + target_link_libraries(dynarmic_tests PRIVATE SceVideoOut SceAudioOut ScePad SceSystemService ps4sup) + create_ps4_eboot(dynarmic_tests dynarmic_tests IV0000-BREW00090_00-DYNARMICTS000000) +endif() diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 5f0a8af3aa..9c89d092c0 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -82,7 +82,10 @@ 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 @@ -116,7 +119,9 @@ struct InputSubsystem::Impl { #ifdef ENABLE_LIBUSB UnregisterEngine(gcadapter); #endif +#ifndef __OPENORBIS__ UnregisterEngine(udp_client); +#endif UnregisterEngine(tas_input); UnregisterEngine(camera); #ifdef ANDROID @@ -152,8 +157,10 @@ 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_SDL3 auto joycon_devices = joycon->GetInputDevices(); devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end()); @@ -186,9 +193,11 @@ struct InputSubsystem::Impl { return gcadapter; } #endif +#ifndef __OPENORBIS__ if (engine == udp_client->GetEngineName()) { return udp_client; } +#endif #ifdef HAVE_SDL3 if (engine == sdl->GetEngineName()) { return sdl; @@ -271,9 +280,11 @@ struct InputSubsystem::Impl { return true; } #endif +#ifndef __OPENORBIS__ if (engine == udp_client->GetEngineName()) { return true; } +#endif if (engine == tas_input->GetEngineName()) { return true; } @@ -300,7 +311,9 @@ struct InputSubsystem::Impl { #ifdef ENABLE_LIBUSB gcadapter->BeginConfiguration(); #endif +#ifndef __OPENORBIS__ udp_client->BeginConfiguration(); +#endif #ifdef HAVE_SDL3 sdl->BeginConfiguration(); joycon->BeginConfiguration(); @@ -316,7 +329,9 @@ struct InputSubsystem::Impl { #ifdef ENABLE_LIBUSB gcadapter->EndConfiguration(); #endif +#ifndef __OPENORBIS__ udp_client->EndConfiguration(); +#endif #ifdef HAVE_SDL3 sdl->EndConfiguration(); joycon->EndConfiguration(); @@ -341,7 +356,9 @@ struct InputSubsystem::Impl { std::shared_ptr mouse; std::shared_ptr touch_screen; std::shared_ptr tas_input; +#ifndef __OPENORBIS__ std::shared_ptr udp_client; +#endif std::shared_ptr camera; std::shared_ptr virtual_amiibo; std::shared_ptr virtual_gamepad; @@ -470,7 +487,9 @@ 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) { diff --git a/src/qt_common/config/uisettings.h b/src/qt_common/config/uisettings.h index 4549a36345..2b155ef3f2 100644 --- a/src/qt_common/config/uisettings.h +++ b/src/qt_common/config/uisettings.h @@ -160,7 +160,13 @@ struct Values { Setting enable_discord_presence{linkage, false, "enable_discord_presence", Category::Ui}; // logging - Setting show_console{linkage, false, "showConsole", Category::Ui}; + Setting show_console{linkage, +#ifdef __OPENORBIS__ + true, +#else + false, +#endif + "showConsole", Category::Ui}; // Screenshots Setting enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as", diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 875c40604c..2cf669a669 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -37,3 +37,8 @@ if (NOT MSVC) $<$:-Wno-unused-parameter> $<$:-Wno-missing-field-initializers>) endif() + +if (PLATFORM_PS4) + target_link_libraries(tests PRIVATE SceVideoOut SceAudioOut ScePad SceSystemService ps4sup) + create_ps4_eboot(tests tests IV0000-BREW00090_00-DYNARMICTS000000) +endif() diff --git a/src/tests/common/host_memory.cpp b/src/tests/common/host_memory.cpp index cb040c942d..527e35397a 100644 --- a/src/tests/common/host_memory.cpp +++ b/src/tests/common/host_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -14,13 +17,21 @@ static constexpr size_t BACKING_SIZE = 4_GiB; static constexpr auto PERMS = Common::MemoryPermission::ReadWrite; static constexpr auto HEAP = false; +#ifndef __OPENORBIS__ TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") { - { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } - { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } + { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + } + { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + } } TEST_CASE("HostMemory: Simple map", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x5000, 0x8000, 0x1000, PERMS, HEAP); volatile u8* const data = mem.VirtualBasePointer() + 0x5000; @@ -30,6 +41,7 @@ TEST_CASE("HostMemory: Simple map", "[common]") { TEST_CASE("HostMemory: Simple mirror map", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP); mem.Map(0x8000, 0x4000, 0x1000, PERMS, HEAP); @@ -41,6 +53,7 @@ TEST_CASE("HostMemory: Simple mirror map", "[common]") { TEST_CASE("HostMemory: Simple unmap", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP); volatile u8* const data = mem.VirtualBasePointer() + 0x5000; @@ -52,6 +65,7 @@ TEST_CASE("HostMemory: Simple unmap", "[common]") { TEST_CASE("HostMemory: Simple unmap and remap", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP); volatile u8* const data = mem.VirtualBasePointer() + 0x5000; @@ -67,71 +81,9 @@ TEST_CASE("HostMemory: Simple unmap and remap", "[common]") { REQUIRE(data[0x3000] == 50); } -TEST_CASE("HostMemory: Nieche allocation", "[common]") { - HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); - mem.Map(0x0000, 0, 0x20000, PERMS, HEAP); - mem.Unmap(0x0000, 0x4000, HEAP); - mem.Map(0x1000, 0, 0x2000, PERMS, HEAP); - mem.Map(0x3000, 0, 0x1000, PERMS, HEAP); - mem.Map(0, 0, 0x1000, PERMS, HEAP); -} - -TEST_CASE("HostMemory: Full unmap", "[common]") { - HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); - mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); - mem.Unmap(0x8000, 0x4000, HEAP); - mem.Map(0x6000, 0, 0x16000, PERMS, HEAP); -} - -TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") { - HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); - mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); - mem.Unmap(0x2000, 0x4000, HEAP); - mem.Map(0x2000, 0x80000, 0x4000, PERMS, HEAP); -} - -TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") { - HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); - mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); - mem.Unmap(0x6000, 0x4000, HEAP); - mem.Map(0x8000, 0, 0x2000, PERMS, HEAP); -} - -TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") { - HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); - mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); - mem.Map(0x4000, 0, 0x1b000, PERMS, HEAP); - mem.Unmap(0x3000, 0x1c000, HEAP); - mem.Map(0x3000, 0, 0x20000, PERMS, HEAP); -} - -TEST_CASE("HostMemory: Unmap between placeholders", "[common]") { - HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); - mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); - mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); - mem.Unmap(0x2000, 0x4000, HEAP); - mem.Map(0x2000, 0, 0x4000, PERMS, HEAP); -} - -TEST_CASE("HostMemory: Unmap to origin", "[common]") { - HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); - mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); - mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); - mem.Unmap(0x4000, 0x4000, HEAP); - mem.Map(0, 0, 0x4000, PERMS, HEAP); - mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); -} - -TEST_CASE("HostMemory: Unmap to right", "[common]") { - HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); - mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); - mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); - mem.Unmap(0x8000, 0x4000, HEAP); - mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); -} - TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP); volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; @@ -144,6 +96,7 @@ TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP); volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; @@ -158,6 +111,7 @@ TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP); volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; @@ -172,6 +126,7 @@ TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0x10000, 0x2000, PERMS, HEAP); mem.Map(0x6000, 0x20000, 0x2000, PERMS, HEAP); @@ -184,3 +139,75 @@ TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common REQUIRE(ptr[0x0000] == 19); REQUIRE(ptr[0x3fff] == 12); } +#endif + +TEST_CASE("HostMemory: Nieche allocation", "[common]") { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + mem.Map(0x0000, 0, 0x20000, PERMS, HEAP); + mem.Unmap(0x0000, 0x4000, HEAP); + mem.Map(0x1000, 0, 0x2000, PERMS, HEAP); + mem.Map(0x3000, 0, 0x1000, PERMS, HEAP); + mem.Map(0, 0, 0x1000, PERMS, HEAP); +} + +TEST_CASE("HostMemory: Full unmap", "[common]") { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); + mem.Unmap(0x8000, 0x4000, HEAP); + mem.Map(0x6000, 0, 0x16000, PERMS, HEAP); +} + +TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); + mem.Unmap(0x2000, 0x4000, HEAP); + mem.Map(0x2000, 0x80000, 0x4000, PERMS, HEAP); +} + +TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); + mem.Unmap(0x6000, 0x4000, HEAP); + mem.Map(0x8000, 0, 0x2000, PERMS, HEAP); +} + +TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); + mem.Map(0x4000, 0, 0x1b000, PERMS, HEAP); + mem.Unmap(0x3000, 0x1c000, HEAP); + mem.Map(0x3000, 0, 0x20000, PERMS, HEAP); +} + +TEST_CASE("HostMemory: Unmap between placeholders", "[common]") { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); + mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); + mem.Unmap(0x2000, 0x4000, HEAP); + mem.Map(0x2000, 0, 0x4000, PERMS, HEAP); +} + +TEST_CASE("HostMemory: Unmap to origin", "[common]") { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); + mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); + mem.Unmap(0x4000, 0x4000, HEAP); + mem.Map(0, 0, 0x4000, PERMS, HEAP); + mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); +} + +TEST_CASE("HostMemory: Unmap to right", "[common]") { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); + mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); + mem.Unmap(0x8000, 0x4000, HEAP); + mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); +} diff --git a/src/tests/input_common/calibration_configuration_job.cpp b/src/tests/input_common/calibration_configuration_job.cpp index 6ded0dfc86..570a062f42 100644 --- a/src/tests/input_common/calibration_configuration_job.cpp +++ b/src/tests/input_common/calibration_configuration_job.cpp @@ -15,7 +15,8 @@ #include "input_common/drivers/udp_client.h" #include "input_common/helpers/udp_protocol.h" - +// PS4 doesn't support cemuhook +#ifndef __OPENORBIS__ class FakeCemuhookServer { public: FakeCemuhookServer() @@ -132,3 +133,4 @@ TEST_CASE("CalibrationConfigurationJob completed", "[input_common]") { REQUIRE(max_x == 200); REQUIRE(max_y == 200); } +#endif diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp index 5fd8037446..70a469586f 100644 --- a/src/tests/video_core/memory_tracker.cpp +++ b/src/tests/video_core/memory_tracker.cpp @@ -573,6 +573,7 @@ TEST_CASE("MemoryTracker: FlushCachedWrites batching") { REQUIRE(std::get<1>(calls[0]) == PAGE * 3); } +#if 0 TEST_CASE("DeviceMemoryManager: UpdatePagesCachedBatch basic") { Core::DeviceMemory device_memory; Tegra::MaxwellDeviceMemoryManager manager(device_memory); @@ -587,3 +588,4 @@ TEST_CASE("DeviceMemoryManager: UpdatePagesCachedBatch basic") { manager.UpdatePagesCachedBatch(ranges, 1); SUCCEED("UpdatePagesCachedBatch executed without error"); } +#endif diff --git a/src/video_core/vulkan_common/vulkan.h b/src/video_core/vulkan_common/vulkan.h index 2609e8dc0f..cd35fbc6ea 100644 --- a/src/video_core/vulkan_common/vulkan.h +++ b/src/video_core/vulkan_common/vulkan.h @@ -15,6 +15,8 @@ #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 diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp index 398342296e..a810268c7f 100644 --- a/src/video_core/vulkan_common/vulkan_instance.cpp +++ b/src/video_core/vulkan_common/vulkan_instance.cpp @@ -59,6 +59,8 @@ 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); diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp index 8b9dcb4d42..7aea55fdca 100644 --- a/src/video_core/vulkan_common/vulkan_surface.cpp +++ b/src/video_core/vulkan_common/vulkan_surface.cpp @@ -76,6 +76,8 @@ 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{ diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index 09a9bd6da1..e1747a91b7 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -75,3 +75,9 @@ if (NOT MSVC) $<$:-Wno-unused-parameter> $<$:-Wno-missing-field-initializers>) endif() + +if (PLATFORM_PS4) + target_link_libraries(yuzu-cmd PRIVATE SceVideoOut SceAudioOut ScePad SceSystemService) + target_link_libraries(yuzu-cmd PRIVATE ps4sup) + create_ps4_eboot(yuzu-cmd eden-cli IV0000-BREW00090_00-EDENEMULAT000000) +endif() diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp index cd390d29f9..e2aa68113f 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp @@ -22,10 +22,17 @@ EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) : input_subsystem{input_subsystem_}, system{system_} { input_subsystem->Initialize(); +#ifdef __OPENORBIS__ + if (!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { + LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError()); + exit(1); + } +#else if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError()); exit(1); } +#endif } EmuWindow_SDL3::~EmuWindow_SDL3() { @@ -165,11 +172,11 @@ void EmuWindow_SDL3::Fullscreen() { void EmuWindow_SDL3::WaitEvent() { // Called on main thread - SDL_Event event; + SDL_Event event{}; - if (!SDL_WaitEvent(&event)) { + if (!SDL_WaitEventTimeout(&event, -1)) { const char* error = SDL_GetError(); - if (!error || strcmp(error, "") == 0) { + if (!error || strlen(error) == 0) { // https://github.com/libsdl-org/SDL/issues/5780 // Sometimes SDL will return without actually having hit an error condition; // just ignore it in this case. @@ -236,12 +243,7 @@ void EmuWindow_SDL3::WaitEvent() { const u32 current_time = SDL_GetTicks(); if (current_time > last_time + 2000) { const auto results = system.GetAndResetPerfStats(); - const auto title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)", - Common::g_build_fullname, - Common::g_scm_branch, - Common::g_scm_desc, - results.average_game_fps, - results.emulation_speed * 100.0); + const auto title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, results.emulation_speed * 100.0); SDL_SetWindowTitle(render_window, title.c_str()); last_time = current_time; } diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index d20c126159..1b81e9793b 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -15,7 +15,9 @@ #include "common/logging.h" #include "common/scm_rev.h" #include "common/settings.h" +#include "common/settings_enums.h" #include "common/string_util.h" +#include "common/virtual_buffer.h" #include "core/core.h" #include "core/core_timing.h" #include "core/cpu_manager.h" @@ -52,7 +54,12 @@ #include #endif -#ifdef _WIN32 +#if defined(__OPENORBIS__) +#include +#include +#include +#include +#elif defined(_WIN32) extern "C" { // tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable // graphics @@ -183,6 +190,14 @@ int main(int argc, char** argv) { freopen("CONOUT$", "wb", stderr); } #endif +#ifdef __OPENORBIS__ + // May prevent spurious crashes on swap handlers... + setvbuf(stdout, nullptr, _IONBF, 0); + setvbuf(stderr, nullptr, _IONBF, 0); + + sceUserServiceInitialize(nullptr); + Common::InitSwap(); +#endif Common::Log::Initialize(); Common::Log::SetColorConsoleBackendEnabled(true); @@ -205,13 +220,19 @@ int main(int argc, char** argv) { std::optional selected_user{}; std::optional override_gdb_port{}; bool use_multiplayer = false; - bool fullscreen = false; std::string nickname{}; std::string password{}; std::string address{}; std::string input_profile{}; u16 port = Network::DefaultRoomPort; + // Platforms that start with fullscreen +#if defined(__OPENORBIS__) || defined(__ANDROID__) + bool fullscreen = true; +#else + bool fullscreen = false; +#endif + static struct option long_options[] = { // clang-format off {"debug", no_argument, 0, 'd'}, @@ -227,7 +248,11 @@ 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:fhvcip::c:u:d:", long_options, &option_index); if (arg != -1) { @@ -348,6 +373,8 @@ int main(int argc, char** argv) { // Apply the command line arguments system.ApplySettings(); + Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Null); + Common::Log::SetGlobalFilter(Common::Log::Filter(Common::Log::Level::Trace)); std::unique_ptr emu_window; switch (Settings::values.renderer_backend.GetValue()) { @@ -438,10 +465,16 @@ int main(int argc, char** argv) { [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); } - system.RegisterExitCallback([&] { + auto const exit_fn = [&] { +#ifdef __OPENORBIS__ + sceSystemServiceLoadExec("EXIT", nullptr); +#else // Just exit right away. exit(0); - }); +#endif + }; + system.RegisterExitCallback(exit_fn); + void(system.Run()); if (system.DebuggerEnabled()) { system.InitializeDebugger(); @@ -453,6 +486,7 @@ int main(int argc, char** argv) { void(system.Pause()); system.ShutdownMainProcess(); detached_tasks.WaitForAllTasks(); + exit_fn(); return 0; }