Merge branch 'master' into windows-clang

This commit is contained in:
qurious-pixel 2025-12-21 19:55:49 -08:00 committed by GitHub
commit 931bafcf87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
181 changed files with 7324 additions and 1567 deletions

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/7zip/C>
$<INSTALL_INTERFACE:/7zip/C>)
target_include_directories(3rdparty_7zip INTERFACE 7zip)
target_include_directories(3rdparty_7zip SYSTEM INTERFACE 7zip)
set_property(TARGET 3rdparty_7zip PROPERTY FOLDER "3rdparty/")

View file

@ -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()

View file

@ -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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/soundtouch/include>
$<INSTALL_INTERFACE:/soundtouch/include>)

View file

@ -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)

View file

@ -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()

View file

@ -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()

@ -1 +1 @@
Subproject commit 595bf0007ab1929570c7671f091313c8fc20644e
Subproject commit 187240970746d00bbd26b0f5873ed54d2477f9f3

@ -1 +1 @@
Subproject commit 759ac5d698baefca53f1975a0bb1d2dcbdb9f836
Subproject commit 008e03eac0ac1d5f85e16f5fcaefdda3fee75cb8

View file

@ -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)

View file

@ -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")

View file

@ -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})

View file

@ -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)

View file

@ -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()

View file

@ -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)

View file

@ -1,2 +1,2 @@
add_library(3rdparty_stblib INTERFACE)
target_include_directories(3rdparty_stblib INTERFACE stb)
target_include_directories(3rdparty_stblib SYSTEM INTERFACE stb)

View file

@ -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()

View file

@ -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)

View file

@ -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<LPWSTR>(&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<std::string> fmt::split(std::string_view source, std::initializer_li
return result;
}
std::vector<std::string_view> fmt::split_sv(std::string_view source, std::initializer_list<std::string_view> separators, bool is_skip_empty)
{
std::vector<std::string_view> 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;

View file

@ -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<std::string> split(std::string_view source, std::initializer_list<std::string_view> 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<std::string_view> split_sv(std::string_view source, std::initializer_list<std::string_view> 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 <typename T>
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 <typename T>
std::string merge(std::initializer_list<T> sources, const std::string& separator)
std::string merge(std::initializer_list<T> 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;

View file

@ -2165,10 +2165,17 @@ void thread_base::start()
ensure(m_thread);
ensure(::ResumeThread(reinterpret_cast<HANDLE>(+m_thread)) != static_cast<DWORD>(-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<pthread_t*>(&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<pthread_t*>(&m_thread.raw()), &attrs, entry_point, this) == 0);
#else
ensure(pthread_create(reinterpret_cast<pthread_t*>(&m_thread.raw()), nullptr, entry_point, this) == 0);
#endif

View file

@ -23,7 +23,6 @@ Intersection (&) and symmetric difference (^) is also available.
#include "util/types.hpp"
#include "util/atomic.hpp"
#include "Utilities/StrFmt.h"
template <typename T>
concept BitSetEnum = std::is_enum_v<T> && requires(T x)
@ -384,6 +383,9 @@ public:
}
};
template <typename T>
struct fmt_unveil;
template <typename T>
struct fmt_unveil<bs_t<T>>
{

View file

@ -27,9 +27,9 @@ void fmt_class_string<cheat_type>::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))

View file

@ -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;
};

View file

@ -17,6 +17,8 @@
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#elif __APPLE__
#include <os/os_sync_wait_on_address.h>
#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<int>(val), timeout, nullptr, static_cast<int>(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<void*>(uaddr), static_cast<uint64_t>(val), sizeof(uint), OS_SYNC_WAIT_ON_ADDRESS_NONE, OS_CLOCK_MACH_ABSOLUTE_TIME, nsec);
}
else
{
return os_sync_wait_on_address(const_cast<void*>(uaddr), static_cast<uint64_t>(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<void*>(uaddr), sizeof(uint), OS_SYNC_WAKE_BY_ADDRESS_NONE);
}
else if (val-- >= 0)
{
ret = os_sync_wake_by_address_any(const_cast<void*>(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<volatile void*, waiter*> map;
struct bucket_t
{
std::mutex mutex;
std::unordered_multimap<volatile void*, waiter*> 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<u64>(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;
}
}

View file

@ -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

View file

@ -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<std::string, install_entry*>& 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);

View file

@ -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<package_reader>& readers, std::deque<std::string>& bootable_paths);
const psf::registry& get_psf() const { return m_psf; }

View file

@ -12,6 +12,7 @@
#include <cstring>
#include <cstdio>
#include <ctime>
#include "Utilities/StrFmt.h"
#include "Utilities/StrUtil.h"
#include "Utilities/File.h"

View file

@ -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)

View file

@ -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<u64, 64> s_cpu_counter{0};
static atomic_t<u64, 128> s_cpu_counter{0};
// List of posted tasks for suspend_all
//static atomic_t<cpu_thread::suspend_work*> s_cpu_work[128]{};

View file

@ -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;

View file

@ -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<T>::get_type(ir->getContext())});
#else
return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshl, {llvm_value_t<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<T>::get_type(ir->getContext())});
#else
return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshr, {llvm_value_t<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<T>::get_type(ir->getContext())});
#else
return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t<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<T>::get_type(ir->getContext())});
#else
return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t<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<Types>()...});
#else
return llvm::Intrinsic::getDeclaration(_module, id, {get_type<Types>()...});
#endif
}
template <typename T1, typename T2>

View file

