diff --git a/.ci/build-mac-arm64.sh b/.ci/build-mac-arm64.sh index b60c7db385..b7e05d87e0 100755 --- a/.ci/build-mac-arm64.sh +++ b/.ci/build-mac-arm64.sh @@ -6,8 +6,9 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 -brew install -f --overwrite --quiet pipenv googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers +brew install -f --overwrite --quiet googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative + brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 # moltenvk based on commit for 1.4.0 release @@ -42,11 +43,10 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader cd "/tmp/Qt" - "$BREW_PATH/bin/pipenv" run pip3 uninstall py7zr requests semantic_version lxml - "$BREW_PATH/bin/pipenv" run pip3 install py7zr requests semantic_version lxml --no-cache + pip3 install py7zr requests semantic_version lxml --no-cache --break-system-packages mkdir -p "$QT_VER/macos" ; ln -s "macos" "$QT_VER/clang_64" sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" - "$BREW_PATH/bin/pipenv" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" + python3 "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" fi cd "$WORKDIR" @@ -57,15 +57,14 @@ export SDL3_DIR="$BREW_PATH/opt/sdl3/lib/cmake/SDL3" export PATH="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH" export LDFLAGS="-L$BREW_PATH/lib $BREW_PATH/opt/ffmpeg@5/lib/libavcodec.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavformat.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavutil.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswscale.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswresample.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++.1.dylib $BREW_PATH/lib/libSDL3.dylib $BREW_PATH/lib/libGLEW.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib -Wl,-rpath,$BREW_PATH/lib" -export CPPFLAGS="-I$BREW_PATH/include -I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" -export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" +export CPPFLAGS="-I$BREW_PATH/include -D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" +export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" export LIBRARY_PATH="$BREW_PATH/lib" export LD_LIBRARY_PATH="$BREW_PATH/lib" export VULKAN_SDK VULKAN_SDK="$BREW_PATH/opt/molten-vk" -ln -s "$VULKAN_SDK/lib/libMoltenVK.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" || true -export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json" +ln -s "$BREW_PATH/opt/vulkan-loader/lib/libvulkan.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" || true export LLVM_DIR LLVM_DIR="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER" @@ -73,12 +72,9 @@ LLVM_DIR="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER" # shellcheck disable=SC2046 git submodule -q update --init --depth=1 --jobs=8 $(awk '/path/ && !/ffmpeg/ && !/llvm/ && !/opencv/ && !/SDL/ && !/feralinteractive/ { print $3 }' .gitmodules) -# 3rdparty fixes -sed -i '' "s/extern const double NSAppKitVersionNumber;/const double NSAppKitVersionNumber = 1343;/g" 3rdparty/hidapi/hidapi/mac/hid.c - mkdir build && cd build || exit 1 -export MACOSX_DEPLOYMENT_TARGET=14.0 +export MACOSX_DEPLOYMENT_TARGET=14.4 "$BREW_PATH/bin/cmake" .. \ -DBUILD_RPCS3_TESTS=OFF \ @@ -110,7 +106,7 @@ export MACOSX_DEPLOYMENT_TARGET=14.0 -DCMAKE_OSX_ARCHITECTURES=arm64 \ -DCMAKE_IGNORE_PATH="$BREW_PATH/lib" \ -DCMAKE_IGNORE_PREFIX_PATH=/opt/homebrew/opt \ - -DCMAKE_CXX_FLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" \ + -DCMAKE_CXX_FLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_OSX_SYSROOT="$(xcrun --sdk macosx --show-sdk-path)" \ -G Ninja diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index 98034c458e..6328ce05d3 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -6,13 +6,11 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 -brew install -f --overwrite --quiet ccache pipenv "llvm@$LLVM_COMPILER_VER" +brew install -f --overwrite --quiet ccache "llvm@$LLVM_COMPILER_VER" brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" # shellcheck disable=SC3009 -rm /usr/local/bin/{idle3.14,pip3.14,pydoc3.14,python3.14,python3.14-config} && \ -rm /usr/local/bin/{idle3,pip3,pydoc3,python3,python3-config} arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers +arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet python@3.14 opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 @@ -42,10 +40,10 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader cd "/tmp/Qt" - "/opt/homebrew/bin/pipenv" --python "/opt/homebrew/bin/python3" run pip3 install py7zr requests semantic_version lxml + pip3 install py7zr requests semantic_version lxml --no-cache --break-system-packages mkdir -p "$QT_VER/macos" ; ln -s "macos" "$QT_VER/clang_64" sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" - "/opt/homebrew/bin/pipenv" --python "/opt/homebrew/bin/python3" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" + python3 "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" fi cd "$WORKDIR" @@ -57,15 +55,14 @@ export SDL3_DIR="$BREW_X64_PATH/opt/sdl3/lib/cmake/SDL3" export PATH="/opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH" # shellcheck disable=SC2155 export LDFLAGS="-L$BREW_X64_PATH/lib -Wl,-rpath,$BREW_X64_PATH/lib,-L$(brew --prefix llvm)/lib/c++" -export CPPFLAGS="-I$BREW_X64_PATH/include -msse -msse2 -mcx16 -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" -export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" +export CPPFLAGS="-I$BREW_X64_PATH/include -msse -msse2 -mcx16 -D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" +export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" export LIBRARY_PATH="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER/lib:$BREW_X64_PATH/lib" export LD_LIBRARY_PATH="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER/lib:$BREW_X64_PATH/lib" export VULKAN_SDK VULKAN_SDK="$BREW_X64_PATH/opt/molten-vk" -ln -s "$VULKAN_SDK/lib/libMoltenVK.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" -export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json" +ln -s "$BREW_X64_PATH/opt/vulkan-loader/lib/libvulkan.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" export LLVM_DIR LLVM_DIR="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER" @@ -73,12 +70,9 @@ LLVM_DIR="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER" # shellcheck disable=SC2046 git submodule -q update --init --depth=1 --jobs=8 $(awk '/path/ && !/ffmpeg/ && !/llvm/ && !/opencv/ && !/SDL/ && !/feralinteractive/ { print $3 }' .gitmodules) -# 3rdparty fixes -sed -i '' "s/extern const double NSAppKitVersionNumber;/const double NSAppKitVersionNumber = 1343;/g" 3rdparty/hidapi/hidapi/mac/hid.c - mkdir build && cd build || exit 1 -export MACOSX_DEPLOYMENT_TARGET=14.0 +export MACOSX_DEPLOYMENT_TARGET=14.4 "/opt/homebrew/bin/cmake" .. \ -DBUILD_RPCS3_TESTS=OFF \ @@ -112,7 +106,7 @@ export MACOSX_DEPLOYMENT_TARGET=14.0 -DCMAKE_TOOLCHAIN_FILE=buildfiles/cmake/TCDarwinX86_64.cmake \ -DCMAKE_IGNORE_PATH="$BREW_X64_PATH/lib" \ -DCMAKE_IGNORE_PREFIX_PATH=/usr/local/opt \ - -DCMAKE_CXX_FLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" \ + -DCMAKE_CXX_FLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_OSX_SYSROOT="$(xcrun --sdk macosx --show-sdk-path)" \ -G Ninja diff --git a/.ci/deploy-mac-arm64.sh b/.ci/deploy-mac-arm64.sh index 57cd72dafb..8a27d04676 100755 --- a/.ci/deploy-mac-arm64.sh +++ b/.ci/deploy-mac-arm64.sh @@ -15,6 +15,12 @@ echo "AVVER=$AVVER" >> ../.ci/ci-vars.env cd bin mkdir "rpcs3.app/Contents/lib/" || true +mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true +wget https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar +tar -xvf MoltenVK-macos-privateapi.tar +cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" +cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +sed -i '' "s/.\//..\/..\/..\/Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" cp "$(realpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" cp "$(realpath /opt/homebrew/opt/gcc/lib/gcc/current/libgcc_s.1.1.dylib)" "rpcs3.app/Contents/Frameworks/libgcc_s.1.1.dylib" diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 92acb70003..ec71fe0262 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -14,7 +14,13 @@ AVVER="${COMM_TAG}-${COMM_COUNT}" echo "AVVER=$AVVER" >> ../.ci/ci-vars.env cd bin -mkdir "rpcs3.app/Contents/lib/" +mkdir "rpcs3.app/Contents/lib/" || true +mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true +wget https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar +tar -xvf MoltenVK-macos-privateapi.tar +cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" +cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +sed -i '' "s/.\//..\/..\/..\/Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib)" "rpcs3.app/Contents/Frameworks/libunwind.1.dylib" diff --git a/3rdparty/7zip/CMakeLists.txt b/3rdparty/7zip/CMakeLists.txt index 706d869472..a8f5d87cb3 100644 --- a/3rdparty/7zip/CMakeLists.txt +++ b/3rdparty/7zip/CMakeLists.txt @@ -59,11 +59,11 @@ if(WIN32 OR APPLE) 7zip/C/XzEnc.c 7zip/C/XzIn.c 7zip/C/ZstdDec.c) - target_include_directories(3rdparty_7zip INTERFACE + target_include_directories(3rdparty_7zip SYSTEM INTERFACE $ $) - target_include_directories(3rdparty_7zip INTERFACE 7zip) + target_include_directories(3rdparty_7zip SYSTEM INTERFACE 7zip) set_property(TARGET 3rdparty_7zip PROPERTY FOLDER "3rdparty/") diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 324069e363..a800ba1dd5 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -35,7 +35,7 @@ if (USE_SYSTEM_FLATBUFFERS) message(FATAL_ERROR "flatc failed to regenerate flatbuffers headers.") endif() else() - target_include_directories(3rdparty_flatbuffers INTERFACE flatbuffers/include) + target_include_directories(3rdparty_flatbuffers SYSTEM INTERFACE flatbuffers/include) endif() # libPNG @@ -56,7 +56,7 @@ if (USE_SYSTEM_VULKAN_MEMORY_ALLOCATOR) add_library(3rdparty::vulkanmemoryallocator ALIAS GPUOpen::VulkanMemoryAllocator) else() add_library(3rdparty_vulkanmemoryallocator INTERFACE) - target_include_directories(3rdparty_vulkanmemoryallocator INTERFACE GPUOpen/VulkanMemoryAllocator/include) + target_include_directories(3rdparty_vulkanmemoryallocator SYSTEM INTERFACE GPUOpen/VulkanMemoryAllocator/include) add_library(3rdparty::vulkanmemoryallocator ALIAS 3rdparty_vulkanmemoryallocator) endif() @@ -111,7 +111,7 @@ if (NOT ANDROID) find_package(OpenGL REQUIRED OPTIONAL_COMPONENTS EGL) add_library(3rdparty_opengl INTERFACE) - target_include_directories(3rdparty_opengl INTERFACE GL) + target_include_directories(3rdparty_opengl SYSTEM INTERFACE GL) if (WIN32) if(NOT MSVC) @@ -204,7 +204,7 @@ if(USE_VULKAN) find_package(Wayland) if (WAYLAND_FOUND) target_include_directories(3rdparty_vulkan - INTERFACE ${WAYLAND_INCLUDE_DIR}) + SYSTEM INTERFACE ${WAYLAND_INCLUDE_DIR}) endif() endif() @@ -298,7 +298,7 @@ if(NOT ANDROID) message(STATUS "RPCS3: using shared ffmpeg") find_package(FFMPEG REQUIRED) - target_include_directories(3rdparty_ffmpeg INTERFACE ${FFMPEG_INCLUDE_DIR}) + target_include_directories(3rdparty_ffmpeg SYSTEM INTERFACE ${FFMPEG_INCLUDE_DIR}) target_link_libraries(3rdparty_ffmpeg INTERFACE ${FFMPEG_LIBRARIES}) else() message(STATUS "RPCS3: using builtin ffmpeg") @@ -328,7 +328,7 @@ if(NOT ANDROID) ${FFMPEG_LIB_SWSCALE} ${FFMPEG_LIB_SWRESAMPLE} ) - target_include_directories(3rdparty_ffmpeg INTERFACE "ffmpeg/include") + target_include_directories(3rdparty_ffmpeg SYSTEM INTERFACE "ffmpeg/include") endif() endif() diff --git a/3rdparty/SoundTouch/CMakeLists.txt b/3rdparty/SoundTouch/CMakeLists.txt index acc7d02714..75b6ca5ac9 100644 --- a/3rdparty/SoundTouch/CMakeLists.txt +++ b/3rdparty/SoundTouch/CMakeLists.txt @@ -11,11 +11,11 @@ add_library(soundtouch STATIC EXCLUDE_FROM_ALL soundtouch/source/SoundTouch/TDStretch.cpp ) -target_include_directories(soundtouch PRIVATE +target_include_directories(soundtouch SYSTEM PRIVATE soundtouch/source/SoundTouch soundtouch/include) -target_include_directories(soundtouch INTERFACE +target_include_directories(soundtouch SYSTEM INTERFACE $ $) diff --git a/3rdparty/asmjit/CMakeLists.txt b/3rdparty/asmjit/CMakeLists.txt index dbbf85f362..4714a788d2 100644 --- a/3rdparty/asmjit/CMakeLists.txt +++ b/3rdparty/asmjit/CMakeLists.txt @@ -9,7 +9,7 @@ set(ASMJIT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/asmjit" CACHE PATH "Location of 'asm include("${ASMJIT_DIR}/CMakeLists.txt") add_library(asmjit ${ASMJIT_SRC}) -target_include_directories(asmjit PUBLIC ${ASMJIT_DIR}/src) +target_include_directories(asmjit SYSTEM PUBLIC ${ASMJIT_DIR}/src) target_link_libraries(asmjit PRIVATE ${ASMJIT_DEPS}) target_compile_options(asmjit PRIVATE -Wno-nontrivial-memcall -Wno-deprecated-anon-enum-enum-conversion) diff --git a/3rdparty/discord-rpc/CMakeLists.txt b/3rdparty/discord-rpc/CMakeLists.txt index 2ad705d52c..9212ca5a60 100644 --- a/3rdparty/discord-rpc/CMakeLists.txt +++ b/3rdparty/discord-rpc/CMakeLists.txt @@ -9,7 +9,7 @@ if (USE_DISCORD_RPC AND (WIN32 OR CMAKE_SYSTEM MATCHES "Linux" OR APPLE)) set(WARNINGS_AS_ERRORS FALSE CACHE BOOL "When enabled, compiles with `-Werror` (on *nix platforms).") add_subdirectory(discord-rpc EXCLUDE_FROM_ALL) - target_include_directories(3rdparty_discordRPC INTERFACE discord-rpc/include) + target_include_directories(3rdparty_discordRPC SYSTEM INTERFACE discord-rpc/include) target_compile_definitions(3rdparty_discordRPC INTERFACE -DWITH_DISCORD_RPC) target_link_libraries(3rdparty_discordRPC INTERFACE discord-rpc) endif() diff --git a/3rdparty/feralinteractive/CMakeLists.txt b/3rdparty/feralinteractive/CMakeLists.txt index c7b136e5f0..0b1f6fc7e7 100644 --- a/3rdparty/feralinteractive/CMakeLists.txt +++ b/3rdparty/feralinteractive/CMakeLists.txt @@ -3,7 +3,7 @@ add_library(3rdparty_feralinteractive INTERFACE) if (CMAKE_SYSTEM MATCHES "Linux") - target_include_directories(3rdparty_feralinteractive INTERFACE feralinteractive/lib) + target_include_directories(3rdparty_feralinteractive SYSTEM INTERFACE feralinteractive/lib) target_compile_definitions(3rdparty_feralinteractive INTERFACE -DGAMEMODE_AVAILABLE) target_link_libraries(3rdparty_feralinteractive INTERFACE feralinteractive) endif() diff --git a/3rdparty/flatbuffers b/3rdparty/flatbuffers index 595bf0007a..1872409707 160000 --- a/3rdparty/flatbuffers +++ b/3rdparty/flatbuffers @@ -1 +1 @@ -Subproject commit 595bf0007ab1929570c7671f091313c8fc20644e +Subproject commit 187240970746d00bbd26b0f5873ed54d2477f9f3 diff --git a/3rdparty/fusion/fusion b/3rdparty/fusion/fusion index 759ac5d698..008e03eac0 160000 --- a/3rdparty/fusion/fusion +++ b/3rdparty/fusion/fusion @@ -1 +1 @@ -Subproject commit 759ac5d698baefca53f1975a0bb1d2dcbdb9f836 +Subproject commit 008e03eac0ac1d5f85e16f5fcaefdda3fee75cb8 diff --git a/3rdparty/glslang/CMakeLists.txt b/3rdparty/glslang/CMakeLists.txt index c86d0b384c..10cdecd63b 100644 --- a/3rdparty/glslang/CMakeLists.txt +++ b/3rdparty/glslang/CMakeLists.txt @@ -7,7 +7,7 @@ if(USE_SYSTEM_GLSLANG) target_link_libraries(3rdparty_glslang INTERFACE glslang::SPIRV) get_target_property(SPIRV_INCLUDE_DIRS glslang::SPIRV INTERFACE_INCLUDE_DIRECTORIES) list(TRANSFORM SPIRV_INCLUDE_DIRS APPEND "/glslang") - target_include_directories(3rdparty_glslang INTERFACE ${SPIRV_INCLUDE_DIRS}) + target_include_directories(3rdparty_glslang SYSTEM INTERFACE ${SPIRV_INCLUDE_DIRS}) else() set(ENABLE_PCH OFF CACHE BOOL "Enables Precompiled header" FORCE) set(BUILD_EXTERNAL OFF CACHE BOOL "Build external dependencies in /External" FORCE) diff --git a/3rdparty/hidapi/CMakeLists.txt b/3rdparty/hidapi/CMakeLists.txt index 2d043d6936..cdaac856b0 100644 --- a/3rdparty/hidapi/CMakeLists.txt +++ b/3rdparty/hidapi/CMakeLists.txt @@ -4,7 +4,7 @@ if(USE_SYSTEM_HIDAPI) pkg_check_modules(hidapi-hidraw REQUIRED IMPORTED_TARGET hidapi-hidraw) add_library(3rdparty_hidapi INTERFACE) target_link_libraries(3rdparty_hidapi INTERFACE PkgConfig::hidapi-hidraw) - target_include_directories(3rdparty_hidapi INTERFACE PkgConfig::hidapi-hidraw) + target_include_directories(3rdparty_hidapi SYSTEM INTERFACE PkgConfig::hidapi-hidraw) else() set(BUILD_SHARED_LIBS FALSE CACHE BOOL "Don't build shared libs") set(HIDAPI_INSTALL_TARGETS FALSE CACHE BOOL "Don't install anything") diff --git a/3rdparty/libpng/CMakeLists.txt b/3rdparty/libpng/CMakeLists.txt index f24c2c9709..9ac9c976c4 100644 --- a/3rdparty/libpng/CMakeLists.txt +++ b/3rdparty/libpng/CMakeLists.txt @@ -6,14 +6,14 @@ if (NOT USE_SYSTEM_LIBPNG) set(PNG_TESTS OFF CACHE BOOL "Build libpng tests") set(SKIP_INSTALL_ALL ON) add_subdirectory(libpng EXCLUDE_FROM_ALL) - target_include_directories(png_static INTERFACE "${libpng_BINARY_DIR}" "${libpng_SOURCE_DIR}") + target_include_directories(png_static SYSTEM INTERFACE "${libpng_BINARY_DIR}" "${libpng_SOURCE_DIR}") set(LIBPNG_TARGET png_static PARENT_SCOPE) else() find_package(PNG REQUIRED) add_library(3rdparty_system_libpng INTERFACE) - target_include_directories(3rdparty_system_libpng INTERFACE ${PNG_INCLUDE_DIR}) + target_include_directories(3rdparty_system_libpng SYSTEM INTERFACE ${PNG_INCLUDE_DIR}) target_link_libraries(3rdparty_system_libpng INTERFACE ${PNG_LIBRARY}) target_compile_definitions(3rdparty_system_libpng INTERFACE ${PNG_DEFINITIONS}) diff --git a/3rdparty/llvm/CMakeLists.txt b/3rdparty/llvm/CMakeLists.txt index d1295886d8..a4af3b3ef5 100644 --- a/3rdparty/llvm/CMakeLists.txt +++ b/3rdparty/llvm/CMakeLists.txt @@ -107,7 +107,7 @@ if(WITH_LLVM) add_library(3rdparty_llvm INTERFACE) target_link_libraries(3rdparty_llvm INTERFACE ${LLVM_LIBS}) - target_include_directories(3rdparty_llvm INTERFACE ${LLVM_INCLUDE_DIRS}) + target_include_directories(3rdparty_llvm SYSTEM INTERFACE ${LLVM_INCLUDE_DIRS}) separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) target_compile_definitions(3rdparty_llvm INTERFACE ${LLVM_DEFINITIONS_LIST} LLVM_AVAILABLE) diff --git a/3rdparty/miniupnp/CMakeLists.txt b/3rdparty/miniupnp/CMakeLists.txt index c40d4a5ebd..1baab6dbdd 100644 --- a/3rdparty/miniupnp/CMakeLists.txt +++ b/3rdparty/miniupnp/CMakeLists.txt @@ -3,9 +3,9 @@ if(USE_SYSTEM_MINIUPNPC) pkg_check_modules(MiniUPnPc REQUIRED IMPORTED_TARGET miniupnpc>=2.3.3) add_library(3rdparty_miniupnpc INTERFACE) target_link_libraries(3rdparty_miniupnpc INTERFACE PkgConfig::MiniUPnPc) - target_include_directories(3rdparty_miniupnpc INTERFACE PkgConfig::MiniUPnPc) + target_include_directories(3rdparty_miniupnpc SYSTEM INTERFACE PkgConfig::MiniUPnPc) list(TRANSFORM MiniUPnPc_INCLUDE_DIRS APPEND "/miniupnpc") - target_include_directories(3rdparty_miniupnpc INTERFACE ${MiniUPnPc_INCLUDE_DIRS}) + target_include_directories(3rdparty_miniupnpc SYSTEM INTERFACE ${MiniUPnPc_INCLUDE_DIRS}) else() option (UPNPC_BUILD_STATIC "Build static library" TRUE) option (UPNPC_BUILD_SHARED "Build shared library" FALSE) @@ -17,5 +17,5 @@ else() add_subdirectory(miniupnp/miniupnpc EXCLUDE_FROM_ALL) add_library(3rdparty_miniupnpc INTERFACE) target_link_libraries(3rdparty_miniupnpc INTERFACE libminiupnpc-static) - target_include_directories(3rdparty_miniupnpc INTERFACE libminiupnpc-static) + target_include_directories(3rdparty_miniupnpc SYSTEM INTERFACE libminiupnpc-static) endif() diff --git a/3rdparty/rtmidi/CMakeLists.txt b/3rdparty/rtmidi/CMakeLists.txt index b9dd286ce0..3369f78aa6 100644 --- a/3rdparty/rtmidi/CMakeLists.txt +++ b/3rdparty/rtmidi/CMakeLists.txt @@ -3,7 +3,7 @@ if(USE_SYSTEM_RTMIDI) pkg_check_modules(RtMidi REQUIRED IMPORTED_TARGET rtmidi>=6.0.0) add_library(rtmidi INTERFACE) target_link_libraries(rtmidi INTERFACE PkgConfig::RtMidi) - target_include_directories(rtmidi INTERFACE PkgConfig::RtMidi) + target_include_directories(rtmidi SYSTEM INTERFACE PkgConfig::RtMidi) else() option(RTMIDI_API_JACK "Compile with JACK support." OFF) option(RTMIDI_BUILD_TESTING "Build test programs" OFF) diff --git a/3rdparty/stblib/CMakeLists.txt b/3rdparty/stblib/CMakeLists.txt index 3426459edb..9afd906060 100644 --- a/3rdparty/stblib/CMakeLists.txt +++ b/3rdparty/stblib/CMakeLists.txt @@ -1,2 +1,2 @@ add_library(3rdparty_stblib INTERFACE) -target_include_directories(3rdparty_stblib INTERFACE stb) +target_include_directories(3rdparty_stblib SYSTEM INTERFACE stb) diff --git a/3rdparty/zlib/CMakeLists.txt b/3rdparty/zlib/CMakeLists.txt index d9dffb07be..55d7353acf 100644 --- a/3rdparty/zlib/CMakeLists.txt +++ b/3rdparty/zlib/CMakeLists.txt @@ -13,6 +13,6 @@ else() add_library(3rdparty_zlib INTERFACE) target_link_libraries(3rdparty_zlib INTERFACE zlibstatic) - target_include_directories(3rdparty_zlib INTERFACE zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib) + target_include_directories(3rdparty_zlib SYSTEM INTERFACE zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib) target_compile_definitions(3rdparty_zlib INTERFACE -DZLIB_CONST=1) endif() diff --git a/3rdparty/zstd/CMakeLists.txt b/3rdparty/zstd/CMakeLists.txt index 431272966d..bff61148c2 100644 --- a/3rdparty/zstd/CMakeLists.txt +++ b/3rdparty/zstd/CMakeLists.txt @@ -3,7 +3,7 @@ if(USE_SYSTEM_ZSTD) pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd) add_library(3rdparty_zstd INTERFACE) target_link_libraries(3rdparty_zstd INTERFACE PkgConfig::zstd) - target_include_directories(3rdparty_zstd INTERFACE PkgConfig::RtMidi) + target_include_directories(3rdparty_zstd SYSTEM INTERFACE PkgConfig::RtMidi) else() option(ZSTD_BUILD_PROGRAMS "BUILD PROGRAMS" OFF) option(ZSTD_BUILD_SHARED "BUILD SHARED LIBRARIES" OFF) diff --git a/Utilities/StrFmt.cpp b/Utilities/StrFmt.cpp index 189773731e..26fb1a09da 100644 --- a/Utilities/StrFmt.cpp +++ b/Utilities/StrFmt.cpp @@ -83,7 +83,8 @@ std::string fmt::win_error_to_string(unsigned long error, void* module_handle) if (FormatMessageW((module_handle ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM) | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, module_handle, error, 0, reinterpret_cast(&message_buffer), 0, nullptr)) { - message = fmt::format("%s (0x%x)", fmt::trim(wchar_to_utf8(message_buffer), " \t\n\r\f\v"), error); + const std::string utf8 = wchar_to_utf8(message_buffer); + message = fmt::format("%s (0x%x)", fmt::trim_sv(utf8, " \t\n\r\f\v"), error); } else { @@ -823,6 +824,50 @@ std::vector fmt::split(std::string_view source, std::initializer_li return result; } +std::vector fmt::split_sv(std::string_view source, std::initializer_list separators, bool is_skip_empty) +{ + std::vector result; + + for (usz index = 0; index < source.size();) + { + usz pos = -1; + usz sep_size = 0; + + for (auto& separator : separators) + { + if (usz pos0 = source.find(separator, index); pos0 < pos) + { + pos = pos0; + sep_size = separator.size(); + } + } + + if (!sep_size) + { + result.emplace_back(&source[index], source.size() - index); + return result; + } + + std::string_view piece = {&source[index], pos - index}; + + index = pos + sep_size; + + if (piece.empty() && is_skip_empty) + { + continue; + } + + result.emplace_back(std::move(piece)); + } + + if (result.empty() && !is_skip_empty) + { + result.emplace_back(); + } + + return result; +} + std::string fmt::trim(const std::string& source, std::string_view values) { const usz begin = source.find_first_not_of(values); @@ -838,6 +883,21 @@ std::string fmt::trim(const std::string& source, std::string_view values) return source.substr(begin, end + 1 - begin); } +std::string_view fmt::trim_sv(std::string_view source, std::string_view values) +{ + const usz begin = source.find_first_not_of(values); + + if (begin == source.npos) + return {}; + + const usz end = source.find_last_not_of(values); + + if (end == source.npos) + return source.substr(begin); + + return source.substr(begin, end + 1 - begin); +} + std::string fmt::trim_front(const std::string& source, std::string_view values) { const usz begin = source.find_first_not_of(values); @@ -848,12 +908,32 @@ std::string fmt::trim_front(const std::string& source, std::string_view values) return source.substr(begin); } +std::string_view fmt::trim_front_sv(std::string_view source, std::string_view values) +{ + const usz begin = source.find_first_not_of(values); + + if (begin == source.npos) + return {}; + + return source.substr(begin); +} + void fmt::trim_back(std::string& source, std::string_view values) { const usz index = source.find_last_not_of(values); source.resize(index + 1); } +std::string_view fmt::trim_back_sv(std::string_view source, std::string_view values) +{ + const usz index = source.find_last_not_of(values); + if (index == std::string_view::npos) + return {}; + + source.remove_suffix(source.size() - (index + 1)); + return source; +} + std::string fmt::to_upper(std::string_view string) { std::string result; diff --git a/Utilities/StrUtil.h b/Utilities/StrUtil.h index 285718ac21..b5df886164 100644 --- a/Utilities/StrUtil.h +++ b/Utilities/StrUtil.h @@ -139,57 +139,90 @@ namespace fmt // Splits the string into a vector of strings using the separators. The vector may contain empty strings unless is_skip_empty is true. std::vector split(std::string_view source, std::initializer_list separators, bool is_skip_empty = true); + // Splits the string_view into a vector of string_views using the separators. The vector may contain empty string_views unless is_skip_empty is true. + std::vector split_sv(std::string_view source, std::initializer_list separators, bool is_skip_empty = true); + // Removes all preceding and trailing characters specified by 'values' from 'source'. std::string trim(const std::string& source, std::string_view values = " \t"); + // Removes all preceding and trailing characters specified by 'values' from 'source' and returns the result. + std::string_view trim_sv(std::string_view source, std::string_view values = " \t"); + // Removes all preceding characters specified by 'values' from 'source'. std::string trim_front(const std::string& source, std::string_view values = " \t"); + // Removes all preceding characters specified by 'values' from 'source' and returns the result. + std::string_view trim_front_sv(std::string_view source, std::string_view values = " \t"); + // Removes all trailing characters specified by 'values' from 'source'. void trim_back(std::string& source, std::string_view values = " \t"); + // Removes all trailing characters specified by 'values' from 'source' and returns the result. + std::string_view trim_back_sv(std::string_view source, std::string_view values = " \t"); + template - std::string merge(const T& source, const std::string& separator) + std::string merge(const T& source, std::string_view separator) { if (source.empty()) { return {}; } + usz total = (source.size() - 1) * separator.size(); + for (const auto& s : source) + { + total += s.size(); + } + std::string result; + result.reserve(total); auto it = source.begin(); auto end = source.end(); + for (--end; it != end; ++it) { - result += std::string{*it} + separator; + result.append(*it); + + if (!separator.empty()) + result.append(separator); } - return result + std::string{source.back()}; + return result.append(source.back()); } template - std::string merge(std::initializer_list sources, const std::string& separator) + std::string merge(std::initializer_list sources, std::string_view separator) { if (!sources.size()) { return {}; } + usz total = (sources.size() - 1) * separator.size(); + for (const auto& s : sources) + { + if (s.empty()) continue; + total += s.size() + (s.size() - 1) * separator.size(); + } + std::string result; + result.reserve(total); + bool first = true; for (const auto& v : sources) { if (first) { - result = fmt::merge(v, separator); - first = false; + first = false; } - else + else if (!separator.empty()) { - result += separator + fmt::merge(v, separator); + result.append(separator); } + + result.append(fmt::merge(v, separator)); } return result; diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 9b6d250c19..810b8fd7c5 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -2165,10 +2165,17 @@ void thread_base::start() ensure(m_thread); ensure(::ResumeThread(reinterpret_cast(+m_thread)) != static_cast(-1)); #elif defined(__APPLE__) - pthread_attr_t stack_size_attr; - pthread_attr_init(&stack_size_attr); - pthread_attr_setstacksize(&stack_size_attr, 0x800000); - ensure(pthread_create(reinterpret_cast(&m_thread.raw()), &stack_size_attr, entry_point, this) == 0); + pthread_attr_t attrs; + struct sched_param sp; + memset(&sp, 0, sizeof(struct sched_param)); + sp.sched_priority=99; + pthread_attr_init(&attrs); + pthread_attr_setstacksize(&attrs, 0x800000); + + pthread_attr_set_qos_class_np(&attrs, QOS_CLASS_USER_INTERACTIVE, 0); + pthread_attr_setschedpolicy(&attrs, SCHED_RR); + pthread_attr_setschedparam(&attrs, &sp); + ensure(pthread_create(reinterpret_cast(&m_thread.raw()), &attrs, entry_point, this) == 0); #else ensure(pthread_create(reinterpret_cast(&m_thread.raw()), nullptr, entry_point, this) == 0); #endif diff --git a/Utilities/bit_set.h b/Utilities/bit_set.h index 8b98ae0fd4..9fdbf15de1 100644 --- a/Utilities/bit_set.h +++ b/Utilities/bit_set.h @@ -23,7 +23,6 @@ Intersection (&) and symmetric difference (^) is also available. #include "util/types.hpp" #include "util/atomic.hpp" -#include "Utilities/StrFmt.h" template concept BitSetEnum = std::is_enum_v && requires(T x) @@ -384,6 +383,9 @@ public: } }; +template +struct fmt_unveil; + template struct fmt_unveil> { diff --git a/Utilities/cheat_info.cpp b/Utilities/cheat_info.cpp index a16be2767f..cc8934f15a 100644 --- a/Utilities/cheat_info.cpp +++ b/Utilities/cheat_info.cpp @@ -27,9 +27,9 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } -bool cheat_info::from_str(const std::string& cheat_line) +bool cheat_info::from_str(std::string_view cheat_line) { - auto cheat_vec = fmt::split(cheat_line, {"@@@"}, false); + const auto cheat_vec = fmt::split(cheat_line, {"@@@"}, false); s64 val64 = 0; if (cheat_vec.size() != 5 || !try_to_int64(&val64, cheat_vec[2], 0, cheat_type_max - 1)) diff --git a/Utilities/cheat_info.h b/Utilities/cheat_info.h index 3ceb32716b..a109d86e56 100644 --- a/Utilities/cheat_info.h +++ b/Utilities/cheat_info.h @@ -28,6 +28,6 @@ struct cheat_info u32 offset{}; std::string red_script{}; - bool from_str(const std::string& cheat_line); + bool from_str(std::string_view cheat_line); std::string to_str() const; }; diff --git a/Utilities/sync.h b/Utilities/sync.h index db46d6104f..513a45ee51 100644 --- a/Utilities/sync.h +++ b/Utilities/sync.h @@ -17,6 +17,8 @@ #include #include #include +#elif __APPLE__ +#include #endif #ifdef _WIN32 @@ -76,21 +78,71 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t { #ifdef __linux__ return syscall(SYS_futex, uaddr, futex_op, static_cast(val), timeout, nullptr, static_cast(mask)); +#elif __APPLE__ + switch (futex_op) + { + case FUTEX_WAIT_PRIVATE: + case FUTEX_WAIT_BITSET_PRIVATE: + { + if (timeout) + { + const uint64_t nsec = timeout->tv_nsec + timeout->tv_sec * 1000000000ull; + return os_sync_wait_on_address_with_timeout(const_cast(uaddr), static_cast(val), sizeof(uint), OS_SYNC_WAIT_ON_ADDRESS_NONE, OS_CLOCK_MACH_ABSOLUTE_TIME, nsec); + } + else + { + return os_sync_wait_on_address(const_cast(uaddr), static_cast(val), sizeof(uint), OS_SYNC_WAIT_ON_ADDRESS_NONE); + } + } + + case FUTEX_WAKE_PRIVATE: + case FUTEX_WAKE_BITSET_PRIVATE: + { + for (;;) + { + int ret = 0; + if (val == INT32_MAX) + { + ret = os_sync_wake_by_address_all(const_cast(uaddr), sizeof(uint), OS_SYNC_WAKE_BY_ADDRESS_NONE); + } + else if (val-- >= 0) + { + ret = os_sync_wake_by_address_any(const_cast(uaddr), sizeof(uint), OS_SYNC_WAKE_BY_ADDRESS_NONE); + } + if (val <= 0 || val == INT32_MAX || (ret < 0 && errno == ENOENT)) + { + return ret; + } + } + } + } + errno = EINVAL; + return -1; #else static struct futex_manager { struct waiter { - uint val; - uint mask; - std::condition_variable cv; + uint val; + uint mask; + std::condition_variable cv; }; - std::mutex mutex; - std::unordered_multimap map; + struct bucket_t + { + std::mutex mutex; + std::unordered_multimap map; + }; + + // Not a power of 2 on purpose (for alignment optimiations) + bucket_t bucks[63]; int operator()(volatile void* uaddr, int futex_op, uint val, const timespec* timeout, uint mask) { + auto& bucket = bucks[(reinterpret_cast(uaddr) / 8) % std::size(bucks)]; + auto& mutex = bucket.mutex; + auto& map = bucket.map; + std::unique_lock lock(mutex); switch (futex_op) @@ -111,7 +163,9 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t waiter rec; rec.val = val; rec.mask = mask; - const auto& ref = *map.emplace(uaddr, &rec); + + // Announce the waiter + map.emplace(uaddr, &rec); int res = 0; @@ -127,6 +181,16 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t { res = -1; errno = ETIMEDOUT; + + // Cleanup + for (auto range = map.equal_range(uaddr); range.first != range.second; range.first++) + { + if (range.first->second == &rec) + { + map.erase(range.first); + break; + } + } } } else @@ -134,7 +198,6 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t // TODO: absolute timeout } - map.erase(std::find(map.find(uaddr), map.end(), ref)); return res; } @@ -153,13 +216,29 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t if (entry.mask & mask) { - entry.cv.notify_one(); entry.mask = 0; + entry.cv.notify_one(); res++; val--; } } + if (res) + { + // Cleanup + for (auto range = map.equal_range(uaddr); range.first != range.second;) + { + if (range.first->second->mask == 0) + { + map.erase(range.first); + range = map.equal_range(uaddr); + continue; + } + + range.first++; + } + } + return res; } } diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index 74d579870f..249aca9910 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -198,6 +198,7 @@ if(BUILD_RPCS3_TESTS) tests/test_simple_array.cpp tests/test_address_range.cpp tests/test_rsx_cfg.cpp + tests/test_rsx_fp_asm.cpp ) target_link_libraries(rpcs3_test diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index bc7481fcd6..de23f5f1ba 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -598,15 +598,15 @@ bool package_reader::read_param_sfo() const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u; - std::string name(entry.name_size + BUF_PADDING, '\0'); + std::string name_buf(entry.name_size + BUF_PADDING, '\0'); - if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name.data()); read_size < entry.name_size) + if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size) { pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset); continue; } - fmt::trim_back(name, "\0"sv); + std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv); // We're looking for the PARAM.SFO file, if there is any if (usz ndelim = name.find_first_not_of('/'); ndelim == umax || name.substr(ndelim) != "PARAM.SFO") @@ -854,18 +854,18 @@ bool package_reader::fill_data(std::map& all_instal break; } - std::string name(entry.name_size + BUF_PADDING, '\0'); + std::string name_buf(entry.name_size + BUF_PADDING, '\0'); const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u; - if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name.data()); read_size < entry.name_size) + if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size) { num_failures++; pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset); break; } - fmt::trim_back(name, "\0"sv); + std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv); std::string path = m_install_path + vfs::escape(name); diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index d253561509..0b7971c07e 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -347,6 +347,7 @@ public: }; bool is_valid() const { return m_is_valid; } + const PKGHeader& get_header() const { return m_header; } package_install_result check_target_app_version() const; static package_install_result extract_data(std::deque& readers, std::deque& bootable_paths); const psf::registry& get_psf() const { return m_psf; } diff --git a/rpcs3/Crypto/utils.cpp b/rpcs3/Crypto/utils.cpp index 8d2fd4e9aa..71f687bf83 100644 --- a/rpcs3/Crypto/utils.cpp +++ b/rpcs3/Crypto/utils.cpp @@ -12,6 +12,7 @@ #include #include #include +#include "Utilities/StrFmt.h" #include "Utilities/StrUtil.h" #include "Utilities/File.h" diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index aef3321208..8bcd0f5215 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -412,6 +412,7 @@ target_sources(rpcs3_emu PRIVATE Io/pad_config_types.cpp Io/pad_types.cpp Io/PadHandler.cpp + Io/ps_move_data.cpp Io/rb3drums_config.cpp Io/RB3MidiDrums.cpp Io/RB3MidiGuitar.cpp @@ -442,6 +443,8 @@ target_sources(rpcs3_emu PRIVATE NP/np_requests.cpp NP/signaling_handler.cpp NP/np_structs_extra.cpp + NP/clans_client.cpp + NP/clans_config.cpp NP/rpcn_client.cpp NP/rpcn_config.cpp NP/rpcn_countries.cpp @@ -517,12 +520,15 @@ target_sources(rpcs3_emu PRIVATE RSX/Overlays/overlay_video.cpp RSX/Overlays/Shaders/shader_loading_dialog.cpp RSX/Overlays/Shaders/shader_loading_dialog_native.cpp + RSX/Program/Assembler/FPASM.cpp + RSX/Program/Assembler/FPOpcodes.cpp RSX/Program/Assembler/FPToCFG.cpp + RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp + RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp RSX/Program/CgBinaryProgram.cpp RSX/Program/CgBinaryFragmentProgram.cpp RSX/Program/CgBinaryVertexProgram.cpp RSX/Program/FragmentProgramDecompiler.cpp - RSX/Program/FragmentProgramRegister.cpp RSX/Program/GLSLCommon.cpp RSX/Program/ProgramStateCache.cpp RSX/Program/program_util.cpp @@ -624,6 +630,11 @@ if(TARGET 3rdparty_vulkan) RSX/VK/VKTextureCache.cpp RSX/VK/VulkanAPI.cpp ) + if(APPLE) + target_sources(rpcs3_emu PRIVATE + RSX/VK/vkutils/metal_layer.mm + ) + endif() endif() find_package(Threads REQUIRED) diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index bb8e46eee1..afec56f7e1 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -506,7 +506,7 @@ extern f64 get_cpu_program_usage_percent(u64 hash) thread_local DECLARE(cpu_thread::g_tls_this_thread) = nullptr; // Total number of CPU threads -static atomic_t s_cpu_counter{0}; +static atomic_t s_cpu_counter{0}; // List of posted tasks for suspend_all //static atomic_t s_cpu_work[128]{}; diff --git a/rpcs3/Emu/CPU/CPUTranslator.cpp b/rpcs3/Emu/CPU/CPUTranslator.cpp index d17b1ec977..7cb9186d4d 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.cpp +++ b/rpcs3/Emu/CPU/CPUTranslator.cpp @@ -71,7 +71,11 @@ cpu_translator::cpu_translator(llvm::Module* _module, bool is_be) result = m_ir->CreateInsertElement(v, m_ir->CreateExtractElement(data0, m_ir->CreateExtractElement(mask, i)), i); v->addIncoming(result, loop); m_ir->CreateCondBr(m_ir->CreateICmpULT(i, m_ir->getInt32(16)), loop, next); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) + m_ir->SetInsertPoint(next->getFirstNonPHIIt()); +#else m_ir->SetInsertPoint(next->getFirstNonPHI()); +#endif result = m_ir->CreateSelect(m_ir->CreateICmpSLT(index, zeros), zeros, result); return result; diff --git a/rpcs3/Emu/CPU/CPUTranslator.h b/rpcs3/Emu/CPU/CPUTranslator.h index bb5b262294..c709349080 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.h +++ b/rpcs3/Emu/CPU/CPUTranslator.h @@ -1149,7 +1149,11 @@ struct llvm_fshl static llvm::Function* get_fshl(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) + return llvm::Intrinsic::getOrInsertDeclaration(_module, llvm::Intrinsic::fshl, {llvm_value_t::get_type(ir->getContext())}); +#else return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshl, {llvm_value_t::get_type(ir->getContext())}); +#endif } static llvm::Value* fold(llvm::IRBuilder<>* ir, llvm::Value* v1, llvm::Value* v2, llvm::Value* v3) @@ -1221,7 +1225,11 @@ struct llvm_fshr static llvm::Function* get_fshr(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) + return llvm::Intrinsic::getOrInsertDeclaration(_module, llvm::Intrinsic::fshr, {llvm_value_t::get_type(ir->getContext())}); +#else return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshr, {llvm_value_t::get_type(ir->getContext())}); +#endif } static llvm::Value* fold(llvm::IRBuilder<>* ir, llvm::Value* v1, llvm::Value* v2, llvm::Value* v3) @@ -2220,7 +2228,11 @@ struct llvm_add_sat static llvm::Function* get_add_sat(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) + return llvm::Intrinsic::getOrInsertDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); +#else return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); +#endif } llvm::Value* eval(llvm::IRBuilder<>* ir) const @@ -2303,7 +2315,11 @@ struct llvm_sub_sat static llvm::Function* get_sub_sat(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) + return llvm::Intrinsic::getOrInsertDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); +#else return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); +#endif } llvm::Value* eval(llvm::IRBuilder<>* ir) const @@ -3592,7 +3608,11 @@ public: llvm::Function* get_intrinsic(llvm::Intrinsic::ID id) { const auto _module = m_ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) + return llvm::Intrinsic::getOrInsertDeclaration(_module, id, {get_type()...}); +#else return llvm::Intrinsic::getDeclaration(_module, id, {get_type()...}); +#endif } template diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index 7f02a0eabe..2de2d2cd3c 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -276,6 +276,8 @@ public: u64 start_timestamp_us = 0; + std::array fake_move_data {}; // No need to be in savestate + atomic_t m_wake_up = 0; atomic_t m_done = 0; @@ -624,9 +626,9 @@ public: cellGem.notice("Could not load mouse gem config. Using defaults."); } - cellGem.notice("Real gem config=\n", g_cfg_gem_real.to_string()); - cellGem.notice("Fake gem config=\n", g_cfg_gem_fake.to_string()); - cellGem.notice("Mouse gem config=\n", g_cfg_gem_mouse.to_string()); + cellGem.notice("Real gem config=%s", g_cfg_gem_real.to_string()); + cellGem.notice("Fake gem config=%s", g_cfg_gem_fake.to_string()); + cellGem.notice("Mouse gem config=%s", g_cfg_gem_mouse.to_string()); } }; @@ -696,22 +698,165 @@ namespace gem { } - static inline u8 Y(u8 r, u8 g, u8 b) { return static_cast(0.299f * r + 0.587f * g + 0.114f * b); } - static inline u8 U(u8 r, u8 g, u8 b) { return static_cast(-0.14713f * r - 0.28886f * g + 0.436f * b); } - static inline u8 V(u8 r, u8 g, u8 b) { return static_cast(0.615f * r - 0.51499f * g - 0.10001f * b); } + YUV(const u8 rgb[3]) + { + const u8 r = rgb[0]; + const u8 g = rgb[1]; + const u8 b = rgb[2]; + y = Y(r, g, b); + u = U(r, g, b); + v = V(r, g, b); + } + + static inline u8 Y(u8 r, u8 g, u8 b) { return static_cast(std::clamp(0.299f * r + 0.587f * g + 0.114f * b, 0.0f, 255.0f)); } + static inline u8 U(u8 r, u8 g, u8 b) { return static_cast(std::clamp(-0.169f * r - 0.331f * g + 0.499f * b + 128, 0.0f, 255.0f)); } + static inline u8 V(u8 r, u8 g, u8 b) { return static_cast(std::clamp(0.499f * r - 0.460f * g - 0.040f * b + 128, 0.0f, 255.0f)); } }; - bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format, - const std::vector& video_data_in, u32 width, u32 height, - u8* video_data_out, u32 video_data_out_size, std::string_view caller) + template + static inline void debayer_raw8_impl(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b) { - if (output_format != CELL_GEM_NO_VIDEO_OUTPUT && !video_data_out) + constexpr u32 in_pitch = 640; + constexpr u32 out_pitch = 640 * 4; + + // Hamilton–Adams demosaicing + for (s32 y = 0; y < 480; y++) + { + const bool is_even_y = (y % 2) == 0; + const u8* srcc = src + y * in_pitch; + const u8* srcu = src + std::max(0, y - 1) * in_pitch; + const u8* srcd = src + std::min(480 - 1, y + 1) * in_pitch; + + u8* dst0 = dst + y * out_pitch; + + // Split loops (roughly twice the performance by removing one condition) + if (is_even_y) + { + for (s32 x = 0; x < 640; x++, dst0 += 4) + { + const bool is_even_x = (x % 2) == 0; + const int xl = std::max(0, x - 1); + const int xr = std::min(640 - 1, x + 1); + + u8 r, b, g; + + if (is_even_x) + { + // Blue pixel + const u8 up = srcu[x]; + const u8 down = srcd[x]; + const u8 left = srcc[xl]; + const u8 right = srcc[xr]; + const int dh = std::abs(int(left) - int(right)); + const int dv = std::abs(int(up) - int(down)); + + r = (srcu[xl] + srcu[xr] + srcd[xl] + srcd[xr]) / 4; + if (dh < dv) + g = (left + right) / 2; + else if (dv < dh) + g = (up + down) / 2; + else + g = (up + down + left + right) / 4; + b = srcc[x]; + } + else + { + // Green (on blue row) + r = (srcu[x] + srcd[x]) / 2; + g = srcc[x]; + b = (srcc[xl] + srcc[xr]) / 2; + } + + if constexpr (use_gain) + { + dst0[0] = static_cast(std::clamp(r * gain_r, 0.0f, 255.0f)); + dst0[1] = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); + dst0[2] = static_cast(std::clamp(g * gain_g, 0.0f, 255.0f)); + } + else + { + dst0[0] = r; + dst0[1] = g; + dst0[2] = b; + } + dst0[3] = alpha; + } + } + else + { + for (s32 x = 0; x < 640; x++, dst0 += 4) + { + const bool is_even_x = (x % 2) == 0; + const int xl = std::max(0, x - 1); + const int xr = std::min(640 - 1, x + 1); + + u8 r, b, g; + + if (is_even_x) + { + // Green (on red row) + r = (srcc[xl] + srcc[xr]) / 2; + g = srcc[x]; + b = (srcu[x] + srcd[x]) / 2; + } + else + { + // Red pixel + const u8 up = srcu[x]; + const u8 down = srcd[x]; + const u8 left = srcc[xl]; + const u8 right = srcc[xr]; + const int dh = std::abs(int(left) - int(right)); + const int dv = std::abs(int(up) - int(down)); + + r = srcc[x]; + if (dh < dv) + g = (left + right) / 2; + else if (dv < dh) + g = (up + down) / 2; + else + g = (up + down + left + right) / 4; + b = (srcu[xl] + srcu[xr] + srcd[xl] + srcd[xr]) / 4; + } + + if constexpr (use_gain) + { + dst0[0] = static_cast(std::clamp(r * gain_r, 0.0f, 255.0f)); + dst0[1] = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); + dst0[2] = static_cast(std::clamp(g * gain_g, 0.0f, 255.0f)); + } + else + { + dst0[0] = r; + dst0[1] = g; + dst0[2] = b; + } + dst0[3] = alpha; + } + } + } + } + + static void debayer_raw8(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b) + { + if (gain_r != 1.0f || gain_g != 1.0f || gain_b != 1.0f) + debayer_raw8_impl(src, dst, alpha, gain_r, gain_g, gain_b); + else + debayer_raw8_impl(src, dst, alpha, gain_r, gain_g, gain_b); + } + + bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc, + const std::vector& video_data_in, u32 width, u32 height, + u8* video_data_out, u32 video_data_out_size, u8* buffer_memory, + std::string_view caller) + { + if (vc.output_format != CELL_GEM_NO_VIDEO_OUTPUT && !video_data_out) { return false; } const u32 required_in_size = get_buffer_size_by_format(static_cast(input_format), width, height); - const s32 required_out_size = cellGemGetVideoConvertSize(output_format); + const s32 required_out_size = cellGemGetVideoConvertSize(vc.output_format); if (video_data_in.size() != required_in_size) { @@ -721,7 +866,7 @@ namespace gem if (required_out_size < 0 || video_data_out_size != static_cast(required_out_size)) { - cellGem.error("convert: out_size unknown: required=%d, actual=%d, format %d (called from %s)", required_out_size, video_data_out_size, output_format, caller); + cellGem.error("convert: out_size unknown: required=%d, actual=%d, format %d (called from %s)", required_out_size, video_data_out_size, vc.output_format, caller); return false; } @@ -730,7 +875,121 @@ namespace gem return false; } - switch (output_format) + thread_local std::vector corrected_buffer; + thread_local std::vector combined_buffer; + thread_local std::vector conversion_buffer; + + const u8* src_data = video_data_in.data(); + const u8 alpha = vc.alpha; + const f32 gain_r = vc.gain * vc.blue_gain; + const f32 gain_g = vc.gain * vc.green_gain; + const f32 gain_b = vc.gain * vc.red_gain; + + // Only RAW8 should be relevant for cellGem unless I'm mistaken + if (input_format == CELL_CAMERA_RAW8) + { + // TODO: CELL_GEM_AUTO_WHITE_BALANCE + // TODO: CELL_GEM_GAMMA_BOOST + + // Correct outliers + if (vc.conversion_flags & CELL_GEM_FILTER_OUTLIER_PIXELS) + { + corrected_buffer.resize(width * height); + + for (u32 y = 0; y < height; y++) + { + const u8* src = src_data + y * 640; + u8* dst = &corrected_buffer[y * 640]; + + for (u32 x = 0; x < width; x++, src++) + { + // Let's just say these 2 are outliers + if (const u8 val = *src; val > 0 && val < 255) + { + *dst++ = val; + continue; + } + + // Just take the 4 neighbours for now + s32 sum = 0; + if (y >= 2) sum += *(src - (2 * 640)); + if (x >= 2) sum += *(src - 2); + if (x < 638) sum += *(src + 2); + if (y < 478) sum += *(src + (2 * 640)); + + *dst++ = sum / 4; // Ignore count. It will only be less than 4 on the edges + } + } + + src_data = corrected_buffer.data(); + } + + // Combine with previous frame + if (buffer_memory && (vc.conversion_flags & CELL_GEM_COMBINE_PREVIOUS_INPUT_FRAME)) + { + combined_buffer.resize(width * height); + + for (u32 i = 0; i < combined_buffer.size(); i++) + { + const u8 val = src_data[i]; + u8& old = buffer_memory[i]; + combined_buffer[i] = (old + val) / 2; + old = val; + } + + src_data = combined_buffer.data(); + } + + switch (vc.output_format) + { + case CELL_GEM_YUV_640x480: + case CELL_GEM_YUV422_640x480: + case CELL_GEM_YUV411_640x480: + { + // Let's debayer the image first for YUV formats + conversion_buffer.resize(cellGemGetVideoConvertSize(CELL_GEM_RGBA_640x480)); + + debayer_raw8(src_data, conversion_buffer.data(), alpha, gain_r, gain_g, gain_b); + + src_data = conversion_buffer.data(); + input_format = CELL_CAMERA_RGBA; + width = 640; + height = 480; + break; + } + case CELL_GEM_BAYER_RESTORED: + case CELL_GEM_BAYER_RESTORED_RGGB: + case CELL_GEM_BAYER_RESTORED_RASTERIZED: + { + // Let's apply gain + if (gain_r != 1.0f || gain_g != 1.0f || gain_b != 1.0f) + { + conversion_buffer.resize(cellGemGetVideoConvertSize(CELL_GEM_RGBA_640x480)); + + const f32 bggr_gains[2][2] = {{gain_b, gain_g}, {gain_g, gain_r}}; + const u8* src = src_data; + u8* dst = conversion_buffer.data(); + + for (u32 y = 0; y < 480; y++) + { + const f32* gains = bggr_gains[y % 2]; + + for (u32 x = 0; x < 640; x++) + { + *dst++ = static_cast(std::clamp(*src++ * gains[x % 2], 0.0f, 255.0f)); + } + } + + src_data = conversion_buffer.data(); + } + break; + } + default: + break; + } + } + + switch (vc.output_format) { case CELL_GEM_RGBA_640x480: // RGBA output; 640*480*4-byte output buffer required { @@ -738,51 +997,18 @@ namespace gem { case CELL_CAMERA_RAW8: { - const u32 in_pitch = width; - const u32 out_pitch = width * 4; - - for (u32 y = 0; y < height - 1; y += 2) - { - const u8* src0 = &video_data_in[y * in_pitch]; - const u8* src1 = src0 + in_pitch; - - u8* dst0 = video_data_out + y * out_pitch; - u8* dst1 = dst0 + out_pitch; - - for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst0 += 8, dst1 += 8) - { - const u8 b = src0[0]; - const u8 g0 = src0[1]; - const u8 g1 = src1[0]; - const u8 r = src1[1]; - - const u8 top[4] = { r, g0, b, 255 }; - const u8 bottom[4] = { r, g1, b, 255 }; - - // Top-Left - std::memcpy(dst0, top, 4); - - // Top-Right Pixel - std::memcpy(dst0 + 4, top, 4); - - // Bottom-Left Pixel - std::memcpy(dst1, bottom, 4); - - // Bottom-Right Pixel - std::memcpy(dst1 + 4, bottom, 4); - } - } + debayer_raw8(src_data, video_data_out, alpha, gain_r, gain_g, gain_b); break; } case CELL_CAMERA_RGBA: { - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); break; } default: { - cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } @@ -792,17 +1018,18 @@ namespace gem { if (input_format == CELL_CAMERA_RAW8) { - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); } else { - cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); return false; } break; } case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous) { + // YUV 4:4:4 planar. 1 value each per pixel const u32 yuv_pitch = width; u8* dst_y = video_data_out; @@ -813,61 +1040,21 @@ namespace gem { case CELL_CAMERA_RAW8: { - const u32 in_pitch = width; - - for (u32 y = 0; y < height - 1; y += 2) - { - const u8* src0 = &video_data_in[y * in_pitch]; - const u8* src1 = src0 + in_pitch; - - u8* dst_y0 = dst_y + y * yuv_pitch; - u8* dst_y1 = dst_y0 + yuv_pitch; - - u8* dst_u0 = dst_u + y * yuv_pitch; - u8* dst_u1 = dst_u0 + yuv_pitch; - - u8* dst_v0 = dst_v + y * yuv_pitch; - u8* dst_v1 = dst_v0 + yuv_pitch; - - for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst_y0 += 2, dst_y1 += 2, dst_u0 += 2, dst_u1 += 2, dst_v0 += 2, dst_v1 += 2) - { - const u8 b = src0[0]; - const u8 g0 = src0[1]; - const u8 g1 = src1[0]; - const u8 r = src1[1]; - - // Convert RGBA to YUV - const YUV yuv_top = YUV(r, g0, b); - const YUV yuv_bottom = YUV(r, g1, b); - - dst_y0[0] = dst_y0[1] = yuv_top.y; - dst_y1[0] = dst_y1[1] = yuv_bottom.y; - - dst_u0[0] = dst_u0[1] = yuv_top.u; - dst_u1[0] = dst_u1[1] = yuv_bottom.u; - - dst_v0[0] = dst_v0[1] = yuv_top.v; - dst_v1[0] = dst_v1[1] = yuv_bottom.v; - } - } + fmt::throw_exception("Unreachable: should already be debayered"); break; } case CELL_CAMERA_RGBA: { - const u32 in_pitch = width / 4; + const u32 in_pitch = width * 4; for (u32 y = 0; y < height; y++) { - const u8* src = &video_data_in[y * in_pitch]; + const u8* src = src_data + y * in_pitch; for (u32 x = 0; x < width; x++, src += 4) { - const u8 r = src[0]; - const u8 g = src[1]; - const u8 b = src[2]; - // Convert RGBA to YUV - const YUV yuv = YUV(r, g, b); + const YUV yuv = YUV(src); *dst_y++ = yuv.y; *dst_u++ = yuv.u; @@ -878,8 +1065,8 @@ namespace gem } default: { - cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } @@ -887,6 +1074,7 @@ namespace gem } case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous) { + // YUV 4:2:2 planar. 1 Y value per pixel, 1 U/V value per 2 horizontal pixels const u32 y_pitch = width; const u32 uv_pitch = width / 2; @@ -898,43 +1086,7 @@ namespace gem { case CELL_CAMERA_RAW8: { - const u32 in_pitch = width; - - for (u32 y = 0; y < height - 1; y += 2) - { - const u8* src0 = &video_data_in[y * in_pitch]; - const u8* src1 = src0 + in_pitch; - - u8* dst_y0 = dst_y + y * y_pitch; - u8* dst_y1 = dst_y0 + y_pitch; - - u8* dst_u0 = dst_u + y * uv_pitch; - u8* dst_u1 = dst_u0 + uv_pitch; - - u8* dst_v0 = dst_v + y * uv_pitch; - u8* dst_v1 = dst_v0 + uv_pitch; - - for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst_y0 += 2, dst_y1 += 2) - { - const u8 b = src0[0]; - const u8 g0 = src0[1]; - const u8 g1 = src1[0]; - const u8 r = src1[1]; - - // Convert RGBA to YUV - const YUV yuv_top = YUV(r, g0, b); - const YUV yuv_bottom = YUV(r, g1, b); - - dst_y0[0] = dst_y0[1] = yuv_top.y; - dst_y1[0] = dst_y1[1] = yuv_bottom.y; - - *dst_u0++ = yuv_top.u; - *dst_u1++ = yuv_bottom.u; - - *dst_v0++ = yuv_top.v; - *dst_v1++ = yuv_bottom.v; - } - } + fmt::throw_exception("Unreachable: should already be debayered"); break; } case CELL_CAMERA_RGBA: @@ -943,33 +1095,28 @@ namespace gem for (u32 y = 0; y < height; y++) { - const u8* src = &video_data_in[y * in_pitch]; + const u8* src = src_data + y * in_pitch; for (u32 x = 0; x < width - 1; x += 2, src += 8, dst_y += 2) { - const u8 r_0 = src[0]; - const u8 g_0 = src[1]; - const u8 b_0 = src[2]; - const u8 r_1 = src[4]; - const u8 g_1 = src[5]; - const u8 b_1 = src[6]; - // Convert RGBA to YUV - const YUV yuv_0 = YUV(r_0, g_0, b_0); - const u8 y_1 = YUV::Y(r_1, g_1, b_1); + const YUV yuv_0 = YUV(src); + const YUV yuv_1 = YUV(src + 4); dst_y[0] = yuv_0.y; - dst_y[1] = y_1; - *dst_u++ = yuv_0.u; - *dst_v++ = yuv_0.v; + dst_y[1] = yuv_1.y; + + // Average U/V from 2 horizontal pixels + *dst_u++ = (yuv_0.u + yuv_1.u) / 2; + *dst_v++ = (yuv_0.v + yuv_1.v) / 2; } } break; } default: { - cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } @@ -977,109 +1124,54 @@ namespace gem } case CELL_GEM_YUV411_640x480: // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous) { - const u32 y_pitch = width; - const u32 uv_pitch = width / 4; - + // YUV 4:1:1 planar. 1 Y value per pixel, 1 U/V value per 2x2 pixel block u8* dst_y = video_data_out; - u8* dst_u = dst_y + y_pitch * height; - u8* dst_v = dst_u + uv_pitch * height; + u8* dst_u = dst_y + 640 * 480; + u8* dst_v = dst_u + 320 * 240; switch (input_format) { case CELL_CAMERA_RAW8: { - const u32 in_pitch = width; - - for (u32 y = 0; y < height - 1; y += 2) - { - const u8* src0 = &video_data_in[y * in_pitch]; - const u8* src1 = src0 + in_pitch; - - u8* dst_y0 = dst_y + y * y_pitch; - u8* dst_y1 = dst_y0 + y_pitch; - - u8* dst_u0 = dst_u + y * uv_pitch; - u8* dst_u1 = dst_u0 + uv_pitch; - - u8* dst_v0 = dst_v + y * uv_pitch; - u8* dst_v1 = dst_v0 + uv_pitch; - - for (u32 x = 0; x < width - 3; x += 4, src0 += 4, src1 += 4, dst_y0 += 4, dst_y1 += 4) - { - const u8 b_left = src0[0]; - const u8 g0_left = src0[1]; - const u8 b_right = src0[2]; - const u8 g0_right = src0[3]; - - const u8 g1_left = src1[0]; - const u8 r_left = src1[1]; - const u8 g1_right = src1[2]; - const u8 r_right = src1[3]; - - // Convert RGBA to YUV - const YUV yuv_top_left = YUV(r_left, g0_left, b_left); // Re-used for top-right - const u8 y_top_right = YUV::Y(r_right, g0_right, b_right); - const YUV yuv_bottom_left = YUV(r_left, g1_left, b_left); // Re-used for bottom-right - const u8 y_bottom_right = YUV::Y(r_right, g1_right, b_right); - - dst_y0[0] = dst_y0[1] = yuv_top_left.y; - dst_y0[2] = dst_y0[3] = y_top_right; - - dst_y1[0] = dst_y1[1] = yuv_bottom_left.y; - dst_y1[2] = dst_y1[3] = y_bottom_right; - - *dst_u0++ = yuv_top_left.u; - *dst_u1++ = yuv_bottom_left.u; - - *dst_v0++ = yuv_top_left.v; - *dst_v1++ = yuv_bottom_left.v; - } - } + fmt::throw_exception("Unreachable: should already be debayered"); break; } case CELL_CAMERA_RGBA: { const u32 in_pitch = width * 4; - for (u32 y = 0; y < height; y++) + // 2 rows at a time to get a 2x2 pixel block + for (u32 y = 0; y < height - 1; y += 2) { - const u8* src = &video_data_in[y * in_pitch]; + const u8* src = src_data + y * in_pitch; + const u8* src2 = src + in_pitch; + u8* dst_y1 = dst_y + y * 640; + u8* dst_y2 = dst_y1 + 640; - for (u32 x = 0; x < width - 3; x += 4, src += 16, dst_y += 4) + for (u32 x = 0; x < width - 1; x += 2, src += 8, src2 += 8, dst_y1 += 2, dst_y2 += 2) { - const u8 r_0 = src[0]; - const u8 g_0 = src[1]; - const u8 b_0 = src[2]; - const u8 r_1 = src[4]; - const u8 g_1 = src[5]; - const u8 b_1 = src[6]; - const u8 r_2 = src[8]; - const u8 g_2 = src[9]; - const u8 b_2 = src[10]; - const u8 r_3 = src[12]; - const u8 g_3 = src[13]; - const u8 b_3 = src[14]; - // Convert RGBA to YUV - const YUV yuv_0 = YUV(r_0, g_0, b_0); - const u8 y_1 = YUV::Y(r_1, g_1, b_1); - const u8 y_2 = YUV::Y(r_2, g_2, b_2); - const u8 y_3 = YUV::Y(r_3, g_3, b_3); + const YUV yuv_0 = YUV(src); + const YUV yuv_1 = YUV(src + 4); + const YUV yuv_2 = YUV(src2); + const YUV yuv_3 = YUV(src2 + 4); - dst_y[0] = yuv_0.y; - dst_y[1] = y_1; - dst_y[2] = y_2; - dst_y[3] = y_3; - *dst_u++ = yuv_0.u; - *dst_v++ = yuv_0.v; + dst_y1[0] = yuv_0.y; + dst_y1[1] = yuv_1.y; + dst_y2[0] = yuv_2.y; + dst_y2[1] = yuv_3.y; + + // Average U/V from 2x2 pixel block + *dst_u++ = (yuv_0.u + yuv_1.u + yuv_2.u + yuv_3.u) / 4; + *dst_v++ = (yuv_0.v + yuv_1.v + yuv_2.v + yuv_3.v) / 4; } } break; } default: { - cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } @@ -1096,7 +1188,7 @@ namespace gem for (u32 y = 0; y < height - 1; y += 2) { - const u8* src0 = &video_data_in[y * in_pitch]; + const u8* src0 = src_data + y * in_pitch; const u8* src1 = src0 + in_pitch; u8* dst0 = video_data_out + (y / 2) * out_pitch; @@ -1109,8 +1201,8 @@ namespace gem const u8 g1 = src1[0]; const u8 r = src1[1]; - const u8 top[4] = { r, g0, b, 255 }; - const u8 bottom[4] = { r, g1, b, 255 }; + const u8 top[4] = { r, g0, b, alpha }; + const u8 bottom[4] = { r, g1, b, alpha }; // Top-Left std::memcpy(dst0, top, 4); @@ -1128,7 +1220,7 @@ namespace gem for (u32 y = 0; y < height / 2; y++) { - const u8* src = &video_data_in[y * 2 * in_pitch]; + const u8* src = src_data + y * 2 * in_pitch; u8* dst = video_data_out + y * out_pitch; for (u32 x = 0; x < width / 2; x++, src += 4 * 2, dst += 4) @@ -1140,19 +1232,97 @@ namespace gem } default: { - cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } break; } case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B + { + if (input_format == CELL_CAMERA_RAW8) + { + const u32 dst_w = std::min(320u, width / 2); + const u32 dst_h = std::min(240u, height / 2); + const u32 in_pitch = width; + constexpr u32 out_pitch = 320 * 4; + + for (u32 y = 0; y < dst_h; y++) + { + const u8* src0 = src_data + y * 2 * in_pitch; + const u8* src1 = src0 + in_pitch; + + u8* dst = video_data_out + y * out_pitch; + + for (u32 x = 0; x < dst_w; x++, src0 += 2, src1 += 2, dst += 4) + { + const u8 b = src0[0]; + const u8 g0 = src0[1]; + const u8 g1 = src1[0]; + const u8 r = src1[1]; + + dst[0] = r; + dst[1] = g0; + dst[2] = g1; + dst[3] = b; + } + } + } + else + { + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); + return false; + } + break; + } case CELL_GEM_BAYER_RESTORED_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters { - cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); - return false; + if (input_format == CELL_CAMERA_RAW8) + { + const u32 dst_w = std::min(320u, width / 2); + const u32 dst_h = std::min(240u, height / 2); + const u32 in_pitch = width; + constexpr u32 out_plane = 320 * 240; + constexpr u32 out_pitch = 320; + + u8* dst_plane_r = video_data_out; + u8* dst_plane_g1 = video_data_out + out_plane; + u8* dst_plane_g2 = video_data_out + out_plane * 2; + u8* dst_plane_b = video_data_out + out_plane * 3; + + for (u32 y = 0; y < dst_h; y++) + { + const u8* src0 = src_data + y * 2 * in_pitch; + const u8* src1 = src0 + in_pitch; + + u8* dst_r = dst_plane_r + y * out_pitch; + u8* dst_g1 = dst_plane_g1 + y * out_pitch; + u8* dst_g2 = dst_plane_g2 + y * out_pitch; + u8* dst_b = dst_plane_b + y * out_pitch; + + for (u32 x = 0; x < dst_w; x++, src0 += 2, src1 += 2) + { + const u8 b = src0[0]; + const u8 g0 = src0[1]; + const u8 g1 = src1[0]; + const u8 r = src1[1]; + + dst_r[x] = r; + dst_g1[x] = g0; + dst_g2[x] = g1; + dst_b[x] = b; + } + } + } + else + { + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); + return false; + } + break; } case CELL_GEM_NO_VIDEO_OUTPUT: // Disable video output { @@ -1161,7 +1331,7 @@ namespace gem } default: { - cellGem.error("Trying to convert %s to %s (called from %s)", input_format, output_format, caller); + cellGem.error("Trying to convert %s to %s (called from %s)", input_format, vc.output_format, caller); return false; } } @@ -1340,13 +1510,15 @@ void gem_config_data::operator()() const auto& shared_data = g_fxo->get(); - if (gem::convert_image_format(shared_data.format, vc.output_format, video_data_in, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size, "cellGem")) + if (gem::convert_image_format(shared_data.format, vc, video_data_in, shared_data.width, shared_data.height, + vc.video_data_out ? vc.video_data_out.get_ptr() : nullptr, video_data_out_size, + vc.buffer_memory ? vc.buffer_memory.get_ptr() : nullptr, "cellGem")) { cellGem.trace("Converted video frame of format %s to %s", shared_data.format.load(), vc.output_format.get()); if (g_cfg.io.paint_move_spheres) { - paint_spheres(vc.output_format, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size); + paint_spheres(vc.output_format, shared_data.width, shared_data.height, vc.video_data_out ? vc.video_data_out.get_ptr() : nullptr, video_data_out_size); } } @@ -1617,7 +1789,7 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle const f32 scaling_width = x_max / static_cast(shared_data.width); const f32 scaling_height = y_max / static_cast(shared_data.height); - const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius; + const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius); // Image coordinates in pixels const f32 image_x = static_cast(x_pos) / scaling_width; @@ -1657,7 +1829,7 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle } } -static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, const ps_move_data& move_data) +static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, ps_move_data& move_data) { const auto& shared_data = g_fxo->get(); @@ -1670,7 +1842,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con const f32 scaling_width = x_max / static_cast(shared_data.width); const f32 scaling_height = y_max / static_cast(shared_data.height); - const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius; + const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius); // Image coordinates in pixels const f32 image_x = static_cast(x_pos) / scaling_width; @@ -1703,10 +1875,10 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con // Calculate orientation if (g_cfg.io.move == move_handler::real || (g_cfg.io.move == move_handler::fake && move_data.orientation_enabled)) { - gem_state->quat[0] = move_data.quaternion[0]; // x - gem_state->quat[1] = move_data.quaternion[1]; // y - gem_state->quat[2] = move_data.quaternion[2]; // z - gem_state->quat[3] = move_data.quaternion[3]; // w + gem_state->quat[0] = move_data.quaternion.x(); + gem_state->quat[1] = move_data.quaternion.y(); + gem_state->quat[2] = move_data.quaternion.z(); + gem_state->quat[3] = move_data.quaternion.w(); } else { @@ -1733,6 +1905,17 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con gem_state->quat[3] = q_w; } + if constexpr (!ps_move_data::use_imu_for_velocity) + { + move_data.update_velocity(shared_data.frame_timestamp_us, gem_state->pos); + + for (u32 i = 0; i < 3; i++) + { + gem_state->vel[i] = move_data.vel_world[i]; + gem_state->accel[i] = move_data.accel_world[i]; + } + } + // Update visibility for fake handlers if (g_cfg.io.move != move_handler::real) { @@ -1906,9 +2089,17 @@ static void ps_move_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& co if constexpr (std::is_same_v>) { gem_state->temperature = pad->move_data.temperature; - gem_state->accel[0] = pad->move_data.accelerometer_x * 1000; // linear velocity in mm/s² - gem_state->accel[1] = pad->move_data.accelerometer_y * 1000; // linear velocity in mm/s² - gem_state->accel[2] = pad->move_data.accelerometer_z * 1000; // linear velocity in mm/s² + + for (u32 i = 0; i < 3; i++) + { + if constexpr (ps_move_data::use_imu_for_velocity) + { + gem_state->vel[i] = pad->move_data.vel_world[i]; + gem_state->accel[i] = pad->move_data.accel_world[i]; + } + gem_state->angvel[i] = pad->move_data.angvel_world[i]; + gem_state->angaccel[i] = pad->move_data.angaccel_world[i]; + } pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max, pad->move_data); } @@ -2147,7 +2338,8 @@ static void mouse_pos_to_gem_state(u32 mouse_no, gem_config::gem_controller& con if constexpr (std::is_same_v>) { - pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, {}); + ps_move_data& move_data = ::at32(g_fxo->get().fake_move_data, mouse_no); + pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, move_data); } else if constexpr (std::is_same_v>) { @@ -2215,7 +2407,8 @@ static void gun_pos_to_gem_state(u32 gem_no, gem_config::gem_controller& control if constexpr (std::is_same_v>) { - pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, {}); + ps_move_data& move_data = ::at32(g_fxo->get().fake_move_data, gem_no); + pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, move_data); } else if constexpr (std::is_same_v>) { @@ -2769,7 +2962,7 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v inertial_state->timestamp = (get_guest_system_time() - gem.start_timestamp_us); inertial_state->counter = gem.inertial_counter++; - inertial_state->accelerometer[0] = 10; // Current gravity in m/s² + inertial_state->accelerometer[2] = 1.0f; // Current gravity in G units (9.81 == 1 unit) switch (g_cfg.io.move) { @@ -2786,12 +2979,12 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v if (pad && pad->is_connected() && !pad->is_copilot()) { inertial_state->temperature = pad->move_data.temperature; - inertial_state->accelerometer[0] = pad->move_data.accelerometer_x; - inertial_state->accelerometer[1] = pad->move_data.accelerometer_y; - inertial_state->accelerometer[2] = pad->move_data.accelerometer_z; - inertial_state->gyro[0] = pad->move_data.gyro_x; - inertial_state->gyro[1] = pad->move_data.gyro_y; - inertial_state->gyro[2] = pad->move_data.gyro_z; + + for (u32 i = 0; i < 3; i++) + { + inertial_state->accelerometer[i] = pad->move_data.accelerometer[i]; + inertial_state->gyro[i] = pad->move_data.gyro[i]; + } } } diff --git a/rpcs3/Emu/Cell/Modules/cellGem.h b/rpcs3/Emu/Cell/Modules/cellGem.h index c70e2386ba..7dfdcb3b14 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.h +++ b/rpcs3/Emu/Cell/Modules/cellGem.h @@ -246,15 +246,15 @@ struct CellGemInfo // z increases towards user (away from the camera) struct CellGemState { - be_t pos[4]; // center of sphere (mm) - be_t vel[4]; // velocity of sphere (mm/s) - be_t accel[4]; // acceleration of sphere (mm/s²) + be_t pos[4]; // center of sphere in world coordinates (mm) + be_t vel[4]; // velocity of sphere in world coordinates (mm/s) + be_t accel[4]; // acceleration of sphere in world coordinates (mm/s²) be_t quat[4]; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up) - be_t angvel[4]; // angular velocity of controller (radians/s) - be_t angaccel[4]; // angular acceleration of controller (radians/s²) - be_t handle_pos[4]; // center of controller handle (mm) - be_t handle_vel[4]; // velocity of controller handle (mm/s) - be_t handle_accel[4]; // acceleration of controller handle (mm/s²) + be_t angvel[4]; // angular velocity of controller in world coordinates (radians/s) + be_t angaccel[4]; // angular acceleration of controller in world coordinates (radians/s²) + be_t handle_pos[4]; // center of controller handle in world coordinates (mm) + be_t handle_vel[4]; // velocity of controller handle in world coordinates (mm/s) + be_t handle_accel[4]; // acceleration of controller handle in world coordinates (mm/s²) CellGemPadData pad; CellGemExtPortData ext; be_t timestamp; // system_time_t (microseconds) diff --git a/rpcs3/Emu/Cell/Modules/sceNp.h b/rpcs3/Emu/Cell/Modules/sceNp.h index 12ca388ba2..6f29a2f8a9 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.h +++ b/rpcs3/Emu/Cell/Modules/sceNp.h @@ -32,6 +32,7 @@ using SceNpBasicMessageRecvAction = u32; using SceNpClanId = u32; using SceNpClansMessageId = u32; +using SceNpClansMemberRole = u32; using SceNpClansMemberStatus = s32; using SceNpCustomMenuIndexMask = u32; diff --git a/rpcs3/Emu/Cell/Modules/sceNp2.cpp b/rpcs3/Emu/Cell/Modules/sceNp2.cpp index 7a2ee50bd8..820fbdf1c0 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp2.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp2.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" +#include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" @@ -540,7 +541,9 @@ error_code sceNpMatching2GetClanLobbyId(SceNpMatching2ContextId ctxId, SceNpClan return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - return CELL_OK; + // Returning this rather than `CELL_OK` allows for games to + // not need Matching2 Clans support to connect, when Clans are enabled. + return SCE_NP_MATCHING2_SERVER_ERROR_SERVICE_UNAVAILABLE; } error_code sceNpMatching2GetLobbyMemberDataInternal( @@ -819,10 +822,7 @@ error_code sceNpMatching2AbortRequest(SceNpMatching2ContextId ctxId, SceNpMatchi return SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND; } - if (!nph.abort_request(reqId)) - return SCE_NP_MATCHING2_ERROR_REQUEST_NOT_FOUND; - - return CELL_OK; + return nph.abort_request(reqId); } error_code sceNpMatching2GetServerInfo( diff --git a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp index 91d22079fe..64ac967d8e 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp @@ -1,10 +1,14 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" +#include "Emu/NP/np_handler.h" +#include "Emu/NP/clans_client.h" #include "sceNp.h" +#include #include "sceNpClans.h" + LOG_CHANNEL(sceNpClans); template<> @@ -14,6 +18,7 @@ void fmt_class_string::format(std::string& out, u64 arg) { switch (error) { + STR_CASE(SCE_NP_CLANS_SUCCESS); STR_CASE(SCE_NP_CLANS_ERROR_ALREADY_INITIALIZED); STR_CASE(SCE_NP_CLANS_ERROR_NOT_INITIALIZED); STR_CASE(SCE_NP_CLANS_ERROR_NOT_SUPPORTED); @@ -94,6 +99,9 @@ error_code sceNpClansInit(vm::cptr commId, vm::cptr client = std::make_shared(); + clans_manager.client = client; clans_manager.is_initialized = true; return CELL_OK; @@ -110,6 +118,7 @@ error_code sceNpClansTerm() return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + clans_manager.client.reset(); clans_manager.is_initialized = false; return CELL_OK; @@ -117,7 +126,7 @@ error_code sceNpClansTerm() error_code sceNpClansCreateRequest(vm::ptr handle, u64 flags) { - sceNpClans.todo("sceNpClansCreateRequest(handle=*0x%x, flags=0x%llx)", handle, flags); + sceNpClans.warning("sceNpClansCreateRequest(handle=*0x%x, flags=0x%x)", handle, flags); if (!g_fxo->get().is_initialized) { @@ -134,36 +143,58 @@ error_code sceNpClansCreateRequest(vm::ptr handle, u64 return SCE_NP_CLANS_ERROR_NOT_SUPPORTED; } + auto& clans_manager = g_fxo->get(); + + s32 reqId = 0; + SceNpClansError res = clans_manager.client->create_request(&reqId); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + + *handle = reqId; + return CELL_OK; } -error_code sceNpClansDestroyRequest(vm::ptr handle) +error_code sceNpClansDestroyRequest(SceNpClansRequestHandle handle) { - sceNpClans.todo("sceNpClansDestroyRequest(handle=*0x%x)", handle); + sceNpClans.warning("sceNpClansDestroyRequest(handle=*0x%x)", handle); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& clans_manager = g_fxo->get(); + + SceNpClansError res = clans_manager.client->destroy_request(handle); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } -error_code sceNpClansAbortRequest(vm::ptr handle) +error_code sceNpClansAbortRequest(SceNpClansRequestHandle handle) { - sceNpClans.todo("sceNpClansAbortRequest(handle=*0x%x)", handle); + sceNpClans.warning("sceNpClansAbortRequest(handle=*0x%x)", handle); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& clans_manager = g_fxo->get(); + clans_manager.client->destroy_request(handle); + return CELL_OK; } -error_code sceNpClansCreateClan(vm::ptr handle, vm::cptr name, vm::cptr tag, vm::ptr clanId) +error_code sceNpClansCreateClan(SceNpClansRequestHandle handle, vm::cptr name, vm::cptr tag, vm::ptr clanId) { - sceNpClans.todo("sceNpClansCreateClan(handle=*0x%x, name=%s, tag=%s, clanId=*0x%x)", handle, name, tag, clanId); + sceNpClans.warning("sceNpClansCreateClan(handle=*0x%x, name=%s, tag=%s, clanId=*0x%x)", handle, name, tag, clanId); if (!g_fxo->get().is_initialized) { @@ -180,31 +211,60 @@ error_code sceNpClansCreateClan(vm::ptr handle, vm::cpt return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + std::string name_str; + vm::read_string(name.addr(), SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH, name_str); + + std::string tag_str; + vm::read_string(tag.addr(), SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH, tag_str); + + SceNpClansError res = clans_manager.client->create_clan(nph, handle, name_str, tag_str, clanId); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } -error_code sceNpClansDisbandClan(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansDisbandClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansDisbandClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); + sceNpClans.warning("sceNpClansDisbandClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + if (!clanId) + { + return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + } + + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError res = clans_manager.client->disband_dlan(nph, handle, clanId); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } -error_code sceNpClansGetClanList(vm::ptr handle, vm::cptr paging, vm::ptr clanList, vm::ptr pageResult) +error_code sceNpClansGetClanList(SceNpClansRequestHandle handle, vm::cptr paging, vm::ptr clanList, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansGetClanList(handle=*0x%x, paging=*0x%x, clanList=*0x%x, pageResult=*0x%x)", handle, paging, clanList, pageResult); + sceNpClans.warning("sceNpClansGetClanList(handle=*0x%x, paging=*0x%x, clanList=*0x%x, pageResult=*0x%x)", handle, paging, clanList, pageResult); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!pageResult || (paging && !clanList)) // TODO: confirm + if (!pageResult || (paging && !clanList)) { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } @@ -217,10 +277,35 @@ error_code sceNpClansGetClanList(vm::ptr handle, vm::cp } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansEntry host_clanList[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->get_clan_list(nph, handle, &host_paging, host_clanList, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (clanList && host_pageResult.count > 0) + { + std::memcpy(clanList.get_ptr(), host_clanList, sizeof(SceNpClansEntry) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansGetClanListByNpId(vm::ptr handle, vm::cptr paging, vm::cptr npid, vm::ptr clanList, vm::ptr pageResult) +// TODO: seems to not be needed, even by the PS3..? +error_code sceNpClansGetClanListByNpId(SceNpClansRequestHandle handle, vm::cptr paging, vm::cptr npid, vm::ptr clanList, vm::ptr pageResult) { sceNpClans.todo("sceNpClansGetClanListByNpId(handle=*0x%x, paging=*0x%x, npid=*0x%x, clanList=*0x%x, pageResult=*0x%x)", handle, paging, npid, clanList, pageResult); @@ -245,7 +330,8 @@ error_code sceNpClansGetClanListByNpId(vm::ptr handle, return CELL_OK; } -error_code sceNpClansSearchByProfile(vm::ptr handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) +// TODO: seems to not be needed, even by the PS3..? +error_code sceNpClansSearchByProfile(SceNpClansRequestHandle handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) { sceNpClans.todo("sceNpClansSearchByProfile(handle=*0x%x, paging=*0x%x, search=*0x%x, results=*0x%x, pageResult=*0x%x)", handle, paging, search, results, pageResult); @@ -270,9 +356,9 @@ error_code sceNpClansSearchByProfile(vm::ptr handle, vm return CELL_OK; } -error_code sceNpClansSearchByName(vm::ptr handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) +error_code sceNpClansSearchByName(SceNpClansRequestHandle handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansSearchByName(handle=*0x%x, paging=*0x%x, search=*0x%x, results=*0x%x, pageResult=*0x%x)", handle, paging, search, results, pageResult); + sceNpClans.warning("sceNpClansSearchByName(handle=*0x%x, paging=*0x%x, search=*0x%x, results=*0x%x, pageResult=*0x%x)", handle, paging, search, results, pageResult); if (!g_fxo->get().is_initialized) { @@ -292,12 +378,38 @@ error_code sceNpClansSearchByName(vm::ptr handle, vm::c } } + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansSearchableName host_search = {}; + std::memcpy(&host_search, search.get_ptr(), sizeof(SceNpClansSearchableName)); + + SceNpClansClanBasicInfo host_results[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->clan_search(handle, &host_paging, &host_search, host_results, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (results && host_pageResult.count > 0) + { + std::memcpy(results.get_ptr(), host_results, sizeof(SceNpClansClanBasicInfo) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansGetClanInfo(vm::ptr handle, SceNpClanId clanId, vm::ptr info) +error_code sceNpClansGetClanInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::ptr info) { - sceNpClans.todo("sceNpClansGetClanInfo(handle=*0x%x, clanId=%d, info=*0x%x)", handle, clanId, info); + sceNpClans.warning("sceNpClansGetClanInfo(handle=*0x%x, clanId=*0x%x, info=*0x%x)", handle, clanId, info); if (!g_fxo->get().is_initialized) { @@ -310,12 +422,24 @@ error_code sceNpClansGetClanInfo(vm::ptr handle, SceNpC return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& clans_manager = g_fxo->get(); + + SceNpClansClanInfo host_info = {}; + + SceNpClansError ret = clans_manager.client->get_clan_info(handle, clanId, &host_info); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(info.get_ptr(), &host_info, sizeof(SceNpClansClanInfo)); + return CELL_OK; } -error_code sceNpClansUpdateClanInfo(vm::ptr handle, SceNpClanId clanId, vm::cptr info) +error_code sceNpClansUpdateClanInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr info) { - sceNpClans.todo("sceNpClansUpdateClanInfo(handle=*0x%x, clanId=%d, info=*0x%x)", handle, clanId, info); + sceNpClans.warning("sceNpClansUpdateClanInfo(handle=*0x%x, clanId=*0x%x, info=*0x%x)", handle, clanId, info); if (!g_fxo->get().is_initialized) { @@ -328,17 +452,24 @@ error_code sceNpClansUpdateClanInfo(vm::ptr handle, Sce return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - //if (info->something > X) - //{ - // return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; - //} + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansUpdatableClanInfo host_info = {}; + std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableClanInfo)); + + SceNpClansError ret = clans_manager.client->update_clan_info(nph, handle, clanId, &host_info); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } return CELL_OK; } -error_code sceNpClansGetMemberList(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, SceNpClansMemberStatus status, vm::ptr memList, vm::ptr pageResult) +error_code sceNpClansGetMemberList(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr paging, SceNpClansMemberStatus status, vm::ptr memList, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansGetMemberList(handle=*0x%x, clanId=%d, paging=*0x%x, status=%d, memList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, status, memList, pageResult); + sceNpClans.warning("sceNpClansGetMemberList(handle=*0x%x, clanId=*0x%x, paging=*0x%x, status=0x%x, memList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, status, memList, pageResult); if (!g_fxo->get().is_initialized) { @@ -358,12 +489,36 @@ error_code sceNpClansGetMemberList(vm::ptr handle, SceN } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansMemberEntry host_memList_addr[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->get_member_list(nph, handle, clanId, &host_paging, status, host_memList_addr, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (memList && host_pageResult.count > 0) + { + std::memcpy(memList.get_ptr(), host_memList_addr, sizeof(SceNpClansMemberEntry) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansGetMemberInfo(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::ptr memInfo) +error_code sceNpClansGetMemberInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, vm::ptr memInfo) { - sceNpClans.todo("sceNpClansGetMemberInfo(handle=*0x%x, clanId=%d, npid=*0x%x, memInfo=*0x%x)", handle, clanId, npid, memInfo); + sceNpClans.warning("sceNpClansGetMemberInfo(handle=*0x%x, clanId=*0x%x, npid=*0x%x, memInfo=*0x%x)", handle, clanId, npid, memInfo); if (!g_fxo->get().is_initialized) { @@ -375,12 +530,28 @@ error_code sceNpClansGetMemberInfo(vm::ptr handle, SceN return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMemberEntry host_memInfo = {}; + + SceNpClansError ret = clans_manager.client->get_member_info(nph, handle, clanId, host_npid, &host_memInfo); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(memInfo.get_ptr(), &host_memInfo, sizeof(SceNpClansMemberEntry)); + return CELL_OK; } -error_code sceNpClansUpdateMemberInfo(vm::ptr handle, SceNpClanId clanId, vm::cptr info) +error_code sceNpClansUpdateMemberInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr info) { - sceNpClans.todo("sceNpClansUpdateMemberInfo(handle=*0x%x, clanId=%d, memInfo=*0x%x)", handle, clanId, info); + sceNpClans.warning("sceNpClansUpdateMemberInfo(handle=*0x%x, clanId=*0x%x, info=*0x%x)", handle, clanId, info); if (!g_fxo->get().is_initialized) { @@ -389,21 +560,27 @@ error_code sceNpClansUpdateMemberInfo(vm::ptr handle, S if (!info) { - // TODO: add more checks for info return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - //if (info->something > X) - //{ - // return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; - //} + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansUpdatableMemberInfo host_info = {}; + std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableMemberInfo)); + + SceNpClansError ret = clans_manager.client->update_member_info(nph, handle, clanId, &host_info); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } return CELL_OK; } -error_code sceNpClansChangeMemberRole(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, u32 role) +error_code sceNpClansChangeMemberRole(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, u32 role) { - sceNpClans.todo("sceNpClansChangeMemberRole(handle=*0x%x, clanId=%d, npid=*0x%x, role=%d)", handle, clanId, npid, role); + sceNpClans.warning("sceNpClansChangeMemberRole(handle=*0x%x, clanId=*0x%x, npid=*0x%x, role=0x%x)", handle, clanId, npid, role); if (!g_fxo->get().is_initialized) { @@ -415,10 +592,23 @@ error_code sceNpClansChangeMemberRole(vm::ptr handle, S return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->change_member_role(nph, handle, clanId, host_npid, static_cast(role)); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansGetAutoAcceptStatus(vm::ptr handle, SceNpClanId clanId, vm::ptr enable) +// TODO: no struct currently implements `autoAccept` as a field +error_code sceNpClansGetAutoAcceptStatus(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::ptr enable) { sceNpClans.todo("sceNpClansGetAutoAcceptStatus(handle=*0x%x, clanId=%d, enable=*0x%x)", handle, clanId, enable); @@ -435,7 +625,8 @@ error_code sceNpClansGetAutoAcceptStatus(vm::ptr handle return CELL_OK; } -error_code sceNpClansUpdateAutoAcceptStatus(vm::ptr handle, SceNpClanId clanId, b8 enable) +// TODO: no struct currently implements `autoAccept` as a field +error_code sceNpClansUpdateAutoAcceptStatus(SceNpClansRequestHandle handle, SceNpClanId clanId, b8 enable) { sceNpClans.todo("sceNpClansUpdateAutoAcceptStatus(handle=*0x%x, clanId=%d, enable=%d)", handle, clanId, enable); @@ -447,33 +638,51 @@ error_code sceNpClansUpdateAutoAcceptStatus(vm::ptr han return CELL_OK; } -error_code sceNpClansJoinClan(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansJoinClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansJoinClan(handle=*0x%x, clanId=%d)", handle, clanId); + sceNpClans.warning("sceNpClansJoinClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError ret = clans_manager.client->join_clan(nph, handle, clanId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansLeaveClan(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansLeaveClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansLeaveClan(handle=*0x%x, clanId=%d)", handle, clanId); + sceNpClans.warning("sceNpClansLeaveClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError ret = clans_manager.client->leave_clan(nph, handle, clanId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansKickMember(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) +error_code sceNpClansKickMember(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) { - sceNpClans.todo("sceNpClansKickMember(handle=*0x%x, clanId=%d, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); + sceNpClans.warning("sceNpClansKickMember(handle=*0x%x, clanId=*0x%x, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); if (!g_fxo->get().is_initialized) { @@ -493,12 +702,30 @@ error_code sceNpClansKickMember(vm::ptr handle, SceNpCl } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->kick_member(nph, handle, clanId, host_npid, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansSendInvitation(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) +error_code sceNpClansSendInvitation(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) { - sceNpClans.todo("sceNpClansSendInvitation(handle=*0x%x, clanId=%d, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); + sceNpClans.warning("sceNpClansSendInvitation(handle=*0x%x, clanId=*0x%x, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); if (!g_fxo->get().is_initialized) { @@ -518,12 +745,30 @@ error_code sceNpClansSendInvitation(vm::ptr handle, Sce } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->send_invitation(nph, handle, clanId, host_npid, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansCancelInvitation(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) +error_code sceNpClansCancelInvitation(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid) { - sceNpClans.todo("sceNpClansCancelInvitation(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); + sceNpClans.warning("sceNpClansCancelInvitation(handle=*0x%x, clanId=*0x%x, npid=*0x%x)", handle, clanId, npid); if (!g_fxo->get().is_initialized) { @@ -535,12 +780,24 @@ error_code sceNpClansCancelInvitation(vm::ptr handle, S return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->cancel_invitation(nph, handle, clanId, host_npid); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansSendInvitationResponse(vm::ptr handle, SceNpClanId clanId, vm::cptr message, b8 accept) +error_code sceNpClansSendInvitationResponse(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr message, b8 accept) { - sceNpClans.todo("sceNpClansSendInvitationResponse(handle=*0x%x, clanId=%d, message=*0x%x, accept=%d)", handle, clanId, message, accept); + sceNpClans.warning("sceNpClansSendInvitationResponse(handle=*0x%x, clanId=*0x%x, message=*0x%x, accept=%d)", handle, clanId, message, accept); if (!g_fxo->get().is_initialized) { @@ -555,12 +812,32 @@ error_code sceNpClansSendInvitationResponse(vm::ptr han } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->send_invitation_response(nph, handle, clanId, &host_message, accept); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansSendMembershipRequest(vm::ptr handle, u32 clanId, vm::cptr message) +error_code sceNpClansSendMembershipRequest(SceNpClansRequestHandle handle, u32 clanId, vm::cptr message) { - sceNpClans.todo("sceNpClansSendMembershipRequest(handle=*0x%x, clanId=%d, message=*0x%x)", handle, clanId, message); + sceNpClans.warning("sceNpClansSendMembershipRequest(handle=*0x%x, clanId=*0x%x, message=*0x%x)", handle, clanId, message); if (!g_fxo->get().is_initialized) { @@ -575,24 +852,49 @@ error_code sceNpClansSendMembershipRequest(vm::ptr hand } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->request_membership(nph, handle, clanId, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansCancelMembershipRequest(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansCancelMembershipRequest(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansCancelMembershipRequest(handle=*0x%x, clanId=%d)", handle, clanId); + sceNpClans.warning("sceNpClansCancelMembershipRequest(handle=*0x%x, clanId=*0x%x)", handle, clanId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError ret = clans_manager.client->cancel_request_membership(nph, handle, clanId); + + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansSendMembershipResponse(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message, b8 allow) +error_code sceNpClansSendMembershipResponse(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message, b8 allow) { - sceNpClans.todo("sceNpClansSendMembershipResponse(handle=*0x%x, clanId=%d, npid=*0x%x, message=*0x%x, allow=%d)", handle, clanId, npid, message, allow); + sceNpClans.warning("sceNpClansSendMembershipResponse(handle=*0x%x, clanId=*0x%x, npid=*0x%x, message=*0x%x, allow=%d)", handle, clanId, npid, message, allow); if (!g_fxo->get().is_initialized) { @@ -612,12 +914,30 @@ error_code sceNpClansSendMembershipResponse(vm::ptr han } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->send_membership_response(nph, handle, clanId, host_npid, &host_message, allow); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansGetBlacklist(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, vm::ptr bl, vm::ptr pageResult) +error_code sceNpClansGetBlacklist(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr paging, vm::ptr bl, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansGetBlacklist(handle=*0x%x, clanId=%d, paging=*0x%x, bl=*0x%x, pageResult=*0x%x)", handle, clanId, paging, bl, pageResult); + sceNpClans.warning("sceNpClansGetBlacklist(handle=*0x%x, clanId=*0x%x, paging=*0x%x, bl=*0x%x, pageResult=*0x%x)", handle, clanId, paging, bl, pageResult); if (!g_fxo->get().is_initialized) { @@ -637,53 +957,101 @@ error_code sceNpClansGetBlacklist(vm::ptr handle, SceNp } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansBlacklistEntry host_blacklist[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->get_blacklist(nph, handle, clanId, &host_paging, host_blacklist, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (bl && host_pageResult.count > 0) + { + std::memcpy(bl.get_ptr(), host_blacklist, sizeof(SceNpClansBlacklistEntry) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansAddBlacklistEntry(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) +error_code sceNpClansAddBlacklistEntry(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr member) { - sceNpClans.todo("sceNpClansAddBlacklistEntry(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); + sceNpClans.warning("sceNpClansAddBlacklistEntry(handle=*0x%x, clanId=*0x%x, member=*0x%x)", handle, clanId, member); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!npid) + if (!member) { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_member = {}; + std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->add_blacklist_entry(nph, handle, clanId, host_member); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansRemoveBlacklistEntry(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) +error_code sceNpClansRemoveBlacklistEntry(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr member) { - sceNpClans.todo("sceNpClansRemoveBlacklistEntry(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); + sceNpClans.warning("sceNpClansRemoveBlacklistEntry(handle=*0x%x, clanId=*0x%x, member=*0x%x)", handle, clanId, member); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!npid) + if (!member) { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_member = {}; + std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->remove_blacklist_entry(nph, handle, clanId, host_member); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansRetrieveAnnouncements(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mlist, vm::ptr pageResult) +error_code sceNpClansRetrieveAnnouncements(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mlist, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansRetrieveAnnouncements(handle=*0x%x, clanId=%d, paging=*0x%x, mlist=*0x%x, pageResult=*0x%x)", handle, clanId, paging, mlist, pageResult); + sceNpClans.warning("sceNpClansRetrieveAnnouncements(handle=*0x%x, clanId=*0x%x, paging=*0x%x, mlist=*0x%x, pageResult=*0x%x)", handle, clanId, paging, mlist, pageResult); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!pageResult || (paging && !mlist)) // TODO: confirm + if (!pageResult || (paging && !mlist) || clanId == UINT32_MAX) // TODO: confirm { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } @@ -696,12 +1064,36 @@ error_code sceNpClansRetrieveAnnouncements(vm::ptr hand } } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansMessageEntry host_announcements[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->retrieve_announcements(nph, handle, clanId, &host_paging, host_announcements, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (mlist && host_pageResult.count > 0) + { + std::memcpy(mlist.get_ptr(), host_announcements, sizeof(SceNpClansMessageEntry) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansPostAnnouncement(vm::ptr handle, SceNpClanId clanId, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) +error_code sceNpClansPostAnnouncement(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) { - sceNpClans.todo("sceNpClansPostAnnouncement(handle=*0x%x, clanId=%d, message=*0x%x, data=*0x%x, duration=%d, mId=*0x%x)", handle, clanId, message, data, duration, mId); + sceNpClans.warning("sceNpClansPostAnnouncement(handle=*0x%x, clanId=*0x%x, message=*0x%x, data=*0x%x, duration=*0x%x, mId=*0x%x)", handle, clanId, message, data, duration, mId); if (!g_fxo->get().is_initialized) { @@ -713,32 +1105,57 @@ error_code sceNpClansPostAnnouncement(vm::ptr handle, S return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - if (!data) // TODO verify - { - return SCE_NP_CLANS_ERROR_NOT_SUPPORTED; - } - if (strlen(message->body) > SCE_NP_CLANS_ANNOUNCEMENT_MESSAGE_BODY_MAX_LENGTH || strlen(message->subject) > SCE_NP_CLANS_MESSAGE_SUBJECT_MAX_LENGTH) // TODO: correct max? { return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansMessage host_announcement = {}; + std::memcpy(&host_announcement, message.get_ptr(), sizeof(SceNpClansMessage)); + + SceNpClansMessageData host_data = {}; + if (data) + { + std::memcpy(&host_data, data.get_ptr(), sizeof(SceNpClansMessageData)); + } + + SceNpClansMessageId host_announcementId = 0; + SceNpClansError ret = clans_manager.client->post_announcement(nph, handle, clanId, &host_announcement, &host_data, duration, &host_announcementId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + *mId = host_announcementId; + return CELL_OK; } -error_code sceNpClansRemoveAnnouncement(vm::ptr handle, SceNpClanId clanId, SceNpClansMessageId mId) +error_code sceNpClansRemoveAnnouncement(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClansMessageId mId) { - sceNpClans.todo("sceNpClansPostAnnouncement(handle=*0x%x, clanId=%d, mId=%d)", handle, clanId, mId); + sceNpClans.warning("sceNpClansRemoveAnnouncement(handle=*0x%x, clanId=*0x%x, mId=*0x%x)", handle, clanId, mId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansError ret = clans_manager.client->delete_announcement(nph, handle, clanId, mId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansPostChallenge(vm::ptr handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) +error_code sceNpClansPostChallenge(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) { sceNpClans.todo("sceNpClansPostChallenge(handle=*0x%x, clanId=%d, targetClan=%d, message=*0x%x, data=*0x%x, duration=%d, mId=*0x%x)", handle, clanId, targetClan, message, data, duration, mId); @@ -765,7 +1182,7 @@ error_code sceNpClansPostChallenge(vm::ptr handle, SceN return CELL_OK; } -error_code sceNpClansRetrievePostedChallenges(vm::ptr handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) +error_code sceNpClansRetrievePostedChallenges(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) { sceNpClans.todo("sceNpClansRetrievePostedChallenges(handle=*0x%x, clanId=%d, targetClan=%d, paging=*0x%x, mList=*0x%x, pageResult=*0x%x)", handle, clanId, targetClan, paging, mList, pageResult); @@ -790,7 +1207,7 @@ error_code sceNpClansRetrievePostedChallenges(vm::ptr h return CELL_OK; } -error_code sceNpClansRemovePostedChallenge(vm::ptr handle, SceNpClanId clanId, SceNpClanId targetClan, SceNpClansMessageId mId) +error_code sceNpClansRemovePostedChallenge(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, SceNpClansMessageId mId) { sceNpClans.todo("sceNpClansRemovePostedChallenge(handle=*0x%x, clanId=%d, targetClan=%d, mId=%d)", handle, clanId, targetClan, mId); @@ -802,7 +1219,7 @@ error_code sceNpClansRemovePostedChallenge(vm::ptr hand return CELL_OK; } -error_code sceNpClansRetrieveChallenges(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) +error_code sceNpClansRetrieveChallenges(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) { sceNpClans.todo("sceNpClansRetrieveChallenges(handle=*0x%x, clanId=%d, paging=*0x%x, mList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, mList, pageResult); diff --git a/rpcs3/Emu/Cell/Modules/sceNpClans.h b/rpcs3/Emu/Cell/Modules/sceNpClans.h index f0d1d54630..9b31c87d25 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpClans.h +++ b/rpcs3/Emu/Cell/Modules/sceNpClans.h @@ -5,6 +5,8 @@ // Return codes enum SceNpClansError : u32 { + SCE_NP_CLANS_SUCCESS = CELL_OK, + SCE_NP_CLANS_ERROR_ALREADY_INITIALIZED = 0x80022701, SCE_NP_CLANS_ERROR_NOT_INITIALIZED = 0x80022702, SCE_NP_CLANS_ERROR_NOT_SUPPORTED = 0x80022703, @@ -138,7 +140,7 @@ enum }; // Request handle for clan API -using SceNpClansRequestHandle = vm::ptr; +using SceNpClansRequestHandle = u32; // Paging request structure struct SceNpClansPagingRequest @@ -159,8 +161,8 @@ struct SceNpClansClanBasicInfo { be_t clanId; be_t numMembers; - s8 name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; - s8 tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; + char name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; + char tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; u8 reserved[2]; }; @@ -197,7 +199,7 @@ struct SceNpClansSearchableProfile be_t intAttr2SearchOp; be_t intAttr3SearchOp; be_t binAttr1SearchOp; - s8 tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; + char tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; u8 reserved[3]; }; @@ -205,7 +207,7 @@ struct SceNpClansSearchableProfile struct SceNpClansSearchableName { be_t nameSearchOp; - s8 name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; + char name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; u8 reserved[3]; }; @@ -213,7 +215,7 @@ struct SceNpClansSearchableName struct SceNpClansUpdatableClanInfo { be_t fields; - s8 description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1]; + char description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1]; SceNpClansSearchableAttr attr; u8 binData1; be_t binData1Size; @@ -233,8 +235,8 @@ struct SceNpClansUpdatableMemberInfo be_t fields; u8 binData1; be_t binData1Size; - u8 binAttr1[SCE_NP_CLANS_CLAN_BINARY_ATTRIBUTE1_MAX_SIZE + 1]; - s8 description[SCE_NP_CLANS_MEMBER_DESCRIPTION_MAX_LENGTH + 1]; + u8 binAttr1[SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE]; + char description[SCE_NP_CLANS_MEMBER_DESCRIPTION_MAX_LENGTH + 1]; b8 allowMsg; u8 reserved[3]; }; @@ -271,7 +273,7 @@ struct SceNpClansMessageEntry SceNpClansMessage message; SceNpClansMessageData data; SceNpId npid; - u8 reserved[4]; + SceNpClanId postedBy; }; // Blacklist entry structure @@ -280,10 +282,3 @@ struct SceNpClansBlacklistEntry SceNpId entry; SceNpId registeredBy; }; - -// fxm objects - -struct sce_np_clans_manager -{ - atomic_t is_initialized = false; -}; diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index d23903997f..4d04b13666 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -3931,6 +3931,17 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s continue; } + for (auto it2 = it->second.begin(); it2 != it->second.end();) + { + if (*it2 < lsa || *it2 >= limit) + { + it2 = it->second.erase(it2); + continue; + } + + it2++; + } + it++; } @@ -7320,7 +7331,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } // spu_log.success("PUTLLC0 Pattern Detected! (put_pc=0x%x, %s) (putllc0=%d, putllc16+0=%d, all=%d)", pattern.put_pc, func_hash, ++stats.nowrite, ++stats.single, +stats.all); - // add_pattern(false, inst_attr::putllc0, pattern.put_pc - lsa, value.data); + // add_pattern(inst_attr::putllc0, pattern.put_pc - lsa, value.data); continue; } @@ -7411,7 +7422,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (allow_pattern) { - add_pattern(false, inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data); + add_pattern(inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data); } spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)" @@ -7433,7 +7444,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (inst_attr attr = m_inst_attrs[(read_pc - entry_point) / 4]; attr == inst_attr::none) { - add_pattern(false, inst_attr::rchcnt_loop, read_pc - result.entry_point, 0); + add_pattern(inst_attr::rchcnt_loop, read_pc - result.entry_point, 0); spu_log.error("Channel Loop Pattern Detected! Report to developers! (read_pc=0x%x, branch_pc=0x%x, branch_target=0x%x, 0x%x-%s)", read_pc, pattern.branch_pc, pattern.branch_target, entry_point, func_hash); } @@ -8519,7 +8530,7 @@ std::array& block_reg_info::evaluate_start_state(const s return walkby_state; } -void spu_recompiler_base::add_pattern(bool fill_all, inst_attr attr, u32 start, u64 info) +void spu_recompiler_base::add_pattern(inst_attr attr, u32 start, u64 info) { m_patterns[start] = pattern_info{info}; m_inst_attrs[start / 4] = attr; diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 989ba2e84f..eb44289320 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2617,7 +2617,11 @@ public: { if (b2 != bqbi) { +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) + auto ins = &*b2->block->getFirstNonPHIIt(); +#else auto ins = b2->block->getFirstNonPHI(); +#endif if (b2->bb->preds.size() == 1) { diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index 6bddb5a035..57d842e69d 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -402,7 +402,7 @@ protected: std::unordered_map m_patterns; - void add_pattern(bool fill_all, inst_attr attr, u32 start, u64 info); + void add_pattern(inst_attr attr, u32 start, u64 info); private: // For private use diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 80d1b4d956..4442cfeea4 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -488,7 +488,7 @@ waitpkg_func static void __tpause(u32 cycles, u32 cstate) namespace vm { - std::array, 2048> g_resrv_waiters_count{}; + std::array, 1024> g_resrv_waiters_count{}; } void do_cell_atomic_128_store(u32 addr, const void* to_write); @@ -499,7 +499,7 @@ const spu_decoder s_spu_itype; namespace vm { - extern atomic_t g_range_lock_set[64]; + extern atomic_t g_range_lock_set[64]; // Defined here for performance reasons writer_lock::~writer_lock() noexcept @@ -2000,7 +2000,7 @@ void spu_thread::do_dma_transfer(spu_thread* _this, const spu_mfc_cmd& args, u8* cpu_thread* _cpu = _this ? _this : get_current_cpu_thread(); - atomic_t* range_lock = nullptr; + atomic_t* range_lock = nullptr; if (!_this) [[unlikely]] { @@ -4928,12 +4928,12 @@ bool spu_thread::reservation_check(u32 addr, const decltype(rdata)& data, u32 cu return !res; } -bool spu_thread::reservation_check(u32 addr, u32 hash, atomic_t* range_lock) +bool spu_thread::reservation_check(u32 addr, u32 hash, atomic_t* range_lock) { if ((addr >> 28) < 2 || (addr >> 28) == 0xd) { // Always-allocated memory does not need strict checking (vm::main or vm::stack) - return compute_rdata_hash32(*vm::get_super_ptr(addr)) == hash; + return compute_rdata_hash32(*vm::get_super_ptr(addr)) != hash; } // Ensure data is allocated (HACK: would raise LR event if not) @@ -5067,6 +5067,8 @@ void spu_thread::deregister_cache_line_waiter(usz index) return; } + ensure(index < std::size(g_spu_waiters_by_value)); + g_spu_waiters_by_value[index].atomic_op([](u64& x) { x--; diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index 9adb15a47c..9596f7b006 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -708,7 +708,7 @@ public: const decltype(rdata)* resrv_mem{}; // Range Lock pointer - atomic_t* range_lock{}; + atomic_t* range_lock{}; u32 srr0 = 0; u32 ch_tag_upd = 0; @@ -903,7 +903,7 @@ public: // It is safe to use on any address, even if not directly accessed by SPU (so it's slower) // Optionally pass a known allocated address for internal optimization (the current Effective-Address of the MFC command) bool reservation_check(u32 addr, const decltype(rdata)& data, u32 current_eal = 0) const; - static bool reservation_check(u32 addr, u32 hash, atomic_t* range_lock); + static bool reservation_check(u32 addr, u32 hash, atomic_t* range_lock); usz register_cache_line_waiter(u32 addr); void deregister_cache_line_waiter(usz index); @@ -915,7 +915,7 @@ public: static atomic_t g_raw_spu_id[5]; static atomic_t g_spu_work_count; - static atomic_t g_spu_waiters_by_value[6]; + static atomic_t g_spu_waiters_by_value[6]; static u32 find_raw_spu(u32 id) { diff --git a/rpcs3/Emu/Cell/lv2/lv2.cpp b/rpcs3/Emu/Cell/lv2/lv2.cpp index a81eb949b5..b476571ab7 100644 --- a/rpcs3/Emu/Cell/lv2/lv2.cpp +++ b/rpcs3/Emu/Cell/lv2/lv2.cpp @@ -2228,11 +2228,8 @@ void lv2_obj::notify_all() noexcept break; } - if (cpu != &g_to_notify) - { - // Note: by the time of notification the thread could have been deallocated which is why the direct function is used - atomic_wait_engine::notify_all(cpu); - } + // Note: by the time of notification the thread could have been deallocated which is why the direct function is used + atomic_wait_engine::notify_all(cpu); } g_to_notify[0] = nullptr; @@ -2260,7 +2257,7 @@ void lv2_obj::notify_all() noexcept // There may be 6 waiters, but checking them all may be performance expensive // Instead, check 2 at max, but use the CPU ID index to tell which index to start checking so the work would be distributed across all threads - atomic_t* range_lock = nullptr; + atomic_t* range_lock = nullptr; if (cpu->get_class() == thread_class::spu) { diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 5bb74808be..92fc21b747 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -848,7 +848,7 @@ lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s3 fs::file file(local_path, open_mode); - if (!file && open_mode == fs::read && fs::g_tls_error == fs::error::noent) + if (!file && open_mode == fs::read && fs::g_tls_error == fs::error::noent && mp.mp != &g_mp_sys_dev_hdd1) { // Try to gather split file (TODO) std::vector fragments; @@ -1389,7 +1389,7 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr path, vm::ptr fd) } // Add additional entries for split file candidates (while ends with .66600) - while (data.back().name.ends_with(".66600")) + while (mp.mp != &g_mp_sys_dev_hdd1 && data.back().name.ends_with(".66600")) { data.emplace_back(data.back()).name.resize(data.back().name.size() - 6); } @@ -1505,6 +1505,7 @@ error_code sys_fs_closedir(ppu_thread& ppu, u32 fd) error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr sb) { ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_stat(path=%s, sb=*0x%x)", path, sb); @@ -1552,8 +1553,18 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr // Try to analyse split file (TODO) u64 total_size = 0; - for (u32 i = 66601; i <= 66699; i++) + // Use attributes from the first fragment (consistently with sys_fs_open+fstat + fs::stat_t info_split{}; + if (mp.mp != &g_mp_sys_dev_hdd1 && fs::get_stat(local_path + ".66600", info_split) && !info_split.is_directory) { + // Success + total_size += info_split.size; + } + + for (u32 i = 66601; total_size && i <= 66699; i++) + { + info = {}; + if (fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) { total_size += info.size; @@ -1564,11 +1575,11 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr } } - // Use attributes from the first fragment (consistently with sys_fs_open+fstat) - if (fs::get_stat(local_path + ".66600", info) && !info.is_directory) + if (total_size) { // Success - info.size += total_size; + info_split.size = total_size; + info = info_split; break; } @@ -2895,7 +2906,7 @@ error_code sys_fs_chmod(ppu_thread&, vm::cptr path, s32 mode) for (u32 i = 66601; i <= 66699; i++) { - if (!fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) + if (mp != &g_mp_sys_dev_hdd1 && !fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) { break; } diff --git a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp index 63c0c16af9..e6c96ffd64 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp @@ -347,7 +347,7 @@ error_code sys_mutex_unlock(ppu_thread& ppu, u32 mutex_id) const auto mutex = idm::check(mutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_mutex& mutex) -> CellError { // At unlock, we have some time to do other jobs when the thread is unlikely to be in other critical sections - notify.enqueue_on_top(vm::reservation_notifier_notify(ppu.res_notify, ppu.res_notify_time)); + notify.enqueue_on_top(vm::reservation_notifier_notify(ppu.res_notify, ppu.res_notify_time, true)); auto result = mutex.try_unlock(ppu); diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index fd5257b03a..78183293e7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -321,7 +321,7 @@ static void LIBUSB_CALL log_cb(libusb_context* /*ctx*/, enum libusb_log_level le if (!str) return; - const std::string msg = fmt::trim(str, " \t\n"); + const std::string_view msg = fmt::trim_sv(str, " \t\n"); switch (level) { @@ -555,7 +555,8 @@ usb_handler_thread::usb_handler_thread() usb_devices.push_back(std::make_shared(usb_info, get_new_location())); } - const std::vector devices_list = fmt::split(g_cfg.io.midi_devices.to_string(), { "@@@" }); + const std::string midi_devices = g_cfg.io.midi_devices.to_string(); + const std::vector devices_list = fmt::split_sv(midi_devices, { "@@@" }); for (usz index = 0; index < std::min(max_midi_devices, devices_list.size()); index++) { const midi_device device = midi_device::from_string(::at32(devices_list, index)); diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index 4fc8fa6376..d25ba1379c 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -874,12 +874,12 @@ void PadHandlerBase::set_raw_orientation(ps_move_data& move_data, f32 accel_x, f // The default position is flat on the ground, pointing forward. // The accelerometers constantly measure G forces. // The gyros measure changes in orientation and will reset when the device isn't moved anymore. - move_data.accelerometer_x = -accel_x; // move_data: Increases if the device is rolled to the left - move_data.accelerometer_y = accel_z; // move_data: Increases if the device is pitched upwards - move_data.accelerometer_z = accel_y; // move_data: Increases if the device is moved upwards - move_data.gyro_x = degree_to_rad(-gyro_x); // move_data: Increases if the device is pitched upwards - move_data.gyro_y = degree_to_rad(gyro_z); // move_data: Increases if the device is rolled to the right - move_data.gyro_z = degree_to_rad(-gyro_y); // move_data: Increases if the device is yawed to the left + move_data.accelerometer.x() = -accel_x; // move_data: Increases if the device is rolled to the left + move_data.accelerometer.y() = accel_z; // move_data: Increases if the device is pitched upwards + move_data.accelerometer.z() = accel_y; // move_data: Increases if the device is moved upwards + move_data.gyro.x() = degree_to_rad(-gyro_x); // move_data: Increases if the device is pitched upwards + move_data.gyro.y() = degree_to_rad(gyro_z); // move_data: Increases if the device is rolled to the right + move_data.gyro.z() = degree_to_rad(-gyro_y); // move_data: Increases if the device is yawed to the left } void PadHandlerBase::set_raw_orientation(Pad& pad) @@ -950,7 +950,7 @@ void PadDevice::update_orientation(ps_move_data& move_data) // Get elapsed time since last update const u64 now_us = get_system_time(); - const float elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f); + const f32 elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f); last_ahrs_update_time_us = now_us; // The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly. @@ -959,17 +959,17 @@ void PadDevice::update_orientation(ps_move_data& move_data) const FusionVector accelerometer{ .axis { - .x = -move_data.accelerometer_x, - .y = +move_data.accelerometer_y, - .z = +move_data.accelerometer_z + .x = -move_data.accelerometer.x(), + .y = +move_data.accelerometer.y(), + .z = +move_data.accelerometer.z() } }; const FusionVector gyroscope{ .axis { - .x = +PadHandlerBase::rad_to_degree(move_data.gyro_x), - .y = +PadHandlerBase::rad_to_degree(move_data.gyro_z), - .z = -PadHandlerBase::rad_to_degree(move_data.gyro_y) + .x = +PadHandlerBase::rad_to_degree(move_data.gyro.x()), + .y = +PadHandlerBase::rad_to_degree(move_data.gyro.z()), + .z = -PadHandlerBase::rad_to_degree(move_data.gyro.y()) } }; @@ -979,9 +979,9 @@ void PadDevice::update_orientation(ps_move_data& move_data) { magnetometer = FusionVector{ .axis { - .x = move_data.magnetometer_x, - .y = move_data.magnetometer_y, - .z = move_data.magnetometer_z + .x = move_data.magnetometer.x(), + .y = move_data.magnetometer.y(), + .z = move_data.magnetometer.z() } }; } @@ -995,4 +995,5 @@ void PadDevice::update_orientation(ps_move_data& move_data) move_data.quaternion[1] = quaternion.array[2]; move_data.quaternion[2] = quaternion.array[3]; move_data.quaternion[3] = quaternion.array[0]; + move_data.update_orientation(elapsed_sec); } diff --git a/rpcs3/Emu/Io/PadHandler.h b/rpcs3/Emu/Io/PadHandler.h index e9fc166d93..a27e0bcc50 100644 --- a/rpcs3/Emu/Io/PadHandler.h +++ b/rpcs3/Emu/Io/PadHandler.h @@ -205,7 +205,7 @@ protected: std::set key_codes; const std::string& def = cfg_string.def; - const std::vector names = cfg_pad::get_buttons(cfg_string); + const std::vector names = cfg_pad::get_buttons(cfg_string.to_string()); T def_code = umax; for (const std::string& nam : names) diff --git a/rpcs3/Emu/Io/RB3MidiDrums.cpp b/rpcs3/Emu/Io/RB3MidiDrums.cpp index 67ff121d77..61b172bbdd 100644 --- a/rpcs3/Emu/Io/RB3MidiDrums.cpp +++ b/rpcs3/Emu/Io/RB3MidiDrums.cpp @@ -177,7 +177,7 @@ Note str_to_note(const std::string_view name) std::optional> parse_midi_override(const std::string_view config) { - auto split = fmt::split(config, {"="}); + const auto split = fmt::split_sv(config, {"="}); if (split.size() != 2) { return {}; @@ -236,8 +236,9 @@ std::unordered_map create_id_to_note_mapping() }; // Apply configured overrides. - const std::vector segments = fmt::split(g_cfg_rb3drums.midi_overrides.to_string(), {","}); - for (const std::string& segment : segments) + const std::string midi_overrides = g_cfg_rb3drums.midi_overrides.to_string(); + const std::vector segments = fmt::split_sv(midi_overrides, {","}); + for (const std::string_view& segment : segments) { if (const auto midi_override = parse_midi_override(segment)) { @@ -259,7 +260,7 @@ std::vector parse_combo(const std::string_view name, const std::string_view return {}; } std::vector notes; - const auto& note_names = fmt::split(csv, {","}); + const auto note_names = fmt::split_sv(csv, {","}); for (const auto& note_name : note_names) { const auto note = str_to_note(note_name); diff --git a/rpcs3/Emu/Io/camera_config.cpp b/rpcs3/Emu/Io/camera_config.cpp index d7071b05c3..f498de72b1 100644 --- a/rpcs3/Emu/Io/camera_config.cpp +++ b/rpcs3/Emu/Io/camera_config.cpp @@ -36,7 +36,7 @@ void cfg_camera::save() const } } -cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& camera, bool& success) +cfg_camera::camera_setting cfg_camera::get_camera_setting(std::string_view camera, bool& success) { camera_setting setting; const std::string value = cameras.get_value(camera); @@ -64,7 +64,7 @@ std::string cfg_camera::camera_setting::to_string() const return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format); } -void cfg_camera::camera_setting::from_string(const std::string& text) +void cfg_camera::camera_setting::from_string(std::string_view text) { if (text.empty()) { diff --git a/rpcs3/Emu/Io/camera_config.h b/rpcs3/Emu/Io/camera_config.h index a918dea458..1c576e32c1 100644 --- a/rpcs3/Emu/Io/camera_config.h +++ b/rpcs3/Emu/Io/camera_config.h @@ -19,9 +19,9 @@ struct cfg_camera final : cfg::node static constexpr u32 member_count = 5; std::string to_string() const; - void from_string(const std::string& text); + void from_string(std::string_view text); }; - camera_setting get_camera_setting(const std::string& camera, bool& success); + camera_setting get_camera_setting(std::string_view camera, bool& success); void set_camera_setting(const std::string& camera, const camera_setting& setting); const std::string path; diff --git a/rpcs3/Emu/Io/midi_config_types.cpp b/rpcs3/Emu/Io/midi_config_types.cpp index abe976ee93..0105da3d51 100644 --- a/rpcs3/Emu/Io/midi_config_types.cpp +++ b/rpcs3/Emu/Io/midi_config_types.cpp @@ -27,11 +27,11 @@ void fmt_class_string::format(std::string& out, u64 arg) fmt::append(out, "%sßßß%s", obj.type, obj.name); } -midi_device midi_device::from_string(const std::string& str) +midi_device midi_device::from_string(std::string_view str) { midi_device res{}; - if (const std::vector parts = fmt::split(str, {"ßßß"}); !parts.empty()) + if (const std::vector parts = fmt::split_sv(str, {"ßßß"}); !parts.empty()) { u64 result; diff --git a/rpcs3/Emu/Io/midi_config_types.h b/rpcs3/Emu/Io/midi_config_types.h index 9d2f40adf1..d21cbfbfce 100644 --- a/rpcs3/Emu/Io/midi_config_types.h +++ b/rpcs3/Emu/Io/midi_config_types.h @@ -1,6 +1,7 @@ #pragma once #include +#include static constexpr usz max_midi_devices = 3; @@ -17,5 +18,5 @@ struct midi_device midi_device_type type{}; std::string name; - static midi_device from_string(const std::string& str); + static midi_device from_string(std::string_view str); }; diff --git a/rpcs3/Emu/Io/pad_config.cpp b/rpcs3/Emu/Io/pad_config.cpp index 614d972716..937626076e 100644 --- a/rpcs3/Emu/Io/pad_config.cpp +++ b/rpcs3/Emu/Io/pad_config.cpp @@ -5,7 +5,7 @@ extern std::string g_input_config_override; -std::vector cfg_pad::get_buttons(const std::string& str) +std::vector cfg_pad::get_buttons(std::string_view str) { std::vector vec = fmt::split(str, {","}); diff --git a/rpcs3/Emu/Io/pad_config.h b/rpcs3/Emu/Io/pad_config.h index fa695c4941..7dd4bd6323 100644 --- a/rpcs3/Emu/Io/pad_config.h +++ b/rpcs3/Emu/Io/pad_config.h @@ -25,7 +25,7 @@ struct cfg_pad final : cfg::node cfg_pad() {}; cfg_pad(node* owner, const std::string& name) : cfg::node(owner, name) {} - static std::vector get_buttons(const std::string& str); + static std::vector get_buttons(std::string_view str); static std::string get_buttons(std::vector vec); u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const; diff --git a/rpcs3/Emu/Io/pad_types.cpp b/rpcs3/Emu/Io/pad_types.cpp index 9005bc7fe5..ad3369a7a7 100644 --- a/rpcs3/Emu/Io/pad_types.cpp +++ b/rpcs3/Emu/Io/pad_types.cpp @@ -159,20 +159,6 @@ u32 get_axis_keycode(u32 offset, u16 value) } } -void ps_move_data::reset_sensors() -{ - quaternion = default_quaternion; - accelerometer_x = 0.0f; - accelerometer_y = 0.0f; - accelerometer_z = 0.0f; - gyro_x = 0.0f; - gyro_y = 0.0f; - gyro_z = 0.0f; - magnetometer_x = 0.0f; - magnetometer_y = 0.0f; - magnetometer_z = 0.0f; -} - bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id) { if (m_pressure_intensity_button_index < 0) diff --git a/rpcs3/Emu/Io/pad_types.h b/rpcs3/Emu/Io/pad_types.h index 8c669a9413..5fd9c8973a 100644 --- a/rpcs3/Emu/Io/pad_types.h +++ b/rpcs3/Emu/Io/pad_types.h @@ -2,7 +2,8 @@ #include "util/types.hpp" #include "util/endian.hpp" -#include "Emu/Io/pad_config_types.h" +#include "pad_config_types.h" +#include "ps_move_data.h" #include #include @@ -469,38 +470,6 @@ struct VibrateMotor {} }; -struct ps_move_data -{ - u32 external_device_id = 0; - std::array external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE - std::array external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE - std::array external_device_data{}; - bool external_device_connected = false; - bool external_device_read_requested = false; - bool external_device_write_requested = false; - - bool calibration_requested = false; - bool calibration_succeeded = false; - - bool magnetometer_enabled = false; - bool orientation_enabled = false; - - static constexpr std::array default_quaternion { 1.0f, 0.0f, 0.0f, 0.0f }; - std::array quaternion = default_quaternion; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up) - f32 accelerometer_x = 0.0f; // linear velocity in m/s² - f32 accelerometer_y = 0.0f; // linear velocity in m/s² - f32 accelerometer_z = 0.0f; // linear velocity in m/s² - f32 gyro_x = 0.0f; // angular velocity in rad/s - f32 gyro_y = 0.0f; // angular velocity in rad/s - f32 gyro_z = 0.0f; // angular velocity in rad/s - f32 magnetometer_x = 0.0f; - f32 magnetometer_y = 0.0f; - f32 magnetometer_z = 0.0f; - s16 temperature = 0; - - void reset_sensors(); -}; - struct Pad { const pad_handler m_pad_handler; diff --git a/rpcs3/Emu/Io/ps_move_data.cpp b/rpcs3/Emu/Io/ps_move_data.cpp new file mode 100644 index 0000000000..0a167eed39 --- /dev/null +++ b/rpcs3/Emu/Io/ps_move_data.cpp @@ -0,0 +1,137 @@ +#include "stdafx.h" +#include "ps_move_data.h" + +const ps_move_data::vect<4> ps_move_data::default_quaternion = ps_move_data::vect<4>({ 0.0f, 0.0f, 0.0f, 1.0f }); + +ps_move_data::ps_move_data() + : quaternion(default_quaternion) +{ +} + +void ps_move_data::reset_sensors() +{ + quaternion = default_quaternion; + accelerometer = {}; + gyro = {}; + prev_gyro = {}; + angular_acceleration = {}; + magnetometer = {}; + //prev_pos_world = {}; // probably no reset needed ? + vel_world = {}; + prev_vel_world = {}; + accel_world = {}; + angvel_world = {}; + angaccel_world = {}; +} + +void ps_move_data::update_orientation(f32 delta_time) +{ + if (!delta_time) + return; + + // Rotate vector v by quaternion q + const auto rotate_vector = [](const vect<4>& q, const vect<3>& v) + { + const vect<4> qv({0.0f, v.x(), v.y(), v.z()}); + const vect<4> q_inv({q.w(), -q.x(), -q.y(), -q.z()}); + + // t = q * v + vect<4> t; + t.w() = -q.x() * qv.x() - q.y() * qv.y() - q.z() * qv.z(); + t.x() = q.w() * qv.x() + q.y() * qv.z() - q.z() * qv.y(); + t.y() = q.w() * qv.y() - q.x() * qv.z() + q.z() * qv.x(); + t.z() = q.w() * qv.z() + q.x() * qv.y() - q.y() * qv.x(); + + // r = t * q_inv + vect<4> r; + r.w() = -t.x() * q_inv.x() - t.y() * q_inv.y() - t.z() * q_inv.z(); + r.x() = t.w() * q_inv.x() + t.y() * q_inv.z() - t.z() * q_inv.y(); + r.y() = t.w() * q_inv.y() - t.x() * q_inv.z() + t.z() * q_inv.x(); + r.z() = t.w() * q_inv.z() + t.x() * q_inv.y() - t.y() * q_inv.x(); + + return vect<3>({r.x(), r.y(), r.z()}); + }; + + if constexpr (use_imu_for_velocity) + { + // Gravity in world frame + constexpr f32 gravity = 9.81f; + constexpr vect<3> g({0.0f, 0.0f, -gravity}); + + // Rotate gravity into sensor frame + const vect<3> g_sensor = rotate_vector(quaternion, g); + + // Remove gravity + vect<3> linear_local; + for (u32 i = 0; i < 3; i++) + { + linear_local[i] = (accelerometer[i] * gravity) - g_sensor[i]; + } + + // Linear acceleration (rotate to world coordinates) + accel_world = rotate_vector(quaternion, linear_local); + + // convert to mm/s² + for (u32 i = 0; i < 3; i++) + { + accel_world[i] *= 1000.0f; + } + + // Linear velocity (integrate acceleration) + for (u32 i = 0; i < 3; i++) + { + vel_world[i] = prev_vel_world[i] + accel_world[i] * delta_time; + } + + prev_vel_world = vel_world; + } + + // Compute raw angular acceleration + for (u32 i = 0; i < 3; i++) + { + const f32 alpha = (gyro[i] - prev_gyro[i]) / delta_time; + + // Filtering + constexpr f32 weight = 0.8f; + constexpr f32 weight_inv = 1.0f - weight; + angular_acceleration[i] = weight * angular_acceleration[i] + weight_inv * alpha; + } + + // Angular velocity (rotate to world coordinates) + angvel_world = rotate_vector(quaternion, gyro); + + // Angular acceleration (rotate to world coordinates) + angaccel_world = rotate_vector(quaternion, angular_acceleration); + + prev_gyro = gyro; +} + +void ps_move_data::update_velocity(u64 timestamp, be_t pos_world[4]) +{ + if constexpr (use_imu_for_velocity) + return; + + if (last_velocity_update_time_us == timestamp) + return; + + // Get elapsed time since last update + const f32 delta_time = (last_velocity_update_time_us == 0) ? 0.0f : ((timestamp - last_velocity_update_time_us) / 1'000'000.0f); + last_velocity_update_time_us = timestamp; + + if (!delta_time) + return; + + for (u32 i = 0; i < 3; i++) + { + // Linear velocity + constexpr f32 weight = 0.8f; + constexpr f32 weight_inv = 1.0f - weight; + vel_world[i] = weight * ((pos_world[i] - prev_pos_world[i]) / delta_time) + weight_inv * prev_vel_world[i]; + prev_pos_world[i] = pos_world[i]; + + // Linear acceleration + accel_world[i] = (vel_world[i] - prev_vel_world[i]) / delta_time; + } + + prev_vel_world = vel_world; +} diff --git a/rpcs3/Emu/Io/ps_move_data.h b/rpcs3/Emu/Io/ps_move_data.h new file mode 100644 index 0000000000..1ae30f5c66 --- /dev/null +++ b/rpcs3/Emu/Io/ps_move_data.h @@ -0,0 +1,75 @@ +#pragma once + +struct ps_move_data +{ + template + struct vect + { + public: + constexpr vect() = default; + constexpr vect(const std::array& vec) : data(vec) {}; + + template + T& operator[](I i) { return data[i]; } + + template + const T& operator[](I i) const { return data[i]; } + + T x() const requires (Size >= 1) { return data[0]; } + T y() const requires (Size >= 2) { return data[1]; } + T z() const requires (Size >= 3) { return data[2]; } + T w() const requires (Size >= 4) { return data[3]; } + + T& x() requires (Size >= 1) { return data[0]; } + T& y() requires (Size >= 2) { return data[1]; } + T& z() requires (Size >= 3) { return data[2]; } + T& w() requires (Size >= 4) { return data[3]; } + + private: + std::array data {}; + }; + + ps_move_data(); + + u32 external_device_id = 0; + std::array external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE + std::array external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE + std::array external_device_data{}; + bool external_device_connected = false; + bool external_device_read_requested = false; + bool external_device_write_requested = false; + + bool calibration_requested = false; + bool calibration_succeeded = false; + + bool magnetometer_enabled = false; + bool orientation_enabled = false; + + // Disable IMU tracking of velocity and acceleration (massive drift) + static constexpr bool use_imu_for_velocity = false; + u64 last_velocity_update_time_us = 0; + + static const vect<4> default_quaternion; + vect<4> quaternion {}; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up) + + // Raw values (local) + vect<3> accelerometer {}; // linear acceleration in G units (9.81 = 1 unit) + vect<3> gyro {}; // angular velocity in rad/s + vect<3> prev_gyro {}; // previous angular velocity in rad/s + vect<3> angular_acceleration {}; // angular acceleration in rad/s² + vect<3> magnetometer {}; + + // In world coordinates + vect<3> prev_pos_world {}; + vect<3> vel_world {}; // velocity of sphere in world coordinates (mm/s) + vect<3> prev_vel_world {}; // previous velocity of sphere in world coordinates (mm/s) + vect<3> accel_world {}; // acceleration of sphere in world coordinates (mm/s²) + vect<3> angvel_world {}; // angular velocity of controller in world coordinates (radians/s) + vect<3> angaccel_world {}; // angular acceleration of controller in world coordinates (radians/s²) + + s16 temperature = 0; + + void reset_sensors(); + void update_orientation(f32 delta_time); + void update_velocity(u64 timestamp, be_t pos_world[4]); +}; diff --git a/rpcs3/Emu/Memory/vm.cpp b/rpcs3/Emu/Memory/vm.cpp index 6181c2c6bb..112b1f8354 100644 --- a/rpcs3/Emu/Memory/vm.cpp +++ b/rpcs3/Emu/Memory/vm.cpp @@ -74,7 +74,7 @@ namespace vm std::array, g_cfg.core.ppu_threads.max> g_locks{}; // Range lock slot allocation bits - atomic_t g_range_lock_bits[2]{}; + atomic_t g_range_lock_bits[2]{}; auto& get_range_lock_bits(bool is_exclusive_range) { @@ -82,7 +82,7 @@ namespace vm } // Memory range lock slots (sparse atomics) - atomic_t g_range_lock_set[64]{}; + atomic_t g_range_lock_set[64]{}; // Memory pages std::array g_pages; @@ -142,7 +142,7 @@ namespace vm } } - atomic_t* alloc_range_lock() + atomic_t* alloc_range_lock() { const auto [bits, ok] = get_range_lock_bits(false).fetch_op([](u64& bits) { @@ -167,7 +167,7 @@ namespace vm template static u64 for_all_range_locks(u64 input, F func); - void range_lock_internal(atomic_t* range_lock, u32 begin, u32 size) + void range_lock_internal(atomic_t* range_lock, u32 begin, u32 size) { perf_meter<"RHW_LOCK"_u64> perf0(0); @@ -275,7 +275,7 @@ namespace vm } } - void free_range_lock(atomic_t* range_lock) noexcept + void free_range_lock(atomic_t* range_lock) noexcept { if (range_lock < g_range_lock_set || range_lock >= std::end(g_range_lock_set)) { @@ -316,7 +316,7 @@ namespace vm return result; } - static atomic_t* _lock_main_range_lock(u64 flags, u32 addr, u32 size) + static atomic_t* _lock_main_range_lock(u64 flags, u32 addr, u32 size) { // Shouldn't really happen if (size == 0) @@ -460,7 +460,7 @@ namespace vm { } - writer_lock::writer_lock(u32 const addr, atomic_t* range_lock, u32 const size, u64 const flags) noexcept + writer_lock::writer_lock(u32 const addr, atomic_t* range_lock, u32 const size, u64 const flags) noexcept : range_lock(range_lock) { cpu_thread* cpu{}; diff --git a/rpcs3/Emu/Memory/vm_locking.h b/rpcs3/Emu/Memory/vm_locking.h index c4d805554f..d2b45ddd13 100644 --- a/rpcs3/Emu/Memory/vm_locking.h +++ b/rpcs3/Emu/Memory/vm_locking.h @@ -28,7 +28,7 @@ namespace vm range_bits = 3, }; - extern atomic_t g_range_lock_bits[2]; + extern atomic_t g_range_lock_bits[2]; extern atomic_t g_shmem[]; @@ -36,13 +36,13 @@ namespace vm void passive_lock(cpu_thread& cpu); // Register range lock for further use - atomic_t* alloc_range_lock(); + atomic_t* alloc_range_lock(); - void range_lock_internal(atomic_t* range_lock, u32 begin, u32 size); + void range_lock_internal(atomic_t* range_lock, u32 begin, u32 size); // Lock memory range ignoring memory protection (Size!=0 also implies aligned begin) template - FORCE_INLINE void range_lock(atomic_t* range_lock, u32 begin, u32 _size) + FORCE_INLINE void range_lock(atomic_t* range_lock, u32 begin, u32 _size) { if constexpr (Size == 0) { @@ -80,7 +80,7 @@ namespace vm } // Release it - void free_range_lock(atomic_t*) noexcept; + void free_range_lock(atomic_t*) noexcept; // Unregister reader void passive_unlock(cpu_thread& cpu); @@ -91,12 +91,12 @@ namespace vm struct writer_lock final { - atomic_t* range_lock; + atomic_t* range_lock; writer_lock(const writer_lock&) = delete; writer_lock& operator=(const writer_lock&) = delete; writer_lock() noexcept; - writer_lock(u32 addr, atomic_t* range_lock = nullptr, u32 size = 128, u64 flags = range_locked) noexcept; + writer_lock(u32 addr, atomic_t* range_lock = nullptr, u32 size = 128, u64 flags = range_locked) noexcept; ~writer_lock() noexcept; }; } // namespace vm diff --git a/rpcs3/Emu/Memory/vm_reservation.h b/rpcs3/Emu/Memory/vm_reservation.h index d21a593959..1a1e818075 100644 --- a/rpcs3/Emu/Memory/vm_reservation.h +++ b/rpcs3/Emu/Memory/vm_reservation.h @@ -34,32 +34,33 @@ namespace vm void reservation_update(u32 addr); std::pair try_reservation_update(u32 addr); - struct reservation_waiter_t + struct alignas(8) reservation_waiter_t { u32 wait_flag = 0; u32 waiters_count = 0; }; - static inline atomic_t* reservation_notifier(u32 raddr, u64 rtime) + static inline atomic_t* reservation_notifier(u32 raddr, u64 rtime) { - constexpr u32 wait_vars_for_each = 64; + constexpr u32 wait_vars_for_each = 32; constexpr u32 unique_address_bit_mask = 0b1111; constexpr u32 unique_rtime_bit_mask = 0b1; - extern std::array, wait_vars_for_each * (unique_address_bit_mask + 1) * (unique_rtime_bit_mask + 1)> g_resrv_waiters_count; + extern std::array, wait_vars_for_each * (unique_address_bit_mask + 1) * (unique_rtime_bit_mask + 1)> g_resrv_waiters_count; // Storage efficient method to distinguish different nearby addresses (which are likely) - const usz index = std::popcount(raddr & -2048) * (1 << 5) + ((rtime / 128) & unique_rtime_bit_mask) * (1 << 4) + ((raddr / 128) & unique_address_bit_mask); + const usz index = std::min(std::popcount(raddr & -2048), 31) * (1 << 5) + ((rtime / 128) & unique_rtime_bit_mask) * (1 << 4) + ((raddr / 128) & unique_address_bit_mask); return &g_resrv_waiters_count[index]; } // Returns waiter count static inline u32 reservation_notifier_count(u32 raddr, u64 rtime) { - return reservation_notifier(raddr, rtime)->load().waiters_count; + reservation_waiter_t v = reservation_notifier(raddr, rtime)->load(); + return v.wait_flag % 2 == 1 ? v.waiters_count : 0; } - static inline void reservation_notifier_end_wait(atomic_t& waiter) + static inline void reservation_notifier_end_wait(atomic_t& waiter) { waiter.atomic_op([](reservation_waiter_t& value) { @@ -73,9 +74,9 @@ namespace vm }); } - static inline std::pair*, u32> reservation_notifier_begin_wait(u32 raddr, u64 rtime) + static inline std::pair*, u32> reservation_notifier_begin_wait(u32 raddr, u64 rtime) { - atomic_t& waiter = *reservation_notifier(raddr, rtime); + atomic_t& waiter = *reservation_notifier(raddr, rtime); u32 wait_flag = 0; diff --git a/rpcs3/Emu/NP/clans_client.cpp b/rpcs3/Emu/NP/clans_client.cpp new file mode 100644 index 0000000000..7883901476 --- /dev/null +++ b/rpcs3/Emu/NP/clans_client.cpp @@ -0,0 +1,1151 @@ +#include "stdafx.h" + +#include +#include + +// wolfssl uses old-style casts which break clang builds +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wextern-c-compat" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +#include "Emu/Cell/Modules/sceNp.h" +#include "Emu/Cell/Modules/sceNpClans.h" +#include "Emu/NP/clans_client.h" +#include "Emu/NP/clans_config.h" +#include "Emu/NP/np_helpers.h" +#include "Emu/system_config.h" + +LOG_CHANNEL(clan_log, "clans"); + + +const char* REQ_TYPE_FUNC = "func"; +const char* REQ_TYPE_SEC = "sec"; + +constexpr const char JID_FORMAT[] = "%s@un.br.np.playstation.net"; + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanRequestType::FUNC: return "func"; + case clan::ClanRequestType::SEC: return "sec"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanManagerOperationType::VIEW: return "view"; + case clan::ClanManagerOperationType::UPDATE: return "update"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanSearchFilterOperator::Equal: return "eq"; + case clan::ClanSearchFilterOperator::NotEqual: return "ne"; + case clan::ClanSearchFilterOperator::GreaterThan: return "gt"; + case clan::ClanSearchFilterOperator::GreaterThanOrEqual: return "ge"; + case clan::ClanSearchFilterOperator::LessThan: return "lt"; + case clan::ClanSearchFilterOperator::LessThanOrEqual: return "le"; + case clan::ClanSearchFilterOperator::Like: return "lk"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanRequestAction::GetClanList: return "get_clan_list"; + case clan::ClanRequestAction::GetClanInfo: return "get_clan_info"; + case clan::ClanRequestAction::GetMemberInfo: return "get_member_info"; + case clan::ClanRequestAction::GetMemberList: return "get_member_list"; + case clan::ClanRequestAction::GetBlacklist: return "get_blacklist"; + case clan::ClanRequestAction::RecordBlacklistEntry: return "record_blacklist_entry"; + case clan::ClanRequestAction::DeleteBlacklistEntry: return "delete_blacklist_entry"; + case clan::ClanRequestAction::ClanSearch: return "clan_search"; + case clan::ClanRequestAction::CreateClan: return "create_clan"; + case clan::ClanRequestAction::DisbandClan: return "disband_clan"; + case clan::ClanRequestAction::RequestMembership: return "request_membership"; + case clan::ClanRequestAction::CancelRequestMembership: return "cancel_request_membership"; + case clan::ClanRequestAction::AcceptMembershipRequest: return "accept_membership_request"; + case clan::ClanRequestAction::DeclineMembershipRequest: return "decline_membership_request"; + case clan::ClanRequestAction::SendInvitation: return "send_invitation"; + case clan::ClanRequestAction::CancelInvitation: return "cancel_invitation"; + case clan::ClanRequestAction::AcceptInvitation: return "accept_invitation"; + case clan::ClanRequestAction::DeclineInvitation: return "decline_invitation"; + case clan::ClanRequestAction::UpdateMemberInfo: return "update_member_info"; + case clan::ClanRequestAction::UpdateClanInfo: return "update_clan_info"; + case clan::ClanRequestAction::JoinClan: return "join_clan"; + case clan::ClanRequestAction::LeaveClan: return "leave_clan"; + case clan::ClanRequestAction::KickMember: return "kick_member"; + case clan::ClanRequestAction::ChangeMemberRole: return "change_member_role"; + case clan::ClanRequestAction::RetrieveAnnouncements: return "retrieve_announcements"; + case clan::ClanRequestAction::PostAnnouncement: return "post_announcement"; + case clan::ClanRequestAction::DeleteAnnouncement: return "delete_announcement"; + } + + return unknown; + }); +} + +namespace clan +{ + struct curl_memory + { + char* response; + size_t size; + }; + + size_t clans_client::curl_write_callback(void* data, size_t size, size_t nmemb, void* clientp) + { + size_t realsize = size * nmemb; + std::vector* mem = static_cast*>(clientp); + + size_t old_size = mem->size(); + mem->resize(old_size + realsize); + memcpy(mem->data() + old_size, data, realsize); + + return realsize; + } + + struct clan_request_ctx + { + clan_request_ctx() + { + curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + } + } + + ~clan_request_ctx() + { + if (curl) + { + curl_easy_cleanup(curl); + curl = nullptr; + } + } + + CURL* curl = nullptr; + + // TODO: this was arbitrarily chosen -- see if there's a real amount + static const u32 SCE_NP_CLANS_MAX_CTX_NUM = 16; + + static const u32 id_base = 0xA001; + static const u32 id_step = 1; + static const u32 id_count = SCE_NP_CLANS_MAX_CTX_NUM; + SAVESTATE_INIT_POS(55); + }; + + clans_client::clans_client() + { + g_cfg_clans.load(); + } + + clans_client::~clans_client() + { + idm::clear(); + } + + SceNpClansError clans_client::create_request(s32* req_id) + { + if (!g_cfg.net.clans_enabled) + { + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + } + + const s32 id = idm::make(); + + if (id == id_manager::id_traits::invalid) + { + return SceNpClansError::SCE_NP_CLANS_ERROR_EXCEEDS_MAX; + } + + auto ctx = idm::get_unlocked(id); + if (!ctx || !ctx->curl) + { + idm::remove(id); + return SceNpClansError::SCE_NP_CLANS_ERROR_NOT_INITIALIZED; + } + + *req_id = id; + + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::destroy_request(u32 req_id) + { + if (idm::remove(req_id)) + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + + return SceNpClansError::SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + } + + SceNpClansError clans_client::send_request(u32 req_id, ClanRequestAction action, ClanManagerOperationType op_type, pugi::xml_document* xml_body, pugi::xml_document* out_response) + { + auto ctx = idm::get_unlocked(req_id); + + if (!ctx || !ctx->curl) + return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; + + CURL* curl = ctx->curl; + + ClanRequestType req_type = ClanRequestType::FUNC; + pugi::xml_node clan = xml_body->child("clan"); + if (clan && clan.child("ticket")) + { + req_type = ClanRequestType::SEC; + } + + std::string host = g_cfg_clans.get_host(); + std::string protocol = g_cfg_clans.get_use_https() ? "https" : "http"; + std::string url = fmt::format("%s://%s/clan_manager_%s/%s/%s", protocol, host, op_type, req_type, action); + + std::ostringstream oss; + xml_body->save(oss, "\t", 8U); + + std::string xml = oss.str(); + + char err_buf[CURL_ERROR_SIZE]; + err_buf[0] = '\0'; + + std::vector response_buffer; + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err_buf); + + // WARN: This disables certificate verification! + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, xml.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, xml.size()); + + CURLcode res = curl_easy_perform(curl); + + if (res != CURLE_OK) + { + out_response = nullptr; + clan_log.error("curl_easy_perform() failed: %s", curl_easy_strerror(res)); + clan_log.error("Error buffer: %s", err_buf); + return SCE_NP_CLANS_ERROR_BAD_REQUEST; + } + + response_buffer.push_back('\0'); + + pugi::xml_parse_result xml_res = out_response->load_string(response_buffer.data()); + if (!xml_res) + { + clan_log.error("XML parsing failed: %s", xml_res.description()); + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + } + + pugi::xml_node clan_result = out_response->child("clan"); + if (!clan_result) + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + + pugi::xml_attribute result = clan_result.attribute("result"); + if (!result) + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + + std::string result_str = result.as_string(); + if (result_str != "00") + return static_cast(0x80022800 | std::stoul(result_str, nullptr, 16)); + + return SCE_NP_CLANS_SUCCESS; + } + + std::string clans_client::get_clan_ticket(np::np_handler& nph) + { + // Pretend we failed to get a ticket if the emulator isn't + // connected to RPCN. + if (nph.get_psn_status() != SCE_NP_MANAGER_STATUS_ONLINE) + return ""; + + const auto& npid = nph.get_npid(); + + const char* service_id = CLANS_SERVICE_ID; + const unsigned char* cookie = nullptr; + const u32 cookie_size = 0; + const char* entitlement_id = CLANS_ENTITLEMENT_ID; + const u32 consumed_count = 0; + + // Use the cached ticket if available + np::ticket clan_ticket; + if (!nph.get_clan_ticket_ready()) + { + // If not cached, request a new ticket + nph.req_ticket(0x00020001, &npid, service_id, cookie, cookie_size, entitlement_id, consumed_count); + } + + clan_ticket = nph.get_clan_ticket(); + if (clan_ticket.empty()) + { + clan_log.error("Failed to get clan ticket"); + return ""; + } + + std::vector ticket_bytes(1024); + uint32_t ticket_size = UINT32_MAX; + + Base64_Encode_NoNl(clan_ticket.data(), clan_ticket.size(), ticket_bytes.data(), &ticket_size); + std::string ticket_str = std::string(reinterpret_cast(ticket_bytes.data()), ticket_size); + + return ticket_str; + } + +#pragma region Outgoing API Requests + SceNpClansError clans_client::get_clan_list(np::np_handler& nph, u32 req_id, SceNpClansPagingRequest* paging, SceNpClansEntry* clan_list, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetClanList, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node info = list.child("info"); info; info = info.next_sibling("info"), i++) + { + pugi::xml_attribute id = info.attribute("id"); + uint32_t clan_id = id.as_uint(); + + pugi::xml_node name = info.child("name"); + std::string name_str = name.text().as_string(); + + pugi::xml_node tag = info.child("tag"); + std::string tag_str = tag.text().as_string(); + + pugi::xml_node role = info.child("role"); + int32_t role_int = role.text().as_uint(); + + pugi::xml_node status = info.child("status"); + uint32_t status_int = status.text().as_uint(); + + pugi::xml_node onlinename = info.child("onlinename"); + std::string onlinename_str = onlinename.text().as_string(); + + pugi::xml_node members = info.child("members"); + uint32_t members_int = members.text().as_uint(); + + SceNpClansEntry entry = SceNpClansEntry{ + .info = SceNpClansClanBasicInfo{ + .clanId = clan_id, + .numMembers = members_int, + .name = "", + .tag = "", + .reserved = {0, 0}, + }, + .role = static_cast(role_int), + .status = static_cast(status_int)}; + + strcpy_trunc(entry.info.name, name_str); + strcpy_trunc(entry.info.tag, tag_str); + + clan_list[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult{ + .count = results_count, + .total = total_count}; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo* clan_info) + { + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetClanInfo, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node info = clan_result.child("info"); + + std::string name_str = info.child("name").text().as_string(); + std::string tag_str = info.child("tag").text().as_string(); + uint32_t members_int = info.child("members").text().as_uint(); + std::string date_created_str = info.child("date-created").text().as_string(); + std::string description_str = info.child("description").text().as_string(); + + *clan_info = SceNpClansClanInfo{ + .info = SceNpClansClanBasicInfo{ + .clanId = clan_id, + .numMembers = members_int, + .name = "", + .tag = "", + }, + .updatable = SceNpClansUpdatableClanInfo{ + .description = "", + }}; + + strcpy_trunc(clan_info->info.name, name_str); + strcpy_trunc(clan_info->info.tag, tag_str); + strcpy_trunc(clan_info->updatable.description, description_str); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberEntry* mem_info) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberInfo, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node member_info = clan_result.child("info"); + + pugi::xml_attribute member_jid = member_info.attribute("jid"); + std::string member_jid_str = member_jid.as_string(); + std::string member_username = fmt::split(member_jid_str, {"@"})[0]; + + SceNpId member_npid; + if (strncmp(member_username.c_str(), nph.get_npid().handle.data, 16) == 0) + { + member_npid = nph.get_npid(); + } + else + { + np::string_to_npid(member_username, member_npid); + } + + pugi::xml_node role = member_info.child("role"); + uint32_t role_int = role.text().as_uint(); + + pugi::xml_node status = member_info.child("status"); + uint32_t status_int = status.text().as_uint(); + + pugi::xml_node description = member_info.child("description"); + std::string description_str = description.text().as_string(); + + *mem_info = SceNpClansMemberEntry + { + .npid = member_npid, + .role = static_cast(role_int), + .status = static_cast(status_int), + .updatable = SceNpClansUpdatableMemberInfo{ + .description = "", + } + }; + + strcpy_trunc(mem_info->updatable.description, description_str); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMemberStatus /*status*/, SceNpClansMemberEntry* mem_list, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberList, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node member_info = list.child("info"); member_info; member_info = member_info.next_sibling("info")) + { + std::string member_jid = member_info.attribute("jid").as_string(); + std::string member_username = fmt::split(member_jid, {"@"})[0]; + + SceNpId member_npid; + if (strncmp(member_username.c_str(), nph.get_npid().handle.data, 16) == 0) + { + member_npid = nph.get_npid(); + } + else + { + np::string_to_npid(member_username, member_npid); + } + + uint32_t role_int = member_info.child("role").text().as_uint(); + uint32_t status_int = member_info.child("status").text().as_uint(); + std::string description_str = member_info.child("description").text().as_string(); + + SceNpClansMemberEntry entry = SceNpClansMemberEntry + { + .npid = member_npid, + .role = static_cast(role_int), + .status = static_cast(status_int), + }; + + strcpy_trunc(entry.updatable.description, description_str); + + mem_list[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetBlacklist, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node member = list.child("entry"); member; member = member.next_sibling("entry")) + { + pugi::xml_node member_jid = member.child("jid"); + std::string member_jid_str = member_jid.text().as_string(); + std::string member_username = fmt::split(member_jid_str, {"@"})[0]; + + SceNpId member_npid = {}; + if (strncmp(member_username.c_str(), nph.get_npid().handle.data, 16) == 0) + { + member_npid = nph.get_npid(); + } + else + { + np::string_to_npid(member_username.c_str(), member_npid); + } + + SceNpClansBlacklistEntry entry = SceNpClansBlacklistEntry + { + .entry = member_npid, + }; + + bl[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::RecordBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::DeleteBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::clan_search(u32 req_id, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clan_list, SceNpClansPagingResult* page_result) + { + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_node filter = clan.append_child("filter"); + pugi::xml_node name = filter.append_child("name"); + + std::string op_name = fmt::format("%s", static_cast(static_cast(search->nameSearchOp))); + name.append_attribute("op").set_value(op_name.c_str()); + name.append_attribute("value").set_value(search->name); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::ClanSearch, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node node = list.child("info"); node; node = node.next_sibling("info")) + { + uint32_t clan_id = node.attribute("id").as_uint(); + std::string name_str = node.child("name").text().as_string(); + std::string tag_str = node.child("tag").text().as_string(); + uint32_t members_int = node.child("members").text().as_uint(); + + SceNpClansClanBasicInfo entry = SceNpClansClanBasicInfo + { + .clanId = clan_id, + .numMembers = members_int, + .name = "", + .tag = "", + .reserved = {0, 0}, + }; + + strcpy_trunc(entry.name, name_str); + strcpy_trunc(entry.tag, tag_str); + + clan_list[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::create_clan(np::np_handler& nph, u32 req_id, std::string_view name, std::string_view tag, vm::ptr clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + + clan.append_child("name").text().set(name.data()); + clan.append_child("tag").text().set(tag.data()); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::CreateClan, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_attribute id = clan_result.attribute("id"); + uint32_t clan_id_int = id.as_uint(); + + *clan_id = clan_id_int; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::disband_dlan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::DisbandClan, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* /*message*/) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::RequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::cancel_request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::CancelRequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/, b8 allow) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, allow ? ClanRequestAction::AcceptMembershipRequest : ClanRequestAction::DeclineMembershipRequest, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::SendInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::CancelInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* /*message*/, b8 accept) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, accept ? ClanRequestAction::AcceptInvitation : ClanRequestAction::DeclineInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableMemberInfo* info) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_node role = clan.append_child("onlinename"); + role.text().set(nph.get_npid().handle.data); + + pugi::xml_node description = clan.append_child("description"); + description.text().set(info->description); + + pugi::xml_node status = clan.append_child("bin-attr1"); + + byte bin_attr_1[SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE * 2 + 1] = {0}; + uint32_t bin_attr_1_size = UINT32_MAX; + Base64_Encode_NoNl(info->binAttr1, info->binData1Size, bin_attr_1, &bin_attr_1_size); + + if (bin_attr_1_size == UINT32_MAX) + return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + + // `reinterpret_cast` used to let the compiler select the correct overload of `set` + status.text().set(reinterpret_cast(bin_attr_1)); + + pugi::xml_node allow_msg = clan.append_child("allow-msg"); + allow_msg.text().set(static_cast(info->allowMsg)); + + pugi::xml_node size = clan.append_child("size"); + size.text().set(info->binData1Size); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::UpdateMemberInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableClanInfo* info) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + // TODO: implement binary and integer attributes (not implemented in server yet) + + pugi::xml_node description = clan.append_child("description"); + description.text().set(info->description); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::UpdateClanInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::join_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::JoinClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::leave_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::LeaveClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::KickMember, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberRole role) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_node role_node = clan.append_child("role"); + role_node.text().set(static_cast(role)); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::ChangeMemberRole, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::RetrieveAnnouncements, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node node = list.child("msg-info"); node; node = node.next_sibling("msg-info")) + { + pugi::xml_attribute id = node.attribute("id"); + uint32_t msg_id = id.as_uint(); + + std::string subject_str = node.child("subject").text().as_string(); + std::string msg_str = node.child("msg").text().as_string(); + std::string author_jid = node.child("jid").text().as_string(); + std::string msg_date = node.child("msg-date").text().as_string(); + + SceNpId author_npid; + std::string author_username = fmt::split(author_jid, {"@"})[0]; + + if (strncmp(author_username.c_str(), nph.get_npid().handle.data, 16) == 0) + { + author_npid = nph.get_npid(); + } + else + { + np::string_to_npid(author_username, author_npid); + } + + // TODO: implement `binData` and `fromId` + + SceNpClansMessageEntry entry = SceNpClansMessageEntry + { + .mId = msg_id, + .message = SceNpClansMessage { + .subject = "", + .body = "", + }, + .npid = author_npid, + .postedBy = clan_id, + }; + + strcpy_trunc(entry.message.subject, subject_str); + strcpy_trunc(entry.message.body, msg_str); + + announcements[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* announcement, SceNpClansMessageData* /*data*/, u32 duration, SceNpClansMessageId* msg_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_node subject = clan.append_child("subject"); + subject.text().set(announcement->subject); + + pugi::xml_node msg = clan.append_child("msg"); + msg.text().set(announcement->body); + + pugi::xml_node expire_date = clan.append_child("expire-date"); + expire_date.text().set(duration); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::PostAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node msg_id_node = clan_result.child("id"); + *msg_id = msg_id_node.text().as_uint(); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::delete_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessageId announcement_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + clan.append_child("msg-id").text().set(announcement_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::DeleteAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + } +} +#pragma endregion diff --git a/rpcs3/Emu/NP/clans_client.h b/rpcs3/Emu/NP/clans_client.h new file mode 100644 index 0000000000..68ed4a1cf1 --- /dev/null +++ b/rpcs3/Emu/NP/clans_client.h @@ -0,0 +1,129 @@ +#pragma once + +#include +#include + +#include +#include +#include + +inline const char* CLANS_ENTITLEMENT_ID = "NPWR00432_00"; +inline const char* CLANS_SERVICE_ID = "IV0001-NPXS01001_00"; + +namespace clan +{ + enum class ClanManagerOperationType + { + VIEW, + UPDATE + }; + + enum class ClanRequestType + { + FUNC, + SEC + }; + + enum class ClanSearchFilterOperator : u8 + { + Equal, + NotEqual, + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, + Like, + }; + + enum class ClanRequestAction + { + GetClanList, + GetClanInfo, + GetMemberInfo, + GetMemberList, + GetBlacklist, + RecordBlacklistEntry, + DeleteBlacklistEntry, + ClanSearch, + CreateClan, + DisbandClan, + RequestMembership, + CancelRequestMembership, + AcceptMembershipRequest, + DeclineMembershipRequest, + SendInvitation, + CancelInvitation, + AcceptInvitation, + DeclineInvitation, + UpdateMemberInfo, + UpdateClanInfo, + JoinClan, + LeaveClan, + KickMember, + ChangeMemberRole, + RetrieveAnnouncements, + PostAnnouncement, + DeleteAnnouncement + }; + + class clans_client + { + private: + + + static size_t curl_write_callback(void* data, size_t size, size_t nmemb, void* clientp); + SceNpClansError send_request(u32 reqId, ClanRequestAction action, ClanManagerOperationType type, pugi::xml_document* xml_body, pugi::xml_document* out_response); + + /// @brief Forge and get a V2.1 Ticket for clan operations + std::string get_clan_ticket(np::np_handler& nph); + + public: + clans_client(); + ~clans_client(); + + SceNpClansError create_request(s32* req_id); + SceNpClansError destroy_request(u32 req_id); + + SceNpClansError clan_search(u32 req_id, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clan_list, SceNpClansPagingResult* page_result); + + SceNpClansError create_clan(np::np_handler& nph, u32 req_id, std::string_view name, std::string_view tag, vm::ptr clan_id); + SceNpClansError disband_dlan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + + SceNpClansError get_clan_list(np::np_handler& nph, u32 req_id, SceNpClansPagingRequest* paging, SceNpClansEntry* clan_list, SceNpClansPagingResult* page_result); + SceNpClansError get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo* clan_info); + + SceNpClansError get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberEntry* mem_info); + SceNpClansError get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMemberStatus status, SceNpClansMemberEntry* mem_list, SceNpClansPagingResult* page_result); + + SceNpClansError get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* page_result); + SceNpClansError add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + SceNpClansError remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + + SceNpClansError request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message); + SceNpClansError cancel_request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + SceNpClansError send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message, b8 allow); + + SceNpClansError send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message); + SceNpClansError cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + SceNpClansError send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message, b8 accept); + + SceNpClansError join_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + SceNpClansError leave_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + + SceNpClansError update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableMemberInfo* info); + SceNpClansError update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableClanInfo* info); + + SceNpClansError kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message); + SceNpClansError change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberRole role); + + SceNpClansError retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* page_result); + SceNpClansError post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* announcement, SceNpClansMessageData* data, u32 duration, SceNpClansMessageId* announcement_id); + SceNpClansError delete_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessageId announcement_id); + }; +} // namespace clan + +struct sce_np_clans_manager +{ + atomic_t is_initialized = false; + std::shared_ptr client; +}; diff --git a/rpcs3/Emu/NP/clans_config.cpp b/rpcs3/Emu/NP/clans_config.cpp new file mode 100644 index 0000000000..08fd09c4b2 --- /dev/null +++ b/rpcs3/Emu/NP/clans_config.cpp @@ -0,0 +1,154 @@ +#include "Utilities/StrUtil.h" +#include "stdafx.h" +#include "clans_config.h" +#include "Utilities/File.h" + +cfg_clans g_cfg_clans; + +LOG_CHANNEL(clans_config_log, "clans_config"); + +void cfg_clans::load() +{ + const std::string path = cfg_clans::get_path(); + + fs::file cfg_file(path, fs::read); + if (cfg_file) + { + clans_config_log.notice("Loading Clans config. Path: %s", path); + from_string(cfg_file.to_string()); + } + else + { + clans_config_log.notice("Clans config missing. Using default settings. Path: %s", path); + from_default(); + } +} + +void cfg_clans::save() const +{ +#ifdef _WIN32 + const std::string path_to_cfg = fs::get_config_dir(true); + if (!fs::create_path(path_to_cfg)) + { + clans_config_log.error("Could not create path: %s", path_to_cfg); + } +#endif + + const std::string path = cfg_clans::get_path(); + + if (!cfg::node::save(path)) + { + clans_config_log.error("Could not save config: %s (error=%s)", path, fs::g_tls_error); + } +} + +std::string cfg_clans::get_path() +{ + return fs::get_config_dir(true) + "clans.yml"; +} + +std::string cfg_clans::get_host() const +{ + return host.to_string(); +} + +bool cfg_clans::get_use_https() const +{ + return use_https.get(); +} + +std::vector> cfg_clans::get_hosts() +{ + std::vector> vec_hosts; + const std::string host_str = hosts.to_string(); + const auto hosts_list = fmt::split_sv(host_str, {"|||"}); + + for (const auto& cur_host : hosts_list) + { + const auto desc_and_host = fmt::split(cur_host, {"|"}); + if (desc_and_host.size() != 2) + { + clans_config_log.error("Invalid host in the list of hosts: %s", cur_host); + continue; + } + vec_hosts.push_back(std::make_pair(std::move(desc_and_host[0]), std::move(desc_and_host[1]))); + } + + if (vec_hosts.empty()) + { + hosts.from_default(); + save(); + return get_hosts(); + } + + return vec_hosts; +} + +void cfg_clans::set_host(std::string_view host) +{ + this->host.from_string(host); +} + +void cfg_clans::set_use_https(bool use_https) +{ + this->use_https.set(use_https); +} + +void cfg_clans::set_hosts(const std::vector>& vec_hosts) +{ + std::string final_string; + for (const auto& [cur_desc, cur_host] : vec_hosts) + { + fmt::append(final_string, "%s|%s|||", cur_desc, cur_host); + } + + if (final_string.empty()) + { + hosts.from_default(); + return; + } + + final_string.resize(final_string.size() - 3); + hosts.from_string(final_string); +} + +bool cfg_clans::add_host(std::string_view new_description, std::string_view new_host) +{ + auto cur_hosts = get_hosts(); + + for (const auto& [cur_desc, cur_host] : cur_hosts) + { + if (cur_desc == new_description && cur_host == new_host) + return false; + } + + cur_hosts.push_back(std::make_pair(std::string(new_description), std::string(new_host))); + set_hosts(cur_hosts); + + return true; +} + +bool cfg_clans::del_host(std::string_view del_description, std::string_view del_host) +{ + // Do not delete default servers + const auto def_desc_and_host = fmt::split_sv(hosts.def, {"|"}); + ensure(def_desc_and_host.size() == 2); + if (del_description == def_desc_and_host[0] && del_host == def_desc_and_host[1]) + { + return true; + } + + auto cur_hosts = get_hosts(); + + for (auto it = cur_hosts.begin(); it != cur_hosts.end(); it++) + { + if (it->first == del_description && it->second == del_host) + { + cur_hosts.erase(it); + set_hosts(cur_hosts); + return true; + } + } + + return false; +} diff --git a/rpcs3/Emu/NP/clans_config.h b/rpcs3/Emu/NP/clans_config.h new file mode 100644 index 0000000000..96dcde497d --- /dev/null +++ b/rpcs3/Emu/NP/clans_config.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Utilities/Config.h" + +struct cfg_clans : cfg::node +{ + cfg::uint32 version{this, "Version", 1}; + cfg::string host{this, "Host", "clans.rpcs3.net"}; + cfg::string hosts{this, "Hosts", "Official Clans Server|clans.rpcs3.net"}; + cfg::_bool use_https{this, "Use HTTPS", true}; + + void load(); + void save() const; + + std::string get_host() const; + bool get_use_https() const; + std::vector> get_hosts(); + + void set_host(std::string_view host); + void set_use_https(bool use_https); + bool add_host(std::string_view description, std::string_view host); + bool del_host(std::string_view description, std::string_view host); + +private: + static std::string get_path(); + void set_hosts(const std::vector>& vec_hosts); +}; + +extern cfg_clans g_cfg_clans; diff --git a/rpcs3/Emu/NP/generated/np2_structs.fbs b/rpcs3/Emu/NP/generated/np2_structs.fbs index bf360bdb86..9ca6e4a437 100644 --- a/rpcs3/Emu/NP/generated/np2_structs.fbs +++ b/rpcs3/Emu/NP/generated/np2_structs.fbs @@ -206,7 +206,7 @@ table SetRoomDataInternalRequest { flagAttr:uint32; roomBinAttrInternal:[BinAttr]; passwordConfig:[RoomGroupPasswordConfig]; - passwordSlotMask:uint64; + passwordSlotMask:[uint64]; ownerPrivilegeRank:[uint16]; } diff --git a/rpcs3/Emu/NP/generated/np2_structs_generated.h b/rpcs3/Emu/NP/generated/np2_structs_generated.h index f1d24ae568..5dabacbbc9 100644 --- a/rpcs3/Emu/NP/generated/np2_structs_generated.h +++ b/rpcs3/Emu/NP/generated/np2_structs_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && - FLATBUFFERS_VERSION_MINOR == 3 && - FLATBUFFERS_VERSION_REVISION == 25, +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 9 && + FLATBUFFERS_VERSION_REVISION == 23, "Non-compatible flatbuffers version included"); struct SignalingAddr; @@ -2762,8 +2762,8 @@ struct SetRoomDataInternalRequest FLATBUFFERS_FINAL_CLASS : private ::flatbuffer const ::flatbuffers::Vector<::flatbuffers::Offset> *passwordConfig() const { return GetPointer> *>(VT_PASSWORDCONFIG); } - uint64_t passwordSlotMask() const { - return GetField(VT_PASSWORDSLOTMASK, 0); + const ::flatbuffers::Vector *passwordSlotMask() const { + return GetPointer *>(VT_PASSWORDSLOTMASK); } const ::flatbuffers::Vector *ownerPrivilegeRank() const { return GetPointer *>(VT_OWNERPRIVILEGERANK); @@ -2779,7 +2779,8 @@ struct SetRoomDataInternalRequest FLATBUFFERS_FINAL_CLASS : private ::flatbuffer VerifyOffset(verifier, VT_PASSWORDCONFIG) && verifier.VerifyVector(passwordConfig()) && verifier.VerifyVectorOfTables(passwordConfig()) && - VerifyField(verifier, VT_PASSWORDSLOTMASK, 8) && + VerifyOffset(verifier, VT_PASSWORDSLOTMASK) && + verifier.VerifyVector(passwordSlotMask()) && VerifyOffset(verifier, VT_OWNERPRIVILEGERANK) && verifier.VerifyVector(ownerPrivilegeRank()) && verifier.EndTable(); @@ -2805,8 +2806,8 @@ struct SetRoomDataInternalRequestBuilder { void add_passwordConfig(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> passwordConfig) { fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDCONFIG, passwordConfig); } - void add_passwordSlotMask(uint64_t passwordSlotMask) { - fbb_.AddElement(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask, 0); + void add_passwordSlotMask(::flatbuffers::Offset<::flatbuffers::Vector> passwordSlotMask) { + fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask); } void add_ownerPrivilegeRank(::flatbuffers::Offset<::flatbuffers::Vector> ownerPrivilegeRank) { fbb_.AddOffset(SetRoomDataInternalRequest::VT_OWNERPRIVILEGERANK, ownerPrivilegeRank); @@ -2829,12 +2830,12 @@ inline ::flatbuffers::Offset CreateSetRoomDataIntern uint32_t flagAttr = 0, ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> roomBinAttrInternal = 0, ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> passwordConfig = 0, - uint64_t passwordSlotMask = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> passwordSlotMask = 0, ::flatbuffers::Offset<::flatbuffers::Vector> ownerPrivilegeRank = 0) { SetRoomDataInternalRequestBuilder builder_(_fbb); - builder_.add_passwordSlotMask(passwordSlotMask); builder_.add_roomId(roomId); builder_.add_ownerPrivilegeRank(ownerPrivilegeRank); + builder_.add_passwordSlotMask(passwordSlotMask); builder_.add_passwordConfig(passwordConfig); builder_.add_roomBinAttrInternal(roomBinAttrInternal); builder_.add_flagAttr(flagAttr); @@ -2849,10 +2850,11 @@ inline ::flatbuffers::Offset CreateSetRoomDataIntern uint32_t flagAttr = 0, const std::vector<::flatbuffers::Offset> *roomBinAttrInternal = nullptr, const std::vector<::flatbuffers::Offset> *passwordConfig = nullptr, - uint64_t passwordSlotMask = 0, + const std::vector *passwordSlotMask = nullptr, const std::vector *ownerPrivilegeRank = nullptr) { auto roomBinAttrInternal__ = roomBinAttrInternal ? _fbb.CreateVector<::flatbuffers::Offset>(*roomBinAttrInternal) : 0; auto passwordConfig__ = passwordConfig ? _fbb.CreateVector<::flatbuffers::Offset>(*passwordConfig) : 0; + auto passwordSlotMask__ = passwordSlotMask ? _fbb.CreateVector(*passwordSlotMask) : 0; auto ownerPrivilegeRank__ = ownerPrivilegeRank ? _fbb.CreateVector(*ownerPrivilegeRank) : 0; return CreateSetRoomDataInternalRequest( _fbb, @@ -2861,7 +2863,7 @@ inline ::flatbuffers::Offset CreateSetRoomDataIntern flagAttr, roomBinAttrInternal__, passwordConfig__, - passwordSlotMask, + passwordSlotMask__, ownerPrivilegeRank__); } diff --git a/rpcs3/Emu/NP/np_cache.cpp b/rpcs3/Emu/NP/np_cache.cpp index 3b1c38bd9e..fc90a641a5 100644 --- a/rpcs3/Emu/NP/np_cache.cpp +++ b/rpcs3/Emu/NP/np_cache.cpp @@ -4,6 +4,7 @@ #include "Emu/NP/np_allocator.h" #include "Emu/NP/np_cache.h" #include "Emu/NP/np_helpers.h" +#include "Emu/NP/np_structs_extra.h" LOG_CHANNEL(np_cache); @@ -155,6 +156,8 @@ namespace np slots.openPublicSlotNum = open_public_slots; slots.openPrivateSlotNum = open_private_slots; + extra_nps::print_SceNpMatching2RoomSlotInfo(&slots); + return {CELL_OK, slots}; } diff --git a/rpcs3/Emu/NP/np_dnshook.cpp b/rpcs3/Emu/NP/np_dnshook.cpp index a035947e1f..515cba5fc6 100644 --- a/rpcs3/Emu/NP/np_dnshook.cpp +++ b/rpcs3/Emu/NP/np_dnshook.cpp @@ -28,7 +28,8 @@ namespace np dnshook::dnshook() { // Init switch map for dns - auto swaps = fmt::split(g_cfg.net.swap_list.to_string(), {"&&"}); + const std::string swap_list = g_cfg.net.swap_list.to_string(); + const auto swaps = fmt::split_sv(swap_list, {"&&"}); for (usz i = 0; i < swaps.size(); i++) { auto host_and_ip = fmt::split(swaps[i], {"="}); diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 30eac7f470..f76788287b 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -239,6 +239,25 @@ namespace np return true; } + std::string ticket::get_service_id() const + { + if (!parse_success) + { + return ""; + } + + const auto& node = nodes[0].data.data_nodes[8]; + if (node.len != SCE_NP_SERVICE_ID_SIZE) + { + return ""; + } + + // Trim null characters + const auto& vec = node.data.data_vec; + auto it = std::find(vec.begin(), vec.end(), 0); + return std::string(vec.begin(), it); + } + std::optional ticket::parse_node(std::size_t index) const { if ((index + MIN_TICKET_DATA_SIZE) > size()) @@ -1273,19 +1292,20 @@ namespace np return false; } - u32 np_handler::generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, SceNpMatching2Event event_type) + u32 np_handler::generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, SceNpMatching2Event event_type, bool abortable) { - callback_info ret; - const auto ctx = get_match2_context(ctx_id); ensure(ctx); const u32 req_id = get_req_id(optParam ? optParam->appReqId : ctx->default_match2_optparam.appReqId); - ret.ctx_id = ctx_id; - ret.cb_arg = (optParam && optParam->cbFuncArg) ? optParam->cbFuncArg : ctx->default_match2_optparam.cbFuncArg; - ret.cb = (optParam && optParam->cbFunc) ? optParam->cbFunc : ctx->default_match2_optparam.cbFunc; - ret.event_type = event_type; + callback_info ret{ + .ctx_id = ctx_id, + .cb = (optParam && optParam->cbFunc) ? optParam->cbFunc : ctx->default_match2_optparam.cbFunc, + .cb_arg = (optParam && optParam->cbFuncArg) ? optParam->cbFuncArg : ctx->default_match2_optparam.cbFuncArg, + .event_type = event_type, + .abortable = abortable, + }; nph_log.trace("Callback used is 0x%x with req_id %d", ret.cb, req_id); @@ -1310,16 +1330,22 @@ namespace np return cb_info; } - bool np_handler::abort_request(u32 req_id) + error_code np_handler::abort_request(u32 req_id) { - auto cb_info_opt = take_pending_request(req_id); + std::lock_guard lock(mutex_pending_requests); - if (!cb_info_opt) - return false; + if (!pending_requests.contains(req_id)) + return SCE_NP_MATCHING2_ERROR_REQUEST_NOT_FOUND; - cb_info_opt->queue_callback(req_id, 0, SCE_NP_MATCHING2_ERROR_ABORTED, 0); + if (!::at32(pending_requests, req_id).abortable) + return SCE_NP_MATCHING2_ERROR_CANNOT_ABORT; - return true; + const auto cb_info = std::move(::at32(pending_requests, req_id)); + pending_requests.erase(req_id); + + cb_info.queue_callback(req_id, 0, SCE_NP_MATCHING2_ERROR_ABORTED, 0); + + return CELL_OK; } event_data& np_handler::allocate_req_result(u32 event_key, u32 max_size, u32 initial_size) @@ -1345,6 +1371,24 @@ namespace np return history; } + u32 np_handler::get_clan_ticket_ready() + { + return clan_ticket_ready.load(); + } + + ticket np_handler::get_clan_ticket() + { + clan_ticket_ready.wait(0, atomic_wait_timeout{60'000'000'000}); // 60 seconds + + if (!clan_ticket_ready.load()) + { + rpcn_log.error("Failed to get clan ticket within timeout."); + return ticket{}; + } + + return clan_ticket; + } + constexpr usz MAX_HISTORY_ENTRIES = 200; void np_handler::add_player_to_history(const SceNpId* npid, const char* description) @@ -1702,4 +1746,17 @@ namespace np return ctx; } + void np_handler::callback_info::queue_callback(u32 req_id, u32 event_key, s32 error_code, u32 data_size) const + { + if (cb) + { + sysutil_register_cb([=, ctx_id = this->ctx_id, event_type = this->event_type, cb = this->cb, cb_arg = this->cb_arg](ppu_thread& cb_ppu) -> s32 + { + sceNp2.trace("Calling callback 0x%x with req_id %d, event_type: 0x%x, error_code: 0x%x", cb, req_id, event_type, error_code); + cb(cb_ppu, ctx_id, req_id, event_type, event_key, error_code, data_size, cb_arg); + return 0; + }); + } + } + } // namespace np diff --git a/rpcs3/Emu/NP/np_handler.h b/rpcs3/Emu/NP/np_handler.h index c7a086f224..d3c1213cc6 100644 --- a/rpcs3/Emu/NP/np_handler.h +++ b/rpcs3/Emu/NP/np_handler.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/Modules/sceNp.h" @@ -69,6 +70,7 @@ namespace np bool empty() const; bool get_value(s32 param_id, vm::ptr param) const; + std::string get_service_id() const; private: std::optional parse_node(std::size_t index) const; @@ -253,11 +255,13 @@ namespace np // Misc stuff void req_ticket(u32 version, const SceNpId* npid, const char* service_id, const u8* cookie, u32 cookie_size, const char* entitlement_id, u32 consumed_count); const ticket& get_ticket() const; + u32 get_clan_ticket_ready(); + ticket get_clan_ticket(); void add_player_to_history(const SceNpId* npid, const char* description); u32 add_players_to_history(const SceNpId* npids, const char* description, u32 count); u32 get_players_history_count(u32 options); bool get_player_history_entry(u32 options, u32 index, SceNpId* npid); - bool abort_request(u32 req_id); + error_code abort_request(u32 req_id); // For signaling void req_sign_infos(const std::string& npid, u32 conn_id); @@ -372,21 +376,12 @@ namespace np vm::ptr cb; vm::ptr cb_arg; SceNpMatching2Event event_type; + bool abortable; - void queue_callback(u32 req_id, u32 event_key, s32 error_code, u32 data_size) const - { - if (cb) - { - sysutil_register_cb([=, ctx_id = this->ctx_id, event_type = this->event_type, cb = this->cb, cb_arg = this->cb_arg](ppu_thread& cb_ppu) -> s32 - { - cb(cb_ppu, ctx_id, req_id, event_type, event_key, error_code, data_size, cb_arg); - return 0; - }); - } - } + void queue_callback(u32 req_id, u32 event_key, s32 error_code, u32 data_size) const; }; - u32 generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, SceNpMatching2Event event_type); + u32 generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, SceNpMatching2Event event_type, bool abortable); std::optional take_pending_request(u32 req_id); private: @@ -415,6 +410,10 @@ namespace np ticket current_ticket; + // Clan ticket + atomic_t clan_ticket_ready = 0; + ticket clan_ticket; + // IP & DNS info std::string hostname = "localhost"; std::array ether_address{}; diff --git a/rpcs3/Emu/NP/np_requests.cpp b/rpcs3/Emu/NP/np_requests.cpp index 23e18f1353..23e3d7ca75 100644 --- a/rpcs3/Emu/NP/np_requests.cpp +++ b/rpcs3/Emu/NP/np_requests.cpp @@ -1,8 +1,9 @@ +#include "stdafx.h" #include "Emu/Cell/Modules/sceNp.h" #include "Emu/Cell/Modules/sceNp2.h" +#include "Emu/NP/clans_client.h" #include "Emu/NP/rpcn_types.h" #include "Utilities/StrFmt.h" -#include "stdafx.h" #include "Emu/Cell/PPUCallback.h" #include "Emu/Cell/lv2/sys_sync.h" #include "Emu/system_config.h" @@ -69,7 +70,7 @@ namespace np u32 np_handler::get_server_status(SceNpMatching2ContextId ctx_id, vm::cptr optParam, u16 server_id) { // TODO: actually implement interaction with server for this? - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetServerInfo); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetServerInfo, true); const u32 event_key = get_event_key(); auto& edata = allocate_req_result(event_key, SCE_NP_MATCHING2_EVENT_DATA_MAX_SIZE_GetServerInfo, sizeof(SceNpMatching2GetServerInfoResponse)); @@ -87,7 +88,7 @@ namespace np u32 np_handler::create_server_context(SceNpMatching2ContextId ctx_id, vm::cptr optParam, u16 /*server_id*/) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateServerContext); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateServerContext, false); const auto cb_info_opt = take_pending_request(req_id); ensure(cb_info_opt); @@ -98,7 +99,7 @@ namespace np u32 np_handler::delete_server_context(SceNpMatching2ContextId ctx_id, vm::cptr optParam, u16 /*server_id*/) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_DeleteServerContext); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_DeleteServerContext, false); const auto cb_info_opt = take_pending_request(req_id); ensure(cb_info_opt); @@ -109,7 +110,7 @@ namespace np u32 np_handler::get_world_list(SceNpMatching2ContextId ctx_id, vm::cptr optParam, u16 server_id) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetWorldInfoList); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetWorldInfoList, true); if (!get_rpcn()->get_world_list(req_id, get_match2_context(ctx_id)->communicationId, server_id)) { @@ -159,7 +160,7 @@ namespace np u32 np_handler::create_join_room(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2CreateJoinRoomRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateJoinRoom); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateJoinRoom, false); extra_nps::print_SceNpMatching2CreateJoinRoomRequest(req); @@ -221,7 +222,7 @@ namespace np u32 np_handler::join_room(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2JoinRoomRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_JoinRoom); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_JoinRoom, false); extra_nps::print_SceNpMatching2JoinRoomRequest(req); @@ -311,7 +312,7 @@ namespace np u32 np_handler::leave_room(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2LeaveRoomRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_LeaveRoom); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_LeaveRoom, false); if (!get_rpcn()->leave_room(req_id, get_match2_context(ctx_id)->communicationId, req)) { @@ -356,7 +357,7 @@ namespace np u32 np_handler::search_room(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SearchRoomRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SearchRoom); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SearchRoom, true); extra_nps::print_SceNpMatching2SearchRoomRequest(req); @@ -395,7 +396,7 @@ namespace np u32 np_handler::get_roomdata_external_list(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2GetRoomDataExternalListRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataExternalList); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataExternalList, true); extra_nps::print_SceNpMatching2GetRoomDataExternalListRequest(req); @@ -435,7 +436,7 @@ namespace np u32 np_handler::set_roomdata_external(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SetRoomDataExternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataExternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataExternal, false); extra_nps::print_SceNpMatching2SetRoomDataExternalRequest(req); @@ -470,7 +471,7 @@ namespace np u32 np_handler::get_roomdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2GetRoomDataInternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataInternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataInternal, true); if (!get_rpcn()->get_roomdata_internal(req_id, get_match2_context(ctx_id)->communicationId, req)) { @@ -524,7 +525,7 @@ namespace np u32 np_handler::set_roomdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SetRoomDataInternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataInternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataInternal, false); extra_nps::print_SceNpMatching2SetRoomDataInternalRequest(req); @@ -558,7 +559,7 @@ namespace np u32 np_handler::get_roommemberdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2GetRoomMemberDataInternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomMemberDataInternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomMemberDataInternal, true); extra_nps::print_SceNpMatching2GetRoomMemberDataInternalRequest(req); if (!get_rpcn()->get_roommemberdata_internal(req_id, get_match2_context(ctx_id)->communicationId, req)) @@ -610,7 +611,7 @@ namespace np u32 np_handler::set_roommemberdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SetRoomMemberDataInternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomMemberDataInternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomMemberDataInternal, false); extra_nps::print_SceNpMatching2SetRoomMemberDataInternalRequest(req); @@ -646,7 +647,7 @@ namespace np u32 np_handler::set_userinfo(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SetUserInfoRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetUserInfo); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetUserInfo, false); if (!get_rpcn()->set_userinfo(req_id, get_match2_context(ctx_id)->communicationId, req)) { @@ -675,7 +676,7 @@ namespace np u32 np_handler::get_ping_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SignalingGetPingInfoRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SignalingGetPingInfo); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SignalingGetPingInfo, true); if (!get_rpcn()->ping_room_owner(req_id, get_match2_context(ctx_id)->communicationId, req->roomId)) { @@ -722,7 +723,7 @@ namespace np u32 np_handler::send_room_message(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SendRoomMessageRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SendRoomMessage); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SendRoomMessage, false); if (!get_rpcn()->send_room_message(req_id, get_match2_context(ctx_id)->communicationId, req)) { @@ -803,7 +804,7 @@ namespace np extra_nps::print_SceNpMatching2GetLobbyInfoListRequest(req); - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetLobbyInfoList); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetLobbyInfoList, false); auto cb_info_opt = take_pending_request(req_id); if (!cb_info_opt) @@ -863,7 +864,20 @@ namespace np auto ticket_raw = reply.get_rawdata(); ensure(!reply.is_error(), "Malformed reply to RequestTicket command"); - current_ticket = ticket(std::move(ticket_raw)); + auto incoming_ticket = ticket(std::move(ticket_raw)); + + // Clans: check if ticket belongs to the clan service. + // If so, hijack the ticket and cache it for future use. + if (incoming_ticket.get_service_id() == CLANS_SERVICE_ID) + { + clan_ticket = incoming_ticket; + clan_ticket_ready.store(1); + clan_ticket_ready.notify_all(); + + return; + } + + current_ticket = incoming_ticket; auto ticket_size = static_cast(current_ticket.size()); if (manager_cb) diff --git a/rpcs3/Emu/NP/np_structs_extra.cpp b/rpcs3/Emu/NP/np_structs_extra.cpp index 2acba3d045..52f394ff2e 100644 --- a/rpcs3/Emu/NP/np_structs_extra.cpp +++ b/rpcs3/Emu/NP/np_structs_extra.cpp @@ -104,7 +104,7 @@ namespace extra_nps for (u32 i = 0; i < req->roomSearchableIntAttrExternalNum && req->roomSearchableIntAttrExternal; i++) print_int_attr(&req->roomSearchableIntAttrExternal[i]); - + sceNp2.warning("roomSearchableBinAttrExternal: *0x%x", req->roomSearchableBinAttrExternal); sceNp2.warning("roomSearchableBinAttrExternalNum: %d", req->roomSearchableBinAttrExternalNum); @@ -186,7 +186,7 @@ namespace extra_nps sceNp2.warning("SceNpMatching2SearchRoomResponse:"); print_range(&resp->range); - const SceNpMatching2RoomDataExternal *room_ptr = resp->roomDataExternal.get_ptr(); + const SceNpMatching2RoomDataExternal* room_ptr = resp->roomDataExternal.get_ptr(); for (u32 i = 0; i < resp->range.total; i++) { sceNp2.warning("SceNpMatching2SearchRoomResponse[%d]:", i); @@ -471,7 +471,7 @@ namespace extra_nps { sceNp.warning("ptr: *0x%x", data->value.data.ptr); sceNp.warning("size: %d", data->value.data.size); - sceNp.warning("data:\n%s", fmt::buf_to_hexstring(static_cast(data->value.data.ptr.get_ptr()), data->value.data.size)); + sceNp.warning("data:\n%s", fmt::buf_to_hexstring(static_cast(data->value.data.ptr.get_ptr()), data->value.data.size)); } else { @@ -577,4 +577,16 @@ namespace extra_nps } } + void print_SceNpMatching2RoomSlotInfo(const SceNpMatching2RoomSlotInfo* data) + { + sceNp.warning("SceNpMatching2RoomSlotInfo:"); + sceNp.warning("roomId: %d", data->roomId); + sceNp.warning("joinedSlotMask: %x", data->joinedSlotMask); + sceNp.warning("passwordSlotMask: %x", data->passwordSlotMask); + sceNp.warning("publicSlotNum: %d", data->publicSlotNum); + sceNp.warning("privateSlotNum: %d", data->privateSlotNum); + sceNp.warning("openPublicSlotNum: %d", data->openPublicSlotNum); + sceNp.warning("openPrivateSlotNum: %d", data->openPrivateSlotNum); + } + } // namespace extra_nps diff --git a/rpcs3/Emu/NP/np_structs_extra.h b/rpcs3/Emu/NP/np_structs_extra.h index d05711fabd..1c722a0cf2 100644 --- a/rpcs3/Emu/NP/np_structs_extra.h +++ b/rpcs3/Emu/NP/np_structs_extra.h @@ -41,4 +41,5 @@ namespace extra_nps void print_SceNpMatchingRoomStatus(const SceNpMatchingRoomStatus* data); void print_SceNpMatchingJoinedRoomInfo(const SceNpMatchingJoinedRoomInfo* data); void print_SceNpMatchingSearchJoinRoomInfo(const SceNpMatchingSearchJoinRoomInfo* data); + void print_SceNpMatching2RoomSlotInfo(const SceNpMatching2RoomSlotInfo* data); } // namespace extra_nps diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index c1dce01cdb..1ebbfdd14e 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -255,7 +255,7 @@ namespace rpcn rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size())); } - constexpr u32 RPCN_PROTOCOL_VERSION = 26; + constexpr u32 RPCN_PROTOCOL_VERSION = 27; constexpr usz RPCN_HEADER_SIZE = 15; const char* error_to_explanation(rpcn::ErrorType error) @@ -1495,7 +1495,7 @@ namespace rpcn return notifs; } - std::unordered_map>> rpcn_client::get_replies() + std::map>> rpcn_client::get_replies() { std::lock_guard lock(mutex_replies); auto ret_replies = std::move(replies); @@ -2019,9 +2019,13 @@ namespace rpcn } final_grouppasswordconfig_vec = builder.CreateVector(davec); } - u64 final_passwordSlotMask = 0; + + flatbuffers::Offset> final_passwordSlotMask; if (req->passwordSlotMask) - final_passwordSlotMask = *req->passwordSlotMask; + { + const u64 value = *req->passwordSlotMask; + final_passwordSlotMask = builder.CreateVector(&value, 1); + } flatbuffers::Offset> final_ownerprivilege_vec; if (req->ownerPrivilegeRankNum && req->ownerPrivilegeRank) diff --git a/rpcs3/Emu/NP/rpcn_client.h b/rpcs3/Emu/NP/rpcn_client.h index 11acb291a5..6d7126ffa6 100644 --- a/rpcs3/Emu/NP/rpcn_client.h +++ b/rpcs3/Emu/NP/rpcn_client.h @@ -314,7 +314,7 @@ namespace rpcn std::optional> get_friend_presence_by_npid(const std::string& npid); std::vector>> get_notifications(); - std::unordered_map>> get_replies(); + std::map>> get_replies(); std::unordered_map get_presence_updates(); std::map get_presence_states(); @@ -428,8 +428,8 @@ namespace rpcn shared_mutex mutex_notifs, mutex_replies, mutex_replies_sync, mutex_presence_updates; std::vector>> notifications; // notif type / data - std::unordered_map>> replies; // req id / (command / data) - std::unordered_map>> replies_sync; // same but for sync replies(see handle_input()) + std::map>> replies; // req id / (command / data) + std::map>> replies_sync; // same but for sync replies(see handle_input()) std::unordered_map presence_updates; // npid / presence data // Messages diff --git a/rpcs3/Emu/NP/rpcn_config.cpp b/rpcs3/Emu/NP/rpcn_config.cpp index b489d7d924..0b5cabe768 100644 --- a/rpcs3/Emu/NP/rpcn_config.cpp +++ b/rpcs3/Emu/NP/rpcn_config.cpp @@ -79,7 +79,8 @@ std::string cfg_rpcn::get_host() const std::vector> cfg_rpcn::get_hosts() { std::vector> vec_hosts; - auto hosts_list = fmt::split(hosts.to_string(), {"|||"}); + const std::string host_str = hosts.to_string(); + const auto hosts_list = fmt::split_sv(host_str, {"|||"}); for (const auto& cur_host : hosts_list) { @@ -190,9 +191,8 @@ bool cfg_rpcn::add_host(std::string_view new_description, std::string_view new_h bool cfg_rpcn::del_host(std::string_view del_description, std::string_view del_host) { - // Do not delete default servers - if ((del_description == "Official RPCN Server" && del_host == "np.rpcs3.net") || - (del_description == "RPCN Test Server" && del_host == "test-np.rpcs3.net")) + // Do not delete default server + if (del_description == "Official RPCN Server" && del_host == "np.rpcs3.net") { return true; } diff --git a/rpcs3/Emu/NP/rpcn_config.h b/rpcs3/Emu/NP/rpcn_config.h index a94eb053b7..0a8c7c9cc5 100644 --- a/rpcs3/Emu/NP/rpcn_config.h +++ b/rpcs3/Emu/NP/rpcn_config.h @@ -9,7 +9,7 @@ struct cfg_rpcn : cfg::node cfg::string npid{this, "NPID", ""}; cfg::string password{this, "Password", ""}; cfg::string token{this, "Token", ""}; - cfg::string hosts{this, "Hosts", "Official RPCN Server|np.rpcs3.net|||RPCN Test Server|test-np.rpcs3.net"}; + cfg::string hosts{this, "Hosts", "Official RPCN Server|np.rpcs3.net"}; cfg::_bool ipv6_support{this, "IPv6 support", true}; void load(); diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index 00dd6e7d95..6852e670fb 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -337,7 +337,7 @@ namespace rsx AUDIT(_loc < _size); const auto remaining = (_size - _loc); - memmove(pos + 1, pos, remaining * sizeof(Ty)); + std::memmove(pos + 1, pos, remaining * sizeof(Ty)); *pos = val; _size++; @@ -365,7 +365,7 @@ namespace rsx AUDIT(_loc < _size); const u32 remaining = (_size - _loc); - memmove(pos + 1, pos, remaining * sizeof(Ty)); + std::memmove(pos + 1, pos, remaining * sizeof(Ty)); *pos = val; _size++; @@ -373,6 +373,31 @@ namespace rsx return pos; } + iterator insert(iterator where, span_like auto const& values) + { + ensure(where >= _data); + const auto _loc = offset(where); + const auto in_size = static_cast(values.size()); + const auto in_size_bytes = in_size * sizeof(Ty); + + reserve(_size + in_size); + + if (_loc >= _size) + { + where = _data + _size; + std::memcpy(where, values.data(), in_size_bytes); + _size += in_size; + return where; + } + + const u32 remaining_bytes = (_size - _loc) * sizeof(Ty); + where = _data + _loc; + std::memmove(where + in_size, where, remaining_bytes); + std::memmove(where, values.data(), in_size_bytes); + _size += in_size; + return where; + } + void operator += (const rsx::simple_array& that) { const auto old_size = _size; diff --git a/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp b/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp index b1dc3fee5a..91ef3f13d9 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp @@ -120,9 +120,15 @@ namespace rsx { result.font_names.emplace_back("Arial.ttf"); result.font_names.emplace_back("arial.ttf"); -#ifndef _WIN32 - result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu - result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch +#ifdef __APPLE__ + result.font_names.emplace_back("DejaVuSans.ttf"); + result.font_names.emplace_back("NotoSans-Regular.ttf"); + result.font_names.emplace_back("Roboto-Regular.ttf"); + result.font_names.emplace_back("OpenSans-Regular.ttf"); + result.font_names.emplace_back("FreeSans.ttf"); +#elif !defined(_WIN32) + result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu + result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch #endif // Attempt to load a font from dev_flash as a last resort result.font_names.emplace_back("SCE-PS3-VR-R-LATIN.TTF"); @@ -207,7 +213,7 @@ namespace rsx return font_found; }; - for (const auto& font_file : fs_settings.font_names) + for (const std::string& font_file : fs_settings.font_names) { if (fs::is_file(font_file)) { @@ -256,7 +262,7 @@ namespace rsx { if (fallback_bytes.empty()) { - fmt::throw_exception("Failed to initialize font for character 0x%x on codepage %d.", static_cast(c), static_cast(codepage_id)); + fmt::throw_exception("Failed to initialize font for character 0x%x on codepage %d.\nLookup dirs:\n%s\nTarget fonts:\n%s", static_cast(c), static_cast(codepage_id), fmt::merge(fs_settings.lookup_font_dirs, "\n"), fmt::merge(fs_settings.font_names, "\n")); } rsx_log.error("Failed to initialize font for character 0x%x on codepage %d. Falling back to font '%s'", static_cast(c), static_cast(codepage_id), fallback_file); diff --git a/rpcs3/Emu/RSX/Program/Assembler/CFG.h b/rpcs3/Emu/RSX/Program/Assembler/CFG.h index 9bc44a22d1..818bc2a018 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/CFG.h +++ b/rpcs3/Emu/RSX/Program/Assembler/CFG.h @@ -34,6 +34,11 @@ namespace rsx::assembler } }; + struct CFGPass + { + virtual void run(FlowGraph& graph) = 0; + }; + FlowGraph deconstruct_fragment_program(const RSXFragmentProgram& prog); } diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp new file mode 100644 index 0000000000..2d74fafc73 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -0,0 +1,455 @@ +#include "stdafx.h" +#include "FPASM.h" +#include "Emu/RSX/Program/RSXFragmentProgram.h" + +#include + +#ifndef _WIN32 +#define sscanf_s sscanf +#endif + +namespace rsx::assembler +{ + struct FP_opcode_encoding_t + { + FP_opcode op; + bool exec_if_lt; + bool exec_if_eq; + bool exec_if_gt; + bool set_cond; + }; + + static std::unordered_map s_opcode_lookup + { + // Arithmetic + { "NOP", { .op = RSX_FP_OPCODE_NOP, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "MOV", { .op = RSX_FP_OPCODE_MOV, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "MUL", { .op = RSX_FP_OPCODE_MUL, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "ADD", { .op = RSX_FP_OPCODE_ADD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "MAD", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "FMA", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "DP3", { .op = RSX_FP_OPCODE_DP3, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "DP4", { .op = RSX_FP_OPCODE_DP4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + + // Constant load + { "SFL", {.op = RSX_FP_OPCODE_SFL, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "STR", {.op = RSX_FP_OPCODE_STR, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + + // Pack-unpack operations are great for testing dependencies + { "PKH", { .op = RSX_FP_OPCODE_PK2, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UPH", { .op = RSX_FP_OPCODE_UP2, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "PK16U", { .op = RSX_FP_OPCODE_PK16, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UP16U", { .op = RSX_FP_OPCODE_UP16, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "PK8U", { .op = RSX_FP_OPCODE_PKB, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UP8U", { .op = RSX_FP_OPCODE_UPB, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "PK8G", { .op = RSX_FP_OPCODE_PKG, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UP8G", { .op = RSX_FP_OPCODE_UPG, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "PK8S", { .op = RSX_FP_OPCODE_PK4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UP8S", { .op = RSX_FP_OPCODE_UP4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + + // Basic conditionals + { "IF.LT", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = true, .exec_if_eq = false, .exec_if_gt = false, .set_cond = false } }, + { "IF.LE", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = false, .set_cond = false } }, + { "IF.EQ", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = true, .exec_if_gt = false, .set_cond = false } }, + { "IF.GE", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "IF.GT", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = true, .set_cond = false } }, + + { "SLT", { .op = RSX_FP_OPCODE_SLT, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } }, + { "SEQ", { .op = RSX_FP_OPCODE_SEQ, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } }, + { "SGT", { .op = RSX_FP_OPCODE_SGT, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } }, + + // TODO: Add more + + }; + + Instruction* FPIR::load(const RegisterRef& ref, int operand, Instruction* prev) + { + Instruction* target = prev; + if (!target) + { + m_instructions.push_back({}); + target = &m_instructions.back(); + } + + SRC_Common src{ .HEX = target->bytecode[operand + 1] }; + src.reg_type = RSX_FP_REGISTER_TYPE_TEMP; + src.fp16 = ref.reg.f16 ? 1 : 0; + src.tmp_reg_index = static_cast(ref.reg.id); + + src.swizzle_x = 0; + src.swizzle_y = 1; + src.swizzle_z = 2; + src.swizzle_w = 3; + + target->bytecode[operand + 1] = src.HEX; + return target; + } + + Instruction* FPIR::load(const std::array& constants, int operand, Instruction* prev) + { + Instruction* target = prev; + if (!target) + { + m_instructions.push_back({}); + target = &m_instructions.back(); + } + + // Unsupported for now + ensure(target->length == 4, "FPIR cannot encode more than one constant load per instruction"); + + SRC_Common src{ .HEX = target->bytecode[operand + 1] }; + src.reg_type = RSX_FP_REGISTER_TYPE_CONSTANT; + target->bytecode[operand + 1] = src.HEX; + + src.swizzle_x = 0; + src.swizzle_y = 1; + src.swizzle_z = 2; + src.swizzle_w = 3; + + // Embed literal constant + std::memcpy(&target->bytecode[4], constants.data(), 4 * sizeof(u32)); + target->length = 8; + return target; + } + + Instruction* FPIR::store(const RegisterRef& ref, Instruction* prev) + { + Instruction* target = prev; + if (!target) + { + m_instructions.push_back({}); + target = &m_instructions.back(); + } + + OPDEST dst{ .HEX = target->bytecode[0] }; + dst.dest_reg = static_cast(ref.reg.id); + dst.fp16 = ref.reg.f16 ? 1 : 0; + dst.write_mask = ref.mask; + dst.prec = ref.reg.f16 ? RSX_FP_PRECISION_HALF : RSX_FP_PRECISION_REAL; + + target->bytecode[0] = dst.HEX; + return target; + } + + void FPIR::mov(const RegisterRef& dst, f32 constant) + { + Instruction* inst = store(dst); + inst = load(std::array{ constant, constant, constant, constant }, 0); + inst->opcode = RSX_FP_OPCODE_MOV; + } + + void FPIR::mov(const RegisterRef& dst, const RegisterRef& src) + { + Instruction* inst = store(dst); + inst = load(src, 0); + inst->opcode = RSX_FP_OPCODE_MOV; + } + + void FPIR::add(const RegisterRef& dst, const std::array& constants) + { + Instruction* inst = store(dst); + inst = load(constants, 0); + inst->opcode = RSX_FP_OPCODE_ADD; + } + + void FPIR::add(const RegisterRef& dst, const RegisterRef& src) + { + Instruction* inst = store(dst); + inst = load(src, 0); + inst->opcode = RSX_FP_OPCODE_ADD; + } + + const std::vector& FPIR::instructions() const + { + return m_instructions; + } + + std::vector FPIR::compile() const + { + std::vector result; + result.reserve(m_instructions.size() * 4); + + for (const auto& inst : m_instructions) + { + const auto src = reinterpret_cast*>(inst.bytecode); + for (u32 j = 0; j < inst.length; ++j) + { + const u16 low = src[j * 2]; + const u16 hi = src[j * 2 + 1]; + const u32 word = static_cast(low) | (static_cast(hi) << 16u); + result.push_back(word); + } + } + + return result; + } + + FPIR FPIR::from_source(std::string_view asm_) + { + std::vector instructions = fmt::split(asm_, { "\n", ";" }); + if (instructions.empty()) + { + return {}; + } + + auto transform_inst = [](std::string_view s) + { + std::string result; + result.reserve(s.size()); + + bool literal = false; + for (const auto& c : s) + { + if (c == ' ') + { + if (!literal && !result.empty() && result.back() != ',') + { + result += ','; // Replace token separator space with comma + } + continue; + } + + if (std::isspace(c)) + { + continue; + } + + if (!literal && c == '{') + { + literal = true; + } + + if (literal && c == '}') + { + literal = false; + } + + if (c == ',') + { + result += (literal ? '|' : ','); + continue; + } + + result += c; + } + return result; + }; + + auto decode_instruction = [&](std::string_view inst, std::string& op, std::string& dst, std::vector& sources) + { + const auto i = transform_inst(inst); + if (i.empty()) + { + return; + } + + const auto tokens = fmt::split(i, { "," }); + ensure(!tokens.empty(), "Invalid input"); + + op = tokens.front(); + + if (tokens.size() > 1) + { + dst = tokens[1]; + } + + for (size_t n = 2; n < tokens.size(); ++n) + { + sources.push_back(tokens[n]); + } + }; + + auto get_ref = [](std::string_view reg) + { + ensure(reg.length() > 1, "Invalid register specifier"); + + const auto parts = fmt::split(reg, { "." }); + ensure(parts.size() > 0 && parts.size() <= 2); + + const auto index = std::stoi(parts[0].substr(1)); + RegisterRef ref + { + .reg { .id = index, .f16 = false }, + .mask = 0x0F + }; + + if (parts.size() > 1 && parts[1].length() > 0) + { + // FIXME: No swizzles for now, just lane masking + ref.mask = 0; + if (parts[1].find("x") != std::string::npos) ref.mask |= (1u << 0); + if (parts[1].find("y") != std::string::npos) ref.mask |= (1u << 1); + if (parts[1].find("z") != std::string::npos) ref.mask |= (1u << 2); + if (parts[1].find("w") != std::string::npos) ref.mask |= (1u << 3); + } + + if (reg[0] == 'H' || reg[0] == 'h') + { + ref.reg.f16 = true; + } + + return ref; + }; + + auto get_constants = [](std::string_view reg) -> std::array + { + float x, y, z, w; + if (sscanf_s(reg.data(), "#{%f|%f|%f|%f}", &x, &y, &z, &w) == 4) + { + return { x, y, z, w }; + } + + if (sscanf_s(reg.data(), "#{%f}", &x) == 1) + { + return { x, x, x, x }; + } + + fmt::throw_exception("Invalid constant literal"); + }; + + auto encode_branch_else = [](Instruction* inst, u32 end) + { + SRC1 src1{ .HEX = inst->bytecode[2] }; + src1.else_offset = static_cast(end); + inst->bytecode[2] = src1.HEX; + }; + + auto encode_branch_end = [](Instruction *inst, u32 end) + { + SRC2 src2 { .HEX = inst->bytecode[3] }; + src2.end_offset = static_cast(end); + inst->bytecode[3] = src2.HEX; + + SRC1 src1{ .HEX = inst->bytecode[2] }; + if (!src1.else_offset) + { + src1.else_offset = static_cast(end); + inst->bytecode[2] = src1.HEX; + } + }; + + auto encode_opcode = [](std::string_view op, Instruction* inst) + { + OPDEST d0 { .HEX = inst->bytecode[0] }; + SRC0 s0 { .HEX = inst->bytecode[1] }; + SRC1 s1 { .HEX = inst->bytecode[2] }; + + const auto found = s_opcode_lookup.find(op); + if (found == s_opcode_lookup.end()) + { + fmt::throw_exception("Unhandled instruction '%s'", op); + } + const auto& encoding = found->second; + + inst->opcode = encoding.op; + d0.opcode = encoding.op & 0x3F; + s1.opcode_hi = (encoding.op > 0x3F)? 1 : 0; + s0.exec_if_eq = encoding.exec_if_eq ? 1 : 0; + s0.exec_if_gr = encoding.exec_if_gt ? 1 : 0; + s0.exec_if_lt = encoding.exec_if_lt ? 1 : 0; + d0.set_cond = encoding.set_cond ? 1 : 0; + inst->bytecode[0] = d0.HEX; + inst->bytecode[1] = s0.HEX; + inst->bytecode[2] = s1.HEX; + }; + + std::string op, dst; + std::vector sources; + + std::stack if_ops; + std::stack loop_ops; + u32 pc = 0; + + FPIR ir{}; + + for (const auto& instruction : instructions) + { + op.clear(); + dst.clear(); + sources.clear(); + decode_instruction(instruction, op, dst, sources); + + if (op.empty()) + { + continue; + } + + if (op.starts_with("IF.")) + { + if_ops.push(ir.m_instructions.size()); + } + else if (op == "LOOP") + { + loop_ops.push(ir.m_instructions.size()); + } + else if (op == "ELSE") + { + ensure(!if_ops.empty()); + encode_branch_else(&ir.m_instructions[if_ops.top()], pc); + continue; + } + else if (op == "ENDIF") + { + ensure(!if_ops.empty()); + encode_branch_end(&ir.m_instructions[if_ops.top()], pc); + if_ops.pop(); + continue; + } + else if (op == "ENDLOOP") + { + ensure(!loop_ops.empty()); + encode_branch_end(&ir.m_instructions[loop_ops.top()], pc); + loop_ops.pop(); + continue; + } + + ir.m_instructions.push_back({}); + Instruction* target = &ir.m_instructions.back(); + pc += 4; + + encode_opcode(op, target); + ensure(sources.size() == FP::get_operand_count(static_cast(target->opcode)), "Invalid operand count for opcode"); + + if (dst.empty()) + { + OPDEST dst{ .HEX = target->bytecode[0] }; + dst.no_dest = 1; + target->bytecode[0] = dst.HEX; + } + else + { + ir.store(get_ref(dst), target); + } + + int operand = 0; + bool has_literal = false; + for (const auto& source : sources) + { + if (source.front() == '#') + { + const auto literal = get_constants(source); + ir.load(literal, operand++, target); + has_literal = true; + continue; + } + + ir.load(get_ref(source), operand++, target); + } + + if (has_literal) + { + pc += 4; + } + } + + if (!ir.m_instructions.empty()) + { + OPDEST d0{ .HEX = ir.m_instructions.back().bytecode[0] }; + d0.end = 1; + + ir.m_instructions.back().bytecode[0] = d0.HEX; + } + + return ir; + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.h b/rpcs3/Emu/RSX/Program/Assembler/FPASM.h new file mode 100644 index 0000000000..83fc2fb6b1 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.h @@ -0,0 +1,29 @@ +#pragma once + +#include "IR.h" + +namespace rsx::assembler +{ + class FPIR + { + public: + void mov(const RegisterRef& dst, f32 constant); + void mov(const RegisterRef& dst, const RegisterRef& src); + + void add(const RegisterRef& dst, const std::array& constants); + void add(const RegisterRef& dst, const RegisterRef& src); + + const std::vector& instructions() const; + std::vector compile() const; + + static FPIR from_source(std::string_view asm_); + + private: + Instruction* load(const RegisterRef& reg, int operand, Instruction* target = nullptr); + Instruction* load(const std::array& constants, int operand, Instruction* target = nullptr); + Instruction* store(const RegisterRef& reg, Instruction* target = nullptr); + + std::vector m_instructions; + }; +} + diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp new file mode 100644 index 0000000000..3ab4f3d893 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp @@ -0,0 +1,426 @@ +#include "stdafx.h" +#include "FPOpcodes.h" + +#include "Emu/RSX/Common/simple_array.hpp" +#include "Emu/RSX/Program/RSXFragmentProgram.h" + +#include + +namespace rsx::assembler::FP +{ + u8 get_operand_count(FP_opcode opcode) + { + switch (opcode) + { + case RSX_FP_OPCODE_NOP: + return 0; + case RSX_FP_OPCODE_MOV: + return 1; + case RSX_FP_OPCODE_MUL: + case RSX_FP_OPCODE_ADD: + return 2; + case RSX_FP_OPCODE_MAD: + return 3; + case RSX_FP_OPCODE_DP3: + case RSX_FP_OPCODE_DP4: + return 2; + case RSX_FP_OPCODE_DST: + return 2; + case RSX_FP_OPCODE_MIN: + case RSX_FP_OPCODE_MAX: + return 2; + case RSX_FP_OPCODE_SLT: + case RSX_FP_OPCODE_SGE: + case RSX_FP_OPCODE_SLE: + case RSX_FP_OPCODE_SGT: + case RSX_FP_OPCODE_SNE: + case RSX_FP_OPCODE_SEQ: + return 2; + case RSX_FP_OPCODE_FRC: + case RSX_FP_OPCODE_FLR: + return 1; + case RSX_FP_OPCODE_KIL: + return 0; + case RSX_FP_OPCODE_PK4: + case RSX_FP_OPCODE_UP4: + return 1; + case RSX_FP_OPCODE_DDX: + case RSX_FP_OPCODE_DDY: + return 1; + case RSX_FP_OPCODE_TEX: + case RSX_FP_OPCODE_TXD: + case RSX_FP_OPCODE_TXP: + return 1; + case RSX_FP_OPCODE_RCP: + case RSX_FP_OPCODE_RSQ: + case RSX_FP_OPCODE_EX2: + case RSX_FP_OPCODE_LG2: + return 1; + case RSX_FP_OPCODE_LIT: + return 1; + case RSX_FP_OPCODE_LRP: + return 3; + case RSX_FP_OPCODE_STR: + case RSX_FP_OPCODE_SFL: + return 0; + case RSX_FP_OPCODE_COS: + case RSX_FP_OPCODE_SIN: + return 1; + case RSX_FP_OPCODE_PK2: + case RSX_FP_OPCODE_UP2: + return 1; + case RSX_FP_OPCODE_PKB: + case RSX_FP_OPCODE_UPB: + case RSX_FP_OPCODE_PK16: + case RSX_FP_OPCODE_UP16: + case RSX_FP_OPCODE_PKG: + case RSX_FP_OPCODE_UPG: + return 1; + case RSX_FP_OPCODE_DP2A: + return 3; + case RSX_FP_OPCODE_TXL: + case RSX_FP_OPCODE_TXB: + return 2; + case RSX_FP_OPCODE_DP2: + return 2; + case RSX_FP_OPCODE_NRM: + return 1; + case RSX_FP_OPCODE_DIV: + case RSX_FP_OPCODE_DIVSQ: + return 2; + case RSX_FP_OPCODE_LIF: + return 1; + case RSX_FP_OPCODE_FENCT: + case RSX_FP_OPCODE_FENCB: + case RSX_FP_OPCODE_BRK: + case RSX_FP_OPCODE_CAL: + case RSX_FP_OPCODE_IFE: + case RSX_FP_OPCODE_LOOP: + case RSX_FP_OPCODE_REP: + case RSX_FP_OPCODE_RET: + // Flow control. Special registers are provided for these outside the common file + return 0; + + // The rest are unimplemented and not encountered in real software. + // TODO: Probe these on real PS3 and figure out what they actually do. + case RSX_FP_OPCODE_POW: + fmt::throw_exception("Unimplemented POW instruction."); // Unused + case RSX_FP_OPCODE_BEM: + case RSX_FP_OPCODE_TEXBEM: + case RSX_FP_OPCODE_TXPBEM: + case RSX_FP_OPCODE_BEMLUM: + fmt::throw_exception("Unimplemented BEM class instruction"); // Unused + case RSX_FP_OPCODE_REFL: + return 2; + case RSX_FP_OPCODE_TIMESWTEX: + fmt::throw_exception("Unimplemented TIMESWTEX instruction"); // Unused + default: + break; + } + + return 0; + } + + // Returns a lane mask for the given operand. + // The lane mask is the fixed function hardware lane so swizzles need to be applied on top to resolve the real data channel. + u32 get_src_vector_lane_mask(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand) + { + constexpr u32 x = 0b0001; + constexpr u32 y = 0b0010; + constexpr u32 z = 0b0100; + constexpr u32 w = 0b1000; + constexpr u32 xy = 0b0011; + constexpr u32 xyz = 0b0111; + constexpr u32 xyzw = 0b1111; + + const auto decode = [&](const rsx::simple_array& masks) -> u32 + { + return operand < masks.size() + ? masks[operand] + : 0u; + }; + + auto opcode = static_cast(instruction->opcode); + if (operand >= get_operand_count(opcode)) + { + return 0; + } + + OPDEST d0 { .HEX = instruction->bytecode[0] }; + const u32 dst_write_mask = d0.no_dest ? 0 : d0.write_mask; + + switch (opcode) + { + case RSX_FP_OPCODE_NOP: + return 0; + case RSX_FP_OPCODE_MOV: + case RSX_FP_OPCODE_MUL: + case RSX_FP_OPCODE_ADD: + case RSX_FP_OPCODE_MAD: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_DP3: + return xyz; + case RSX_FP_OPCODE_DP4: + return xyzw; + case RSX_FP_OPCODE_DST: + return decode({ y | z, y | w }); + case RSX_FP_OPCODE_MIN: + case RSX_FP_OPCODE_MAX: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_SLT: + case RSX_FP_OPCODE_SGE: + case RSX_FP_OPCODE_SLE: + case RSX_FP_OPCODE_SGT: + case RSX_FP_OPCODE_SNE: + case RSX_FP_OPCODE_SEQ: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_FRC: + case RSX_FP_OPCODE_FLR: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_KIL: + return 0; + case RSX_FP_OPCODE_PK4: + return xyzw; + case RSX_FP_OPCODE_UP4: + return x; + case RSX_FP_OPCODE_DDX: + case RSX_FP_OPCODE_DDY: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_TEX: + case RSX_FP_OPCODE_TXD: + switch (prog.get_texture_dimension(d0.tex_num)) + { + case rsx::texture_dimension_extended::texture_dimension_1d: + return x; + case rsx::texture_dimension_extended::texture_dimension_2d: + return xy; + case rsx::texture_dimension_extended::texture_dimension_3d: + case rsx::texture_dimension_extended::texture_dimension_cubemap: + return xyz; + default: + return 0; + } + case RSX_FP_OPCODE_TXP: + switch (prog.get_texture_dimension(d0.tex_num)) + { + case rsx::texture_dimension_extended::texture_dimension_1d: + return xy; + case rsx::texture_dimension_extended::texture_dimension_2d: + return xyz; + case rsx::texture_dimension_extended::texture_dimension_3d: + case rsx::texture_dimension_extended::texture_dimension_cubemap: + return xyzw; + default: + return 0; + } + case RSX_FP_OPCODE_RCP: + case RSX_FP_OPCODE_RSQ: + case RSX_FP_OPCODE_EX2: + case RSX_FP_OPCODE_LG2: + return x; + case RSX_FP_OPCODE_LIT: + return xyzw; + case RSX_FP_OPCODE_LRP: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_STR: + case RSX_FP_OPCODE_SFL: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_COS: + case RSX_FP_OPCODE_SIN: + return x; + case RSX_FP_OPCODE_PK2: + return xy; + case RSX_FP_OPCODE_UP2: + return x; + case RSX_FP_OPCODE_PKB: + return xyzw; + case RSX_FP_OPCODE_UPB: + return x; + case RSX_FP_OPCODE_PK16: + return xy; + case RSX_FP_OPCODE_UP16: + return x; + case RSX_FP_OPCODE_PKG: + return xyzw; + case RSX_FP_OPCODE_UPG: + return x; + case RSX_FP_OPCODE_DP2A: + return decode({ xy, xy, x }); + case RSX_FP_OPCODE_TXL: + case RSX_FP_OPCODE_TXB: + return decode({ xy, x }); + case RSX_FP_OPCODE_REFL: + return xyzw; + case RSX_FP_OPCODE_DP2: + return xy; + case RSX_FP_OPCODE_NRM: + return xyz; + case RSX_FP_OPCODE_DIV: + case RSX_FP_OPCODE_DIVSQ: + return decode({ xyzw, x }) & dst_write_mask; + case RSX_FP_OPCODE_LIF: + return decode({ y | w }); + case RSX_FP_OPCODE_FENCT: + case RSX_FP_OPCODE_FENCB: + case RSX_FP_OPCODE_BRK: + case RSX_FP_OPCODE_CAL: + case RSX_FP_OPCODE_IFE: + case RSX_FP_OPCODE_LOOP: + case RSX_FP_OPCODE_REP: + case RSX_FP_OPCODE_RET: + // Flow control. Special registers are provided for these outside the common file + return 0; + + case RSX_FP_OPCODE_POW: + fmt::throw_exception("Unimplemented POW instruction."); // Unused ?? + case RSX_FP_OPCODE_BEM: + case RSX_FP_OPCODE_TEXBEM: + case RSX_FP_OPCODE_TXPBEM: + case RSX_FP_OPCODE_BEMLUM: + fmt::throw_exception("Unimplemented BEM class instruction"); // Unused + case RSX_FP_OPCODE_TIMESWTEX: + fmt::throw_exception("Unimplemented TIMESWTEX instruction"); // Unused + default: + break; + } + + return 0; + } + + // Resolved vector lane mask with swizzles applied. + u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand) + { + // Brute-force this. There's only 16 permutations. + constexpr u32 x = 0b0001; + constexpr u32 y = 0b0010; + constexpr u32 z = 0b0100; + constexpr u32 w = 0b1000; + + const u32 lane_mask = get_src_vector_lane_mask(prog, instruction, operand); + if (!lane_mask) + { + return lane_mask; + } + + // Now we resolve matching lanes. + // This sequence can be drastically sped up using lookup tables but that will come later. + std::unordered_set inputs; + SRC_Common src { .HEX = instruction->bytecode[operand + 1] }; + + if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP) + { + return 0; + } + + if (lane_mask & x) inputs.insert(src.swizzle_x); + if (lane_mask & y) inputs.insert(src.swizzle_y); + if (lane_mask & z) inputs.insert(src.swizzle_z); + if (lane_mask & w) inputs.insert(src.swizzle_w); + + u32 result = 0; + if (inputs.contains(0)) result |= x; + if (inputs.contains(1)) result |= y; + if (inputs.contains(2)) result |= z; + if (inputs.contains(3)) result |= w; + + return result; + } + + bool is_delay_slot(const Instruction* instruction) + { + OPDEST dst { .HEX = instruction->bytecode[0] }; + SRC0 src0 { .HEX = instruction->bytecode[1] }; + SRC1 src1{ .HEX = instruction->bytecode[2] }; + + if (dst.opcode != RSX_FP_OPCODE_MOV || // These slots are always populated with MOV + dst.no_dest || // Must have a sink + src0.reg_type != RSX_FP_REGISTER_TYPE_TEMP || // Must read from reg + dst.dest_reg != src0.tmp_reg_index || // Must be a write-to-self + dst.fp16 || // Always full lane. We need to collect more data on this but it won't matter + dst.saturate || // Precision modifier + (dst.prec != RSX_FP_PRECISION_REAL && + dst.prec != RSX_FP_PRECISION_UNKNOWN)) // Cannot have precision modifiers + { + return false; + } + + // Check if we have precision modifiers on the source + if (src0.abs || src0.neg || src1.scale) + { + return false; + } + + if (dst.mask_x && src0.swizzle_x != 0) return false; + if (dst.mask_y && src0.swizzle_y != 1) return false; + if (dst.mask_z && src0.swizzle_z != 2) return false; + if (dst.mask_w && src0.swizzle_w != 3) return false; + + return true; + } + + RegisterRef get_src_register(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand) + { + SRC_Common src{ .HEX = instruction->bytecode[operand + 1] }; + if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP) + { + return {}; + } + + const u32 read_lanes = get_src_vector_lane_mask_shuffled(prog, instruction, operand); + if (!read_lanes) + { + return {}; + } + + RegisterRef ref{ .mask = read_lanes }; + Register& reg = ref.reg; + + reg.f16 = !!src.fp16; + reg.id = src.tmp_reg_index; + return ref; + } + + RegisterRef get_dst_register(const Instruction* instruction) + { + OPDEST dst { .HEX = instruction->bytecode[0] }; + if (dst.no_dest) + { + return {}; + } + + RegisterRef ref{ .mask = dst.write_mask }; + ref.reg.f16 = dst.fp16; + ref.reg.id = dst.dest_reg; + return ref; + } + + // Convert vector mask to file range + rsx::simple_array get_register_file_range(const RegisterRef& reg) + { + if (!reg.mask) + { + return {}; + } + + const u32 lane_width = reg.reg.f16 ? 2 : 4; + const u32 file_offset = reg.reg.id * lane_width * 4; + + ensure(file_offset < constants::register_file_max_len, "Invalid register index"); + + rsx::simple_array result{}; + auto insert_lane = [&](u32 word_offset) + { + for (u32 i = 0; i < lane_width; ++i) + { + result.push_back(file_offset + (word_offset * lane_width) + i); + } + }; + + if (reg.x) insert_lane(0); + if (reg.y) insert_lane(1); + if (reg.z) insert_lane(2); + if (reg.w) insert_lane(3); + + return result; + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h new file mode 100644 index 0000000000..1de0985a35 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h @@ -0,0 +1,126 @@ +#pragma once + +#include "IR.h" +#include "Emu/RSX/Common/simple_array.hpp" + +struct RSXFragmentProgram; + +namespace rsx::assembler +{ + enum FP_opcode + { + RSX_FP_OPCODE_NOP = 0x00, // No-Operation + RSX_FP_OPCODE_MOV = 0x01, // Move + RSX_FP_OPCODE_MUL = 0x02, // Multiply + RSX_FP_OPCODE_ADD = 0x03, // Add + RSX_FP_OPCODE_MAD = 0x04, // Multiply-Add + RSX_FP_OPCODE_DP3 = 0x05, // 3-component Dot Product + RSX_FP_OPCODE_DP4 = 0x06, // 4-component Dot Product + RSX_FP_OPCODE_DST = 0x07, // Distance + RSX_FP_OPCODE_MIN = 0x08, // Minimum + RSX_FP_OPCODE_MAX = 0x09, // Maximum + RSX_FP_OPCODE_SLT = 0x0A, // Set-If-LessThan + RSX_FP_OPCODE_SGE = 0x0B, // Set-If-GreaterEqual + RSX_FP_OPCODE_SLE = 0x0C, // Set-If-LessEqual + RSX_FP_OPCODE_SGT = 0x0D, // Set-If-GreaterThan + RSX_FP_OPCODE_SNE = 0x0E, // Set-If-NotEqual + RSX_FP_OPCODE_SEQ = 0x0F, // Set-If-Equal + RSX_FP_OPCODE_FRC = 0x10, // Fraction (fract) + RSX_FP_OPCODE_FLR = 0x11, // Floor + RSX_FP_OPCODE_KIL = 0x12, // Kill fragment + RSX_FP_OPCODE_PK4 = 0x13, // Pack four signed 8-bit values + RSX_FP_OPCODE_UP4 = 0x14, // Unpack four signed 8-bit values + RSX_FP_OPCODE_DDX = 0x15, // Partial-derivative in x (Screen space derivative w.r.t. x) + RSX_FP_OPCODE_DDY = 0x16, // Partial-derivative in y (Screen space derivative w.r.t. y) + RSX_FP_OPCODE_TEX = 0x17, // Texture lookup + RSX_FP_OPCODE_TXP = 0x18, // Texture sample with projection (Projective texture lookup) + RSX_FP_OPCODE_TXD = 0x19, // Texture sample with partial differentiation (Texture lookup with derivatives) + RSX_FP_OPCODE_RCP = 0x1A, // Reciprocal + RSX_FP_OPCODE_RSQ = 0x1B, // Reciprocal Square Root + RSX_FP_OPCODE_EX2 = 0x1C, // Exponentiation base 2 + RSX_FP_OPCODE_LG2 = 0x1D, // Log base 2 + RSX_FP_OPCODE_LIT = 0x1E, // Lighting coefficients + RSX_FP_OPCODE_LRP = 0x1F, // Linear Interpolation + RSX_FP_OPCODE_STR = 0x20, // Set-If-True + RSX_FP_OPCODE_SFL = 0x21, // Set-If-False + RSX_FP_OPCODE_COS = 0x22, // Cosine + RSX_FP_OPCODE_SIN = 0x23, // Sine + RSX_FP_OPCODE_PK2 = 0x24, // Pack two 16-bit floats + RSX_FP_OPCODE_UP2 = 0x25, // Unpack two 16-bit floats + RSX_FP_OPCODE_POW = 0x26, // Power + RSX_FP_OPCODE_PKB = 0x27, // Pack bytes + RSX_FP_OPCODE_UPB = 0x28, // Unpack bytes + RSX_FP_OPCODE_PK16 = 0x29, // Pack 16 bits + RSX_FP_OPCODE_UP16 = 0x2A, // Unpack 16 + RSX_FP_OPCODE_BEM = 0x2B, // Bump-environment map (a.k.a. 2D coordinate transform) + RSX_FP_OPCODE_PKG = 0x2C, // Pack with sRGB transformation + RSX_FP_OPCODE_UPG = 0x2D, // Unpack gamma + RSX_FP_OPCODE_DP2A = 0x2E, // 2-component dot product with scalar addition + RSX_FP_OPCODE_TXL = 0x2F, // Texture sample with explicit LOD + RSX_FP_OPCODE_TXB = 0x31, // Texture sample with bias + RSX_FP_OPCODE_TEXBEM = 0x33, + RSX_FP_OPCODE_TXPBEM = 0x34, + RSX_FP_OPCODE_BEMLUM = 0x35, + RSX_FP_OPCODE_REFL = 0x36, // Reflection vector + RSX_FP_OPCODE_TIMESWTEX = 0x37, + RSX_FP_OPCODE_DP2 = 0x38, // 2-component dot product + RSX_FP_OPCODE_NRM = 0x39, // Normalize + RSX_FP_OPCODE_DIV = 0x3A, // Division + RSX_FP_OPCODE_DIVSQ = 0x3B, // Divide by Square Root + RSX_FP_OPCODE_LIF = 0x3C, // Final part of LIT + RSX_FP_OPCODE_FENCT = 0x3D, // Fence T? + RSX_FP_OPCODE_FENCB = 0x3E, // Fence B? + RSX_FP_OPCODE_BRK = 0x40, // Break + RSX_FP_OPCODE_CAL = 0x41, // Subroutine call + RSX_FP_OPCODE_IFE = 0x42, // If + RSX_FP_OPCODE_LOOP = 0x43, // Loop + RSX_FP_OPCODE_REP = 0x44, // Repeat + RSX_FP_OPCODE_RET = 0x45, // Return + + + // Custom opcodes for dependency injection + RSX_FP_OPCODE_OR16_LO = 0x46, // Performs a 16-bit OR, taking one register channel as input and overwriting low 16 bits of the output + RSX_FP_OPCODE_OR16_HI = 0x47, // Same as the lo variant but now overwrites the high 16-bit block + }; + + namespace FP + { + namespace constants + { + // The ISA can encode for 48 registers of any width. + // This allows to encode R0-R64 and H0-H95, though there aren't enough addressing bits for the latter. + constexpr u32 register_file_max_len = 48 * 16; + + // Enums for analysis passes. + constexpr char content_unknown = 0; + constexpr char content_float32 = 'R'; + constexpr char content_float16 = 'H'; + constexpr char content_dual = 'D'; + } + + using register_file_t = std::array; + + // Returns number of operands consumed by an instruction + u8 get_operand_count(FP_opcode opcode); + + // Returns a lane mask for the given operand. + // The lane mask is the fixed function hardware lane so swizzles need to be applied on top to resolve the real data channel. + u32 get_src_vector_lane_mask(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand); + + // Resolved vector lane mask with swizzles applied. + u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand); + + // Returns true on delay slot instructions. + bool is_delay_slot(const Instruction* instruction); + + // Generate register references + RegisterRef get_src_register(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand); + RegisterRef get_dst_register(const Instruction* instruction); + + // Convert vector mask to file ranges + rsx::simple_array get_register_file_range(const RegisterRef& reg); + + // Compile a register file annotated blob to register references + std::vector compile_register_file(const std::array& file); + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index d8de4eda0b..99e7c56f6e 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -1,5 +1,4 @@ #include "stdafx.h" - #include "CFG.h" #include "Emu/RSX/Common/simple_array.hpp" @@ -75,8 +74,19 @@ namespace rsx::assembler { if (auto found = find_block_for_pc(id)) { - parent->insert_succ(found, edge_type); - found->insert_pred(parent, edge_type); + auto succ = found; + if (found->is_of_type(EdgeType::ELSE) && + (edge_type == EdgeType::ENDIF || edge_type == EdgeType::ENDLOOP)) + { + // If we landed on an "ELSE" node, link to its "ENDIF" counterpart + auto if_parent = found->pred.front().from; + auto endif_edge = std::find_if(if_parent->succ.begin(), if_parent->succ.end(), FN(x.type == EdgeType::ENDIF)); + ensure(endif_edge != if_parent->succ.end(), "CFG: Invalid ELSE node"); + succ = endif_edge->to; + } + + parent->insert_succ(succ, edge_type); + succ->insert_pred(parent, edge_type); return found; } @@ -101,6 +111,43 @@ namespace rsx::assembler if (found) { + auto front_edge = std::find_if(bb->pred.begin(), bb->pred.end(), FN(x.type != EdgeType::ENDIF && x.type != EdgeType::ENDLOOP)); + if (front_edge != bb->pred.end()) + { + auto parent = ensure(front_edge->from); + switch (front_edge->type) + { + case EdgeType::IF: + case EdgeType::ELSE: + { + // Find the merge node from the parent. + auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDIF)); + ensure(succ != parent->succ.end(), "CFG: Broken IF linkage. Please report to developers."); + bb->insert_succ(succ->to, EdgeType::ENDIF); + succ->to->insert_pred(bb, EdgeType::ENDIF); + break; + } + case EdgeType::LOOP: + { + // Find the merge node from the parent + auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDLOOP)); + ensure(succ != parent->succ.end(), "CFG: Broken LOOP linkage. Please report to developers."); + bb->insert_succ(succ->to, EdgeType::ENDLOOP); + succ->to->insert_pred(bb, EdgeType::ENDLOOP); + break; + } + default: + // Missing an edge type? + rsx_log.error("CFG: Unexpected block exit. Report to developers."); + break; + } + } + else if (bb->pred.empty()) + { + // Impossible situation. + rsx_log.error("CFG: Child block has no parent but has successor! Report to developers."); + } + bb = *found; } @@ -113,7 +160,7 @@ namespace rsx::assembler src2.HEX = decoded._u32[3]; end = !!dst.end; - const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6); + const u32 opcode = dst.opcode | (src1.opcode_hi << 6); if (opcode == RSX_FP_OPCODE_NOP) { @@ -126,6 +173,7 @@ namespace rsx::assembler std::memcpy(ir_inst.bytecode, &decoded._u32[0], 16); ir_inst.length = 4; ir_inst.addr = pc * 16; + ir_inst.opcode = opcode; switch (opcode) { @@ -146,22 +194,45 @@ namespace rsx::assembler case RSX_FP_OPCODE_IFE: { // Inserts if and else and end blocks - auto parent = bb; - bb = safe_insert_block(parent, pc + 1, EdgeType::IF); - if (src2.end_offset != src1.else_offset) + const u32 end_addr = src2.end_offset >> 2u; + const u32 else_addr = src1.else_offset >> 2u; + if (end_addr == pc + 1u) { - else_blocks.push_back(safe_insert_block(parent, src1.else_offset >> 2, EdgeType::ELSE)); + // NOP. Empty IF block + bb->instructions.pop_back(); + break; } - end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDIF)); + + if (else_addr > end_addr) + { + // Our systems support this, but it is not verified on real hardware. + rsx_log.error("CFG: Non-contiguous branch detected. Report to developers."); + } + + auto parent = bb; + bb = safe_insert_block(parent, pc + 1u, EdgeType::IF); + if (end_addr != else_addr) + { + else_blocks.push_back(safe_insert_block(parent, else_addr, EdgeType::ELSE)); + } + end_blocks.push_back(safe_insert_block(parent, end_addr, EdgeType::ENDIF)); break; } case RSX_FP_OPCODE_LOOP: case RSX_FP_OPCODE_REP: { // Inserts for and end blocks + const u32 end_addr = src2.end_offset >> 2u; + if (end_addr == pc + 1u) + { + // NOP. Empty LOOP block + bb->instructions.pop_back(); + break; + } + auto parent = bb; - bb = safe_insert_block(parent, pc + 1, EdgeType::LOOP); - end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDLOOP)); + bb = safe_insert_block(parent, pc + 1u, EdgeType::LOOP); + end_blocks.push_back(safe_insert_block(parent, end_addr, EdgeType::ENDLOOP)); break; } default: @@ -174,6 +245,7 @@ namespace rsx::assembler ir_inst.length += 4; pc++; } + break; } pc++; diff --git a/rpcs3/Emu/RSX/Program/Assembler/IR.h b/rpcs3/Emu/RSX/Program/Assembler/IR.h index 65960f3d99..635aec7209 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/IR.h +++ b/rpcs3/Emu/RSX/Program/Assembler/IR.h @@ -10,6 +10,16 @@ namespace rsx::assembler { int id = 0; bool f16 = false; + + bool operator == (const Register& other) const + { + return id == other.id && f16 == other.f16; + } + + std::string to_string() const + { + return std::string(f16 ? "H" : "R") + std::to_string(id); + } }; struct RegisterRef @@ -19,7 +29,7 @@ namespace rsx::assembler // Vector information union { - u32 mask; + u32 mask = 0; struct { @@ -29,6 +39,16 @@ namespace rsx::assembler bool w : 1; }; }; + + operator bool() const + { + return !!mask; + } + + bool operator == (const RegisterRef& other) const + { + return reg == other.reg && mask == other.mask; + } }; struct Instruction @@ -71,6 +91,7 @@ namespace rsx::assembler struct BasicBlock { u32 id = 0; + std::vector instructions; // Program instructions for the RSX processor std::vector succ; // Forward edges. Sorted closest first. std::vector pred; // Back edges. Sorted closest first. @@ -78,6 +99,9 @@ namespace rsx::assembler std::vector prologue; // Prologue, created by passes std::vector epilogue; // Epilogue, created by passes + std::vector input_list; // Register inputs. + std::vector clobber_list; // Clobbered outputs + FlowEdge* insert_succ(BasicBlock* b, EdgeType type = EdgeType::NONE) { FlowEdge e{ .type = type, .from = this, .to = b }; @@ -91,5 +115,25 @@ namespace rsx::assembler pred.push_back(e); return &pred.back(); } + + bool is_of_type(EdgeType type) const + { + return pred.size() == 1 && + pred.front().type == type; + } + + bool has_sibling_of_type(EdgeType type) const + { + if (pred.size() != 1) + { + return false; + } + + auto source_node = pred.front().from; + return std::find_if( + source_node->succ.begin(), + source_node->succ.end(), + FN(x.type == type)) != source_node->succ.end(); + } }; } diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp new file mode 100644 index 0000000000..1b34f53091 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp @@ -0,0 +1,226 @@ +#include "stdafx.h" + +#include "RegisterAnnotationPass.h" +#include "Emu/RSX/Program/Assembler/FPOpcodes.h" +#include "Emu/RSX/Program/RSXFragmentProgram.h" + +#include +#include + +namespace rsx::assembler::FP +{ + using namespace constants; + + bool is_delay_slot(const Instruction& instruction) + { + const OPDEST dst{ .HEX = instruction.bytecode[0] }; + const SRC0 src0{ .HEX = instruction.bytecode[1] }; + const SRC1 src1{ .HEX = instruction.bytecode[2] }; + + if (dst.opcode != RSX_FP_OPCODE_MOV || // These slots are always populated with MOV + dst.no_dest || // Must have a sink + src0.reg_type != RSX_FP_REGISTER_TYPE_TEMP || // Must read from reg + dst.dest_reg != src0.tmp_reg_index || // Must be a write-to-self + dst.fp16 != src0.fp16 || // Must really be the same register + src0.abs || src0.neg || + dst.saturate) // Precision modifier + { + return false; + } + + switch (dst.prec) + { + case RSX_FP_PRECISION_REAL: + case RSX_FP_PRECISION_UNKNOWN: + break; + case RSX_FP_PRECISION_HALF: + if (!src0.fp16) return false; + break; + case RSX_FP_PRECISION_FIXED12: + case RSX_FP_PRECISION_FIXED9: + case RSX_FP_PRECISION_SATURATE: + return false; + } + + // Check if we have precision modifiers on the source + if (src0.abs || src0.neg || src1.scale) + { + return false; + } + + if (dst.mask_x && src0.swizzle_x != 0) return false; + if (dst.mask_y && src0.swizzle_y != 1) return false; + if (dst.mask_z && src0.swizzle_z != 2) return false; + if (dst.mask_w && src0.swizzle_w != 3) return false; + + return true; + } + + std::vector compile_register_file(const register_file_t& file) + { + std::vector results; + + // F16 register processing + for (int reg16 = 0; reg16 < 48; ++reg16) + { + const u32 offset = reg16 * 8; + auto word = *reinterpret_cast(&file[offset]); + + if (!word) [[ likely ]] + { + // Trivial rejection, very commonly hit. + continue; + } + + RegisterRef ref{ .reg {.id = reg16, .f16 = true } }; + ref.x = (file[offset] == content_dual || file[offset] == content_float16); + ref.y = (file[offset + 2] == content_dual || file[offset + 2] == content_float16); + ref.z = (file[offset + 4] == content_dual || file[offset + 4] == content_float16); + ref.w = (file[offset + 6] == content_dual || file[offset + 6] == content_float16); + + if (ref) + { + results.push_back(std::move(ref)); + } + } + + // Helper to check a span for 32-bit access + auto match_any_32 = [](const std::span lanes) + { + return std::any_of(lanes.begin(), lanes.end(), FN(x == content_dual || x == content_float32)); + }; + + // F32 register processing + for (int reg32 = 0; reg32 < 24; ++reg32) + { + const u32 offset = reg32 * 16; + auto word0 = *reinterpret_cast(&file[offset]); + auto word1 = *reinterpret_cast(&file[offset + 8]); + + if (!word0 && !word1) [[ likely ]] + { + // Trivial rejection, very commonly hit. + continue; + } + + RegisterRef ref{ .reg {.id = reg32, .f16 = false } }; + if (word0) + { + ref.x = match_any_32({ &file[offset], 4 }); + ref.y = match_any_32({ &file[offset + 4], 4 }); + } + + if (word1) + { + ref.z = match_any_32({ &file[offset + 8], 4 }); + ref.w = match_any_32({ &file[offset + 12], 4 }); + } + + if (ref) + { + results.push_back(std::move(ref)); + } + } + + return results; + } + + // Decay instructions into register references + void annotate_instructions(BasicBlock* block, const RSXFragmentProgram& prog, bool skip_delay_slots) + { + for (auto& instruction : block->instructions) + { + if (skip_delay_slots && is_delay_slot(instruction)) + { + continue; + } + + const u32 operand_count = get_operand_count(static_cast(instruction.opcode)); + for (u32 i = 0; i < operand_count; i++) + { + RegisterRef reg = get_src_register(prog, &instruction, i); + if (!reg.mask) + { + // Likely a literal constant + continue; + } + + instruction.srcs.push_back(std::move(reg)); + } + + RegisterRef dst = get_dst_register(&instruction); + if (dst) + { + instruction.dsts.push_back(std::move(dst)); + } + } + } + + // Annotate each block with input and output lanes (read and clobber list) + void annotate_block_io(BasicBlock* block) + { + alignas(16) register_file_t output_register_file; + alignas(16) register_file_t input_register_file; // We'll eventually replace with a bitfield mask, but for ease of debugging, we use char for now + + std::memset(output_register_file.data(), content_unknown, register_file_max_len); + std::memset(input_register_file.data(), content_unknown, register_file_max_len); + + for (const auto& instruction : block->instructions) + { + for (const auto& src : instruction.srcs) + { + const auto read_bytes = get_register_file_range(src); + const char expected_type = src.reg.f16 ? content_float16 : content_float32; + for (const auto& index : read_bytes) + { + if (output_register_file[index] != content_unknown) + { + // Something already wrote to this lane + continue; + } + + if (input_register_file[index] == expected_type) + { + // We already know about this input + continue; + } + + if (input_register_file[index] == 0) + { + // Not known, tag as input + input_register_file[index] = expected_type; + continue; + } + + // Collision on the lane + input_register_file[index] = content_dual; + } + } + + if (!instruction.dsts.empty()) + { + const auto& dst = instruction.dsts.front(); + const auto write_bytes = get_register_file_range(dst); + const char expected_type = dst.reg.f16 ? content_float16 : content_float32; + + for (const auto& index : write_bytes) + { + output_register_file[index] = expected_type; + } + } + } + + // Compile the input and output refs into register references + block->clobber_list = compile_register_file(output_register_file); + block->input_list = compile_register_file(input_register_file); + } + + void RegisterAnnotationPass::run(FlowGraph& graph) + { + for (auto& block : graph.blocks) + { + annotate_instructions(&block, m_prog, m_config.skip_delay_slots); + annotate_block_io(&block); + } + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h new file mode 100644 index 0000000000..b5cab3da85 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../../CFG.h" + +struct RSXFragmentProgram; + +namespace rsx::assembler::FP +{ + struct RegisterAnnotationPassOptions + { + bool skip_delay_slots = false; // When enabled, detect delay slots and ignore annotating them. + }; + + // The annotation pass annotates each basic block with 2 pieces of information: + // 1. The "input" register list for a block. + // 2. The "output" register list for a block (clobber list). + // The information can be used by other passes to set up prologue/epilogue on each block. + // The pass also populates register reference members of each instruction, such as the input and output lanes. + class RegisterAnnotationPass : public CFGPass + { + public: + RegisterAnnotationPass( + const RSXFragmentProgram& prog, + const RegisterAnnotationPassOptions& options = {}) + : m_prog(prog), m_config(options) + {} + + void run(FlowGraph& graph) override; + + private: + const RSXFragmentProgram& m_prog; + RegisterAnnotationPassOptions m_config; + }; +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp new file mode 100644 index 0000000000..27fcd488a7 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -0,0 +1,484 @@ +#include "stdafx.h" + +#include "RegisterDependencyPass.h" +#include "Emu/RSX/Program/Assembler/FPOpcodes.h" +#include "Emu/RSX/Program/RSXFragmentProgram.h" + +#include +#include + +namespace rsx::assembler::FP +{ + using namespace constants; + + struct DependencyPassContext + { + std::unordered_map exec_register_map; + std::unordered_map sync_register_map; + }; + + enum Register32BarrierFlags + { + NONE = 0, + OR_WORD0 = 1, + OR_WORD1 = 2, + DEFAULT = OR_WORD0 | OR_WORD1 + }; + + struct RegisterBarrier32 + { + RegisterRef ref; + u32 flags[4]; + }; + + std::vector decode_lanes16(const std::unordered_set& lanes) + { + std::vector result; + + for (u32 index = 0, file_offset = 0; index < 48; ++index, file_offset += 8) + { + // Each register has 4 16-bit lanes + u32 mask = 0; + if (lanes.contains(file_offset + 0)) mask |= (1u << 0); + if (lanes.contains(file_offset + 2)) mask |= (1u << 1); + if (lanes.contains(file_offset + 4)) mask |= (1u << 2); + if (lanes.contains(file_offset + 6)) mask |= (1u << 3); + + if (mask == 0) + { + continue; + } + + RegisterRef ref{ .reg{.id = static_cast(index), .f16 = true } }; + ref.mask = mask; + result.push_back(std::move(ref)); + } + return result; + } + + std::vector decode_lanes32(const std::unordered_set& lanes) + { + std::vector result; + + for (u32 index = 0, file_offset = 0; index < 48; ++index, file_offset += 16) + { + // Each register has 8 16-bit lanes + RegisterBarrier32 barrier{}; + auto& ref = barrier.ref; + + for (u32 lane = 0; lane < 16; lane += 2) + { + if (!lanes.contains(file_offset + lane)) + { + continue; + } + + const u32 ch = (lane / 4); + const u32 flags = (lane & 3) + ? Register32BarrierFlags::OR_WORD1 + : Register32BarrierFlags::OR_WORD0; + + ref.mask |= (1u << ch); + barrier.flags[ch] |= flags; + } + + if (ref.mask == 0) + { + continue; + } + + ref.reg = {.id = static_cast(index), .f16 = false }; + result.push_back(std::move(barrier)); + } + + return result; + } + + std::vector build_barrier32(const RegisterBarrier32& barrier) + { + // Upto 4 instructions are needed per 32-bit register + // R0.x = packHalf2x16(H0.xy) + // R0.y = packHalf2x16(H0.zw); + // R0.z = packHalf2x16(H1.xy); + // R0.w = packHalf2x16(H1.zw); + + std::vector result; + + for (u32 mask = barrier.ref.mask, ch = 0; mask > 0; mask >>= 1, ++ch) + { + if (!(mask & 1)) + { + continue; + } + + const auto& reg = barrier.ref.reg; + const auto reg_id = reg.id; + + Instruction instruction{}; + OPDEST dst{}; + dst.prec = RSX_FP_PRECISION_REAL; + dst.fp16 = 0; + dst.dest_reg = reg_id; + dst.write_mask = (1u << ch); + + const u32 src_reg_id = (ch / 2) + (reg_id * 2); + const bool is_word0 = !(ch & 1); // Only even + + SRC0 src0{}; + if (is_word0) + { + src0.swizzle_x = 0; + src0.swizzle_y = 1; + } + else + { + src0.swizzle_x = 2; + src0.swizzle_y = 3; + } + + src0.swizzle_z = 2; + src0.swizzle_w = 3; + src0.reg_type = RSX_FP_REGISTER_TYPE_TEMP; + src0.tmp_reg_index = src_reg_id; + src0.fp16 = 1; + + // Prepare source 1 to match the output in case we need to encode an OR + SRC1 src1{}; + src1.reg_type = RSX_FP_REGISTER_TYPE_TEMP; + src1.tmp_reg_index = reg_id; + src1.swizzle_x = ch; + src1.swizzle_y = ch; + src1.swizzle_z = ch; + src1.swizzle_w = ch; + + u32 opcode = 0; + switch (barrier.flags[ch]) + { + case Register32BarrierFlags::DEFAULT: + opcode = RSX_FP_OPCODE_PK2; + break; + case Register32BarrierFlags::OR_WORD0: + opcode = RSX_FP_OPCODE_OR16_LO; + // Swap inputs + std::swap(src0.HEX, src1.HEX); + break; + case Register32BarrierFlags::OR_WORD1: + opcode = RSX_FP_OPCODE_OR16_HI; + src0.swizzle_x = src0.swizzle_y; + std::swap(src0.HEX, src1.HEX); + break; + case Register32BarrierFlags::NONE: + default: + fmt::throw_exception("Unexpected lane barrier with no mask."); + } + + dst.opcode = opcode & 0x3F; + src1.opcode_hi = (opcode > 0x3F) ? 1 : 0; + src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1; + + instruction.opcode = opcode; + instruction.bytecode[0] = dst.HEX; + instruction.bytecode[1] = src0.HEX; + instruction.bytecode[2] = src1.HEX; + + Register src_reg{ .id = static_cast(src_reg_id), .f16 = true }; + instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF }); + instruction.dsts.push_back({ .reg{ .id = reg_id, .f16 = false }, .mask = (1u << ch) }); + result.push_back(std::move(instruction)); + } + + return result; + } + + std::vector build_barrier16(const RegisterRef& reg) + { + // H0.xy = unpackHalf2x16(R0.x) + // H0.zw = unpackHalf2x16(R0.y) + // H1.xy = unpackHalf2x16(R0.z) + // H1.zw = unpackHalf2x16(R0.w) + + std::vector result; + + for (u32 mask = reg.mask, ch = 0; mask > 0; mask >>= 1, ++ch) + { + if (!(mask & 1)) + { + continue; + } + + Instruction instruction{}; + OPDEST dst{}; + dst.opcode = RSX_FP_OPCODE_UP2; + dst.prec = RSX_FP_PRECISION_HALF; + dst.fp16 = 1; + dst.dest_reg = reg.reg.id; + dst.write_mask = 1u << ch; + + const u32 src_reg_id = reg.reg.id / 2; + const bool is_odd_reg = !!(reg.reg.id & 1); + const bool is_odd_ch = !!(ch & 1); + const bool is_word0 = ch < 2; + + // If we're an even channel, we should also write the next channel (y/w) + if (!is_odd_ch && (mask & 2)) + { + mask >>= 1; + ++ch; + dst.write_mask |= (1u << ch); + } + + SRC0 src0{}; + src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1; + + if (is_word0) + { + src0.swizzle_x = is_odd_reg ? 2 : 0; + } + else + { + src0.swizzle_x = is_odd_reg ? 3 : 1; + } + + src0.swizzle_y = 1; + src0.swizzle_z = 2; + src0.swizzle_w = 3; + src0.reg_type = RSX_FP_REGISTER_TYPE_TEMP; + src0.tmp_reg_index = src_reg_id; + + instruction.opcode = dst.opcode; + instruction.bytecode[0] = dst.HEX; + instruction.bytecode[1] = src0.HEX; + + Register src_reg{ .id = static_cast(src_reg_id), .f16 = true }; + instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF }); + instruction.dsts.push_back({ .reg{.id = reg.reg.id, .f16 = false }, .mask = dst.write_mask }); + result.push_back(std::move(instruction)); + } + + return result; + } + + std::vector resolve_dependencies(const std::unordered_set& lanes, bool f16) + { + std::vector result; + + if (f16) + { + const auto regs = decode_lanes16(lanes); + for (const auto& ref : regs) + { + auto instructions = build_barrier16(ref); + result.insert(result.end(), instructions.begin(), instructions.end()); + } + + return result; + } + + const auto barriers = decode_lanes32(lanes); + for (const auto& barrier : barriers) + { + auto instructions = build_barrier32(barrier); + result.insert(result.end(), std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end())); + } + + return result; + } + + void insert_dependency_barriers(DependencyPassContext& ctx, BasicBlock* block) + { + register_file_t& register_file = ctx.exec_register_map[block]; + std::memset(register_file.data(), content_unknown, register_file_max_len); + + std::unordered_set barrier16; + std::unordered_set barrier32; + + // This subpass does not care about the prologue and epilogue and assumes each block is unique. + for (auto it = block->instructions.begin(); it != block->instructions.end(); ++it) + { + auto& inst = *it; + + barrier16.clear(); + barrier32.clear(); + + for (const auto& src : inst.srcs) + { + const auto read_bytes = get_register_file_range(src); + const char expected_type = src.reg.f16 ? content_float16 : content_float32; + for (const auto& index : read_bytes) + { + if (register_file[index] == content_unknown) + { + // Skip input + continue; + } + + if (register_file[index] == expected_type || register_file[index] == content_dual) + { + // Match - nothing to do + continue; + } + + // Collision on the lane + register_file[index] = content_dual; + (src.reg.f16 ? barrier16 : barrier32).insert(index); + } + } + + for (const auto& dst : inst.dsts) + { + const auto write_bytes = get_register_file_range(dst); + const char expected_type = dst.reg.f16 ? content_float16 : content_float32; + + for (const auto& index : write_bytes) + { + register_file[index] = expected_type; + } + } + + // We need to inject some barrier instructions + if (!barrier16.empty()) + { + auto barrier16_in = decode_lanes16(barrier16); + std::vector instructions; + instructions.reserve(barrier16_in.size()); + + for (const auto& reg : barrier16_in) + { + auto barrier = build_barrier16(reg); + instructions.insert(instructions.end(), std::make_move_iterator(barrier.begin()), std::make_move_iterator(barrier.end())); + } + + it = block->instructions.insert(it, std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end())); + std::advance(it, instructions.size()); + } + + if (!barrier32.empty()) + { + auto barrier32_in = decode_lanes32(barrier32); + std::vector instructions; + instructions.reserve(barrier32_in.size()); + + for (const auto& reg : barrier32_in) + { + auto barrier = build_barrier32(reg); + instructions.insert(instructions.end(), std::make_move_iterator(barrier.begin()), std::make_move_iterator(barrier.end())); + } + + it = block->instructions.insert(it, std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end())); + std::advance(it, instructions.size()); + } + } + } + + void insert_block_register_dependency(DependencyPassContext& ctx, BasicBlock* block, const std::unordered_set& lanes, bool f16) + { + std::unordered_set clobbered_lanes; + std::unordered_set lanes_to_search; + + for (auto& back_edge : block->pred) + { + auto target = back_edge.from; + + // Quick check - if we've reached an IF-ELSE anchor, don't traverse upwards. + // The IF and ELSE edges are already a complete set and will bre processed before this node. + if (back_edge.type == EdgeType::ENDIF && + &back_edge == &block->pred.back() && + target->succ.size() == 3 && + target->succ[1].type == EdgeType::ELSE && + target->succ[2].type == EdgeType::ENDIF && + target->succ[2].to == block) + { + return; + } + + // Did this target even clobber our register? + ensure(ctx.exec_register_map.find(target) != ctx.exec_register_map.end(), "Block has not been pre-processed"); + + if (ctx.sync_register_map.find(target) == ctx.sync_register_map.end()) + { + auto& blob = ctx.sync_register_map[target]; + std::memset(blob.data(), content_unknown, register_file_max_len); + } + + auto& sync_register_file = ctx.sync_register_map[target]; + const auto& exec_register_file = ctx.exec_register_map[target]; + const auto clobber_type = f16 ? content_float32 : content_float16; + + lanes_to_search.clear(); + clobbered_lanes.clear(); + + for (auto& lane : lanes) + { + if (exec_register_file[lane] == clobber_type && + sync_register_file[lane] == content_unknown) + { + clobbered_lanes.insert(lane); + sync_register_file[lane] = content_dual; + continue; + } + + if (exec_register_file[lane] == content_unknown) + { + lanes_to_search.insert(lane); + } + } + + if (!clobbered_lanes.empty()) + { + auto instructions = resolve_dependencies(clobbered_lanes, f16); + target->epilogue.insert(target->epilogue.end(), std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end())); + } + + if (lanes_to_search.empty()) + { + continue; + } + + // We have some missing lanes. Search upwards + if (!target->pred.empty()) + { + // We only need to search the last predecessor which is the true "root" of the branch + insert_block_register_dependency(ctx, target, lanes_to_search, f16); + } + } + } + + void insert_block_dependencies(DependencyPassContext& ctx, BasicBlock* block) + { + auto range_from_ref = [](const RegisterRef& ref) + { + const auto range = get_register_file_range(ref); + + std::unordered_set result; + for (const auto& value : range) + { + result.insert(value); + } + return result; + }; + + for (auto& ref : block->input_list) + { + const auto range = range_from_ref(ref); + insert_block_register_dependency(ctx, block, range, ref.reg.f16); + } + } + + void RegisterDependencyPass::run(FlowGraph& graph) + { + DependencyPassContext ctx{}; + + // First, run intra-block dependency + for (auto& block : graph.blocks) + { + insert_dependency_barriers(ctx, &block); + } + + // Then, create prologue/epilogue instructions + // Traverse the list in reverse order to bubble up dependencies correctly. + for (auto it = graph.blocks.rbegin(); it != graph.blocks.rend(); ++it) + { + insert_block_dependencies(ctx, &(*it)); + } + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.h b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.h new file mode 100644 index 0000000000..48068691e1 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.h @@ -0,0 +1,15 @@ +#pragma once + +#include "../../CFG.h" + +namespace rsx::assembler::FP +{ + // The register dependency pass identifies data hazards for each basic block and injects barrier instructions. + // Real PS3 does not have explicit barriers, but does instead often use delay slots or fence instructions to stall until a specific hardware unit clears the fence to advance. + // For decompiled shaders, we have the problem that aliasing is not real and is instead simulated. We do not have access to unions on the GPU without really nasty tricks. + class RegisterDependencyPass : public CFGPass + { + public: + void run(FlowGraph& graph) override; + }; +} diff --git a/rpcs3/Emu/RSX/Program/CgBinaryFragmentProgram.cpp b/rpcs3/Emu/RSX/Program/CgBinaryFragmentProgram.cpp index a06818de10..1dfe83e468 100644 --- a/rpcs3/Emu/RSX/Program/CgBinaryFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/Program/CgBinaryFragmentProgram.cpp @@ -273,7 +273,7 @@ void CgBinaryDisasm::TaskFP() src2.HEX = GetData(data[3]); m_step = 4 * sizeof(u32); - m_opcode = dst.opcode | (src1.opcode_is_branch << 6); + m_opcode = dst.opcode | (src1.opcode_hi << 6); auto SCT = [&]() { diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index 2ebfd7d8d7..94b92ce98e 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -3,12 +3,19 @@ #include "FragmentProgramDecompiler.h" #include "ProgramStateCache.h" +#include "Assembler/Passes/FP/RegisterAnnotationPass.h" +#include "Assembler/Passes/FP/RegisterDependencyPass.h" + +#include "Emu/system_config.h" + #include namespace rsx { namespace fragment_program { + using namespace rsx::assembler; + static const std::string reg_table[] = { "wpos", @@ -17,10 +24,33 @@ namespace rsx "tc0", "tc1", "tc2", "tc3", "tc4", "tc5", "tc6", "tc7", "tc8", "tc9", "ssa" }; + + static const std::vector s_fp32_output_set = + { + {.reg {.id = 0, .f16 = false }, .mask = 0xf }, + {.reg {.id = 2, .f16 = false }, .mask = 0xf }, + {.reg {.id = 3, .f16 = false }, .mask = 0xf }, + {.reg {.id = 4, .f16 = false }, .mask = 0xf }, + }; + + static const std::vector s_fp16_output_set = + { + {.reg {.id = 0, .f16 = true }, .mask = 0xf }, + {.reg {.id = 4, .f16 = true }, .mask = 0xf }, + {.reg {.id = 6, .f16 = true }, .mask = 0xf }, + {.reg {.id = 8, .f16 = true }, .mask = 0xf }, + }; + + static const RegisterRef s_z_export_reg = + { + .reg {.id = 1, .f16 = false }, + .mask = (1u << 2) + }; } } using namespace rsx::fragment_program; +using namespace rsx::assembler; // SIMD vector lanes enum VectorLane : u8 @@ -31,6 +61,26 @@ enum VectorLane : u8 W = 3, }; +std::vector get_fragment_program_output_set(u32 ctrl, u32 mrt_count) +{ + std::vector result; + if (mrt_count > 0) + { + result = (ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) + ? s_fp32_output_set + : s_fp16_output_set; + + result.resize(mrt_count); + } + + if (ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) + { + result.push_back(s_z_export_reg); + } + + return result; +} + FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size) : m_size(size) , m_prog(prog) @@ -151,8 +201,6 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags) } const u32 reg_index = dst.fp16 ? (dst.dest_reg >> 1) : dst.dest_reg; - ensure(reg_index < temp_registers.size()); - if (dst.opcode == RSX_FP_OPCODE_MOV && src0.reg_type == RSX_FP_REGISTER_TYPE_TEMP && src0.tmp_reg_index == reg_index) @@ -165,8 +213,6 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags) return; } } - - temp_registers[reg_index].tag(dst.dest_reg, !!dst.fp16, dst.mask_x, dst.mask_y, dst.mask_z, dst.mask_w); } void FragmentProgramDecompiler::AddFlowOp(const std::string& code) @@ -522,26 +568,7 @@ template std::string FragmentProgramDecompiler::GetSRC(T src) switch (src.reg_type) { case RSX_FP_REGISTER_TYPE_TEMP: - - if (!src.fp16) - { - if (dst.opcode == RSX_FP_OPCODE_UP16 || - dst.opcode == RSX_FP_OPCODE_UP2 || - dst.opcode == RSX_FP_OPCODE_UP4 || - dst.opcode == RSX_FP_OPCODE_UPB || - dst.opcode == RSX_FP_OPCODE_UPG) - { - auto ® = temp_registers[src.tmp_reg_index]; - if (reg.requires_gather(src.swizzle_x)) - { - properties.has_gather_op = true; - AddReg(src.tmp_reg_index, src.fp16); - ret = getFloatTypeName(4) + reg.gather_r(); - break; - } - } - } - else if (precision_modifier == RSX_FP_PRECISION_HALF) + if (src.fp16 && precision_modifier == RSX_FP_PRECISION_HALF) { // clamp16() is not a cheap operation when emulated; avoid at all costs precision_modifier = RSX_FP_PRECISION_REAL; @@ -762,7 +789,6 @@ std::string FragmentProgramDecompiler::BuildCode() const std::string float4_type = (fp16_out && device_props.has_native_half_support)? getHalfTypeName(4) : getFloatTypeName(4); const std::string init_value = float4_type + "(0.)"; std::array output_register_names; - std::array ouput_register_indices = { 0, 2, 3, 4 }; // Holder for any "cleanup" before exiting main std::stringstream main_epilogue; @@ -772,17 +798,6 @@ std::string FragmentProgramDecompiler::BuildCode() { // Hw tests show that the depth export register is default-initialized to 0 and not wpos.z!! m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "r1", init_value); - - auto& r1 = temp_registers[1]; - if (r1.requires_gather(VectorLane::Z)) - { - // r1.zw was not written to - properties.has_gather_op = true; - main_epilogue << " r1.z = " << float4_type << r1.gather_r() << ".z;\n"; - - // Emit debug warning. Useful to diagnose regressions, but should be removed in future. - rsx_log.warning("ROP reads from shader depth without writing to it. Final value will be gathered."); - } } // Add the color output registers. They are statically written to and have guaranteed initialization (except r1.z which == wpos.z) @@ -810,33 +825,6 @@ std::string FragmentProgramDecompiler::BuildCode() continue; } - const auto block_index = ouput_register_indices[n]; - auto& r = temp_registers[block_index]; - - if (fp16_out) - { - // Check if we need a split/extract op - if (r.requires_split(0)) - { - main_epilogue << " " << reg_name << " = " << float4_type << r.split_h0() << ";\n"; - - // Emit debug warning. Useful to diagnose regressions, but should be removed in future. - rsx_log.warning("ROP reads from %s without writing to it. Final value will be extracted from the 32-bit register.", reg_name); - } - - continue; - } - - if (!r.requires_gather128()) - { - // Nothing to do - continue; - } - - // We need to gather the data from existing registers - main_epilogue << " " << reg_name << " = " << float4_type << r.gather_r() << ";\n"; - properties.has_gather_op = true; - // Emit debug warning. Useful to diagnose regressions, but should be removed in future. rsx_log.warning("ROP reads from %s without writing to it. Final value will be gathered.", reg_name); } @@ -1024,28 +1012,6 @@ std::string FragmentProgramDecompiler::BuildCode() OS << Format(divsq_func); } - // Declare register gather/merge if needed - if (properties.has_gather_op) - { - std::string float2 = getFloatTypeName(2); - - OS << float4 << " gather(" << float4 << " _h0, " << float4 << " _h1)\n"; - OS << "{\n"; - OS << " float x = uintBitsToFloat(packHalf2x16(_h0.xy));\n"; - OS << " float y = uintBitsToFloat(packHalf2x16(_h0.zw));\n"; - OS << " float z = uintBitsToFloat(packHalf2x16(_h1.xy));\n"; - OS << " float w = uintBitsToFloat(packHalf2x16(_h1.zw));\n"; - OS << " return " << float4 << "(x, y, z, w);\n"; - OS << "}\n\n"; - - OS << float2 << " gather(" << float4 << " _h)\n"; - OS << "{\n"; - OS << " float x = uintBitsToFloat(packHalf2x16(_h.xy));\n"; - OS << " float y = uintBitsToFloat(packHalf2x16(_h.zw));\n"; - OS << " return " << float2 << "(x, y);\n"; - OS << "}\n\n"; - } - if (properties.has_dynamic_register_load) { OS << @@ -1149,6 +1115,14 @@ bool FragmentProgramDecompiler::handle_sct_scb(u32 opcode) return true; case RSX_FP_OPCODE_PKB: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packUnorm4x8($0)))"); return true; case RSX_FP_OPCODE_SIN: SetDst("sin($0.xxxx)"); return true; + + // Custom ISA extensions for 16-bit OR + case RSX_FP_OPCODE_OR16_HI: + SetDst("$float4(uintBitsToFloat((floatBitsToUint($0.x) & 0x0000ffff) | (packHalf2x16($1.xx) & 0xffff0000)))"); + return true; + case RSX_FP_OPCODE_OR16_LO: + SetDst("$float4(uintBitsToFloat((floatBitsToUint($0.x) & 0xffff0000) | (packHalf2x16($1.xx) & 0x0000ffff)))"); + return true; } return false; } @@ -1295,7 +1269,37 @@ bool FragmentProgramDecompiler::handle_tex_srb(u32 opcode) std::string FragmentProgramDecompiler::Decompile() { - const auto graph = rsx::assembler::deconstruct_fragment_program(m_prog); + auto graph = deconstruct_fragment_program(m_prog); + + if (!graph.blocks.empty()) + { + // The RSX CFG is missing the output block. We inject a fake tail block that ingests the ROP outputs. + BasicBlock* rop_block = nullptr; + BasicBlock* tail_block = &graph.blocks.back(); + if (tail_block->instructions.empty()) + { + // Merge block. Use this directly + rop_block = tail_block; + } + else + { + graph.blocks.push_back({}); + rop_block = &graph.blocks.back(); + + tail_block->insert_succ(rop_block); + rop_block->insert_pred(tail_block); + } + + const auto rop_inputs = get_fragment_program_output_set(m_prog.ctrl, m_prog.mrt_buffers_count); + rop_block->input_list.insert(rop_block->input_list.end(), rop_inputs.begin(), rop_inputs.end()); + + FP::RegisterAnnotationPass annotation_pass{ m_prog, { .skip_delay_slots = true } }; + FP::RegisterDependencyPass dependency_pass{}; + + annotation_pass.run(graph); + dependency_pass.run(graph); + } + m_size = 0; m_location = 0; m_loop_count = 0; @@ -1303,57 +1307,105 @@ std::string FragmentProgramDecompiler::Decompile() m_is_valid_ucode = true; m_constant_offsets.clear(); - enum + // For GLSL scope wind/unwind. We store the min scope depth and loop count for each block and "unwind" to it. + // This should recover information lost when multiple nodes converge on a single merge node or even skip a merge node as is the case with "ELSE" nodes. + std::unordered_map> block_data; + + auto push_block_info = [&](const BasicBlock* block) { - FORCE_NONE, - FORCE_SCT, - FORCE_SCB, + u32 loop = m_loop_count; + int level = m_code_level; + + auto found = block_data.find(block); + if (found != block_data.end()) + { + level = std::min(level, found->second.first); + loop = std::min(loop, found->second.second); + } + + block_data[block] = { level, loop }; }; - int forced_unit = FORCE_NONE; + auto emit_block = [&](const std::vector& instructions) + { + for (auto& inst : instructions) + { + m_instruction = &inst; + dst.HEX = inst.bytecode[0]; + src0.HEX = inst.bytecode[1]; + src1.HEX = inst.bytecode[2]; + src2.HEX = inst.bytecode[3]; + + ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation"); + } + }; for (const auto &block : graph.blocks) { - // TODO: Handle block prologue if any + auto found = block_data.find(&block); + if (found != block_data.end()) + { + const auto [level, loop] = found->second; + for (int i = m_code_level; i > level; i--) + { + m_code_level--; + AddCode("}"); + } + + m_loop_count = loop; + } + if (!block.pred.empty()) { - // CFG guarantees predecessors are sorted, closest one first - for (const auto& pred : block.pred) + // Predecessors are always sorted closest last. + // This gives some adjacency info and tells us how the previous block connects to this one. + const auto& pred = block.pred.back(); + switch (pred.type) { - switch (pred.type) - { - case rsx::assembler::EdgeType::ENDLOOP: - m_loop_count--; - [[ fallthrough ]]; - case rsx::assembler::EdgeType::ENDIF: - m_code_level--; - AddCode("}"); - break; - case rsx::assembler::EdgeType::LOOP: - m_loop_count++; - [[ fallthrough ]]; - case rsx::assembler::EdgeType::IF: - // Instruction will be inserted by the SIP decoder - AddCode("{"); - m_code_level++; - break; - case rsx::assembler::EdgeType::ELSE: - // This one needs more testing - m_code_level--; - AddCode("}"); - AddCode("else"); - AddCode("{"); - m_code_level++; - break; - default: - // Start a new block anyway - fmt::throw_exception("Unexpected block found"); - } + case EdgeType::LOOP: + m_loop_count++; + [[ fallthrough ]]; + case EdgeType::IF: + AddCode("{"); + m_code_level++; + break; + case EdgeType::ELSE: + AddCode("else"); + AddCode("{"); + m_code_level++; + break; + case EdgeType::ENDIF: + case EdgeType::ENDLOOP: + // Pure merge block? + break; + case EdgeType::NONE: + ensure(block.instructions.empty()); + break; + default: + fmt::throw_exception("Unhandled edge type %d", static_cast(pred.type)); + break; } } + if (!block.prologue.empty()) + { + AddCode("// Prologue"); + emit_block(block.prologue); + } + + const bool early_epilogue = + !block.epilogue.empty() && + !block.succ.empty() && + (block.succ.front().type == EdgeType::IF || block.succ.front().type == EdgeType::LOOP); + for (const auto& inst : block.instructions) { + if (early_epilogue && &inst == &block.instructions.back()) + { + AddCode("// Epilogue"); + emit_block(block.epilogue); + } + m_instruction = &inst; dst.HEX = inst.bytecode[0]; @@ -1363,11 +1415,9 @@ std::string FragmentProgramDecompiler::Decompile() opflags = 0; - const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6); - auto SIP = [&]() { - switch (opcode) + switch (m_instruction->opcode) { case RSX_FP_OPCODE_BRK: if (m_loop_count) AddFlowOp("break"); @@ -1377,12 +1427,10 @@ std::string FragmentProgramDecompiler::Decompile() rsx_log.error("Unimplemented SIP instruction: CAL"); break; case RSX_FP_OPCODE_FENCT: - AddCode("//FENCT"); - forced_unit = FORCE_SCT; + AddCode("// FENCT"); break; case RSX_FP_OPCODE_FENCB: - AddCode("//FENCB"); - forced_unit = FORCE_SCB; + AddCode("// FENCB"); break; case RSX_FP_OPCODE_IFE: AddCode("if($cond)"); @@ -1406,7 +1454,7 @@ std::string FragmentProgramDecompiler::Decompile() return true; }; - switch (opcode) + switch (m_instruction->opcode) { case RSX_FP_OPCODE_NOP: break; @@ -1415,19 +1463,10 @@ std::string FragmentProgramDecompiler::Decompile() AddFlowOp("_kill()"); break; default: - int prev_force_unit = forced_unit; - - // Some instructions do not respect forced unit - // Tested with Tales of Vesperia if (SIP()) break; - if (handle_tex_srb(opcode)) break; - - // FENCT/FENCB do not actually reject instructions if they dont match the forced unit - // Looks like they are optimization hints and not hard-coded forced paths - if (handle_sct_scb(opcode)) break; - forced_unit = FORCE_NONE; - - rsx_log.error("Unknown/illegal instruction: 0x%x (forced unit %d)", opcode, prev_force_unit); + if (handle_tex_srb(m_instruction->opcode)) break; + if (handle_sct_scb(m_instruction->opcode)) break; + rsx_log.error("Unknown/illegal instruction: 0x%x", m_instruction->opcode); break; } @@ -1435,16 +1474,28 @@ std::string FragmentProgramDecompiler::Decompile() if (dst.end) break; } - // TODO: Handle block epilogue if needed + if (!early_epilogue && !block.epilogue.empty()) + { + AddCode("// Epilogue"); + emit_block(block.epilogue); + } + + for (auto& succ : block.succ) + { + switch (succ.type) + { + case EdgeType::ENDIF: + case EdgeType::ENDLOOP: + case EdgeType::ELSE: + push_block_info(succ.to); + break; + default: + break; + } + } } - while (m_code_level > 1) - { - rsx_log.error("Hanging block found at end of shader. Malformed shader?"); - - m_code_level--; - AddCode("}"); - } + ensure(m_code_level == 1); // flush m_code_level m_code_level = 1; diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h index b68750bdfc..09a02804c3 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h @@ -1,6 +1,5 @@ #pragma once #include "ShaderParam.h" -#include "FragmentProgramRegister.h" #include "RSXFragmentProgram.h" #include "Assembler/CFG.h" @@ -53,8 +52,6 @@ class FragmentProgramDecompiler int m_code_level; std::unordered_map m_constant_offsets; - std::array temp_registers; - std::string GetMask() const; void SetDst(std::string code, u32 flags = 0); @@ -175,7 +172,6 @@ public: // Decoded properties (out) bool has_lit_op = false; - bool has_gather_op = false; bool has_no_output = false; bool has_discard_op = false; bool has_tex_op = false; diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp deleted file mode 100644 index a14b142df6..0000000000 --- a/rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp +++ /dev/null @@ -1,196 +0,0 @@ -#include "stdafx.h" -#include "FragmentProgramRegister.h" - -namespace rsx -{ - MixedPrecisionRegister::MixedPrecisionRegister() - { - std::fill(content_mask.begin(), content_mask.end(), data_type_bits::undefined); - } - - void MixedPrecisionRegister::tag_h0(bool x, bool y, bool z, bool w) - { - if (x) content_mask[0] = data_type_bits::f16; - if (y) content_mask[1] = data_type_bits::f16; - if (z) content_mask[2] = data_type_bits::f16; - if (w) content_mask[3] = data_type_bits::f16; - } - - void MixedPrecisionRegister::tag_h1(bool x, bool y, bool z, bool w) - { - if (x) content_mask[4] = data_type_bits::f16; - if (y) content_mask[5] = data_type_bits::f16; - if (z) content_mask[6] = data_type_bits::f16; - if (w) content_mask[7] = data_type_bits::f16; - } - - void MixedPrecisionRegister::tag_r(bool x, bool y, bool z, bool w) - { - if (x) content_mask[0] = content_mask[1] = data_type_bits::f32; - if (y) content_mask[2] = content_mask[3] = data_type_bits::f32; - if (z) content_mask[4] = content_mask[5] = data_type_bits::f32; - if (w) content_mask[6] = content_mask[7] = data_type_bits::f32; - } - - void MixedPrecisionRegister::tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w) - { - if (file_index == umax) - { - // First-time use. Initialize... - const u32 real_index = is_fp16 ? (index >> 1) : index; - file_index = real_index; - } - - if (is_fp16) - { - ensure((index / 2) == file_index); - - if (index & 1) - { - tag_h1(x, y, z, w); - return; - } - - tag_h0(x, y, z, w); - return; - } - - tag_r(x, y, z, w); - } - - std::string MixedPrecisionRegister::gather_r() const - { - const auto half_index = file_index << 1; - const std::string reg = "r" + std::to_string(file_index); - const std::string gather_half_regs[] = { - "gather(h" + std::to_string(half_index) + ")", - "gather(h" + std::to_string(half_index + 1) + ")" - }; - - std::string outputs[4]; - for (int ch = 0; ch < 4; ++ch) - { - // FIXME: This approach ignores mixed register bits. Not ideal!!!! - const auto channel0 = content_mask[ch * 2]; - const auto is_fp16_ch = channel0 == content_mask[ch * 2 + 1] && channel0 == data_type_bits::f16; - outputs[ch] = is_fp16_ch ? gather_half_regs[ch / 2] : reg; - } - - // Grouping. Only replace relevant bits... - if (outputs[0] == outputs[1]) outputs[0] = ""; - if (outputs[2] == outputs[3]) outputs[2] = ""; - - // Assemble - bool group = false; - std::string result = ""; - constexpr std::string_view swz_mask = "xyzw"; - - for (int ch = 0; ch < 4; ++ch) - { - if (outputs[ch].empty()) - { - group = true; - continue; - } - - if (!result.empty()) - { - result += ", "; - } - - if (group) - { - ensure(ch > 0); - group = false; - - if (outputs[ch] == reg) - { - result += reg + "." + swz_mask[ch - 1] + swz_mask[ch]; - continue; - } - - result += outputs[ch]; - continue; - } - - const int subch = outputs[ch] == reg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles - result += outputs[ch] + "." + swz_mask[subch]; - } - - // Optimize dual-gather (128-bit gather) to use special function - const std::string double_gather = gather_half_regs[0] + ", " + gather_half_regs[1]; - if (result == double_gather) - { - result = "gather(h" + std::to_string(half_index) + ", h" + std::to_string(half_index + 1) + ")"; - } - - return "(" + result + ")"; - } - - std::string MixedPrecisionRegister::fetch_halfreg(u32 word_index) const - { - // Reads half-word 0 (H16x4) from a full real (R32x4) register - constexpr std::string_view swz_mask = "xyzw"; - const std::string reg = "r" + std::to_string(file_index); - const std::string hreg = "h" + std::to_string(file_index * 2 + word_index); - - const std::string word0_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2] + ")"; - const std::string word1_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2 + 1] + ")"; - const std::string words[] = { - "unpackHalf2x16(" + word0_bits + ")", - "unpackHalf2x16(" + word1_bits + ")" - }; - - // Assemble - std::string outputs[4]; - - ensure(word_index <= 1); - const int word_offset = word_index * 4; - for (int ch = 0; ch < 4; ++ch) - { - outputs[ch] = content_mask[ch + word_offset] == data_type_bits::f32 - ? words[ch / 2] - : hreg; - } - - // Grouping. Only replace relevant bits... - if (outputs[0] == outputs[1]) outputs[0] = ""; - if (outputs[2] == outputs[3]) outputs[2] = ""; - - // Assemble - bool group = false; - std::string result = ""; - - for (int ch = 0; ch < 4; ++ch) - { - if (outputs[ch].empty()) - { - group = true; - continue; - } - - if (!result.empty()) - { - result += ", "; - } - - if (group) - { - ensure(ch > 0); - group = false; - result += outputs[ch]; - - if (outputs[ch] == hreg) - { - result += std::string(".") + swz_mask[ch - 1] + swz_mask[ch]; - } - continue; - } - - const int subch = outputs[ch] == hreg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles - result += outputs[ch] + "." + swz_mask[subch]; - } - - return "(" + result + ")"; - } -} diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramRegister.h b/rpcs3/Emu/RSX/Program/FragmentProgramRegister.h deleted file mode 100644 index 6cfc8e76c3..0000000000 --- a/rpcs3/Emu/RSX/Program/FragmentProgramRegister.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -#include - -namespace rsx -{ - class MixedPrecisionRegister - { - enum data_type_bits - { - undefined = 0, - f16 = 1, - f32 = 2 - }; - - std::array content_mask; // Content details for each half-word - u32 file_index = umax; - - void tag_h0(bool x, bool y, bool z, bool w); - - void tag_h1(bool x, bool y, bool z, bool w); - - void tag_r(bool x, bool y, bool z, bool w); - - std::string fetch_halfreg(u32 word_index) const; - - public: - MixedPrecisionRegister(); - - void tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w); - - std::string gather_r() const; - - std::string split_h0() const - { - return fetch_halfreg(0); - } - - std::string split_h1() const - { - return fetch_halfreg(1); - } - - // Getters - - // Return true if all values are unwritten to (undefined) - bool floating() const - { - return file_index == umax; - } - - // Return true if the first half register is all undefined - bool floating_h0() const - { - return content_mask[0] == content_mask[1] && - content_mask[1] == content_mask[2] && - content_mask[2] == content_mask[3] && - content_mask[3] == data_type_bits::undefined; - } - - // Return true if the second half register is all undefined - bool floating_h1() const - { - return content_mask[4] == content_mask[5] && - content_mask[5] == content_mask[6] && - content_mask[6] == content_mask[7] && - content_mask[7] == data_type_bits::undefined; - } - - // Return true if any of the half-words are 16-bit - bool requires_gather(u8 channel) const - { - // Data fetched from the single precision register requires merging of the two half registers - const auto channel_offset = channel * 2; - ensure(channel_offset <= 6); - - return (content_mask[channel_offset] == data_type_bits::f16 || content_mask[channel_offset + 1] == data_type_bits::f16); - } - - // Return true if the entire 128-bit register is filled with 2xfp16x4 data words - bool requires_gather128() const - { - // Full 128-bit check - for (const auto& ch : content_mask) - { - if (ch == data_type_bits::f16) - { - return true; - } - } - - return false; - } - - // Return true if the half-register is polluted with fp32 data - bool requires_split(u32 word_index) const - { - const u32 content_offset = word_index * 4; - for (u32 i = 0; i < 4; ++i) - { - if (content_mask[content_offset + i] == data_type_bits::f32) - { - return true; - } - } - - return false; - } - }; -} - diff --git a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h index f834b7c7f5..d93ec760e6 100644 --- a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h +++ b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h @@ -1,6 +1,7 @@ #pragma once #include "program_util.h" +#include "Assembler/FPOpcodes.h" #include #include @@ -23,76 +24,7 @@ enum register_precision RSX_FP_PRECISION_UNKNOWN = 5 // Unknown what this actually does; seems to do nothing on hwtests but then why would their compiler emit it? }; -enum fp_opcode -{ - RSX_FP_OPCODE_NOP = 0x00, // No-Operation - RSX_FP_OPCODE_MOV = 0x01, // Move - RSX_FP_OPCODE_MUL = 0x02, // Multiply - RSX_FP_OPCODE_ADD = 0x03, // Add - RSX_FP_OPCODE_MAD = 0x04, // Multiply-Add - RSX_FP_OPCODE_DP3 = 0x05, // 3-component Dot Product - RSX_FP_OPCODE_DP4 = 0x06, // 4-component Dot Product - RSX_FP_OPCODE_DST = 0x07, // Distance - RSX_FP_OPCODE_MIN = 0x08, // Minimum - RSX_FP_OPCODE_MAX = 0x09, // Maximum - RSX_FP_OPCODE_SLT = 0x0A, // Set-If-LessThan - RSX_FP_OPCODE_SGE = 0x0B, // Set-If-GreaterEqual - RSX_FP_OPCODE_SLE = 0x0C, // Set-If-LessEqual - RSX_FP_OPCODE_SGT = 0x0D, // Set-If-GreaterThan - RSX_FP_OPCODE_SNE = 0x0E, // Set-If-NotEqual - RSX_FP_OPCODE_SEQ = 0x0F, // Set-If-Equal - RSX_FP_OPCODE_FRC = 0x10, // Fraction (fract) - RSX_FP_OPCODE_FLR = 0x11, // Floor - RSX_FP_OPCODE_KIL = 0x12, // Kill fragment - RSX_FP_OPCODE_PK4 = 0x13, // Pack four signed 8-bit values - RSX_FP_OPCODE_UP4 = 0x14, // Unpack four signed 8-bit values - RSX_FP_OPCODE_DDX = 0x15, // Partial-derivative in x (Screen space derivative w.r.t. x) - RSX_FP_OPCODE_DDY = 0x16, // Partial-derivative in y (Screen space derivative w.r.t. y) - RSX_FP_OPCODE_TEX = 0x17, // Texture lookup - RSX_FP_OPCODE_TXP = 0x18, // Texture sample with projection (Projective texture lookup) - RSX_FP_OPCODE_TXD = 0x19, // Texture sample with partial differentiation (Texture lookup with derivatives) - RSX_FP_OPCODE_RCP = 0x1A, // Reciprocal - RSX_FP_OPCODE_RSQ = 0x1B, // Reciprocal Square Root - RSX_FP_OPCODE_EX2 = 0x1C, // Exponentiation base 2 - RSX_FP_OPCODE_LG2 = 0x1D, // Log base 2 - RSX_FP_OPCODE_LIT = 0x1E, // Lighting coefficients - RSX_FP_OPCODE_LRP = 0x1F, // Linear Interpolation - RSX_FP_OPCODE_STR = 0x20, // Set-If-True - RSX_FP_OPCODE_SFL = 0x21, // Set-If-False - RSX_FP_OPCODE_COS = 0x22, // Cosine - RSX_FP_OPCODE_SIN = 0x23, // Sine - RSX_FP_OPCODE_PK2 = 0x24, // Pack two 16-bit floats - RSX_FP_OPCODE_UP2 = 0x25, // Unpack two 16-bit floats - RSX_FP_OPCODE_POW = 0x26, // Power - RSX_FP_OPCODE_PKB = 0x27, // Pack bytes - RSX_FP_OPCODE_UPB = 0x28, // Unpack bytes - RSX_FP_OPCODE_PK16 = 0x29, // Pack 16 bits - RSX_FP_OPCODE_UP16 = 0x2A, // Unpack 16 - RSX_FP_OPCODE_BEM = 0x2B, // Bump-environment map (a.k.a. 2D coordinate transform) - RSX_FP_OPCODE_PKG = 0x2C, // Pack with sRGB transformation - RSX_FP_OPCODE_UPG = 0x2D, // Unpack gamma - RSX_FP_OPCODE_DP2A = 0x2E, // 2-component dot product with scalar addition - RSX_FP_OPCODE_TXL = 0x2F, // Texture sample with explicit LOD - RSX_FP_OPCODE_TXB = 0x31, // Texture sample with bias - RSX_FP_OPCODE_TEXBEM = 0x33, - RSX_FP_OPCODE_TXPBEM = 0x34, - RSX_FP_OPCODE_BEMLUM = 0x35, - RSX_FP_OPCODE_REFL = 0x36, // Reflection vector - RSX_FP_OPCODE_TIMESWTEX = 0x37, - RSX_FP_OPCODE_DP2 = 0x38, // 2-component dot product - RSX_FP_OPCODE_NRM = 0x39, // Normalize - RSX_FP_OPCODE_DIV = 0x3A, // Division - RSX_FP_OPCODE_DIVSQ = 0x3B, // Divide by Square Root - RSX_FP_OPCODE_LIF = 0x3C, // Final part of LIT - RSX_FP_OPCODE_FENCT = 0x3D, // Fence T? - RSX_FP_OPCODE_FENCB = 0x3E, // Fence B? - RSX_FP_OPCODE_BRK = 0x40, // Break - RSX_FP_OPCODE_CAL = 0x41, // Subroutine call - RSX_FP_OPCODE_IFE = 0x42, // If - RSX_FP_OPCODE_LOOP = 0x43, // Loop - RSX_FP_OPCODE_REP = 0x44, // Repeat - RSX_FP_OPCODE_RET = 0x45 // Return -}; +using enum rsx::assembler::FP_opcode; union OPDEST { @@ -116,6 +48,12 @@ union OPDEST u32 no_dest : 1; u32 saturate : 1; // _sat }; + + struct + { + u32 : 9; + u32 write_mask : 4; + }; }; union SRC0 @@ -164,7 +102,7 @@ union SRC1 u32 src1_prec_mod : 3; // Precision modifier for src1 (CoD:MW series) u32 src2_prec_mod : 3; // Precision modifier for src2 (unproven, should affect MAD instruction) u32 scale : 3; - u32 opcode_is_branch : 1; + u32 opcode_hi : 1; // Opcode high bit }; struct @@ -207,6 +145,23 @@ union SRC2 }; }; +union SRC_Common +{ + u32 HEX; + + struct + { + u32 reg_type : 2; + u32 tmp_reg_index : 6; + u32 fp16 : 1; + u32 swizzle_x : 2; + u32 swizzle_y : 2; + u32 swizzle_z : 2; + u32 swizzle_w : 2; + u32 neg : 1; + }; +}; + constexpr const char* rsx_fp_input_attr_regs[] = { "WPOS", "COL0", "COL1", "FOGC", "TEX0", diff --git a/rpcs3/Emu/RSX/Program/ShaderParam.h b/rpcs3/Emu/RSX/Program/ShaderParam.h index 266ab51cbd..6131f36ae9 100644 --- a/rpcs3/Emu/RSX/Program/ShaderParam.h +++ b/rpcs3/Emu/RSX/Program/ShaderParam.h @@ -244,10 +244,10 @@ public: std::vector swizzles; ShaderVariable() = default; - ShaderVariable(const std::string& var) + ShaderVariable(std::string_view var) { // Separate 'double destination' variables 'X=Y=SRC' - std::string simple_var; + std::string_view simple_var; const auto eq_pos = var.find('='); if (eq_pos != umax) @@ -267,11 +267,11 @@ public: simple_var = simple_var.substr(brace_pos); } - auto var_blocks = fmt::split(simple_var, { "." }); + const auto var_blocks = fmt::split_sv(simple_var, { "." }); ensure((!var_blocks.empty())); - name = prefix + var_blocks[0]; + name = prefix + std::string(var_blocks[0]); if (var_blocks.size() == 1) { diff --git a/rpcs3/Emu/RSX/VK/VulkanAPI.h b/rpcs3/Emu/RSX/VK/VulkanAPI.h index c91e9cb5a5..411d653807 100644 --- a/rpcs3/Emu/RSX/VK/VulkanAPI.h +++ b/rpcs3/Emu/RSX/VK/VulkanAPI.h @@ -4,7 +4,7 @@ #ifdef _WIN32 #define VK_USE_PLATFORM_WIN32_KHR #elif defined(__APPLE__) -#define VK_USE_PLATFORM_MACOS_MVK +#define VK_USE_PLATFORM_METAL_EXT #elif defined(ANDROID) #define VK_USE_PLATFORM_ANDROID_KHR #else @@ -29,7 +29,7 @@ // Undefine header configuration variables #undef VK_USE_PLATFORM_WIN32_KHR -#undef VK_USE_PLATFORM_MACOS_MVK +#undef VK_USE_PLATFORM_METAL_EXT #undef VK_USE_PLATFORM_ANDROID_KHR #undef VK_USE_PLATFORM_XLIB_KHR #undef VK_USE_PLATFORM_WAYLAND_KHR diff --git a/rpcs3/Emu/RSX/VK/vkutils/buffer_object.cpp b/rpcs3/Emu/RSX/VK/vkutils/buffer_object.cpp index daf60ad03c..47f79ad98a 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/buffer_object.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/buffer_object.cpp @@ -50,6 +50,7 @@ namespace vk : m_device(dev) { const bool nullable = !!(flags & VK_BUFFER_CREATE_ALLOW_NULL_RPCS3); + const bool no_vmem_recovery = !!(flags & VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3); info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; info.flags = flags & ~VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3; @@ -69,18 +70,27 @@ namespace vk fmt::throw_exception("No compatible memory type was found!"); } - memory = std::make_unique(m_device, memory_reqs.size, memory_reqs.alignment, allocation_type_info, allocation_pool, nullable); + memory_allocation_request request + { + .size = memory_reqs.size, + .alignment = memory_reqs.alignment, + .memory_type = &allocation_type_info, + .pool = allocation_pool, + .throw_on_fail = !nullable, + .recover_vmem_on_fail = !no_vmem_recovery + }; + memory = std::make_unique(m_device, request); + if (auto device_memory = memory->get_vk_device_memory(); device_memory != VK_NULL_HANDLE) { vkBindBufferMemory(dev, value, device_memory, memory->get_vk_device_memory_offset()); + return; } - else - { - ensure(nullable); - vkDestroyBuffer(m_device, value, nullptr); - value = VK_NULL_HANDLE; - } + + ensure(nullable); + vkDestroyBuffer(m_device, value, nullptr); + value = VK_NULL_HANDLE; } buffer::buffer(const vk::render_device& dev, VkBufferUsageFlags usage, void* host_pointer, u64 size) diff --git a/rpcs3/Emu/RSX/VK/vkutils/buffer_object.h b/rpcs3/Emu/RSX/VK/vkutils/buffer_object.h index ba5309749a..136b6938bf 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/buffer_object.h +++ b/rpcs3/Emu/RSX/VK/vkutils/buffer_object.h @@ -9,9 +9,10 @@ namespace vk { enum : u32 { - VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 = 0x80000000, + VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 = 0x10000000, // If we cannot allocate memory for the buffer, just return an empty but valid object with a null handle. + VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3 = 0x20000000, // If we cannot allocate memory for the buffer, do not run recovery routine to recover VRAM. Crash or return empty handle immediately instead. - VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3 = (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3) + VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3 = (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3) }; struct buffer_view : public unique_resource diff --git a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp index 7fa6a46a81..e3207875f8 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp @@ -53,7 +53,7 @@ namespace vk VkFlags create_flags = 0; if (m_prefer_writethrough) { - create_flags |= VK_BUFFER_CREATE_ALLOW_NULL_RPCS3; + create_flags |= (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3); } heap = std::make_unique(*g_render_device, size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM); @@ -146,7 +146,7 @@ namespace vk VkFlags create_flags = 0; if (m_prefer_writethrough) { - create_flags |= VK_BUFFER_CREATE_ALLOW_NULL_RPCS3; + create_flags |= (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3); } heap = std::make_unique(*g_render_device, aligned_new_size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM); diff --git a/rpcs3/Emu/RSX/VK/vkutils/device.cpp b/rpcs3/Emu/RSX/VK/vkutils/device.cpp index e29a9b66c2..f7b7ffb19c 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/device.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/device.cpp @@ -3,6 +3,9 @@ #include "util/logs.hpp" #include "Emu/system_config.h" #include +#ifdef __APPLE__ +#include +#endif namespace vk { @@ -120,6 +123,9 @@ namespace vk optional_features_support.external_memory_host = device_extensions.is_supported(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); optional_features_support.synchronization_2 = device_extensions.is_supported(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); optional_features_support.unrestricted_depth_range = device_extensions.is_supported(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); +#ifdef __APPLE__ + optional_features_support.portability = device_extensions.is_supported(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); +#endif optional_features_support.debug_utils = instance_extensions.is_supported(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); optional_features_support.surface_capabilities_2 = instance_extensions.is_supported(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME); @@ -555,6 +561,13 @@ namespace vk { requested_extensions.push_back(VK_EXT_DEVICE_FAULT_EXTENSION_NAME); } + +#ifdef __APPLE__ + if (pgpu->optional_features_support.portability) + { + requested_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); + } +#endif enabled_features.robustBufferAccess = VK_TRUE; enabled_features.fullDrawIndexUint32 = VK_TRUE; diff --git a/rpcs3/Emu/RSX/VK/vkutils/device.h b/rpcs3/Emu/RSX/VK/vkutils/device.h index c121d1b20a..5ca00eb622 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/device.h +++ b/rpcs3/Emu/RSX/VK/vkutils/device.h @@ -102,6 +102,7 @@ namespace vk bool unrestricted_depth_range = false; bool extended_device_fault = false; bool texture_compression_bc = false; + bool portability = false; } optional_features_support; friend class render_device; diff --git a/rpcs3/Emu/RSX/VK/vkutils/image.cpp b/rpcs3/Emu/RSX/VK/vkutils/image.cpp index 31499b0ce1..02d9bf34bd 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/image.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/image.cpp @@ -128,7 +128,16 @@ namespace vk fmt::throw_exception("No compatible memory type was found!"); } - memory = std::make_shared(m_device, memory_req.size, memory_req.alignment, allocation_type_info, allocation_pool, nullable); + memory_allocation_request alloc_request + { + .size = memory_req.size, + .alignment = memory_req.alignment, + .memory_type = &allocation_type_info, + .pool = allocation_pool, + .throw_on_fail = !nullable + }; + memory = std::make_shared(m_device, alloc_request); + if (auto device_mem = memory->get_vk_device_memory(); device_mem != VK_NULL_HANDLE) [[likely]] { diff --git a/rpcs3/Emu/RSX/VK/vkutils/instance.cpp b/rpcs3/Emu/RSX/VK/vkutils/instance.cpp index fff6691aea..49224fcc27 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/instance.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/instance.cpp @@ -124,10 +124,11 @@ namespace vk } #ifdef __APPLE__ + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); if (support.is_supported(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME)) { extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME); - layers.push_back(kMVKMoltenVKDriverLayerName); mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_RESUME_LOST_DEVICE", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_true }); mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_FAST_MATH_ENABLED", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_fast_math }); @@ -154,7 +155,7 @@ namespace vk #ifdef _WIN32 extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); #elif defined(__APPLE__) - extensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); #else bool found_surface_ext = false; #ifdef HAVE_X11 @@ -187,15 +188,32 @@ namespace vk if (g_cfg.video.debug_output) layers.push_back("VK_LAYER_KHRONOS_validation"); } +#ifdef __APPLE__ + // MoltenVK's ICD will not be detected without these extensions enabled. + else + { + extensions_loaded = true; + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + } +#endif VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.pApplicationInfo = &app; instance_info.enabledLayerCount = static_cast(layers.size()); instance_info.ppEnabledLayerNames = layers.data(); +#ifdef __APPLE__ + instance_info.enabledExtensionCount = static_cast(extensions.size()); + instance_info.ppEnabledExtensionNames = extensions.data(); +#else instance_info.enabledExtensionCount = fast ? 0 : static_cast(extensions.size()); instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data(); +#endif instance_info.pNext = next_info; +#ifdef __APPLE__ + instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; +#endif if (VkResult result = vkCreateInstance(&instance_info, nullptr, &m_instance); result != VK_SUCCESS) { diff --git a/rpcs3/Emu/RSX/VK/vkutils/memory.cpp b/rpcs3/Emu/RSX/VK/vkutils/memory.cpp index 2b84e1f61c..fcb1d448d4 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/memory.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/memory.cpp @@ -224,7 +224,7 @@ namespace vk vmaDestroyAllocator(m_allocator); } - mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) + mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(const memory_allocation_request& request) { VmaAllocation vma_alloc; VkMemoryRequirements mem_req = {}; @@ -233,11 +233,11 @@ namespace vk auto do_vma_alloc = [&]() -> std::tuple { - for (const auto& memory_type_index : memory_type) + for (const auto& memory_type_index : *request.memory_type) { mem_req.memoryTypeBits = 1u << memory_type_index; - mem_req.size = ::align2(block_sz, alignment); - mem_req.alignment = alignment; + mem_req.size = ::align2(request.size, request.alignment); + mem_req.alignment = request.alignment; create_info.memoryTypeBits = 1u << memory_type_index; create_info.flags = m_allocation_flags; @@ -256,26 +256,29 @@ namespace vk const auto [status, type] = do_vma_alloc(); if (status == VK_SUCCESS) { - vmm_notify_memory_allocated(vma_alloc, type, block_sz, pool); + vmm_notify_memory_allocated(vma_alloc, type, request.size, request.pool); return vma_alloc; } } - const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; - if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && - vmm_handle_memory_pressure(severity)) + if (request.recover_vmem_on_fail) { - // Out of memory. Try again. - const auto [status, type] = do_vma_alloc(); - if (status == VK_SUCCESS) + const auto severity = (request.throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; + if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && + vmm_handle_memory_pressure(severity)) { - rsx_log.warning("Renderer ran out of video memory but successfully recovered."); - vmm_notify_memory_allocated(vma_alloc, type, block_sz, pool); - return vma_alloc; + // Out of memory. Try again. + const auto [status, type] = do_vma_alloc(); + if (status == VK_SUCCESS) + { + rsx_log.warning("Renderer ran out of video memory but successfully recovered."); + vmm_notify_memory_allocated(vma_alloc, type, request.size, request.pool); + return vma_alloc; + } } } - if (!throw_on_fail) + if (!request.throw_on_fail) { return VK_NULL_HANDLE; } @@ -361,18 +364,18 @@ namespace vk m_allocation_flags = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; } - mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) + mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(const memory_allocation_request& request) { VkResult error_code = VK_ERROR_UNKNOWN; VkDeviceMemory memory; VkMemoryAllocateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - info.allocationSize = block_sz; + info.allocationSize = request.size; auto do_vk_alloc = [&]() -> std::tuple { - for (const auto& memory_type_index : memory_type) + for (const auto& memory_type_index : *request.memory_type) { info.memoryTypeIndex = memory_type_index; error_code = vkAllocateMemory(m_device, &info, nullptr, &memory); @@ -389,26 +392,29 @@ namespace vk const auto [status, type] = do_vk_alloc(); if (status == VK_SUCCESS) { - vmm_notify_memory_allocated(memory, type, block_sz, pool); + vmm_notify_memory_allocated(memory, type, request.size, request.pool); return memory; } } - const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; - if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && - vmm_handle_memory_pressure(severity)) + if (request.recover_vmem_on_fail) { - // Out of memory. Try again. - const auto [status, type] = do_vk_alloc(); - if (status == VK_SUCCESS) + const auto severity = (request.throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; + if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && + vmm_handle_memory_pressure(severity)) { - rsx_log.warning("Renderer ran out of video memory but successfully recovered."); - vmm_notify_memory_allocated(memory, type, block_sz, pool); - return memory; + // Out of memory. Try again. + const auto [status, type] = do_vk_alloc(); + if (status == VK_SUCCESS) + { + rsx_log.warning("Renderer ran out of video memory but successfully recovered."); + vmm_notify_memory_allocated(memory, type, request.size, request.pool); + return memory; + } } } - if (!throw_on_fail) + if (!request.throw_on_fail) { return VK_NULL_HANDLE; } @@ -455,11 +461,11 @@ namespace vk return g_render_device->get_allocator(); } - memory_block::memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable) - : m_device(dev), m_size(block_sz) + memory_block::memory_block(VkDevice dev, const memory_allocation_request& alloc_request) + : m_device(dev), m_size(alloc_request.size) { m_mem_allocator = get_current_mem_allocator(); - m_mem_handle = m_mem_allocator->alloc(block_sz, alignment, memory_type, pool, !nullable); + m_mem_handle = m_mem_allocator->alloc(alloc_request); } memory_block::~memory_block() diff --git a/rpcs3/Emu/RSX/VK/vkutils/memory.h b/rpcs3/Emu/RSX/VK/vkutils/memory.h index abe0c75582..5c4d05ecdf 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/memory.h +++ b/rpcs3/Emu/RSX/VK/vkutils/memory.h @@ -66,6 +66,16 @@ namespace vk u64 size; }; + struct memory_allocation_request + { + u64 size = 0; + u64 alignment = 1; + const memory_type_info* memory_type = nullptr; + vmm_allocation_pool pool = VMM_ALLOCATION_POOL_UNDEFINED; + bool throw_on_fail = true; + bool recover_vmem_on_fail = true; + }; + class mem_allocator_base { public: @@ -76,7 +86,7 @@ namespace vk virtual void destroy() = 0; - virtual mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) = 0; + virtual mem_handle_t alloc(const memory_allocation_request& request) = 0; virtual void free(mem_handle_t mem_handle) = 0; virtual void* map(mem_handle_t mem_handle, u64 offset, u64 size) = 0; virtual void unmap(mem_handle_t mem_handle) = 0; @@ -104,7 +114,7 @@ namespace vk void destroy() override; - mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override; + mem_handle_t alloc(const memory_allocation_request& request) override; void free(mem_handle_t mem_handle) override; void* map(mem_handle_t mem_handle, u64 offset, u64 /*size*/) override; @@ -134,7 +144,7 @@ namespace vk void destroy() override {} - mem_handle_t alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override; + mem_handle_t alloc(const memory_allocation_request& request) override; void free(mem_handle_t mem_handle) override; void* map(mem_handle_t mem_handle, u64 offset, u64 size) override; @@ -147,7 +157,7 @@ namespace vk struct memory_block { - memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable = false); + memory_block(VkDevice dev, const memory_allocation_request& alloc_request); virtual ~memory_block(); virtual VkDeviceMemory get_vk_device_memory(); diff --git a/rpcs3/Emu/RSX/VK/vkutils/metal_layer.h b/rpcs3/Emu/RSX/VK/vkutils/metal_layer.h new file mode 100644 index 0000000000..d587b55bf7 --- /dev/null +++ b/rpcs3/Emu/RSX/VK/vkutils/metal_layer.h @@ -0,0 +1,2 @@ +#pragma once +void* GetCAMetalLayerFromMetalView(void* view); diff --git a/rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm b/rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm new file mode 100644 index 0000000000..5c593bc5b6 --- /dev/null +++ b/rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm @@ -0,0 +1,10 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wmissing-declarations" +#import +#import +#import + +void* GetCAMetalLayerFromMetalView(void* view) { return ((NSView*)view).layer; } +#pragma GCC diagnostic pop diff --git a/rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp b/rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp index 9e4217692d..e4485324d5 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp +++ b/rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp @@ -1,6 +1,7 @@ #pragma once #include "swapchain_core.h" +#include "metal_layer.h" namespace vk { @@ -12,11 +13,11 @@ namespace vk VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* /*config*/) { VkSurfaceKHR result = VK_NULL_HANDLE; - VkMacOSSurfaceCreateInfoMVK createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; - createInfo.pView = window_handle; + VkMetalSurfaceCreateInfoEXT createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + createInfo.pLayer = GetCAMetalLayerFromMetalView(window_handle); - CHECK_RESULT(vkCreateMacOSSurfaceMVK(vk_instance, &createInfo, NULL, &result)); + CHECK_RESULT(vkCreateMetalSurfaceEXT(vk_instance, &createInfo, NULL, &result)); return result; } #endif diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index dd6c3506b2..651c220b60 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1866,8 +1866,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // PS1 Classic located in dev_hdd0/game sys_log.notice("PS1 Game: %s, %s", m_title_id, m_title); - const std::string tail = m_path.substr(hdd0_game.size()); - const std::string dirname = fmt::trim_front(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)); + const std::string_view tail = std::string_view(m_path).substr(hdd0_game.size()); + const std::string dirname = std::string(fmt::trim_front_sv(tail, fs::delim).substr(0, tail.find_first_of(fs::delim))); const std::string game_path = "/dev_hdd0/game/" + dirname; argv.resize(9); @@ -1894,8 +1894,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // PSP Remaster located in dev_hdd0/game sys_log.notice("PSP Remaster Game: %s, %s", m_title_id, m_title); - const std::string tail = m_path.substr(hdd0_game.size()); - const std::string dirname = fmt::trim_front(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)); + const std::string_view tail = std::string_view(m_path).substr(hdd0_game.size()); + const std::string dirname = std::string(fmt::trim_front_sv(tail, fs::delim).substr(0, tail.find_first_of(fs::delim))); const std::string game_path = "/dev_hdd0/game/" + dirname; argv.resize(2); @@ -1913,7 +1913,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // Add HG games not in HDD0 to games.yml [[maybe_unused]] const games_config::result res = m_games_config.add_external_hdd_game(m_title_id, game_dir); - const std::string dir = fmt::trim(game_dir.substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim); + const std::string dir = std::string(fmt::trim_sv(std::string_view(game_dir).substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim)); vfs::mount("/dev_hdd0/game/" + dir, game_dir + '/'); } } @@ -2265,10 +2265,10 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, auto unescape = [](std::string_view path) { // Unescape from host FS - std::vector escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); + const std::vector escaped = fmt::split_sv(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); std::vector result; - for (auto& sv : escaped) - result.emplace_back(vfs::unescape(sv)); + for (const auto& sv : escaped) + result.push_back(vfs::unescape(sv)); return fmt::merge(result, "/"); }; @@ -2315,7 +2315,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, game_dir = game_dir.substr(0, game_dir.size() - 4); } - const std::string dir = fmt::trim(game_dir.substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim); + const std::string dir = std::string(fmt::trim_sv(std::string_view(game_dir).substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim)); m_dir = "/dev_hdd0/game/" + dir + '/'; argv[0] = m_dir + unescape(resolved_path.substr(GetCallbacks().resolve_path(game_dir).size())); diff --git a/rpcs3/Emu/VFS.cpp b/rpcs3/Emu/VFS.cpp index 8484c57246..f5963ca7c1 100644 --- a/rpcs3/Emu/VFS.cpp +++ b/rpcs3/Emu/VFS.cpp @@ -137,7 +137,7 @@ bool vfs::unmount(std::string_view vpath) return false; } - const std::vector entry_list = fmt::split(vpath, {"/"}); + const std::vector entry_list = fmt::split_sv(vpath, {"/"}); if (entry_list.empty()) { @@ -166,7 +166,7 @@ bool vfs::unmount(std::string_view vpath) } // Get the current name based on the depth - const std::string& name = ::at32(entry_list, depth); + const std::string_view name = ::at32(entry_list, depth); // Go through all children of this node for (auto it = dir.dirs.begin(); it != dir.dirs.end();) @@ -456,10 +456,10 @@ std::string vfs::retrieve(std::string_view path, const vfs_directory* node, std: auto unescape_path = [](std::string_view path) { // Unescape from host FS - std::vector escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); + const std::vector escaped = fmt::split_sv(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); std::vector result; - for (auto& sv : escaped) - result.emplace_back(vfs::unescape(sv)); + for (const auto& sv : escaped) + result.push_back(vfs::unescape(sv)); return fmt::merge(result, "/"); }; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 2b45ce82a0..7c7b89fcf9 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -36,7 +36,7 @@ struct cfg_root : cfg::node cfg::_int<0, 16> spu_delay_penalty{ this, "SPU delay penalty", 3 }; // Number of milliseconds to block a thread if a virtual 'core' isn't free cfg::_bool spu_loop_detection{ this, "SPU loop detection", false }; // Try to detect wait loops and trigger thread yield cfg::_int<1, 6> max_spurs_threads{ this, "Max SPURS Threads", 6, true }; // HACK. If less then 6, max number of running SPURS threads in each thread group. - cfg::_enum spu_block_size{ this, "SPU Analyzer Block Size", spu_block_size_type::mega }; + cfg::_enum spu_block_size{ this, "SPU Block Size", spu_block_size_type::safe }; cfg::_bool spu_accurate_dma{ this, "Accurate SPU DMA", false }; cfg::_bool spu_accurate_reservations{ this, "Accurate SPU Reservations", true }; cfg::_bool accurate_cache_line_stores{ this, "Accurate Cache Line Stores", false }; @@ -322,6 +322,7 @@ struct cfg_root : cfg::node cfg::_enum psn_status{this, "PSN status", np_psn_status::disabled}; cfg::string country{this, "PSN Country", "us"}; + cfg::_bool clans_enabled{this, "Clans Enabled", false}; } net{this}; struct node_savestate : cfg::node diff --git a/rpcs3/Emu/system_utils.cpp b/rpcs3/Emu/system_utils.cpp index e840887bac..3cf886d06d 100644 --- a/rpcs3/Emu/system_utils.cpp +++ b/rpcs3/Emu/system_utils.cpp @@ -184,6 +184,11 @@ namespace rpcs3::utils return g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, get_emu_dir()); } + std::string get_hdd0_game_dir() + { + return get_hdd0_dir() + "game/"; + } + u64 get_cache_disk_usage() { if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_cache_dir(), 1); data_size != umax) diff --git a/rpcs3/Emu/system_utils.hpp b/rpcs3/Emu/system_utils.hpp index b4142dacb9..c2825abb24 100644 --- a/rpcs3/Emu/system_utils.hpp +++ b/rpcs3/Emu/system_utils.hpp @@ -34,6 +34,8 @@ namespace rpcs3::utils std::string get_flash3_dir(); std::string get_bdvd_dir(); + std::string get_hdd0_game_dir(); + // Cache directories and disk usage u64 get_cache_disk_usage(); std::string get_cache_dir(); diff --git a/rpcs3/Input/evdev_joystick_handler.cpp b/rpcs3/Input/evdev_joystick_handler.cpp index 6ddb0ff989..03f2016c27 100644 --- a/rpcs3/Input/evdev_joystick_handler.cpp +++ b/rpcs3/Input/evdev_joystick_handler.cpp @@ -1346,7 +1346,7 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr pad) const auto find_buttons = [&](const cfg::string& name) -> std::set { - const std::vector names = cfg_pad::get_buttons(name); + const std::vector names = cfg_pad::get_buttons(name.to_string()); // In evdev we store indices to an EvdevButton vector in our pad objects instead of the usual key codes. std::set indices; diff --git a/rpcs3/Input/keyboard_pad_handler.cpp b/rpcs3/Input/keyboard_pad_handler.cpp index c9da8dcb29..e70cf8d45b 100644 --- a/rpcs3/Input/keyboard_pad_handler.cpp +++ b/rpcs3/Input/keyboard_pad_handler.cpp @@ -835,7 +835,7 @@ std::string keyboard_pad_handler::GetKeyName(const u32& keyCode) std::set keyboard_pad_handler::GetKeyCodes(const cfg::string& cfg_string) { std::set key_codes; - for (const std::string& key_name : cfg_pad::get_buttons(cfg_string)) + for (const std::string& key_name : cfg_pad::get_buttons(cfg_string.to_string())) { if (u32 code = GetKeyCode(QString::fromStdString(key_name)); code != Qt::NoButton) { diff --git a/rpcs3/Input/mm_joystick_handler.cpp b/rpcs3/Input/mm_joystick_handler.cpp index cbe9b60223..fed1f428df 100644 --- a/rpcs3/Input/mm_joystick_handler.cpp +++ b/rpcs3/Input/mm_joystick_handler.cpp @@ -148,7 +148,7 @@ std::vector mm_joystick_handler::list_devices() template std::set mm_joystick_handler::find_keys(const cfg::string& cfg_string) const { - return find_keys(cfg_pad::get_buttons(cfg_string)); + return find_keys(cfg_pad::get_buttons(cfg_string.to_string())); } template diff --git a/rpcs3/Input/ps_move_handler.cpp b/rpcs3/Input/ps_move_handler.cpp index acf5fac3b7..059ffee2bf 100644 --- a/rpcs3/Input/ps_move_handler.cpp +++ b/rpcs3/Input/ps_move_handler.cpp @@ -176,6 +176,9 @@ void ps_move_handler::init_config(cfg_pad* cfg) cfg->ltriggerthreshold.def = 0; // between 0 and 255 cfg->rtriggerthreshold.def = 0; // between 0 and 255 + // We have to enable orientation by default + cfg->orientation_enabled.def = true; + // apply defaults cfg->from_default(); } @@ -608,7 +611,17 @@ std::unordered_map ps_move_handler::get_button_values(const std::share const u16 extra_buttons = input.sequence_number << 8 | input.buttons_3; key_buf[ps_move_key_codes::ps] = (extra_buttons & button_flags::ps) ? 255 : 0; key_buf[ps_move_key_codes::move] = (extra_buttons & button_flags::move) ? 255 : 0; - key_buf[ps_move_key_codes::t] = (extra_buttons & button_flags::t) ? input.trigger_2 : 0; + + u16 trigger = 0; + if (extra_buttons & button_flags::t) + { + switch (dev->model) + { + case ps_move_model::ZCM1: trigger = (input.trigger_1 + input.trigger_2) / 2; break; + case ps_move_model::ZCM2: trigger = input.trigger_1; break; + } + } + key_buf[ps_move_key_codes::t] = trigger; dev->battery_level = input.battery_level; @@ -675,12 +688,20 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding) if (dev->model == ps_move_model::ZCM1) { - accel_x -= static_cast(zero_shift); - accel_y -= static_cast(zero_shift); - accel_z -= static_cast(zero_shift); - gyro_x -= static_cast(zero_shift); - gyro_y -= static_cast(zero_shift); - gyro_z -= static_cast(zero_shift); + const auto decode_16bit = [](s16 val) + { + const u8* data = reinterpret_cast(&val); + const u8 low = data[0] & 0xFF; + const u8 high = data[1] & 0xFF; + const s32 res = (low | (high << 8)) - zero_shift; + return static_cast(res); + }; + accel_x = decode_16bit(input.accel_x_1); + accel_y = decode_16bit(input.accel_y_1); + accel_z = decode_16bit(input.accel_z_1); + gyro_x = decode_16bit(input.gyro_x_1); + gyro_y = decode_16bit(input.gyro_y_1); + gyro_z = decode_16bit(input.gyro_z_1); } if (!device->config || !device->config->orientation_enabled) @@ -711,21 +732,21 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding) gyro_z /= MOVE_ONE_G; } - pad->move_data.accelerometer_x = accel_x; - pad->move_data.accelerometer_y = accel_y; - pad->move_data.accelerometer_z = accel_z; - pad->move_data.gyro_x = gyro_x; - pad->move_data.gyro_y = gyro_y; - pad->move_data.gyro_z = gyro_z; + pad->move_data.accelerometer.x() = accel_x; + pad->move_data.accelerometer.y() = accel_y; + pad->move_data.accelerometer.z() = accel_z; + pad->move_data.gyro.x() = gyro_x; + pad->move_data.gyro.y() = gyro_y; + pad->move_data.gyro.z() = gyro_z; if (dev->model == ps_move_model::ZCM1) { const ps_move_input_report_ZCM1& input_zcm1 = dev->input_report_ZCM1; #define TWELVE_BIT_SIGNED(x) (((x) & 0x800) ? (-(((~(x)) & 0xFFF) + 1)) : (x)) - pad->move_data.magnetometer_x = static_cast(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2)); - pad->move_data.magnetometer_y = static_cast(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4)); - pad->move_data.magnetometer_z = static_cast(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z)); + pad->move_data.magnetometer.x() = static_cast(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2)); + pad->move_data.magnetometer.y() = static_cast(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4)); + pad->move_data.magnetometer.z() = static_cast(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z)); } } diff --git a/rpcs3/Input/ps_move_tracker.cpp b/rpcs3/Input/ps_move_tracker.cpp index d3deead687..d7e21a2193 100644 --- a/rpcs3/Input/ps_move_tracker.cpp +++ b/rpcs3/Input/ps_move_tracker.cpp @@ -13,14 +13,21 @@ LOG_CHANNEL(ps_move); namespace gem { - extern bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format, + extern bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc, const std::vector& video_data_in, u32 width, u32 height, - u8* video_data_out, u32 video_data_out_size, std::string_view caller); + u8* video_data_out, u32 video_data_out_size, u8* buffer_memory, + std::string_view caller); } template ps_move_tracker::ps_move_tracker() { + m_vc_attr.alpha = 255; + m_vc_attr.gain = 1.0f; + m_vc_attr.red_gain = 1.0f; + m_vc_attr.green_gain = 1.0f; + m_vc_attr.blue_gain = 1.0f; + init_workers(); } @@ -238,7 +245,9 @@ void ps_move_tracker::convert_image(s32 output_format) m_image_binary[index].resize(size); } - if (gem::convert_image_format(CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba), "gemTracker")) + m_vc_attr.output_format = CellGemVideoConvertFormatEnum{output_format}; + + if (gem::convert_image_format(CellCameraFormat{m_format}, m_vc_attr, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba), nullptr, "gemTracker")) { ps_move.trace("Converted video frame of format %s to %s", CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}); } diff --git a/rpcs3/Input/ps_move_tracker.h b/rpcs3/Input/ps_move_tracker.h index a1fa0e8936..29fa8652d5 100644 --- a/rpcs3/Input/ps_move_tracker.h +++ b/rpcs3/Input/ps_move_tracker.h @@ -77,6 +77,8 @@ private: void draw_sphere_size_range(f32 result_radius); + CellGemVideoConvertAttribute m_vc_attr {}; + u32 m_width = 0; u32 m_height = 0; s32 m_format = 0; diff --git a/rpcs3/Loader/disc.cpp b/rpcs3/Loader/disc.cpp index b2ea4dc855..9ae33e42e0 100644 --- a/rpcs3/Loader/disc.cpp +++ b/rpcs3/Loader/disc.cpp @@ -96,7 +96,7 @@ namespace disc for (usz i = 0; i < lines.size(); i++) { - const std::string& line = lines[i]; + const std::string_view line = lines[i]; const usz pos = line.find('='); if (pos == umax) @@ -104,12 +104,12 @@ namespace disc continue; } - const std::string key = fmt::trim(line.substr(0, pos)); - std::string value; + const std::string_view key = fmt::trim_sv(line.substr(0, pos)); + std::string_view value; if (pos != (line.size() - 1)) { - value = fmt::trim(line.substr(pos + 1)); + value = fmt::trim_sv(line.substr(pos + 1)); } if (value.empty() && i != (lines.size() - 1) && line.size() != 1) diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 1b5716f01b..63f080c5c9 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -40,7 +40,7 @@ Use - ..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(SolutionDir)3rdparty\glslang\glslang + ..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(SolutionDir)3rdparty\glslang\glslang;$(SolutionDir)3rdparty\curl\curl\include MaxSpeed AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions) AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions) @@ -91,6 +91,7 @@ + @@ -111,6 +112,7 @@ + @@ -156,8 +158,11 @@ + + - + + @@ -184,6 +189,7 @@ + @@ -601,6 +607,7 @@ + @@ -701,8 +708,11 @@ + + - + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 23b7ef174d..17ccca3792 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -136,6 +136,12 @@ {d99df916-8a99-428b-869a-9f14ac0ab411} + + {d13db076-47e4-45b9-bb8a-6b711ea40622} + + + {7fb59544-9761-4b4a-bb04-07deb43cf3c2} + @@ -1354,9 +1360,6 @@ Emu\Cell - - Emu\GPU\RSX\Program - Utilities @@ -1378,6 +1381,21 @@ Emu\GPU\RSX\Program\Assembler + + Emu\GPU\RSX\Program\Assembler\Passes\FP + + + Emu\GPU\RSX\Program\Assembler\Passes\FP + + + Emu\GPU\RSX\Program\Assembler + + + Emu\GPU\RSX\Program\Assembler + + + Emu\Io + @@ -2746,9 +2764,6 @@ Emu\Audio - - Emu\GPU\RSX\Program - Utilities @@ -2776,6 +2791,21 @@ Emu\GPU\RSX\Program\Assembler + + Emu\GPU\RSX\Program\Assembler + + + Emu\GPU\RSX\Program\Assembler\Passes\FP + + + Emu\GPU\RSX\Program\Assembler\Passes\FP + + + Emu\GPU\RSX\Program\Assembler + + + Emu\Io + diff --git a/rpcs3/rpcs3.plist.in b/rpcs3/rpcs3.plist.in index 53902adea3..a854779d9d 100644 --- a/rpcs3/rpcs3.plist.in +++ b/rpcs3/rpcs3.plist.in @@ -33,7 +33,7 @@ LSApplicationCategoryType public.app-category.games LSMinimumSystemVersion - 14.0 + 14.4 NSCameraUsageDescription The camera will be used for PlayStation Eye emulation NSMicrophoneUsageDescription diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index b5ffb8ebd8..d8d5053179 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -406,6 +406,9 @@ true + + true + true @@ -697,6 +700,9 @@ true + + true + true @@ -807,6 +813,7 @@ + @@ -1488,6 +1495,16 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing %(Identity)... diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index aa90e50cd0..2e9340b01b 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -864,6 +864,12 @@ Generated Files\Release + + Generated Files\Debug + + + Generated Files\Release + Generated Files\Debug @@ -909,6 +915,9 @@ Gui\rpcn + + Gui\clans + Gui\message dialog @@ -1699,6 +1708,9 @@ Gui\rpcn + + Gui\rpcn + Gui\message dialog diff --git a/rpcs3/rpcs3_version.cpp b/rpcs3/rpcs3_version.cpp index 8d789d2ba9..ab18b56caf 100644 --- a/rpcs3/rpcs3_version.cpp +++ b/rpcs3/rpcs3_version.cpp @@ -17,11 +17,11 @@ namespace rpcs3 std::pair get_commit_and_hash() { - const auto commit_and_hash = fmt::split(RPCS3_GIT_VERSION, {"-"}); + auto commit_and_hash = fmt::split(RPCS3_GIT_VERSION, {"-"}); if (commit_and_hash.size() != 2) return std::make_pair("0", "00000000"); - return std::make_pair(commit_and_hash[0], commit_and_hash[1]); + return std::make_pair(std::move(commit_and_hash[0]), std::move(commit_and_hash[1])); } // TODO: Make this accessible from cmake and keep in sync with MACOSX_BUNDLE_BUNDLE_VERSION. diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 43c0024905..98be856a25 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(rpcs3_ui STATIC camera_settings_dialog.cpp cg_disasm_window.cpp cheat_manager.cpp + clans_settings_dialog.cpp config_adapter.cpp config_checker.cpp curl_handle.cpp diff --git a/rpcs3/rpcs3qt/camera_settings_dialog.cpp b/rpcs3/rpcs3qt/camera_settings_dialog.cpp index 0cc5843595..da3576183a 100644 --- a/rpcs3/rpcs3qt/camera_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/camera_settings_dialog.cpp @@ -70,8 +70,8 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent) camera_log.notice("Found camera: '%s'", camera_info.description()); } - connect(ui->combo_camera, QOverload::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_camera_change); - connect(ui->combo_settings, QOverload::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_settings_change); + connect(ui->combo_camera, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_camera_change); + connect(ui->combo_settings, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_settings_change); connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { if (button == ui->buttonBox->button(QDialogButtonBox::Save)) diff --git a/rpcs3/rpcs3qt/cheat_manager.cpp b/rpcs3/rpcs3qt/cheat_manager.cpp index e6b760a0a2..a24a2cd5fc 100644 --- a/rpcs3/rpcs3qt/cheat_manager.cpp +++ b/rpcs3/rpcs3qt/cheat_manager.cpp @@ -129,11 +129,11 @@ void cheat_engine::save() const cheat_file.write(out.c_str(), out.size()); } -void cheat_engine::import_cheats_from_str(const std::string& str_cheats) +void cheat_engine::import_cheats_from_str(std::string_view str_cheats) { - auto cheats_vec = fmt::split(str_cheats, {"^^^"}); + const auto cheats_vec = fmt::split_sv(str_cheats, {"^^^"}); - for (auto& cheat_line : cheats_vec) + for (const auto& cheat_line : cheats_vec) { cheat_info new_cheat; if (new_cheat.from_str(cheat_line)) diff --git a/rpcs3/rpcs3qt/cheat_manager.h b/rpcs3/rpcs3qt/cheat_manager.h index 458c7400b9..b915faa8a2 100644 --- a/rpcs3/rpcs3qt/cheat_manager.h +++ b/rpcs3/rpcs3qt/cheat_manager.h @@ -25,7 +25,7 @@ public: cheat_info* get(const std::string& game, const u32 offset); bool erase(const std::string& game, const u32 offset); - void import_cheats_from_str(const std::string& str_cheats); + void import_cheats_from_str(std::string_view str_cheats); std::string export_cheats_to_str() const; void save() const; diff --git a/rpcs3/rpcs3qt/clans_settings_dialog.cpp b/rpcs3/rpcs3qt/clans_settings_dialog.cpp new file mode 100644 index 0000000000..9745d11294 --- /dev/null +++ b/rpcs3/rpcs3qt/clans_settings_dialog.cpp @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "clans_settings_dialog.h" +#include "Emu/NP/clans_config.h" + +clans_settings_dialog::clans_settings_dialog(QWidget* parent) + : QDialog(parent) +{ + g_cfg_clans.load(); + + setWindowTitle(tr("Clans Configuration")); + setObjectName("clans_settings_dialog"); + + QVBoxLayout* vbox_global = new QVBoxLayout(); + + QGroupBox* grp_server = new QGroupBox(tr("Server:")); + QVBoxLayout* vbox_server = new QVBoxLayout(); + + QHBoxLayout* hbox_lbl_combo = new QHBoxLayout(); + QLabel* lbl_server = new QLabel(tr("Server:")); + m_cbx_servers = new QComboBox(); + m_cbx_protocol = new QComboBox(); + + m_cbx_protocol->addItem("HTTPS"); + m_cbx_protocol->addItem("HTTP"); + m_cbx_protocol->setCurrentIndex(g_cfg_clans.get_use_https() ? 0 : 1); + + refresh_combobox(); + + hbox_lbl_combo->addWidget(lbl_server); + hbox_lbl_combo->addWidget(m_cbx_servers); + hbox_lbl_combo->addWidget(m_cbx_protocol); + + QHBoxLayout* hbox_buttons = new QHBoxLayout(); + QPushButton* btn_add_server = new QPushButton(tr("Add")); + QPushButton* btn_del_server = new QPushButton(tr("Del")); + hbox_buttons->addStretch(); + hbox_buttons->addWidget(btn_add_server); + hbox_buttons->addWidget(btn_del_server); + + vbox_server->addLayout(hbox_lbl_combo); + vbox_server->addLayout(hbox_buttons); + + grp_server->setLayout(vbox_server); + vbox_global->addWidget(grp_server); + + setLayout(vbox_global); + + connect(m_cbx_servers, &QComboBox::currentIndexChanged, this, [this](int index) + { + if (index < 0) + return; + + QVariant host = m_cbx_servers->itemData(index); + + if (!host.isValid() || !host.canConvert()) + return; + + g_cfg_clans.set_host(host.toString().toStdString()); + g_cfg_clans.save(); + }); + + connect(m_cbx_protocol, &QComboBox::currentIndexChanged, this, [this](int index) + { + if (index < 0) + return; + + g_cfg_clans.set_use_https(index == 0); + g_cfg_clans.save(); + }); + + connect(btn_add_server, &QAbstractButton::clicked, this, [this]() + { + clans_add_server_dialog dlg(this); + dlg.exec(); + const auto& new_server = dlg.get_new_server(); + if (new_server) + { + if (!g_cfg_clans.add_host(new_server->first, new_server->second)) + { + QMessageBox::critical(this, tr("Existing Server"), tr("You already have a server with this description & hostname in the list."), QMessageBox::Ok); + return; + } + + g_cfg_clans.save(); + refresh_combobox(); + } + }); + + connect(btn_del_server, &QAbstractButton::clicked, this, [this]() + { + const int index = m_cbx_servers->currentIndex(); + + if (index < 0) + return; + + const std::string desc = m_cbx_servers->itemText(index).toStdString(); + const std::string host = m_cbx_servers->itemData(index).toString().toStdString(); + + if (g_cfg_clans.del_host(desc, host)) + { + g_cfg_clans.save(); + refresh_combobox(); + } + else + { + QMessageBox::warning(this, tr("Cannot Delete"), tr("This server cannot be deleted."), QMessageBox::Ok); + } + }); +} + +void clans_settings_dialog::refresh_combobox() +{ + g_cfg_clans.load(); + const auto vec_hosts = g_cfg_clans.get_hosts(); + const auto cur_host = g_cfg_clans.get_host(); + int i = 0, index = 0; + + m_cbx_servers->clear(); + + for (const auto& [desc, host] : vec_hosts) + { + m_cbx_servers->addItem(QString::fromStdString(desc), QString::fromStdString(host)); + if (cur_host == host) + index = i; + + i++; + } + + m_cbx_servers->setCurrentIndex(index); +} + +clans_add_server_dialog::clans_add_server_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("Clans: Add Server")); + setObjectName("clans_add_server_dialog"); + setMinimumSize(QSize(400, 200)); + + QVBoxLayout* vbox_global = new QVBoxLayout(); + + QLabel* lbl_description = new QLabel(tr("Description:")); + QLineEdit* edt_description = new QLineEdit(); + QLabel* lbl_host = new QLabel(tr("Host:")); + QLineEdit* edt_host = new QLineEdit(); + QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + vbox_global->addWidget(lbl_description); + vbox_global->addWidget(edt_description); + vbox_global->addWidget(lbl_host); + vbox_global->addWidget(edt_host); + vbox_global->addWidget(btn_box); + + setLayout(vbox_global); + + connect(btn_box, &QDialogButtonBox::accepted, this, [this, edt_description, edt_host]() + { + const QString description = edt_description->text(); + const QString host = edt_host->text(); + + if (description.isEmpty()) + { + QMessageBox::critical(this, tr("Missing Description!"), tr("You must enter a description!"), QMessageBox::Ok); + return; + } + if (host.isEmpty()) + { + QMessageBox::critical(this, tr("Missing Hostname!"), tr("You must enter a hostname for the server!"), QMessageBox::Ok); + return; + } + + m_new_server = std::make_pair(description.toStdString(), host.toStdString()); + QDialog::accept(); + }); + connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +const std::optional>& clans_add_server_dialog::get_new_server() const +{ + return m_new_server; +} diff --git a/rpcs3/rpcs3qt/clans_settings_dialog.h b/rpcs3/rpcs3qt/clans_settings_dialog.h new file mode 100644 index 0000000000..7de6105382 --- /dev/null +++ b/rpcs3/rpcs3qt/clans_settings_dialog.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +class clans_settings_dialog : public QDialog +{ + Q_OBJECT +public: + clans_settings_dialog(QWidget* parent = nullptr); + +private: + void refresh_combobox(); + +private: + QComboBox* m_cbx_servers = nullptr; + QComboBox* m_cbx_protocol = nullptr; +}; + +class clans_add_server_dialog : public QDialog +{ + Q_OBJECT +public: + clans_add_server_dialog(QWidget* parent = nullptr); + const std::optional>& get_new_server() const; + +private: + std::optional> m_new_server; +}; diff --git a/rpcs3/rpcs3qt/debugger_frame.cpp b/rpcs3/rpcs3qt/debugger_frame.cpp index 8eb4bce3fa..7ffe1b32a2 100644 --- a/rpcs3/rpcs3qt/debugger_frame.cpp +++ b/rpcs3/rpcs3qt/debugger_frame.cpp @@ -126,7 +126,7 @@ debugger_frame::debugger_frame(std::shared_ptr gui_settings, QWidg m_regs->setLineWrapMode(QPlainTextEdit::NoWrap); m_regs->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); m_regs->setContextMenuPolicy(Qt::CustomContextMenu); - + m_debugger_list->setFont(m_mono); m_misc_state->setFont(m_mono); m_regs->setFont(m_mono); @@ -180,7 +180,7 @@ debugger_frame::debugger_frame(std::shared_ptr gui_settings, QWidg m_choice_units->clearFocus(); }); - connect(m_choice_units, QOverload::of(&QComboBox::currentIndexChanged), this, [&](){ m_is_spu_disasm_mode = false; OnSelectUnit(); }); + connect(m_choice_units, &QComboBox::currentIndexChanged, this, [&](){ m_is_spu_disasm_mode = false; OnSelectUnit(); }); connect(this, &QDockWidget::visibilityChanged, this, &debugger_frame::EnableUpdateTimer); connect(m_debugger_list, &debugger_list::BreakpointRequested, m_breakpoint_list, &breakpoint_list::HandleBreakpointRequest); @@ -1082,7 +1082,7 @@ void debugger_frame::UpdateUnitList() if (reselected_index != umax) { - // Include no-thread at index 0 + // Include no-thread at index 0 m_choice_units->setCurrentIndex(::narrow(reselected_index + 1)); } } diff --git a/rpcs3/rpcs3qt/dimensions_dialog.cpp b/rpcs3/rpcs3qt/dimensions_dialog.cpp index a6eb07018e..4ea4c7dc19 100644 --- a/rpcs3/rpcs3qt/dimensions_dialog.cpp +++ b/rpcs3/rpcs3qt/dimensions_dialog.cpp @@ -433,7 +433,7 @@ minifig_creator_dialog::minifig_creator_dialog(QWidget* parent) setLayout(vbox_panel); - connect(combo_figlist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) + connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index) { const u16 fig_info = combo_figlist->itemData(index).toUInt(); if (fig_info != 0xFFFF) @@ -486,7 +486,7 @@ minifig_creator_dialog::minifig_creator_dialog(QWidget* parent) connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); - connect(co_compl, QOverload::of(&QCompleter::activated), [=](const QString& text) + connect(co_compl, qOverload(&QCompleter::activated), [=](const QString& text) { combo_figlist->setCurrentIndex(combo_figlist->findText(text)); }); diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 37692423a3..935bfd2cc8 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -375,7 +375,7 @@ void emu_settings::EnhanceComboBox(QComboBox* combobox, emu_settings_type type, combobox->setCurrentIndex(index); - connect(combobox, QOverload::of(&QComboBox::currentIndexChanged), combobox, [this, is_ranged, combobox, type](int index) + connect(combobox, &QComboBox::currentIndexChanged, combobox, [this, is_ranged, combobox, type](int index) { if (index < 0) return; @@ -973,9 +973,9 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case emu_settings_type::SPUBlockSize: switch (static_cast(index)) { - case spu_block_size_type::safe: return tr("Safe", "SPU Analyzer Block Size"); - case spu_block_size_type::mega: return tr("Mega", "SPU Analyzer Block Size"); - case spu_block_size_type::giga: return tr("Giga", "SPU Analyzer Block Size"); + case spu_block_size_type::safe: return tr("Safe", "SPU block size"); + case spu_block_size_type::mega: return tr("Mega", "SPU block size"); + case spu_block_size_type::giga: return tr("Giga", "SPU block size"); } break; case emu_settings_type::ThreadSchedulerMode: diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 7e69656eac..cc8ab57b96 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -200,6 +200,7 @@ enum class emu_settings_type BindAddress, EnableUpnp, PSNCountry, + EnableClans, // System LicenseArea, @@ -239,7 +240,7 @@ inline static const std::map settings_location { emu_settings_type::XFloatAccuracy, { "Core", "XFloat Accuracy"}}, { emu_settings_type::MFCCommandsShuffling, { "Core", "MFC Commands Shuffling Limit"}}, { emu_settings_type::SetDAZandFTZ, { "Core", "Set DAZ and FTZ"}}, - { emu_settings_type::SPUBlockSize, { "Core", "SPU Analyzer Block Size"}}, + { emu_settings_type::SPUBlockSize, { "Core", "SPU Block Size"}}, { emu_settings_type::SPUCache, { "Core", "SPU Cache"}}, { emu_settings_type::DebugConsoleMode, { "Core", "Debug Console Mode"}}, { emu_settings_type::MaxSPURSThreads, { "Core", "Max SPURS Threads"}}, @@ -411,6 +412,7 @@ inline static const std::map settings_location { emu_settings_type::BindAddress, { "Net", "Bind address"}}, { emu_settings_type::EnableUpnp, { "Net", "UPNP Enabled"}}, { emu_settings_type::PSNCountry, { "Net", "PSN Country"}}, + { emu_settings_type::EnableClans, { "Net", "Clans Enabled"}}, // System { emu_settings_type::LicenseArea, { "System", "License Area"}}, diff --git a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp index aee4c44c77..c0abb6e5e1 100644 --- a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp @@ -302,7 +302,7 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) combo->setCurrentIndex(combo->findData(static_cast(saved_btn_id))); - connect(combo, QOverload::of(&QComboBox::currentIndexChanged), this, [this, player, id, combo](int index) + connect(combo, &QComboBox::currentIndexChanged, this, [this, player, id, combo](int index) { if (index < 0 || !combo) return; diff --git a/rpcs3/rpcs3qt/game_compatibility.cpp b/rpcs3/rpcs3qt/game_compatibility.cpp index 2d901b6642..2845659716 100644 --- a/rpcs3/rpcs3qt/game_compatibility.cpp +++ b/rpcs3/rpcs3qt/game_compatibility.cpp @@ -272,6 +272,10 @@ compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, gam info.category = QString::fromStdString(std::string(psf::get_string(psf, "CATEGORY"))); info.version = QString::fromStdString(std::string(psf::get_string(psf, "APP_VER"))); + // Technically, there is no specific package's header info providing its installation size on disk. + // We use "data_size" header as an approximation (a bit larger) for this purpose + info.data_size = reader.get_header().data_size.value(); + if (!info.category.isEmpty()) { const Localized localized; diff --git a/rpcs3/rpcs3qt/game_compatibility.h b/rpcs3/rpcs3qt/game_compatibility.h index f24ebfbe1f..664bafea34 100644 --- a/rpcs3/rpcs3qt/game_compatibility.h +++ b/rpcs3/rpcs3qt/game_compatibility.h @@ -1,5 +1,7 @@ #pragma once +#include "util/types.hpp" + #include #include @@ -108,6 +110,7 @@ namespace compat QString version; // May be empty QString category; // HG, DG, GD etc. QString local_cat; // Localized category + u64 data_size = 0; // Installation size package_type type = package_type::other; // The type of package (Update, DLC or other) }; diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 7bf27c4695..f68bc44896 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -185,13 +185,13 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std connect(m_game_list, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); connect(m_game_list, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot); - connect(m_game_list, &QTableWidget::itemDoubleClicked, this, QOverload::of(&game_list_frame::doubleClickedSlot)); + connect(m_game_list, &QTableWidget::itemDoubleClicked, this, qOverload(&game_list_frame::doubleClickedSlot)); connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this, &game_list_frame::OnColClicked); connect(m_game_grid, &QWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); connect(m_game_grid, &game_list_grid::ItemSelectionChanged, this, &game_list_frame::NotifyGameSelection); - connect(m_game_grid, &game_list_grid::ItemDoubleClicked, this, QOverload::of(&game_list_frame::doubleClickedSlot)); + connect(m_game_grid, &game_list_grid::ItemDoubleClicked, this, qOverload(&game_list_frame::doubleClickedSlot)); connect(m_game_compat, &game_compatibility::DownloadStarted, this, [this]() { @@ -1383,7 +1383,7 @@ void game_list_frame::ShowContextMenu(const QPoint& pos) const std::string hdd1_cache_base_dir = rpcs3::utils::get_hdd1_dir() + "caches/"; const bool has_hdd1_cache_dir = !GetDirListBySerial(hdd1_cache_base_dir, current_game.serial).empty(); - + if (has_hdd1_cache_dir) { QAction* remove_hdd1_cache = remove_menu->addAction(tr("&Remove HDD1 Cache")); @@ -1856,7 +1856,7 @@ void game_list_frame::ShowContextMenu(const QPoint& pos) { text += tr("\nCurrent free disk space: %0\n").arg(gui::utils::format_byte_size(stat.avail_free)); } - + if (has_data_dir) { text += tr("\nPermanently remove %0 and selected (optional) contents from drive?\n").arg(is_disc_game ? tr("Game Data") : gameinfo->localized_category); diff --git a/rpcs3/rpcs3qt/infinity_dialog.cpp b/rpcs3/rpcs3qt/infinity_dialog.cpp index 223a753c15..7aa00e5819 100644 --- a/rpcs3/rpcs3qt/infinity_dialog.cpp +++ b/rpcs3/rpcs3qt/infinity_dialog.cpp @@ -448,7 +448,7 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot) for (const auto& [figure, entry] : list_figures) { const auto& [num, figure_name] = entry; - + // Apply series filter (0 = all, 1-3 = specific series) if (series_filter != 0 && num != series_filter) continue; @@ -486,7 +486,7 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot) co_compl->setFilterMode(Qt::MatchContains); combo_figlist->setCompleter(co_compl); - connect(co_compl, QOverload::of(&QCompleter::activated), [=](const QString& text) + connect(co_compl, qOverload(&QCompleter::activated), [=](const QString& text) { combo_figlist->setCurrentIndex(combo_figlist->findText(text)); }); @@ -536,12 +536,12 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot) filter_group->addButton(btn_series2, 2); // ID 2 for series 2 filter_group->addButton(btn_series3, 3); // ID 3 for series 3 - connect(filter_group, QOverload::of(&QButtonGroup::idClicked), [=](int id) + connect(filter_group, &QButtonGroup::idClicked, [=](int id) { populate_combo(id); }); - connect(combo_figlist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) + connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index) { const u32 fig_info = combo_figlist->itemData(index).toUInt(); if (fig_info != 0xFFFFFFFF) diff --git a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp index 71a8bd3f88..61932f4cd1 100644 --- a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp +++ b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp @@ -160,7 +160,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) setLayout(vbox_panel); - connect(combo_figlist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) + connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index) { const u16 fig_info = combo_figlist->itemData(index).toUInt(); if (fig_info != 0xFFFF) @@ -243,7 +243,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); - connect(co_compl, QOverload::of(&QCompleter::activated), [=](const QString& text) + connect(co_compl, qOverload(&QCompleter::activated), [=](const QString& text) { combo_figlist->setCurrentText(text); combo_figlist->setCurrentIndex(combo_figlist->findText(text)); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 5509a6f69c..168d08a344 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -12,6 +12,7 @@ #include "log_frame.h" #include "settings_dialog.h" #include "rpcn_settings_dialog.h" +#include "clans_settings_dialog.h" #include "auto_pause_settings_dialog.h" #include "cg_disasm_window.h" #include "log_viewer.h" @@ -860,11 +861,28 @@ bool main_window::InstallPackages(QStringList file_paths, bool from_boot) info.changelog = tr("Changelog:\n%0", "Block for Changelog").arg(info.changelog); } - const QString info_string = QStringLiteral("%0\n\n%1%2%3%4").arg(file_info.fileName()).arg(info.title).arg(info.local_cat).arg(info.title_id).arg(info.version); - QString message = tr("Do you want to install this package?\n\n%0").arg(info_string); + u64 free_space = 0; - QMessageBox mb(QMessageBox::Icon::Question, tr("PKG Decrypter / Installer"), message, QMessageBox::Yes | QMessageBox::No, this); + // Retrieve disk space info on data path's drive + if (fs::device_stat stat{}; fs::statfs(rpcs3::utils::get_hdd0_dir(), stat)) + { + free_space = stat.avail_free; + } + + const QString installation_info = + tr("Installation path: %0\nAvailable disk space: %1%2\nRequired disk space: %3") + .arg(QString::fromStdString(rpcs3::utils::get_hdd0_game_dir())) + .arg(gui::utils::format_byte_size(free_space)) + .arg(info.data_size <= free_space ? QString() : tr(" - NOT ENOUGH SPACE")) + .arg(gui::utils::format_byte_size(info.data_size)); + + const QString info_string = QStringLiteral("%0\n\n%1%2%3%4").arg(file_info.fileName()).arg(info.title).arg(info.local_cat).arg(info.title_id).arg(info.version); + QString message = tr("Do you want to install this package?\n\n%0\n\n%1").arg(info_string).arg(installation_info); + + QMessageBox mb(QMessageBox::Icon::Question, tr("PKG Decrypter / Installer"), gui::utils::make_paragraph(message), QMessageBox::Yes | QMessageBox::No, this); mb.setDefaultButton(QMessageBox::No); + mb.setTextFormat(Qt::RichText); // Support HTML tags + mb.button(QMessageBox::Yes)->setEnabled(info.data_size <= free_space); if (!info.changelog.isEmpty()) { @@ -2974,6 +2992,18 @@ void main_window::CreateConnects() dlg.exec(); }); + connect(ui->confClansAct, &QAction::triggered, this, [this]() + { + if (!Emu.IsStopped()) + { + QMessageBox::critical(this, tr("Error: Emulation Running"), tr("You need to stop the emulator before editing Clans connection information!"), QMessageBox::Ok); + return; + } + + clans_settings_dialog dlg(this); + dlg.exec(); + }); + connect(ui->confIPCAct, &QAction::triggered, this, [this]() { ipc_settings_dialog dlg(this); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 72861a5d72..c9c8c0645d 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -282,6 +282,7 @@ + @@ -1252,6 +1253,14 @@ Configure RPCN + + + Clans + + + Configure Clans + + IPC diff --git a/rpcs3/rpcs3qt/memory_string_searcher.cpp b/rpcs3/rpcs3qt/memory_string_searcher.cpp index c55cb8144d..de8ee1814f 100644 --- a/rpcs3/rpcs3qt/memory_string_searcher.cpp +++ b/rpcs3/rpcs3qt/memory_string_searcher.cpp @@ -115,11 +115,7 @@ u64 memory_viewer_panel::OnSearch(std::string wstr, u32 mode) } // Concat strings - wstr.clear(); - for (const std::string& part : parts) - { - wstr += part; - } + wstr = fmt::merge(parts, {}); if (const usz pos = wstr.find_first_not_of(hex_chars); pos != umax) { diff --git a/rpcs3/rpcs3qt/memory_viewer_panel.cpp b/rpcs3/rpcs3qt/memory_viewer_panel.cpp index 2a8b6c29b1..bcaa6537ef 100644 --- a/rpcs3/rpcs3qt/memory_viewer_panel.cpp +++ b/rpcs3/rpcs3qt/memory_viewer_panel.cpp @@ -290,7 +290,7 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr(u8"Ʌ"), group_search); button_collapse_viewer->setFixedWidth(QLabel(button_collapse_viewer->text()).sizeHint().width() * 3); button_collapse_viewer->setAutoDefault(false); - + m_search_line = new QLineEdit(group_search); m_search_line->setFixedWidth(QLabel(QString("This is the very length of the lineedit due to hidpi reasons.").chopped(4)).sizeHint().width()); m_search_line->setPlaceholderText(tr("Search...")); @@ -328,7 +328,7 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr::of(&QComboBox::currentIndexChanged), group_search, [this, button_search](int index) + connect(m_cbox_input_mode, &QComboBox::currentIndexChanged, group_search, [this, button_search](int index) { if (index < 1 || m_rsx) { @@ -1305,7 +1305,7 @@ void memory_viewer_panel::ShowAtPC(u32 pc, std::function func) if (!panel->isVisible()) panel->show(); - + panel->raise(); return; diff --git a/rpcs3/rpcs3qt/microphone_creator.cpp b/rpcs3/rpcs3qt/microphone_creator.cpp index 2821fd82e4..c44449c544 100644 --- a/rpcs3/rpcs3qt/microphone_creator.cpp +++ b/rpcs3/rpcs3qt/microphone_creator.cpp @@ -61,23 +61,23 @@ std::array microphone_creator::get_selection_list() const std::string microphone_creator::set_device(u32 num, const QString& text) { - ensure(num < m_sel_list.size()); + std::string& device = ::at32(m_sel_list, num); if (text == get_none()) - m_sel_list[num].clear(); + device.clear(); else - m_sel_list[num] = text.toStdString(); + device = text.toStdString(); return m_sel_list[0] + "@@@" + m_sel_list[1] + "@@@" + m_sel_list[2] + "@@@" + m_sel_list[3] + "@@@"; } -void microphone_creator::parse_devices(const std::string& list) +void microphone_creator::parse_devices(std::string_view list) { m_sel_list = {}; - const std::vector devices_list = fmt::split(list, { "@@@" }); + std::vector devices_list = fmt::split(list, { "@@@" }); for (usz index = 0; index < std::min(m_sel_list.size(), devices_list.size()); index++) { - m_sel_list[index] = devices_list[index]; + m_sel_list[index] = std::move(devices_list[index]); } } diff --git a/rpcs3/rpcs3qt/microphone_creator.h b/rpcs3/rpcs3qt/microphone_creator.h index 95a3827c93..37b2672083 100644 --- a/rpcs3/rpcs3qt/microphone_creator.h +++ b/rpcs3/rpcs3qt/microphone_creator.h @@ -17,7 +17,7 @@ public: microphone_creator(); QString get_none(); std::string set_device(u32 num, const QString& text); - void parse_devices(const std::string& list); + void parse_devices(std::string_view list); void refresh_list(); QStringList get_microphone_list() const; std::array get_selection_list() const; diff --git a/rpcs3/rpcs3qt/midi_creator.cpp b/rpcs3/rpcs3qt/midi_creator.cpp index 57476af3d6..d4ad70d5e9 100644 --- a/rpcs3/rpcs3qt/midi_creator.cpp +++ b/rpcs3/rpcs3qt/midi_creator.cpp @@ -104,11 +104,11 @@ std::string midi_creator::set_device(u32 num, const midi_device& device) return result; } -void midi_creator::parse_devices(const std::string& list) +void midi_creator::parse_devices(std::string_view list) { m_sel_list = {}; - const std::vector devices_list = fmt::split(list, { "@@@" }); + const std::vector devices_list = fmt::split_sv(list, { "@@@" }); for (usz index = 0; index < std::min(m_sel_list.size(), devices_list.size()); index++) { m_sel_list[index] = midi_device::from_string(devices_list[index]); diff --git a/rpcs3/rpcs3qt/midi_creator.h b/rpcs3/rpcs3qt/midi_creator.h index cfcbae3ebc..5a8cb05fd6 100644 --- a/rpcs3/rpcs3qt/midi_creator.h +++ b/rpcs3/rpcs3qt/midi_creator.h @@ -14,7 +14,7 @@ public: midi_creator(); QString get_none(); std::string set_device(u32 num, const midi_device& device); - void parse_devices(const std::string& list); + void parse_devices(std::string_view list); void refresh_list(); QStringList get_midi_list() const; std::array get_selection_list() const; diff --git a/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp index 0899096c95..13ee4bc092 100644 --- a/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp @@ -70,7 +70,7 @@ pad_motion_settings_dialog::pad_motion_settings_dialog(QDialog* parent, std::sha } } - connect(ui->cb_choose_device, QOverload::of(&QComboBox::currentIndexChanged), this, &pad_motion_settings_dialog::change_device); + connect(ui->cb_choose_device, &QComboBox::currentIndexChanged, this, &pad_motion_settings_dialog::change_device); // Combobox: Configure Axis m_motion_axis_list = m_handler->get_motion_axis_list(); @@ -111,7 +111,7 @@ pad_motion_settings_dialog::pad_motion_settings_dialog(QDialog* parent, std::sha m_config_entries[i]->shift.set(value); }); - connect(m_axis_names[i], QOverload::of(&QComboBox::currentIndexChanged), this, [this, i](int index) + connect(m_axis_names[i], &QComboBox::currentIndexChanged, this, [this, i](int index) { std::lock_guard lock(m_config_mutex); if (!m_config_entries[i]->axis.from_string(m_axis_names[i]->itemText(index).toStdString())) diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 8075577b95..36ece3b0ec 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -124,7 +124,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti connect(ui->chooseHandler, &QComboBox::currentTextChanged, this, &pad_settings_dialog::ChangeHandler); // Combobox: Devices - connect(ui->chooseDevice, QOverload::of(&QComboBox::currentIndexChanged), this, &pad_settings_dialog::ChangeDevice); + connect(ui->chooseDevice, &QComboBox::currentIndexChanged, this, &pad_settings_dialog::ChangeDevice); // Combobox: Configs connect(ui->chooseConfig, &QComboBox::currentTextChanged, this, &pad_settings_dialog::ChangeConfig); @@ -179,7 +179,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti ui->chooseClass->addItem(tr("Copilot for Player 6"), u32{CELL_PAD_FAKE_TYPE_COPILOT_6}); ui->chooseClass->addItem(tr("Copilot for Player 7"), u32{CELL_PAD_FAKE_TYPE_COPILOT_7}); - connect(ui->chooseClass, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->chooseClass, &QComboBox::currentIndexChanged, this, [this](int index) { if (index < 0) return; HandleDeviceClassChange(ui->chooseClass->currentData().toUInt()); @@ -2196,7 +2196,7 @@ void pad_settings_dialog::SubscribeTooltips() SubscribeTooltip(ui->gb_mouse_accel, tooltips.gamepad_settings.mouse_acceleration); SubscribeTooltip(ui->gb_mouse_dz, tooltips.gamepad_settings.mouse_deadzones); SubscribeTooltip(ui->gb_mouse_movement, tooltips.gamepad_settings.mouse_movement); - + for (int i = button_ids::id_pad_begin + 1; i < button_ids::id_pad_end; i++) { SubscribeTooltip(m_pad_buttons->button(i), tooltips.gamepad_settings.button_assignment); diff --git a/rpcs3/rpcs3qt/patch_creator_dialog.cpp b/rpcs3/rpcs3qt/patch_creator_dialog.cpp index b94fd65421..539a2e0f82 100644 --- a/rpcs3/rpcs3qt/patch_creator_dialog.cpp +++ b/rpcs3/rpcs3qt/patch_creator_dialog.cpp @@ -72,7 +72,7 @@ patch_creator_dialog::patch_creator_dialog(QWidget* parent) connect(ui->addPatchButton, &QAbstractButton::clicked, this, [this]() { add_instruction(ui->instructionTable->rowCount()); }); init_patch_type_bombo_box(ui->addPatchTypeComboBox, patch_type::be32, false); - connect(ui->addPatchTypeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index){ update_validator(index, ui->addPatchTypeComboBox, ui->addPatchOffsetEdit); }); + connect(ui->addPatchTypeComboBox, &QComboBox::currentIndexChanged, this, [this](int index){ update_validator(index, ui->addPatchTypeComboBox, ui->addPatchOffsetEdit); }); update_validator(ui->addPatchTypeComboBox->currentIndex(), ui->addPatchTypeComboBox, ui->addPatchOffsetEdit); generate_yml(); diff --git a/rpcs3/rpcs3qt/patch_manager_dialog.cpp b/rpcs3/rpcs3qt/patch_manager_dialog.cpp index 0df58fb35e..04c8906e9b 100644 --- a/rpcs3/rpcs3qt/patch_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/patch_manager_dialog.cpp @@ -107,7 +107,7 @@ patch_manager_dialog::patch_manager_dialog(std::shared_ptr gui_set connect(ui->patch_tree, &QTreeWidget::itemChanged, this, &patch_manager_dialog::handle_item_changed); connect(ui->patch_tree, &QTreeWidget::customContextMenuRequested, this, &patch_manager_dialog::handle_custom_context_menu_requested); connect(ui->cb_owned_games_only, &QCheckBox::checkStateChanged, this, &patch_manager_dialog::handle_show_owned_games_only); - connect(ui->configurable_selector, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->configurable_selector, &QComboBox::currentIndexChanged, this, [this](int index) { if (index >= 0) { @@ -116,7 +116,7 @@ patch_manager_dialog::patch_manager_dialog(std::shared_ptr gui_set handle_item_selected(item, item); } }); - connect(ui->configurable_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->configurable_combo_box, &QComboBox::currentIndexChanged, this, [this](int index) { if (index >= 0) { diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.cpp b/rpcs3/rpcs3qt/pkg_install_dialog.cpp index 11431b2ad4..370f4d053c 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.cpp +++ b/rpcs3/rpcs3qt/pkg_install_dialog.cpp @@ -2,6 +2,10 @@ #include "game_compatibility.h" #include "numbered_widget_item.h" #include "richtext_item_delegate.h" +#include "qt_utils.h" + +#include "Emu/system_utils.hpp" +#include "Utilities/File.h" #include #include @@ -17,6 +21,7 @@ enum Roles TitleRole = Qt::UserRole + 2, TitleIdRole = Qt::UserRole + 3, VersionRole = Qt::UserRole + 4, + DataSizeRole = Qt::UserRole + 5, }; pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibility* compat, QWidget* parent) @@ -89,7 +94,8 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil append_comma(); accumulated_info += file_info.fileName(); - const QString text = tr("%0 (%2)", "Package text").arg(info.title.simplified()).arg(accumulated_info); + const QString text = tr("%0 (%1) - %2", "Package text").arg(info.title.simplified()) + .arg(accumulated_info).arg(gui::utils::format_byte_size(info.data_size)); QListWidgetItem* item = new numbered_widget_item(text, m_dir_list); item->setData(Roles::FullPathRole, info.path); @@ -97,6 +103,7 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil item->setData(Roles::TitleRole, info.title); item->setData(Roles::TitleIdRole, info.title_id); item->setData(Roles::VersionRole, info.version); + item->setData(Roles::DataSizeRole, static_cast(info.data_size)); item->setToolTip(tooltip); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Checked); @@ -106,6 +113,10 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil m_dir_list->setCurrentRow(0); m_dir_list->setMinimumWidth((m_dir_list->sizeHintForColumn(0) * 125) / 100); + // Create contextual label (updated in connect(m_dir_list, &QListWidget::itemChanged ...)) + QLabel* installation_info = new QLabel(); + installation_info->setTextFormat(Qt::RichText); // Support HTML tags + // Create buttons QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); buttons->button(QDialogButtonBox::Ok)->setText(tr("Install")); @@ -123,19 +134,9 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil } }); - connect(m_dir_list, &QListWidget::itemChanged, this, [this, buttons](QListWidgetItem*) + connect(m_dir_list, &QListWidget::itemChanged, this, [this, installation_info, buttons](QListWidgetItem*) { - bool any_checked = false; - for (int i = 0; i < m_dir_list->count(); i++) - { - if (m_dir_list->item(i)->checkState() == Qt::Checked) - { - any_checked = true; - break; - } - } - - buttons->button(QDialogButtonBox::Ok)->setEnabled(any_checked); + UpdateInfo(installation_info, buttons); }); QToolButton* move_up = new QToolButton; @@ -159,11 +160,41 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil vbox->addWidget(description); vbox->addLayout(hbox); vbox->addWidget(m_dir_list); + vbox->addWidget(installation_info); vbox->addWidget(buttons); setLayout(vbox); setWindowTitle(tr("Batch PKG Installation")); setObjectName("pkg_install_dialog"); + UpdateInfo(installation_info, buttons); // Just to show and check available and required size +} + +void pkg_install_dialog::UpdateInfo(QLabel* installation_info, QDialogButtonBox* buttons) const +{ + u64 data_size = 0; + u64 free_space = 0; + + // Retrieve disk space info on data path's drive + if (fs::device_stat stat{}; fs::statfs(rpcs3::utils::get_hdd0_game_dir(), stat)) + { + free_space = stat.avail_free; + } + + for (int i = 0; i < m_dir_list->count(); i++) + { + if (m_dir_list->item(i)->checkState() == Qt::Checked) + { + data_size += m_dir_list->item(i)->data(Roles::DataSizeRole).toULongLong(); + } + } + + installation_info->setText(gui::utils::make_paragraph( + tr("Installation path: %0\nAvailable disk space: %1%2\nRequired disk space: %3") + .arg(QString::fromStdString(rpcs3::utils::get_hdd0_game_dir())) + .arg(gui::utils::format_byte_size(free_space)) + .arg(data_size <= free_space ? QString() : tr(" - NOT ENOUGH SPACE")) + .arg(gui::utils::format_byte_size(data_size)))); + buttons->button(QDialogButtonBox::Ok)->setEnabled(data_size && (data_size <= free_space)); } void pkg_install_dialog::MoveItem(int offset) const diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.h b/rpcs3/rpcs3qt/pkg_install_dialog.h index bb566b1b67..2122259a1d 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.h +++ b/rpcs3/rpcs3qt/pkg_install_dialog.h @@ -2,6 +2,8 @@ #include #include +#include +#include namespace compat { @@ -19,6 +21,7 @@ public: std::vector GetPathsToInstall() const; private: + void UpdateInfo(QLabel* installation_info, QDialogButtonBox* buttons) const; void MoveItem(int offset) const; QListWidget* m_dir_list; diff --git a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp index de63d80b5d..d4736f01e2 100644 --- a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp +++ b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp @@ -71,10 +71,14 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) // Flip image if necessary if (flip_horizontally || flip_vertically) { - Qt::Orientations orientation {}; +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + Qt::Orientations orientation{}; orientation.setFlag(Qt::Orientation::Horizontal, flip_horizontally); orientation.setFlag(Qt::Orientation::Vertical, flip_vertically); image.flip(orientation); +#else + image.mirror(flip_horizontally, flip_vertically); +#endif } if (image.format() != QImage::Format_RGBA8888) @@ -114,45 +118,60 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) case CELL_CAMERA_RAW8: // The game seems to expect BGGR { // Let's use a very simple algorithm to convert the image to raw BGGR - const auto convert_to_bggr = [&image_buffer, &image, width, height](u32 y_begin, u32 y_end) + const auto convert_to_bggr = [this, &image_buffer, &image, width, height](u32 y_begin, u32 y_end) { u8* dst = &image_buffer.data[image_buffer.width * y_begin]; for (u32 y = y_begin; y < height && y < y_end; y++) { const u8* src = image.constScanLine(y); + const u8* srcu = image.constScanLine(std::max(0, y - 1)); + const u8* srcd = image.constScanLine(std::min(height - 1, y + 1)); const bool is_top_pixel = (y % 2) == 0; + // We apply gaussian blur to get better demosaicing results later when debayering again + const auto blurred = [&](s32 x, s32 c) + { + const s32 i = x * 4 + c; + const s32 il = std::max(0, x - 1) * 4 + c; + const s32 ir = std::min(width - 1, x + 1) * 4 + c; + const s32 sum = + srcu[i] + + src[il] + 4 * src[i] + src[ir] + + srcd[i]; + return static_cast(std::clamp((sum + 4) / 8, 0, 255)); + }; + // Split loops (roughly twice the performance by removing one condition) if (is_top_pixel) { - for (u32 x = 0; x < width; x++, dst++, src += 4) + for (u32 x = 0; x < width; x++, dst++) { const bool is_left_pixel = (x % 2) == 0; if (is_left_pixel) { - *dst = src[2]; // Blue + *dst = blurred(x, 2); // Blue } else { - *dst = src[1]; // Green + *dst = blurred(x, 1); // Green } } } else { - for (u32 x = 0; x < width; x++, dst++, src += 4) + for (u32 x = 0; x < width; x++, dst++) { const bool is_left_pixel = (x % 2) == 0; if (is_left_pixel) { - *dst = src[1]; // Green + *dst = blurred(x, 1); // Green } else { - *dst = src[0]; // Red + *dst = blurred(x, 0); // Red } } } @@ -182,13 +201,13 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) // Simple RGB to Y0_U_Y1_V conversion from stackoverflow. const auto convert_to_yuv422 = [&image_buffer, &image, width, height, format = m_format](u32 y_begin, u32 y_end) { - constexpr int yuv_bytes_per_pixel = 2; - const int yuv_pitch = image_buffer.width * yuv_bytes_per_pixel; + constexpr s32 yuv_bytes_per_pixel = 2; + const s32 yuv_pitch = image_buffer.width * yuv_bytes_per_pixel; - const int y0_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3; - const int u_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2; - const int y1_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1; - const int v_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0; + const s32 y0_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3; + const s32 u_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2; + const s32 y1_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1; + const s32 v_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0; for (u32 y = y_begin; y < height && y < y_end; y++) { @@ -197,19 +216,19 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) for (u32 x = 0; x < width - 1; x += 2, src += 8) { - const float r1 = src[0]; - const float g1 = src[1]; - const float b1 = src[2]; - const float r2 = src[4]; - const float g2 = src[5]; - const float b2 = src[6]; + const f32 r1 = src[0]; + const f32 g1 = src[1]; + const f32 b1 = src[2]; + const f32 r2 = src[4]; + const f32 g2 = src[5]; + const f32 b2 = src[6]; - const int y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f; - const int u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f; - const int v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f; - const int y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f; + const s32 y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f; + const s32 u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f; + const s32 v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f; + const s32 y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f; - const int yuv_index = x * yuv_bytes_per_pixel; + const s32 yuv_index = x * yuv_bytes_per_pixel; yuv_row_ptr[yuv_index + y0_offset] = static_cast(std::clamp(y0, 0, 255)); yuv_row_ptr[yuv_index + u_offset] = static_cast(std::clamp( u, 0, 255)); yuv_row_ptr[yuv_index + y1_offset] = static_cast(std::clamp(y1, 0, 255)); diff --git a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp index 57b4f70d02..c64391c021 100644 --- a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp @@ -225,7 +225,7 @@ void raw_mouse_settings_dialog::add_tabs(QTabWidget* tabs) update_combo_box(player); - connect(combo, QOverload::of(&QComboBox::currentIndexChanged), this, [this, player, combo](int index) + connect(combo, &QComboBox::currentIndexChanged, this, [this, player, combo](int index) { if (index < 0 || !combo) return; diff --git a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp index 7cc819521a..74d5b14e6a 100644 --- a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp @@ -196,7 +196,7 @@ rpcn_account_dialog::rpcn_account_dialog(QWidget* parent) setLayout(vbox_global); - connect(cbx_servers, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(cbx_servers, &QComboBox::currentIndexChanged, this, [this](int index) { if (index < 0) return; diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 2e3964bbe7..81e85546e9 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -553,7 +553,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std reset_zcull_options(); connect(this, &settings_dialog::signal_restore_dependant_defaults, this, reset_zcull_options); - connect(ui->zcullPrecisionMode, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) + connect(ui->zcullPrecisionMode, &QComboBox::currentIndexChanged, [this](int index) { if (index < 0) return; @@ -589,7 +589,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->stereoRenderMode->setEnabled(stereo_allowed && stereo_enabled); ui->stereoRenderEnabled->setEnabled(stereo_allowed); }; - connect(ui->resBox, QOverload::of(&QComboBox::currentIndexChanged), this, [enable_3D_modes](int){ enable_3D_modes(); }); + connect(ui->resBox, &QComboBox::currentIndexChanged, this, [enable_3D_modes](int){ enable_3D_modes(); }); connect(ui->stereoRenderEnabled, &QCheckBox::checkStateChanged, this, [enable_3D_modes](Qt::CheckState){ enable_3D_modes(); }); enable_3D_modes(); } @@ -872,7 +872,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std apply_fsr_specific_options(); connect(ui->renderBox, &QComboBox::currentTextChanged, apply_renderer_specific_options); connect(ui->renderBox, &QComboBox::currentTextChanged, this, apply_fsr_specific_options); - connect(ui->outputScalingMode, QOverload::of(&QComboBox::currentIndexChanged), this, apply_fsr_specific_options); + connect(ui->outputScalingMode, &QComboBox::currentIndexChanged, this, apply_fsr_specific_options); // _ _ _______ _ // /\ | (_) |__ __| | | @@ -997,7 +997,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std #else SubscribeTooltip(ui->gb_audio_out, tooltips.settings.audio_out_linux); #endif - connect(ui->audioOutBox, QOverload::of(&QComboBox::currentIndexChanged), this, [change_audio_output_device, get_audio_output_devices](int) + connect(ui->audioOutBox, &QComboBox::currentIndexChanged, this, [change_audio_output_device, get_audio_output_devices](int) { get_audio_output_devices(false); change_audio_output_device(0); // Set device to 'Default' @@ -1006,7 +1006,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->combo_audio_channel_layout, emu_settings_type::AudioChannelLayout); SubscribeTooltip(ui->gb_audio_channel_layout, tooltips.settings.audio_channel_layout); - connect(ui->combo_audio_format, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->combo_audio_format, &QComboBox::currentIndexChanged, this, [this](int index) { const auto [text, value] = get_data(ui->combo_audio_format, index); ui->list_audio_formats->setEnabled(static_cast(value) == audio_format::manual); @@ -1071,7 +1071,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std SubscribeTooltip(ui->gb_audio_avport, tooltips.settings.audio_avport); SubscribeTooltip(ui->gb_audio_device, tooltips.settings.audio_device); - connect(ui->audioDeviceBox, QOverload::of(&QComboBox::currentIndexChanged), this, change_audio_output_device); + connect(ui->audioDeviceBox, &QComboBox::currentIndexChanged, this, change_audio_output_device); connect(this, &settings_dialog::signal_restore_dependant_defaults, this, [change_audio_output_device]() { change_audio_output_device(0); }); // Set device to 'Default' get_audio_output_devices(); @@ -1111,7 +1111,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->microphoneBox, emu_settings_type::MicrophoneType); SubscribeTooltip(ui->microphoneBox, tooltips.settings.microphone); - connect(ui->microphoneBox, QOverload::of(&QComboBox::currentIndexChanged), change_microphone_type); + connect(ui->microphoneBox, &QComboBox::currentIndexChanged, change_microphone_type); propagate_used_devices(); // Enables/Disables comboboxes and checks values from config for sanity // Checkboxes @@ -1184,7 +1184,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std cfg_log.error("The selected camera was not found. Selecting default camera as fallback."); ui->cameraIdBox->setCurrentIndex(ui->cameraIdBox->findData(QString::fromStdString(default_camera))); } - connect(ui->cameraIdBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->cameraIdBox, &QComboBox::currentIndexChanged, this, [this](int index) { if (index >= 0) m_emu_settings->SetSetting(emu_settings_type::CameraID, ui->cameraIdBox->itemData(index).toString().toStdString()); }); @@ -1424,7 +1424,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std // Comboboxes - connect(ui->netStatusBox, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) + connect(ui->netStatusBox, &QComboBox::currentIndexChanged, [this](int index) { if (index < 0) return; const auto [text, value] = get_data(ui->netStatusBox, index); @@ -1435,10 +1435,12 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std { ui->psnStatusBox->setCurrentIndex(find_item(ui->psnStatusBox, static_cast(g_cfg.net.psn_status.def))); ui->psnStatusBox->setEnabled(false); + ui->enable_clans->setEnabled(false); } else { ui->psnStatusBox->setEnabled(true); + ui->enable_clans->setEnabled(true); } }); m_emu_settings->EnhanceComboBox(ui->netStatusBox, emu_settings_type::InternetStatus); @@ -1447,8 +1449,11 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->psnStatusBox, emu_settings_type::PSNStatus); SubscribeTooltip(ui->gb_psnStatusBox, tooltips.settings.psn_status); + m_emu_settings->EnhanceCheckBox(ui->enable_clans, emu_settings_type::EnableClans); + SubscribeTooltip(ui->enable_clans, tooltips.settings.enable_clans); + settings_dialog::refresh_countrybox(); - connect(ui->psnCountryBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->psnCountryBox, &QComboBox::currentIndexChanged, this, [this](int index) { if (index < 0) return; @@ -2277,7 +2282,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->combo_updates->addItem(tr("Automatic", "Updates"), gui::update_auto); ui->combo_updates->addItem(tr("No", "Updates"), gui::update_off); ui->combo_updates->setCurrentIndex(ui->combo_updates->findData(m_gui_settings->GetValue(gui::m_check_upd_start).toString())); - connect(ui->combo_updates, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) + connect(ui->combo_updates, &QComboBox::currentIndexChanged, [this](int index) { if (index >= 0) m_gui_settings->SetValue(gui::m_check_upd_start, ui->combo_updates->itemData(index)); }); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 4556ae9356..6a27f45be3 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -2319,6 +2319,13 @@ + + + + Enable Clans + + + diff --git a/rpcs3/rpcs3qt/skylander_dialog.cpp b/rpcs3/rpcs3qt/skylander_dialog.cpp index 86dfb61464..6623d27f03 100644 --- a/rpcs3/rpcs3qt/skylander_dialog.cpp +++ b/rpcs3/rpcs3qt/skylander_dialog.cpp @@ -586,7 +586,7 @@ skylander_creator_dialog::skylander_creator_dialog(QWidget* parent) setLayout(vbox_panel); - connect(combo_skylist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) + connect(combo_skylist, &QComboBox::currentIndexChanged, [=](int index) { const u32 sky_info = combo_skylist->itemData(index).toUInt(); if (sky_info != 0xFFFFFFFF) @@ -663,7 +663,7 @@ skylander_creator_dialog::skylander_creator_dialog(QWidget* parent) connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); - connect(co_compl, QOverload::of(&QCompleter::activated),[=](const QString& text) + connect(co_compl, qOverload(&QCompleter::activated), [=](const QString& text) { combo_skylist->setCurrentText(text); combo_skylist->setCurrentIndex(combo_skylist->findText(text)); diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 3b84a66270..abdc60f3dd 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -91,7 +91,7 @@ public: const QString xfloat = tr("Control accuracy to SPU float vectors processing.\nFixes bugs in various games at the cost of performance.\nThis setting is only applied when SPU Decoder is set to Dynamic or LLVM."); const QString enable_thread_scheduler = tr("Control how RPCS3 utilizes the threads of your system.\nEach option heavily depends on the game and on your CPU. It's recommended to try each option to find out which performs the best.\nChanging the thread scheduler is not supported on CPUs with less than 12 threads."); const QString spu_loop_detection = tr("Try to detect loop conditions in SPU kernels and use them as scheduling hints.\nImproves performance and reduces CPU usage.\nMay cause severe audio stuttering in rare cases."); - const QString spu_block_size = tr("This option controls the SPU analyser, particularly the size of compiled units. The Mega and Giga modes may improve performance by tying smaller units together, decreasing the number of compiled units but increasing their size.\nUse the Safe mode for maximum compatibility at the cost of lower performance."); + const QString spu_block_size = tr("This option controls the SPU analyser, particularly the size of compiled units. The Mega and Giga modes may improve performance by tying smaller units together, decreasing the number of compiled units but increasing their size.\nUse the Safe mode for maximum compatibility."); const QString preferred_spu_threads = tr("Some SPU stages are sensitive to race conditions and allowing a limited number at a time helps alleviate performance stalls.\nSetting this to a smaller value might improve performance and reduce stuttering in some games.\nLeave this on auto if performance is negatively affected when setting a small value."); const QString max_cpu_preempt = tr("Reduces CPU usage and power consumption, improving battery life on mobile devices. (0 means disabled)\nHigher values cause a more pronounced effect, but may cause audio or performance issues. A value of 50 or less is recommended.\nThis option forces an FPS limit because it's active when framerate is stable.\nThe lighter the game is on the hardware, the more power is saved by it. (until the preemption count barrier is reached)"); @@ -260,6 +260,7 @@ public: const QString bind = tr("Interface IP Address to bind to.\nOnly available in custom configurations."); const QString enable_upnp = tr("Enable UPNP.\nThis will automatically forward ports bound on 0.0.0.0 if your router has UPNP enabled."); const QString psn_country = tr("Changes the RPCN country."); + const QString enable_clans = tr("Enable connection to the Clans server.\nOnly affects games supporting the Clans feature."); // system diff --git a/rpcs3/tests/rpcs3_test.vcxproj b/rpcs3/tests/rpcs3_test.vcxproj index 22992e6a07..2851f2faa6 100644 --- a/rpcs3/tests/rpcs3_test.vcxproj +++ b/rpcs3/tests/rpcs3_test.vcxproj @@ -89,6 +89,7 @@ + diff --git a/rpcs3/tests/test_fmt.cpp b/rpcs3/tests/test_fmt.cpp index e44b4adab0..95c31d17c6 100644 --- a/rpcs3/tests/test_fmt.cpp +++ b/rpcs3/tests/test_fmt.cpp @@ -25,6 +25,26 @@ namespace fmt EXPECT_EQ("b"s, fmt::trim(" aba ", " a")); } + TEST(StrUtil, TrimSv) + { + EXPECT_EQ(""sv, fmt::trim_sv("", "")); + EXPECT_EQ(""sv, fmt::trim_sv("", " ")); + EXPECT_EQ(""sv, fmt::trim_sv("", "a ")); + EXPECT_EQ(" "sv, fmt::trim_sv(" ", "")); + EXPECT_EQ(""sv, fmt::trim_sv(" ", " ")); + EXPECT_EQ("a"sv, fmt::trim_sv("a ", " ")); + EXPECT_EQ("a"sv, fmt::trim_sv(" a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv("a a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv("a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv(" a a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv(" a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv("a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv(" a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv(" a a", " ")); + EXPECT_EQ(""sv, fmt::trim_sv(" a a ", " a")); + EXPECT_EQ("b"sv, fmt::trim_sv(" aba ", " a")); + } + TEST(StrUtil, TrimFront) { EXPECT_EQ(""s, fmt::trim_front("", "")); @@ -45,6 +65,26 @@ namespace fmt EXPECT_EQ("ba "s, fmt::trim_front(" aba ", " a")); } + TEST(StrUtil, TrimFrontSv) + { + EXPECT_EQ(""sv, fmt::trim_front_sv("", "")); + EXPECT_EQ(""sv, fmt::trim_front_sv("", " ")); + EXPECT_EQ(""sv, fmt::trim_front_sv("", "a ")); + EXPECT_EQ(" "sv, fmt::trim_front_sv(" ", "")); + EXPECT_EQ(""sv, fmt::trim_front_sv(" ", " ")); + EXPECT_EQ("a "sv, fmt::trim_front_sv("a ", " ")); + EXPECT_EQ("a"sv, fmt::trim_front_sv(" a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_front_sv("a a", " ")); + EXPECT_EQ("a a "sv, fmt::trim_front_sv("a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_front_sv(" a a", " ")); + EXPECT_EQ("a a "sv, fmt::trim_front_sv(" a a ", " ")); + EXPECT_EQ("a a "sv, fmt::trim_front_sv("a a ", " ")); + EXPECT_EQ("a a "sv, fmt::trim_front_sv(" a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_front_sv(" a a", " ")); + EXPECT_EQ(""sv, fmt::trim_front_sv(" a a ", " a")); + EXPECT_EQ("ba "sv, fmt::trim_front_sv(" aba ", " a")); + } + TEST(StrUtil, TrimBack) { std::string str; @@ -112,6 +152,26 @@ namespace fmt EXPECT_EQ(" ab"s, str); } + TEST(StrUtil, TrimBackSv) + { + EXPECT_EQ(""sv, fmt::trim_back_sv({}, "")); + EXPECT_EQ(""sv, fmt::trim_back_sv({}, " ")); + EXPECT_EQ(""sv, fmt::trim_back_sv({}, "a ")); + EXPECT_EQ(" "sv, fmt::trim_back_sv(" ", "")); + EXPECT_EQ(""sv, fmt::trim_back_sv(" ", " ")); + EXPECT_EQ("a"sv, fmt::trim_back_sv("a ", " ")); + EXPECT_EQ(" a"sv, fmt::trim_back_sv(" a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_back_sv("a a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_back_sv("a a ", " ")); + EXPECT_EQ(" a a"sv, fmt::trim_back_sv(" a a", " ")); + EXPECT_EQ(" a a"sv, fmt::trim_back_sv(" a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_back_sv("a a ", " ")); + EXPECT_EQ(" a a"sv, fmt::trim_back_sv(" a a ", " ")); + EXPECT_EQ(" a a"sv, fmt::trim_back_sv(" a a", " ")); + EXPECT_EQ(""sv, fmt::trim_back_sv(" a a ", " a")); + EXPECT_EQ(" ab"sv, fmt::trim_back_sv(" aba ", " a")); + } + TEST(StrUtil, ToUpperToLower) { const std::string lowercase = "abcdefghijklmnopqrstuvwxyzäüöß0123456789 .,-<#+"; @@ -340,6 +400,155 @@ namespace fmt EXPECT_EQ(vec({"This", "is", "test!"}), fmt::split(" This is a test! ", {"a", " ", "b"}, true)); } + TEST(StrUtil, SplitSv) + { + using vec = std::vector; + + EXPECT_EQ(vec{""}, fmt::split_sv("", {}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {""}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {" "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a"}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a b"}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a", " "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {""}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv(" ", {" "}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a"}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a "}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a b"}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv(" ", {"a", " "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv(" ", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {""}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv(" ", {" "}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a"}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a "}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a b"}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv(" ", {"a", " "}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv(" ", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {}, false)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {""}, false)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {" "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("a", {"a"}, false)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {"a "}, false)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {"a b"}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("a", {"a", " "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("a", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {}, false)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {""}, false)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {" "}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv("aa", {"a"}, false)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {"a "}, false)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {"a b"}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv("aa", {"a", " "}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv("aa", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{"a b"}, fmt::split_sv("a b", {}, false)); + EXPECT_EQ(vec{"a b"}, fmt::split_sv("a b", {""}, false)); + EXPECT_EQ(vec({"a", "b"}), fmt::split_sv("a b", {" "}, false)); + EXPECT_EQ(vec({"", " b"}), fmt::split_sv("a b", {"a"}, false)); + EXPECT_EQ(vec({"", "b"}), fmt::split_sv("a b", {"a "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("a b", {"a b"}, false)); + EXPECT_EQ(vec({"", "", "b"}), fmt::split_sv("a b", {"a", " "}, false)); + EXPECT_EQ(vec({"", "", ""}), fmt::split_sv("a b", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{"a b c c b a"}, fmt::split_sv("a b c c b a", {}, false)); + EXPECT_EQ(vec{"a b c c b a"}, fmt::split_sv("a b c c b a", {""}, false)); + EXPECT_EQ(vec({"a", "b", "c", "c", "b", "a"}), fmt::split_sv("a b c c b a", {" "}, false)); + EXPECT_EQ(vec({"", " b c c b "}), fmt::split_sv("a b c c b a", {"a"}, false)); + EXPECT_EQ(vec({"", "b c c b a"}), fmt::split_sv("a b c c b a", {"a "}, false)); + EXPECT_EQ(vec({"", " c c b a"}), fmt::split_sv("a b c c b a", {"a b"}, false)); + EXPECT_EQ(vec({"", "", "b", "c", "c", "b", ""}), fmt::split_sv("a b c c b a", {"a", " "}, false)); + EXPECT_EQ(vec({"", "", "", "", "c", "c", "", "", ""}), fmt::split_sv("a b c c b a", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {}, false)); + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {""}, false)); + EXPECT_EQ(vec({"", "This", "is", "a", "test!"}), fmt::split_sv(" This is a test! ", {" "}, false)); + EXPECT_EQ(vec({" This is ", " test! "}), fmt::split_sv(" This is a test! ", {"a"}, false)); + EXPECT_EQ(vec({" This is ", "test! "}), fmt::split_sv(" This is a test! ", {"a "}, false)); + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {"a b"}, false)); + EXPECT_EQ(vec({"", "This", "is", "", "", "test!"}), fmt::split_sv(" This is a test! ", {"a", " "}, false)); + EXPECT_EQ(vec({"", "This", "is", "", "", "test!"}), fmt::split_sv(" This is a test! ", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{}, fmt::split_sv("", {}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {""}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {" "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {""}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {" "}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a"}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a "}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {""}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {" "}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a"}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a "}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {}, true)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {""}, true)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {" "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a", {"a"}, true)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {"a "}, true)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {}, true)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {""}, true)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {" "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("aa", {"a"}, true)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {"a "}, true)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("aa", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("aa", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{"a b"}, fmt::split_sv("a b", {}, true)); + EXPECT_EQ(vec{"a b"}, fmt::split_sv("a b", {""}, true)); + EXPECT_EQ(vec({"a", "b"}), fmt::split_sv("a b", {" "}, true)); + EXPECT_EQ(vec{" b"}, fmt::split_sv("a b", {"a"}, true)); + EXPECT_EQ(vec{"b"}, fmt::split_sv("a b", {"a "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a b", {"a b"}, true)); + EXPECT_EQ(vec{"b"}, fmt::split_sv("a b", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a b", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{"a b c c b a"}, fmt::split_sv("a b c c b a", {}, true)); + EXPECT_EQ(vec{"a b c c b a"}, fmt::split_sv("a b c c b a", {""}, true)); + EXPECT_EQ(vec({"a", "b", "c", "c", "b", "a"}), fmt::split_sv("a b c c b a", {" "}, true)); + EXPECT_EQ(vec{" b c c b "}, fmt::split_sv("a b c c b a", {"a"}, true)); + EXPECT_EQ(vec{"b c c b a"}, fmt::split_sv("a b c c b a", {"a "}, true)); + EXPECT_EQ(vec{" c c b a"}, fmt::split_sv("a b c c b a", {"a b"}, true)); + EXPECT_EQ(vec({"b", "c", "c", "b"}), fmt::split_sv("a b c c b a", {"a", " "}, true)); + EXPECT_EQ(vec({"c", "c"}), fmt::split_sv("a b c c b a", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {}, true)); + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {""}, true)); + EXPECT_EQ(vec({"This", "is", "a", "test!"}), fmt::split_sv(" This is a test! ", {" "}, true)); + EXPECT_EQ(vec({" This is ", " test! "}), fmt::split_sv(" This is a test! ", {"a"}, true)); + EXPECT_EQ(vec({" This is ", "test! "}), fmt::split_sv(" This is a test! ", {"a "}, true)); + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {"a b"}, true)); + EXPECT_EQ(vec({"This", "is", "test!"}), fmt::split_sv(" This is a test! ", {"a", " "}, true)); + EXPECT_EQ(vec({"This", "is", "test!"}), fmt::split_sv(" This is a test! ", {"a", " ", "b"}, true)); + } + TEST(StrUtil, Merge) { using vec = std::vector; diff --git a/rpcs3/tests/test_rsx_cfg.cpp b/rpcs3/tests/test_rsx_cfg.cpp index 1708774d76..ded749fd24 100644 --- a/rpcs3/tests/test_rsx_cfg.cpp +++ b/rpcs3/tests/test_rsx_cfg.cpp @@ -2,89 +2,28 @@ #include "Emu/RSX/Common/simple_array.hpp" #include "Emu/RSX/Program/Assembler/CFG.h" +#include "Emu/RSX/Program/Assembler/FPASM.h" #include "Emu/RSX/Program/RSXFragmentProgram.h" #include namespace rsx::assembler { - auto swap_bytes16 = [](u32 dword) -> u32 + static const BasicBlock* get_graph_block_by_id(const FlowGraph& graph, u32 id) { - // Lazy encode, but good enough for what we need here. - union v32 - { - u32 HEX; - u8 _v[4]; - }; - - u8* src_bytes = reinterpret_cast(&dword); - v32 dst_bytes; - - dst_bytes._v[0] = src_bytes[1]; - dst_bytes._v[1] = src_bytes[0]; - dst_bytes._v[2] = src_bytes[3]; - dst_bytes._v[3] = src_bytes[2]; - - return dst_bytes.HEX; - }; - - // Instruction mocks because we don't have a working assember (yet) - auto encode_instruction = [](u32 opcode, bool end = false) -> v128 - { - OPDEST dst{}; - dst.opcode = opcode; - - if (end) - { - dst.end = 1; - } - - return v128::from32(swap_bytes16(dst.HEX), 0, 0, 0); - }; - - auto create_if(u32 end, u32 _else = 0) - { - OPDEST dst{}; - dst.opcode = RSX_FP_OPCODE_IFE & 0x3Fu; - - SRC1 src1{}; - src1.else_offset = (_else ? _else : end) << 2; - src1.opcode_is_branch = 1; - - SRC2 src2{}; - src2.end_offset = end << 2; - - return v128::from32(swap_bytes16(dst.HEX), 0, swap_bytes16(src1.HEX), swap_bytes16(src2.HEX)); - }; - - TEST(CFG, FpToCFG_Basic) - { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), - encode_instruction(RSX_FP_OPCODE_MOV, true) - }; - - RSXFragmentProgram program{}; - program.data = buffer.data(); - - FlowGraph graph = deconstruct_fragment_program(program); - - EXPECT_EQ(graph.blocks.size(), 1); - EXPECT_EQ(graph.blocks.front().instructions.size(), 2); - EXPECT_EQ(graph.blocks.front().instructions.front().length, 4); - EXPECT_EQ(graph.blocks.front().instructions[0].addr, 0); - EXPECT_EQ(graph.blocks.front().instructions[1].addr, 16); + auto found = std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == id)); + return &(*found); } - TEST(CFG, FpToCFG_IF) { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), // 0 - encode_instruction(RSX_FP_OPCODE_MOV), // 1 - create_if(4), // 2 (BR, 4) - encode_instruction(RSX_FP_OPCODE_ADD), // 3 - encode_instruction(RSX_FP_OPCODE_MOV, true), // 4 (Merge block) - }; + auto ir = FPIR::from_source(R"( + ADD R0, R0, R0; + MOV R1, R0; + IF.LT; + ADD R1, R1, R0; + ENDIF; + MOV R0, R1; + )"); const std::pair expected_block_data[3] = { { 0, 3 }, // Head @@ -93,7 +32,8 @@ namespace rsx::assembler }; RSXFragmentProgram program{}; - program.data = buffer.data(); + auto bytecode = ir.compile(); + program.data = bytecode.data(); FlowGraph graph = deconstruct_fragment_program(program); @@ -108,24 +48,26 @@ namespace rsx::assembler } // Check edges - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 3))->pred[0].type, EdgeType::IF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[0].type, EdgeType::IF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 4))->pred[0].type, EdgeType::ENDIF); + EXPECT_EQ(get_graph_block_by_id(graph, 3)->pred[0].type, EdgeType::IF); + EXPECT_EQ(get_graph_block_by_id(graph, 0)->succ[0].type, EdgeType::IF); + EXPECT_EQ(get_graph_block_by_id(graph, 4)->pred[0].type, EdgeType::ENDIF); } TEST(CFG, FpToCFG_NestedIF) { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), // 0 - encode_instruction(RSX_FP_OPCODE_MOV), // 1 - create_if(8), // 2 (BR, 8) - encode_instruction(RSX_FP_OPCODE_ADD), // 3 - create_if(6), // 4 (BR, 6) - encode_instruction(RSX_FP_OPCODE_MOV), // 5 - encode_instruction(RSX_FP_OPCODE_MOV), // 6 (merge block 1) - encode_instruction(RSX_FP_OPCODE_ADD), // 7 - encode_instruction(RSX_FP_OPCODE_MOV, true) // 8 (merge block 2 - }; + auto ir = FPIR::from_source( + "ADD R0, R0, R0;" // 0 + "MOV R1, R0;" // 1 + "IF.LT;" // 2 (BR, 8) + " ADD R1, R1, R0;" // 3 + " IF.GT;" // 4 (BR, 6) + " MOV R3, R0;" // 5 + " ENDIF;" + " MOV R2, R3;" // 6 (merge block 1) + " ADD R1, R2, R1;" // 7 + "ENDIF;" + "MOV R0, R1;" // 8 (merge block 2 + ); const std::pair expected_block_data[5] = { { 0, 3 }, // Head @@ -136,7 +78,8 @@ namespace rsx::assembler }; RSXFragmentProgram program{}; - program.data = buffer.data(); + auto bytecode = ir.compile(); + program.data = bytecode.data(); FlowGraph graph = deconstruct_fragment_program(program); @@ -153,17 +96,19 @@ namespace rsx::assembler TEST(CFG, FpToCFG_NestedIF_MultiplePred) { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), // 0 - encode_instruction(RSX_FP_OPCODE_MOV), // 1 - create_if(6), // 2 (BR, 6) - encode_instruction(RSX_FP_OPCODE_ADD), // 3 - create_if(6), // 4 (BR, 6) - encode_instruction(RSX_FP_OPCODE_MOV), // 5 - encode_instruction(RSX_FP_OPCODE_MOV), // 6 (merge block) - encode_instruction(RSX_FP_OPCODE_ADD), // 7 - encode_instruction(RSX_FP_OPCODE_MOV, true) // 8 - }; + auto ir = FPIR::from_source( + "ADD R0, R0, R0;" // 0 + "MOV R1, R0;" // 1 + "IF.LT;" // 2 (BR, 6) + " ADD R1, R1, R0;" // 3 + " IF.GT;" // 4 (BR, 6) + " MOV R3, R0;" // 5 + " ENDIF;" // ENDIF (4) + "ENDIF;" // ENDIF (2) + "MOV R2, R3;" // 6 (merge block, unified) + "ADD R1, R2, R1;" // 7 + "MOV R0, R1;" // 8 + ); const std::pair expected_block_data[4] = { { 0, 3 }, // Head @@ -173,7 +118,8 @@ namespace rsx::assembler }; RSXFragmentProgram program{}; - program.data = buffer.data(); + auto bytecode = ir.compile(); + program.data = bytecode.data(); FlowGraph graph = deconstruct_fragment_program(program); @@ -187,32 +133,40 @@ namespace rsx::assembler EXPECT_EQ(it->instructions.size(), expected.second); } + const BasicBlock + *bb0 = get_graph_block_by_id(graph, 0), + *bb6 = get_graph_block_by_id(graph, 6); + // Predecessors must be ordered, closest first - ASSERT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred.size(), 2); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[0].type, EdgeType::ENDIF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[0].from->id, 3); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[1].type, EdgeType::ENDIF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[1].from->id, 0); + ASSERT_EQ(bb6->pred.size(), 3); + EXPECT_EQ(bb6->pred[0].type, EdgeType::ENDIF); + EXPECT_EQ(bb6->pred[0].from->id, 5); + EXPECT_EQ(bb6->pred[1].type, EdgeType::ENDIF); + EXPECT_EQ(bb6->pred[1].from->id, 3); + EXPECT_EQ(bb6->pred[2].type, EdgeType::ENDIF); + EXPECT_EQ(bb6->pred[2].from->id, 0); // Successors must also be ordered, closest first - ASSERT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ.size(), 2); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[0].type, EdgeType::IF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[0].to->id, 3); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[1].type, EdgeType::ENDIF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[1].to->id, 6); + ASSERT_EQ(bb0->succ.size(), 2); + EXPECT_EQ(bb0->succ[0].type, EdgeType::IF); + EXPECT_EQ(bb0->succ[0].to->id, 3); + EXPECT_EQ(bb0->succ[1].type, EdgeType::ENDIF); + EXPECT_EQ(bb0->succ[1].to->id, 6); } TEST(CFG, FpToCFG_IF_ELSE) { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), // 0 - encode_instruction(RSX_FP_OPCODE_MOV), // 1 - create_if(6, 4), // 2 (BR, 6) - encode_instruction(RSX_FP_OPCODE_ADD), // 3 - encode_instruction(RSX_FP_OPCODE_MOV), // 4 (Else) - encode_instruction(RSX_FP_OPCODE_ADD), // 5 - encode_instruction(RSX_FP_OPCODE_MOV, true), // 6 (Merge) - }; + auto ir = FPIR::from_source( + "ADD R0, R0, R0;" // 0 + "MOV R1, R0;" // 1 + "IF.LT;" // 2 (BR, 6) + " ADD R1, R1, R0;" // 3 + "ELSE;" // ELSE (2) + " MOV R2, R3;" // 4 + " ADD R1, R2, R1;" // 5 + "ENDIF;" // ENDIF (2) + "MOV R0, R1;" // 6 (merge) + ); const std::pair expected_block_data[4] = { { 0, 3 }, // Head @@ -222,7 +176,8 @@ namespace rsx::assembler }; RSXFragmentProgram program{}; - program.data = buffer.data(); + auto bytecode = ir.compile(); + program.data = bytecode.data(); FlowGraph graph = deconstruct_fragment_program(program); @@ -235,5 +190,42 @@ namespace rsx::assembler EXPECT_EQ(it->id, expected.first); EXPECT_EQ(it->instructions.size(), expected.second); } + + // The IF and ELSE branches don't link to each other directly. Their predecessor should point to both and they both point to the merge. + const BasicBlock + *bb0 = get_graph_block_by_id(graph, 0), + *bb3 = get_graph_block_by_id(graph, 3), + *bb4 = get_graph_block_by_id(graph, 4), + *bb6 = get_graph_block_by_id(graph, 6); + + EXPECT_EQ(bb0->succ.size(), 3); + EXPECT_EQ(bb3->succ.size(), 1); + EXPECT_EQ(bb4->succ.size(), 1); + + EXPECT_EQ(bb3->succ.front().to, bb6); + EXPECT_EQ(bb4->succ.front().to, bb6); + + EXPECT_EQ(bb6->pred.size(), 3); + EXPECT_EQ(bb6->pred[0].from, bb4); + EXPECT_EQ(bb6->pred[1].from, bb3); + EXPECT_EQ(bb6->pred[2].from, bb0); + } + + TEST(CFG, FpToCFG_EmptyIF) + { + auto ir = FPIR::from_source( + "IF.LT;" // Empty branch + "ENDIF;" + "MOV R0, R1;" // False merge block. + ); + + RSXFragmentProgram program{}; + auto bytecode = ir.compile(); + program.data = bytecode.data(); + + FlowGraph graph = deconstruct_fragment_program(program); + + ASSERT_EQ(graph.blocks.size(), 1); + EXPECT_EQ(graph.blocks.front().instructions.size(), 1); } } diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp new file mode 100644 index 0000000000..704df5a23d --- /dev/null +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -0,0 +1,761 @@ +#include + +#include "Emu/RSX/Common/simple_array.hpp" +#include "Emu/RSX/Program/Assembler/FPASM.h" +#include "Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h" +#include "Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.h" +#include "Emu/RSX/Program/RSXFragmentProgram.h" + +namespace rsx::assembler +{ +#define DECLARE_REG32(num)\ + Register R##num{ .id = num, .f16 = false } + +#define DECLARE_REG16(num)\ + Register H##num{ .id = num, .f16 = true } + + DECLARE_REG32(0); + DECLARE_REG32(1); + DECLARE_REG32(2); + DECLARE_REG32(3); + DECLARE_REG32(4); + DECLARE_REG32(5); + DECLARE_REG32(6); + DECLARE_REG32(7); + DECLARE_REG32(8); + + DECLARE_REG16(0); + DECLARE_REG16(1); + DECLARE_REG16(2); + DECLARE_REG16(3); + DECLARE_REG16(4); + DECLARE_REG16(5); + DECLARE_REG16(6); + DECLARE_REG16(7); + DECLARE_REG16(8); + +#undef DECLARE_REG32 +#undef DECLARE_REG16 + + static const BasicBlock* get_graph_block(const FlowGraph& graph, u32 index) + { + ensure(index < graph.blocks.size()); + for (auto it = graph.blocks.begin(); it != graph.blocks.end(); ++it) + { + if (!index) + { + return &(*it); + } + index--; + } + return nullptr; + }; + + static FlowGraph CFG_from_source(const std::string& asm_) + { + auto ir = FPIR::from_source(asm_); + + FlowGraph graph{}; + graph.blocks.push_back({}); + + auto& bb = graph.blocks.back(); + bb.instructions = ir.instructions(); + return graph; + } + + TEST(TestFPIR, FromSource) + { + auto ir = FPIR::from_source(R"( + MOV R0, #{ 0.125 }; + ADD R1, R0, R0; + )"); + + const auto instructions = ir.instructions(); + + ASSERT_EQ(instructions.size(), 2); + + EXPECT_EQ(OPDEST{ .HEX = instructions[0].bytecode[0] }.end, 0); + EXPECT_EQ(OPDEST{ .HEX = instructions[0].bytecode[0] }.opcode, RSX_FP_OPCODE_MOV); + EXPECT_EQ(SRC0{ .HEX = instructions[0].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_CONSTANT); + EXPECT_EQ(OPDEST{ .HEX = instructions[0].bytecode[0] }.opcode, RSX_FP_OPCODE_MOV); + EXPECT_EQ(instructions[0].length, 8); + + EXPECT_EQ(OPDEST{ .HEX = instructions[1].bytecode[0] }.end, 1); + EXPECT_EQ(OPDEST{ .HEX = instructions[1].bytecode[0] }.opcode, RSX_FP_OPCODE_ADD); + EXPECT_EQ(OPDEST{ .HEX = instructions[1].bytecode[0] }.dest_reg, 1); + EXPECT_EQ(OPDEST{ .HEX = instructions[1].bytecode[0] }.fp16, 0); + EXPECT_EQ(SRC0{ .HEX = instructions[1].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(instructions[1].length, 4); + } + + TEST(TestFPIR, RegisterAnnotationPass) + { + // Code snippet reads from R0, R1 and H4, clobbers R1, H0 + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H0, H4; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 2); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + FP::RegisterAnnotationPass annotation_pass{ prog }; + + annotation_pass.run(graph); + + ASSERT_EQ(block.clobber_list.size(), 2); + ASSERT_EQ(block.input_list.size(), 3); + + EXPECT_EQ(block.clobber_list[0].reg, H0); + EXPECT_EQ(block.clobber_list[1].reg, R1); + + EXPECT_EQ(block.input_list[0].reg, H4); + EXPECT_EQ(block.input_list[1].reg, R0); + EXPECT_EQ(block.input_list[2].reg, R1); + } + + TEST(TestFPIR, RegisterAnnotationPass_MixedIO) + { + // Code snippet reads from R0, R1, clobbers R0, R1, H0. + // The H2 read does not count because R1 is clobbered. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + PK8U R0, R1; + MOV H0, H2; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + FP::RegisterAnnotationPass annotation_pass{ prog }; + + annotation_pass.run(graph); + + ASSERT_EQ(block.clobber_list.size(), 3); + ASSERT_EQ(block.input_list.size(), 2); + + EXPECT_EQ(block.clobber_list[0].reg, H0); + EXPECT_EQ(block.clobber_list[1].reg, R0); + EXPECT_EQ(block.clobber_list[2].reg, R1); + + EXPECT_EQ(block.input_list[0].reg, R0); + EXPECT_EQ(block.input_list[1].reg, R1); + } + + TEST(TestFPIR, RegisterDependencyPass_Simple16) + { + // Instruction 2 clobers R0 which in turn clobbers H0. + // Instruction 3 reads from H0 so a barrier16 is needed between them. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + PK8U R0, R1; + MOV H2, H0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(block.instructions.size(), 5); + + // H0.xy = unpackHalf2(r0.x); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.opcode, RSX_FP_OPCODE_UP2); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.fp16, 1); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_x, true); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_y, true); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.tmp_reg_index, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.fp16, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.swizzle_x, 0); + + // H0.zw = unpackHalf2(r0.y); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.opcode, RSX_FP_OPCODE_UP2); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_z, true); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_w, true); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.tmp_reg_index, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.fp16, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.swizzle_x, 1); + } + + TEST(TestFPIR, RegisterDependencyPass_Simple32) + { + // Instruction 2 clobers H1 which in turn clobbers R0. + // Instruction 3 reads from R0 so a barrier32 is needed between them. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H1, R1 + MOV R2, R0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(block.instructions.size(), 5); + + // R0.z = packHalf2(H1.xy); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.dest_reg, 0); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_z, true); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.tmp_reg_index, 1); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.swizzle_x, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.swizzle_y, 1); + + // R0.w = packHalf2(H1.zw); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.dest_reg, 0); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_w, true); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.tmp_reg_index, 1); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.swizzle_x, 2); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.swizzle_y, 3); + } + + TEST(TestFPIR, RegisterDependencyPass_Complex_IF_BothPredecessorsClobber) + { + // Multi-level but only single IF + // Mockup of a simple lighting function, R0 = Light vector, R1 = Decompressed normal. DP4 used for simplicity. + // Data hazards sprinkled in for testing. R3 is clobbered in the ancestor and the IF branch. + // Barrier should go in the IF branch here. + auto ir = FPIR::from_source(R"( + DP4 R2, R0, R1 + SFL R3 + SGT R3, R2, R0 + IF.GE + ADD R0, R0, R2 + MOV H6, #{ 0.25 } + ENDIF + ADD R0, R0, R3 + MOV R1, R0 + )"); + + auto bytecode = ir.compile(); + + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + + auto graph = deconstruct_fragment_program(prog); + auto bb0 = get_graph_block(graph, 0); + auto bb1 = get_graph_block(graph, 1); + auto bb2 = get_graph_block(graph, 2); + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(bb0->instructions.size(), 4); + ASSERT_EQ(bb1->instructions.size(), 2); + ASSERT_EQ(bb2->instructions.size(), 2); + + // bb1 has a epilogue + ASSERT_EQ(bb1->epilogue.size(), 2); + + // bb1 epilogue updates R3.xy + + // R3.x = packHalf2(H6.xy) + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.dest_reg, 3); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_x, true); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.tmp_reg_index, 6); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.swizzle_x, 0); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.swizzle_y, 1); + + // R3.y = packHalf2(H6.zw) + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.dest_reg, 3); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_y, true); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.tmp_reg_index, 6); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_x, 2); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_y, 3); + } + + TEST(TestFPIR, RegisterDependencyPass_Complex_IF_ELSE_OneBranchClobbers) + { + // Single IF-ELSE, if clobbers, ELSE does not + auto ir = FPIR::from_source(R"( + DP4 R2, R0, R1 + SFL R3 + SGT R3, R2, R0 + IF.GE + ADD R0, R0, R2 + MOV H6, #{ 0.25 } + ELSE + ADD R0, R0, R1 + ENDIF + ADD R0, R0, R3 + MOV R1, R0 + )"); + + auto bytecode = ir.compile(); + + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + ASSERT_EQ(graph.blocks.size(), 4); + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + const BasicBlock + *bb0 = get_graph_block(graph, 0), + *bb1 = get_graph_block(graph, 1), + *bb2 = get_graph_block(graph, 2), + *bb3 = get_graph_block(graph, 3); + + ASSERT_EQ(bb0->instructions.size(), 4); + ASSERT_EQ(bb1->instructions.size(), 2); + ASSERT_EQ(bb2->instructions.size(), 1); + ASSERT_EQ(bb3->instructions.size(), 2); + + // bb1 has a epilogue + ASSERT_EQ(bb0->epilogue.size(), 0); + ASSERT_EQ(bb1->epilogue.size(), 2); + ASSERT_EQ(bb2->epilogue.size(), 0); + + // bb1 epilogue updates R3.xy + + // R3.x = packHalf2(H6.xy) + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.dest_reg, 3); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_x, true); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.tmp_reg_index, 6); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.swizzle_x, 0); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.swizzle_y, 1); + + // R3.y = packHalf2(H6.zw) + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.dest_reg, 3); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_y, true); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.tmp_reg_index, 6); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_x, 2); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_y, 3); + } + + + TEST(TestFPIR, RegisterDependencyPass_Complex_IF_ELSE_Simpsons) + { + // Complex IF-ELSE nest observed in Simpson's game. Rewritten for simplicity. + // There is no tail block. No epilogues should be injected in this scenario since H4 (the trigger) is defined on all branches. + // R2 is indeed clobbered but the outer ELSE branch should not be able to see the inner IF-ELSE blocks as predecessors. + auto ir = FPIR::from_source(R"( + MOV R2, #{ 0.25 }; + IF.GT; + SLT R4, H2, #{ 0.125 }; + IF.GT; + ADD H2, H0, H3; + FMA H4, R2, H2, H3; + ELSE; + MOV H2, #{ 0.125 }; + ADD H0, H0, H2; + FMA H4, R2, H2, H3; + ENDIF; + ELSE; + FMA H4, R2, H2, H3; + MOV H0, H4; + ENDIF; + )"); + + auto bytecode = ir.compile(); + + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + ASSERT_EQ(graph.blocks.size(), 6); + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + const BasicBlock + *bb0 = get_graph_block(graph, 0), + *bb1 = get_graph_block(graph, 1), + *bb2 = get_graph_block(graph, 2), + *bb3 = get_graph_block(graph, 3), + *bb4 = get_graph_block(graph, 4), + *bb5 = get_graph_block(graph, 5); + + // Sanity + EXPECT_EQ(bb0->instructions.size(), 2); + EXPECT_EQ(bb1->instructions.size(), 2); + EXPECT_EQ(bb2->instructions.size(), 2); + EXPECT_EQ(bb3->instructions.size(), 3); + EXPECT_EQ(bb4->instructions.size(), 2); + EXPECT_EQ(bb5->instructions.size(), 0); // Phi/Merge only. + + // Nested children must recursively fall out to the closest ENDIF + ASSERT_EQ(bb4->pred.size(), 1); + EXPECT_EQ(bb4->pred.front().type, EdgeType::ELSE); + EXPECT_EQ(bb5->pred.size(), 4); // 2 IF and 2 ELSE paths exist + + // Check that we get no epilogues + EXPECT_EQ(bb0->epilogue.size(), 0); + EXPECT_EQ(bb1->epilogue.size(), 0); + EXPECT_EQ(bb2->epilogue.size(), 0); + EXPECT_EQ(bb3->epilogue.size(), 0); + EXPECT_EQ(bb4->epilogue.size(), 0); + EXPECT_EQ(bb5->epilogue.size(), 0); + } + + TEST(TestFPIR, RegisterDependencyPass_Partial32_0) + { + // Instruction 2 partially clobers H1 which in turn clobbers R0. + // Instruction 3 reads from R0 so a partial barrier32 is needed between them. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H1.x, R1.x; + MOV R2, R0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(block.instructions.size(), 4); + + OPDEST dst{ .HEX = block.instructions[2].bytecode[0] }; + SRC0 src0{ .HEX = block.instructions[2].bytecode[1] }; + SRC1 src1{ .HEX = block.instructions[2].bytecode[2] }; + + const u32 opcode = dst.opcode | (src1.opcode_hi << 6); + + // R0.z = packHalf2(H1.xy); + EXPECT_EQ(opcode, RSX_FP_OPCODE_OR16_LO); + EXPECT_EQ(dst.fp16, 0); + EXPECT_EQ(dst.dest_reg, 0); + EXPECT_EQ(dst.mask_x, false); + EXPECT_EQ(dst.mask_y, false); + EXPECT_EQ(dst.mask_z, true); + EXPECT_EQ(dst.mask_w, false); + EXPECT_EQ(src0.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(src0.tmp_reg_index, 0); + EXPECT_EQ(src0.fp16, 0); + EXPECT_EQ(src0.swizzle_x, 2); + EXPECT_EQ(src1.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(src1.tmp_reg_index, 1); + EXPECT_EQ(src1.fp16, 1); + EXPECT_EQ(src1.swizzle_x, 0); + } + + TEST(TestFPIR, RegisterDependencyPass_Partial32_1) + { + // Instruction 2 partially clobers H1 which in turn clobbers R0. + // Instruction 3 reads from R0 so a partial barrier32 is needed between them. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H1.y, R1.y; + MOV R2, R0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(block.instructions.size(), 4); + + OPDEST dst{ .HEX = block.instructions[2].bytecode[0] }; + SRC0 src0{ .HEX = block.instructions[2].bytecode[1] }; + SRC1 src1{ .HEX = block.instructions[2].bytecode[2] }; + + const u32 opcode = dst.opcode | (src1.opcode_hi << 6); + + // R0.z = packHalf2(H1.xy); + EXPECT_EQ(opcode, RSX_FP_OPCODE_OR16_HI); + EXPECT_EQ(dst.fp16, 0); + EXPECT_EQ(dst.dest_reg, 0); + EXPECT_EQ(dst.mask_x, false); + EXPECT_EQ(dst.mask_y, false); + EXPECT_EQ(dst.mask_z, true); + EXPECT_EQ(dst.mask_w, false); + EXPECT_EQ(src0.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(src0.tmp_reg_index, 0); + EXPECT_EQ(src0.fp16, 0); + EXPECT_EQ(src0.swizzle_x, 2); + EXPECT_EQ(src1.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(src1.tmp_reg_index, 1); + EXPECT_EQ(src1.fp16, 1); + EXPECT_EQ(src1.swizzle_x, 1); + } + + TEST(TestFPIR, RegisterDependencyPass_SkipDelaySlots) + { + // Instruction 2 clobers H1 which in turn clobbers R0. + // Instruction 3 reads from R0 but is a delay slot that does nothing and can be NOPed. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H1, R1 + MOV R0, R0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog, { .skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // Delay slot detection will cause no dependency injection + ASSERT_EQ(block.instructions.size(), 3); + } + + TEST(TestFPIR, RegisterDependencyPass_Skip_IF_ELSE_Ancestors) + { + // R4/H8 is clobbered but an IF-ELSE chain follows it. + // Merge block reads H8, but since both IF-ELSE legs resolve the dependency, we do not need a barrier for H8. + // H6 is included as a control. + auto ir = FPIR::from_source(R"( + MOV R4, #{ 0.25 } + MOV H6.x, #{ 0.125 } + IF.LT + MOV H8, #{ 0.0 } + ELSE + MOV H8, #{ 0.25 } + ENDIF + ADD R0, R3, H8 + )"); + + auto bytecode = ir.compile(); + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + // Verify state before + ASSERT_EQ(graph.blocks.size(), 4); + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 3); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 3)->instructions.size(), 1); + + FP::RegisterAnnotationPass annotation_pass{ prog, {.skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // We get one barrier on R3 (H6) but nont for R4 (H8) + EXPECT_EQ(get_graph_block(graph, 0)->epilogue.size(), 1); + + // No intra-block barriers + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 3); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 3)->instructions.size(), 1); + } + + TEST(TestFPIR, RegisterDependencyPass_Process_IF_Ancestors) + { + // H8.x is clobbered but only an IF sequence follows with no ELSE. + // Merge block reads r4.x, but since both IF-ELSE legs resolve the dependency, we do not need a barrier. + auto ir = FPIR::from_source(R"( + MOV H8.x, #{ 0.25 } + IF.LT + MOV R4.x, #{ 0.0 } + ENDIF + MOV R0, R4 + )"); + + auto bytecode = ir.compile(); + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + // Verify state before + ASSERT_EQ(graph.blocks.size(), 3); + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 1); + + FP::RegisterAnnotationPass annotation_pass{ prog, {.skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // A barrier will be inserted into block 0 epilogue + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 1); + + EXPECT_EQ(get_graph_block(graph, 0)->epilogue.size(), 1); + EXPECT_EQ(get_graph_block(graph, 1)->epilogue.size(), 0); + EXPECT_EQ(get_graph_block(graph, 2)->epilogue.size(), 0); + } + + TEST(TestFPIR, RegisterDependencyPass_Complex_IF_ELSE_Ancestor_Clobber) + { + // 2 clobbered registers up the chain. + // 1 full barrier is needed for R4 (4 instructions) + auto ir = FPIR::from_source(R"( + MOV R4, #{ 0.0 } + IF.LT + MOV H9, #{ 0.25 } + ENDIF + MOV H8, #{ 0.25 } + IF.LT + IF.GT + ADD R0, R0, R0 + ELSE + ADD R0, R1, R0 + ENDIF + ENDIF + ADD R0, R0, R4 + )"); + + auto bytecode = ir.compile(); + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + // Verify state before + ASSERT_EQ(graph.blocks.size(), 7); + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 3)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 4)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 5)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 6)->instructions.size(), 1); + + FP::RegisterAnnotationPass annotation_pass{ prog, {.skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // Full-lane barrier on writing blocks + EXPECT_EQ(get_graph_block(graph, 1)->epilogue.size(), 2); + EXPECT_EQ(get_graph_block(graph, 2)->epilogue.size(), 2); + + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 3)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 4)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 5)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 6)->instructions.size(), 1); + } + + TEST(TestFPIR, RegisterDependencyPass_SplinterCell_DelaySlot) + { + // Real shader pattern found in splinter cell blacklist. + // TEX instructions replaced with MOV for simplicity. + // There are no dependent reads here, no barriers are expected. + // In the game, instruction 4 was misclassified as a delay slot, causing a skipped clobber. + auto ir = FPIR::from_source(R"( + MOV R0.w, #{ 0.25 } + MOV H0, H8 + MUL R0.w, H0.w, R0.w + MOV R0.xyz, H0.xyz + MOV R1, #{ 0.25 } + FMA H0, R0, #{ 0.125 }, R1 + )"); + + auto bytecode = ir.compile(); + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + // Verify state before + ASSERT_EQ(graph.blocks.size(), 1); + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 6); + + FP::RegisterAnnotationPass annotation_pass{ prog, {.skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // Verify state after + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 6); + EXPECT_EQ(get_graph_block(graph, 0)->epilogue.size(), 0); + } +} diff --git a/rpcs3/tests/test_simple_array.cpp b/rpcs3/tests/test_simple_array.cpp index 8d64599b96..ebedff861d 100644 --- a/rpcs3/tests/test_simple_array.cpp +++ b/rpcs3/tests/test_simple_array.cpp @@ -324,6 +324,40 @@ namespace rsx EXPECT_EQ(arr.find_if(FN(x == 99)), nullptr); } + TEST(SimpleArray, InsertArray) + { + rsx::simple_array arr{ + 0, 1, 2, 6, 7, 8, 9 + }; + + const std::vector tail{ + 10, 11, 12 + }; + + const std::vector mid{ + 3, 4, 5 + }; + + // Insert end + arr.insert(arr.end(), tail); + EXPECT_EQ(arr.size(), 10); + + // Insert mid + auto it = arr.begin(); + std::advance(it, 3); + it = arr.insert(it, mid); + + EXPECT_EQ(arr.size(), 13); + EXPECT_EQ(std::distance(arr.begin(), it), 3); + EXPECT_EQ(*it, 3); + + // Verify + for (unsigned i = 0; i < arr.size(); ++i) + { + EXPECT_EQ(arr[i], static_cast(i)); + } + } + TEST(AlignedAllocator, Alloc) { auto ptr = rsx::aligned_allocator::malloc<256>(16); diff --git a/rpcs3/util/atomic.cpp b/rpcs3/util/atomic.cpp index 67f48f8dc7..595162cd04 100644 --- a/rpcs3/util/atomic.cpp +++ b/rpcs3/util/atomic.cpp @@ -1,6 +1,6 @@ #include "atomic.hpp" -#if defined(__linux__) +#if defined(__linux__) || defined(__APPLE__) #define USE_FUTEX #elif !defined(_WIN32) #define USE_STD diff --git a/rpcs3/util/logs.cpp b/rpcs3/util/logs.cpp index d48a22c0aa..0276f90e0b 100644 --- a/rpcs3/util/logs.cpp +++ b/rpcs3/util/logs.cpp @@ -89,8 +89,8 @@ namespace logs z_stream m_zs{}; shared_mutex m_m{}; - atomic_t m_buf{0}; // MSB (39 bits): push begin, LSB (25 bis): push size - atomic_t m_out{0}; // Amount of bytes written to file + atomic_t m_buf{0}; // MSB (39 bits): push begin, LSB (25 bis): push size + atomic_t m_out{0}; // Amount of bytes written to file uchar m_zout[65536]{}; diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp index d4f450ab69..60a337e750 100644 --- a/rpcs3/util/media_utils.cpp +++ b/rpcs3/util/media_utils.cpp @@ -82,8 +82,8 @@ namespace utils return; } - std::string msg = line; - fmt::trim_back(msg, "\n\r\t "); + std::string msg_buf = line; + std::string_view msg = fmt::trim_back_sv(msg_buf, "\n\r\t "); if (level <= AV_LOG_ERROR) media_log.error("av_log: %s", msg); diff --git a/rpcs3/util/sysinfo.cpp b/rpcs3/util/sysinfo.cpp index 140f608d35..d060f27e22 100755 --- a/rpcs3/util/sysinfo.cpp +++ b/rpcs3/util/sysinfo.cpp @@ -850,7 +850,7 @@ static const bool s_tsc_freq_evaluated = []() -> bool printf("[TSC calibration] Available clock sources: '%s'\n", clock_sources.c_str()); // Check if the Kernel has blacklisted the TSC - const auto available_clocks = fmt::split(clock_sources, { " " }); + const auto available_clocks = fmt::split_sv(clock_sources, { " " }); const bool tsc_reliable = std::find(available_clocks.begin(), available_clocks.end(), "tsc") != available_clocks.end(); if (!tsc_reliable)