@ -276,6 +276,8 @@ public:
u64 start_timestamp_us = 0;
std::array<ps_move_data, CELL_GEM_MAX_NUM> fake_move_data {}; // No need to be in savestate
atomic_t<u32> m_wake_up = 0;
atomic_t<u32> 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<u8>(0.299f * r + 0.587f * g + 0.114f * b); }
static inline u8 U(u8 r, u8 g, u8 b) { return static_cast<u8>(-0.14713f * r - 0.28886f * g + 0.436f * b); }
static inline u8 V(u8 r, u8 g, u8 b) { return static_cast<u8>(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<u8>(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<u8>(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<u8>(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<u8>& video_data_in, u32 width, u32 height,
u8* video_data_out, u32 video_data_out_size, std::string_view caller)
template <bool use_gain>
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;
// HamiltonAdams 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<u8>(std::clamp(r * gain_r, 0.0f, 255.0f));
dst0[1] = static_cast<u8>(std::clamp(b * gain_b, 0.0f, 255.0f));
dst0[2] = static_cast<u8>(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<u8>(std::clamp(r * gain_r, 0.0f, 255.0f));
dst0[1] = static_cast<u8>(std::clamp(b * gain_b, 0.0f, 255.0f));
dst0[2] = static_cast<u8>(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<true>(src, dst, alpha, gain_r, gain_g, gain_b);
else
debayer_raw8_impl<false>(src, dst, alpha, gain_r, gain_g, gain_b);
}
bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc,
const std::vector<u8>& 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<s32>(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<u32>(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<u8> corrected_buffer;
thread_local std::vector<u8> combined_buffer;
thread_local std::vector<u8> 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<u8>(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<usz>(required_in_size, required_out_size));
std::memcpy(video_data_out, src_data, std::min<usz>(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<usz>(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<usz>(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<usz>(required_in_size, required_out_size));
std::memcpy(video_data_out, src_data, std::min<usz>(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<usz>(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<usz>(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<usz>(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<usz>(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<usz>(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<usz>(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<usz>(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<usz>(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<usz>(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<usz>(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<usz>(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<gem_camera_shared>();
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<f32>(shared_data.width);
const f32 scaling_height = y_max / static_cast<f32>(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<f32>(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<CellGemState>& 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<CellGemState>& 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<gem_camera_shared>();
@ -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<f32>(shared_data.width);
const f32 scaling_height = y_max / static_cast<f32>(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<f32>(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<T, vm::ptr<CellGemState>>)
{
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<T, vm::ptr<CellGemState>>)
{
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<gem_config>().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<T, vm::ptr<CellGemImageState>>)
{
@ -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<T, vm::ptr<CellGemState>>)
{
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<gem_config>().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<T, vm::ptr<CellGemImageState>>)
{
@ -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];
}
}
}

View file

@ -246,15 +246,15 @@ struct CellGemInfo
// z increases towards user (away from the camera)
struct CellGemState
{
be_t<f32> pos[4]; // center of sphere (mm)
be_t<f32> vel[4]; // velocity of sphere (mm/s)
be_t<f32> accel[4]; // acceleration of sphere (mm/s²)
be_t<f32> pos[4]; // center of sphere in world coordinates (mm)
be_t<f32> vel[4]; // velocity of sphere in world coordinates (mm/s)
be_t<f32> accel[4]; // acceleration of sphere in world coordinates (mm/s²)
be_t<f32> quat[4]; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
be_t<f32> angvel[4]; // angular velocity of controller (radians/s)
be_t<f32> angaccel[4]; // angular acceleration of controller (radians/s²)
be_t<f32> handle_pos[4]; // center of controller handle (mm)
be_t<f32> handle_vel[4]; // velocity of controller handle (mm/s)
be_t<f32> handle_accel[4]; // acceleration of controller handle (mm/s²)
be_t<f32> angvel[4]; // angular velocity of controller in world coordinates (radians/s)
be_t<f32> angaccel[4]; // angular acceleration of controller in world coordinates (radians/s²)
be_t<f32> handle_pos[4]; // center of controller handle in world coordinates (mm)
be_t<f32> handle_vel[4]; // velocity of controller handle in world coordinates (mm/s)
be_t<f32> handle_accel[4]; // acceleration of controller handle in world coordinates (mm/s²)
CellGemPadData pad;
CellGemExtPortData ext;
be_t<u64> timestamp; // system_time_t (microseconds)

View file

@ -32,6 +32,7 @@ using SceNpBasicMessageRecvAction = u32;
using SceNpClanId = u32;
using SceNpClansMessageId = u32;
using SceNpClansMemberRole = u32;
using SceNpClansMemberStatus = s32;
using SceNpCustomMenuIndexMask = u32;

View file

@ -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(

View file

@ -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 <memory>
#include "sceNpClans.h"
LOG_CHANNEL(sceNpClans);
template<>
@ -14,6 +18,7 @@ void fmt_class_string<SceNpClansError>::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<SceNpCommunicationId> commId, vm::cptr<SceNpC
return SCE_NP_CLANS_ERROR_NOT_SUPPORTED;
}
// Allocate space for a client somewhere
std::shared_ptr<clan::clans_client> client = std::make_shared<clan::clans_client>();
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<SceNpClansRequestHandle> 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<sce_np_clans_manager>().is_initialized)
{
@ -134,36 +143,58 @@ error_code sceNpClansCreateRequest(vm::ptr<SceNpClansRequestHandle> handle, u64
return SCE_NP_CLANS_ERROR_NOT_SUPPORTED;
}
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle)
error_code sceNpClansDestroyRequest(SceNpClansRequestHandle handle)
{
sceNpClans.todo("sceNpClansDestroyRequest(handle=*0x%x)", handle);
sceNpClans.warning("sceNpClansDestroyRequest(handle=*0x%x)", handle);
if (!g_fxo->get<sce_np_clans_manager>().is_initialized)
{
return SCE_NP_CLANS_ERROR_NOT_INITIALIZED;
}
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
SceNpClansError res = clans_manager.client->destroy_request(handle);
if (res != SCE_NP_CLANS_SUCCESS)
{
return res;
}
return CELL_OK;
}
error_code sceNpClansAbortRequest(vm::ptr<SceNpClansRequestHandle> handle)
error_code sceNpClansAbortRequest(SceNpClansRequestHandle handle)
{
sceNpClans.todo("sceNpClansAbortRequest(handle=*0x%x)", handle);
sceNpClans.warning("sceNpClansAbortRequest(handle=*0x%x)", handle);
if (!g_fxo->get<sce_np_clans_manager>().is_initialized)
{
return SCE_NP_CLANS_ERROR_NOT_INITIALIZED;
}
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
clans_manager.client->destroy_request(handle);
return CELL_OK;
}
error_code sceNpClansCreateClan(vm::ptr<SceNpClansRequestHandle> handle, vm::cptr<char> name, vm::cptr<char> tag, vm::ptr<SceNpClanId> clanId)
error_code sceNpClansCreateClan(SceNpClansRequestHandle handle, vm::cptr<char> name, vm::cptr<char> tag, vm::ptr<SceNpClanId> 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<sce_np_clans_manager>().is_initialized)
{
@ -180,31 +211,60 @@ error_code sceNpClansCreateClan(vm::ptr<SceNpClansRequestHandle> handle, vm::cpt
return SCE_NP_CLANS_ERROR_EXCEEDS_MAX;
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> 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<sce_np_clans_manager>().is_initialized)
{
return SCE_NP_CLANS_ERROR_NOT_INITIALIZED;
}
if (!clanId)
{
return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT;
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansEntry> clanList, vm::ptr<SceNpClansPagingResult> pageResult)
error_code sceNpClansGetClanList(SceNpClansRequestHandle handle, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansEntry> clanList, vm::ptr<SceNpClansPagingResult> 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<sce_np_clans_manager>().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<SceNpClansRequestHandle> handle, vm::cp
}
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, vm::cptr<SceNpClansPagingRequest> paging, vm::cptr<SceNpId> npid, vm::ptr<SceNpClansEntry> clanList, vm::ptr<SceNpClansPagingResult> pageResult)
// TODO: seems to not be needed, even by the PS3..?
error_code sceNpClansGetClanListByNpId(SceNpClansRequestHandle handle, vm::cptr<SceNpClansPagingRequest> paging, vm::cptr<SceNpId> npid, vm::ptr<SceNpClansEntry> clanList, vm::ptr<SceNpClansPagingResult> 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<SceNpClansRequestHandle> handle,
return CELL_OK;
}
error_code sceNpClansSearchByProfile(vm::ptr<SceNpClansRequestHandle> handle, vm::cptr<SceNpClansPagingRequest> paging, vm::cptr<SceNpClansSearchableProfile> search, vm::ptr<SceNpClansClanBasicInfo> results, vm::ptr<SceNpClansPagingResult> pageResult)
// TODO: seems to not be needed, even by the PS3..?
error_code sceNpClansSearchByProfile(SceNpClansRequestHandle handle, vm::cptr<SceNpClansPagingRequest> paging, vm::cptr<SceNpClansSearchableProfile> search, vm::ptr<SceNpClansClanBasicInfo> results, vm::ptr<SceNpClansPagingResult> 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<SceNpClansRequestHandle> handle, vm
return CELL_OK;
}
error_code sceNpClansSearchByName(vm::ptr<SceNpClansRequestHandle> handle, vm::cptr<SceNpClansPagingRequest> paging, vm::cptr<SceNpClansSearchableName> search, vm::ptr<SceNpClansClanBasicInfo> results, vm::ptr<SceNpClansPagingResult> pageResult)
error_code sceNpClansSearchByName(SceNpClansRequestHandle handle, vm::cptr<SceNpClansPagingRequest> paging, vm::cptr<SceNpClansSearchableName> search, vm::ptr<SceNpClansClanBasicInfo> results, vm::ptr<SceNpClansPagingResult> 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<sce_np_clans_manager>().is_initialized)
{
@ -292,12 +378,38 @@ error_code sceNpClansSearchByName(vm::ptr<SceNpClansRequestHandle> handle, vm::c
}
}
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::ptr<SceNpClansClanInfo> info)
error_code sceNpClansGetClanInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::ptr<SceNpClansClanInfo> 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<sce_np_clans_manager>().is_initialized)
{
@ -310,12 +422,24 @@ error_code sceNpClansGetClanInfo(vm::ptr<SceNpClansRequestHandle> handle, SceNpC
return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT;
}
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpClansUpdatableClanInfo> info)
error_code sceNpClansUpdateClanInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpClansUpdatableClanInfo> 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<sce_np_clans_manager>().is_initialized)
{
@ -328,17 +452,24 @@ error_code sceNpClansUpdateClanInfo(vm::ptr<SceNpClansRequestHandle> 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<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpClansPagingRequest> paging, SceNpClansMemberStatus status, vm::ptr<SceNpClansMemberEntry> memList, vm::ptr<SceNpClansPagingResult> pageResult)
error_code sceNpClansGetMemberList(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpClansPagingRequest> paging, SceNpClansMemberStatus status, vm::ptr<SceNpClansMemberEntry> memList, vm::ptr<SceNpClansPagingResult> 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<sce_np_clans_manager>().is_initialized)
{
@ -358,12 +489,36 @@ error_code sceNpClansGetMemberList(vm::ptr<SceNpClansRequestHandle> handle, SceN
}
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpId> npid, vm::ptr<SceNpClansMemberEntry> memInfo)
error_code sceNpClansGetMemberInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpId> npid, vm::ptr<SceNpClansMemberEntry> 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<sce_np_clans_manager>().is_initialized)
{
@ -375,12 +530,28 @@ error_code sceNpClansGetMemberInfo(vm::ptr<SceNpClansRequestHandle> handle, SceN
return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT;
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpClansUpdatableMemberInfo> info)
error_code sceNpClansUpdateMemberInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpClansUpdatableMemberInfo> 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<sce_np_clans_manager>().is_initialized)
{
@ -389,21 +560,27 @@ error_code sceNpClansUpdateMemberInfo(vm::ptr<SceNpClansRequestHandle> 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<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpId> npid, u32 role)
error_code sceNpClansChangeMemberRole(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpId> 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<sce_np_clans_manager>().is_initialized)
{
@ -415,10 +592,23 @@ error_code sceNpClansChangeMemberRole(vm::ptr<SceNpClansRequestHandle> handle, S
return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT;
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansMemberRole>(role));
if (ret != SCE_NP_CLANS_SUCCESS)
{
return ret;
}
return CELL_OK;
}
error_code sceNpClansGetAutoAcceptStatus(vm::ptr<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::ptr<b8> enable)
// TODO: no struct currently implements `autoAccept` as a field
error_code sceNpClansGetAutoAcceptStatus(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::ptr<b8> enable)
{
sceNpClans.todo("sceNpClansGetAutoAcceptStatus(handle=*0x%x, clanId=%d, enable=*0x%x)", handle, clanId, enable);
@ -435,7 +625,8 @@ error_code sceNpClansGetAutoAcceptStatus(vm::ptr<SceNpClansRequestHandle> handle
return CELL_OK;
}
error_code sceNpClansUpdateAutoAcceptStatus(vm::ptr<SceNpClansRequestHandle> 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<SceNpClansRequestHandle> han
return CELL_OK;
}
error_code sceNpClansJoinClan(vm::ptr<SceNpClansRequestHandle> 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<sce_np_clans_manager>().is_initialized)
{
return SCE_NP_CLANS_ERROR_NOT_INITIALIZED;
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> 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<sce_np_clans_manager>().is_initialized)
{
return SCE_NP_CLANS_ERROR_NOT_INITIALIZED;
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpId> npid, vm::cptr<SceNpClansMessage> message)
error_code sceNpClansKickMember(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpId> npid, vm::cptr<SceNpClansMessage> 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<sce_np_clans_manager>().is_initialized)
{
@ -493,12 +702,30 @@ error_code sceNpClansKickMember(vm::ptr<SceNpClansRequestHandle> handle, SceNpCl
}
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpId> npid, vm::cptr<SceNpClansMessage> message)
error_code sceNpClansSendInvitation(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpId> npid, vm::cptr<SceNpClansMessage> 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<sce_np_clans_manager>().is_initialized)
{
@ -518,12 +745,30 @@ error_code sceNpClansSendInvitation(vm::ptr<SceNpClansRequestHandle> handle, Sce
}
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpId> npid)
error_code sceNpClansCancelInvitation(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpId> 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<sce_np_clans_manager>().is_initialized)
{
@ -535,12 +780,24 @@ error_code sceNpClansCancelInvitation(vm::ptr<SceNpClansRequestHandle> handle, S
return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT;
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpClansMessage> message, b8 accept)
error_code sceNpClansSendInvitationResponse(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpClansMessage> 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<sce_np_clans_manager>().is_initialized)
{
@ -555,12 +812,32 @@ error_code sceNpClansSendInvitationResponse(vm::ptr<SceNpClansRequestHandle> han
}
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, u32 clanId, vm::cptr<SceNpClansMessage> message)
error_code sceNpClansSendMembershipRequest(SceNpClansRequestHandle handle, u32 clanId, vm::cptr<SceNpClansMessage> 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<sce_np_clans_manager>().is_initialized)
{
@ -575,24 +852,49 @@ error_code sceNpClansSendMembershipRequest(vm::ptr<SceNpClansRequestHandle> hand
}
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> 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<sce_np_clans_manager>().is_initialized)
{
return SCE_NP_CLANS_ERROR_NOT_INITIALIZED;
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpId> npid, vm::cptr<SceNpClansMessage> message, b8 allow)
error_code sceNpClansSendMembershipResponse(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpId> npid, vm::cptr<SceNpClansMessage> 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<sce_np_clans_manager>().is_initialized)
{
@ -612,12 +914,30 @@ error_code sceNpClansSendMembershipResponse(vm::ptr<SceNpClansRequestHandle> han
}
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansBlacklistEntry> bl, vm::ptr<SceNpClansPagingResult> pageResult)
error_code sceNpClansGetBlacklist(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansBlacklistEntry> bl, vm::ptr<SceNpClansPagingResult> 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<sce_np_clans_manager>().is_initialized)
{
@ -637,53 +957,101 @@ error_code sceNpClansGetBlacklist(vm::ptr<SceNpClansRequestHandle> handle, SceNp
}
}
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpId> npid)
error_code sceNpClansAddBlacklistEntry(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpId> 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<sce_np_clans_manager>().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<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpId> npid)
error_code sceNpClansRemoveBlacklistEntry(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpId> 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<sce_np_clans_manager>().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<named_thread<np::np_handler>>();
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansMessageEntry> mlist, vm::ptr<SceNpClansPagingResult> pageResult)
error_code sceNpClansRetrieveAnnouncements(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansMessageEntry> mlist, vm::ptr<SceNpClansPagingResult> 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<sce_np_clans_manager>().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<SceNpClansRequestHandle> hand
}
}
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpClansMessage> message, vm::cptr<SceNpClansMessageData> data, u32 duration, vm::ptr<SceNpClansMessageId> mId)
error_code sceNpClansPostAnnouncement(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpClansMessage> message, vm::cptr<SceNpClansMessageData> data, u32 duration, vm::ptr<SceNpClansMessageId> 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<sce_np_clans_manager>().is_initialized)
{
@ -713,32 +1105,57 @@ error_code sceNpClansPostAnnouncement(vm::ptr<SceNpClansRequestHandle> 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<sce_np_clans_manager>();
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
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<SceNpClansRequestHandle> 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<sce_np_clans_manager>().is_initialized)
{
return SCE_NP_CLANS_ERROR_NOT_INITIALIZED;
}
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
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<SceNpClansRequestHandle> handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr<SceNpClansMessage> message, vm::cptr<SceNpClansMessageData> data, u32 duration, vm::ptr<SceNpClansMessageId> mId)
error_code sceNpClansPostChallenge(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr<SceNpClansMessage> message, vm::cptr<SceNpClansMessageData> data, u32 duration, vm::ptr<SceNpClansMessageId> 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<SceNpClansRequestHandle> handle, SceN
return CELL_OK;
}
error_code sceNpClansRetrievePostedChallenges(vm::ptr<SceNpClansRequestHandle> handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansMessageEntry> mList, vm::ptr<SceNpClansPagingResult> pageResult)
error_code sceNpClansRetrievePostedChallenges(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansMessageEntry> mList, vm::ptr<SceNpClansPagingResult> 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<SceNpClansRequestHandle> h
return CELL_OK;
}
error_code sceNpClansRemovePostedChallenge(vm::ptr<SceNpClansRequestHandle> 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<SceNpClansRequestHandle> hand
return CELL_OK;
}
error_code sceNpClansRetrieveChallenges(vm::ptr<SceNpClansRequestHandle> handle, SceNpClanId clanId, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansMessageEntry> mList, vm::ptr<SceNpClansPagingResult> pageResult)
error_code sceNpClansRetrieveChallenges(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr<SceNpClansPagingRequest> paging, vm::ptr<SceNpClansMessageEntry> mList, vm::ptr<SceNpClansPagingResult> pageResult)
{
sceNpClans.todo("sceNpClansRetrieveChallenges(handle=*0x%x, clanId=%d, paging=*0x%x, mList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, mList, pageResult);

View file

@ -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<struct SceNpClansRequest>;
using SceNpClansRequestHandle = u32;
// Paging request structure
struct SceNpClansPagingRequest
@ -159,8 +161,8 @@ struct SceNpClansClanBasicInfo
{
be_t<u32> clanId;
be_t<u32> 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<s32> intAttr2SearchOp;
be_t<s32> intAttr3SearchOp;
be_t<s32> 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<s32> 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<u32> 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<u32> binData1Size;
@ -233,8 +235,8 @@ struct SceNpClansUpdatableMemberInfo
be_t<u32> fields;
u8 binData1;
be_t<u32> 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<bool> is_initialized = false;
};

View file

@ -3931,6 +3931,17 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* 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<u32>* 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<u32>* 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<u32>* 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<reg_state_t, s_reg_max>& 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;

View file

@ -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)
{

View file

@ -402,7 +402,7 @@ protected:
std::unordered_map<u32, pattern_info> 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

View file

@ -488,7 +488,7 @@ waitpkg_func static void __tpause(u32 cycles, u32 cstate)
namespace vm
{
std::array<atomic_t<reservation_waiter_t>, 2048> g_resrv_waiters_count{};
std::array<atomic_t<reservation_waiter_t, 128>, 1024> g_resrv_waiters_count{};
}
void do_cell_atomic_128_store(u32 addr, const void* to_write);
@ -499,7 +499,7 @@ const spu_decoder<spu_itype> s_spu_itype;
namespace vm
{
extern atomic_t<u64, 64> g_range_lock_set[64];
extern atomic_t<u64, 128> 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<u64, 64>* range_lock = nullptr;
atomic_t<u64, 128>* 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<u64, 64>* range_lock)
bool spu_thread::reservation_check(u32 addr, u32 hash, atomic_t<u64, 128>* 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<decltype(rdata)>(addr)) == hash;
return compute_rdata_hash32(*vm::get_super_ptr<decltype(rdata)>(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--;

View file

@ -708,7 +708,7 @@ public:
const decltype(rdata)* resrv_mem{};
// Range Lock pointer
atomic_t<u64, 64>* range_lock{};
atomic_t<u64, 128>* 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<u64, 64>* range_lock);
static bool reservation_check(u32 addr, u32 hash, atomic_t<u64, 128>* range_lock);
usz register_cache_line_waiter(u32 addr);
void deregister_cache_line_waiter(usz index);
@ -915,7 +915,7 @@ public:
static atomic_t<u32> g_raw_spu_id[5];
static atomic_t<u32> g_spu_work_count;
static atomic_t<u64> g_spu_waiters_by_value[6];
static atomic_t<u64, 128> g_spu_waiters_by_value[6];
static u32 find_raw_spu(u32 id)
{

View file

@ -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<u64, 64>* range_lock = nullptr;
atomic_t<u64, 128>* range_lock = nullptr;
if (cpu->get_class() == thread_class::spu)
{

View file

@ -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<fs::file> fragments;
@ -1389,7 +1389,7 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> 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<char> path, vm::ptr<CellFsStat> 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<char> path, vm::ptr<CellFsStat>
// 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<char> path, vm::ptr<CellFsStat>
}
}
// 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<char> 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;
}

View file

@ -347,7 +347,7 @@ error_code sys_mutex_unlock(ppu_thread& ppu, u32 mutex_id)
const auto mutex = idm::check<lv2_obj, lv2_mutex>(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);

View file

@ -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_device_vfs>(usb_info, get_new_location()));
}
const std::vector<std::string> 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<std::string_view> 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));

View file

@ -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);
}

View file

@ -205,7 +205,7 @@ protected:
std::set<T> key_codes;
const std::string& def = cfg_string.def;
const std::vector<std::string> names = cfg_pad::get_buttons(cfg_string);
const std::vector<std::string> names = cfg_pad::get_buttons(cfg_string.to_string());
T def_code = umax;
for (const std::string& nam : names)

View file

@ -177,7 +177,7 @@ Note str_to_note(const std::string_view name)
std::optional<std::pair<Id, Note>> 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<Id, Note> create_id_to_note_mapping()
};
// Apply configured overrides.
const std::vector<std::string> 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<std::string_view> 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<u8> parse_combo(const std::string_view name, const std::string_view
return {};
}
std::vector<u8> 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);

View file

@ -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())
{

View file

@ -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;

View file

@ -27,11 +27,11 @@ void fmt_class_string<midi_device>::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<std::string> parts = fmt::split(str, {"ßßß"}); !parts.empty())
if (const std::vector<std::string_view> parts = fmt::split_sv(str, {"ßßß"}); !parts.empty())
{
u64 result;

View file

@ -1,6 +1,7 @@
#pragma once
#include <string>
#include <string_view>
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);
};

View file

@ -5,7 +5,7 @@
extern std::string g_input_config_override;
std::vector<std::string> cfg_pad::get_buttons(const std::string& str)
std::vector<std::string> cfg_pad::get_buttons(std::string_view str)
{
std::vector<std::string> vec = fmt::split(str, {","});

View file

@ -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<std::string> get_buttons(const std::string& str);
static std::vector<std::string> get_buttons(std::string_view str);
static std::string get_buttons(std::vector<std::string> vec);
u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const;

View file

@ -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)

View file

@ -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 <map>
#include <set>
@ -469,38 +470,6 @@ struct VibrateMotor
{}
};
struct ps_move_data
{
u32 external_device_id = 0;
std::array<u8, 38> external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE
std::array<u8, 40> external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE
std::array<u8, 5> 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<f32, 4> default_quaternion { 1.0f, 0.0f, 0.0f, 0.0f };
std::array<f32, 4> 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;

View file

@ -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<f32> 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;
}

View file

@ -0,0 +1,75 @@
#pragma once
struct ps_move_data
{
template <int Size, typename T = f32>
struct vect
{
public:
constexpr vect() = default;
constexpr vect(const std::array<T, Size>& vec) : data(vec) {};
template <typename I>
T& operator[](I i) { return data[i]; }
template <typename I>
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<T, Size> data {};
};
ps_move_data();
u32 external_device_id = 0;
std::array<u8, 38> external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE
std::array<u8, 40> external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE
std::array<u8, 5> 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<f32> pos_world[4]);
};

View file

@ -74,7 +74,7 @@ namespace vm
std::array<atomic_t<cpu_thread*>, g_cfg.core.ppu_threads.max> g_locks{};
// Range lock slot allocation bits
atomic_t<u64, 64> g_range_lock_bits[2]{};
atomic_t<u64, 128> 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<u64, 64> g_range_lock_set[64]{};
atomic_t<u64, 128> g_range_lock_set[64]{};
// Memory pages
std::array<memory_page, 0x100000000 / 4096> g_pages;
@ -142,7 +142,7 @@ namespace vm
}
}
atomic_t<u64, 64>* alloc_range_lock()
atomic_t<u64, 128>* alloc_range_lock()
{
const auto [bits, ok] = get_range_lock_bits(false).fetch_op([](u64& bits)
{
@ -167,7 +167,7 @@ namespace vm
template <typename F>
static u64 for_all_range_locks(u64 input, F func);
void range_lock_internal(atomic_t<u64, 64>* range_lock, u32 begin, u32 size)
void range_lock_internal(atomic_t<u64, 128>* 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<u64, 64>* range_lock) noexcept
void free_range_lock(atomic_t<u64, 128>* 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<u64, 64>* _lock_main_range_lock(u64 flags, u32 addr, u32 size)
static atomic_t<u64, 128>* _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<u64, 64>* range_lock, u32 const size, u64 const flags) noexcept
writer_lock::writer_lock(u32 const addr, atomic_t<u64, 128>* range_lock, u32 const size, u64 const flags) noexcept
: range_lock(range_lock)
{
cpu_thread* cpu{};

View file

@ -28,7 +28,7 @@ namespace vm
range_bits = 3,
};
extern atomic_t<u64, 64> g_range_lock_bits[2];
extern atomic_t<u64, 128> g_range_lock_bits[2];
extern atomic_t<u64> g_shmem[];
@ -36,13 +36,13 @@ namespace vm
void passive_lock(cpu_thread& cpu);
// Register range lock for further use
atomic_t<u64, 64>* alloc_range_lock();
atomic_t<u64, 128>* alloc_range_lock();
void range_lock_internal(atomic_t<u64, 64>* range_lock, u32 begin, u32 size);
void range_lock_internal(atomic_t<u64, 128>* range_lock, u32 begin, u32 size);
// Lock memory range ignoring memory protection (Size!=0 also implies aligned begin)
template <uint Size = 0>
FORCE_INLINE void range_lock(atomic_t<u64, 64>* range_lock, u32 begin, u32 _size)
FORCE_INLINE void range_lock(atomic_t<u64, 128>* range_lock, u32 begin, u32 _size)
{
if constexpr (Size == 0)
{
@ -80,7 +80,7 @@ namespace vm
}
// Release it
void free_range_lock(atomic_t<u64, 64>*) noexcept;
void free_range_lock(atomic_t<u64, 128>*) noexcept;
// Unregister reader
void passive_unlock(cpu_thread& cpu);
@ -91,12 +91,12 @@ namespace vm
struct writer_lock final
{
atomic_t<u64, 64>* range_lock;
atomic_t<u64, 128>* range_lock;
writer_lock(const writer_lock&) = delete;
writer_lock& operator=(const writer_lock&) = delete;
writer_lock() noexcept;
writer_lock(u32 addr, atomic_t<u64, 64>* range_lock = nullptr, u32 size = 128, u64 flags = range_locked) noexcept;
writer_lock(u32 addr, atomic_t<u64, 128>* range_lock = nullptr, u32 size = 128, u64 flags = range_locked) noexcept;
~writer_lock() noexcept;
};
} // namespace vm

View file

@ -34,32 +34,33 @@ namespace vm
void reservation_update(u32 addr);
std::pair<bool, u64> 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_waiter_t>* reservation_notifier(u32 raddr, u64 rtime)
static inline atomic_t<reservation_waiter_t, 128>* 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<atomic_t<reservation_waiter_t>, wait_vars_for_each * (unique_address_bit_mask + 1) * (unique_rtime_bit_mask + 1)> g_resrv_waiters_count;
extern std::array<atomic_t<reservation_waiter_t, 128>, 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<usz>(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<reservation_waiter_t>& waiter)
static inline void reservation_notifier_end_wait(atomic_t<reservation_waiter_t, 128>& waiter)
{
waiter.atomic_op([](reservation_waiter_t& value)
{
@ -73,9 +74,9 @@ namespace vm
});
}
static inline std::pair<atomic_t<reservation_waiter_t>*, u32> reservation_notifier_begin_wait(u32 raddr, u64 rtime)
static inline std::pair<atomic_t<reservation_waiter_t, 128>*, u32> reservation_notifier_begin_wait(u32 raddr, u64 rtime)
{
atomic_t<reservation_waiter_t>& waiter = *reservation_notifier(raddr, rtime);
atomic_t<reservation_waiter_t, 128>& waiter = *reservation_notifier(raddr, rtime);
u32 wait_flag = 0;

File diff suppressed because it is too large Load diff

129
rpcs3/Emu/NP/clans_client.h Normal file
View file

@ -0,0 +1,129 @@
#pragma once
#include <curl/curl.h>
#include <pugixml.hpp>
#include <Emu/Cell/Modules/sceNpClans.h>
#include <Emu/NP/np_handler.h>
#include <Emu/IdManager.h>
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<SceNpClanId> 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<bool> is_initialized = false;
std::shared_ptr<clan::clans_client> client;
};

View file

@ -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<std::pair<std::string, std::string>> cfg_clans::get_hosts()
{
std::vector<std::pair<std::string, std::string>> 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<std::pair<std::string, std::string>>& 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;
}

View file

@ -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<std::pair<std::string, std::string>> 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<std::pair<std::string, std::string>>& vec_hosts);
};
extern cfg_clans g_cfg_clans;

View file

@ -206,7 +206,7 @@ table SetRoomDataInternalRequest {
flagAttr:uint32;
roomBinAttrInternal:[BinAttr];
passwordConfig:[RoomGroupPasswordConfig];
passwordSlotMask:uint64;
passwordSlotMask:[uint64];
ownerPrivilegeRank:[uint16];
}

View file

@ -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<RoomGroupPasswordConfig>> *passwordConfig() const {
return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *>(VT_PASSWORDCONFIG);
}
uint64_t passwordSlotMask() const {
return GetField<uint64_t>(VT_PASSWORDSLOTMASK, 0);
const ::flatbuffers::Vector<uint64_t> *passwordSlotMask() const {
return GetPointer<const ::flatbuffers::Vector<uint64_t> *>(VT_PASSWORDSLOTMASK);
}
const ::flatbuffers::Vector<uint16_t> *ownerPrivilegeRank() const {
return GetPointer<const ::flatbuffers::Vector<uint16_t> *>(VT_OWNERPRIVILEGERANK);
@ -2779,7 +2779,8 @@ struct SetRoomDataInternalRequest FLATBUFFERS_FINAL_CLASS : private ::flatbuffer
VerifyOffset(verifier, VT_PASSWORDCONFIG) &&
verifier.VerifyVector(passwordConfig()) &&
verifier.VerifyVectorOfTables(passwordConfig()) &&
VerifyField<uint64_t>(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<RoomGroupPasswordConfig>>> passwordConfig) {
fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDCONFIG, passwordConfig);
}
void add_passwordSlotMask(uint64_t passwordSlotMask) {
fbb_.AddElement<uint64_t>(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask, 0);
void add_passwordSlotMask(::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> passwordSlotMask) {
fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask);
}
void add_ownerPrivilegeRank(::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> ownerPrivilegeRank) {
fbb_.AddOffset(SetRoomDataInternalRequest::VT_OWNERPRIVILEGERANK, ownerPrivilegeRank);
@ -2829,12 +2830,12 @@ inline ::flatbuffers::Offset<SetRoomDataInternalRequest> CreateSetRoomDataIntern
uint32_t flagAttr = 0,
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<BinAttr>>> roomBinAttrInternal = 0,
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>>> passwordConfig = 0,
uint64_t passwordSlotMask = 0,
::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> passwordSlotMask = 0,
::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> 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<SetRoomDataInternalRequest> CreateSetRoomDataIntern
uint32_t flagAttr = 0,
const std::vector<::flatbuffers::Offset<BinAttr>> *roomBinAttrInternal = nullptr,
const std::vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *passwordConfig = nullptr,
uint64_t passwordSlotMask = 0,
const std::vector<uint64_t> *passwordSlotMask = nullptr,
const std::vector<uint16_t> *ownerPrivilegeRank = nullptr) {
auto roomBinAttrInternal__ = roomBinAttrInternal ? _fbb.CreateVector<::flatbuffers::Offset<BinAttr>>(*roomBinAttrInternal) : 0;
auto passwordConfig__ = passwordConfig ? _fbb.CreateVector<::flatbuffers::Offset<RoomGroupPasswordConfig>>(*passwordConfig) : 0;
auto passwordSlotMask__ = passwordSlotMask ? _fbb.CreateVector<uint64_t>(*passwordSlotMask) : 0;
auto ownerPrivilegeRank__ = ownerPrivilegeRank ? _fbb.CreateVector<uint16_t>(*ownerPrivilegeRank) : 0;
return CreateSetRoomDataInternalRequest(
_fbb,
@ -2861,7 +2863,7 @@ inline ::flatbuffers::Offset<SetRoomDataInternalRequest> CreateSetRoomDataIntern
flagAttr,
roomBinAttrInternal__,
passwordConfig__,
passwordSlotMask,
passwordSlotMask__,
ownerPrivilegeRank__);
}

View file

@ -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};
}

View file

@ -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], {"="});

View file

@ -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_data> 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<SceNpMatching2RequestOptParam> optParam, SceNpMatching2Event event_type)
u32 np_handler::generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> 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

View file

@ -3,6 +3,7 @@
#include <queue>
#include <map>
#include <unordered_map>
#include <condition_variable>
#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<SceNpTicketParam> param) const;
std::string get_service_id() const;
private:
std::optional<ticket_data> 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<SceNpMatching2RequestCallback> cb;
vm::ptr<void> 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<SceNpMatching2RequestOptParam> optParam, SceNpMatching2Event event_type);
u32 generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, SceNpMatching2Event event_type, bool abortable);
std::optional<callback_info> take_pending_request(u32 req_id);
private:
@ -415,6 +410,10 @@ namespace np
ticket current_ticket;
// Clan ticket
atomic_t<u32> clan_ticket_ready = 0;
ticket clan_ticket;
// IP & DNS info
std::string hostname = "localhost";
std::array<u8, 6> ether_address{};

View file

@ -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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<SceNpMatching2RequestOptParam> 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<s32>(current_ticket.size());
if (manager_cb)

View file

@ -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<u8 *>(data->value.data.ptr.get_ptr()), data->value.data.size));
sceNp.warning("data:\n%s", fmt::buf_to_hexstring(static_cast<u8*>(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

View file

@ -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

View file

@ -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<u32, std::pair<rpcn::CommandType, std::vector<u8>>> rpcn_client::get_replies()
std::map<u32, std::pair<rpcn::CommandType, std::vector<u8>>> 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<flatbuffers::Vector<u64>> final_passwordSlotMask;
if (req->passwordSlotMask)
final_passwordSlotMask = *req->passwordSlotMask;
{
const u64 value = *req->passwordSlotMask;
final_passwordSlotMask = builder.CreateVector(&value, 1);
}
flatbuffers::Offset<flatbuffers::Vector<u16>> final_ownerprivilege_vec;
if (req->ownerPrivilegeRankNum && req->ownerPrivilegeRank)

View file

@ -314,7 +314,7 @@ namespace rpcn
std::optional<std::pair<std::string, friend_online_data>> get_friend_presence_by_npid(const std::string& npid);
std::vector<std::pair<rpcn::NotificationType, std::vector<u8>>> get_notifications();
std::unordered_map<u32, std::pair<rpcn::CommandType, std::vector<u8>>> get_replies();
std::map<u32, std::pair<rpcn::CommandType, std::vector<u8>>> get_replies();
std::unordered_map<std::string, friend_online_data> get_presence_updates();
std::map<std::string, friend_online_data> get_presence_states();
@ -428,8 +428,8 @@ namespace rpcn
shared_mutex mutex_notifs, mutex_replies, mutex_replies_sync, mutex_presence_updates;
std::vector<std::pair<rpcn::NotificationType, std::vector<u8>>> notifications; // notif type / data
std::unordered_map<u32, std::pair<rpcn::CommandType, std::vector<u8>>> replies; // req id / (command / data)
std::unordered_map<u64, std::pair<rpcn::CommandType, std::vector<u8>>> replies_sync; // same but for sync replies(see handle_input())
std::map<u32, std::pair<rpcn::CommandType, std::vector<u8>>> replies; // req id / (command / data)
std::map<u64, std::pair<rpcn::CommandType, std::vector<u8>>> replies_sync; // same but for sync replies(see handle_input())
std::unordered_map<std::string, friend_online_data> presence_updates; // npid / presence data
// Messages

View file

@ -79,7 +79,8 @@ std::string cfg_rpcn::get_host() const
std::vector<std::pair<std::string, std::string>> cfg_rpcn::get_hosts()
{
std::vector<std::pair<std::string, std::string>> 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;
}

View file

@ -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();

View file

@ -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<Ty> auto const& values)
{
ensure(where >= _data);
const auto _loc = offset(where);
const auto in_size = static_cast<u32>(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<Ty>& that)
{
const auto old_size = _size;

View file

@ -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<u32>(c), static_cast<u32>(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<u32>(c), static_cast<u32>(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<u32>(c), static_cast<u32>(codepage_id), fallback_file);

View file

@ -34,6 +34,11 @@ namespace rsx::assembler
}
};
struct CFGPass
{
virtual void run(FlowGraph& graph) = 0;
};
FlowGraph deconstruct_fragment_program(const RSXFragmentProgram& prog);
}

View file

@ -0,0 +1,455 @@
#include "stdafx.h"
#include "FPASM.h"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <stack>
#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<std::string_view, FP_opcode_encoding_t> 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<u32>(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<f32, 4>& 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<u32>(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<f32, 4>{ 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<f32, 4>& 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<Instruction>& FPIR::instructions() const
{
return m_instructions;
}
std::vector<u32> FPIR::compile() const
{
std::vector<u32> result;
result.reserve(m_instructions.size() * 4);
for (const auto& inst : m_instructions)
{
const auto src = reinterpret_cast<const be_t<u16>*>(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<u16>(low) | (static_cast<u32>(hi) << 16u);
result.push_back(word);
}
}
return result;
}
FPIR FPIR::from_source(std::string_view asm_)
{
std::vector<std::string> 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<std::string>& 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<f32, 4>
{
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<u32>(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<u32>(end);
inst->bytecode[3] = src2.HEX;
SRC1 src1{ .HEX = inst->bytecode[2] };
if (!src1.else_offset)
{
src1.else_offset = static_cast<u32>(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<std::string> sources;
std::stack<size_t> if_ops;
std::stack<size_t> 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<FP_opcode>(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;
}
}

View file

@ -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<f32, 4>& constants);
void add(const RegisterRef& dst, const RegisterRef& src);
const std::vector<Instruction>& instructions() const;
std::vector<u32> 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<f32, 4>& constants, int operand, Instruction* target = nullptr);
Instruction* store(const RegisterRef& reg, Instruction* target = nullptr);
std::vector<Instruction> m_instructions;
};
}

View file

@ -0,0 +1,426 @@
#include "stdafx.h"
#include "FPOpcodes.h"
#include "Emu/RSX/Common/simple_array.hpp"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <unordered_set>
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<u32>& masks) -> u32
{
return operand < masks.size()
? masks[operand]
: 0u;
};
auto opcode = static_cast<FP_opcode>(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<u32> 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<u32> 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<u32> 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;
}
}

View file

@ -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<char, constants::register_file_max_len>;
// 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<u32> get_register_file_range(const RegisterRef& reg);
// Compile a register file annotated blob to register references
std::vector<RegisterRef> compile_register_file(const std::array<char, 48 * 8>& file);
}
}

View file

@ -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++;

View file

@ -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<Instruction> instructions; // Program instructions for the RSX processor
std::vector<FlowEdge> succ; // Forward edges. Sorted closest first.
std::vector<FlowEdge> pred; // Back edges. Sorted closest first.
@ -78,6 +99,9 @@ namespace rsx::assembler
std::vector<Instruction> prologue; // Prologue, created by passes
std::vector<Instruction> epilogue; // Epilogue, created by passes
std::vector<RegisterRef> input_list; // Register inputs.
std::vector<RegisterRef> 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();
}
};
}

View file

@ -0,0 +1,226 @@
#include "stdafx.h"
#include "RegisterAnnotationPass.h"
#include "Emu/RSX/Program/Assembler/FPOpcodes.h"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <span>
#include <unordered_map>
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<RegisterRef> compile_register_file(const register_file_t& file)
{
std::vector<RegisterRef> results;
// F16 register processing
for (int reg16 = 0; reg16 < 48; ++reg16)
{
const u32 offset = reg16 * 8;
auto word = *reinterpret_cast<const u64*>(&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<const char> 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<const u64*>(&file[offset]);
auto word1 = *reinterpret_cast<const u64*>(&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<FP_opcode>(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);
}
}
}

View file

@ -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;
};
}

View file

@ -0,0 +1,484 @@
#include "stdafx.h"
#include "RegisterDependencyPass.h"
#include "Emu/RSX/Program/Assembler/FPOpcodes.h"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <unordered_map>
#include <unordered_set>
namespace rsx::assembler::FP
{
using namespace constants;
struct DependencyPassContext
{
std::unordered_map<BasicBlock*, register_file_t> exec_register_map;
std::unordered_map<BasicBlock*, register_file_t> 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<RegisterRef> decode_lanes16(const std::unordered_set<u32>& lanes)
{
std::vector<RegisterRef> 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<int>(index), .f16 = true } };
ref.mask = mask;
result.push_back(std::move(ref));
}
return result;
}
std::vector<RegisterBarrier32> decode_lanes32(const std::unordered_set<u32>& lanes)
{
std::vector<RegisterBarrier32> 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<int>(index), .f16 = false };
result.push_back(std::move(barrier));
}
return result;
}
std::vector<Instruction> 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<Instruction> 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<int>(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<Instruction> 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<Instruction> 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<int>(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<Instruction> resolve_dependencies(const std::unordered_set<u32>& lanes, bool f16)
{
std::vector<Instruction> 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<u32> barrier16;
std::unordered_set<u32> 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<Instruction> 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<Instruction> 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<u32>& lanes, bool f16)
{
std::unordered_set<u32> clobbered_lanes;
std::unordered_set<u32> 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<u32> 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));
}
}
}

View file

@ -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;
};
}

View file

@ -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 = [&]()
{

View file

@ -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 <algorithm>
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<RegisterRef> 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<RegisterRef> 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<RegisterRef> get_fragment_program_output_set(u32 ctrl, u32 mrt_count)
{
std::vector<RegisterRef> 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<typename T> 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 &reg = 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<std::string, 4> output_register_names;
std::array<u32, 4> 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<const BasicBlock*, std::pair<int, u32>> 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<Instruction>& 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<int>(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;

View file

@ -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<u32, u32> m_constant_offsets;
std::array<rsx::MixedPrecisionRegister, 64> 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;

Some files were not shown because too many files have changed in this diff Show more