diff --git a/.ci/build-linux-aarch64.sh b/.ci/build-linux-aarch64.sh index 1fe640809c..26fc1dc9d9 100755 --- a/.ci/build-linux-aarch64.sh +++ b/.ci/build-linux-aarch64.sh @@ -35,7 +35,7 @@ cmake .. \ -DUSE_SYSTEM_CURL=ON \ -DUSE_SDL=ON \ -DUSE_SYSTEM_SDL=ON \ - -DUSE_SYSTEM_FFMPEG=OFF \ + -DUSE_SYSTEM_FFMPEG=ON \ -DUSE_SYSTEM_OPENCV=ON \ -DUSE_DISCORD_RPC=ON \ -DOpenGL_GL_PREFERENCE=LEGACY \ diff --git a/.ci/build-linux.sh b/.ci/build-linux.sh index 13a9e802f3..d9f66f6a04 100755 --- a/.ci/build-linux.sh +++ b/.ci/build-linux.sh @@ -46,7 +46,7 @@ cmake .. \ -DUSE_SYSTEM_CURL=ON \ -DUSE_SDL=ON \ -DUSE_SYSTEM_SDL=ON \ - -DUSE_SYSTEM_FFMPEG=OFF \ + -DUSE_SYSTEM_FFMPEG=ON \ -DUSE_SYSTEM_OPENCV=ON \ -DUSE_DISCORD_RPC=ON \ -DOpenGL_GL_PREFERENCE=LEGACY \ diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index 9e16fce4ec..ae0341cc0e 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -13,31 +13,18 @@ export HOMEBREW_NO_AUTO_UPDATE=1 export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 -brew update brew install -f --overwrite --quiet ccache "llvm@$LLVM_COMPILER_VER" brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" -if [ "$AARCH64" -eq 1 ]; then - brew install -f --overwrite --quiet googletest opencv@4 sdl3 vulkan-headers vulkan-loader molten-vk - brew unlink --quiet ffmpeg fmt qtbase qtsvg qtdeclarative protobuf || true -else - 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 python@3.14 opencv@4 "llvm@$LLVM_COMPILER_VER" sdl3 vulkan-headers vulkan-loader molten-vk - arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative protobuf || true -fi +brew install -f --overwrite --quiet googletest opencv@4 sdl3 vulkan-headers vulkan-loader molten-vk +brew unlink --quiet ffmpeg fmt qtbase qtsvg qtdeclarative protobuf || true export CXX=clang++ export CC=clang export BREW_PATH; -if [ "$AARCH64" -eq 1 ]; then - BREW_PATH="$(brew --prefix)" - export BREW_BIN="/opt/homebrew/bin" - export BREW_SBIN="/opt/homebrew/sbin" -else - BREW_PATH="$("/usr/local/bin/brew" --prefix)" - export BREW_BIN="/usr/local/bin" - export BREW_SBIN="/usr/local/sbin" -fi +BREW_PATH="$(brew --prefix)" +export BREW_BIN="$BREW_PATH/bin" +export BREW_SBIN="$BREW_PATH/sbin" export WORKDIR; WORKDIR="$(pwd)" @@ -68,7 +55,7 @@ ditto "/tmp/Qt/$QT_VER" "qt-downloader/$QT_VER" export Qt6_DIR="$WORKDIR/qt-downloader/$QT_VER/clang_64/lib/cmake/Qt$QT_VER_MAIN" export SDL3_DIR="$BREW_PATH/opt/sdl3/lib/cmake/SDL3" -export PATH="/opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/bin:$PATH" +export PATH="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/bin:$PATH" export LDFLAGS="-L$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++ -L$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/unwind -lunwind" export VULKAN_SDK @@ -82,12 +69,13 @@ LLVM_DIR="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER" git submodule -q update --init --depth=1 --jobs=8 $(awk '/path/ && !/llvm/ && !/opencv/ && !/SDL/ && !/feralinteractive/ { print $3 }' .gitmodules) mkdir build && cd build || exit 1 +# The below should be uncommented once bugs with Qt 6 QListWidgets when using the OS 26 visual style are resolved. +# sudo xcode-select -switch /Applications/Xcode_26.3.app/Contents/Developer -if [ "$AARCH64" -eq 1 ]; then cmake .. \ -DBUILD_RPCS3_TESTS="${RUN_UNIT_TESTS}" \ -DRUN_RPCS3_TESTS="${RUN_UNIT_TESTS}" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=14.4 \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 \ -DCMAKE_OSX_SYSROOT="$(xcrun --sdk macosx --show-sdk-path)" \ -DMACOSX_BUNDLE_SHORT_VERSION_STRING="${COMM_TAG}" \ -DMACOSX_BUNDLE_BUNDLE_VERSION="${COMM_COUNT}" \ @@ -102,31 +90,8 @@ cmake .. \ -DUSE_SYSTEM_SDL=ON \ -DUSE_SYSTEM_OPENCV=ON \ -G Ninja -else -cmake .. \ - -DBUILD_RPCS3_TESTS=OFF \ - -DRUN_RPCS3_TESTS=OFF \ - -DCMAKE_OSX_ARCHITECTURES=x86_64 \ - -DCMAKE_SYSTEM_PROCESSOR=x86_64 \ - -DCMAKE_TOOLCHAIN_FILE=buildfiles/cmake/TCDarwinX86_64.cmake \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=14.4 \ - -DCMAKE_OSX_SYSROOT="$(xcrun --sdk macosx --show-sdk-path)" \ - -DMACOSX_BUNDLE_SHORT_VERSION_STRING="${COMM_TAG}" \ - -DMACOSX_BUNDLE_BUNDLE_VERSION="${COMM_COUNT}"\ - -DSTATIC_LINK_LLVM=ON \ - -DUSE_SDL=ON \ - -DUSE_DISCORD_RPC=ON \ - -DUSE_AUDIOUNIT=ON \ - -DUSE_SYSTEM_FFMPEG=OFF \ - -DUSE_NATIVE_INSTRUCTIONS=OFF \ - -DUSE_PRECOMPILED_HEADERS=OFF \ - -DUSE_SYSTEM_MVK=ON \ - -DUSE_SYSTEM_SDL=ON \ - -DUSE_SYSTEM_OPENCV=ON \ - -G Ninja -fi -ninja; build_status=$?; +ninja -j4; build_status=$?; cd .. diff --git a/.ci/deploy-linux.sh b/.ci/deploy-linux.sh index b0599400d2..3309eb89e0 100755 --- a/.ci/deploy-linux.sh +++ b/.ci/deploy-linux.sh @@ -19,6 +19,9 @@ if [ "$DEPLOY_APPIMAGE" = "true" ]; then APPIMAGE_EXTRACT_AND_RUN=1 linuxdeploy --appdir AppDir --plugin qt --plugin checkrt + # Restore App Icon + ln -sr ./AppDir/rpcs3.svg ./AppDir/.DirIcon + # Remove libwayland-client because it has platform-dependent exports and breaks other OSes rm -f ./AppDir/usr/lib/libwayland-client.so* diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 0b8c1994e0..045b93ac9f 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -4,15 +4,15 @@ cd build || exit 1 cd bin -git clone --revision=32dceb35e2c95b46cec501033cbc3a1ddf32d6e8 https://github.com/KhronosGroup/MoltenVK.git +git clone --revision=a075e5e417f87675ea3137b7365f3e5a99608d72 https://github.com/KhronosGroup/MoltenVK.git cd MoltenVK ./fetchDependencies --macos make macos MVK_USE_METAL_PRIVATE_API=1 cd ../ mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true -cp "MoltenVK/Package/Latest/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" -cp "MoltenVK/Package/Latest/MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +cp "MoltenVK/Package/Release/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" +cp "MoltenVK/Package/Release/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 $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" @@ -56,9 +56,25 @@ rm -f rpcs3.app/Contents/translations/qt_help_*.qm || true mv rpcs3.app RPCS3_.app mv RPCS3_.app RPCS3.app -# Hack -install_name_tool -delete_rpath /opt/homebrew/lib RPCS3.app/Contents/MacOS/rpcs3 || true -install_name_tool -delete_rpath /usr/local/lib RPCS3.app/Contents/MacOS/rpcs3 || true +# Hack to fix rpath issues +BIN="RPCS3.app/Contents/MacOS/rpcs3" +install_name_tool -delete_rpath /opt/homebrew/lib $BIN || true +install_name_tool -delete_rpath /usr/local/lib $BIN || true + +# Fix dylib IDs +for lib in RPCS3.app/Contents/Frameworks/*.dylib; do + name=$(basename "$lib") + install_name_tool -id "@rpath/$name" "$lib" +done + +# Rewrite any hardcoded Homebrew paths to use @rpath +find "RPCS3.app/Contents/" -type f \( -perm +111 -o -name "*.dylib" \) | while read -r bin; do + otool -L "$bin" | grep -E "/opt/homebrew|/usr/local" | awk '{print $1}' | while read -r dep; do + base=$(basename "$dep") + echo "Fixing $dep -> @rpath/$base in $bin" + install_name_tool -change "$dep" "@rpath/$base" "$bin" + done +done # NOTE: "--deep" is deprecated codesign --deep -fs - RPCS3.app @@ -67,7 +83,7 @@ echo "[InternetShortcut]" > Quickstart.url echo "URL=https://rpcs3.net/quickstart" >> Quickstart.url echo "IconIndex=0" >> Quickstart.url -if [ "$AARCH64" -eq 1 ]; then +if [ "$(arch)" = "arm64" ]; then ARCHIVE_FILEPATH="$BUILD_ARTIFACTSTAGINGDIRECTORY/rpcs3-v${LVER}_macos_aarch64.7z" else ARCHIVE_FILEPATH="$BUILD_ARTIFACTSTAGINGDIRECTORY/rpcs3-v${LVER}_macos.7z" diff --git a/.ci/setup-windows.sh b/.ci/setup-windows.sh index d8016d8c13..9008860e75 100755 --- a/.ci/setup-windows.sh +++ b/.ci/setup-windows.sh @@ -16,7 +16,7 @@ QT_MM_URL="${QT_HOST}${QT_PREFIX}addons.qtmultimedia.${QT_PREFIX_2}qtmultimedia$ QT_SVG_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qtsvg${QT_SUFFIX}" QT_TRANSLATIONS_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qttranslations${QT_SUFFIX}" LLVMLIBS_URL="https://github.com/RPCS3/llvm-mirror/releases/download/custom-build-win-${LLVM_VER}/llvmlibs_mt.7z" -VULKAN_SDK_URL="https://www.dropbox.com/scl/fi/sjjh0fc4ld281pjbl2xzu/VulkanSDK-${VULKAN_VER}-Installer.exe?rlkey=f6wzc0lvms5vwkt2z3qabfv9d&dl=1" +VULKAN_SDK_URL="https://www.dropbox.com/scl/fi/olpvqk4346ig7i8btadk5/vulkansdk-windows-X64-${VULKAN_VER}.exe?rlkey=huvssch6sy904qkg8ti9p65br&st=pztptdin&dl=1" CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.12.3/ccache-4.12.3-windows-x86_64.zip" DEP_URLS=" \ @@ -88,7 +88,7 @@ for url in $DEP_URLS; do *qt*) checksum=$(curl -fL "${url}.sha1"); algo="sha1"; outDir="$QTDIR/" ;; *llvm*) checksum=$(curl -fL "${url}.sha256"); algo="sha256"; outDir="./build/lib_ext/Release-x64" ;; *ccache*) checksum=$CCACHE_SHA; algo="sha256"; outDir="$CCACHE_BIN_DIR" ;; - *Vulkan*) + *vulkansdk*) # Vulkan setup needs to be run in batch environment # Need to subshell this or else it doesn't wait download_and_verify "$url" "$VULKAN_SDK_SHA" "sha256" "$fileName" diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 3ed584437a..3ac6b37c46 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -17,7 +17,7 @@ jobs: Windows_Build: if: github.event_name == 'workflow_dispatch' name: LLVM Windows (MSVC) - runs-on: windows-2025 + runs-on: windows-2025-vs2026 env: COMPILER: msvc CCACHE_SHA: '859141059ac950e1e8cd042c66f842f26b9e3a62a1669a69fe6ba180cb58bbdf' diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index eb42b7aae1..515555a224 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -30,23 +30,23 @@ jobs: matrix: include: - os: ubuntu-24.04 - docker_img: "rpcs3/rpcs3-ci-jammy:1.11" + docker_img: "rpcs3/rpcs3-ci-jammy:1.14" build_sh: "/rpcs3/.ci/build-linux.sh" compiler: clang UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux" - os: ubuntu-24.04 - docker_img: "rpcs3/rpcs3-ci-jammy:1.11" + docker_img: "rpcs3/rpcs3-ci-jammy:1.14" build_sh: "/rpcs3/.ci/build-linux.sh" compiler: gcc - os: ubuntu-24.04-arm - docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.11" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.14" build_sh: "/rpcs3/.ci/build-linux-aarch64.sh" compiler: clang UPLOAD_COMMIT_HASH: a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1 UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux-arm64" - os: ubuntu-24.04-arm - docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.11" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.14" build_sh: "/rpcs3/.ci/build-linux-aarch64.sh" compiler: gcc name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} @@ -123,25 +123,24 @@ jobs: matrix: include: - name: Intel - AARCH64: 0 + runs-on: macos-15-intel UPLOAD_COMMIT_HASH: 51ae32f468089a8169aaf1567de355ff4a3e0842 UPLOAD_REPO_FULL_NAME: rpcs3/rpcs3-binaries-mac - name: Apple Silicon - AARCH64: 1 + runs-on: macos-15 UPLOAD_COMMIT_HASH: 8e21bdbc40711a3fccd18fbf17b742348b0f4281 UPLOAD_REPO_FULL_NAME: rpcs3/rpcs3-binaries-mac-arm64 name: RPCS3 Mac ${{ matrix.name }} - runs-on: macos-14 + runs-on: ${{ matrix.runs-on }} env: CCACHE_DIR: /tmp/ccache_dir - QT_VER: '6.11.0' + QT_VER: '6.11.1' QT_VER_MAIN: '6' LLVM_COMPILER_VER: '21' RELEASE_MESSAGE: ../GitHubReleaseMessage.txt UPLOAD_COMMIT_HASH: ${{ matrix.UPLOAD_COMMIT_HASH }} UPLOAD_REPO_FULL_NAME: ${{ matrix.UPLOAD_REPO_FULL_NAME }} - AARCH64: ${{ matrix.AARCH64 }} - RUN_UNIT_TESTS: github.event_name == 'pull_request' && 'ON' || 'OFF' + RUN_UNIT_TESTS: ${{ matrix.name == 'Apple Silicon' && (github.event_name == 'pull_request' && 'ON') || 'OFF' }} steps: - name: Checkout repository uses: actions/checkout@main @@ -209,16 +208,16 @@ jobs: # Only run push event on master branch of main repo, but run all PRs if: github.event_name != 'push' || (github.repository == 'RPCS3/rpcs3' && github.ref_name == 'master') name: RPCS3 Windows - runs-on: windows-2025 + runs-on: windows-2025-vs2026 env: COMPILER: msvc QT_VER_MAIN: '6' - QT_VER: '6.11.0' + QT_VER: '6.11.1' QT_VER_MSVC: 'msvc2022' - QT_DATE: '202603180535' + QT_DATE: '202605090529' LLVM_VER: '19.1.7' - VULKAN_VER: '1.3.268.0' - VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5' + VULKAN_VER: '1.4.341.1' + VULKAN_SDK_SHA: 'bcf2d75aa9556889ab974858666e20b3655b6055a0db704ccb47279ff33b5bfe' CCACHE_SHA: '859141059ac950e1e8cd042c66f842f26b9e3a62a1669a69fe6ba180cb58bbdf' CCACHE_BIN_DIR: 'C:\ccache_bin' CCACHE_DIR: 'C:\ccache' @@ -235,7 +234,7 @@ jobs: fetch-depth: 0 - name: Setup NuGet - uses: nuget/setup-nuget@v2 + uses: nuget/setup-nuget@v4 - name: Restore NuGet packages run: nuget restore rpcs3.sln diff --git a/3rdparty/7zip/7zip b/3rdparty/7zip/7zip index 839151eaaa..f9d78aff31 160000 --- a/3rdparty/7zip/7zip +++ b/3rdparty/7zip/7zip @@ -1 +1 @@ -Subproject commit 839151eaaad24771892afaae6bac690e31e58384 +Subproject commit f9d78aff31a5f2521ae7ddbdc97c4a8855808959 diff --git a/3rdparty/FAudio b/3rdparty/FAudio index 0372329dbb..75c79d4c8a 160000 --- a/3rdparty/FAudio +++ b/3rdparty/FAudio @@ -1 +1 @@ -Subproject commit 0372329dbb56e7814d0dea7b6eafa7a613bd8042 +Subproject commit 75c79d4c8ab59dfc4313e40c1997e03f7d32229f diff --git a/3rdparty/OpenAL/openal-soft b/3rdparty/OpenAL/openal-soft index c41d64c6a3..b2c48f7718 160000 --- a/3rdparty/OpenAL/openal-soft +++ b/3rdparty/OpenAL/openal-soft @@ -1 +1 @@ -Subproject commit c41d64c6a35f6174bf4a27010aeac52a8d3bb2c6 +Subproject commit b2c48f7718ef3fcf67921a8b6534c4914e328970 diff --git a/3rdparty/curl/curl b/3rdparty/curl/curl index 8c908d2d0a..a05f34973e 160000 --- a/3rdparty/curl/curl +++ b/3rdparty/curl/curl @@ -1 +1 @@ -Subproject commit 8c908d2d0a6d32abdedda2c52e90bd56ec76c24d +Subproject commit a05f34973e6c4bb629d018f7cb51487be1c904d8 diff --git a/3rdparty/curl/libcurl.vcxproj b/3rdparty/curl/libcurl.vcxproj index 4db28782a3..9acb78a8b7 100644 --- a/3rdparty/curl/libcurl.vcxproj +++ b/3rdparty/curl/libcurl.vcxproj @@ -66,6 +66,7 @@ + @@ -107,7 +108,6 @@ - @@ -118,6 +118,7 @@ + @@ -172,6 +173,7 @@ + @@ -195,6 +197,8 @@ + + @@ -255,6 +259,7 @@ + @@ -275,7 +280,6 @@ - @@ -309,7 +313,6 @@ - @@ -322,6 +325,7 @@ + @@ -332,6 +336,7 @@ + @@ -368,6 +373,7 @@ + @@ -395,6 +401,8 @@ + + diff --git a/3rdparty/curl/libcurl.vcxproj.filters b/3rdparty/curl/libcurl.vcxproj.filters index d38316e767..482b2f1452 100644 --- a/3rdparty/curl/libcurl.vcxproj.filters +++ b/3rdparty/curl/libcurl.vcxproj.filters @@ -66,9 +66,6 @@ Source Files - - Source Files - Source Files @@ -552,6 +549,21 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -668,9 +680,6 @@ Header Files - - Header Files - Header Files @@ -1040,9 +1049,6 @@ Header Files - - Header Files - Header Files @@ -1121,6 +1127,24 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + diff --git a/3rdparty/ffmpeg b/3rdparty/ffmpeg index ce81114ed9..649d9d9353 160000 --- a/3rdparty/ffmpeg +++ b/3rdparty/ffmpeg @@ -1 +1 @@ -Subproject commit ce81114ed99e5510f6cd983f59a1eac9f33bb73c +Subproject commit 649d9d9353184a17ae78d9df4a6fbbe7456f0428 diff --git a/3rdparty/glslang/glslang b/3rdparty/glslang/glslang index fc9889c889..f0bd0257c3 160000 --- a/3rdparty/glslang/glslang +++ b/3rdparty/glslang/glslang @@ -1 +1 @@ -Subproject commit fc9889c889561c5882e83819dcaffef5ed45529b +Subproject commit f0bd0257c308b9a26562c1a30c4748a0219cc951 diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL index 5848e584a1..8e37db5e79 160000 --- a/3rdparty/libsdl-org/SDL +++ b/3rdparty/libsdl-org/SDL @@ -1 +1 @@ -Subproject commit 5848e584a1b606de26e3dbd1c7e4ecbc34f807a6 +Subproject commit 8e37db5e797b6167f3a00d697d816a684bd259c7 diff --git a/3rdparty/libusb/libusb b/3rdparty/libusb/libusb index 15a7ebb4d4..87a55632db 160000 --- a/3rdparty/libusb/libusb +++ b/3rdparty/libusb/libusb @@ -1 +1 @@ -Subproject commit 15a7ebb4d426c5ce196684347d2b7cafad862626 +Subproject commit 87a55632db62c9bdc58cd31d3ccfa673f1bb017f diff --git a/3rdparty/libusb/libusb_static.vcxproj b/3rdparty/libusb/libusb_static.vcxproj index 66dfc2ac29..79e0cfb4b2 100644 --- a/3rdparty/libusb/libusb_static.vcxproj +++ b/3rdparty/libusb/libusb_static.vcxproj @@ -54,6 +54,9 @@ NDEBUG;%(PreprocessorDefinitions) + + LIBUSB_WINDOWS_HOTPLUG;%(PreprocessorDefinitions) + $(OutDir)libusb-1.0.lib @@ -64,6 +67,7 @@ + @@ -72,6 +76,7 @@ + diff --git a/3rdparty/opencv/opencv b/3rdparty/opencv/opencv index 67f53c26a7..6900a1265b 160000 --- a/3rdparty/opencv/opencv +++ b/3rdparty/opencv/opencv @@ -1 +1 @@ -Subproject commit 67f53c26a701c2aeefd8033ec2f2079e04c438ca +Subproject commit 6900a1265b8e1b0a19d7955b5bd6599a4a8b8576 diff --git a/3rdparty/pine/pine_server.h b/3rdparty/pine/pine_server.h index fe88ddb474..5c59e87da3 100644 --- a/3rdparty/pine/pine_server.h +++ b/3rdparty/pine/pine_server.h @@ -164,14 +164,17 @@ namespace pine return IPCBuffer{ 5, MakeFailIPC(ret_buffer) }; }; - const auto write_string = [&](const std::string& str) + const auto write_string = [&](std::string_view str) { if (!SafetyChecks(buf_cnt, 0, ret_cnt, str.size() + 1 + sizeof(u32), buf_size)) return false; ToArray(ret_buffer, ::narrow(str.size() + 1), ret_cnt); ret_cnt += sizeof(u32); - memcpy(&ret_buffer[ret_cnt], str.data(), str.size()); - ret_cnt += str.size(); + if (str.size()) + { + std::memcpy(&ret_buffer[ret_cnt], str.data(), str.size()); + ret_cnt += str.size(); + } ret_buffer[ret_cnt++] = '\0'; return true; }; @@ -512,7 +515,7 @@ namespace pine */ static inline bool SafetyChecks(usz command_len, usz command_size, usz reply_len, usz reply_size = 0, usz buf_size = MAX_IPC_SIZE - 1) { - bool res = ((command_len + command_size) > buf_size || + const bool res = ((command_len + command_size) > buf_size || (reply_len + reply_size) >= MAX_IPC_RETURN_SIZE); if (res) [[unlikely]] return false; @@ -524,8 +527,8 @@ namespace pine pine_server() noexcept { #ifdef _WIN32 - WSADATA wsa; - struct sockaddr_in server; + WSADATA wsa {}; + struct sockaddr_in server {}; m_sock = INVALID_SOCKET; m_msgsock = INVALID_SOCKET; @@ -579,7 +582,7 @@ namespace pine fmt::append(m_socket_name, ".%d", slot); } - struct sockaddr_un server; + struct sockaddr_un server {}; m_sock = socket(AF_UNIX, SOCK_STREAM, 0); if (m_sock < 0) diff --git a/3rdparty/pugixml b/3rdparty/pugixml index ee86beb30e..c8033ce9d0 160000 --- a/3rdparty/pugixml +++ b/3rdparty/pugixml @@ -1 +1 @@ -Subproject commit ee86beb30e4973f5feffe3ce63bfa4fbadf72f38 +Subproject commit c8033ce9d039e7f9d134877c363397b3cfe20816 diff --git a/3rdparty/wolfssl/wolfssl b/3rdparty/wolfssl/wolfssl index 1d363f3adc..ac01707f55 160000 --- a/3rdparty/wolfssl/wolfssl +++ b/3rdparty/wolfssl/wolfssl @@ -1 +1 @@ -Subproject commit 1d363f3adceba9d1478230ede476a37b0dcdef24 +Subproject commit ac01707f552c611fbd135cc723b2682b3e7f80f2 diff --git a/3rdparty/wolfssl/wolfssl.vcxproj b/3rdparty/wolfssl/wolfssl.vcxproj index 90da8b7f38..66f8a5ef1f 100644 --- a/3rdparty/wolfssl/wolfssl.vcxproj +++ b/3rdparty/wolfssl/wolfssl.vcxproj @@ -71,15 +71,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -139,6 +164,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -166,8 +262,6 @@ - - @@ -180,7 +274,6 @@ - @@ -194,6 +287,7 @@ + @@ -212,7 +306,6 @@ - @@ -221,12 +314,16 @@ + + + + - + false false @@ -247,6 +344,7 @@ true true + diff --git a/BUILDING.md b/BUILDING.md index c1774908fd..fb8a8144cf 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -20,27 +20,27 @@ The following tools are required to build RPCS3 on Windows 10 or later: with standalone **CMake** tool. - [Python 3.6+](https://www.python.org/downloads/) (add to PATH) -- [Qt 6.11.0](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt) -- [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0. +- [Qt 6.11.1](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt) +- [Vulkan SDK 1.4.341.1](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)). Note that future SDKs may not work. The `sln` solution available only on **Visual Studio** is the preferred building solution. It easily allows to build the **RPCS3** application in `Release` and `Debug` mode. In order to build **RPCS3** with the `sln` solution (with **Visual Studio**), **Qt** libs need to be detected. To detect the libs: -- add and set the `QTDIR` environment variable, e.g. `\6.11.0\msvc2022_64\` +- add and set the `QTDIR` environment variable, e.g. `\6.11.1\msvc2022_64\` - or use the [Visual Studio Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022) **NOTE:** If you have issues with the **Visual Studio Qt Plugin**, you may want to uninstall it and install the [Legacy Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.LEGACYQtVisualStudioTools2022) instead. In order to build **RPCS3** with the `CMake` solution (with both **Visual Studio** and standalone **CMake** tool): -- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `\6.11.0\msvc2022_64\` +- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `\6.11.1\msvc2022_64\` ### Linux These are the essentials tools to build RPCS3 on Linux. Some of them can be installed through your favorite package manager: - Clang 17+ or GCC 13+ - [CMake 3.28.0+](https://www.cmake.org/download/) -- [Qt 6.11.0](https://www.qt.io/download-qt-installer) -- [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0. +- [Qt 6.11.1](https://www.qt.io/download-qt-installer) +- [Vulkan SDK 1.4.341.1](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)). Note that future SDKs may not work. - [SDL3](https://github.com/libsdl-org/SDL/releases) (for the FAudio backend) **If you have an NVIDIA GPU, you may need to install the libglvnd package.** @@ -123,7 +123,7 @@ Start **Visual Studio**, click on `Open a project or solution` and select the `r ##### Configuring the Qt Plugin (if used) 1) go to `Extensions->Qt VS Tools->Qt Versions` -2) add the path to your Qt installation with compiler e.g. `\6.11.0\msvc2022_64`, version will fill in automatically +2) add the path to your Qt installation with compiler e.g. `\6.11.1\msvc2022_64`, version will fill in automatically 3) go to `Extensions->Qt VS Tools->Options->Legacy Project Format`. (Only available in the **Legacy Qt Plugin**) 4) set `Build: Run pre-build setup` to `true`. (Only available in the **Legacy Qt Plugin**) diff --git a/CMakeLists.txt b/CMakeLists.txt index 217c40f341..cbb2f66fc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,8 +94,8 @@ if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) endif() if(APPLE AND CMAKE_OSX_ARCHITECTURES STREQUAL "arm64") - include_directories(/opt/homebrew/include) - link_directories(/opt/homebrew/lib) + list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew") + list(APPEND CMAKE_SYSTEM_PREFIX_PATH "/opt/homebrew") endif() if(MSVC) diff --git a/README.md b/README.md index ac6e3cd594..351688be64 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The world's first free and open-source PlayStation 3 emulator/debugger, written You can find some basic information on our [**website**](https://rpcs3.net/). Game info is being populated on the [**Wiki**](https://wiki.rpcs3.net/). For discussion about this emulator, PS3 emulation, and game compatibility reports, please visit our [**forums**](https://forums.rpcs3.net) and our [**Discord server**](https://discord.gg/RPCS3). -[**Support Lead Developers Nekotekina and kd-11 on Patreon**](https://www.patreon.com/Nekotekina) +[**Support the Lead Developers on Patreon**](https://rpcs3.net/patreon) ## Contributing @@ -23,6 +23,16 @@ If you want to contribute as a developer, please take a look at the following pa You should also contact any of the developers in the forums or in the Discord server to learn more about the current state of the emulator. +### AI Use + +Use of AI tools for research and reverse engineering purposes is permitted. However, contributors are expected to fully own and understand all code they submit. Any communication with the team — including code, code comments, and GitHub comments — must come from the human contributor, not an AI agent acting autonomously. + +We have unfortunately seen a rise in untested and unverified AI-generated slop being submitted to this project. This wastes maintainer time and, in worse cases, such changes get merged and break functionality for all users. Repeated violations will result in a ban from the repository. Please be respectful of everyone's time. + +**Pull requests opened by AI agents or automated tools must include a disclosure in the PR description** stating the scope of AI involvement — which parts were AI-generated and what human testing or review was performed prior to submission. PRs that omit this disclosure may be closed without review. + +If you are unsure about your work, open a discussion issue to talk it through with the team, or reach out to a maintainer on [Discord](https://discord.gg/RPCS3). + ## Building See [BUILDING.md](BUILDING.md) for more information about how to setup an environment to build RPCS3. diff --git a/Utilities/Config.cpp b/Utilities/Config.cpp index f242bd6172..6b2e3bf841 100644 --- a/Utilities/Config.cpp +++ b/Utilities/Config.cpp @@ -253,10 +253,6 @@ bool try_to_float(f64* out, std::string_view value, f64 min, f64 max, std::strin bool try_to_string(std::string* out, f64 value, std::string_view name) { -#ifdef __APPLE__ - if (out) *out = std::to_string(value); - return true; -#else std::array str{}; if (auto [ptr, ec] = std::to_chars(str.data(), str.data() + str.size(), value, std::chars_format::fixed); ec == std::errc()) @@ -269,11 +265,12 @@ bool try_to_string(std::string* out, f64 value, std::string_view name) if (out) cfg_log.error("cfg::try_to_string('%s'): could not convert value '%f' to string. error='%s'", name, value, std::make_error_code(ec).message()); return false; } -#endif } bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string::format) func, std::string_view value, std::string_view name) { + ensure(func); + u64 max = umax; for (u64 i = 0;; i++) @@ -302,7 +299,7 @@ bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string::format) f const char* end = start + value.size(); int base = 10; - if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X')) + if (value.size() >= 2 && start[0] == '0' && (start[1] == 'x' || start[1] == 'X')) { // Limited hex support base = 16; @@ -760,7 +757,7 @@ void cfg::log_entry::from_default() std::pair cfg::device_info::get_usb_ids() const { - auto string_to_hex = [](const std::string& str) -> u16 + auto string_to_hex = [](std::string_view str) -> u16 { u16 value = 0x0000; if (!str.empty() && std::from_chars(str.data(), str.data() + str.size(), value, 16).ec != std::errc{}) diff --git a/Utilities/Config.h b/Utilities/Config.h index 4f8e578fde..d17f970dc5 100644 --- a/Utilities/Config.h +++ b/Utilities/Config.h @@ -717,6 +717,11 @@ namespace cfg m_value = std::string(value); return true; } + + void set(std::string_view value) + { + m_value = std::string(value); + } }; // Simple set entry (TODO: template for various types) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index aff4537dea..20e25794d2 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -38,6 +38,17 @@ static std::unique_ptr to_wchar(std::string_view source) // Buffer for max possible output length std::unique_ptr buffer(new wchar_t[buf_size + 8 + 32768]); + // If path points to an optical raw device, copy it AS IS + if (fs::is_optical_raw_device(std::string(source))) + { + ensure(MultiByteToWideChar(CP_UTF8, 0, source.data(), size, buffer.get() + 32768, size)); // "to_wchar" + + // Canonicalize wide path (replace '/', ".", "..", \\ repetitions, etc) + ensure(GetFullPathNameW(buffer.get() + 32768, 32768, buffer.get(), nullptr) - 1 < 32768 - 1); // "to_wchar" + + return buffer; + } + // Prepend wide path prefix (4 characters) std::memcpy(buffer.get() + 32768, L"\\\\\?\\", 4 * sizeof(wchar_t)); @@ -400,11 +411,12 @@ namespace fs class windows_file final : public file_base { HANDLE m_handle; + bool m_raw_device; atomic_t m_pos {0}; public: - windows_file(HANDLE handle) - : m_handle(handle) + windows_file(HANDLE handle, bool raw_device = false) + : m_handle(handle), m_raw_device(raw_device) { } @@ -564,11 +576,20 @@ namespace fs u64 size() override { - // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). - LARGE_INTEGER size; - ensure(GetFileSizeEx(m_handle, &size)); // "file::size" + if (!m_raw_device) + { + // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). + LARGE_INTEGER size; - return size.QuadPart; + ensure(GetFileSizeEx(m_handle, &size)); // "file::size" + return size.QuadPart; + } + + // For a raw device, we need to use DeviceIoControl. + DISK_GEOMETRY_EX geometry; + + ensure(DeviceIoControl(m_handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr)); + return geometry.DiskSize.QuadPart; } native_handle get_handle() override @@ -1091,6 +1112,55 @@ bool fs::is_symlink(const std::string& path) return true; } +bool fs::is_optical_raw_device([[maybe_unused]] const std::string& path) +{ +#ifdef _WIN32 + if (path.starts_with("\\\\.\\")) + { + return true; + } +#endif + return false; +} + +bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device) +{ + if (fs::is_optical_raw_device(path)) + { + if (raw_device) + { + *raw_device = path; + } + + return true; + } + +#ifdef _WIN32 + // Skip a useless check to detect an optical raw device if navigating on subfolders (e.g. C:\subfolder_1\subfolder_2\), + // it means we are on a HDD/SSD. A path for an optical drive should include only the drive letter (e.g. E:\) + const size_t drive_delim_pos = path.find_first_of(":"); + + if (drive_delim_pos != 1 || drive_delim_pos != path.find_last_not_of(delim)) + { + return false; + } + + const std::string drive_letter = path.substr(0, drive_delim_pos + 1); // e.g. "E:" + const std::string drive_path = drive_letter + "\\"; // e.g. "E:\" + + if (GetDriveTypeA(drive_path.c_str()) == DRIVE_CDROM) + { + if (raw_device) + { + *raw_device = "\\\\.\\" + drive_letter; + } + + return true; + } +#endif + return false; +} + bool fs::statfs(const std::string& path, fs::device_stat& info) { if (auto device = get_virtual_device(path)) @@ -1658,9 +1728,29 @@ fs::file::file(const std::string& path, bs_t mode) return; } + // If path points to an optical raw device, complete the file opening + // (the following GetFileInformationByHandle() would always fail on a raw device). + if (is_optical_raw_device(path)) + { + DISK_GEOMETRY_EX geometry; + + // Try to retrieve information on content. If it fails, no disc is probably mounted so abort the file opening + if (!DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr)) + { + const DWORD last_error = GetLastError(); + CloseHandle(handle); + g_tls_error = to_error(last_error); + return; + } + + m_file = std::make_unique(handle, true); + return; + } + // Check if the handle is actually valid. // This can fail on empty mounted drives (e.g. with ERROR_NOT_READY or ERROR_INVALID_FUNCTION). BY_HANDLE_FILE_INFORMATION info{}; + if (!GetFileInformationByHandle(handle, &info)) { const DWORD last_error = GetLastError(); @@ -1671,7 +1761,7 @@ fs::file::file(const std::string& path, bs_t mode) g_tls_error = fs::error::isdir; return; } - + g_tls_error = to_error(last_error); return; } diff --git a/Utilities/File.h b/Utilities/File.h index 3d332dd0be..778bfacd48 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -213,6 +213,12 @@ namespace fs // Check whether the path points to an existing symlink bool is_symlink(const std::string& path); + // Check whether the path points to a raw device + bool is_optical_raw_device(const std::string& path); + + // Check whether the path points to an optical drive. If so, provide the raw device in "raw_device" if requested + bool get_optical_raw_device(const std::string& path, std::string* raw_device = nullptr); + // Get filesystem information bool statfs(const std::string& path, device_stat& info); diff --git a/Utilities/JIT.h b/Utilities/JIT.h index 86fc72ed55..aba30c3400 100644 --- a/Utilities/JIT.h +++ b/Utilities/JIT.h @@ -525,7 +525,7 @@ class jit_compiler final atomic_t m_disk_space = umax; public: - jit_compiler(const std::unordered_map& _link, const std::string& _cpu, u32 flags = 0, std::function symbols_cement = {}) noexcept; + jit_compiler(const std::unordered_map& _link, std::string_view _cpu, u32 flags = 0, std::function symbols_cement = {}) noexcept; jit_compiler& operator=(thread_state) noexcept; ~jit_compiler() noexcept; @@ -543,9 +543,15 @@ public: // Add module (path to obj cache dir) void add(std::unique_ptr _module, const std::string& path); + // Returns false after LLVM fatal recovery. The compiler must be discarded. + bool try_add(std::unique_ptr _module, const std::string& path, std::string& error); + // Add module (not cached) void add(std::unique_ptr _module); + // Returns false after LLVM fatal recovery. The compiler must be discarded. + bool try_add(std::unique_ptr _module, std::string& error); + // Add object (path to obj file) bool add(const std::string& path); @@ -558,11 +564,14 @@ public: // Finalize void fin(); + // Returns false after LLVM fatal recovery. The compiler must be discarded. + bool try_fin(std::string& error); + // Get compiled function address u64 get(const std::string& name); // Get CPU info - static std::string cpu(const std::string& _cpu); + static std::string cpu(std::string_view _cpu); // Get system triple (PPU) static std::string triple1(); diff --git a/Utilities/JITLLVM.cpp b/Utilities/JITLLVM.cpp index 34e1572185..3bfb30cea2 100644 --- a/Utilities/JITLLVM.cpp +++ b/Utilities/JITLLVM.cpp @@ -12,6 +12,10 @@ #include +#if defined(__APPLE__) +#include +#endif + LOG_CHANNEL(jit_log, "JIT"); #ifdef LLVM_AVAILABLE @@ -50,6 +54,44 @@ LOG_CHANNEL(jit_log, "JIT"); #include "Emu/CPU/Backends/AArch64/AArch64Common.h" #endif +namespace +{ + thread_local std::string* g_llvm_fatal_message = nullptr; + + template + bool run_recoverable_llvm(F&& func, std::string& error) + { + error.clear(); + + // Run LLVM codegen in a disposable thread. If LLVM invokes the fatal + // handler, only this helper thread exits. + named_thread worker("LLVM JIT", [&]() + { +#if defined(__APPLE__) + pthread_jit_write_protect_np(false); +#endif + g_llvm_fatal_message = &error; + + std::forward(func)(); + + g_llvm_fatal_message = nullptr; +#if defined(__APPLE__) + pthread_jit_write_protect_np(true); +#endif + }); + + worker(); + const bool result = static_cast(worker) == thread_state::finished; + + if (!result && error.empty()) + { + error = "LLVM crash recovery invoked"; + } + + return result; + } +} + const bool jit_initialize = []() -> bool { llvm::InitializeNativeTarget(); @@ -509,9 +551,9 @@ public: } }; -std::string jit_compiler::cpu(const std::string& _cpu) +std::string jit_compiler::cpu(std::string_view _cpu) { - std::string m_cpu = _cpu; + std::string m_cpu = std::string(_cpu); if (m_cpu.empty()) { @@ -639,7 +681,7 @@ bool jit_compiler::add_sub_disk_space(ssz space) }).second; } -jit_compiler::jit_compiler(const std::unordered_map& _link, const std::string& _cpu, u32 flags, std::function symbols_cement) noexcept +jit_compiler::jit_compiler(const std::unordered_map& _link, std::string_view _cpu, u32 flags, std::function symbols_cement) noexcept : m_context(new llvm::LLVMContext) , m_cpu(cpu(_cpu)) { @@ -649,6 +691,13 @@ jit_compiler::jit_compiler(const std::unordered_map& _link, co llvm::install_fatal_error_handler([](void*, const char* msg, bool) { const std::string_view out = msg ? msg : ""; + + if (g_llvm_fatal_message) + { + *g_llvm_fatal_message = out; + thread_ctrl::silent_exit(); + } + fmt::throw_exception("LLVM Emergency Exit Invoked: '%s'", out); }, nullptr); @@ -701,6 +750,15 @@ jit_compiler::jit_compiler(const std::unordered_map& _link, co else attributes.push_back("-dotprod"); + // The recompilers emit i8mm intrinsics (e.g. ummla) gated on utils::has_i8mm(). + // The JIT target features must advertise i8mm too, otherwise the backend fails + // with "Cannot select: intrinsic %llvm.aarch64.neon.ummla" whenever the resolved + // -mcpu does not already imply it (e.g. the cortex-a78 fallback on Apple silicon). + if (utils::has_i8mm()) + attributes.push_back("+i8mm"); + else + attributes.push_back("-i8mm"); + if (utils::has_sve()) attributes.push_back("+sve"); else @@ -788,6 +846,33 @@ void jit_compiler::add(std::unique_ptr _module, const std::string& } } +bool jit_compiler::try_add(std::unique_ptr _module, const std::string& path, std::string& error) +{ + ObjectCache cache{path, this}; + m_engine->setObjectCache(&cache); + + const auto ptr = _module.get(); + m_engine->addModule(std::move(_module)); + + if (!run_recoverable_llvm([&]() + { + m_engine->generateCodeForModule(ptr); + }, error)) + { + return false; + } + + m_engine->setObjectCache(nullptr); + + for (auto& func : ptr->functions()) + { + // Delete IR to lower memory consumption + func.deleteBody(); + } + + return true; +} + void jit_compiler::add(std::unique_ptr _module) { const auto ptr = _module.get(); @@ -801,6 +886,28 @@ void jit_compiler::add(std::unique_ptr _module) } } +bool jit_compiler::try_add(std::unique_ptr _module, std::string& error) +{ + const auto ptr = _module.get(); + m_engine->addModule(std::move(_module)); + + if (!run_recoverable_llvm([&]() + { + m_engine->generateCodeForModule(ptr); + }, error)) + { + return false; + } + + for (auto& func : ptr->functions()) + { + // Delete IR to lower memory consumption + func.deleteBody(); + } + + return true; +} + bool jit_compiler::add(const std::string& path) { auto cache = ObjectCache::load(path); @@ -852,6 +959,14 @@ void jit_compiler::fin() m_engine->finalizeObject(); } +bool jit_compiler::try_fin(std::string& error) +{ + return run_recoverable_llvm([&]() + { + m_engine->finalizeObject(); + }, error); +} + u64 jit_compiler::get(const std::string& name) { return m_engine->getGlobalValueAddress(name); diff --git a/Utilities/StrFmt.cpp b/Utilities/StrFmt.cpp index d68ef51cb5..39a2cb9233 100644 --- a/Utilities/StrFmt.cpp +++ b/Utilities/StrFmt.cpp @@ -630,6 +630,13 @@ namespace fmt thread_ctrl::emergency_exit(out); } + [[noreturn]] void raw_verify_error(std::source_location loc, std::source_location propagated_loc, const char8_t* msg, usz object) + { + std::string out; + fmt::append(out, "%s (object: 0x%x)%s%s", msg ? msg : u8"Verification failed", object, loc, propagated_loc); + thread_ctrl::emergency_exit(out); + } + [[noreturn]] void raw_range_error(std::source_location loc, std::string_view index, usz container_size) { std::string out; @@ -921,6 +928,13 @@ std::string_view fmt::trim_front_sv(std::string_view source, std::string_view va void fmt::trim_back(std::string& source, std::string_view values) { const usz index = source.find_last_not_of(values); + + if (index == source.npos) + { + source.clear(); + return; + } + source.resize(index + 1); } diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 57d7446daf..abe6a108f8 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -1384,11 +1384,7 @@ bool handle_access_violation(u32 addr, bool is_writing, bool is_exec, ucontext_t return false; } - if (a_size != 4) - { - // Might be unimplemented, such as writing MFC proxy EAL+EAH using 64-bit store - break; - } + bool handled = true; switch (op) { @@ -1398,14 +1394,37 @@ bool handle_access_violation(u32 addr, bool is_writing, bool is_exec, ucontext_t case X64OP_LOAD_TEST: { u32 value; - if (is_writing || !thread->read_reg(addr, value)) + const u32 addr_aligned = addr & -4; + + if (addr % 4 + a_size > 4) + { + handled = false; + break; + } + + if (is_writing || !thread->read_reg(addr_aligned, value)) { return false; } + // Adjust value for 8-bit and 16-bit reads + value >>= ((4 - a_size) * 8) - ((addr % 4) * 8); + value &= a_size == 4 ? u32{umax} : ((1u << (a_size * 8)) - 1); + if (op != X64OP_LOAD_BE) { - value = stx::se_storage::swap(value); + if (a_size == 4) + { + value = stx::se_storage::swap(value); + } + else if (a_size == 2) + { + value = stx::se_storage::swap(value); + } + else + { + ensure(a_size == 1); + } } if (op == X64OP_LOAD_CMP) @@ -1440,12 +1459,35 @@ bool handle_access_violation(u32 addr, bool is_writing, bool is_exec, ucontext_t case X64OP_BEXTR: { u32 value; - if (is_writing || !thread->read_reg(addr, value)) + const u32 addr_aligned = addr & -4; + + if (addr % 4 + a_size > 4) + { + handled = false; + break; + } + + if (is_writing || !thread->read_reg(addr_aligned, value)) { return false; } - value = stx::se_storage::swap(value); + // Adjust value for 8-bit and 16-bit reads + value >>= ((4 - a_size) * 8) - ((addr % 4) * 8); + value &= a_size == 4 ? u32{umax} : ((1u << (a_size * 8)) - 1); + + if (a_size == 4) + { + value = stx::se_storage::swap(value); + } + else if (a_size == 2) + { + value = stx::se_storage::swap(value); + } + else + { + ensure(a_size == 1); + } u64 ctrl; if (!get_x64_reg_value(context, s_tls_reg3, d_size, i_size, ctrl)) @@ -1471,6 +1513,13 @@ bool handle_access_violation(u32 addr, bool is_writing, bool is_exec, ucontext_t case X64OP_STORE: case X64OP_STORE_BE: { + if (a_size != 4) + { + // Might be unimplemented, such as writing MFC proxy EAL+EAH using 64-bit store + handled = false; + break; + } + u64 reg_value; if (!is_writing || !get_x64_reg_value(context, reg, d_size, i_size, reg_value)) { @@ -1489,12 +1538,19 @@ bool handle_access_violation(u32 addr, bool is_writing, bool is_exec, ucontext_t case X64OP_STOS: default: { - sig_log.error("Invalid or unsupported operation (op=%d, reg=%d, d_size=%lld, i_size=%lld)", +op, +reg, d_size, i_size); + sig_log.error("Invalid or unsupported operation (op=%d, addr=0x%x, reg=%d, d_size=%lld, i_size=%lld, a_size=%d)", +op, addr, +reg, d_size, i_size, a_size); report_opcode(); return false; } } + if (!handled) + { + sig_log.error("Invalid or unsupported operation (op=%d, addr=0x%x, reg=%d, d_size=%lld, i_size=%lld, a_size=%d)", +op, addr, +reg, d_size, i_size, a_size); + report_opcode(); + break; + } + // skip processed instruction RIP(context) += i_size; g_tls_fault_spu++; @@ -2988,6 +3044,32 @@ void thread_ctrl::set_name(std::string name) report_fatal_error(reason); } +void thread_ctrl::silent_exit() noexcept +{ + if (const auto _this = g_tls_this_thread) + { + g_tls_error_callback(); + + u64 _self = _this->finalize(thread_state::errored); + + if (_self == umax) + { + // Unused, detached thread support remnant + delete _this; + } + + thread_base::finalize(umax); + } + +#ifdef _WIN32 + _endthreadex(0); +#else + pthread_exit(nullptr); +#endif + + std::abort(); +} + void thread_ctrl::detect_cpu_layout() { if (!g_native_core_layout.compare_and_swap_test(native_core_arrangement::undefined, native_core_arrangement::generic)) diff --git a/Utilities/Thread.h b/Utilities/Thread.h index bafcea0b9f..5d6d07c870 100644 --- a/Utilities/Thread.h +++ b/Utilities/Thread.h @@ -315,6 +315,9 @@ public: // Exit. [[noreturn]] static void emergency_exit(std::string_view reason); + // Exit the current named thread as errored without reporting a fatal error. + [[noreturn]] static void silent_exit() noexcept; + // Get current thread (may be nullptr) static thread_base* get_current() { @@ -797,31 +800,30 @@ public: m_count = 0; // Create all threads - for (u32 i = 0; i < count - 1; i++) + for (; m_count < count - 1; m_count++) { // Copy the context std::remove_cvref_t context(static_cast(f)); // Perform the check and additional preparations for each context - if (!std::invoke(std::forward(check), i, context)) + if (!std::invoke(std::forward(check), m_count, context)) { return; } - m_count++; - new (static_cast(m_threads + i)) Thread(std::string(name) + std::to_string(i + 1), std::move(context)); + new (static_cast(m_threads + m_count)) Thread(std::string(name) + std::to_string(m_count + 1), std::move(context)); } // Move the context (if movable) std::remove_cvref_t context(std::forward(f)); - if (!std::invoke(std::forward(check), m_count - 1, context)) + if (!std::invoke(std::forward(check), m_count, context)) { return; } + new (static_cast(m_threads + m_count)) Thread(std::string(name) + std::to_string(m_count + 1), std::move(context)); m_count++; - new (static_cast(m_threads + m_count - 1)) Thread(std::string(name) + std::to_string(m_count - 1), std::move(context)); } // Default constructor diff --git a/Utilities/cheat_info.cpp b/Utilities/cheat_info.cpp index 7745d26732..e67e103214 100644 --- a/Utilities/cheat_info.cpp +++ b/Utilities/cheat_info.cpp @@ -29,7 +29,7 @@ void fmt_class_string::format(std::string& out, u64 arg) bool cheat_info::from_str(std::string_view cheat_line) { - const auto cheat_vec = fmt::split(cheat_line, {"@@@"}, false); + 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)) @@ -38,11 +38,11 @@ bool cheat_info::from_str(std::string_view cheat_line) return false; } - game = cheat_vec[0]; - description = cheat_vec[1]; + game = std::move(cheat_vec[0]); + description = std::move(cheat_vec[1]); type = cheat_type{::narrow(val64)}; offset = std::stoul(cheat_vec[3]); - red_script = cheat_vec[4]; + red_script = std::move(cheat_vec[4]); return true; } diff --git a/Utilities/geometry.h b/Utilities/geometry.h index 3ffbc04dd3..a5881d1cd5 100644 --- a/Utilities/geometry.h +++ b/Utilities/geometry.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -1027,3 +1028,6 @@ using color1u = color1_base; using color1i = color1_base; using color1f = color1_base; using color1d = color1_base; + +using mat3f = color3_base[3]; +static_assert(sizeof(mat3f) == sizeof(float) * 3 * 3); diff --git a/Utilities/sema.cpp b/Utilities/sema.cpp index ee3dd98ffa..81e3616f53 100644 --- a/Utilities/sema.cpp +++ b/Utilities/sema.cpp @@ -41,7 +41,13 @@ void semaphore_base::imp_wait() // Add waiter value += c_waiter; } - }); + else + { + return false; + } + + return true; + }).first; if (value & c_value_mask) { diff --git a/Utilities/stack_trace.cpp b/Utilities/stack_trace.cpp index 049e0f1805..0a95269848 100644 --- a/Utilities/stack_trace.cpp +++ b/Utilities/stack_trace.cpp @@ -119,23 +119,23 @@ namespace utils if (sym->NameLen) { - const auto function_name = wstr_to_utf8(sym->Name, static_cast(sym->NameLen)); + std::string function_name = wstr_to_utf8(sym->Name, static_cast(sym->NameLen)); // Attempt to get file and line information if available DWORD unused2; if (SymGetLineFromAddrW64(hProcess, reinterpret_cast(pointer), &unused2, &line_info)) { - const auto full_path = fmt::format("%s:%u %s", wstr_to_utf8(line_info.FileName, -1), line_info.LineNumber, function_name); - result.push_back(full_path); + std::string full_path = fmt::format("%s:%u %s", wstr_to_utf8(line_info.FileName, -1), line_info.LineNumber, function_name); + result.push_back(std::move(full_path)); } else { - result.push_back(function_name); + result.push_back(std::move(function_name)); } } else { - result.push_back(fmt::format("rpcs3@0xp", pointer)); + result.push_back(fmt::format("rpcs3@0x%p", pointer)); } } diff --git a/Utilities/stack_trace.h b/Utilities/stack_trace.h index d0cec0cf4c..d186a698f4 100644 --- a/Utilities/stack_trace.h +++ b/Utilities/stack_trace.h @@ -14,7 +14,7 @@ namespace utils // Printing utilities template - concept Logger = requires (T& t, const std::string& msg) + concept Logger = requires (T& t, std::string_view msg) { { t.print(msg) }; }; @@ -28,7 +28,7 @@ namespace utils : log(chan) {} - void print(const std::string& s) + void print(std::string_view s) { log.error("%s", s); } diff --git a/Utilities/stereo_config.cpp b/Utilities/stereo_config.cpp new file mode 100644 index 0000000000..186b8ef5e3 --- /dev/null +++ b/Utilities/stereo_config.cpp @@ -0,0 +1,159 @@ +#include "stdafx.h" +#include "stereo_config.h" +#include "Emu/system_config.h" + +stereo_config::stereo_matrices::stereo_matrices(const std::array, 3>& l, const std::array, 3>& r) +{ + for (usz i = 0; i < 3; i++) + { + left[i] = l[i]; + right[i] = r[i]; + } +} + +atomic_t stereo_config::s_live_preview_enabled = false; +stereo_config::stereo_matrices stereo_config::m_live_matrices = {}; + +const std::unordered_map stereo_config::m_matrices = +{ + {stereo_render_mode_options::anaglyph_red_cyan, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 1) + } + )}, + {stereo_render_mode_options::anaglyph_red_green, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0) + } + )}, + {stereo_render_mode_options::anaglyph_red_blue, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 1) + } + )}, + {stereo_render_mode_options::anaglyph_magenta_cyan, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0.5f) + }, + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0.5f) + } + )}, + {stereo_render_mode_options::anaglyph_trioscopic, stereo_matrices( + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0) + }, + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 1) + } + )}, + {stereo_render_mode_options::anaglyph_amber_blue, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 1.0f / 3.0f), + color3_base(0, 0, 1.0f / 3.0f), + color3_base(0, 0, 1.0f / 3.0f) + } + )}, +}; + +void stereo_config::update_from_config(bool stereo_enabled) +{ + m_stereo_mode = stereo_enabled ? g_cfg.video.stereo_render_mode.get() : stereo_render_mode_options::disabled; + + if (m_stereo_mode == stereo_render_mode_options::anaglyph_custom) + { + stereo_config::convert_matrix(m_custom_matrices.left, g_cfg.video.custom_anaglyph_matrices.left.get_map()); + stereo_config::convert_matrix(m_custom_matrices.right, g_cfg.video.custom_anaglyph_matrices.right.get_map()); + } +} + +const stereo_config::stereo_matrices& stereo_config::matrices() const +{ + if (m_in_emulation && s_live_preview_enabled) + { + return m_live_matrices; + } + + switch (m_stereo_mode) + { + case stereo_render_mode_options::disabled: + case stereo_render_mode_options::side_by_side: + case stereo_render_mode_options::over_under: + case stereo_render_mode_options::interlaced: + break; + case stereo_render_mode_options::anaglyph_red_green: + case stereo_render_mode_options::anaglyph_red_blue: + case stereo_render_mode_options::anaglyph_red_cyan: + case stereo_render_mode_options::anaglyph_magenta_cyan: + case stereo_render_mode_options::anaglyph_trioscopic: + case stereo_render_mode_options::anaglyph_amber_blue: + return ::at32(m_matrices, m_stereo_mode); + case stereo_render_mode_options::anaglyph_custom: + return m_custom_matrices; + } + + static const stereo_matrices s_left_only_matrices = stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 1) + }, + { + color3_base(0, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }); + return s_left_only_matrices; +} + +std::map stereo_config::get_custom_matrix(bool is_left) const +{ + return convert_matrix(is_left ? m_custom_matrices.left : m_custom_matrices.right); +} + +std::map stereo_config::convert_matrix(const mat3f& mat) +{ + std::map values {}; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + values[fmt::format("%d%d", i, j)] = fmt::format("%f", mat[i].rgb[j]); + } + } + return values; +} diff --git a/Utilities/stereo_config.h b/Utilities/stereo_config.h new file mode 100644 index 0000000000..e1ea7fce03 --- /dev/null +++ b/Utilities/stereo_config.h @@ -0,0 +1,79 @@ +#pragma once + +#include "geometry.h" +#include "Emu/system_config_types.h" +#include "util/atomic.hpp" +#include "Utilities/StrFmt.h" +#include "Utilities/StrUtil.h" +#include +#include + +struct stereo_config +{ +public: + struct stereo_matrices + { + stereo_matrices() = default; + stereo_matrices(const std::array, 3>& l, const std::array, 3>& r); + + mat3f left {}; + mat3f right {}; + }; + + stereo_config(bool in_emulation) : m_in_emulation(in_emulation) {} + + void set_stereo_mode(stereo_render_mode_options mode) { m_stereo_mode = mode; } + stereo_render_mode_options stereo_mode() const { return m_stereo_mode; } + + const stereo_matrices& matrices() const; + + void update_from_config(bool stereo_enabled); + + void set_custom_matrices(const stereo_matrices& matrices) { m_custom_matrices = matrices; } + static void set_live_matrices(const stereo_matrices& matrices) { m_live_matrices = matrices; } + + std::map get_custom_matrix(bool is_left) const; + + template + static void convert_matrix(mat3f& mat, const T& values) + { + mat[0] = {}; + mat[1] = {}; + mat[2] = {}; + + if (values.empty()) + { + return; + } + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + const std::string key = fmt::format("%d%d", i, j); + const auto it = values.find(key); + if (it == values.cend()) continue; + + const std::string& val_s = it->second; + if (val_s.empty()) continue; + + if (f64 val = 0.0f; try_to_float(&val, val_s, -10.0f, 10.0f)) + { + mat[i].rgb[j] = static_cast(val); + } + } + } + } + + static std::map convert_matrix(const mat3f& mat); + + static atomic_t s_live_preview_enabled; + +private: + stereo_matrices m_custom_matrices {}; + stereo_render_mode_options m_stereo_mode = stereo_render_mode_options::disabled; + bool m_in_emulation = false; + + static stereo_matrices m_live_matrices; + static const std::unordered_map m_matrices; +}; diff --git a/Utilities/sync.h b/Utilities/sync.h index 513a45ee51..e0b8c39cc5 100644 --- a/Utilities/sync.h +++ b/Utilities/sync.h @@ -74,7 +74,7 @@ enum }; #endif -inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* timeout = nullptr, uint mask = 0) +inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* timeout = nullptr, [[maybe_unused]] uint mask = 0) { #ifdef __linux__ return syscall(SYS_futex, uaddr, futex_op, static_cast(val), timeout, nullptr, static_cast(mask)); diff --git a/Utilities/transactional_storage.h b/Utilities/transactional_storage.h index 1be923233e..87b9d63aac 100644 --- a/Utilities/transactional_storage.h +++ b/Utilities/transactional_storage.h @@ -33,7 +33,7 @@ public: std::lock_guard lock(mutex); if (std::shared_ptr new_val = std::invoke(func); new_val) { - storage.push_back(new_val); + storage.push_back(std::move(new_val)); } delete_unused(); } diff --git a/Utilities/version.cpp b/Utilities/version.cpp index 0c3cbb6da1..8c63560743 100644 --- a/Utilities/version.cpp +++ b/Utilities/version.cpp @@ -24,29 +24,27 @@ namespace utils return (m_hi << 24) | (m_mid << 16) | (m_lo << 8) | ((uint(m_type) & 0xf) << 4) | (m_type_index & 0xf); } - std::string version::to_string() const + std::string version::to_string(bool simple) const { - std::string version = std::to_string(hi()) + "." + std::to_string(mid()); + std::string version = fmt::format("%d.%d", hi(), mid()); if (lo()) { - version += '.'; - version += std::to_string(lo()); + fmt::append(version, ".%d", lo()); } - if (type() != version_type::release) + if (!simple && type() != version_type::release) { if (!postfix().empty()) { - version += "-" + postfix(); + fmt::append(version, "-%s", postfix()); } - version += ' '; - version += utils::to_string(type()); + fmt::append(version, " %s", utils::to_string(type())); if (type_index() > 1) { - version += " " + std::to_string(type_index()); + fmt::append(version, " %d", type_index()); } } diff --git a/Utilities/version.h b/Utilities/version.h index 00180b5436..316c1ae96f 100644 --- a/Utilities/version.h +++ b/Utilities/version.h @@ -23,10 +23,10 @@ namespace utils uint m_lo; version_type m_type = version_type::release; uint m_type_index = 1; - const char* m_postfix; + std::string_view m_postfix; public: - constexpr version(uint hi, uint mid, uint lo, version_type type, uint type_index, const char* postfix) + constexpr version(uint hi, uint mid, uint lo, version_type type, uint type_index, std::string_view postfix) : m_hi(hi) , m_mid(mid) , m_lo(lo) @@ -56,7 +56,7 @@ namespace utils return m_type; } - std::string postfix() const + std::string_view postfix() const { return m_postfix; } @@ -67,7 +67,7 @@ namespace utils } uint to_hex() const; - std::string to_string() const; + std::string to_string(bool simple = false) const; }; // Generic version comparison (e.g. 0.0.5 vs 1.3) diff --git a/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss b/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss index 20b8638862..5bc0d14d19 100644 --- a/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss +++ b/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss @@ -349,24 +349,24 @@ QPushButton:disabled { } -QDialog QDialogButtonBox QPushButton { +QDialog QDialogButtonBox QPushButton:default { color: #000000; background-color: #4CC2FF; border-color: #5AC7FF; } -QDialog QDialogButtonBox QPushButton:hover { +QDialog QDialogButtonBox QPushButton:default:hover { background-color: #48B2E9; border-color: #56B8EB; } -QDialog QDialogButtonBox QPushButton:pressed { +QDialog QDialogButtonBox QPushButton:default:pressed { color: #22526A; background-color: #45A4D5; border-color: #45A4D5; } -QDialog QDialogButtonBox QPushButton:disabled { +QDialog QDialogButtonBox QPushButton:default:disabled { color: #ABABAB; background-color: #4C4C4C; border-color: #4C4C4C; @@ -614,10 +614,15 @@ QComboBox QAbstractItemView { QComboBox QAbstractItemView::item { color: #FFFFFF; padding: 4px 6px; + margin: 2px 0px; background-color: transparent; border-radius: 4px; } +QComboBox QAbstractItemView::item:hover { + background-color: #383838; +} + QComboBox QAbstractItemView::item:selected { background-color: #383838; } @@ -642,7 +647,7 @@ QDoubleSpinBox::up-button, QDoubleSpinBox::down-button { border-radius: 4px; width: 8px; height: 8px; - padding: 3px 4px; + padding: 3px 4px; } QSpinBox::up-button:hover, QSpinBox::down-button:hover, @@ -709,6 +714,16 @@ QSlider::handle:horizontal:disabled { image: url("GuiConfigs/dark/slider-handle-disabled.svg"); } +QSlider::sub-page:horizontal { + background-color: #4CC2FF; + border-radius: 2px; + height: 4px; +} + +QSlider::sub-page:horizontal:disabled { + background-color: #767A7D; +} + /* LINE EDIT */ QLineEdit { @@ -791,7 +806,7 @@ QMenuBar::item:pressed { QMenu { background-color: #2C2C2C; - border: 1px solid #131313; + border: 1px solid #3E3E3E; padding: 4px 0px; } @@ -799,12 +814,12 @@ QMenu::item { color: #FFFFFF; background-color: transparent; border-radius: 4px; - padding: 8px 20px; + padding: 4px 20px; margin: 0px 4px; } QMenu::item:selected { - background-color: #383838; + background-color: #353535; } QMenu::item:disabled { @@ -814,8 +829,8 @@ QMenu::item:disabled { QMenu::separator { height: 1px; - background: #131313; - margin: 4px 0px; + background: #3E3E3E; + margin: 4px 8px; } QMenu::indicator, QMenu::right-arrow { diff --git a/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss b/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss index 692691856f..837383609e 100644 --- a/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss +++ b/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss @@ -349,24 +349,24 @@ QPushButton:disabled { } -QDialog QDialogButtonBox QPushButton { +QDialog QDialogButtonBox QPushButton:default { color: #FFFFFF; background-color: #0067C0; border-color: #1473C5; } -QDialog QDialogButtonBox QPushButton:hover { +QDialog QDialogButtonBox QPushButton:default:hover { background-color: #1A76C6; border-color: #2C80CA; } -QDialog QDialogButtonBox QPushButton:pressed { +QDialog QDialogButtonBox QPushButton:default:pressed { color: #C2DAEF; background-color: #3284CB; border-color: #3284CB; } -QDialog QDialogButtonBox QPushButton:disabled { +QDialog QDialogButtonBox QPushButton:default:disabled { color: #FFFFFF; background-color: #C5C5C5; border-color: #C5C5C5; @@ -614,10 +614,15 @@ QComboBox QAbstractItemView { QComboBox QAbstractItemView::item { color: #1B1B1B; padding: 4px 6px; + margin: 2px 0px; background-color: transparent; border-radius: 4px; } +QComboBox QAbstractItemView::item:hover { + background-color: #F0F0F0; +} + QComboBox QAbstractItemView::item:selected { background-color: #F0F0F0; } @@ -642,7 +647,7 @@ QDoubleSpinBox::up-button, QDoubleSpinBox::down-button { border-radius: 4px; width: 8px; height: 8px; - padding: 3px 4px; + padding: 3px 4px; } QSpinBox::up-button:hover, QSpinBox::down-button:hover, @@ -709,6 +714,16 @@ QSlider::handle:horizontal:disabled { image: url("GuiConfigs/light/slider-handle-disabled.svg"); } +QSlider::sub-page:horizontal { + background-color: #1A76C6; + border-radius: 2px; + height: 4px; +} + +QSlider::sub-page:horizontal:disabled { + background-color: #868788; +} + /* LINE EDIT */ QLineEdit { @@ -791,7 +806,7 @@ QMenuBar::item:pressed { QMenu { background-color: #F9F9F9; - border: 1px solid #EEEEEE; + border: 1px solid #E5E5E5; padding: 4px 0px; } @@ -799,7 +814,7 @@ QMenu::item { color: #1B1B1B; background-color: transparent; border-radius: 4px; - padding: 8px 20px; + padding: 4px 20px; margin: 0px 4px; } @@ -814,8 +829,8 @@ QMenu::item:disabled { QMenu::separator { height: 1px; - background: #EEEEEE; - margin: 4px 0px; + background: #E5E5E5; + margin: 4px 8px; } QMenu::indicator, QMenu::right-arrow { diff --git a/bin/GuiConfigs/check_mark_white.png b/bin/GuiConfigs/check_mark_white.png index f9b94ccb87..dd1b888dd6 100644 Binary files a/bin/GuiConfigs/check_mark_white.png and b/bin/GuiConfigs/check_mark_white.png differ diff --git a/bin/GuiConfigs/list_arrow_blue.png b/bin/GuiConfigs/list_arrow_blue.png index 6875377f35..da934a3bc1 100644 Binary files a/bin/GuiConfigs/list_arrow_blue.png and b/bin/GuiConfigs/list_arrow_blue.png differ diff --git a/bin/GuiConfigs/list_arrow_down_blue.png b/bin/GuiConfigs/list_arrow_down_blue.png index 18230006dc..baeee3e269 100644 Binary files a/bin/GuiConfigs/list_arrow_down_blue.png and b/bin/GuiConfigs/list_arrow_down_blue.png differ diff --git a/bin/GuiConfigs/list_arrow_down_green.png b/bin/GuiConfigs/list_arrow_down_green.png index 1ecfc6d288..1d691db108 100644 Binary files a/bin/GuiConfigs/list_arrow_down_green.png and b/bin/GuiConfigs/list_arrow_down_green.png differ diff --git a/bin/GuiConfigs/list_arrow_down_white.png b/bin/GuiConfigs/list_arrow_down_white.png index 12bd7b59dd..3708819116 100644 Binary files a/bin/GuiConfigs/list_arrow_down_white.png and b/bin/GuiConfigs/list_arrow_down_white.png differ diff --git a/bin/GuiConfigs/list_arrow_green.png b/bin/GuiConfigs/list_arrow_green.png index 239b31c8d0..6bc0ad79e4 100644 Binary files a/bin/GuiConfigs/list_arrow_green.png and b/bin/GuiConfigs/list_arrow_green.png differ diff --git a/bin/GuiConfigs/list_arrow_white.png b/bin/GuiConfigs/list_arrow_white.png index aa0ac4cae2..72f51af554 100644 Binary files a/bin/GuiConfigs/list_arrow_white.png and b/bin/GuiConfigs/list_arrow_white.png differ diff --git a/bin/Icons/ui/L1.png b/bin/Icons/ui/L1.png index 8a1d87c540..62b170aa79 100644 Binary files a/bin/Icons/ui/L1.png and b/bin/Icons/ui/L1.png differ diff --git a/bin/Icons/ui/L2.png b/bin/Icons/ui/L2.png index 3b938d19ac..f20162f882 100644 Binary files a/bin/Icons/ui/L2.png and b/bin/Icons/ui/L2.png differ diff --git a/bin/Icons/ui/R1.png b/bin/Icons/ui/R1.png index 722e405c60..0bd24e92bc 100644 Binary files a/bin/Icons/ui/R1.png and b/bin/Icons/ui/R1.png differ diff --git a/bin/Icons/ui/R2.png b/bin/Icons/ui/R2.png index f05529fea9..7573a13715 100644 Binary files a/bin/Icons/ui/R2.png and b/bin/Icons/ui/R2.png differ diff --git a/bin/Icons/ui/circle.png b/bin/Icons/ui/circle.png index 6ed88bb0a6..efe9078481 100644 Binary files a/bin/Icons/ui/circle.png and b/bin/Icons/ui/circle.png differ diff --git a/bin/Icons/ui/cross.png b/bin/Icons/ui/cross.png index f74e38379c..2a4a9855d5 100644 Binary files a/bin/Icons/ui/cross.png and b/bin/Icons/ui/cross.png differ diff --git a/bin/Icons/ui/dpad.png b/bin/Icons/ui/dpad.png index 5a31103da5..4752c94477 100644 Binary files a/bin/Icons/ui/dpad.png and b/bin/Icons/ui/dpad.png differ diff --git a/bin/Icons/ui/dpad_down.png b/bin/Icons/ui/dpad_down.png index dddcc0d479..ac3ed664c3 100644 Binary files a/bin/Icons/ui/dpad_down.png and b/bin/Icons/ui/dpad_down.png differ diff --git a/bin/Icons/ui/dpad_left.png b/bin/Icons/ui/dpad_left.png index 78beb6350c..4582cc1eea 100644 Binary files a/bin/Icons/ui/dpad_left.png and b/bin/Icons/ui/dpad_left.png differ diff --git a/bin/Icons/ui/dpad_right.png b/bin/Icons/ui/dpad_right.png index 94a6590b33..891f6ac4dd 100644 Binary files a/bin/Icons/ui/dpad_right.png and b/bin/Icons/ui/dpad_right.png differ diff --git a/bin/Icons/ui/dpad_up.png b/bin/Icons/ui/dpad_up.png index 5bc87051e6..f4f812c544 100644 Binary files a/bin/Icons/ui/dpad_up.png and b/bin/Icons/ui/dpad_up.png differ diff --git a/bin/Icons/ui/fade_bottom.png b/bin/Icons/ui/fade_bottom.png index dbb7d04b4c..9430ff5f52 100644 Binary files a/bin/Icons/ui/fade_bottom.png and b/bin/Icons/ui/fade_bottom.png differ diff --git a/bin/Icons/ui/fade_top.png b/bin/Icons/ui/fade_top.png index 661225ec8b..b55c895c68 100644 Binary files a/bin/Icons/ui/fade_top.png and b/bin/Icons/ui/fade_top.png differ diff --git a/bin/Icons/ui/home/256/bug-solid.png b/bin/Icons/ui/home/256/bug-solid.png index 2c88980603..f25f0cf696 100644 Binary files a/bin/Icons/ui/home/256/bug-solid.png and b/bin/Icons/ui/home/256/bug-solid.png differ diff --git a/bin/Icons/ui/home/256/circle-left-solid.png b/bin/Icons/ui/home/256/circle-left-solid.png index f6b2e58768..9176fd6854 100644 Binary files a/bin/Icons/ui/home/256/circle-left-solid.png and b/bin/Icons/ui/home/256/circle-left-solid.png differ diff --git a/bin/Icons/ui/home/256/display-solid.png b/bin/Icons/ui/home/256/display-solid.png index 84cc043b07..483c05992b 100644 Binary files a/bin/Icons/ui/home/256/display-solid.png and b/bin/Icons/ui/home/256/display-solid.png differ diff --git a/bin/Icons/ui/home/256/floppy-disk-solid.png b/bin/Icons/ui/home/256/floppy-disk-solid.png index 3701361569..4c0c7c0c4c 100644 Binary files a/bin/Icons/ui/home/256/floppy-disk-solid.png and b/bin/Icons/ui/home/256/floppy-disk-solid.png differ diff --git a/bin/Icons/ui/home/256/gamepad-solid.png b/bin/Icons/ui/home/256/gamepad-solid.png index fe08f1bc58..fbba1f473d 100644 Binary files a/bin/Icons/ui/home/256/gamepad-solid.png and b/bin/Icons/ui/home/256/gamepad-solid.png differ diff --git a/bin/Icons/ui/home/256/gauge-solid.png b/bin/Icons/ui/home/256/gauge-solid.png index 5b6d731a9d..afd4d6b87d 100644 Binary files a/bin/Icons/ui/home/256/gauge-solid.png and b/bin/Icons/ui/home/256/gauge-solid.png differ diff --git a/bin/Icons/ui/home/256/headphones-solid.png b/bin/Icons/ui/home/256/headphones-solid.png index feb015695d..9c43b48cc9 100644 Binary files a/bin/Icons/ui/home/256/headphones-solid.png and b/bin/Icons/ui/home/256/headphones-solid.png differ diff --git a/bin/Icons/ui/home/256/home.png b/bin/Icons/ui/home/256/home.png index 622984c913..b2879b3492 100644 Binary files a/bin/Icons/ui/home/256/home.png and b/bin/Icons/ui/home/256/home.png differ diff --git a/bin/Icons/ui/home/256/maximize-solid.png b/bin/Icons/ui/home/256/maximize-solid.png index 65eaa43308..45819d1b7b 100644 Binary files a/bin/Icons/ui/home/256/maximize-solid.png and b/bin/Icons/ui/home/256/maximize-solid.png differ diff --git a/bin/Icons/ui/home/256/play-button-arrowhead.png b/bin/Icons/ui/home/256/play-button-arrowhead.png index 37e9cefe93..10634c819e 100644 Binary files a/bin/Icons/ui/home/256/play-button-arrowhead.png and b/bin/Icons/ui/home/256/play-button-arrowhead.png differ diff --git a/bin/Icons/ui/home/256/power-off-solid.png b/bin/Icons/ui/home/256/power-off-solid.png index cf0ab44839..ea9ddaa9cf 100644 Binary files a/bin/Icons/ui/home/256/power-off-solid.png and b/bin/Icons/ui/home/256/power-off-solid.png differ diff --git a/bin/Icons/ui/home/256/rotate-left-solid.png b/bin/Icons/ui/home/256/rotate-left-solid.png index 5eaf29edba..284ea56547 100644 Binary files a/bin/Icons/ui/home/256/rotate-left-solid.png and b/bin/Icons/ui/home/256/rotate-left-solid.png differ diff --git a/bin/Icons/ui/home/256/screenshot.png b/bin/Icons/ui/home/256/screenshot.png index 091e2bbc2e..f897d99449 100644 Binary files a/bin/Icons/ui/home/256/screenshot.png and b/bin/Icons/ui/home/256/screenshot.png differ diff --git a/bin/Icons/ui/home/256/settings.png b/bin/Icons/ui/home/256/settings.png index d682b9b27c..ecc8fbf2d9 100644 Binary files a/bin/Icons/ui/home/256/settings.png and b/bin/Icons/ui/home/256/settings.png differ diff --git a/bin/Icons/ui/home/256/sliders-solid.png b/bin/Icons/ui/home/256/sliders-solid.png index cfb030721e..29d609a51c 100644 Binary files a/bin/Icons/ui/home/256/sliders-solid.png and b/bin/Icons/ui/home/256/sliders-solid.png differ diff --git a/bin/Icons/ui/home/256/trophy-solid.png b/bin/Icons/ui/home/256/trophy-solid.png index 1bd98a9ac9..66cf3c52b9 100644 Binary files a/bin/Icons/ui/home/256/trophy-solid.png and b/bin/Icons/ui/home/256/trophy-solid.png differ diff --git a/bin/Icons/ui/home/256/user-group-solid.png b/bin/Icons/ui/home/256/user-group-solid.png index a154fa5ba9..b157bf62ad 100644 Binary files a/bin/Icons/ui/home/256/user-group-solid.png and b/bin/Icons/ui/home/256/user-group-solid.png differ diff --git a/bin/Icons/ui/home/256/video-camera.png b/bin/Icons/ui/home/256/video-camera.png index 9f5460fba6..e15055e9a7 100644 Binary files a/bin/Icons/ui/home/256/video-camera.png and b/bin/Icons/ui/home/256/video-camera.png differ diff --git a/bin/Icons/ui/home/32/bug-solid.png b/bin/Icons/ui/home/32/bug-solid.png index 401fa0ea27..bce7ebd391 100644 Binary files a/bin/Icons/ui/home/32/bug-solid.png and b/bin/Icons/ui/home/32/bug-solid.png differ diff --git a/bin/Icons/ui/home/32/circle-left-solid.png b/bin/Icons/ui/home/32/circle-left-solid.png index 1acf201d63..917d012a06 100644 Binary files a/bin/Icons/ui/home/32/circle-left-solid.png and b/bin/Icons/ui/home/32/circle-left-solid.png differ diff --git a/bin/Icons/ui/home/32/display-solid.png b/bin/Icons/ui/home/32/display-solid.png index 8a48c89494..7be58625a5 100644 Binary files a/bin/Icons/ui/home/32/display-solid.png and b/bin/Icons/ui/home/32/display-solid.png differ diff --git a/bin/Icons/ui/home/32/floppy-disk-solid.png b/bin/Icons/ui/home/32/floppy-disk-solid.png index 836c6523c3..2616eab996 100644 Binary files a/bin/Icons/ui/home/32/floppy-disk-solid.png and b/bin/Icons/ui/home/32/floppy-disk-solid.png differ diff --git a/bin/Icons/ui/home/32/gamepad-solid.png b/bin/Icons/ui/home/32/gamepad-solid.png index b783fee792..da1db84526 100644 Binary files a/bin/Icons/ui/home/32/gamepad-solid.png and b/bin/Icons/ui/home/32/gamepad-solid.png differ diff --git a/bin/Icons/ui/home/32/gauge-solid.png b/bin/Icons/ui/home/32/gauge-solid.png index 9853811ae2..156b1085c1 100644 Binary files a/bin/Icons/ui/home/32/gauge-solid.png and b/bin/Icons/ui/home/32/gauge-solid.png differ diff --git a/bin/Icons/ui/home/32/headphones-solid.png b/bin/Icons/ui/home/32/headphones-solid.png index e2cfabb07c..996ed79fdb 100644 Binary files a/bin/Icons/ui/home/32/headphones-solid.png and b/bin/Icons/ui/home/32/headphones-solid.png differ diff --git a/bin/Icons/ui/home/32/home.png b/bin/Icons/ui/home/32/home.png index 2ec05eab24..e40425cf5c 100644 Binary files a/bin/Icons/ui/home/32/home.png and b/bin/Icons/ui/home/32/home.png differ diff --git a/bin/Icons/ui/home/32/maximize-solid.png b/bin/Icons/ui/home/32/maximize-solid.png index 7a0a9e4fb6..42763ebf40 100644 Binary files a/bin/Icons/ui/home/32/maximize-solid.png and b/bin/Icons/ui/home/32/maximize-solid.png differ diff --git a/bin/Icons/ui/home/32/play-button-arrowhead.png b/bin/Icons/ui/home/32/play-button-arrowhead.png index 6ce5505f4d..c99e11a516 100644 Binary files a/bin/Icons/ui/home/32/play-button-arrowhead.png and b/bin/Icons/ui/home/32/play-button-arrowhead.png differ diff --git a/bin/Icons/ui/home/32/power-off-solid.png b/bin/Icons/ui/home/32/power-off-solid.png index 21ebad8e69..59a914ed77 100644 Binary files a/bin/Icons/ui/home/32/power-off-solid.png and b/bin/Icons/ui/home/32/power-off-solid.png differ diff --git a/bin/Icons/ui/home/32/rotate-left-solid.png b/bin/Icons/ui/home/32/rotate-left-solid.png index b466d28a8e..d637819fe9 100644 Binary files a/bin/Icons/ui/home/32/rotate-left-solid.png and b/bin/Icons/ui/home/32/rotate-left-solid.png differ diff --git a/bin/Icons/ui/home/32/screenshot.png b/bin/Icons/ui/home/32/screenshot.png index 97255279b4..55ced711ba 100644 Binary files a/bin/Icons/ui/home/32/screenshot.png and b/bin/Icons/ui/home/32/screenshot.png differ diff --git a/bin/Icons/ui/home/32/settings.png b/bin/Icons/ui/home/32/settings.png index eeaeceef7d..c049ef1b75 100644 Binary files a/bin/Icons/ui/home/32/settings.png and b/bin/Icons/ui/home/32/settings.png differ diff --git a/bin/Icons/ui/home/32/sliders-solid.png b/bin/Icons/ui/home/32/sliders-solid.png index ca4c75df23..3b0c61ede5 100644 Binary files a/bin/Icons/ui/home/32/sliders-solid.png and b/bin/Icons/ui/home/32/sliders-solid.png differ diff --git a/bin/Icons/ui/home/32/trophy-solid.png b/bin/Icons/ui/home/32/trophy-solid.png index 9d94e14b97..fde5c76ef0 100644 Binary files a/bin/Icons/ui/home/32/trophy-solid.png and b/bin/Icons/ui/home/32/trophy-solid.png differ diff --git a/bin/Icons/ui/home/32/user-group-solid.png b/bin/Icons/ui/home/32/user-group-solid.png index 114c45599d..989f7a8e5f 100644 Binary files a/bin/Icons/ui/home/32/user-group-solid.png and b/bin/Icons/ui/home/32/user-group-solid.png differ diff --git a/bin/Icons/ui/home/32/video-camera.png b/bin/Icons/ui/home/32/video-camera.png index d8fa8dfaaf..976ae49ca0 100644 Binary files a/bin/Icons/ui/home/32/video-camera.png and b/bin/Icons/ui/home/32/video-camera.png differ diff --git a/bin/Icons/ui/left_stick.png b/bin/Icons/ui/left_stick.png index 101b99cb17..d4302c58e1 100644 Binary files a/bin/Icons/ui/left_stick.png and b/bin/Icons/ui/left_stick.png differ diff --git a/bin/Icons/ui/loading.png b/bin/Icons/ui/loading.png index 33288035c7..a6a3a59768 100644 Binary files a/bin/Icons/ui/loading.png and b/bin/Icons/ui/loading.png differ diff --git a/bin/Icons/ui/new.png b/bin/Icons/ui/new.png index ac21f0e9e6..ee3273b2e2 100644 Binary files a/bin/Icons/ui/new.png and b/bin/Icons/ui/new.png differ diff --git a/bin/Icons/ui/right_stick.png b/bin/Icons/ui/right_stick.png index 374f589bf9..d0e32caef4 100644 Binary files a/bin/Icons/ui/right_stick.png and b/bin/Icons/ui/right_stick.png differ diff --git a/bin/Icons/ui/save.png b/bin/Icons/ui/save.png index c1683f7223..a0c216a326 100644 Binary files a/bin/Icons/ui/save.png and b/bin/Icons/ui/save.png differ diff --git a/bin/Icons/ui/select.png b/bin/Icons/ui/select.png index b493aff682..fb20526566 100644 Binary files a/bin/Icons/ui/select.png and b/bin/Icons/ui/select.png differ diff --git a/bin/Icons/ui/spinner-24.png b/bin/Icons/ui/spinner-24.png index 088b5d180a..6239bc9dee 100644 Binary files a/bin/Icons/ui/spinner-24.png and b/bin/Icons/ui/spinner-24.png differ diff --git a/bin/Icons/ui/square.png b/bin/Icons/ui/square.png index 0be20e14c9..09af27a6ac 100644 Binary files a/bin/Icons/ui/square.png and b/bin/Icons/ui/square.png differ diff --git a/bin/Icons/ui/start.png b/bin/Icons/ui/start.png index 3822fd85ff..38d7927467 100644 Binary files a/bin/Icons/ui/start.png and b/bin/Icons/ui/start.png differ diff --git a/bin/Icons/ui/triangle.png b/bin/Icons/ui/triangle.png index 50ba8572d8..cf1037d906 100644 Binary files a/bin/Icons/ui/triangle.png and b/bin/Icons/ui/triangle.png differ diff --git a/buildfiles/msvc/common_default.props b/buildfiles/msvc/common_default.props index bfddbb5465..742cc0637b 100644 --- a/buildfiles/msvc/common_default.props +++ b/buildfiles/msvc/common_default.props @@ -3,6 +3,9 @@ + + true + 10.0 diff --git a/buildfiles/msvc/rpcs3_default.props b/buildfiles/msvc/rpcs3_default.props index b612560a8e..31c4c243d2 100644 --- a/buildfiles/msvc/rpcs3_default.props +++ b/buildfiles/msvc/rpcs3_default.props @@ -9,6 +9,8 @@ $(SolutionDir)build\tmp\$(ProjectName)-$(Configuration)-$(Platform)\ $(SolutionDir)packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.8\build\native\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.targets true + $(SolutionDir)3rdparty\opencv\opencv\opencv413\build + opencv_world4130 diff --git a/buildfiles/msvc/rpcs3_release.props b/buildfiles/msvc/rpcs3_release.props index 378ba797d6..85cc17a8af 100644 --- a/buildfiles/msvc/rpcs3_release.props +++ b/buildfiles/msvc/rpcs3_release.props @@ -2,7 +2,6 @@ - Full diff --git a/rpcs3.sln b/rpcs3.sln index 4cb0830474..748946d239 100644 --- a/rpcs3.sln +++ b/rpcs3.sln @@ -117,6 +117,13 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rpcs3_test", "rpcs3\tests\r {70CD65B0-91D6-4FAE-9A7B-4AF55D0D1B12} = {70CD65B0-91D6-4FAE-9A7B-4AF55D0D1B12} EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pugixml", "pugixml", "{F520F4A6-27D1-4DD2-9BB2-E0625826D0A3}" + ProjectSection(SolutionItems) = preProject + 3rdparty\pugixml\src\pugiconfig.hpp = 3rdparty\pugixml\src\pugiconfig.hpp + 3rdparty\pugixml\src\pugixml.cpp = 3rdparty\pugixml\src\pugixml.cpp + 3rdparty\pugixml\src\pugixml.hpp = 3rdparty\pugixml\src\pugixml.hpp + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -274,6 +281,7 @@ Global {8846A9AA-5539-4C91-8301-F54260E1A07A} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} {3C67A2FF-4710-402A-BE3E-31B0CB0576DF} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} {D1CBF84E-07F8-4ACB-9CD2-BD205FDEEE1E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {F520F4A6-27D1-4DD2-9BB2-E0625826D0A3} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06CC7920-E085-4B81-9582-8DE8AAD42510} diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index ba65a16eaf..373017197a 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -81,7 +81,7 @@ if (NOT ANDROID) # Unix display manager if(X11_FOUND) - target_link_libraries(rpcs3_lib PRIVATE X11::X11) + target_link_libraries(rpcs3_lib PRIVATE X11::X11 xkbcommon-x11) elseif(USE_VULKAN AND UNIX AND NOT WAYLAND_FOUND AND NOT APPLE AND NOT ANDROID) # Wayland has been checked in 3rdparty/CMakeLists.txt already. message(FATAL_ERROR "RPCS3 requires either X11 or Wayland (or both) for Vulkan.") @@ -182,6 +182,7 @@ if(BUILD_RPCS3_TESTS) target_sources(rpcs3_test PRIVATE tests/test.cpp + tests/test_bit_set.cpp tests/test_fmt.cpp tests/test_pair.cpp tests/test_tuple.cpp @@ -191,6 +192,8 @@ if(BUILD_RPCS3_TESTS) tests/test_rsx_cfg.cpp tests/test_rsx_fp_asm.cpp tests/test_dmux_pamf.cpp + tests/test_spu_analyser.cpp + tests/test_types_util.cpp ) target_link_libraries(rpcs3_test diff --git a/rpcs3/Crypto/aes.cpp b/rpcs3/Crypto/aes.cpp index 1cfc72aaab..1f49c9f343 100644 --- a/rpcs3/Crypto/aes.cpp +++ b/rpcs3/Crypto/aes.cpp @@ -958,7 +958,7 @@ void padding(const unsigned char *lastb, unsigned char *pad, size_t length) } } -void aes_cmac(aes_context *ctx, size_t length, unsigned char *input, unsigned char *output) +void aes_cmac(aes_context *ctx, size_t length, const unsigned char *input, unsigned char *output) { unsigned char X[16], Y[16], M_last[16], padded[16]; unsigned char K1[16], K2[16]; diff --git a/rpcs3/Crypto/aes.h b/rpcs3/Crypto/aes.h index 42e20e664d..fb4cb28d86 100644 --- a/rpcs3/Crypto/aes.h +++ b/rpcs3/Crypto/aes.h @@ -172,7 +172,7 @@ int aes_crypt_ctr( aes_context *ctx, const unsigned char *input, unsigned char *output ); -void aes_cmac(aes_context *ctx, size_t length, unsigned char *input, unsigned char *output); +void aes_cmac(aes_context *ctx, size_t length, const unsigned char *input, unsigned char *output); #ifdef __cplusplus } diff --git a/rpcs3/Crypto/key_vault.cpp b/rpcs3/Crypto/key_vault.cpp index 6fa9aa0684..92d308e9f8 100644 --- a/rpcs3/Crypto/key_vault.cpp +++ b/rpcs3/Crypto/key_vault.cpp @@ -5,7 +5,7 @@ LOG_CHANNEL(key_vault_log, "KEY_VAULT"); -SELF_KEY::SELF_KEY(u64 ver_start, u64 ver_end, u16 rev, u32 type, const std::string& e, const std::string& r, const std::string& pb, const std::string& pr, u32 ct) +SELF_KEY::SELF_KEY(u64 ver_start, u64 ver_end, u16 rev, u32 type, std::string_view e, std::string_view r, std::string_view pb, std::string_view pr, u32 ct) { version_start = ver_start; version_end = ver_end; diff --git a/rpcs3/Crypto/key_vault.h b/rpcs3/Crypto/key_vault.h index 4dff84992b..36344defe2 100644 --- a/rpcs3/Crypto/key_vault.h +++ b/rpcs3/Crypto/key_vault.h @@ -30,7 +30,7 @@ struct SELF_KEY u8 priv[0x15]{}; u32 curve_type{}; - SELF_KEY(u64 ver_start, u64 ver_end, u16 rev, u32 type, const std::string& e, const std::string& r, const std::string& pb, const std::string& pr, u32 ct); + SELF_KEY(u64 ver_start, u64 ver_end, u16 rev, u32 type, std::string_view e, std::string_view r, std::string_view pb, std::string_view pr, u32 ct); }; constexpr u32 PASSPHRASE_KEY_LEN = 16; diff --git a/rpcs3/Crypto/unedat.cpp b/rpcs3/Crypto/unedat.cpp index 1ffaa33fc3..6fea6c1859 100644 --- a/rpcs3/Crypto/unedat.cpp +++ b/rpcs3/Crypto/unedat.cpp @@ -679,7 +679,7 @@ u128 GetEdatRifKeyFromRapFile(const fs::file& rap_file) return rifkey; } -bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& input_file_name, const u8* custom_klic, NPD_HEADER* npd_out) +bool VerifyEDATHeaderWithKLicense(const fs::file& input, std::string_view input_file_name, const u8* custom_klic, NPD_HEADER* npd_out) { // Setup NPD and EDAT/SDAT structs. NPD_HEADER NPD; @@ -702,7 +702,7 @@ bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& inpu // Perform header validation (EDAT only). char real_file_name[CRYPTO_MAX_PATH]{}; - extract_file_name(input_file_name.c_str(), real_file_name); + extract_file_name(input_file_name, real_file_name); if (!validate_npd_hashes(real_file_name, custom_klic, &NPD, &EDAT, false)) { edat_log.error("NPD hash validation failed!"); @@ -810,7 +810,7 @@ bool EDATADecrypter::ReadHeader() { // extract key from RIF char real_file_name[CRYPTO_MAX_PATH]{}; - extract_file_name(m_file_name.c_str(), real_file_name); + extract_file_name(m_file_name, real_file_name); if (!validate_npd_hashes(real_file_name, reinterpret_cast(&dec_key), &npdHeader, &edatHeader, false)) { diff --git a/rpcs3/Crypto/unedat.h b/rpcs3/Crypto/unedat.h index 31fce60d4e..cc91509a5e 100644 --- a/rpcs3/Crypto/unedat.h +++ b/rpcs3/Crypto/unedat.h @@ -62,7 +62,7 @@ struct EDAT_HEADER extern fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, u8 *custom_klic); extern void read_npd_edat_header(const fs::file* input, NPD_HEADER& NPD, EDAT_HEADER& EDAT); -extern bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& input_file_name, const u8* custom_klic, NPD_HEADER *npd_out = nullptr); +extern bool VerifyEDATHeaderWithKLicense(const fs::file& input, std::string_view input_file_name, const u8* custom_klic, NPD_HEADER *npd_out = nullptr); u128 GetEdatRifKeyFromRapFile(const fs::file& rap_file); diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index de23f5f1ba..aad8555053 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -807,7 +807,8 @@ bool package_reader::set_install_path() } // TODO: Verify whether other content types require appending title ID - if (m_metadata.content_type != PKG_CONTENT_TYPE_LICENSE) + // Append title ID depending on content type + if (m_metadata.content_type != PKG_CONTENT_TYPE_THEME && m_metadata.content_type != PKG_CONTENT_TYPE_LICENSE) dir += m_install_dir + '/'; // If false, an existing directory is being overwritten: cannot cancel the operation diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 0b7971c07e..621d811207 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -126,7 +126,7 @@ struct PKGEntry struct PKGMetaData { private: - static std::string to_hex_string(u8 buf[], usz size) + static std::string to_hex_string(const u8* buf, usz size) { std::stringstream sstream; for (usz i = 0; i < size; i++) @@ -135,7 +135,7 @@ private: } return sstream.str(); } - static std::string to_hex_string(u8 buf[], usz size, usz dotpos) + static std::string to_hex_string(const u8* buf, usz size, usz dotpos) { std::string result = to_hex_string(buf, size); if (result.size() > dotpos) diff --git a/rpcs3/Crypto/unself.cpp b/rpcs3/Crypto/unself.cpp index 3c187400cb..c0eac8d9c7 100644 --- a/rpcs3/Crypto/unself.cpp +++ b/rpcs3/Crypto/unself.cpp @@ -807,6 +807,8 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) self_f.seek(0); sce_hdr.Load(self_f); + const usz self_size = self_f.size(); + if (out_info) { *out_info = {}; @@ -880,8 +882,9 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) for(u32 i = 0; i < (isElf32 ? elf32_hdr.e_phnum : elf64_hdr.e_phnum); ++i) { - if (self_f.pos() >= self_f.size()) + if (self_f.pos() >= self_size) { + // Read out of bounds (file is truncated or corrupted) return false; } @@ -889,15 +892,23 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) m_seg_ext_hdr.back().Load(self_f); } - if (m_ext_hdr.version_hdr_offset == 0 || utils::add_saturate(m_ext_hdr.version_hdr_offset, sizeof(version_header)) > self_f.size()) + if (m_ext_hdr.version_hdr_offset == 0) { + // 0.85 Selfs have version_hdr_offset set to 0 + m_version_hdr = {}; + } + else if (utils::add_saturate(m_ext_hdr.version_hdr_offset, sizeof(version_header)) > self_size) + { + // Read out of bounds (file is truncated or corrupted) return false; } + else + { + // Read SCE version info. + self_f.seek(m_ext_hdr.version_hdr_offset); - // Read SCE version info. - self_f.seek(m_ext_hdr.version_hdr_offset); - - m_version_hdr.Load(self_f); + m_version_hdr.Load(self_f); + } // Read control info. m_supplemental_hdr_arr.clear(); @@ -905,8 +916,9 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) for (u64 i = 0; i < m_ext_hdr.supplemental_hdr_size;) { - if (self_f.pos() >= self_f.size()) + if (self_f.pos() >= self_size) { + // Read out of bounds (file is truncated or corrupted) return false; } diff --git a/rpcs3/Crypto/utils.cpp b/rpcs3/Crypto/utils.cpp index 51ad284c62..c23893c906 100644 --- a/rpcs3/Crypto/utils.cpp +++ b/rpcs3/Crypto/utils.cpp @@ -24,43 +24,60 @@ // Auxiliary functions (endian swap, xor). // Bytes conversion auxiliary function. -void bytes_to_hex(std::string& hex_str, const unsigned char* data, unsigned int data_length) +void bytes_to_hex(std::string& hex_str, const unsigned char* data, usz data_length) { - size_t str_length = data_length * 2; + const usz str_length = data_length * 2; hex_str.resize(str_length); - for (size_t i = 0; i < str_length; i += 2) + for (usz i = 0; i < str_length; i += 2) { const auto [ptr, err] = std::to_chars(hex_str.data() + i, hex_str.data() + i + 2, *data++, 16); if (err != std::errc()) { fmt::throw_exception("Failed to read bytes: %s", std::make_error_code(err).message()); } + + // Padding handling for values ​​< 0x10 (e.g. 0x05 becomes "5" instead of "05") + // If to_chars only writes 1 character, we move to the right and put '0' + if (ptr == &hex_str[i] + 1) + { + hex_str[i + 1] = hex_str[i]; + hex_str[i] = '0'; + } } } // Hex string conversion auxiliary function. -void hex_to_bytes(unsigned char* data, std::string_view hex_str, unsigned int str_length) +void hex_to_bytes(unsigned char* data, std::string_view hex_str, usz str_length, std::string* error) { - const auto strn_length = (str_length > 0) ? str_length : hex_str.size(); + const usz strn_length = (str_length > 0) ? str_length : hex_str.size(); // Don't convert if the string length is odd. if ((strn_length % 2) == 0) { - for (size_t i = 0; i < strn_length; i += 2) + for (usz i = 0; i < strn_length; i += 2) { const auto [ptr, err] = std::from_chars(hex_str.data() + i, hex_str.data() + i + 2, *data++, 16); if (err != std::errc()) { - fmt::throw_exception("Failed to read hex string: %s", std::make_error_code(err).message()); + std::string msg = fmt::format("Failed to read hex string: %s (hex='%s')", std::make_error_code(err).message(), hex_str); + + if (error) + { + *error = std::move(msg); + } + else + { + fmt::throw_exception("%s", msg); + } } } } } // Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC). -void aescbc128_decrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len) +void aescbc128_decrypt(const unsigned char *key, unsigned char *iv, const unsigned char *in, unsigned char *out, usz len) { aes_context ctx; aes_setkey_dec(&ctx, key, 128); @@ -70,7 +87,7 @@ void aescbc128_decrypt(unsigned char *key, unsigned char *iv, unsigned char *in, memset(iv, 0, 0x10); } -void aescbc128_encrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len) +void aescbc128_encrypt(const unsigned char *key, unsigned char *iv, const unsigned char *in, unsigned char *out, usz len) { aes_context ctx; aes_setkey_enc(&ctx, key, 128); @@ -80,14 +97,14 @@ void aescbc128_encrypt(unsigned char *key, unsigned char *iv, unsigned char *in, memset(iv, 0, 0x10); } -void aesecb128_encrypt(unsigned char *key, unsigned char *in, unsigned char *out) +void aesecb128_encrypt(const unsigned char *key, const unsigned char *in, unsigned char *out) { aes_context ctx; aes_setkey_enc(&ctx, key, 128); aes_crypt_ecb(&ctx, AES_ENCRYPT, in, out); } -bool hmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, usz in_len, unsigned char *hash, usz hash_len) +bool hmac_hash_compare(const unsigned char *key, int key_len, const unsigned char *in, usz in_len, const unsigned char *hash, usz hash_len) { const std::unique_ptr out(new u8[key_len]); @@ -96,12 +113,12 @@ bool hmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, usz i return std::memcmp(out.get(), hash, hash_len) == 0; } -void hmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, usz in_len, unsigned char *hash) +void hmac_hash_forge(const unsigned char *key, int key_len, const unsigned char *in, usz in_len, unsigned char *hash) { sha1_hmac(key, key_len, in, in_len, hash); } -bool cmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, usz in_len, unsigned char *hash, usz hash_len) +bool cmac_hash_compare(const unsigned char *key, int key_len, const unsigned char *in, usz in_len, const unsigned char *hash, usz hash_len) { const std::unique_ptr out(new u8[key_len]); @@ -112,24 +129,22 @@ bool cmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, usz i return std::memcmp(out.get(), hash, hash_len) == 0; } -void cmac_hash_forge(unsigned char *key, int /*key_len*/, unsigned char *in, usz in_len, unsigned char *hash) +void cmac_hash_forge(const unsigned char *key, int /*key_len*/, const unsigned char *in, usz in_len, unsigned char *hash) { aes_context ctx; aes_setkey_enc(&ctx, key, 128); aes_cmac(&ctx, in_len, in, hash); } -char* extract_file_name(const char* file_path, char real_file_name[CRYPTO_MAX_PATH]) +char* extract_file_name(std::string_view file_path, char real_file_name[CRYPTO_MAX_PATH]) { - std::string_view v(file_path); - - if (const auto pos = v.find_last_of(fs::delim); pos != umax) + if (const auto pos = file_path.find_last_of(fs::delim); pos != umax) { - v.remove_prefix(pos + 1); + file_path.remove_prefix(pos + 1); } std::span r(real_file_name, CRYPTO_MAX_PATH); - strcpy_trunc(r, v); + strcpy_trunc(r, file_path); return real_file_name; } @@ -199,7 +214,7 @@ std::array vtrm_portability_laid_paid() return sc_combine_laid_paid(0x0000000000000000L, 0x0000000000000000L); } -int sc_decrypt(const u8* sc_key, const std::array& laid_paid, u8* iv, u8* input, u8* output) +int sc_decrypt(const u8* sc_key, const std::array& laid_paid, u8* iv, const u8* input, u8* output) { aes_context ctx; u8 key[PASSPHRASE_KEY_LEN]; @@ -208,12 +223,12 @@ int sc_decrypt(const u8* sc_key, const std::array& laid_ return aes_crypt_cbc(&ctx, AES_DECRYPT, PASSPHRASE_OUT_LEN, iv, input, output); } -int vtrm_decrypt(int type, u8* iv, u8* input, u8* output) +int vtrm_decrypt(int type, u8* iv, const u8* input, u8* output) { return sc_decrypt(SC_ISO_SERIES_KEY_2, vtrm_get_laid_paid_from_type(type), iv, input, output); } -int vtrm_decrypt_master(s64 laid, s64 paid, u8* iv, u8* input, u8* output) +int vtrm_decrypt_master(s64 laid, s64 paid, u8* iv, const u8* input, u8* output) { return sc_decrypt(SC_ISO_SERIES_INTERNAL_KEY_3, sc_combine_laid_paid(laid, paid), iv, input, output); } @@ -232,7 +247,7 @@ const u8* vtrm_portability_type_mapper(int type) } } -int vtrm_decrypt_with_portability(int type, u8* iv, u8* input, u8* output) +int vtrm_decrypt_with_portability(int type, u8* iv, const u8* input, u8* output) { return sc_decrypt(vtrm_portability_type_mapper(type), vtrm_portability_laid_paid(), iv, input, output); } diff --git a/rpcs3/Crypto/utils.h b/rpcs3/Crypto/utils.h index 6e8dab9734..b8fbd32d8a 100644 --- a/rpcs3/Crypto/utils.h +++ b/rpcs3/Crypto/utils.h @@ -11,28 +11,28 @@ enum { CRYPTO_MAX_PATH = 4096 }; -char* extract_file_name(const char* file_path, char real_file_name[CRYPTO_MAX_PATH]); +char* extract_file_name(std::string_view file_path, char real_file_name[CRYPTO_MAX_PATH]); std::string sha256_get_hash(const char* data, usz size, bool lower_case); // Bytes conversion auxiliary function. -void bytes_to_hex(std::string& hex_str, const unsigned char* data, unsigned int data_length); +void bytes_to_hex(std::string& hex_str, const unsigned char* data, usz data_length); // Hex string conversion auxiliary function. -void hex_to_bytes(unsigned char* data, std::string_view hex_str, unsigned int str_length); +void hex_to_bytes(unsigned char* data, std::string_view hex_str, usz str_length, std::string* error = nullptr); // Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC). -void aescbc128_decrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len); -void aescbc128_encrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len); -void aesecb128_encrypt(unsigned char *key, unsigned char *in, unsigned char *out); -bool hmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, usz in_len, unsigned char *hash, usz hash_len); -void hmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, usz in_len, unsigned char *hash); -bool cmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, usz in_len, unsigned char *hash, usz hash_len); -void cmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, usz in_len, unsigned char *hash); +void aescbc128_decrypt(const unsigned char *key, unsigned char *iv, const unsigned char *in, unsigned char *out, usz len); +void aescbc128_encrypt(const unsigned char *key, unsigned char *iv, const unsigned char *in, unsigned char *out, usz len); +void aesecb128_encrypt(const unsigned char *key, const unsigned char *in, unsigned char *out); +bool hmac_hash_compare(const unsigned char *key, int key_len, const unsigned char *in, usz in_len, const unsigned char *hash, usz hash_len); +void hmac_hash_forge(const unsigned char *key, int key_len, const unsigned char *in, usz in_len, unsigned char *hash); +bool cmac_hash_compare(const unsigned char *key, int key_len, const unsigned char *in, usz in_len, const unsigned char *hash, usz hash_len); +void cmac_hash_forge(const unsigned char *key, int key_len, const unsigned char *in, usz in_len, const unsigned char *hash); void mbedtls_zeroize(void *v, size_t n); // SC passphrase crypto -int vtrm_decrypt(int type, u8* iv, u8* input, u8* output); -int vtrm_decrypt_master(s64 laid, s64 paid, u8* iv, u8* input, u8* output); -int vtrm_decrypt_with_portability(int type, u8* iv, u8* input, u8* output); +int vtrm_decrypt(int type, u8* iv, const u8* input, u8* output); +int vtrm_decrypt_master(s64 laid, s64 paid, u8* iv, const u8* input, u8* output); +int vtrm_decrypt_with_portability(int type, u8* iv, const u8* input, u8* output); diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index f863a2b911..045fa30769 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -77,6 +77,7 @@ target_sources(rpcs3_emu PRIVATE ../../Utilities/sema.cpp ../../Utilities/simple_ringbuf.cpp ../../Utilities/stack_trace.cpp + ../../Utilities/stereo_config.cpp ../../Utilities/StrFmt.cpp ../../Utilities/Thread.cpp ../../Utilities/version.cpp @@ -127,7 +128,7 @@ target_sources(rpcs3_emu PRIVATE ../Loader/TAR.cpp ../Loader/ISO.cpp ../Loader/iso_cache.cpp - ../Loader/iso_validation.cpp + ../Loader/content_validation.cpp ../Loader/TROPUSR.cpp ../Loader/TRP.cpp ) @@ -409,6 +410,7 @@ target_sources(rpcs3_emu PRIVATE Io/Dimensions.cpp Io/evdev_gun_handler.cpp Io/GameTablet.cpp + Io/gem_config.cpp Io/GHLtar.cpp Io/GunCon3.cpp Io/Infinity.cpp @@ -550,6 +552,7 @@ target_sources(rpcs3_emu PRIVATE RSX/Program/GLSLCommon.cpp RSX/Program/ProgramStateCache.cpp RSX/Program/program_util.cpp + RSX/Program/ShaderInterpreter.cpp RSX/Program/SPIRVCommon.cpp RSX/Program/VertexProgramDecompiler.cpp RSX/GSFrameBase.cpp @@ -585,6 +588,7 @@ if(NOT ANDROID AND NOT APPLE) RSX/GL/glutils/buffer_object.cpp RSX/GL/glutils/capabilities.cpp RSX/GL/glutils/common.cpp + RSX/GL/glutils/ex.cpp RSX/GL/glutils/fbo.cpp RSX/GL/glutils/image.cpp RSX/GL/glutils/program.cpp diff --git a/rpcs3/Emu/CPU/Backends/AArch64/AArch64ASM.cpp b/rpcs3/Emu/CPU/Backends/AArch64/AArch64ASM.cpp index 4c8a01e0e2..5ca0e77e95 100644 --- a/rpcs3/Emu/CPU/Backends/AArch64/AArch64ASM.cpp +++ b/rpcs3/Emu/CPU/Backends/AArch64/AArch64ASM.cpp @@ -46,26 +46,26 @@ namespace aarch64 { compiled_instruction_t i{}; i.asm_ = inst; - m_instructions.push_back(i); + m_instructions.push_back(std::move(i)); } void UASM::emit1(const char* inst, const Arg& arg0, const std::vector& clobbered) { int arg_id = 0; - fmt_replacement_list_t repl = { + const fmt_replacement_list_t repl = { { "{0}", arg0.to_string(&arg_id) } }; compiled_instruction_t i{}; i.asm_ = fmt::replace_all(inst, repl); embed_args(i, { arg0 }, clobbered); - m_instructions.push_back(i); + m_instructions.push_back(std::move(i)); } void UASM::emit2(const char* inst, const Arg& arg0, const Arg& arg1, const std::vector& clobbered) { int arg_id = 0; - fmt_replacement_list_t repl = { + const fmt_replacement_list_t repl = { { "{0}", arg0.to_string(&arg_id) }, { "{1}", arg1.to_string(&arg_id) }, }; @@ -73,13 +73,13 @@ namespace aarch64 compiled_instruction_t i{}; i.asm_ = fmt::replace_all(inst, repl); embed_args(i, { arg0, arg1 }, clobbered); - m_instructions.push_back(i); + m_instructions.push_back(std::move(i)); } void UASM::emit3(const char* inst, const Arg& arg0, const Arg& arg1, const Arg& arg2, const std::vector& clobbered) { int arg_id = 0; - fmt_replacement_list_t repl = { + const fmt_replacement_list_t repl = { { "{0}", arg0.to_string(&arg_id) }, { "{1}", arg1.to_string(&arg_id) }, { "{2}", arg2.to_string(&arg_id) }, @@ -88,13 +88,13 @@ namespace aarch64 compiled_instruction_t i{}; i.asm_ = fmt::replace_all(inst, repl); embed_args(i, { arg0, arg1, arg2 }, clobbered); - m_instructions.push_back(i); + m_instructions.push_back(std::move(i)); } void UASM::emit4(const char* inst, const Arg& arg0, const Arg& arg1, const Arg& arg2, const Arg& arg3, const std::vector& clobbered) { int arg_id = 0; - fmt_replacement_list_t repl = { + const fmt_replacement_list_t repl = { { "{0}", arg0.to_string(&arg_id) }, { "{1}", arg1.to_string(&arg_id) }, { "{2}", arg2.to_string(&arg_id) }, @@ -104,14 +104,14 @@ namespace aarch64 compiled_instruction_t i{}; i.asm_ = fmt::replace_all(inst, repl); embed_args(i, { arg0, arg1, arg2, arg3 }, clobbered); - m_instructions.push_back(i); + m_instructions.push_back(std::move(i)); } void UASM::insert(llvm::IRBuilder<>* irb, llvm::LLVMContext& ctx) const { for (const auto& inst : m_instructions) { - auto constraints = fmt::merge(inst.constraints, ","); + const auto constraints = fmt::merge(inst.constraints, ","); llvm_asm(irb, inst.asm_, inst.args, constraints, ctx); } } diff --git a/rpcs3/Emu/CPU/Backends/AArch64/AArch64JIT.cpp b/rpcs3/Emu/CPU/Backends/AArch64/AArch64JIT.cpp index 91aedffd85..2fda215664 100644 --- a/rpcs3/Emu/CPU/Backends/AArch64/AArch64JIT.cpp +++ b/rpcs3/Emu/CPU/Backends/AArch64/AArch64JIT.cpp @@ -219,7 +219,7 @@ namespace aarch64 return result; } - gpr GHC_frame_preservation_pass::get_base_register_for_call(const std::string& callee_name, gpr default_reg) + gpr GHC_frame_preservation_pass::get_base_register_for_call(std::string_view callee_name, gpr default_reg) { // We go over the base_register_lookup table and find the first matching pattern for (const auto& pattern : m_config.base_register_lookup) diff --git a/rpcs3/Emu/CPU/Backends/AArch64/AArch64JIT.h b/rpcs3/Emu/CPU/Backends/AArch64/AArch64JIT.h index 701fe85be9..f5a616e32c 100644 --- a/rpcs3/Emu/CPU/Backends/AArch64/AArch64JIT.h +++ b/rpcs3/Emu/CPU/Backends/AArch64/AArch64JIT.h @@ -45,7 +45,7 @@ namespace aarch64 bool use_stack_frames = true; // Allocate a stack frame for each function. The gateway can alternatively manage a global stack to use as scratch. bool optimize = true; // Optimize instructions when possible. Set to false when debugging. u32 hypervisor_context_offset = 0; // Offset within the "thread" object where we can find the hypervisor context (registers configured at gateway). - std::function exclusion_callback; // [Optional] Callback run on each function before transform. Return "true" to exclude from frame processing. + std::function exclusion_callback; // [Optional] Callback run on each function before transform. Return "true" to exclude from frame processing. std::vector> base_register_lookup; // [Optional] Function lookup table to determine the location of the "thread" context. std::vector faux_function_list; // [Optional] List of faux block names to treat as untrusted - typically fake functions representing codecaves. }; @@ -67,7 +67,7 @@ namespace aarch64 bool is_faux_function(const std::string& function_name); - gpr get_base_register_for_call(const std::string& callee_name, gpr default_reg = gpr::x19); + gpr get_base_register_for_call(std::string_view callee_name, gpr default_reg = gpr::x19); void process_leaf_function(llvm::IRBuilder<>* irb, llvm::Function& f); diff --git a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.cpp b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.cpp index 48b651d1a3..6b411e8111 100644 --- a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.cpp +++ b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.cpp @@ -39,7 +39,7 @@ namespace aarch64 if (head->magic == ESR_CTX_MAGIC) { - return reinterpret_cast(head); + return utils::bless(head); } offset += head->size; diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index 78a0a3bd7b..baa8519f32 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -1509,7 +1509,7 @@ bool cpu_thread::suspend_work::push(cpu_thread* _this) noexcept { for (u32 i = 0; i < work->prf_size; i++) { - utils::prefetch_write(work->prf_list[0]); + utils::prefetch_write(work->prf_list[i]); } } diff --git a/rpcs3/Emu/CPU/CPUTranslator.cpp b/rpcs3/Emu/CPU/CPUTranslator.cpp index 66b5c69af0..56eece2b36 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.cpp +++ b/rpcs3/Emu/CPU/CPUTranslator.cpp @@ -207,6 +207,21 @@ void cpu_translator::initialize(llvm::LLVMContext& context, llvm::ExecutionEngin { m_use_dotprod = true; } + + if (utils::has_i8mm()) + { + m_use_i8mm = true; + } + + if (utils::has_sve() && utils::sve_length() == 128) + { + m_use_sve_128 = true; + } + + if (utils::has_sve2() && utils::sve_length() == 128) + { + m_use_sve2_128 = true; + } #endif } diff --git a/rpcs3/Emu/CPU/CPUTranslator.h b/rpcs3/Emu/CPU/CPUTranslator.h index ab2aed8156..b95ea4051b 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.h +++ b/rpcs3/Emu/CPU/CPUTranslator.h @@ -1319,11 +1319,96 @@ struct llvm_rol llvm_expr_t a1; llvm_expr_t a2; + bool use_sve_xar = false; static_assert(llvm_value_t::is_sint || llvm_value_t::is_uint, "llvm_rol<>: invalid type"); static constexpr bool is_ok = llvm_value_t::is_sint || llvm_value_t::is_uint; +#ifdef ARCH_ARM64 + static bool get_constant_splat(llvm::Value* value, u64& result) + { + if (const auto constant = llvm::dyn_cast(value)) + { + result = constant->getZExtValue(); + return true; + } + + if (llvm::isa(value)) + { + result = 0; + return true; + } + + const auto vector_type = llvm::dyn_cast(value->getType()); + + if (!vector_type) + { + return false; + } + + const auto element_count = vector_type->getNumElements(); + const auto get_element = [&](u32 index) -> llvm::Constant* + { + if (const auto data = llvm::dyn_cast(value)) + { + return data->getElementAsConstant(index); + } + + if (const auto vector = llvm::dyn_cast(value)) + { + return vector->getAggregateElement(index); + } + + return nullptr; + }; + + const auto first_element = llvm::dyn_cast_or_null(get_element(0)); + + if (!first_element) + { + return false; + } + + result = first_element->getZExtValue(); + + for (u32 i = 1; i < element_count; i++) + { + const auto element = llvm::dyn_cast_or_null(get_element(i)); + + if (!element || element->getZExtValue() != result) + { + return false; + } + } + + return true; + } + + static llvm::Value* to_sve_vector(llvm::IRBuilder<>* ir, llvm::Value* value) + { + if (llvm::isa(value->getType())) + { + return value; + } + + const auto fixed_type = llvm::cast(value->getType()); + const auto scalable_type = llvm::ScalableVectorType::get(fixed_type->getElementType(), fixed_type->getNumElements()); + + return ir->CreateInsertVector(scalable_type, llvm::UndefValue::get(scalable_type), value, ir->getInt64(0)); + } + + static llvm::Value* from_sve_vector(llvm::IRBuilder<>* ir, llvm::Value* value, llvm::FixedVectorType* fixed_type) + { + if (value->getType() == fixed_type) + { + return value; + } + + return ir->CreateExtractVector(fixed_type, value, ir->getInt64(0)); + } +#endif + llvm::Value* eval(llvm::IRBuilder<>* ir) const { const auto v1 = a1.eval(ir); @@ -1334,6 +1419,28 @@ struct llvm_rol return llvm_fshl::fold(ir, v1, v1, v2); } +#ifdef ARCH_ARM64 + u64 rotate = 0; + + if (use_sve_xar && llvm::isa(v1->getType()) && get_constant_splat(v2, rotate)) + { + constexpr u64 element_size = llvm_value_t::esize; + const u32 rotate_right = static_cast((element_size - (rotate % element_size)) % element_size); + + if (rotate_right == 0) + { + return v1; + } + + const auto fixed_type = llvm::cast(v1->getType()); + const auto data = to_sve_vector(ir, v1); + const auto zero = llvm::Constant::getNullValue(data->getType()); + const auto result = ir->CreateIntrinsic(llvm::Intrinsic::aarch64_sve_xar, {data->getType()}, {data, zero, ir->getInt32(rotate_right)}); + + return from_sve_vector(ir, result, fixed_type); + } +#endif + return ir->CreateCall(llvm_fshl::get_fshl(ir), {v1, v1, v2}); } @@ -3120,6 +3227,16 @@ protected: // ARMv8 SDOT/UDOT bool m_use_dotprod = false; + + // ARMv8.6 SMMLA/UMMLA + bool m_use_i8mm = false; + + // Allow direct TBL2/TBX2 emission. + bool m_use_tbl2 = true; + + bool m_use_sve_128 = false; + + bool m_use_sve2_128 = false; #else // Allow FMA bool m_use_fma = false; @@ -3397,9 +3514,13 @@ public: template requires llvm_rol::is_ok - static auto rol(T&& a, U&& b) + auto rol(T&& a, U&& b) { +#ifdef ARCH_ARM64 + return llvm_rol{std::forward(a), std::forward(b), m_use_sve2_128}; +#else return llvm_rol{std::forward(a), std::forward(b)}; +#endif } template @@ -3573,11 +3694,22 @@ public: // Infinite-precision shift left template > - auto inf_shl(T&& a, U&& b) + value_t inf_shl(T&& a, U&& b) { static constexpr u32 esz = llvm_value_t::esize; - return expr(select(b < esz, a << b, splat(0)), [](llvm::Value*& value, llvm::Module* _m) -> llvm_match_tuple +#ifdef ARCH_ARM64 + auto sh = eval(std::forward(b)); + auto k = get_known_bits(sh); + const auto max_shift = llvm::APInt(k.Zero.getBitWidth(), esz * 2 - 1); + + if ((k.Zero | max_shift).isAllOnes()) + { + return ushl(std::forward(a), sh); + } +#endif + + auto result = expr(select(b < esz, a << b, splat(0)), [](llvm::Value*& value, llvm::Module* _m) -> llvm_match_tuple { static const auto M = match(); @@ -3595,15 +3727,28 @@ public: value = nullptr; return {}; }); + + return eval(result); } // Infinite-precision logical shift right (unsigned) template > - auto inf_lshr(T&& a, U&& b) + value_t inf_lshr(T&& a, U&& b) { static constexpr u32 esz = llvm_value_t::esize; - return expr(select(b < esz, a >> b, splat(0)), [](llvm::Value*& value, llvm::Module* _m) -> llvm_match_tuple +#ifdef ARCH_ARM64 + auto sh = eval(std::forward(b)); + auto k = get_known_bits(sh); + const auto max_shift = llvm::APInt(k.Zero.getBitWidth(), esz * 2 - 1); + + if ((k.Zero | max_shift).isAllOnes()) + { + return ushl(std::forward(a), -sh); + } +#endif + + auto result = expr(select(b < esz, a >> b, splat(0)), [](llvm::Value*& value, llvm::Module* _m) -> llvm_match_tuple { static const auto M = match(); @@ -3621,6 +3766,8 @@ public: value = nullptr; return {}; }); + + return eval(result); } // Infinite-precision arithmetic shift right (signed) @@ -3660,6 +3807,23 @@ public: #endif } + template + value_t vperm2d128From512(T1 a, T2 b, T3 c) + { + value_t result; + value_t perm512; + + const auto data0 = a.eval(m_ir); + const auto index128 = b.eval(m_ir); + const auto data1 = c.eval(m_ir); + + const auto index512 = m_ir->CreateInsertVector(get_type(), llvm::UndefValue::get(get_type()), index128, m_ir->getInt64(0)); + perm512.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_avx512_vpermi2var_d_512), {data0, index512, data1}); + + result.value = m_ir->CreateExtractVector(get_type(), perm512.value, m_ir->getInt64(0)); + return result; + } + template value_t gf2p8affineqb(T1 a, T2 b, u8 c) { @@ -3720,8 +3884,182 @@ template result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_sdot), {data0, data1, data2}); return result; } - -template + + template + value_t ummla(T1 a, T2 b, T3 c) + { + value_t result; + + const auto data0 = a.eval(m_ir); + const auto data1 = b.eval(m_ir); + const auto data2 = c.eval(m_ir); + + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_ummla), {data0, data1, data2}); + return result; + } + + template + value_t smmla(T1 a, T2 b, T3 c) + { + value_t result; + + const auto data0 = a.eval(m_ir); + const auto data1 = b.eval(m_ir); + const auto data2 = c.eval(m_ir); + + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_smmla), {data0, data1, data2}); + return result; + } + + template + value_t smull(T1 a, T2 b) + { + value_t result; + + const auto data0 = a.eval(m_ir); + const auto data1 = b.eval(m_ir); + + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_smull), {data0, data1}); + return result; + } + + template + value_t umull(T1 a, T2 b) + { + value_t result; + + const auto data0 = a.eval(m_ir); + const auto data1 = b.eval(m_ir); + + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_umull), {data0, data1}); + return result; + } + + llvm::Value* to_sve_vector(llvm::Value* value) + { + if (llvm::isa(value->getType())) + { + return value; + } + + const auto fixed_type = llvm::cast(value->getType()); + const auto scalable_type = llvm::ScalableVectorType::get(fixed_type->getElementType(), fixed_type->getNumElements()); + return m_ir->CreateInsertVector(scalable_type, llvm::UndefValue::get(scalable_type), value, m_ir->getInt64(0)); + } + + llvm::Value* from_sve_vector(llvm::Value* value, llvm::FixedVectorType* fixed_type) + { + if (value->getType() == fixed_type) + { + return value; + } + + return m_ir->CreateExtractVector(fixed_type, value, m_ir->getInt64(0)); + } + + llvm::Value* sve_ptrue(llvm::FixedVectorType* fixed_type) + { + const auto pred_type = llvm::ScalableVectorType::get(m_ir->getInt1Ty(), fixed_type->getNumElements()); + return m_ir->CreateIntrinsic(llvm::Intrinsic::aarch64_sve_ptrue, {pred_type}, {m_ir->getInt32(31)}); + } + + llvm::Value* sve_fnmls(llvm::Value* acc, llvm::Value* lhs, llvm::Value* rhs) + { + const auto fixed_type = llvm::cast(acc->getType()); + const auto vacc = to_sve_vector(acc); + const auto vlhs = to_sve_vector(lhs); + const auto vrhs = to_sve_vector(rhs); + const auto result = m_ir->CreateIntrinsic(llvm::Intrinsic::aarch64_sve_fnmls, {vacc->getType()}, {sve_ptrue(fixed_type), vacc, vlhs, vrhs}); + + return from_sve_vector(result, fixed_type); + } + + template + value_t sve_mull(llvm::Intrinsic::ID id, T1 a, T2 b) + { + value_t result; + + const auto fixed_type = llvm::cast(get_type()); + const auto scalable_type = llvm::ScalableVectorType::get(fixed_type->getElementType(), fixed_type->getNumElements()); + const auto data0 = to_sve_vector(a.eval(m_ir)); + const auto data1 = to_sve_vector(b.eval(m_ir)); + const std::array types{scalable_type}; + + result.value = from_sve_vector(m_ir->CreateIntrinsic(id, types, {data0, data1}), fixed_type); + return result; + } + + template + value_t sve_mlal(llvm::Intrinsic::ID id, T0 acc, T1 a, T2 b) + { + value_t result; + + const auto fixed_type = llvm::cast(get_type()); + const auto scalable_type = llvm::ScalableVectorType::get(fixed_type->getElementType(), fixed_type->getNumElements()); + const auto data0 = to_sve_vector(acc.eval(m_ir)); + const auto data1 = to_sve_vector(a.eval(m_ir)); + const auto data2 = to_sve_vector(b.eval(m_ir)); + const std::array types{scalable_type}; + + result.value = from_sve_vector(m_ir->CreateIntrinsic(id, types, {data0, data1, data2}), fixed_type); + return result; + } + + template + value_t sve_smullb(T1 a, T2 b) + { + return sve_mull(llvm::Intrinsic::aarch64_sve_smullb, a, b); + } + + template + value_t sve_smullt(T1 a, T2 b) + { + return sve_mull(llvm::Intrinsic::aarch64_sve_smullt, a, b); + } + + template + value_t sve_umullb(T1 a, T2 b) + { + return sve_mull(llvm::Intrinsic::aarch64_sve_umullb, a, b); + } + + template + value_t sve_umullt(T1 a, T2 b) + { + return sve_mull(llvm::Intrinsic::aarch64_sve_umullt, a, b); + } + + template + value_t sve_smlalb(T0 acc, T1 a, T2 b) + { + return sve_mlal(llvm::Intrinsic::aarch64_sve_smlalb, acc, a, b); + } + + template + value_t sve_smlalt(T0 acc, T1 a, T2 b) + { + return sve_mlal(llvm::Intrinsic::aarch64_sve_smlalt, acc, a, b); + } + + template + value_t sve_umlalt(T0 acc, T1 a, T2 b) + { + return sve_mlal(llvm::Intrinsic::aarch64_sve_umlalt, acc, a, b); + } + + template > + value_t ushl(T1 a, T2 b) + { + value_t result; + + const auto data0 = a.eval(m_ir); + const auto data1 = b.eval(m_ir); + + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_ushl), {data0, data1}); + return result; + } + + template auto addp(T1 a, T2 b) { using T_vector = typename is_llvm_expr::type; @@ -4030,6 +4368,131 @@ public: }); } +#ifdef ARCH_ARM64 + template + value_t tbl(T1 a, T2 b) + { + value_t result; + const auto data0 = a.eval(m_ir); + const auto index = b.eval(m_ir); + const auto zeros = llvm::ConstantAggregateZero::get(get_type()); + + if (auto c = llvm::dyn_cast(index)) + { + v128 mask{}; + const auto cv = llvm::dyn_cast(c); + + if (cv) + { + for (u32 i = 0; i < 16; i++) + { + const u64 b_val = cv->getElementAsInteger(i); + mask._u8[i] = (b_val < 16) ? static_cast(b_val) : static_cast(16); + } + } + + if (cv || llvm::isa(c)) + { + result.value = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(reinterpret_cast(&mask), 16)); + result.value = m_ir->CreateZExt(result.value, get_type()); + result.value = m_ir->CreateShuffleVector(data0, zeros, result.value); + return result; + } + } + + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_tbl1), { data0, index }); + return result; + } + + template + value_t tbl2(T1 a, T2 b, T3 indices) + { + value_t result; + const auto data0 = a.eval(m_ir); + const auto data1 = b.eval(m_ir); + const auto index = indices.eval(m_ir); + + if (m_use_tbl2) + { + if (auto c = llvm::dyn_cast(index)) + { + v128 mask{}; + v128 bitmask{}; + const auto cv = llvm::dyn_cast(c); + + if (cv) + { + for (u32 i = 0; i < 16; i++) + { + const u64 b_val = cv->getElementAsInteger(i); + mask._u8[i] = (b_val < 32) ? static_cast(b_val) : static_cast(0); + bitmask._u8[i] = (b_val < 32) ? static_cast(0xFF) : static_cast(0x00); + } + } + else if (llvm::isa(c)) + { + bitmask = v128::from8p(0xFF); + } + + if (cv || llvm::isa(c)) + { + auto m_val = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(reinterpret_cast(&mask), 16)); + auto m_ext = m_ir->CreateZExt(m_val, get_type()); + auto lookup = m_ir->CreateShuffleVector(data0, data1, m_ext); + + auto z_mask = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(reinterpret_cast(&bitmask), 16)); + result.value = m_ir->CreateAnd(lookup, z_mask); + return result; + } + } + + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_tbl2), { data0, data1, index }); + return result; + } + + const auto data0_lookup = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_tbl1), { data0, index }); + const auto data1_index = m_ir->CreateSub(index, llvm::ConstantInt::get(get_type(), 16)); + const auto data1_lookup = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_tbl1), { data1, data1_index }); + + result.value = m_ir->CreateOr(data0_lookup, data1_lookup); + return result; + } + + template + value_t tbx(T1 fallback, T2 a, T3 indices) + { + value_t result; + const auto v_fallback = fallback.eval(m_ir); + const auto data0 = a.eval(m_ir); + const auto index = indices.eval(m_ir); + + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_tbx1), { v_fallback, data0, index }); + return result; + } + + template + value_t tbx2(T1 fallback, T2 a, T3 b, T4 indices) + { + value_t result; + const auto v_fallback = fallback.eval(m_ir); + const auto data0 = a.eval(m_ir); + const auto data1 = b.eval(m_ir); + const auto index = indices.eval(m_ir); + + if (m_use_tbl2) + { + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_tbx2), { v_fallback, data0, data1, index }); + return result; + } + + const auto first_lookup = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_tbx1), { v_fallback, data0, index }); + const auto data1_index = m_ir->CreateSub(index, llvm::ConstantInt::get(get_type(), 16)); + + result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_tbx1), { first_lookup, data1, data1_index }); + return result; + } +#endif + // (m << 3) >= 0 ? a : b template static auto select_by_bit4(T&& m, U&& a, V&& b) diff --git a/rpcs3/Emu/CPU/Hypervisor.h b/rpcs3/Emu/CPU/Hypervisor.h index f40428cc48..c09ac9cdc7 100644 --- a/rpcs3/Emu/CPU/Hypervisor.h +++ b/rpcs3/Emu/CPU/Hypervisor.h @@ -4,7 +4,7 @@ namespace rpcs3 { -#if defined(ARCH_x64) +#if defined(ARCH_X64) union hypervisor_context_t { u64 regs[1]; diff --git a/rpcs3/Emu/CPU/sse2neon.h b/rpcs3/Emu/CPU/sse2neon.h index dfb0b1a0e1..c4436b491b 100644 --- a/rpcs3/Emu/CPU/sse2neon.h +++ b/rpcs3/Emu/CPU/sse2neon.h @@ -81,6 +81,8 @@ #if defined(__GNUC__) || defined(__clang__) #pragma push_macro("FORCE_INLINE") #pragma push_macro("ALIGN_STRUCT") +#undef FORCE_INLINE +#undef ALIGN_STRUCT #define FORCE_INLINE static inline __attribute__((always_inline)) #define ALIGN_STRUCT(x) __attribute__((aligned(x))) #define _sse2neon_likely(x) __builtin_expect(!!(x), 1) diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.cpp b/rpcs3/Emu/Cell/Modules/cellAudio.cpp index caad402194..557c7ef13b 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAudio.cpp @@ -398,7 +398,7 @@ cell_audio_thread::cell_audio_thread(utils::serial& ar) ar(key_count, event_period); - keys.resize(ar); + keys.resize(ar.pop()); for (key_info& k : keys) { @@ -421,7 +421,7 @@ void cell_audio_thread::save(utils::serial& ar) USING_SERIALIZATION_VERSION(cellAudio); ar(key_count, event_period); - ar(keys.size()); + ar(static_cast(keys.size())); for (const key_info& k : keys) { @@ -1019,6 +1019,13 @@ void cell_audio_thread::operator()() // Destroy ringbuffer ringbuffer.reset(); + + // Destroy the audio backend on this thread (the one that created it in cfg.reset()). + // The backend's ctor calls CoInitializeEx here on Windows; releasing it on a different + // thread (g_fxo->clear() runs on the GUI thread during Kill()) would land the matching + // CoUninitialize on the GUI thread, draining its OLE reference and silently breaking the + // main window's file drag&drop. Keep COM init/teardown balanced on this thread. + cfg.backend.reset(); } audio_port* cell_audio_thread::open_port() @@ -1662,36 +1669,35 @@ error_code AudioSetNotifyEventQueue(ppu_thread& ppu, u64 key, u32 iFlags) lv2_sleep(20, &ppu); // Dirty hack for sound: confirm the creation of _mxr000 event queue by _cellsurMixerMain thread - constexpr u64 c_mxr000 = 0x8000cafe0246030; + constexpr u64 c_mxr000 = 0x8000cafe02460300; if (key == c_mxr000 || key == 0) { - bool has_sur_mixer_thread = false; - - for (usz count = 0; !lv2_event_queue::find(c_mxr000) && count < 100; count++) + const bool has_sur_mixer_thread = idm::select>([&](u32 id, named_thread& test_ppu) { - if (has_sur_mixer_thread || idm::select>([&](u32 id, named_thread& test_ppu) + // Confirm thread existence + if (id == ppu.id) { - // Confirm thread existence - if (id == ppu.id) - { - return false; - } - - const auto ptr = test_ppu.ppu_tname.load(); - - if (!ptr) - { - return false; - } - - return *ptr == "_cellsurMixerMain"sv; - }).ret) - { - has_sur_mixer_thread = true; + return false; } - else + + const auto ptr = test_ppu.ppu_tname.load(); + + if (!ptr) { + return false; + } + + return *ptr == "_cellsurMixerMain"sv; + }).ret; + + bool was_mxr000_queue_found = false; + + for (usz count = 0; has_sur_mixer_thread && count < 100; count++) + { + if (lv2_event_queue::find(c_mxr000)) + { + was_mxr000_queue_found = true; break; } @@ -1701,13 +1707,14 @@ error_code AudioSetNotifyEventQueue(ppu_thread& ppu, u64 key, u32 iFlags) return {}; } - cellAudio.error("AudioSetNotifyEventQueue(): Waiting for _mxr000. x%d", count); + (count < 3 ? cellAudio.warning : cellAudio.error)("AudioSetNotifyEventQueue(): Waiting for _mxr000. x%d", count); lv2_sleep(50'000, &ppu); } - if (has_sur_mixer_thread && lv2_event_queue::find(c_mxr000)) + if (key == 0 && was_mxr000_queue_found) { + // Correct key value argument key = c_mxr000; } } diff --git a/rpcs3/Emu/Cell/Modules/cellDmux.cpp b/rpcs3/Emu/Cell/Modules/cellDmux.cpp index 7c2fe6f1a9..20365033fb 100644 --- a/rpcs3/Emu/Cell/Modules/cellDmux.cpp +++ b/rpcs3/Emu/Cell/Modules/cellDmux.cpp @@ -997,7 +997,7 @@ error_code cellDmuxResetEs(ppu_thread& ppu, vm::ptr esHandle) return ret; } - if (dmux_status & DMUX_STOPPED) + if (!(dmux_status & DMUX_STOPPED)) { return CELL_DMUX_ERROR_SEQ; } diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index f9f5ea4100..85a7d91ff8 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -27,39 +27,6 @@ LOG_CHANNEL(cellGem); -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](gem_btn value) - { - switch (value) - { - case gem_btn::start: return "Start"; - case gem_btn::select: return "Select"; - case gem_btn::triangle: return "Triangle"; - case gem_btn::circle: return "Circle"; - case gem_btn::cross: return "Cross"; - case gem_btn::square: return "Square"; - case gem_btn::move: return "Move"; - case gem_btn::t: return "T"; - case gem_btn::x_axis: return "X-Axis"; - case gem_btn::y_axis: return "Y-Axis"; - case gem_btn::combo: return "Combo"; - case gem_btn::combo_start: return "Combo Start"; - case gem_btn::combo_select: return "Combo Select"; - case gem_btn::combo_triangle: return "Combo Triangle"; - case gem_btn::combo_circle: return "Combo Circle"; - case gem_btn::combo_cross: return "Combo Cross"; - case gem_btn::combo_square: return "Combo Square"; - case gem_btn::combo_move: return "Combo Move"; - case gem_btn::combo_t: return "Combo T"; - case gem_btn::count: return "Count"; - } - - return unknown; - }); -} - template <> void fmt_class_string::format(std::string& out, u64 arg) { @@ -234,7 +201,7 @@ public: struct gem_controller { u32 status = CELL_GEM_STATUS_DISCONNECTED; // Connection status (CELL_GEM_STATUS_DISCONNECTED or CELL_GEM_STATUS_READY) - u32 ext_status = CELL_GEM_NO_EXTERNAL_PORT_DEVICE; // External port connection status + u32 ext_status = 0; // External port connection status u32 ext_id = 0; // External device ID (type). For example SHARP_SHOOTER_DEVICE_ID u32 port = 0; // Assigned port bool enabled_magnetometer = true; // Whether the magnetometer is enabled (probably used for additional rotational precision) @@ -254,6 +221,8 @@ public: u64 calibration_start_us{0}; // The start timestamp of the calibration in microseconds u64 calibration_status_flags = 0; // The calibration status flags + u8 firing_mode = button_flags::ss_firing_mode_1; // The firing mode of the sharpshooter. Used with fake PS-Move. This is a physical switch with 3 positions, not a button that can be pressed. + static constexpr u64 calibration_time_us = 500000; // The calibration supposedly takes 0.5 seconds (500000 microseconds) }; @@ -2004,12 +1973,12 @@ static void ds3_input_to_pad(const u32 gem_num, be_t& digital_buttons, be_t return; } - const auto handle_input = [&](gem_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/) + const auto handle_input = [&](const emulated_pad_config::input_value& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { case gem_btn::start: digital_buttons |= CELL_GEM_CTRL_START; @@ -2034,7 +2003,7 @@ static void ds3_input_to_pad(const u32 gem_num, be_t& digital_buttons, be_t break; case gem_btn::t: digital_buttons |= CELL_GEM_CTRL_T; - analog_t = std::max(analog_t, value); + analog_t = std::max(analog_t, value.value); break; default: break; @@ -2060,15 +2029,15 @@ static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr& y_pos = 0; const auto& cfg = ::at32(g_cfg_gem_fake.players, gem_num); - cfg->handle_input(pad, true, [&](gem_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/) + cfg->handle_input(pad, true, [&](const auto& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { - case gem_btn::x_axis: x_pos = value; break; - case gem_btn::y_axis: y_pos = value; break; + case gem_btn::x_axis: x_pos = value.value; break; + case gem_btn::y_axis: y_pos = value.value; break; default: break; } }); @@ -2149,6 +2118,337 @@ static void ps_move_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& co } } +static const std::unordered_map ext_btn_map = +{ + { gem_btn::sharpshooter_firing_mode_1, button_flags::ss_firing_mode_1 }, + { gem_btn::sharpshooter_firing_mode_2, button_flags::ss_firing_mode_2 }, + { gem_btn::sharpshooter_firing_mode_3, button_flags::ss_firing_mode_3 }, + { gem_btn::sharpshooter_trigger, button_flags::ss_trigger }, + { gem_btn::sharpshooter_reload, button_flags::ss_reload }, + { gem_btn::racing_wheel_d_pad_up, CELL_PAD_CTRL_UP }, + { gem_btn::racing_wheel_d_pad_right, CELL_PAD_CTRL_RIGHT }, + { gem_btn::racing_wheel_d_pad_down, CELL_PAD_CTRL_DOWN }, + { gem_btn::racing_wheel_d_pad_left, CELL_PAD_CTRL_LEFT }, + { gem_btn::racing_wheel_throttle, 0 }, + { gem_btn::racing_wheel_l1, CELL_PAD_CTRL_L1 }, + { gem_btn::racing_wheel_r1, CELL_PAD_CTRL_R1 }, + { gem_btn::racing_wheel_l2, 0 }, + { gem_btn::racing_wheel_r2, 0 }, + { gem_btn::racing_wheel_paddle_l, button_flags::rw_paddle_l }, + { gem_btn::racing_wheel_paddle_r, button_flags::rw_paddle_r }, + { gem_btn::combo_sharpshooter_firing_mode_1, button_flags::ss_firing_mode_1 }, + { gem_btn::combo_sharpshooter_firing_mode_2, button_flags::ss_firing_mode_2 }, + { gem_btn::combo_sharpshooter_firing_mode_3, button_flags::ss_firing_mode_3 }, + { gem_btn::combo_sharpshooter_trigger, button_flags::ss_trigger }, + { gem_btn::combo_sharpshooter_reload, button_flags::ss_reload }, + { gem_btn::combo_racing_wheel_d_pad_up, CELL_PAD_CTRL_UP }, + { gem_btn::combo_racing_wheel_d_pad_right, CELL_PAD_CTRL_RIGHT }, + { gem_btn::combo_racing_wheel_d_pad_down, CELL_PAD_CTRL_DOWN }, + { gem_btn::combo_racing_wheel_d_pad_left, CELL_PAD_CTRL_LEFT }, + { gem_btn::combo_racing_wheel_throttle, 0 }, + { gem_btn::combo_racing_wheel_l1, CELL_PAD_CTRL_L1 }, + { gem_btn::combo_racing_wheel_r1, CELL_PAD_CTRL_R1 }, + { gem_btn::combo_racing_wheel_l2, 0 }, + { gem_btn::combo_racing_wheel_r2, 0 }, + { gem_btn::combo_racing_wheel_paddle_l, button_flags::rw_paddle_l }, + { gem_btn::combo_racing_wheel_paddle_r, button_flags::rw_paddle_r }, +}; + +static const std::unordered_map ext_btn_indices = +{ + { gem_btn::racing_wheel_throttle, 0 }, + { gem_btn::racing_wheel_l2, 1 }, + { gem_btn::racing_wheel_r2, 2 }, + { gem_btn::racing_wheel_paddle_l, 3 }, + { gem_btn::racing_wheel_paddle_r, 4 }, + { gem_btn::combo_racing_wheel_throttle, 0 }, + { gem_btn::combo_racing_wheel_l2, 1 }, + { gem_btn::combo_racing_wheel_r2, 2 }, + { gem_btn::combo_racing_wheel_paddle_l, 3 }, + { gem_btn::combo_racing_wheel_paddle_r, 4 }, +}; + +static u32 ext_device_id(gem_ext_id id) +{ + switch (id) + { + case gem_ext_id::disconnected: return 0; + case gem_ext_id::sharpshooter: return SHARP_SHOOTER_DEVICE_ID; + case gem_ext_id::racing_wheel: return RACING_WHEEL_DEVICE_ID; + } + fmt::throw_exception("Mo ID found for id = %d", static_cast(id)); +} + +template +static void input_to_ext(u32 external_device_id, CellGemExtPortData& ext, std::set& combos, const emulated_pad_config::input_value& value) +{ + if (!value.pressed) + return; + + const auto set_firing_mode = [&ext](gem_btn btn) + { + // The firing mode is exclusive + ext.custom[0] &= ~button_flags::ss_firing_mode_mask; + ext.custom[0] |= ::at32(ext_btn_map, btn); + }; + + if constexpr (has_combo && is_combo) + { + if (external_device_id == SHARP_SHOOTER_DEVICE_ID) + { + switch (value.btn) + { + case gem_btn::combo_sharpshooter_firing_mode_1: + case gem_btn::combo_sharpshooter_firing_mode_2: + case gem_btn::combo_sharpshooter_firing_mode_3: + set_firing_mode(value.btn); + combos.insert(value.pad_btn); + break; + case gem_btn::combo_sharpshooter_trigger: + case gem_btn::combo_sharpshooter_reload: + ext.custom[0] |= ::at32(ext_btn_map, value.btn); + combos.insert(value.pad_btn); + break; + default: + break; + } + } + else if (external_device_id == RACING_WHEEL_DEVICE_ID) + { + switch (value.btn) + { + case gem_btn::combo_racing_wheel_throttle: + case gem_btn::combo_racing_wheel_l2: + case gem_btn::combo_racing_wheel_r2: + ext.custom[::at32(ext_btn_indices, value.btn)] = static_cast(value.value); + combos.insert(value.pad_btn); + break; + case gem_btn::combo_racing_wheel_paddle_l: + case gem_btn::combo_racing_wheel_paddle_r: + ext.custom[::at32(ext_btn_indices, value.btn)] |= ::at32(ext_btn_map, value.btn); + combos.insert(value.pad_btn); + break; + case gem_btn::combo_racing_wheel_d_pad_up: + case gem_btn::combo_racing_wheel_d_pad_right: + case gem_btn::combo_racing_wheel_d_pad_down: + case gem_btn::combo_racing_wheel_d_pad_left: + ext.digital1 |= ::at32(ext_btn_map, value.btn); + combos.insert(value.pad_btn); + break; + case gem_btn::combo_racing_wheel_l1: + case gem_btn::combo_racing_wheel_r1: + ext.digital2 |= ::at32(ext_btn_map, value.btn); + combos.insert(value.pad_btn); + break; + default: + break; + } + } + } + else + { + if constexpr (has_combo) + { + if (combos.contains(value.pad_btn)) + { + return; + } + } + + if (external_device_id == SHARP_SHOOTER_DEVICE_ID) + { + switch (value.btn) + { + case gem_btn::sharpshooter_firing_mode_1: + case gem_btn::sharpshooter_firing_mode_2: + case gem_btn::sharpshooter_firing_mode_3: + set_firing_mode(value.btn); + break; + case gem_btn::sharpshooter_trigger: + case gem_btn::sharpshooter_reload: + ext.custom[0] |= ::at32(ext_btn_map, value.btn); + break; + default: + break; + } + } + else if (external_device_id == RACING_WHEEL_DEVICE_ID) + { + switch (value.btn) + { + case gem_btn::combo_racing_wheel_throttle: + case gem_btn::combo_racing_wheel_l2: + case gem_btn::combo_racing_wheel_r2: + ext.custom[::at32(ext_btn_indices, value.btn)] = static_cast(value.value); + break; + case gem_btn::combo_racing_wheel_paddle_l: + case gem_btn::combo_racing_wheel_paddle_r: + ext.custom[::at32(ext_btn_indices, value.btn)] |= ::at32(ext_btn_map, value.btn); + break; + case gem_btn::combo_racing_wheel_d_pad_up: + case gem_btn::combo_racing_wheel_d_pad_right: + case gem_btn::combo_racing_wheel_d_pad_down: + case gem_btn::combo_racing_wheel_d_pad_left: + ext.digital1 |= ::at32(ext_btn_map, value.btn); + break; + case gem_btn::combo_racing_wheel_l1: + case gem_btn::combo_racing_wheel_r1: + ext.digital2 |= ::at32(ext_btn_map, value.btn); + break; + default: + break; + } + } + } +} + +static void real_input_to_ext(u32 gem_num, gem_config::gem_controller& controller, CellGemExtPortData& ext) +{ + std::lock_guard lock(pad::g_pad_mutex); + + const auto handler = pad::get_pad_thread(); + const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); + + if (!pad->is_connected() || pad->is_copilot()) + { + controller.ext_status = 0; + controller.ext_id = 0; + return; + } + + ps_move_data& move_data = pad->move_data; + + controller.ext_status = move_data.external_device_connected ? CELL_GEM_EXT_CONNECTED : 0; // TODO: | CELL_GEM_EXT_EXT0 | CELL_GEM_EXT_EXT1 + controller.ext_id = move_data.external_device_connected ? move_data.external_device_id : 0; + + if (!move_data.external_device_connected) + { + return; + } + + // TODO: + // ext.analog_left_x + // ext.analog_left_y + // ext.analog_right_x + // ext.analog_right_y + // ext.digital1 + // ext.digital2 + + ext.status = controller.ext_status; + std::memcpy(ext.custom, move_data.external_device_data.data(), 5); +} + +static void fake_input_to_ext(u32 gem_num, gem_config::gem_controller& controller, CellGemExtPortData& ext) +{ + std::lock_guard lock(pad::g_pad_mutex); + + const auto handler = pad::get_pad_thread(); + const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); + + if (!pad->is_connected() || pad->is_copilot()) + { + controller.ext_status = 0; + controller.ext_id = 0; + return; + } + + ps_move_data& move_data = pad->move_data; + const auto& cfg = ::at32(g_cfg_gem_fake.players, gem_num); + + move_data.external_device_id = ext_device_id(cfg->external_device); + move_data.external_device_connected = move_data.external_device_id != 0; + move_data.external_device_data = {}; + + controller.ext_status = move_data.external_device_connected ? CELL_GEM_EXT_CONNECTED : 0; // TODO: | CELL_GEM_EXT_EXT0 | CELL_GEM_EXT_EXT1 + controller.ext_id = move_data.external_device_connected ? move_data.external_device_id : 0; + + if (!move_data.external_device_connected) + { + return; + } + + // Restore firing mode + ext.custom[0] = controller.firing_mode; + + cfg->handle_input(pad, true, [&move_data, &ext](const auto& value, bool& /*abort*/) + { + static std::set s_combos = {}; + input_to_ext(move_data.external_device_id, ext, s_combos, value); + }); + + ext.status = controller.ext_status; + + // Save firing mode + controller.firing_mode = ext.custom[0] & button_flags::ss_firing_mode_mask; +} + +static void mouse_input_to_ext(u32 mouse_no, gem_config::gem_controller& controller, CellGemExtPortData& ext) +{ + auto& handler = g_fxo->get(); + + std::scoped_lock lock(handler.mutex); + + // Make sure that the mouse handler is initialized + handler.Init(std::min(g_fxo->get().attribute.max_connect, CELL_GEM_MAX_NUM)); + + if (mouse_no >= handler.GetMice().size()) + { + controller.ext_status = 0; + controller.ext_id = 0; + return; + } + + const Mouse& mouse_data = ::at32(handler.GetMice(), mouse_no); + const auto& cfg = ::at32(g_cfg_gem_mouse.players, mouse_no); + + const u32 external_device_id = ext_device_id(cfg->external_device); + const bool external_device_connected = external_device_id != 0; + + controller.ext_status = external_device_connected ? CELL_GEM_EXT_CONNECTED : 0; // TODO: | CELL_GEM_EXT_EXT0 | CELL_GEM_EXT_EXT1 + controller.ext_id = external_device_connected ? external_device_id : 0; + + if (!external_device_connected) + { + return; + } + + // Restore firing mode + ext.custom[0] = controller.firing_mode; + + bool combo_active = false; + std::set combos; + + // Check combo button first + cfg->handle_input(mouse_data, [&combo_active](const auto& value, bool& abort) + { + if (value.pressed && value.btn == gem_btn::combo) + { + combo_active = true; + abort = true; + } + }); + + // Check combos + if (combo_active) + { + cfg->handle_input(mouse_data, [external_device_id, &ext, &combos](const auto& value, bool& /*abort*/) + { + input_to_ext(external_device_id, ext, combos, value); + }); + } + + // Check normal buttons + cfg->handle_input(mouse_data, [external_device_id, &ext, &combos](const auto& value, bool& /*abort*/) + { + input_to_ext(external_device_id, ext, combos, value); + }); + + ext.status = controller.ext_status; + + // Save firing mode + controller.firing_mode = ext.custom[0] & button_flags::ss_firing_mode_mask; +} + /** * \brief Maps external Move controller data to DS3 input. (This can be input from any physical pad, not just the DS3) * Implementation detail: CellGemExtPortData's digital/analog fields map the same way as @@ -2157,7 +2457,7 @@ static void ps_move_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& co * \param ext External data to modify * \return true on success, false if controller is disconnected */ -static void ds3_input_to_ext(u32 gem_num, gem_config::gem_controller& controller, CellGemExtPortData& ext) +static void get_external_device_input(u32 gem_num, gem_config::gem_controller& controller, CellGemExtPortData& ext) { ext = {}; @@ -2166,53 +2466,6 @@ static void ds3_input_to_ext(u32 gem_num, gem_config::gem_controller& controller return; } - std::lock_guard lock(pad::g_pad_mutex); - - const auto handler = pad::get_pad_thread(); - const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); - - if (!pad->is_connected() || pad->is_copilot()) - { - return; - } - - const auto& move_data = pad->move_data; - - controller.ext_status = move_data.external_device_connected ? CELL_GEM_EXT_CONNECTED : 0; // TODO: | CELL_GEM_EXT_EXT0 | CELL_GEM_EXT_EXT1 - controller.ext_id = move_data.external_device_connected ? move_data.external_device_id : 0; - - ext.status = controller.ext_status; - - for (const AnalogStick& stick : pad->m_sticks_external) - { - switch (stick.m_offset) - { - case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X: ext.analog_left_x = stick.m_value; break; - case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y: ext.analog_left_y = stick.m_value; break; - case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X: ext.analog_right_x = stick.m_value; break; - case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y: ext.analog_right_y = stick.m_value; break; - default: break; - } - } - - for (const Button& button : pad->m_buttons_external) - { - if (!button.m_pressed) - continue; - - switch (button.m_offset) - { - case CELL_PAD_BTN_OFFSET_DIGITAL1: ext.digital1 |= button.m_outKeyCode; break; - case CELL_PAD_BTN_OFFSET_DIGITAL2: ext.digital2 |= button.m_outKeyCode; break; - default: break; - } - } - - if (!move_data.external_device_connected) - { - return; - } - // The sharpshooter only sets the custom bytes as follows: // custom[0] (0x01): Firing mode selector is in position 1. // custom[0] (0x02): Firing mode selector is in position 2. @@ -2227,7 +2480,21 @@ static void ds3_input_to_ext(u32 gem_num, gem_config::gem_controller& controller // custom[3] (0x01): Left paddle // custom[3] (0x02): Right paddle - std::memcpy(ext.custom, move_data.external_device_data.data(), 5); + switch (g_cfg.io.move) + { + case move_handler::real: + real_input_to_ext(gem_num, controller, ext); + break; + case move_handler::fake: + fake_input_to_ext(gem_num, controller, ext); + break; + case move_handler::mouse: + case move_handler::raw_mouse: + mouse_input_to_ext(gem_num, controller, ext); + break; + default: + break; + } } /** @@ -2260,7 +2527,7 @@ static bool mouse_input_to_pad(u32 mouse_no, be_t& digital_buttons, be_t combos; @@ -2286,9 +2553,9 @@ static bool mouse_input_to_pad(u32 mouse_no, be_t& digital_buttons, be_thandle_input(mouse_data, [&combo_active](gem_btn btn, pad_button /*pad_btn*/, u16 /*value*/, bool pressed, bool& abort) + cfg->handle_input(mouse_data, [&combo_active](const auto& value, bool& abort) { - if (pressed && btn == gem_btn::combo) + if (value.pressed && value.btn == gem_btn::combo) { combo_active = true; abort = true; @@ -2298,12 +2565,12 @@ static bool mouse_input_to_pad(u32 mouse_no, be_t& digital_buttons, be_thandle_input(mouse_data, [&digital_buttons, &combos](gem_btn btn, pad_button pad_btn, u16 /*value*/, bool pressed, bool& /*abort*/) + cfg->handle_input(mouse_data, [&digital_buttons, &combos](const auto& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { case gem_btn::combo_start: case gem_btn::combo_select: @@ -2313,8 +2580,8 @@ static bool mouse_input_to_pad(u32 mouse_no, be_t& digital_buttons, be_t& digital_buttons, be_thandle_input(mouse_data, [&digital_buttons, &combos](gem_btn btn, pad_button pad_btn, u16 /*value*/, bool pressed, bool& /*abort*/) + cfg->handle_input(mouse_data, [&digital_buttons, &combos](const auto& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { case gem_btn::start: case gem_btn::select: @@ -2339,9 +2606,9 @@ static bool mouse_input_to_pad(u32 mouse_no, be_t& digital_buttons, be_text); + get_external_device_input(gem_num, gem.controllers[gem_num], inertial_state->ext); inertial_state->timestamp = (get_guest_system_time() - gem.start_timestamp_us); inertial_state->counter = gem.inertial_counter++; @@ -3192,7 +3459,7 @@ error_code cellGemGetState(u32 gem_num, u32 flag, u64 time_parameter, vm::ptrext); + get_external_device_input(gem_num, controller, gem_state->ext); if (controller.enabled_tracking) { @@ -3609,7 +3876,7 @@ error_code cellGemReadExternalPortDeviceInfo(u32 gem_num, vm::ptr ext_id, v { // Get external device status CellGemExtPortData ext_port_data{}; - ds3_input_to_ext(gem_num, controller, ext_port_data); + get_external_device_input(gem_num, controller, ext_port_data); } if (!(controller.ext_status & CELL_GEM_EXT_CONNECTED)) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.h b/rpcs3/Emu/Cell/Modules/cellGem.h index 7dfdcb3b14..383527d221 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.h +++ b/rpcs3/Emu/Cell/Modules/cellGem.h @@ -278,3 +278,38 @@ struct CellGemVideoConvertAttribute ENABLE_BITWISE_SERIALIZATION; }; + +namespace +{ + enum button_flags : u16 + { + select = 0x01, + start = 0x08, + triangle = 0x10, + circle = 0x20, + cross = 0x40, + square = 0x80, + ps = 0x0001, + move = 0x4008, + t = 0x8010, + ext_dev = 0x1000, + + // Sharpshooter + ss_firing_mode_1 = 0x01, + ss_firing_mode_2 = 0x02, + ss_firing_mode_3 = 0x04, + ss_firing_mode_mask = ss_firing_mode_1 | ss_firing_mode_2 | ss_firing_mode_3, + ss_trigger = 0x40, + ss_reload = 0x80, + + // Racing Wheel + rw_d_pad_up = 0x10, + rw_d_pad_right = 0x20, + rw_d_pad_down = 0x40, + rw_d_pad_left = 0x80, + rw_l1 = 0x04, + rw_r1 = 0x08, + rw_paddle_l = 0x01, + rw_paddle_r = 0x02, + }; +} diff --git a/rpcs3/Emu/Cell/Modules/cellGifDec.cpp b/rpcs3/Emu/Cell/Modules/cellGifDec.cpp index faf8defa6d..03566183e6 100644 --- a/rpcs3/Emu/Cell/Modules/cellGifDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGifDec.cpp @@ -289,8 +289,8 @@ error_code cellGifDecReadHeader(vm::ptr mainHandle, vm::ptr>(buffer + 0) != 0x47494638u || - (read_from_ptr>(buffer + 4) != 0x6139u && read_from_ptr>(buffer + 4) != 0x6137u)) // Error: The first 6 bytes are not a valid GIF signature + if (read_from_ptr>(buffer, 0) != 0x47494638u || + (read_from_ptr>(buffer, 4) != 0x6139u && read_from_ptr>(buffer, 4) != 0x6137u)) // Error: The first 6 bytes are not a valid GIF signature { return CELL_GIFDEC_ERROR_STREAM_FORMAT; // Surprisingly there is no error code related with headerss } diff --git a/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp b/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp index 5123ec68cf..a6a656e698 100644 --- a/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp @@ -125,26 +125,26 @@ error_code cellJpgDecReadHeader(u32 mainHandle, u32 subHandle, vm::ptrinfo; // Write the header to buffer - std::unique_ptr buffer(new u8[fileSize]); + std::vector buffer(fileSize); switch (subHandle_data->src.srcSelect) { case CELL_JPGDEC_BUFFER: - std::memcpy(buffer.get(), vm::base(subHandle_data->src.streamPtr), fileSize); + std::memcpy(buffer.data(), vm::base(subHandle_data->src.streamPtr), fileSize); break; case CELL_JPGDEC_FILE: { auto file = idm::get_unlocked(fd); file->file.seek(0); - file->file.read(buffer.get(), fileSize); + file->file.read(buffer.data(), fileSize); break; } default: break; // TODO } - if (read_from_ptr>(buffer.get() + 0) != 0xE0FFD8FF || // Error: Not a valid SOI header - read_from_ptr(buffer.get() + 6) != "JFIF"_u32) // Error: Not a valid JFIF string + if (read_from_ptr>(buffer, 0) != 0xE0FFD8FF || // Error: Not a valid SOI header + read_from_ptr(buffer, 6) != "JFIF"_u32) // Error: Not a valid JFIF string { return CELL_JPGDEC_ERROR_HEADER; } @@ -154,7 +154,7 @@ error_code cellJpgDecReadHeader(u32 mainHandle, u32 subHandle, vm::ptr= fileSize) return CELL_JPGDEC_ERROR_HEADER; - u16 block_length = buffer[i] * 0xFF + buffer[i + 1]; + u16 block_length = ::at32(buffer, i) * 0xFF + ::at32(buffer, i + 1); while (true) { @@ -165,15 +165,16 @@ error_code cellJpgDecReadHeader(u32 mainHandle, u32 subHandle, vm::ptr data) data->keycode[i] = current_data.buttons[i].m_keyCode; } - KbConfig& current_config = consumer.GetConfig(port_no); + const KbConfig& current_config = consumer.GetConfig(port_no); // For single character mode to work properly we need to "flush" the buffer after reading or else we'll constantly get the same key presses with each call. // Actual key repeats are handled by adding a new key code to the buffer periodically. Key releases are handled in a similar fashion. diff --git a/rpcs3/Emu/Cell/Modules/cellL10n.cpp b/rpcs3/Emu/Cell/Modules/cellL10n.cpp index 0f47c682e6..60fe8f3583 100644 --- a/rpcs3/Emu/Cell/Modules/cellL10n.cpp +++ b/rpcs3/Emu/Cell/Modules/cellL10n.cpp @@ -1,6 +1,5 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Memory/vm_ref.h" #ifdef _WIN32 #include diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.cpp b/rpcs3/Emu/Cell/Modules/cellMusic.cpp index c23bf274b0..e02d769cda 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusic.cpp @@ -66,6 +66,7 @@ void fmt_class_string::format(std::string& out, u64 arg) struct music_state { +public: shared_mutex mutex; vm::ptr param, vm::ptr userData)> func{}; @@ -79,33 +80,58 @@ struct music_state music_state() { handler = Emu.GetCallbacks().get_music_handler(); - handler->set_status_callback([this](music_handler_base::player_status status) + handler->set_event_status_callback([this](u32 status) { - // TODO: disabled until I find a game that uses CELL_MUSIC_EVENT_STATUS_NOTIFICATION - return; - if (!func) { return; } - s32 result = CELL_OK; - + // Known to be used by NFS: Hot Pursuit + sysutil_register_cb([this, state = status](ppu_thread& ppu) -> s32 + { + cellMusic.notice("Sending status notification %d", state); + func(ppu, CELL_MUSIC_EVENT_STATUS_NOTIFICATION, vm::addr_t(state), userData); + return CELL_OK; + }); + }); + handler->set_playback_status_callback([this](music_handler_base::player_status status) + { switch (status) { case music_handler_base::player_status::end_of_media: - result = CELL_MUSIC_PLAYBACK_FINISHED; + // Let's just play the next song for now if we are in list mode. + // Due to potential main thread recursion this may cause a deadlock with the internal mutex of the handler. + // Let's just call it from another thread instead. + m_wake_up_thread = 1; + m_wake_up_thread.notify_one(); break; default: return; } + }); - sysutil_register_cb([this, &result](ppu_thread& ppu) -> s32 + m_thread = std::make_unique>>("cellMusic State", [this]() + { + while (thread_ctrl::state() != thread_state::aborting) { - cellMusic.notice("Sending status notification %d", result); - func(ppu, CELL_MUSIC_EVENT_STATUS_NOTIFICATION, vm::addr_t(result), userData); - return CELL_OK; - }); + while (thread_ctrl::state() != thread_state::aborting && !m_wake_up_thread) + { + thread_ctrl::wait_on(m_wake_up_thread, 0); + } + m_wake_up_thread = 0; + + if (thread_ctrl::state() == thread_state::aborting) + { + return; + } + + // Play the next song + if (const error_code error = set_playback_command(CELL_MUSIC_PB_CMD_NEXT_TRACK)) + { + cellMusic.error("Failed to play next track. error=0x%x", +error); + } + } }); } @@ -115,6 +141,19 @@ struct music_state save(ar); } + ~music_state() + { + if (m_thread) + { + auto& thread = *m_thread; + thread = thread_state::aborting; + m_wake_up_thread = 1; + m_wake_up_thread.notify_one(); + thread(); + m_thread.reset(); + } + } + void save(utils::serial& ar) { ar(func); @@ -130,7 +169,7 @@ struct music_state } // NOTE: This function only uses CELL_MUSIC enums. CELL_MUSIC2 enums are identical. - error_code set_playback_command(s32 command) + error_code set_playback_command(u32 command) { switch (command) { @@ -145,11 +184,27 @@ struct music_state case CELL_MUSIC_PB_CMD_FASTREVERSE: case CELL_MUSIC_PB_CMD_NEXT: case CELL_MUSIC_PB_CMD_PREV: + case CELL_MUSIC_PB_CMD_NEXT_TRACK: { std::string path; + bool automatic = false; bool no_more_tracks = false; { std::lock_guard lock(mtx); + + // Handle auto-play of the next track in the current playlist. + if (command == CELL_MUSIC_PB_CMD_NEXT_TRACK) + { + // We only auto-play the next song if we are in list mode and the music is playing. + if (current_selection_context.content_type != CELL_SEARCH_CONTENTTYPE_MUSICLIST || handler->get_state() != CELL_MUSIC_PB_STATUS_PLAY) + { + return CELL_OK; + } + + command = CELL_MUSIC_PB_CMD_NEXT; + automatic = true; + } + const std::vector& playlist = current_selection_context.playlist; const u32 current_track = current_selection_context.current_track; u32 next_track = current_track; @@ -191,7 +246,7 @@ struct music_state handler->fast_reverse(path); break; default: - handler->play(path); + handler->play(path, automatic); break; } @@ -203,6 +258,10 @@ struct music_state return CELL_OK; } + +private: + std::unique_ptr>> m_thread; + atomic_t m_wake_up_thread{0}; }; error_code cell_music_select_contents() diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.h b/rpcs3/Emu/Cell/Modules/cellMusic.h index a98b305011..f677a99287 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.h +++ b/rpcs3/Emu/Cell/Modules/cellMusic.h @@ -90,6 +90,8 @@ enum CELL_MUSIC_PB_CMD_PREV = 4, CELL_MUSIC_PB_CMD_FASTFORWARD = 5, CELL_MUSIC_PB_CMD_FASTREVERSE = 6, + + CELL_MUSIC_PB_CMD_NEXT_TRACK = 7, // RPCS3 helper for auto-play of the next track in the current playlist }; enum diff --git a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp index b7f21b90ad..78688c751c 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp @@ -74,6 +74,7 @@ struct music_decode cellMusicDecode.notice("set_decode_command(START): context: %s", current_selection_context.to_string()); music_selection_context context = current_selection_context; + context.current_track = context.first_track; for (usz i = 0; i < context.playlist.size(); i++) { @@ -90,6 +91,7 @@ struct music_decode case CELL_MUSIC_DECODE_CMD_PREV: { decoder.stop(); + read_pos = 0; if (decoder.set_next_index(command == CELL_MUSIC_DECODE_CMD_NEXT) == umax) { @@ -195,9 +197,10 @@ error_code cell_music_decode_read(vm::ptr buf, vm::ptr startTime, u64 if (dec.decoder.m_size == 0) { - return CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA; + return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "m_size == 0" }; } + ensure(dec.decoder.m_size >= dec.read_pos); const u64 size_left = dec.decoder.m_size - dec.read_pos; if (dec.read_pos == 0) @@ -229,10 +232,10 @@ error_code cell_music_decode_read(vm::ptr buf, vm::ptr startTime, u64 if (size_to_read == 0) { - return CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA; // TODO: speculative + return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "size_to_read == 0" }; // TODO: speculative } - std::memcpy(buf.get_ptr(), &dec.decoder.data[dec.read_pos], size_to_read); + std::memcpy(buf.get_ptr(), &::at32(dec.decoder.data, dec.read_pos), size_to_read); if (size_to_read < reqSize) { @@ -384,6 +387,12 @@ error_code cellMusicDecodeSetDecodeCommand(s32 command) return CELL_OK; }); + //sysutil_register_cb([&dec, command](ppu_thread& ppu) -> s32 + //{ + // dec.func(ppu, CELL_MUSIC_DECODE_EVENT_STATUS_NOTIFICATION, vm::addr_t(command), dec.userData); + // return CELL_OK; + //}); + return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/cellMusicExport.cpp b/rpcs3/Emu/Cell/Modules/cellMusicExport.cpp index c01d4b7a51..0d90ab9afd 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicExport.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicExport.cpp @@ -72,7 +72,7 @@ struct music_export }; -bool check_music_path(const std::string& file_path) +bool check_music_path(std::string_view file_path) { if (file_path.size() >= CELL_MUSIC_EXPORT_UTIL_HDD_PATH_MAX) { diff --git a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp index ae578d2329..6ce57e294f 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp @@ -267,9 +267,10 @@ void music_selection_context::set_track(std::string_view track) for (usz i = 0; i < playlist.size(); i++) { - cellMusicSelectionContext.error("set_track: Comparing track '%s' vs '%s'", track, playlist[i]); + cellMusicSelectionContext.notice("set_track: Comparing track '%s' vs '%s'", track, playlist[i]); if (track.ends_with(playlist[i])) { + cellMusicSelectionContext.notice("set_track: Found track '%s': '%s'", track, playlist[i]); first_track = current_track = static_cast(i); return; } @@ -287,6 +288,8 @@ u32 music_selection_context::step_track(bool next) return umax; } + const std::string last_track = (current_track < playlist.size()) ? playlist[current_track] : ""; + switch (repeat_mode) { case CELL_SEARCH_REPEATMODE_NONE: @@ -298,7 +301,7 @@ u32 music_selection_context::step_track(bool next) { // We are at the end of the playlist. cellMusicSelectionContext.notice("step_track: No more tracks to play in playlist..."); - current_track = umax; + current_track = ::size32(playlist) - 1; // NOTE: We could use size instead of size - 1 to allow to use PREV to play the last track again. return umax; } } @@ -309,7 +312,6 @@ u32 music_selection_context::step_track(bool next) { // We are at the start of the playlist. cellMusicSelectionContext.notice("step_track: No more tracks to play in playlist..."); - current_track = umax; return umax; } @@ -334,7 +336,7 @@ u32 music_selection_context::step_track(bool next) // Play the previous track. Start with the last track if we reached the start of the playlist. if (current_track == 0) { - current_track = ::narrow(playlist.size() - 1); + current_track = ::size32(playlist) - 1; } else { @@ -362,8 +364,15 @@ u32 music_selection_context::step_track(bool next) { // We reached the first or last track again. Let's shuffle! cellMusicSelectionContext.notice("step_track: Shuffling playlist..."); - auto engine = std::default_random_engine{}; + std::random_device rd; + auto engine = std::default_random_engine{rd()}; std::shuffle(std::begin(playlist), std::end(playlist), engine); + + // Don't play the same track twice + if (last_track == ::at32(playlist, current_track)) + { + current_track = (current_track + 1) % playlist.size(); + } } } diff --git a/rpcs3/Emu/Cell/Modules/cellPad.cpp b/rpcs3/Emu/Cell/Modules/cellPad.cpp index 9c8e05f74f..da69da4343 100644 --- a/rpcs3/Emu/Cell/Modules/cellPad.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPad.cpp @@ -56,9 +56,9 @@ void fmt_class_string::format(std::string& out, u64 arg) extern void sys_io_serialize(utils::serial& ar); pad_info::pad_info(utils::serial& ar) - : max_connect(ar) - , port_setting(ar) - , reported_info(ar) + : max_connect(ar.pop()) + , port_setting(ar.pop()) + , reported_info(ar.pop()) { //reported_info = {}; sys_io_serialize(ar); @@ -418,7 +418,7 @@ void pad_get_data(u32 port_no, CellPadData* data, bool get_periph_data = false) } }; - for (const Button& button : pad->m_buttons_external) + for (const ButtonExternal& button : pad->m_buttons_external) { // here we check btns, and set pad accordingly, // if something changed, set btnChanged @@ -497,7 +497,7 @@ void pad_get_data(u32 port_no, CellPadData* data, bool get_periph_data = false) } } - for (const AnalogStick& stick : pad->m_sticks_external) + for (const AnalogStickExternal& stick : pad->m_sticks_external) { switch (stick.m_offset) { diff --git a/rpcs3/Emu/Cell/Modules/cellPamf.cpp b/rpcs3/Emu/Cell/Modules/cellPamf.cpp index 4bb383c2d2..314b646a1f 100644 --- a/rpcs3/Emu/Cell/Modules/cellPamf.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPamf.cpp @@ -1,8 +1,8 @@ #include "stdafx.h" #include "Emu/System.h" #include "Emu/Cell/PPUModule.h" +#include "util/bit_set.hpp" -#include #include "cellPamf.h" LOG_CHANNEL(cellPamf); @@ -500,7 +500,7 @@ error_code pamfVerify(vm::cptr pAddr, u64 fileSize, vm::ptrseq_info.grouping_periods.groups.streams; - std::bitset<16> channels_used[6]{}; + std::array, 6> channels_used{}; u32 end_of_streams_addr = 0; u32 next_ep_table_addr = 0; @@ -516,14 +516,16 @@ error_code pamfVerify(vm::cptr pAddr, u64 fileSize, vm::ptr& used_channels = ::at32(channels_used, *type); + // Every channel may only be used once per type - if (channels_used[*type].test(*ch)) + if (used_channels.test(*ch)) { return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid channel" }; } // Mark channel as used - channels_used[*type].set(*ch); + used_channels.set(*ch, true); const u32 ep_offset = streams[stream_idx].ep_offset; const u32 ep_num = streams[stream_idx].ep_num; diff --git a/rpcs3/Emu/Cell/Modules/cellPhotoExport.cpp b/rpcs3/Emu/Cell/Modules/cellPhotoExport.cpp index 473bd435e7..541879153f 100644 --- a/rpcs3/Emu/Cell/Modules/cellPhotoExport.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPhotoExport.cpp @@ -74,7 +74,7 @@ struct photo_export }; -bool check_photo_path(const std::string& file_path) +bool check_photo_path(std::string_view file_path) { if (file_path.size() >= CELL_PHOTO_EXPORT_UTIL_HDD_PATH_MAX) { diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index 7878e86642..22086bdf5c 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -1155,15 +1155,16 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v } } - auto delete_save = [&]() + const auto delete_save = [&]() { - strcpy_trunc(doneGet->dirName, save_entries[selected].dirName); + const SaveDataEntry& entry = ::at32(save_entries, selected); + strcpy_trunc(doneGet->dirName, entry.dirName); doneGet->hddFreeSizeKB = 40 * 1024 * 1024 - 256; // Read explanation in cellHddGameCheck doneGet->excResult = CELL_OK; std::memset(doneGet->reserved, 0, sizeof(doneGet->reserved)); - const std::string old_path = base_dir + ".backup_" + save_entries[selected].escaped + "/"; - const std::string del_path = base_dir + save_entries[selected].escaped + "/"; + const std::string old_path = base_dir + ".backup_" + entry.escaped + "/"; + const std::string del_path = base_dir + entry.escaped + "/"; const fs::dir _dir(del_path); u64 size_bytes = 0; @@ -1456,7 +1457,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v } else { - fmt::throw_exception("Invalid savedata selected"); + fmt::throw_exception("Invalid savedata selected (selected=%d)", selected); } } } diff --git a/rpcs3/Emu/Cell/Modules/cellSearch.cpp b/rpcs3/Emu/Cell/Modules/cellSearch.cpp index bcc0151764..f86584d7c5 100644 --- a/rpcs3/Emu/Cell/Modules/cellSearch.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSearch.cpp @@ -1711,12 +1711,6 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrcontent_ids.begin(), searchObject->content_ids.end(), [&content_hash](const content_id_type& cid){ return cid.first == content_hash; }); if (content != searchObject->content_ids.cend() && content->second) { - // Check if the type of the found content is correct - if (content->second->type != CELL_SEARCH_CONTENTTYPE_MUSIC) - { - return { CELL_SEARCH_ERROR_INVALID_CONTENTTYPE, "Type: %d, Expected: CELL_SEARCH_CONTENTTYPE_MUSIC"}; - } - // Check if the type of the found content matches our search content type if (content->second->type != first_content->type) { @@ -1724,8 +1718,36 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrsecond->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.playlist.back()); + if (content->second->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) + { + const std::string path = content->second->infoPath.contentPath; + const std::string vfs_path = vfs::get(path); + if (!fs::is_dir(vfs_path)) + { + return { CELL_SEARCH_ERROR_CONTENT_NOT_FOUND, "Not a directory: Path='%s'", vfs_path }; + } + + for (auto&& dir_entry : fs::dir{vfs_path}) + { + if (dir_entry.name == "." || dir_entry.name == "..") + { + continue; + } + + std::string track = path + "/" + dir_entry.name; + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path='%s'", content_hash, +content->second->type, track); + context.playlist.push_back(std::move(track)); + } + } + else if (content->second->type == CELL_SEARCH_CONTENTTYPE_MUSIC) + { + context.playlist.push_back(content->second->infoPath.contentPath); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path='%s'", content_hash, +content->second->type, context.playlist.back()); + } + else + { + return { CELL_SEARCH_ERROR_INVALID_CONTENTTYPE, "Type: %d, Expected: CELL_SEARCH_CONTENTTYPE_MUSIC or CELL_SEARCH_CONTENTTYPE_MUSICLIST", +content->second->type }; + } } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) { @@ -1738,14 +1760,14 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr content = get_random_content(); context.playlist.push_back(content->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path=%s", content_hash, +content->type, context.playlist.back()); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path='%s'", content_hash, +content->type, context.playlist.back()); } else { // Select the first track by default // TODO: whole playlist context.playlist.push_back(first_content->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.playlist.back()); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path='%s'", content_hash, +first_content->type, context.playlist.back()); } } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) @@ -1759,14 +1781,14 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr content = get_random_content(); context.playlist.push_back(content->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path=%s", +content->type, context.playlist.back()); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path='%s'", +content->type, context.playlist.back()); } else { // Select the first track by default // TODO: whole playlist context.playlist.push_back(first_content->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.playlist.back()); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path='%s'", +first_content->type, context.playlist.back()); } context.content_type = first_content->type; diff --git a/rpcs3/Emu/Cell/Modules/cellSsl.cpp b/rpcs3/Emu/Cell/Modules/cellSsl.cpp index d7f32a5636..e515c2e69b 100644 --- a/rpcs3/Emu/Cell/Modules/cellSsl.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSsl.cpp @@ -1,6 +1,5 @@ #include "stdafx.h" -#include #include #include "cellSsl.h" @@ -8,6 +7,7 @@ #include "Utilities/File.h" #include "Emu/VFS.h" #include "Emu/IdManager.h" +#include "util/bit_set.hpp" #include "cellRtc.h" @@ -75,7 +75,7 @@ error_code cellSslGetMemoryInfo() return CELL_OK; } -std::string getCert(const std::string& certPath, const int certID, const bool isNormalCert) +std::string getCert(std::string_view certPath, const int certID, const bool isNormalCert) { int newID = certID; @@ -92,7 +92,7 @@ std::string getCert(const std::string& certPath, const int certID, const bool is newID = certID - 1; } - std::string filePath = fmt::format("%sCA%02d.cer", certPath, newID); + const std::string filePath = fmt::format("%sCA%02d.cer", certPath, newID); if (!fs::exists(filePath)) { @@ -106,7 +106,7 @@ error_code cellSslCertificateLoader(u64 flag, vm::ptr buffer, u32 size, vm { cellSsl.trace("cellSslCertificateLoader(flag=%llu, buffer=*0x%x, size=%zu, required=*0x%x)", flag, buffer, size, required); - const std::bitset<58> flagBits(flag); + const bit_set<58> flagBits(flag); const std::string certPath = vfs::get("/dev_flash/data/cert/"); if (required) @@ -114,10 +114,11 @@ error_code cellSslCertificateLoader(u64 flag, vm::ptr buffer, u32 size, vm *required = 0; for (uint i = 1; i <= flagBits.size(); i++) { - if (!flagBits[i-1]) + if (!flagBits.test(i - 1)) continue; + // If we're loading cert 6 (the baltimore cert), then we need set that we're loading the 'normal' set of certs. - *required += ::size32(getCert(certPath, i, flagBits[BaltimoreCert-1])); + *required += ::size32(getCert(certPath, i, flagBits.test(BaltimoreCert - 1))); } } else @@ -125,14 +126,15 @@ error_code cellSslCertificateLoader(u64 flag, vm::ptr buffer, u32 size, vm std::string final; for (uint i = 1; i <= flagBits.size(); i++) { - if (!flagBits[i-1]) + if (!flagBits.test(i - 1)) continue; + // If we're loading cert 6 (the baltimore cert), then we need set that we're loading the 'normal' set of certs. - final.append(getCert(certPath, i, flagBits[BaltimoreCert-1])); + final.append(getCert(certPath, i, flagBits.test(BaltimoreCert - 1))); } - memset(buffer.get_ptr(), '\0', size - 1); - memcpy(buffer.get_ptr(), final.c_str(), final.size()); + std::memset(buffer.get_ptr(), '\0', size - 1); + std::memcpy(buffer.get_ptr(), final.c_str(), final.size()); } return CELL_OK; diff --git a/rpcs3/Emu/Cell/Modules/cellSysmodule.cpp b/rpcs3/Emu/Cell/Modules/cellSysmodule.cpp index 41eae6bb82..cfa1363e85 100644 --- a/rpcs3/Emu/Cell/Modules/cellSysmodule.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSysmodule.cpp @@ -1,19 +1,21 @@ #include "stdafx.h" +#include "Crypto/utils.h" +#include "Emu/Cell/lv2/sys_fs.h" +#include "Emu/Cell/lv2/sys_lwmutex.h" +#include "Emu/Cell/lv2/sys_memory.h" +#include "Emu/Cell/lv2/sys_process.h" +#include "Emu/Cell/lv2/sys_prx.h" +#include "Emu/Cell/lv2/sys_ss.h" #include "Emu/Cell/PPUModule.h" +#include "Emu/savestate_utils.hpp" +#include "sysPrxForUser.h" +#include "util/bit_set.hpp" + +#include "cellSysmodule.h" +#include LOG_CHANNEL(cellSysmodule); -constexpr auto CELL_SYSMODULE_LOADED = CELL_OK; - -enum CellSysmoduleError : u32 -{ - CELL_SYSMODULE_ERROR_DUPLICATED = 0x80012001, - CELL_SYSMODULE_ERROR_UNKNOWN = 0x80012002, - CELL_SYSMODULE_ERROR_UNLOADED = 0x80012003, - CELL_SYSMODULE_ERROR_INVALID_MEMCONTAINER = 0x80012004, - CELL_SYSMODULE_ERROR_FATAL = 0x800120ff, -}; - template<> void fmt_class_string::format(std::string& out, u64 arg) { @@ -32,406 +34,1348 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } -static const char* get_module_name(u16 id) +namespace { - switch (id) - { - case 0x0000: return "sys_net"; - case 0x0001: return "cellHttp"; - case 0x0002: return "cellHttpUtil"; - case 0x0003: return "cellSsl"; - case 0x0004: return "cellHttps"; - case 0x0005: return "libvdec"; - case 0x0006: return "cellAdec"; - case 0x0007: return "cellDmux"; - case 0x0008: return "cellVpost"; - case 0x0009: return "cellRtc"; - case 0x000a: return "cellSpurs"; - case 0x000b: return "cellOvis"; - case 0x000c: return "cellSheap"; - case 0x000d: return "cellSync"; - case 0x000e: return "sys_fs"; - case 0x000f: return "cellJpgDec"; - case 0x0010: return "cellGcmSys"; - case 0x0011: return "cellAudio"; - case 0x0012: return "cellPamf"; - case 0x0013: return "cellAtrac"; - case 0x0014: return "cellNetCtl"; - case 0x0015: return "cellSysutil"; - case 0x0016: return "sceNp"; - case 0x0017: return "sys_io"; - case 0x0018: return "cellPngDec"; - case 0x0019: return "cellFont"; - case 0x001a: return "cellFontFT"; - case 0x001b: return "cell_FreeType2"; - case 0x001c: return "cellUsbd"; - case 0x001d: return "cellSail"; - case 0x001e: return "cellL10n"; - case 0x001f: return "cellResc"; - case 0x0020: return "cellDaisy"; - case 0x0021: return "cellKey2char"; - case 0x0022: return "cellMic"; - case 0x0023: return "cellCamera"; - case 0x0024: return "cellVdecMpeg2"; - case 0x0025: return "cellVdecAvc"; - case 0x0026: return "cellAdecLpcm"; - case 0x0027: return "cellAdecAc3"; - case 0x0028: return "cellAdecAtx"; - case 0x0029: return "cellAdecAt3"; - case 0x002a: return "cellDmuxPamf"; - case 0x002b: return nullptr; - case 0x002c: return nullptr; - case 0x002d: return nullptr; - case 0x002e: return "sys_lv2dbg"; - case 0x002f: return "cellSysutilAvcExt"; - case 0x0030: return "cellUsbPspcm"; - case 0x0031: return "cellSysutilAvconfExt"; - case 0x0032: return "cellUserInfo"; - case 0x0033: return "cellSaveData"; - case 0x0034: return "cellSubDisplay"; - case 0x0035: return "cellRec"; - case 0x0036: return "cellVideoExportUtility"; - case 0x0037: return "cellGameExec"; - case 0x0038: return "sceNp2"; - case 0x0039: return "cellSysutilAp"; - case 0x003a: return "sceNpClans"; - case 0x003b: return "cellOskExtUtility"; - case 0x003c: return "cellVdecDivx"; - case 0x003d: return "cellJpgEnc"; - case 0x003e: return "cellGame"; - case 0x003f: return "cellBGDLUtility"; - case 0x0040: return "cell_FreeType2"; - case 0x0041: return "cellVideoUpload"; - case 0x0042: return "cellSysconfExtUtility"; - case 0x0043: return "cellFiber"; - case 0x0044: return "sceNpCommerce2"; - case 0x0045: return "sceNpTus"; - case 0x0046: return "cellVoice"; - case 0x0047: return "cellAdecCelp8"; - case 0x0048: return "cellCelp8Enc"; - case 0x0049: return "cellSysutilMisc"; - case 0x004a: return "cellMusicUtility"; - // TODO: Check if those libad are correctly matched. - // They belong to those IDs but actual order is unknown. - case 0x004b: return "libad_core"; - case 0x004c: return "libad_async"; - case 0x004d: return "libad_billboard_util"; - case 0x004e: return "cellScreenShotUtility"; - case 0x004f: return "cellMusicDecodeUtility"; - case 0x0050: return "cellSpursJq"; - case 0x0052: return "cellPngEnc"; - case 0x0053: return "cellMusicDecodeUtility"; - case 0x0054: return "libmedi"; - case 0x0055: return "cellSync2"; - case 0x0056: return "sceNpUtil"; - case 0x0057: return "cellRudp"; - case 0x0059: return "sceNpSns"; - case 0x005a: return "libgem"; - case 0x005c: return "cellCrossController"; - case 0xf00a: return "cellCelpEnc"; - case 0xf010: return "cellGifDec"; - case 0xf019: return "cellAdecCelp"; - case 0xf01b: return "cellAdecM2bc"; - case 0xf01d: return "cellAdecM4aac"; - case 0xf01e: return "cellAdecMp3"; - case 0xf023: return "cellImeJpUtility"; - case 0xf028: return "cellMusicUtility"; - case 0xf029: return "cellPhotoUtility"; - case 0xf02a: return "cellPrintUtility"; - case 0xf02b: return "cellPhotoImportUtil"; - case 0xf02c: return "cellMusicExportUtility"; - case 0xf02e: return "cellPhotoDecodeUtil"; - case 0xf02f: return "cellSearchUtility"; - case 0xf030: return "cellSysutilAvc2"; - case 0xf034: return "cellSailRec"; - case 0xf035: return "sceNpTrophy"; - case 0xf044: return "cellSysutilNpEula"; - case 0xf053: return "cellAdecAt3multi"; - case 0xf054: return "cellAtracMulti"; - } + constexpr u16 INTERNAL_MODULE_ID_BASE = 0xf000; + constexpr u16 INTERNAL_MODULE_ID_MASK = 0x0fff; + constexpr u16 UNK_MODULE_ID_BASE = 0xff00; + constexpr u8 UNK_MODULE_ID_MASK = 0x00ff; - return nullptr; + constexpr std::array DEPENDENCIES_NET { CELL_SYSMODULE_NETCTL }; + constexpr std::array DEPENDENCIES_HTTP { CELL_SYSMODULE_NET, CELL_SYSMODULE_RTC }; + constexpr std::array DEPENDENCIES_HTTPUTIL{ CELL_SYSMODULE_HTTP }; + constexpr std::array DEPENDENCIES_HTTPS { CELL_SYSMODULE_SSL, CELL_SYSMODULE_HTTP }; + constexpr std::array DEPENDENCIES_SSL { CELL_SYSMODULE_NET, CELL_SYSMODULE_RTC, CELL_SYSMODULE_FS }; + constexpr std::array DEPENDENCIES_NP2 { CELL_SYSMODULE_SYSUTIL_NP }; + constexpr std::array DEPENDENCIES_CLANS { CELL_SYSMODULE_SYSUTIL_NP, CELL_SYSMODULE_HTTPS }; + constexpr std::array DEPENDENCIES_FS { CELL_SYSMODULE_FS }; + constexpr std::array DEPENDENCIES_AT3P { CELL_SYSMODULE_ADEC_AT3, CELL_SYSMODULE_ADEC_ATX }; + constexpr std::array DEPENDENCIES_AT3M { 0xf053 }; + constexpr std::array DEPENDENCIES_SPURS { CELL_SYSMODULE_SPURS }; + constexpr std::array DEPENDENCIES_VDEC_AL { CELL_SYSMODULE_VDEC_AL }; + constexpr std::array DEPENDENCIES_ADEC_AL { CELL_SYSMODULE_ADEC_AL }; + constexpr std::array DEPENDENCIES_ADEC2 { 0xf03f }; + constexpr std::array DEPENDENCIES_DMUX_AL { CELL_SYSMODULE_DMUX_AL }; + constexpr std::array DEPENDENCIES_VDEC { CELL_SYSMODULE_VDEC_MPEG2, CELL_SYSMODULE_VDEC_AVC, CELL_SYSMODULE_VDEC_AL }; + constexpr std::array DEPENDENCIES_ADEC { CELL_SYSMODULE_ADEC_LPCM, CELL_SYSMODULE_ADEC_AC3, CELL_SYSMODULE_ADEC_AT3, CELL_SYSMODULE_ADEC_ATX, CELL_SYSMODULE_ADEC_AL }; + constexpr std::array DEPENDENCIES_DMUX { CELL_SYSMODULE_DMUX_PAMF, CELL_SYSMODULE_DMUX_AL }; + constexpr std::array DEPENDENCIES_PSPCM { CELL_SYSMODULE_USBD }; + constexpr std::array DEPENDENCIES_COMM { CELL_SYSMODULE_SYSUTIL_NP2, CELL_SYSMODULE_HTTPS }; + constexpr std::array DEPENDENCIES_NP_TUS { CELL_SYSMODULE_SYSUTIL_NP2 }; + constexpr std::array DEPENDENCIES_AD_CORE { CELL_SYSMODULE_FS, CELL_SYSMODULE_NET, CELL_SYSMODULE_SYSUTIL_NP }; + constexpr std::array DEPENDENCIES_AD_ASYNC{ 0x4b }; + constexpr std::array DEPENDENCIES_SPURS_JQ{ CELL_SYSMODULE_FIBER }; + constexpr std::array DEPENDENCIES_MEDI { CELL_SYSMODULE_NET, CELL_SYSMODULE_SYSUTIL_NP }; + + struct module_info + { + std::string_view path; + std::span dependencies; + }; + + constexpr std::array MODULE_INFOS + { + // 93 external modules + module_info + { .path = "external/libnet.sprx", .dependencies = DEPENDENCIES_NET }, // CELL_SYSUTIL_NET + { .path = "external/libhttp.sprx", .dependencies = DEPENDENCIES_HTTP }, // CELL_SYSMODULE_HTTP + { .path = {}, .dependencies = DEPENDENCIES_HTTPUTIL }, // CELL_SYSMODULE_HTTP_UTIL + { .path = "external/libssl.sprx", .dependencies = DEPENDENCIES_SSL }, // CELL_SYSMODULE_SSL + { .path = {}, .dependencies = DEPENDENCIES_HTTPS }, // CELL_SYSMODULE_HTTPS + { .path = {}, .dependencies = DEPENDENCIES_VDEC }, // CELL_SYSMODULE_VDEC + { .path = {}, .dependencies = DEPENDENCIES_ADEC }, // CELL_SYSMODULE_ADEC + { .path = {}, .dependencies = DEPENDENCIES_DMUX }, // CELL_SYSMODULE_DMUX + { .path = "external/libvpost.sprx", .dependencies = {} }, // CELL_SYSMODULE_VPOST + { .path = "external/librtc.sprx", .dependencies = {} }, // CELL_SYSMODULE_RTC + { .path = "external/libsre.sprx", .dependencies = {} }, // CELL_SYSMODULE_SPURS + { .path = {}, .dependencies = DEPENDENCIES_SPURS }, // CELL_SYSMODULE_OVIS + { .path = {}, .dependencies = DEPENDENCIES_SPURS }, // CELL_SYSMODULE_SHEAP + { .path = {}, .dependencies = DEPENDENCIES_SPURS }, // CELL_SYSMODULE_SYNC + { .path = "external/libfs.sprx", .dependencies = {} }, // CELL_SYSMODULE_FS + { .path = "external/libjpgdec.sprx", .dependencies = DEPENDENCIES_FS }, // CELL_SYSMODULE_JPGDEC + { .path = "external/libgcm_sys.sprx", .dependencies = {} }, // CELL_SYSMODULE_GCM_SYS + { .path = "external/libaudio.sprx", .dependencies = {} }, // CELL_SYSMODULE_AUDIO + { .path = "external/libpamf.sprx", .dependencies = {} }, // CELL_SYSMODULE_PAMF + { .path = "external/libatrac3plus.sprx", .dependencies = DEPENDENCIES_AT3P }, // CELL_SYSMODULE_ATRAC3PLUS + { .path = "external/libnetctl.sprx", .dependencies = {} }, // CELL_SYSMODULE_NETCTL + { .path = "external/libsysutil.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL + { .path = "external/libsysutil_np.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_NP + { .path = "external/libio.sprx", .dependencies = {} }, // CELL_SYSMODULE_IO + { .path = "external/libpngdec.sprx", .dependencies = DEPENDENCIES_FS }, // CELL_SYSMODULE_PNGDEC + { .path = "external/libfont.sprx", .dependencies = DEPENDENCIES_FS }, // CELL_SYSMODULE_FONT + { .path = "external/libfontFT.sprx", .dependencies = {} }, // CELL_SYSMODULE_FONTFT + { .path = "external/libfreetype.sprx", .dependencies = DEPENDENCIES_FS }, // CELL_SYSMODULE_FREETYPE + { .path = "external/libusbd.sprx", .dependencies = {} }, // CELL_SYSMODULE_USBD + { .path = "external/libsail.sprx", .dependencies = {} }, // CELL_SYSMODULE_SAIL + { .path = "external/libl10n.sprx", .dependencies = {} }, // CELL_SYSMODULE_L10N + { .path = "external/libresc.sprx", .dependencies = {} }, // CELL_SYSMODULE_RESC + { .path = {}, .dependencies = DEPENDENCIES_SPURS }, // CELL_SYSMODULE_DAISY + { .path = "external/libkey2char.sprx", .dependencies = {} }, // CELL_SYSMODULE_KEY2CHAR + { .path = "external/libmic.sprx", .dependencies = {} }, // CELL_SYSMODULE_MIC + { .path = "external/libcamera.sprx", .dependencies = {} }, // CELL_SYSMODULE_CAMERA + { .path = "external/libsmvd2.sprx", .dependencies = DEPENDENCIES_VDEC_AL }, // CELL_SYSMODULE_VDEC_MPEG2 + { .path = "external/libavcdec.sprx", .dependencies = DEPENDENCIES_VDEC_AL }, // CELL_SYSMODULE_VDEC_AVC + { .path = {}, .dependencies = DEPENDENCIES_ADEC_AL }, // CELL_SYSMODULE_ADEC_LPCM + { .path = "external/libac3dec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // CELL_SYSMODULE_ADEC_AC3 + { .path = "external/libatxdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // CELL_SYSMODULE_ADEC_ATX + { .path = "external/libat3dec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // CELL_SYSMODULE_ADEC_AT3 + { .path = "external/libdmuxpamf.sprx", .dependencies = DEPENDENCIES_DMUX_AL }, // CELL_SYSMODULE_DMUX_PAMF + { .path = "external/libvdec.sprx", .dependencies = {} }, // CELL_SYSMODULE_VDEC_AL + { .path = "external/libadec.sprx", .dependencies = {} }, // CELL_SYSMODULE_ADEC_AL + { .path = "external/libdmux.sprx", .dependencies = {} }, // CELL_SYSMODULE_DMUX_AL + { .path = "external/liblv2dbg_for_dex.sprx", .dependencies = {} }, // CELL_SYSMODULE_LV2DBG + { .path = "external/libsysutil_avc_ext.sprx", .dependencies = {} }, // 0x2f + { .path = "external/libusbpspcm.sprx", .dependencies = DEPENDENCIES_PSPCM }, // CELL_SYSMODULE_USBPSPCM + { .path = "external/libsysutil_avconf_ext.sprx", .dependencies = {} }, // CELL_SYSMODULE_AVCONF_EXT + { .path = "external/libsysutil_userinfo.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_USERINFO + { .path = "external/libsysutil_savedata.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_SAVEDATA + { .path = "external/libsysutil_subdisplay.sprx", .dependencies = {} }, // CELL_SYSMODULE_SUBDISPLAY + { .path = "external/libsysutil_rec.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_REC + { .path = "external/libsysutil_video_export.sprx", .dependencies = {} }, // CELL_SYSMODULE_VIDEO_EXPORT + { .path = "external/libsysutil_game_exec.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_GAME_EXEC + { .path = "external/libsysutil_np2.sprx", .dependencies = DEPENDENCIES_NP2 }, // CELL_SYSMODULE_SYSUTIL_NP2 + { .path = "external/libsysutil_ap.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_AP + { .path = "external/libsysutil_np_clans.sprx", .dependencies = DEPENDENCIES_CLANS }, // CELL_SYSMODULE_SYSUTIL_NP_CLANS + { .path = "external/libsysutil_oskdialog_ext.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_OSK_EXT + { .path = "external/libdivxdec.sprx", .dependencies = DEPENDENCIES_VDEC_AL }, // CELL_SYSMODULE_VDEC_DIVX + { .path = "external/libjpgenc.sprx", .dependencies = DEPENDENCIES_FS }, // CELL_SYSMODULE_JPGENC + { .path = "external/libsysutil_game.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_GAME + { .path = "external/libsysutil_bgdl.sprx", .dependencies = {} }, // CELL_SYSMODULE_BGDL + { .path = "external/libfreetypeTT.sprx", .dependencies = DEPENDENCIES_FS }, // CELL_SYSMODULE_FREETYPE_TT + { .path = "external/libsysutil_video_upload.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_VIDEO_UPLOAD + { .path = "external/libsysutil_sysconf_ext.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_SYSCONF_EXT + { .path = "external/libfiber.sprx", .dependencies = {} }, // CELL_SYSMODULE_FIBER + { .path = "external/libsysutil_np_commerce2.sprx", .dependencies = DEPENDENCIES_COMM }, // CELL_SYSMODULE_SYSUTIL_NP_COMMERCE2 + { .path = "external/libsysutil_np_tus.sprx", .dependencies = DEPENDENCIES_NP_TUS }, // CELL_SYSMODULE_SYSUTIL_NP_TUS + { .path = "external/libvoice.sprx", .dependencies = {} }, // CELL_SYSMODULE_VOICE + { .path = "external/libcelp8dec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // CELL_SYSMODULE_ADEC_CELP8 + { .path = "external/libcelp8enc.sprx", .dependencies = {} }, // CELL_SYSMODULE_CELP8ENC + { .path = "external/libsysutil_misc.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_LICENSEAREA + { .path = "external/libsysutil_music.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_MUSIC2 + { .path = "external/libad_core.sprx", .dependencies = DEPENDENCIES_AD_CORE }, // 0x4b + { .path = "external/libad_async.sprx", .dependencies = DEPENDENCIES_AD_ASYNC }, // 0x4c + { .path = "external/libad_billboard_util.sprx", .dependencies = DEPENDENCIES_AD_CORE }, // 0x4d + { .path = "external/libsysutil_screenshot.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_SCREENSHOT + { .path = "external/libsysutil_music_decode.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_MUSIC_DECODE + { .path = "external/libspurs_jq.sprx", .dependencies = DEPENDENCIES_SPURS_JQ }, // CELL_SYSMODULE_SPURS_JQ + { .path = "external/libsysutil_authdialog.sprx", .dependencies = {} }, // 0x51 + { .path = "external/libpngenc.sprx", .dependencies = DEPENDENCIES_FS }, // CELL_SYSMODULE_PNGENC + { .path = "external/libsysutil_music_decode.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_MUSIC_DECODE2 + { .path = "external/libmedi.sprx", .dependencies = DEPENDENCIES_MEDI }, // 0x54 + { .path = "external/libsync2.sprx", .dependencies = DEPENDENCIES_SPURS_JQ }, // CELL_SYSMODULE_SYNC2 + { .path = "external/libsysutil_np_util.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_NP_UTIL + { .path = "external/librudp.sprx", .dependencies = {} }, // CELL_SYSMODULE_RUDP + { .path = "external/libsysutil_syschat.sprx", .dependencies = {} }, // 0x58 + { .path = "external/libsysutil_np_sns.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_NP_SNS + { .path = "external/libgem.sprx", .dependencies = {} }, // CELL_SYSMODULE_GEM + { .path = "external/libsysutil_photo_network_sharing.sprx", .dependencies = {} }, // 0x5b + { .path = "external/libsysutil_cross_controller.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_CROSS_CONTROLLER + + // 87 internal modules + { .path = "internal/libat3enc.sprx", .dependencies = {} }, // 0xf000 + { .path = "internal/libatxenc.sprx", .dependencies = {} }, // 0xf001 + { .path = "external/libvdec.sprx", .dependencies = {} }, // 0xf002 + { .path = "internal/libvpost.sprx", .dependencies = {} }, // 0xf003 + { .path = "internal/libmp3enc.sprx", .dependencies = DEPENDENCIES_FS }, // 0xf004 + { .path = "external/libaacenc.sprx", .dependencies = {} }, // 0xf005 + { .path = "external/libadec_internal.sprx", .dependencies = {} }, // 0xf006 + { .path = "internal/libapostsrc.sprx", .dependencies = {} }, // 0xf007 + { .path = "internal/libaudio_internal.sprx", .dependencies = {} }, // 0xf008 + { .path = "external/libavchatjpgdec.sprx", .dependencies = {} }, // 0xf009 + { .path = "external/libcelpenc.sprx", .dependencies = {} }, // CELL_SYSMODULE_CELPENC + { .path = "internal/libddlenc.sprx", .dependencies = {} }, // 0xf00b + { .path = "external/libdmux.sprx", .dependencies = {} }, // 0xf00c + { .path = "internal/libexif.sprx", .dependencies = DEPENDENCIES_FS }, // 0xf00d + { .path = "internal/libft2d.sprx", .dependencies = {} }, // 0xf00e + { .path = "internal/libgcm_osd.sprx", .dependencies = {} }, // 0xf00f + { .path = "external/libgifdec.sprx", .dependencies = DEPENDENCIES_FS }, // CELL_SYSMODULE_GIFDEC + { .path = "external/libjpgdec.sprx", .dependencies = DEPENDENCIES_FS }, // 0xf011 + { .path = "external/libjpgenc.sprx", .dependencies = DEPENDENCIES_FS }, // 0xf012 + { .path = "external/libm4venc.sprx", .dependencies = {} }, // 0xf013 + { .path = "external/libpamf.sprx", .dependencies = {} }, // 0xf014 + { .path = "external/libpngdec.sprx", .dependencies = DEPENDENCIES_FS }, // 0xf015 + { .path = "external/libpngenc.sprx", .dependencies = DEPENDENCIES_FS }, // 0xf016 + { .path = "internal/libps2savedata.sprx", .dependencies = {} }, // 0xf017 + { .path = "internal/libtiffdec.sprx", .dependencies = DEPENDENCIES_FS }, // 0xf018 + { .path = "external/libcelpdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // CELL_SYSMODULE_ADEC_CELP + { .path = "internal/libdtsdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf01a + { .path = "external/libm2bcdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // CELL_SYSMODULE_ADEC_M2BC + { .path = "internal/libm2aacdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf01c + { .path = "external/libm4aacdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // CELL_SYSMODULE_ADEC_M4AAC + { .path = "external/libmp3dec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // CELL_SYSMODULE_ADEC_MP3 + { .path = "internal/libtrhddec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf01f + { .path = "external/libsvc1d.sprx", .dependencies = DEPENDENCIES_VDEC_AL }, // 0xf020 + { .path = "external/libsmvd4.sprx", .dependencies = DEPENDENCIES_VDEC_AL }, // 0xf021 + { .path = "external/libsysutil_remoteplay.sprx", .dependencies = {} }, // 0xf022 + { .path = "external/libsysutil_imejp.sprx", .dependencies = {} }, // CELL_SYSMODULE_IMEJP + { .path = "external/libwmadec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf024 + { .path = "internal/libasfparser.sprx", .dependencies = {} }, // 0xf025 + { .path = "external/libddpdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf026 + { .path = "internal/libdtslbrdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf027 + { .path = "external/libsysutil_music.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_MUSIC + { .path = "external/libsysutil_photo_export.sprx", .dependencies = {} }, // CELL_SYSMODULE_PHOTO_EXPORT + { .path = "external/libsysutil_print.sprx", .dependencies = {} }, // CELL_SYSMODULE_PRINT + { .path = "external/libsysutil_photo_import.sprx", .dependencies = {} }, // CELL_SYSMODULE_PHOTO_IMPORT + { .path = "external/libsysutil_music_export.sprx", .dependencies = {} }, // CELL_SYSMODULE_MUSIC_EXPORT + { .path = "external/libavcenc_small.sprx", .dependencies = {} }, // 0xf02d + { .path = "external/libsysutil_photo_decode.sprx", .dependencies = {} }, // CELL_SYSMODULE_PHOTO_DECODE + { .path = "external/libsysutil_search.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_SEARCH + { .path = "external/libsysutil_avc2.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_AVCHAT2 + { .path = "external/libmp4.sprx", .dependencies = {} }, // 0xf031 + { .path = "external/libsysutil_rtcalarm.sprx", .dependencies = {} }, // 0xf032 + { .path = "external/libavcenc_small.sprx", .dependencies = {} }, // 0xf033 + { .path = "external/libsail_rec.sprx", .dependencies = {} }, // CELL_SYSMODULE_SAIL_REC + { .path = "external/libsysutil_np_trophy.sprx", .dependencies = {} }, // CELL_SYSMODULE_SYSUTIL_NP_TROPHY + { .path = "external/libsjvtd.sprx", .dependencies = DEPENDENCIES_VDEC_AL }, // 0xf036 + { .path = "internal/libdtshddec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf037 + { .path = "internal/libmp3sdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf038 + { .path = "external/libsail_avi.sprx", .dependencies = {} }, // 0xf039 + { .path = "external/libavcenc.sprx", .dependencies = {} }, // 0xf03a + { .path = "external/libapostsrc_mini.sprx", .dependencies = {} }, // 0xf03b + { .path = "external/libvoice_internal.sprx", .dependencies = {} }, // 0xf03c + { .path = "external/libmpl1dec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf03d + { .path = "external/libasfparser2_astd.sprx", .dependencies = {} }, // 0xf03e + { .path = "external/libadec2.sprx", .dependencies = {} }, // 0xf03f + { .path = "external/libac3dec2.sprx", .dependencies = DEPENDENCIES_ADEC2 }, // 0xf040 + { .path = "external/libatxdec2.sprx", .dependencies = DEPENDENCIES_ADEC2 }, // 0xf041 + { .path = "internal/libdivx311dec.sprx", .dependencies = DEPENDENCIES_VDEC_AL }, // 0xf042 + { .path = "external/libvpost2.sprx", .dependencies = {} }, // 0xf043 + { .path = "external/libsysutil_np_eula.sprx", .dependencies = {} }, // 0xf044 + { .path = "external/libsysutil_storagedata.sprx", .dependencies = {} }, // 0xf045 + { .path = "external/libm4hdenc.sprx", .dependencies = {} }, // 0xf046 + { .path = "external/libsysutil_savedata_psp.sprx", .dependencies = {} }, // 0xf047 + { .path = "external/libsysutil_video_player.sprx", .dependencies = {} }, // 0xf048 + { .path = "external/libmvcdec.sprx", .dependencies = DEPENDENCIES_VDEC_AL }, // 0xf049 + { .path = "external/libaacenc_spurs.sprx", .dependencies = {} }, // 0xf04a + { .path = "internal/libdtshdcoredec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf04b + { .path = "internal/libpidvd.sprx", .dependencies = {} }, // 0xf04c + { .path = "external/libsysutil_dtcp_ip.sprx", .dependencies = {} }, // 0xf04d + { .path = "external/libsysutil_syschat.sprx", .dependencies = {} }, // 0xf04e + { .path = "external/libsysutil_np_installer.sprx", .dependencies = {} }, // 0xf04f + { .path = "external/libbemp2sys.sprx", .dependencies = {} }, // 0xf050 + { .path = "external/libbeisobmf.sprx", .dependencies = {} }, // 0xf051 + { .path = "external/libsysutil_photo_export2.sprx", .dependencies = {} }, // 0xf052 + { .path = "external/libat3multidec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf053 + { .path = "external/libatrac3multi.sprx", .dependencies = DEPENDENCIES_AT3M }, // CELL_SYSMODULE_LIBATRAC3MULTI + { .path = "external/libsysutil_dec_psnvideo.sprx", .dependencies = {} }, // 0xf055 + { .path = "external/libdtslbrdec.sprx", .dependencies = DEPENDENCIES_ADEC_AL }, // 0xf056 + + // Special modules + { .path = "external/liblv2dbg_for_dex.sprx", .dependencies = {} }, + { .path = "external/liblv2dbg_for_cex.sprx", .dependencies = {} }, + + { .path = "external/libgcm_sys_deh.sprx", .dependencies = {} }, + { .path = "external/libgcm_sys.sprx", .dependencies = {} }, + + { .path = "external/libfs.sprx", .dependencies = {} }, + { .path = "external/libfs_155.sprx", .dependencies = {} }, + + { .path = "external/libprof.sprx", .dependencies = {} }, + { .path = "external/liblv2coredump.sprx", .dependencies = {} }, + { .path = "external/libgpad.sprx", .dependencies = {} } + }; + + constexpr u8 INTERNAL_MODULES_OFFSET = 93; + constexpr u8 INTERNAL_MODULES_COUNT = 87; + constexpr u8 LV2_DBG_IDX = INTERNAL_MODULES_OFFSET + INTERNAL_MODULES_COUNT; + constexpr u8 GCM_SYS_DEH_IDX = LV2_DBG_IDX + 2; + constexpr u8 FS_IDX = LV2_DBG_IDX + 4; + constexpr u8 PROF_IDX = LV2_DBG_IDX + 6; + constexpr u8 LV2COREDUMP_IDX = LV2_DBG_IDX + 7; + constexpr u8 GPAD_IDX = LV2_DBG_IDX + 8; + + constexpr std::string_view MODULE_BASE_PATH = "/dev_flash/sys/"; + constexpr std::string_view MODULE_BASE_PATH_DEBUG = "/app_home/"; + constexpr u8 BASE_PATH_MAX_SIZE = 0x10; + constexpr u8 PATH_MAX_SIZE = 0x40; + static_assert(MODULE_BASE_PATH.size() < BASE_PATH_MAX_SIZE && MODULE_BASE_PATH_DEBUG.size() < BASE_PATH_MAX_SIZE); + static_assert(std::ranges::all_of(MODULE_INFOS, [](const auto& path) { return path.size() + BASE_PATH_MAX_SIZE < PATH_MAX_SIZE; }, &module_info::path)); + + constexpr std::array DEFAULT_MODULES + { + CELL_SYSMODULE_SYSUTIL, + CELL_SYSMODULE_GCM_SYS, + CELL_SYSMODULE_AUDIO, + CELL_SYSMODULE_IO, + CELL_SYSMODULE_SPURS, + CELL_SYSMODULE_FS, + CELL_SYSMODULE_SYSUTIL_NP_TROPHY + }; + + constexpr bit_set LOADABLE_INTERNAL_MODULES + { + "0010000" // 0xf054 + "0000000000010000" // 0xf044 + "0000000000110001" // 0xf030, 0xf034, 0xf035 + "1101111100001000" // 0xf023, 0xf028, 0xf029, 0xf02a, 0xf02b, 0xf02c, 0xf02e, 0xf02f + "0110101000000001" // 0xf010, 0xf019, 0xf01b, 0xf01d, 0xf01e + "0000010000000000" // 0xf00a + }; + + constexpr bit_set LOADABLE_INTERNAL_MODULES_EX + { + "1110111" // 0xf050, 0xf051, 0xf052, 0xf054, 0xf055, 0xf056 + "1110011111111011" // 0xf040, 0xf041, 0xf043, 0xf044, 0xf045, 0xf046, 0xf047, 0xf048, 0xf049, 0xf04a, 0xf04d, 0xf04e, 0xf04f + "1110011001111101" // 0xf030, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036, 0xf039, 0xf03a, 0xf03d, 0xf03e, 0xf03f + "1101111101011111" // 0xf020, 0xf021, 0xf022, 0xf023, 0xf024, 0xf026, 0xf028, 0xf029, 0xf02a, 0xf02b, 0xf02c, 0xf02e, 0xf02f + "0110101000001001" // 0xf010, 0xf013, 0xf019, 0xf01b, 0xf01d, 0xf01e + "0000010000100000" // 0xf005, 0xf00a + }; + + constexpr std::array KEY_UNK{ 'S', 'r', 'e', 'k', 'i', 'r', 't', 'S', 'a', 'h', 'o', 'n', 'a', 'N', 'l', 'a' }; + + struct module_state + { + s32 loaded_count; + s32 prx_id; + }; + + struct sysmodule_context + { + sys_lwmutex_t mutex; + + be_t unk_addr; + + u32 mem_container_id; + b8 use_mem_container; + + module_state module_states[MODULE_INFOS.size()]; + + char module_base_path[BASE_PATH_MAX_SIZE]; + u8 module_base_path_size; + }; + + vm::gvar s_sysmodule_context; } -static const char* get_module_id(u16 id) +template +[[nodiscard]] static bool check_special_handling(ppu_thread& ppu) { - static thread_local char tls_id_name[8]; // for test + // TODO replace with proper struct + const vm::var paramsfo(0x40); + std::memset(paramsfo.get_ptr(), 0, paramsfo.get_count()); - switch (id) + const error_code ret = ppu_execute<&sys_process_get_paramsfo>(ppu, +paramsfo); + + switch (module_id) { - case 0x0000: return "CELL_SYSMODULE_NET"; - case 0x0001: return "CELL_SYSMODULE_HTTP"; - case 0x0002: return "CELL_SYSMODULE_HTTP_UTIL"; - case 0x0003: return "CELL_SYSMODULE_SSL"; - case 0x0004: return "CELL_SYSMODULE_HTTPS"; - case 0x0005: return "CELL_SYSMODULE_VDEC"; - case 0x0006: return "CELL_SYSMODULE_ADEC"; - case 0x0007: return "CELL_SYSMODULE_DMUX"; - case 0x0008: return "CELL_SYSMODULE_VPOST"; - case 0x0009: return "CELL_SYSMODULE_RTC"; - case 0x000a: return "CELL_SYSMODULE_SPURS"; - case 0x000b: return "CELL_SYSMODULE_OVIS"; - case 0x000c: return "CELL_SYSMODULE_SHEAP"; - case 0x000d: return "CELL_SYSMODULE_SYNC"; - case 0x000e: return "CELL_SYSMODULE_FS"; - case 0x000f: return "CELL_SYSMODULE_JPGDEC"; - case 0x0010: return "CELL_SYSMODULE_GCM_SYS"; - case 0x0011: return "CELL_SYSMODULE_AUDIO"; - case 0x0012: return "CELL_SYSMODULE_PAMF"; - case 0x0013: return "CELL_SYSMODULE_ATRAC3PLUS"; - case 0x0014: return "CELL_SYSMODULE_NETCTL"; - case 0x0015: return "CELL_SYSMODULE_SYSUTIL"; - case 0x0016: return "CELL_SYSMODULE_SYSUTIL_NP"; - case 0x0017: return "CELL_SYSMODULE_IO"; - case 0x0018: return "CELL_SYSMODULE_PNGDEC"; - case 0x0019: return "CELL_SYSMODULE_FONT"; - case 0x001a: return "CELL_SYSMODULE_FONTFT"; - case 0x001b: return "CELL_SYSMODULE_FREETYPE"; - case 0x001c: return "CELL_SYSMODULE_USBD"; - case 0x001d: return "CELL_SYSMODULE_SAIL"; - case 0x001e: return "CELL_SYSMODULE_L10N"; - case 0x001f: return "CELL_SYSMODULE_RESC"; - case 0x0020: return "CELL_SYSMODULE_DAISY"; - case 0x0021: return "CELL_SYSMODULE_KEY2CHAR"; - case 0x0022: return "CELL_SYSMODULE_MIC"; - case 0x0023: return "CELL_SYSMODULE_CAMERA"; - case 0x0024: return "CELL_SYSMODULE_VDEC_MPEG2"; - case 0x0025: return "CELL_SYSMODULE_VDEC_AVC"; - case 0x0026: return "CELL_SYSMODULE_ADEC_LPCM"; - case 0x0027: return "CELL_SYSMODULE_ADEC_AC3"; - case 0x0028: return "CELL_SYSMODULE_ADEC_ATX"; - case 0x0029: return "CELL_SYSMODULE_ADEC_AT3"; - case 0x002a: return "CELL_SYSMODULE_DMUX_PAMF"; - case 0x002b: return "CELL_SYSMODULE_VDEC_AL"; - case 0x002c: return "CELL_SYSMODULE_ADEC_AL"; - case 0x002d: return "CELL_SYSMODULE_DMUX_AL"; - case 0x002e: return "CELL_SYSMODULE_LV2DBG"; - case 0x002f: return "CELL_SYSMODULE_SYSUTIL_AVCHAT"; - case 0x0030: return "CELL_SYSMODULE_USBPSPCM"; - case 0x0031: return "CELL_SYSMODULE_AVCONF_EXT"; - case 0x0032: return "CELL_SYSMODULE_SYSUTIL_USERINFO"; - case 0x0033: return "CELL_SYSMODULE_SYSUTIL_SAVEDATA"; - case 0x0034: return "CELL_SYSMODULE_SUBDISPLAY"; - case 0x0035: return "CELL_SYSMODULE_SYSUTIL_REC"; - case 0x0036: return "CELL_SYSMODULE_VIDEO_EXPORT"; - case 0x0037: return "CELL_SYSMODULE_SYSUTIL_GAME_EXEC"; - case 0x0038: return "CELL_SYSMODULE_SYSUTIL_NP2"; - case 0x0039: return "CELL_SYSMODULE_SYSUTIL_AP"; - case 0x003a: return "CELL_SYSMODULE_SYSUTIL_NP_CLANS"; - case 0x003b: return "CELL_SYSMODULE_SYSUTIL_OSK_EXT"; - case 0x003c: return "CELL_SYSMODULE_VDEC_DIVX"; - case 0x003d: return "CELL_SYSMODULE_JPGENC"; - case 0x003e: return "CELL_SYSMODULE_SYSUTIL_GAME"; - case 0x003f: return "CELL_SYSMODULE_BGDL"; - case 0x0040: return "CELL_SYSMODULE_FREETYPE_TT"; - case 0x0041: return "CELL_SYSMODULE_SYSUTIL_VIDEO_UPLOAD"; - case 0x0042: return "CELL_SYSMODULE_SYSUTIL_SYSCONF_EXT"; - case 0x0043: return "CELL_SYSMODULE_FIBER"; - case 0x0044: return "CELL_SYSMODULE_SYSUTIL_NP_COMMERCE2"; - case 0x0045: return "CELL_SYSMODULE_SYSUTIL_NP_TUS"; - case 0x0046: return "CELL_SYSMODULE_VOICE"; - case 0x0047: return "CELL_SYSMODULE_ADEC_CELP8"; - case 0x0048: return "CELL_SYSMODULE_CELP8ENC"; - case 0x0049: return "CELL_SYSMODULE_SYSUTIL_LICENSEAREA"; - case 0x004a: return "CELL_SYSMODULE_SYSUTIL_MUSIC2"; - // TODO: Check if those libad are correctly matched. - // They belong to those IDs but actual order is unknown. - case 0x004b: return "CELL_SYSMODULE_AD_CORE"; - case 0x004c: return "CELL_SYSMODULE_AD_ASYNC"; - case 0x004d: return "CELL_SYSMODULE_AD_BILLBOARD_UTIL"; - case 0x004e: return "CELL_SYSMODULE_SYSUTIL_SCREENSHOT"; - case 0x004f: return "CELL_SYSMODULE_SYSUTIL_MUSIC_DECODE"; - case 0x0050: return "CELL_SYSMODULE_SPURS_JQ"; - case 0x0052: return "CELL_SYSMODULE_PNGENC"; - case 0x0053: return "CELL_SYSMODULE_SYSUTIL_MUSIC_DECODE2"; - case 0x0054: return "CELL_SYSMODULE_MEDI"; - case 0x0055: return "CELL_SYSMODULE_SYNC2"; - case 0x0056: return "CELL_SYSMODULE_SYSUTIL_NP_UTIL"; - case 0x0057: return "CELL_SYSMODULE_RUDP"; - case 0x0059: return "CELL_SYSMODULE_SYSUTIL_NP_SNS"; - case 0x005a: return "CELL_SYSMODULE_GEM"; - case 0x005c: return "CELL_SYSMODULE_SYSUTIL_CROSS_CONTROLLER"; - case 0xf00a: return "CELL_SYSMODULE_CELPENC"; - case 0xf010: return "CELL_SYSMODULE_GIFDEC"; - case 0xf019: return "CELL_SYSMODULE_ADEC_CELP"; - case 0xf01b: return "CELL_SYSMODULE_ADEC_M2BC"; - case 0xf01d: return "CELL_SYSMODULE_ADEC_M4AAC"; - case 0xf01e: return "CELL_SYSMODULE_ADEC_MP3"; - case 0xf023: return "CELL_SYSMODULE_IMEJP"; - case 0xf028: return "CELL_SYSMODULE_SYSUTIL_MUSIC"; - case 0xf029: return "CELL_SYSMODULE_PHOTO_EXPORT"; - case 0xf02a: return "CELL_SYSMODULE_PRINT"; - case 0xf02b: return "CELL_SYSMODULE_PHOTO_IMPORT"; - case 0xf02c: return "CELL_SYSMODULE_MUSIC_EXPORT"; - case 0xf02e: return "CELL_SYSMODULE_PHOTO_DECODE"; - case 0xf02f: return "CELL_SYSMODULE_SYSUTIL_SEARCH"; - case 0xf030: return "CELL_SYSMODULE_SYSUTIL_AVCHAT2"; - case 0xf034: return "CELL_SYSMODULE_SAIL_REC"; - case 0xf035: return "CELL_SYSMODULE_SYSUTIL_NP_TROPHY"; - case 0xf044: return "CELL_SYSMODULE_SYSUTIL_NP_EULA"; - case 0xf053: return "CELL_SYSMODULE_ADEC_AT3MULTI"; - case 0xf054: return "CELL_SYSMODULE_LIBATRAC3MULTI"; - case 0xffff: return "CELL_SYSMODULE_INVALID"; + case CELL_SYSMODULE_LV2DBG: + { + // If the LSB of the u64 at offset 0x18 and the extra load flag "EnableLv2ExceptionHandler" is set, + // or a certain bit in bootflag.dat, loads liblv2dbg_for_dex.sprx instead of liblv2dbg_for_cex + const vm::var boot_flag(8); + std::memset(boot_flag.get_ptr(), 0, boot_flag.get_count()); + + if (ret != CELL_OK || !read_from_ptr>(paramsfo.get_ptr(), 0x18)) + { + const vm::var file_descriptor; + + if (sys_fs_open(ppu, vm::make_str("/dev_hdd0/data/bootflag.dat"), 0, file_descriptor, 0, vm::null, 0) != CELL_OK) + { + return true; + } + + const vm::var nread; + ensure(sys_fs_read(ppu, *file_descriptor, boot_flag, 8, nread) == CELL_OK && *nread == 8ull); // Not checked on LLE + ensure(sys_fs_close(ppu, *file_descriptor) == CELL_OK); // Not checked on LLE + + return !!read_from_ptr>(boot_flag.get_ptr(), 1); + } + + return !read_from_ptr>(paramsfo.get_ptr(), 0x10); + } + case CELL_SYSMODULE_GCM_SYS: + { + // If the extra load flag "EnableGCMDebug" is set, loads libgcm_sys_deh.sprx instead of libgcm_sys.sprx + return ret != CELL_OK || !read_from_ptr>(paramsfo.get_ptr(), 0x10); + } + case CELL_SYSMODULE_FS: + { + // "F.E.A.R. First Encounter Assault Recon" needs an older version of cellFs + return std::ranges::contains(std::array{ "BLUS30003", "BLES00035", "BLES00036" }, std::string_view{ paramsfo.get_ptr() + 1, 9 }); + } + case CELL_SYSMODULE_SAIL: + { + // "Armored Core 4" needs a frame buffer release delay of three frames + return std::ranges::contains(std::array{ "BLJM60012", "BLES00039", "BLUS30027", "BLKS20001" }, std::string_view{ paramsfo.get_ptr() + 1, 9 }); + } + default: + std::unreachable(); + } +} + +static error_code load_module(ppu_thread& ppu, u8 module_idx, u32 args, vm::ptr argp) +{ + cellSysmodule.notice("load_module(module_idx=%d, args=%d, argp=*0x%x)", module_idx, args, argp); + + ensure(module_idx < MODULE_INFOS.size()); + auto& [loaded_count, prx_id] = s_sysmodule_context->module_states[module_idx]; + const auto& [path, dependencies] = MODULE_INFOS[module_idx]; + + cellSysmodule.notice("load_module(): path=\"%s\", loaded_count=%d, prx_id=%d", path, loaded_count, prx_id); + + if (path.empty()) + { + return CELL_OK; } - std::snprintf(tls_id_name, sizeof(tls_id_name), "0x%04X", id); - return tls_id_name; + if (loaded_count == smax + || ppu_execute<&sys_lwmutex_lock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex), 0) != CELL_OK) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + if (loaded_count == 0) + { + const vm::var module_path(72); + std::memcpy(module_path.get_ptr(), s_sysmodule_context->module_base_path, s_sysmodule_context->module_base_path_size); + std::memcpy(module_path.get_ptr() + s_sysmodule_context->module_base_path_size, path.data(), path.size() + 1); + + const error_code ret = s_sysmodule_context->use_mem_container + ? ppu_execute<&sys_prx_load_module_on_memcontainer>(ppu, +module_path, s_sysmodule_context->mem_container_id, 0, vm::null) + : ppu_execute<&sys_prx_load_module>(ppu, +module_path, 0, vm::null); + + prx_id = ret; + + if (ret < 1) + { + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return ret; + } + + if (const error_code ret = ppu_execute<&sys_prx_start_module>(ppu, prx_id, args, argp, +vm::make_var(0), 0, vm::null); ret != CELL_OK) + { + ensure(ppu_execute<&sys_prx_unload_module>(ppu, prx_id, 0, vm::null) == CELL_OK); // Not checked on LLE + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return ret; + } + } + + loaded_count++; + + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return CELL_OK; +} + +template +static error_code unload_module(ppu_thread& ppu, u8 module_idx) +{ + cellSysmodule.notice("unload_module(module_idx=%d)", module_idx); + + ensure(module_idx < MODULE_INFOS.size()); + auto& [loaded_count, prx_id] = s_sysmodule_context->module_states[module_idx]; + const std::string_view& path = MODULE_INFOS[module_idx].path; + + cellSysmodule.notice("unload_module(): path=\"%s\", loaded_count=%d, prx_id=%d", path, loaded_count, prx_id); + + if (path.empty()) + { + return CELL_OK; + } + + if (ppu_execute<&sys_lwmutex_lock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex), 0) != CELL_OK) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + if (loaded_count == 1 || force_unload) + { + ensure(ppu_execute<&sys_prx_stop_module>(ppu, prx_id, 0, vm::null, +vm::make_var(0), 0, vm::null) == CELL_OK); // Not checked on LLE + ensure(ppu_execute<&sys_prx_unload_module>(ppu, prx_id, 0, vm::null) == CELL_OK); // Not checked on LLE + loaded_count = 0; + } + else if (loaded_count > 1) + { + loaded_count--; + } + + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return CELL_OK; +} + +extern error_code sysmoduleModuleStop(ppu_thread& ppu) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellSysmodule.notice("module_stop()"); + + if (s_sysmodule_context->unk_addr) + { + if (sys_memory_free(ppu, s_sysmodule_context->unk_addr) != CELL_OK) + { + return 1; // SYS_PRX_STOP_FAILED + } + + s_sysmodule_context->unk_addr = 0; + } + + if (ppu_execute<&sys_lwmutex_destroy>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) != CELL_OK) + { + return 1; // SYS_PRX_STOP_FAILED + } + + return CELL_OK; // SYS_PRX_STOP_OK +} + +extern error_code sysmoduleModuleStart(ppu_thread& ppu, u32 args, vm::ptr argp) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellSysmodule.notice("module_start(args=%d, argp=*0x%x)", args, argp); + + s_sysmodule_context->use_mem_container = false; + + if (const vm::var auth_id; sys_ss_access_control_engine(1, sys_process_getpid(), auth_id.addr()) == CELL_OK) + { + if ((*auth_id & 0x00ffffff) == 1 || (*auth_id & 0x00ffffff) == 2) + { + return 1; // SYS_PRX_NO_RESIDENT + } + } + else if (sys_ss_access_control_engine(2, auth_id.addr(), 0) != CELL_OK || *auth_id == PAID_44 || *auth_id == 0x1070000056000001ll) + { + return 1; // SYS_PRX_NO_RESIDENT + } + + const vm::var lwmutex_attr + {{ + .protocol = SYS_SYNC_PRIORITY, + .recursive = SYS_SYNC_NOT_RECURSIVE, + .name_u64 = "_smolwm"_u64 + }}; + + if (ppu_execute<&sys_lwmutex_create>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex), +lwmutex_attr) != CELL_OK) + { + return 1; // SYS_PRX_NO_RESIDENT + } + + // TODO replace with proper struct + const vm::var paramsfo(0x40); + std::memset(paramsfo.get_ptr(), 0, paramsfo.get_count()); + + error_code get_paramsfo_ret = ppu_execute<&sys_process_get_paramsfo>(ppu, +paramsfo); + const bool retail_gcm_sys = check_special_handling(ppu); + + // Load default modules + if (const vm::var sdk_version; + ensure(sys_process_get_sdk_version(sys_process_getpid(), sdk_version) == CELL_OK), // Not checked on LLE + *sdk_version < 0x330000) + { + for (const u16 module_id : DEFAULT_MODULES | std::views::take(5) ) + { + if (module_id == CELL_SYSMODULE_GCM_SYS) + { + if (get_paramsfo_ret == CELL_OK && read_from_ptr>(paramsfo.get_ptr(), 0x10) && !retail_gcm_sys) + { + if (load_module(ppu, GCM_SYS_DEH_IDX, 8, paramsfo + 0x28) != CELL_OK) + { + sysmoduleModuleStop(ppu); + return 1; // SYS_PRX_NO_RESIDENT + } + } + else + { + if (load_module(ppu, GCM_SYS_DEH_IDX + retail_gcm_sys, 0, vm::null) != CELL_OK) + { + sysmoduleModuleStop(ppu); + return 1; // SYS_PRX_NO_RESIDENT + } + } + + s_sysmodule_context->module_states[CELL_SYSMODULE_GCM_SYS].loaded_count = + s_sysmodule_context->module_states[GCM_SYS_DEH_IDX + retail_gcm_sys].loaded_count; + } + else + { + if (cellSysmoduleLoadModule(ppu, module_id) != CELL_OK) + { + sysmoduleModuleStop(ppu); + return 1; // SYS_PRX_NO_RESIDENT + } + } + } + } + else + { + // Build a path list of the default modules + const vm::var paths(PATH_MAX_SIZE * size32(DEFAULT_MODULES)); + const vm::var[]> path_list(size32(DEFAULT_MODULES)); + + for (u32 i = 0; i < DEFAULT_MODULES.size(); i++) + { + const std::string_view path = DEFAULT_MODULES[i] == CELL_SYSMODULE_GCM_SYS + ? MODULE_INFOS[GCM_SYS_DEH_IDX + retail_gcm_sys].path + : DEFAULT_MODULES[i] >= INTERNAL_MODULE_ID_BASE + ? MODULE_INFOS[INTERNAL_MODULES_OFFSET + (DEFAULT_MODULES[i] & INTERNAL_MODULE_ID_MASK)].path + : MODULE_INFOS[DEFAULT_MODULES[i]].path; + + std::memcpy(paths.get_ptr() + (i * PATH_MAX_SIZE), MODULE_BASE_PATH.data(), MODULE_BASE_PATH.size()); + std::memcpy(paths.get_ptr() + (i * PATH_MAX_SIZE) + MODULE_BASE_PATH.size(), path.data(), path.size() + 1); + + path_list[i] = paths + i * PATH_MAX_SIZE; + } + + // Load them all at once + const vm::var id_list(size32(DEFAULT_MODULES)); + + if (ppu_execute<&sys_prx_load_module_list>(ppu, DEFAULT_MODULES.size(), +path_list, 0, vm::null, +id_list) != CELL_OK) + { + sysmoduleModuleStop(ppu); + return 1; // SYS_PRX_NO_RESIDENT + } + + // Start the modules + for (const auto [module_id, prx_ix] : std::views::zip(DEFAULT_MODULES, std::span{ id_list.begin().get_ptr(), id_list.get_count() })) + { + if (module_id == CELL_SYSMODULE_GCM_SYS && read_from_ptr>(paramsfo.get_ptr(), 0x10) && !retail_gcm_sys) + { + s_sysmodule_context->module_states[CELL_SYSMODULE_GCM_SYS].prx_id = static_cast(prx_ix); + + if (ppu_execute<&sys_prx_start_module>(ppu, +prx_ix, 8, paramsfo + 0x28, +vm::make_var(0), 0, vm::null) != CELL_OK) + { + sysmoduleModuleStop(ppu); + return 1; // SYS_PRX_NO_RESIDENT + } + + s_sysmodule_context->module_states[CELL_SYSMODULE_GCM_SYS].loaded_count++; + } + else + { + const u8 module_idx = module_id >= INTERNAL_MODULE_ID_BASE ? INTERNAL_MODULES_OFFSET + (module_id & INTERNAL_MODULE_ID_MASK) : module_id; + + s_sysmodule_context->module_states[module_idx].prx_id = static_cast(prx_ix); + + if (ppu_execute<&sys_prx_start_module>(ppu, +prx_ix, 0, vm::null, +vm::make_var(0), 0, vm::null) != CELL_OK) + { + sysmoduleModuleStop(ppu); + return 1;// SYS_PRX_NO_RESIDENT + } + + s_sysmodule_context->module_states[module_idx].loaded_count++; + } + + if (module_id == CELL_SYSMODULE_GCM_SYS) + { + s_sysmodule_context->module_states[GCM_SYS_DEH_IDX + retail_gcm_sys].loaded_count = + s_sysmodule_context->module_states[CELL_SYSMODULE_GCM_SYS].loaded_count; + } + else if (module_id == CELL_SYSMODULE_FS) + { + s_sysmodule_context->module_states[FS_IDX + check_special_handling(ppu)].loaded_count = + s_sysmodule_context->module_states[CELL_SYSMODULE_FS].loaded_count; + } + } + + // LLE overwrites the return value of sys_process_get_paramsfo() here and won't return an error if the allocation fails + get_paramsfo_ret = sys_memory_allocate(ppu, (!retail_gcm_sys + 1) * 0x10000ull, SYS_MEMORY_PAGE_SIZE_64K, s_sysmodule_context.ptr(&sysmodule_context::unk_addr)); + } + + if (get_paramsfo_ret != CELL_OK) + { + return CELL_OK; // SYS_PRX_RESIDENT + } + + // Load additional modules if certain ExtraLoadFlags are set + + if (read_from_ptr>(paramsfo.get_ptr(), 0x10) && !retail_gcm_sys && read_from_ptr>(paramsfo.get_ptr(), 0x18)) + { + cellSysmoduleSetDebugmode(true); + + if (load_module(ppu, GPAD_IDX, 8, paramsfo + 0x28) != CELL_OK) + { + ensure(unload_module(ppu, GPAD_IDX) == CELL_OK); // Not checked on LLE + } + + cellSysmoduleSetDebugmode(false); + } + + if (read_from_ptr>(paramsfo.get_ptr(), 0x10) && read_from_ptr>(paramsfo.get_ptr(), 0x18)) + { + if (load_module(ppu, PROF_IDX, 0, vm::null) != CELL_OK) + { + ensure(unload_module(ppu, PROF_IDX) == CELL_OK); // Not checked on LLE + } + } + + if (read_from_ptr>(paramsfo.get_ptr(), 0x10)) + { + if (load_module(ppu, LV2COREDUMP_IDX, 0, vm::null) != CELL_OK) + { + ensure(unload_module(ppu, LV2COREDUMP_IDX) == CELL_OK); // Not checked on LLE + } + } + + if (read_from_ptr>(paramsfo.get_ptr(), 0x10)) + { + if (cellSysmoduleLoadModuleInternal(ppu, 0xf022) != CELL_OK) + { + sysmoduleModuleStop(ppu); + return 1; // SYS_PRX_NO_RESIDENT + } + } + + return CELL_OK; // SYS_PRX_RESIDENT } error_code cellSysmoduleInitialize() { - cellSysmodule.warning("cellSysmoduleInitialize()"); + cellSysmodule.notice("cellSysmoduleInitialize()"); + // Doesn't do anything return CELL_OK; } error_code cellSysmoduleFinalize() { - cellSysmodule.warning("cellSysmoduleFinalize()"); + cellSysmodule.notice("cellSysmoduleFinalize()"); + // Doesn't do anything return CELL_OK; } -error_code cellSysmoduleSetMemcontainer(u32 ct_id) +error_code cellSysmoduleSetMemcontainer(ppu_thread& ppu, u32 ct_id) { - cellSysmodule.todo("cellSysmoduleSetMemcontainer(ct_id=0x%x)", ct_id); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellSysmodule.notice("cellSysmoduleSetMemcontainer(ct_id=0x%x)", ct_id); + + if (ppu_execute<&sys_lwmutex_lock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex), 0) != CELL_OK) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + if (ct_id == SYS_MEMORY_CONTAINER_ID_INVALID) + { + s_sysmodule_context->use_mem_container = false; + } + else + { + if (const vm::var memory_info; + sys_memory_container_get_size(ppu, +memory_info, ct_id) != CELL_OK || memory_info->available_user_memory < 0x60000) + { + s_sysmodule_context->use_mem_container = false; + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return CELL_SYSMODULE_ERROR_INVALID_MEMCONTAINER; + } + + s_sysmodule_context->use_mem_container = true; + s_sysmodule_context->mem_container_id = ct_id; + } + + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE return CELL_OK; } -error_code cellSysmoduleLoadModule(u16 id) +error_code cellSysmoduleLoadModule(ppu_thread& ppu, u16 id) { - cellSysmodule.warning("cellSysmoduleLoadModule(id=0x%04X=%s)", id, get_module_id(id)); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; - const auto name = get_module_name(id); + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } - if (!name) + cellSysmodule.notice("cellSysmoduleLoadModule(id=0x%x)", id); + + if (id < INTERNAL_MODULES_OFFSET) + { + switch (id) + { + case CELL_SYSMODULE_FS: + { + const bool fs_155 = check_special_handling(ppu); + const error_code ret = load_module(ppu, FS_IDX + fs_155, 0, vm::null); + s_sysmodule_context->module_states[CELL_SYSMODULE_FS].loaded_count = s_sysmodule_context->module_states[FS_IDX + fs_155].loaded_count; + return ret; + } + case CELL_SYSMODULE_GCM_SYS: + { + const bool retail_gcm_sys = check_special_handling(ppu); + const error_code ret = load_module(ppu, GCM_SYS_DEH_IDX + retail_gcm_sys, 0, vm::null); + s_sysmodule_context->module_states[CELL_SYSMODULE_GCM_SYS].loaded_count = s_sysmodule_context->module_states[GCM_SYS_DEH_IDX + retail_gcm_sys].loaded_count; + return ret; + } + case CELL_SYSMODULE_SAIL: + { + if (check_special_handling(ppu)) + { + const vm::var sail_parameters(2); + sail_parameters[0] = 0x19; // Parameter type? Must be set to this, or else cellSail will not set the frame buffer release delay below (see module_start() of cellSail) + sail_parameters[1] = 3; // Delay the release of the frame buffer by three frames instead of two + return load_module(ppu, CELL_SYSMODULE_SAIL, 8, +sail_parameters); + } + + return load_module(ppu, CELL_SYSMODULE_SAIL, 0, vm::null); + } + case CELL_SYSMODULE_LV2DBG: + { + const bool cex_lv2dgb = check_special_handling(ppu); + const error_code ret = load_module(ppu, LV2_DBG_IDX + cex_lv2dgb, 0, vm::null); + s_sysmodule_context->module_states[CELL_SYSMODULE_LV2DBG].loaded_count = s_sysmodule_context->module_states[LV2_DBG_IDX + cex_lv2dgb].loaded_count; + return ret; + } + case 0x58: + { + return CELL_SYSMODULE_ERROR_UNKNOWN; + } + default: + { + // Load dependencies + for (const u16 module_id : MODULE_INFOS[id].dependencies) + { + if (module_id >= INTERNAL_MODULE_ID_BASE) + { + return CELL_SYSMODULE_ERROR_UNKNOWN; + } + + if (const error_code ret = cellSysmoduleLoadModule(ppu, module_id); + ret != CELL_OK && ret != static_cast(CELL_SYSMODULE_ERROR_DUPLICATED)) + { + return ret; + } + } + + return load_module(ppu, static_cast(id), 0, vm::null); + } + } + } + + if ((id & INTERNAL_MODULE_ID_MASK) >= INTERNAL_MODULES_COUNT + || !LOADABLE_INTERNAL_MODULES[id & INTERNAL_MODULE_ID_MASK]) { return CELL_SYSMODULE_ERROR_UNKNOWN; } - //if (Module<>* m = Emu.GetModuleManager().GetModuleById(id)) - //{ - // // CELL_SYSMODULE_ERROR_DUPLICATED shouldn't be returned - // m->Load(); - //} - - return CELL_OK; + return cellSysmoduleLoadModuleInternal(ppu, id); } -error_code cellSysmoduleUnloadModule(u16 id) +error_code cellSysmoduleUnloadModule(ppu_thread& ppu, u16 id) { - cellSysmodule.warning("cellSysmoduleUnloadModule(id=0x%04X=%s)", id, get_module_id(id)); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; - const auto name = get_module_name(id); + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } - if (!name) + cellSysmodule.notice("cellSysmoduleUnloadModule(id=0x%x)", id); + + if (id == 0x58) { return CELL_SYSMODULE_ERROR_UNKNOWN; } - //if (Module<>* m = Emu.GetModuleManager().GetModuleById(id)) - //{ - // if (!m->IsLoaded()) - // { - // cellSysmodule.error("cellSysmoduleUnloadModule() failed: module not loaded (id=0x%04x)", id); - // return CELL_SYSMODULE_ERROR_FATAL; - // } + if (id < INTERNAL_MODULES_OFFSET) + { + const vm::var sdk_version; + ensure(sys_process_get_sdk_version(sys_process_getpid(), sdk_version) == CELL_OK); // Not checked on LLE - // m->Unload(); - //} + // Do not unload a default module + for (const u16 module_id : DEFAULT_MODULES | std::views::take(*sdk_version < 0x330000 ? 5 : DEFAULT_MODULES.size())) + { + if (ppu_execute<&sys_lwmutex_lock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex), 0) != CELL_OK) + { + return CELL_SYSMODULE_ERROR_FATAL; + } - return CELL_OK; -} + if (module_id == id && s_sysmodule_context->module_states[id].loaded_count == 1) + { + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return CELL_OK; + } -error_code cellSysmoduleIsLoaded(u16 id) -{ - cellSysmodule.warning("cellSysmoduleIsLoaded(id=0x%04X=%s)", id, get_module_id(id)); + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + } - const auto name = get_module_name(id); + error_code ret; + switch (id) + { + case CELL_SYSMODULE_FS: + { + const bool fs_155 = check_special_handling(ppu); + ret = unload_module(ppu, FS_IDX + fs_155); + s_sysmodule_context->module_states[CELL_SYSMODULE_FS].loaded_count = s_sysmodule_context->module_states[FS_IDX + fs_155].loaded_count; + break; + } + case CELL_SYSMODULE_GCM_SYS: + { + const bool retail_gcm_sys = check_special_handling(ppu); + ret = unload_module(ppu, GCM_SYS_DEH_IDX + retail_gcm_sys); + s_sysmodule_context->module_states[CELL_SYSMODULE_GCM_SYS].loaded_count = s_sysmodule_context->module_states[GCM_SYS_DEH_IDX + retail_gcm_sys].loaded_count; + break; + } + case CELL_SYSMODULE_LV2DBG: + { + const bool cex_lv2dbg = check_special_handling(ppu); + ret = unload_module(ppu, LV2_DBG_IDX + cex_lv2dbg); + s_sysmodule_context->module_states[CELL_SYSMODULE_LV2DBG].loaded_count = s_sysmodule_context->module_states[LV2_DBG_IDX + cex_lv2dbg].loaded_count; + break; + } + default: + { + ret = unload_module(ppu, static_cast(id)); + } + } - if (!name) + if (ret < CELL_OK) + { + return ret; + } + + // Unload dependencies in reverse order + for (const u16 module_id : MODULE_INFOS[id].dependencies | std::views::reverse) + { + if (module_id >= INTERNAL_MODULE_ID_BASE) + { + return CELL_SYSMODULE_ERROR_UNKNOWN; + } + + if (const error_code ret = cellSysmoduleUnloadModule(ppu, module_id); ret != CELL_OK) + { + return ret; + } + } + + return CELL_OK; + } + + if ((id & INTERNAL_MODULE_ID_MASK) >= INTERNAL_MODULES_COUNT + || !LOADABLE_INTERNAL_MODULES[id & INTERNAL_MODULE_ID_MASK]) { return CELL_SYSMODULE_ERROR_UNKNOWN; } - //if (Module<>* m = Emu.GetModuleManager().GetModuleById(id)) - //{ - // if (!m->IsLoaded()) - // { - // cellSysmodule.warning("cellSysmoduleIsLoaded(): module not loaded (id=0x%04x)", id); - // return CELL_SYSMODULE_ERROR_UNLOADED; - // } - //} - - return CELL_SYSMODULE_LOADED; + return cellSysmoduleUnloadModuleInternal(ppu, id); } -error_code cellSysmoduleGetImagesize() +error_code cellSysmoduleIsLoaded(ppu_thread& ppu, u16 id) { - UNIMPLEMENTED_FUNC(cellSysmodule); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellSysmodule.notice("cellSysmoduleIsLoaded(id=0x%x)", id); + + if (id == 0x58) + { + return CELL_SYSMODULE_ERROR_UNKNOWN; + } + + if (id < INTERNAL_MODULES_OFFSET) + { + if (ppu_execute<&sys_lwmutex_lock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex), 0) != CELL_OK) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + const u16 module_id = MODULE_INFOS[id].path.empty() ? *MODULE_INFOS[id].dependencies.rbegin() : id; + const s32 loaded_count = s_sysmodule_context->module_states[module_id].loaded_count; + + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + + return loaded_count > 0 ? CELL_OK : not_an_error(CELL_SYSMODULE_ERROR_UNLOADED); + } + + if ((id & INTERNAL_MODULE_ID_MASK) >= INTERNAL_MODULES_COUNT + || !LOADABLE_INTERNAL_MODULES[id & INTERNAL_MODULE_ID_MASK]) + { + return CELL_SYSMODULE_ERROR_UNKNOWN; + } + + return cellSysmoduleIsLoadedEx(ppu, id); +} + +error_code cellSysmoduleGetImagesize(ppu_thread& ppu, u16 id, vm::ptr image_size) +{ + cellSysmodule.notice("cellSysmoduleGetImagesize(id=0x%x, image_size=*0x%x)", id, image_size); + + ensure(!!image_size); // Not checked on LLE + + *image_size = 0; + + if (id < UNK_MODULE_ID_BASE || (id & UNK_MODULE_ID_MASK) >= 2) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + const vm::var file_descriptor; + + if (const vm::var path = vm::make_str((id & UNK_MODULE_ID_MASK) == 0 ? "/dev_flash/sys/external/flashATRAC.pic" : ""); // The second string is empty on LLE + sys_fs_open(ppu, path, 0, file_descriptor, 0, vm::null, 0) != CELL_OK) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + const vm::var sb; + + if (sys_fs_fstat(ppu, *file_descriptor, sb) != CELL_OK) + { + ensure(sys_fs_close(ppu, *file_descriptor) == CELL_OK); // Not checked on LLE + return CELL_SYSMODULE_ERROR_FATAL; + } + + *image_size = static_cast(sb->size); + + ensure(sys_fs_close(ppu, *file_descriptor) == CELL_OK); // Not checked on LLE return CELL_OK; } -error_code cellSysmoduleFetchImage() +error_code cellSysmoduleFetchImage(ppu_thread& ppu, u16 id, vm::ptr image_buf, vm::ptr buf_size) { - UNIMPLEMENTED_FUNC(cellSysmodule); + cellSysmodule.notice("cellSysmoduleFetchImage(id=0x%x, image_buf=*0x%x, buf_size=*0x%x)", id, image_buf, buf_size); + + if (id < UNK_MODULE_ID_BASE) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + const vm::var file_descriptor; + + ensure(!!buf_size); // Not checked on LLE + + if (const vm::var path = vm::make_str((id & UNK_MODULE_ID_MASK) == 0 ? "/dev_flash/sys/external/flashATRAC.pic" : ""); // The second string is empty on LLE + (id & UNK_MODULE_ID_MASK) >= 2 || sys_fs_open(ppu, path, 0, file_descriptor, 0, vm::null, 0) != CELL_OK) + { + *buf_size = 0; + return CELL_SYSMODULE_ERROR_FATAL; + } + + const vm::var sb; + + if (sys_fs_fstat(ppu, *file_descriptor, sb) != CELL_OK) + { + ensure(sys_fs_close(ppu, *file_descriptor) == CELL_OK); // Not checked on LLE + *buf_size = 0; + return CELL_SYSMODULE_ERROR_FATAL; + } + + const u64 read_size = std::min(*buf_size, sb->size); + + if (const vm::var nread; sys_fs_read(ppu, *file_descriptor, image_buf, read_size, nread) != CELL_OK) + { + *buf_size = 0; + } + else + { + *buf_size = static_cast(*nread); + } + + ensure(sys_fs_close(ppu, *file_descriptor) == CELL_OK); // Not checked on LLE return CELL_OK; } -error_code cellSysmoduleUnloadModuleInternal() +error_code cellSysmoduleUnloadModuleInternal(ppu_thread& ppu, u16 id) { - UNIMPLEMENTED_FUNC(cellSysmodule); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellSysmodule.notice("cellSysmoduleUnloadModuleInternal(id=0x%x)", id); + + if (id < INTERNAL_MODULE_ID_BASE || id == CELL_SYSMODULE_INVALID || (id & INTERNAL_MODULE_ID_MASK) >= INTERNAL_MODULES_COUNT) + { + return CELL_SYSMODULE_ERROR_UNKNOWN; + } + + if (const error_code ret = unload_module(ppu, INTERNAL_MODULES_OFFSET + (id & INTERNAL_MODULE_ID_MASK)); ret != CELL_OK) + { + return ret; + } + + // Unload dependencies in reverse order + for (const u16 module_id : MODULE_INFOS[INTERNAL_MODULES_OFFSET + (id & INTERNAL_MODULE_ID_MASK)].dependencies | std::views::reverse) + { + if (const error_code ret = module_id < INTERNAL_MODULE_ID_BASE ? cellSysmoduleUnloadModule(ppu, id) : cellSysmoduleUnloadModuleInternal(ppu, id); ret != CELL_OK) + { + return ret; + } + } + return CELL_OK; } -error_code cellSysmoduleLoadModuleInternal() +error_code cellSysmoduleLoadModuleInternal(ppu_thread& ppu, u16 id) { - UNIMPLEMENTED_FUNC(cellSysmodule); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellSysmodule.notice("cellSysmoduleLoadModuleInternal(id=0x%x)", id); + + if (id < INTERNAL_MODULE_ID_BASE || id == CELL_SYSMODULE_INVALID || (id & INTERNAL_MODULE_ID_MASK) >= INTERNAL_MODULES_COUNT) + { + return CELL_SYSMODULE_ERROR_UNKNOWN; + } + + if (id == 0xf024 || id == 0xf03e || id == 0xf020) + { + // TODO replace with proper struct + const vm::var paramsfo(0x40); + std::memset(paramsfo.get_ptr(), 0, paramsfo.get_count()); + + if (ppu_execute<&sys_process_get_paramsfo>(ppu, +paramsfo) != CELL_OK + || (!read_from_ptr>(paramsfo.get_ptr(), 0x18) && !read_from_ptr>(paramsfo.get_ptr(), 0x30))) + { + return CELL_SYSMODULE_ERROR_UNKNOWN; + } + } + + // Load dependencies + for (const u16 module_id : MODULE_INFOS[INTERNAL_MODULES_OFFSET + (id & INTERNAL_MODULE_ID_MASK)].dependencies) + { + if (const error_code ret = module_id < INTERNAL_MODULE_ID_BASE ? cellSysmoduleLoadModule(ppu, id) : cellSysmoduleLoadModuleInternal(ppu, id); + ret != CELL_OK && ret != static_cast(CELL_SYSMODULE_ERROR_DUPLICATED)) + { + return ret; + } + } + + return load_module(ppu, INTERNAL_MODULES_OFFSET + (id & INTERNAL_MODULE_ID_MASK), 0, vm::null); +} + +error_code cellSysmoduleUnloadModuleEx(ppu_thread& ppu, u16 id) +{ + cellSysmodule.notice("cellSysmoduleUnloadModuleEx(id=0x%x)", id); + + if (id >= INTERNAL_MODULE_ID_BASE && (id & INTERNAL_MODULE_ID_MASK) < INTERNAL_MODULES_COUNT + && LOADABLE_INTERNAL_MODULES_EX[id & INTERNAL_MODULE_ID_MASK]) + { + return cellSysmoduleUnloadModuleInternal(ppu, id); + } + + return cellSysmoduleUnloadModuleInternal(ppu, CELL_SYSMODULE_INVALID); +} + +error_code cellSysmoduleLoadModuleEx(ppu_thread& ppu, u16 id) +{ + cellSysmodule.notice("cellSysmoduleLoadModuleEx(id=0x%x)", id); + + if (id >= INTERNAL_MODULE_ID_BASE && (id & INTERNAL_MODULE_ID_MASK) < INTERNAL_MODULES_COUNT + && LOADABLE_INTERNAL_MODULES_EX[id & INTERNAL_MODULE_ID_MASK]) + { + return cellSysmoduleLoadModuleInternal(ppu, id); + } + + return cellSysmoduleLoadModuleInternal(ppu, CELL_SYSMODULE_INVALID); +} + +error_code cellSysmoduleIsLoadedEx(ppu_thread& ppu, u16 id) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellSysmodule.notice("cellSysmoduleIsLoadedEx(id=0x%x)", id); + + if (id < INTERNAL_MODULE_ID_BASE || id == CELL_SYSMODULE_INVALID || (id & INTERNAL_MODULE_ID_MASK) >= INTERNAL_MODULES_COUNT) + { + return CELL_SYSMODULE_ERROR_UNKNOWN; + } + + if (ppu_execute<&sys_lwmutex_lock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex), 0) != CELL_OK) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + if (s_sysmodule_context->module_states[INTERNAL_MODULES_OFFSET + (id & INTERNAL_MODULE_ID_MASK)].loaded_count == 0) + { + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return not_an_error(CELL_SYSMODULE_ERROR_UNLOADED); + } + + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE return CELL_OK; } -error_code cellSysmoduleUnloadModuleEx() +error_code cellSysmoduleLoadModuleFile(ppu_thread& ppu, vm::cptr path, vm::ptr unk) { - UNIMPLEMENTED_FUNC(cellSysmodule); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellSysmodule.notice("cellSysmoduleLoadModuleFile(path=*0x%x=%s, unk=*0x%x)", path, path, unk); + + if (ppu_execute<&sys_lwmutex_lock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex), 0) != CELL_OK) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + ensure(!!unk); // Not checked on LLE + + const error_code ret = ppu_execute<&sys_prx_load_module>(ppu, path, 0, vm::null); + unk->prx_id = ret; + unk->unk = -1; + + if (ret < 1) + { + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return CELL_SYSMODULE_ERROR_FATAL; + } + + const vm::var file_name(0x200); + const vm::var segments(10); + const vm::var prx_module_info + {{ + .size = sizeof(sys_prx_module_info_t), + .filename = file_name, + .filename_size = file_name.get_count(), + .segments = segments, + .segments_num = segments.get_count() + }}; + + static_cast(ppu_execute<&sys_prx_get_module_info>(ppu, +unk->prx_id, 0, +prx_module_info)); + // LLE does "for (int i = 0; i < 0; i++) ..." after this, where the name of the prx would have been compared to "cellATRAC3enc_Library" and "cellATRACXenc_Library" + + if (const error_code ret = ppu_execute<&sys_prx_start_module>(ppu, +unk->prx_id, 0, vm::null, +vm::make_var(0), 0, vm::null); ret != CELL_OK) + { + ensure(ppu_execute<&sys_prx_unload_module>(ppu, +unk->prx_id, 0, vm::null) == CELL_OK); // Not checked on LLE + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return ret; + } + + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE return CELL_OK; } -error_code cellSysmoduleLoadModuleEx() +error_code cellSysmoduleUnloadModuleFile(ppu_thread& ppu, vm::cptr unk) { - UNIMPLEMENTED_FUNC(cellSysmodule); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellSysmodule.notice("cellSysmoduleUnloadModuleFile(unk=*0x%x)", unk); + + if (ppu_execute<&sys_lwmutex_lock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex), 0) != CELL_OK) + { + return CELL_SYSMODULE_ERROR_FATAL; + } + + ensure(!!unk); // Not checked on LLE + + if (ppu_execute<&sys_prx_stop_module>(ppu, +unk->prx_id, 0, vm::null, +vm::make_var(0), 0, vm::null) != CELL_OK + || ppu_execute<&sys_prx_unload_module>(ppu, +unk->prx_id, 0, vm::null) != CELL_OK) + { + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE + return CELL_SYSMODULE_ERROR_FATAL; + } + + ensure(unk->unk >= -1); // If this is not the case, LLE accesses its equivalent of sysmodule_context::modules_states with a negative index + + ensure(ppu_execute<&sys_lwmutex_unlock>(ppu, s_sysmodule_context.ptr(&sysmodule_context::mutex)) == CELL_OK); // Not checked on LLE return CELL_OK; } -error_code cellSysmoduleIsLoadedEx() +error_code cellSysmoduleSetDebugmode(u32 debug_mode) { - UNIMPLEMENTED_FUNC(cellSysmodule); - return CELL_OK; -} + cellSysmodule.notice("cellSysmoduleSetDebugmode(debug_mode=%d)", debug_mode); -error_code cellSysmoduleLoadModuleFile() -{ - UNIMPLEMENTED_FUNC(cellSysmodule); - return CELL_OK; -} + if (debug_mode == 1) + { + std::memcpy(s_sysmodule_context->module_base_path, MODULE_BASE_PATH_DEBUG.data(), MODULE_BASE_PATH_DEBUG.size() + 1); + s_sysmodule_context->module_base_path_size = static_cast(MODULE_BASE_PATH_DEBUG.size()); + } + else + { + std::memcpy(s_sysmodule_context->module_base_path, MODULE_BASE_PATH.data(), MODULE_BASE_PATH.size() + 1); + s_sysmodule_context->module_base_path_size = static_cast(MODULE_BASE_PATH.size()); + } -error_code cellSysmoduleUnloadModuleFile() -{ - UNIMPLEMENTED_FUNC(cellSysmodule); - return CELL_OK; -} - -error_code cellSysmoduleSetDebugmode() -{ - UNIMPLEMENTED_FUNC(cellSysmodule); return CELL_OK; } error_code cellSysmoduleSetInternalmode() { - UNIMPLEMENTED_FUNC(cellSysmodule); + cellSysmodule.notice("cellSysmoduleSetInternalmode()"); + // Doesn't do anything + return CELL_OK; +} + +error_code cellSysmodule_0x59521326(vm::ptr output) +{ + cellSysmodule.notice("cellSysmodule_0x59521326(output=*0x%x)", output); + + const vm::var psid; + + if (!output || sys_ss_get_open_psid(psid) != CELL_OK) + { + return -22; + } + + std::swap(psid->high, psid->low); + + u8 iv[0x10]; + std::memcpy(iv, psid.get_ptr(), sizeof(iv)); + + ensure(vm::check_addr(output.addr(), vm::page_readable | vm::page_writable, 0x10)); + + aescbc128_decrypt(KEY_UNK.data(), iv, reinterpret_cast(psid.get_ptr()), output.get_ptr(), 0x10); + return CELL_OK; } DECLARE(ppu_module_manager::cellSysmodule)("cellSysmodule", []() { + REG_VAR(cellSysmodule, s_sysmodule_context).flag(MFF_HIDDEN).init = [] + { + s_sysmodule_context->unk_addr = 0; + s_sysmodule_context->use_mem_container = false; + std::ranges::fill(s_sysmodule_context->module_states, module_state{ .loaded_count = 0, .prx_id = -1 }); + cellSysmoduleSetDebugmode(false); + }; + REG_FUNC(cellSysmodule, cellSysmoduleInitialize); REG_FUNC(cellSysmodule, cellSysmoduleFinalize); REG_FUNC(cellSysmodule, cellSysmoduleSetMemcontainer); @@ -449,4 +1393,8 @@ DECLARE(ppu_module_manager::cellSysmodule)("cellSysmodule", []() REG_FUNC(cellSysmodule, cellSysmoduleUnloadModuleFile); REG_FUNC(cellSysmodule, cellSysmoduleSetDebugmode); REG_FUNC(cellSysmodule, cellSysmoduleSetInternalmode); + REG_FNID(cellSysmodule, 0x59521326, cellSysmodule_0x59521326); + + REG_HIDDEN_FUNC(sysmoduleModuleStart); + REG_HIDDEN_FUNC(sysmoduleModuleStop); }); diff --git a/rpcs3/Emu/Cell/Modules/cellSysmodule.h b/rpcs3/Emu/Cell/Modules/cellSysmodule.h new file mode 100644 index 0000000000..7f3fa0fc6e --- /dev/null +++ b/rpcs3/Emu/Cell/Modules/cellSysmodule.h @@ -0,0 +1,144 @@ +#pragma once + +// Error codes +enum CellSysmoduleError +{ + CELL_SYSMODULE_ERROR_DUPLICATED = 0x80012001, + CELL_SYSMODULE_ERROR_UNKNOWN = 0x80012002, + CELL_SYSMODULE_ERROR_UNLOADED = 0x80012003, + CELL_SYSMODULE_ERROR_INVALID_MEMCONTAINER = 0x80012004, + CELL_SYSMODULE_ERROR_FATAL = 0x800120ff, +}; + +enum CellSysmoduleModuleID : u16 +{ + CELL_SYSMODULE_NET = 0x00, + CELL_SYSMODULE_HTTP = 0x01, + CELL_SYSMODULE_HTTP_UTIL = 0x02, + CELL_SYSMODULE_SSL = 0x03, + CELL_SYSMODULE_HTTPS = 0x04, + CELL_SYSMODULE_VDEC = 0x05, + CELL_SYSMODULE_ADEC = 0x06, + CELL_SYSMODULE_DMUX = 0x07, + CELL_SYSMODULE_VPOST = 0x08, + CELL_SYSMODULE_RTC = 0x09, + CELL_SYSMODULE_SPURS = 0x0a, + CELL_SYSMODULE_OVIS = 0x0b, + CELL_SYSMODULE_SHEAP = 0x0c, + CELL_SYSMODULE_SYNC = 0x0d, + CELL_SYSMODULE_FS = 0x0e, + CELL_SYSMODULE_JPGDEC = 0x0f, + CELL_SYSMODULE_GCM_SYS = 0x10, + CELL_SYSMODULE_AUDIO = 0x11, + CELL_SYSMODULE_PAMF = 0x12, + CELL_SYSMODULE_ATRAC3PLUS = 0x13, + CELL_SYSMODULE_NETCTL = 0x14, + CELL_SYSMODULE_SYSUTIL = 0x15, + CELL_SYSMODULE_SYSUTIL_NP = 0x16, + CELL_SYSMODULE_IO = 0x17, + CELL_SYSMODULE_PNGDEC = 0x18, + CELL_SYSMODULE_FONT = 0x19, + CELL_SYSMODULE_FONTFT = 0x1a, + CELL_SYSMODULE_FREETYPE = 0x1b, + CELL_SYSMODULE_USBD = 0x1c, + CELL_SYSMODULE_SAIL = 0x1d, + CELL_SYSMODULE_L10N = 0x1e, + CELL_SYSMODULE_RESC = 0x1f, + CELL_SYSMODULE_DAISY = 0x20, + CELL_SYSMODULE_KEY2CHAR = 0x21, + CELL_SYSMODULE_MIC = 0x22, + CELL_SYSMODULE_CAMERA = 0x23, + CELL_SYSMODULE_VDEC_MPEG2 = 0x24, + CELL_SYSMODULE_VDEC_AVC = 0x25, + CELL_SYSMODULE_ADEC_LPCM = 0x26, + CELL_SYSMODULE_ADEC_AC3 = 0x27, + CELL_SYSMODULE_ADEC_ATX = 0x28, + CELL_SYSMODULE_ADEC_AT3 = 0x29, + CELL_SYSMODULE_DMUX_PAMF = 0x2a, + CELL_SYSMODULE_VDEC_AL = 0x2b, + CELL_SYSMODULE_ADEC_AL = 0x2c, + CELL_SYSMODULE_DMUX_AL = 0x2d, + CELL_SYSMODULE_LV2DBG = 0x2e, + // 0x2f + CELL_SYSMODULE_USBPSPCM = 0x30, + CELL_SYSMODULE_AVCONF_EXT = 0x31, + CELL_SYSMODULE_SYSUTIL_USERINFO = 0x32, + CELL_SYSMODULE_SYSUTIL_SAVEDATA = 0x33, + CELL_SYSMODULE_SUBDISPLAY = 0x34, + CELL_SYSMODULE_SYSUTIL_REC = 0x35, + CELL_SYSMODULE_VIDEO_EXPORT = 0x36, + CELL_SYSMODULE_SYSUTIL_GAME_EXEC = 0x37, + CELL_SYSMODULE_SYSUTIL_NP2 = 0x38, + CELL_SYSMODULE_SYSUTIL_AP = 0x39, + CELL_SYSMODULE_SYSUTIL_NP_CLANS = 0x3a, + CELL_SYSMODULE_SYSUTIL_OSK_EXT = 0x3b, + CELL_SYSMODULE_VDEC_DIVX = 0x3c, + CELL_SYSMODULE_JPGENC = 0x3d, + CELL_SYSMODULE_SYSUTIL_GAME = 0x3e, + CELL_SYSMODULE_BGDL = 0x3f, + CELL_SYSMODULE_FREETYPE_TT = 0x40, + CELL_SYSMODULE_SYSUTIL_VIDEO_UPLOAD = 0x41, + CELL_SYSMODULE_SYSUTIL_SYSCONF_EXT = 0x42, + CELL_SYSMODULE_FIBER = 0x43, + CELL_SYSMODULE_SYSUTIL_NP_COMMERCE2 = 0x44, + CELL_SYSMODULE_SYSUTIL_NP_TUS = 0x45, + CELL_SYSMODULE_VOICE = 0x46, + CELL_SYSMODULE_ADEC_CELP8 = 0x47, + CELL_SYSMODULE_CELP8ENC = 0x48, + CELL_SYSMODULE_SYSUTIL_LICENSEAREA = 0x49, + CELL_SYSMODULE_SYSUTIL_MUSIC2 = 0x4a, + // 0x4b + // 0x4c + // 0x4d + CELL_SYSMODULE_SYSUTIL_SCREENSHOT = 0x4e, + CELL_SYSMODULE_SYSUTIL_MUSIC_DECODE = 0x4f, + CELL_SYSMODULE_SPURS_JQ = 0x50, + // 0x51 + CELL_SYSMODULE_PNGENC = 0x52, + CELL_SYSMODULE_SYSUTIL_MUSIC_DECODE2 = 0x53, + // 0x54 + CELL_SYSMODULE_SYNC2 = 0x55, + CELL_SYSMODULE_SYSUTIL_NP_UTIL = 0x56, + CELL_SYSMODULE_RUDP = 0x57, + // 0x58 = Invalid + CELL_SYSMODULE_SYSUTIL_NP_SNS = 0x59, + CELL_SYSMODULE_GEM = 0x5a, + CELL_SYSMODULE_SYSUTIL_CROSS_CONTROLLER = 0x5c, + + // Internal modules + CELL_SYSMODULE_CELPENC = 0xf00a, + CELL_SYSMODULE_GIFDEC = 0xf010, + CELL_SYSMODULE_ADEC_CELP = 0xf019, + CELL_SYSMODULE_ADEC_M2BC = 0xf01b, + CELL_SYSMODULE_ADEC_M4AAC = 0xf01d, + CELL_SYSMODULE_ADEC_MP3 = 0xf01e, + CELL_SYSMODULE_IMEJP = 0xf023, + CELL_SYSMODULE_SYSUTIL_MUSIC = 0xf028, + CELL_SYSMODULE_PHOTO_EXPORT = 0xf029, + CELL_SYSMODULE_PRINT = 0xf02a, + CELL_SYSMODULE_PHOTO_IMPORT = 0xf02b, + CELL_SYSMODULE_MUSIC_EXPORT = 0xf02c, + CELL_SYSMODULE_PHOTO_DECODE = 0xf02e, + CELL_SYSMODULE_SYSUTIL_SEARCH = 0xf02f, + CELL_SYSMODULE_SYSUTIL_AVCHAT2 = 0xf030, + CELL_SYSMODULE_SAIL_REC = 0xf034, + CELL_SYSMODULE_SYSUTIL_NP_TROPHY = 0xf035, + CELL_SYSMODULE_LIBATRAC3MULTI = 0xf054, + + CELL_SYSMODULE_INVALID = 0xffff +}; + +struct SysmoduleUnk +{ + be_t prx_id; + be_t unk; +}; + +CHECK_SIZE_ALIGN(SysmoduleUnk, 8, 4); + +error_code cellSysmoduleLoadModule(ppu_thread& ppu, u16 id); +error_code cellSysmoduleLoadModuleInternal(ppu_thread& ppu, u16 id); +error_code cellSysmoduleUnloadModule(ppu_thread& ppu, u16 id); +error_code cellSysmoduleUnloadModuleInternal(ppu_thread& ppu, u16 id); +error_code cellSysmoduleIsLoadedEx(ppu_thread& ppu, u16 id); +error_code cellSysmoduleSetDebugmode(u32 debug_mode); diff --git a/rpcs3/Emu/Cell/Modules/cellVideoExport.cpp b/rpcs3/Emu/Cell/Modules/cellVideoExport.cpp index 8c0ed93771..30ede6e7a3 100644 --- a/rpcs3/Emu/Cell/Modules/cellVideoExport.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVideoExport.cpp @@ -76,7 +76,7 @@ struct video_export }; -bool check_movie_path(const std::string& file_path) +bool check_movie_path(std::string_view file_path) { if (file_path.size() >= CELL_VIDEO_EXPORT_UTIL_HDD_PATH_MAX) { diff --git a/rpcs3/Emu/Cell/Modules/libmixer.cpp b/rpcs3/Emu/Cell/Modules/libmixer.cpp index e92b5c6cf4..74b05b8d0b 100644 --- a/rpcs3/Emu/Cell/Modules/libmixer.cpp +++ b/rpcs3/Emu/Cell/Modules/libmixer.cpp @@ -35,49 +35,49 @@ struct SurMixerConfig { std::mutex mutex; - u32 audio_port; - s32 priority; - u32 ch_strips_1; - u32 ch_strips_2; - u32 ch_strips_6; - u32 ch_strips_8; + u32 audio_port = 0; + s32 priority = 0; + u32 ch_strips_1 = 0; + u32 ch_strips_2 = 0; + u32 ch_strips_6 = 0; + u32 ch_strips_8 = 0; - vm::ptr cb; - vm::ptr cb_arg; + vm::ptr cb {}; + vm::ptr cb_arg {}; - f32 mixdata[8 * 256]; - u64 mixcount; + f32 mixdata[8 * 256] {}; + u64 mixcount = 0; }; struct SSPlayer { - bool m_created; // SSPlayerCreate/Remove - bool m_connected; // AANConnect/Disconnect - bool m_active; // SSPlayerPlay/Stop - u32 m_channels; // 1 or 2 - u32 m_addr; - u32 m_samples; - u32 m_loop_start; - u32 m_loop_mode; - u32 m_position; - float m_level; - float m_speed; - float m_x; - float m_y; - float m_z; + bool m_created = false; // SSPlayerCreate/Remove + bool m_connected = false; // AANConnect/Disconnect + bool m_active = false; // SSPlayerPlay/Stop + u32 m_channels = 0; // 1 or 2 + u32 m_addr = 0; + u32 m_samples = 0; + u32 m_loop_start = 0; + u32 m_loop_mode = 0; + u32 m_position = 0; + f32 m_level = 0.0f; + f32 m_speed = 0.0f; + f32 m_x = 0.0f; + f32 m_y = 0.0f; + f32 m_z = 0.0f; }; // TODO: use fxm -SurMixerConfig g_surmx; +SurMixerConfig g_surmx {}; std::vector g_ssp; -s32 cellAANAddData(u32 aan_handle, u32 aan_port, u32 offset, vm::ptr addr, u32 samples) +s32 cellAANAddData(u32 aan_handle, u32 aan_port, u32 offset, vm::ptr addr, u32 samples) { libmixer.trace("cellAANAddData(aan_handle=0x%x, aan_port=0x%x, offset=0x%x, addr=*0x%x, samples=%d)", aan_handle, aan_port, offset, addr, samples); u32 type = aan_port >> 16; - u32 port = aan_port & 0xffff; + const u32 port = aan_port & 0xffff; switch (type) { @@ -94,7 +94,8 @@ s32 cellAANAddData(u32 aan_handle, u32 aan_port, u32 offset, vm::ptr addr if (port >= g_surmx.ch_strips_8) type = 0; break; default: - type = 0; break; + type = 0; + break; } if (aan_handle != 0x11111111 || samples != 256 || !type || offset != 0) @@ -110,7 +111,7 @@ s32 cellAANAddData(u32 aan_handle, u32 aan_port, u32 offset, vm::ptr addr // mono upmixing for (u32 i = 0; i < samples; i++) { - const float center = addr[i]; + const f32 center = addr[i]; g_surmx.mixdata[i * 8 + 0] += center; g_surmx.mixdata[i * 8 + 1] += center; } @@ -120,8 +121,8 @@ s32 cellAANAddData(u32 aan_handle, u32 aan_port, u32 offset, vm::ptr addr // stereo upmixing for (u32 i = 0; i < samples; i++) { - const float left = addr[i * 2 + 0]; - const float right = addr[i * 2 + 1]; + const f32 left = addr[i * 2 + 0]; + const f32 right = addr[i * 2 + 1]; g_surmx.mixdata[i * 8 + 0] += left; g_surmx.mixdata[i * 8 + 1] += right; } @@ -131,12 +132,12 @@ s32 cellAANAddData(u32 aan_handle, u32 aan_port, u32 offset, vm::ptr addr // 5.1 upmixing for (u32 i = 0; i < samples; i++) { - const float left = addr[i * 6 + 0]; - const float right = addr[i * 6 + 1]; - const float center = addr[i * 6 + 2]; - const float low_freq = addr[i * 6 + 3]; - const float rear_left = addr[i * 6 + 4]; - const float rear_right = addr[i * 6 + 5]; + const f32 left = addr[i * 6 + 0]; + const f32 right = addr[i * 6 + 1]; + const f32 center = addr[i * 6 + 2]; + const f32 low_freq = addr[i * 6 + 3]; + const f32 rear_left = addr[i * 6 + 4]; + const f32 rear_right = addr[i * 6 + 5]; g_surmx.mixdata[i * 8 + 0] += left; g_surmx.mixdata[i * 8 + 1] += right; g_surmx.mixdata[i * 8 + 2] += center; @@ -205,13 +206,13 @@ s32 cellSSPlayerCreate(vm::ptr handle, vm::ptr config) std::lock_guard lock(g_surmx.mutex); - SSPlayer p; + SSPlayer p {}; p.m_created = true; p.m_connected = false; p.m_active = false; p.m_channels = config->channels; - g_ssp.push_back(p); + g_ssp.push_back(std::move(p)); *handle = ::size32(g_ssp) - 1; return CELL_OK; } @@ -366,7 +367,7 @@ struct surmixer_thread : ppu_thread { //u64 stamp0 = get_guest_system_time(); - memset(g_surmx.mixdata, 0, sizeof(g_surmx.mixdata)); + std::memset(g_surmx.mixdata, 0, sizeof(g_surmx.mixdata)); if (g_surmx.cb) { g_surmx.cb(*this, g_surmx.cb_arg, static_cast(g_surmx.mixcount), 256); @@ -381,10 +382,10 @@ struct surmixer_thread : ppu_thread for (auto& p : g_ssp) if (p.m_active && p.m_created) { auto v = vm::ptrl::make(p.m_addr); // 16-bit LE audio data - float left = 0.0f; - float right = 0.0f; - float speed = std::fabs(p.m_speed); - float fpos = 0.0f; + f32 left = 0.0f; + f32 right = 0.0f; + f32 speed = std::fabs(p.m_speed); + f32 fpos = 0.0f; for (s32 i = 0; i < 256; i++) if (p.m_active) { u32 pos = p.m_position; @@ -454,7 +455,7 @@ struct surmixer_thread : ppu_thread //u64 stamp2 = get_guest_system_time(); - auto buf = vm::_ptr(port.addr.addr() + (g_surmx.mixcount % port.num_blocks) * port.num_channels * AUDIO_BUFFER_SAMPLES * sizeof(float)); + auto buf = vm::_ptr(port.addr.addr() + (g_surmx.mixcount % port.num_blocks) * port.num_channels * AUDIO_BUFFER_SAMPLES * sizeof(f32)); for (auto& mixdata : g_surmx.mixdata) { @@ -497,7 +498,7 @@ s32 cellSurMixerCreate(vm::cptr config) port->num_channels = 8; port->num_blocks = 16; port->attr = 0; - port->size = port->num_channels * port->num_blocks * AUDIO_BUFFER_SAMPLES * sizeof(float); + port->size = port->num_channels * port->num_blocks * AUDIO_BUFFER_SAMPLES * sizeof(f32); port->level = 1.0f; port->level_set.store({ 1.0f, 0.0f }); @@ -574,7 +575,7 @@ s32 cellSurMixerStart() return CELL_OK; } -s32 cellSurMixerSetParameter(u32 param, float value) +s32 cellSurMixerSetParameter(u32 param, f32 value) { libmixer.todo("cellSurMixerSetParameter(param=0x%x, value=%f)", param, value); return CELL_OK; @@ -596,7 +597,7 @@ s32 cellSurMixerFinalize() return CELL_OK; } -s32 cellSurMixerSurBusAddData(u32 busNo, u32 offset, vm::ptr addr, u32 samples) +s32 cellSurMixerSurBusAddData(u32 busNo, u32 offset, vm::ptr addr, u32 samples) { if (busNo < 8 && samples == 256 && offset == 0) { diff --git a/rpcs3/Emu/Cell/Modules/libmixer.h b/rpcs3/Emu/Cell/Modules/libmixer.h index 724ca121f3..f311a65ea0 100644 --- a/rpcs3/Emu/Cell/Modules/libmixer.h +++ b/rpcs3/Emu/Cell/Modules/libmixer.h @@ -41,8 +41,8 @@ enum CELL_SURMIXER_PARAM_REVERBLEVEL = 41, // in dB }; -static const float CELL_SURMIXER_CONT_MUTEON = 1.0; -static const float CELL_SURMIXER_CONT_MUTEOFF = 0.0; +static constexpr f32 CELL_SURMIXER_CONT_MUTEON = 1.0f; +static constexpr f32 CELL_SURMIXER_CONT_MUTEOFF = 0.0f; enum { @@ -137,15 +137,15 @@ struct CellSSPlayerCommonParam struct CellSurMixerPosition { - be_t x; - be_t y; - be_t z; + be_t x; + be_t y; + be_t z; }; struct CellSSPlayerRuntimeInfo { - be_t level; - be_t speed; + be_t level; + be_t speed; CellSurMixerPosition position; }; @@ -163,6 +163,6 @@ struct CellSurMixerChStripParam be_t param; be_t attribute_addr; be_t dBSwitch; - be_t floatVal; + be_t floatVal; be_t intVal; }; diff --git a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp index 866db860ec..dc6add3ebf 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp @@ -166,7 +166,7 @@ struct sce_np_trophy_manager sce_np_trophy_manager() = default; sce_np_trophy_manager(utils::serial& ar) - : is_initialized(ar) + : is_initialized(ar.pop()) { } diff --git a/rpcs3/Emu/Cell/Modules/sysPrxForUser.cpp b/rpcs3/Emu/Cell/Modules/sysPrxForUser.cpp index 483fcfe1e9..5b91733596 100644 --- a/rpcs3/Emu/Cell/Modules/sysPrxForUser.cpp +++ b/rpcs3/Emu/Cell/Modules/sysPrxForUser.cpp @@ -61,7 +61,7 @@ s32 sys_process_is_stack(u32 p) return (p >> 28) == 0xD; } -error_code sys_process_get_paramsfo(vm::ptr buffer) +error_code sys_process_get_paramsfo([[maybe_unused]] ppu_thread& ppu, vm::ptr buffer) { sysPrxForUser.warning("sys_process_get_paramsfo(buffer=*0x%x)", buffer); diff --git a/rpcs3/Emu/Cell/Modules/sysPrxForUser.h b/rpcs3/Emu/Cell/Modules/sysPrxForUser.h index 8870277d55..5a05e2c079 100644 --- a/rpcs3/Emu/Cell/Modules/sysPrxForUser.h +++ b/rpcs3/Emu/Cell/Modules/sysPrxForUser.h @@ -63,5 +63,18 @@ error_code sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, u32 en error_code sys_interrupt_thread_disestablish(ppu_thread& ppu, u32 ih); void sys_ppu_thread_exit(ppu_thread& CPU, u64 val); +error_code sys_process_get_paramsfo(ppu_thread& ppu, vm::ptr buffer); void sys_game_process_exitspawn(ppu_thread& ppu, vm::cptr path, vm::cpptr argv, vm::cpptr envp, u32 data, u32 data_size, s32 prio, u64 flags); void sys_game_process_exitspawn2(ppu_thread& ppu, vm::cptr path, vm::cpptr argv, vm::cpptr envp, u32 data, u32 data_size, s32 prio, u64 flags); + +struct sys_prx_load_module_option_t; +struct sys_prx_unload_module_option_t; +struct sys_prx_module_info_t; + +error_code sys_prx_load_module(ppu_thread& ppu, vm::cptr path, u64 flags, vm::ptr pOpt); +error_code sys_prx_load_module_on_memcontainer(ppu_thread& ppu, vm::cptr path, u32 mem_ct, u64 flags, vm::ptr pOpt); +error_code sys_prx_load_module_list(ppu_thread& ppu, s32 count, vm::cpptr path_list, u64 flags, vm::ptr pOpt, vm::ptr id_list); +error_code sys_prx_start_module(ppu_thread& ppu, u32 id, u32 args, vm::ptr argp, vm::ptr result, u64 flags, vm::ptr pOpt); +error_code sys_prx_stop_module(ppu_thread& ppu, u32 id, u32 args, vm::ptr argp, vm::ptr result, u64 flags, vm::ptr pOpt); +error_code sys_prx_unload_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr pOpt); +error_code sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr info); diff --git a/rpcs3/Emu/Cell/PPUAnalyser.cpp b/rpcs3/Emu/Cell/PPUAnalyser.cpp index 783ed9e477..608e213ab6 100644 --- a/rpcs3/Emu/Cell/PPUAnalyser.cpp +++ b/rpcs3/Emu/Cell/PPUAnalyser.cpp @@ -1191,7 +1191,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, con continue; } - vm::cptr sec_end = vm::cast(sec.addr + sec.size); + vm::cptr sec_end = vm::cast(sec.addr + sec.size); // Probe for (vm::cptr _ptr = vm::cast(sec.addr); _ptr < sec_end;) @@ -1212,7 +1212,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, con break; } - if (size % 4 || size < 0x10 || _ptr + size / 4 > sec_end) + if (size % 4 || size < 0x10 || static_cast(sec_end - _ptr) < size / 4) { sec_end.set(0); break; diff --git a/rpcs3/Emu/Cell/PPUInterpreter.cpp b/rpcs3/Emu/Cell/PPUInterpreter.cpp index a3c6611b15..3fa6d09f7b 100644 --- a/rpcs3/Emu/Cell/PPUInterpreter.cpp +++ b/rpcs3/Emu/Cell/PPUInterpreter.cpp @@ -501,7 +501,7 @@ auto ppu_feed_data(ppu_thread& ppu, u64 addr) if (raddr / 128 == addr / 128) src = &ppu.rdata[addr & 127], size = std::min(128 - (addr % 128), sizeof(T)); else - src = &ppu.rdata[0], size = (addr + u32{sizeof(T)}) % 127, offs = sizeof(T) - size; + src = &ppu.rdata[0], size = (addr + u32{sizeof(T)}) % 128, offs = sizeof(T) - size; if (std::memcmp(buffer + offs, src, size)) { diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index a4b94f5d51..15ff9b1fa8 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -51,6 +51,7 @@ std::unordered_map& ppu_module_manager::get() std::vector g_ppu_function_names; atomic_t liblv2_begin = 0, liblv2_end = 0; +atomic_t libusbd_active = false; extern u32 ppu_generate_id(std::string_view name) { @@ -1928,6 +1929,10 @@ shared_ptr ppu_load_prx(const ppu_prx_object& elf, bool virtual_load, c liblv2_begin = prx->segs[0].addr; liblv2_end = prx->segs[0].addr + prx->segs[0].size; } + if (prx->path.ends_with("sys/external/libusbd.sprx"sv)) + { + libusbd_active = true; + } std::vector applied; @@ -2062,6 +2067,10 @@ void ppu_unload_prx(const lv2_prx& prx) liblv2_begin = 0; liblv2_end = 0; } + if (prx.path.ends_with("sys/external/libusbd.sprx"sv)) + { + libusbd_active = false; + } // Format patch name std::string hash = fmt::format("PRX-%s", fmt::base57(prx.sha1)); diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index f5d91cc519..d291dad18e 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -64,6 +64,7 @@ #include "util/v128.hpp" #include "util/simd.hpp" #include "util/sysinfo.hpp" +#include "util/fnv_hash.hpp" #include "Utilities/sema.h" @@ -161,7 +162,7 @@ bool serialize(utils::serial& ar, typename ppu_thread::cr_b } else { - o.unpack(ar); + o.unpack(ar.pop()); } return true; @@ -589,7 +590,7 @@ u32 ppu_read_mmio_aware_u32(u8* vm_base, u32 eal) } // Value is assumed to be swapped - return read_from_ptr(vm_base + eal); + return read_from_ptr(vm_base, eal); } void ppu_write_mmio_aware_u32(u8* vm_base, u32 eal, u32 value) @@ -2425,7 +2426,7 @@ ppu_thread::~ppu_thread() } ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u32 _prio, int detached) - : cpu_thread(idm::last_id()) + : cpu_thread(idm::last_id()) , stack_size(param.stack_size) , stack_addr(param.stack_addr) , joiner(detached != 0 ? ppu_join_status::detached : ppu_join_status::joinable) @@ -2525,11 +2526,11 @@ struct save_lv2_tag }; ppu_thread::ppu_thread(utils::serial& ar) - : cpu_thread(idm::last_id()) // last_id() is showed to constructor on serialization + : cpu_thread(idm::last_id()) // last_id<>() is shown to constructor on serialization , stack_size(ar) , stack_addr(ar) , joiner(ar.pop()) - , entry_func(std::bit_cast(ar)) + , entry_func(std::bit_cast(ar.pop())) , is_interrupt_thread(ar) { [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(ppu); @@ -3472,9 +3473,6 @@ struct jit_core_allocator // Initialize global semaphore with the max number of threads ::semaphore<0x7fff> sem{std::max(thread_count, 1)}; - // Mutex for special extra-large modules to compile alone - shared_mutex shared_mtx; - static s16 limit() { return static_cast(std::min(0x7fff, utils::get_thread_count())); @@ -3500,11 +3498,28 @@ namespace std::unordered_map map; }; - std::array buckets; + std::array buckets; bucket_t& get_bucket(std::string_view sv) { - return buckets[std::hash()(sv) % std::size(buckets)]; + const std::string& cache_path = fs::get_cache_dir(); + + if (sv.starts_with(cache_path)) + { + sv = sv.substr(cache_path.size()); + } + + const usz hash = rpcs3::hash_array(sv.data(), sv.size()); + + usz final_index = 0; + + for (usz i = 0; i < sizeof(hash); i++) + { + final_index ^= (hash >> (i * 8)) % 256; + } + + final_index ^= sv.size(); + return buckets[final_index % std::size(buckets)]; } jit_module& get(const std::string& name) @@ -4127,9 +4142,12 @@ extern void ppu_precompile(std::vector& dir_queue, std::vectorget().sem); + if (auto prx = ppu_load_prx(obj, true, path, offset)) { obj.clear(), src.close(); // Clear decrypted file and elf object memory + //.unlock(); ppu_initialize(*prx, false, file_size); ppu_finalize(*prx, true); continue; @@ -4623,9 +4641,6 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s // Info to load to main JIT instance (true - compiled) std::vector> link_workload; - // Sync variable to acquire workloads - atomic_t work_cv = 0; - bool compiled_new = false; bool has_mfvscr = false; @@ -5177,7 +5192,7 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s settings += ppu_settings::contains_symbol_resolver; // Avoid invalidating all modules for this purpose // Write version, hash, CPU, settings - fmt::append(obj_name, "v7-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu)); + fmt::append(obj_name, "v7-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string())); } if (cpu ? cpu->state.all_of(cpu_flag::exit) : Emu.IsStopped()) @@ -5232,12 +5247,16 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s // Create worker threads for compilation if (!workload.empty()) { + // Sync variable to acquire workloads + atomic_t work_cv = 0; + atomic_t work_done = 0; + // Update progress dialog g_progr_ptotal += ::size32(workload); *progress_dialog = get_localized_string(localized_string_id::PROGRESS_DIALOG_COMPILING_PPU_MODULES); - const u32 thread_count = std::min(::size32(workload), rpcs3::utils::get_max_threads()); + const u32 thread_count = std::max(std::min(::size32(workload), rpcs3::utils::get_max_threads()), 1) - 1; struct thread_index_allocator { @@ -5246,7 +5265,8 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s struct thread_op { - atomic_t& work_cv; + const std::add_pointer_t> work_cv; + const std::add_pointer_t> work_done; std::vector>>& workload; const ppu_module& main_module; const std::string& cache_path; @@ -5254,10 +5274,11 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s std::unique_lock core_lock; - thread_op(atomic_t& work_cv, std::vector>>& workload + thread_op(atomic_t* _work_cv, atomic_t* _work_done, std::vector>>& workload , const cpu_thread* cpu, const ppu_module& main_module, const std::string& cache_path, decltype(jit_core_allocator::sem)& sem) noexcept - : work_cv(work_cv) + : work_cv(_work_cv) + , work_done(_work_done) , workload(workload) , main_module(main_module) , cache_path(cache_path) @@ -5269,6 +5290,7 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s thread_op(const thread_op& other) noexcept : work_cv(other.work_cv) + , work_done(other.work_done) , workload(other.workload) , main_module(other.main_module) , cache_path(other.cache_path) @@ -5291,7 +5313,7 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s #ifdef __APPLE__ pthread_jit_write_protect_np(false); #endif - for (u32 i = work_cv++; i < workload.size(); i = work_cv++, g_progr_pdone++) + for (u32 i = (*work_cv)++; i < workload.size(); i = (*work_cv)++, (*work_done)++, g_progr_pdone++) { if (cpu ? cpu->state.all_of(cpu_flag::exit) : Emu.IsStopped()) { @@ -5301,24 +5323,11 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s // Keep allocating workload const auto& [obj_name, part] = std::as_const(workload)[i]; - std::shared_lock rlock(g_fxo->get().shared_mtx, std::defer_lock); - std::unique_lock lock(g_fxo->get().shared_mtx, std::defer_lock); - - if (false && part.jit_bounds && part.parent->funcs.size() >= 0x8000) - { - // Make a large symbol-resolving function compile alone because it has massive memory requirements - lock.lock(); - } - else - { - rlock.lock(); - } - ppu_log.warning("LLVM: Compiling module %s%s", cache_path, obj_name); { // Use another JIT instance - jit_compiler jit2({}, g_cfg.core.llvm_cpu, 0x1); + jit_compiler jit2({}, g_cfg.core.llvm_cpu.to_string(), 0x1); ppu_initialize2(jit2, part, cache_path, obj_name); } @@ -5326,32 +5335,66 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s } core_lock.unlock(); + + #ifdef __APPLE__ + pthread_jit_write_protect_np(true); + #endif } }; // Prevent watchdog thread from terminating g_watchdog_hold_ctr++; - named_thread_group threads(fmt::format("PPUW.%u.", ++g_fxo->get().index), thread_count - , thread_op(work_cv, workload, cpu, info, cache_path, g_fxo->get().sem) - , [&](u32 /*thread_index*/, thread_op& op) + const std::string worker_group_name = fmt::format("PPUW.%u.", ++g_fxo->get().index); + const auto try_lock_thread = [&](u32 thread_index, thread_op& op) { + const bool to_lock = (thread_index + *op.work_done) < workload.size() && (cpu ? !cpu->state.all_of(cpu_flag::exit) : !Emu.IsStopped()); + + if (!to_lock) + { + return false; + } + // Allocate "core" op.core_lock.lock(); // Second check before creating another thread - return work_cv < workload.size() && (cpu ? !cpu->state.all_of(cpu_flag::exit) : !Emu.IsStopped()); - }); + const bool to_unlock = !((thread_index + *op.work_done) < workload.size() && (cpu ? !cpu->state.all_of(cpu_flag::exit) : !Emu.IsStopped())); + + if (to_unlock) + { + op.core_lock.unlock(); + return false; + } + + return true; + }; + + named_thread_group threads(worker_group_name, thread_count + , thread_op(&work_cv, &work_done, workload, cpu, info, cache_path, g_fxo->get().sem) + , try_lock_thread); + + const auto old_name = thread_ctrl::get_name(); + thread_ctrl::set_name(worker_group_name + std::to_string(thread_count + 1)); + + thread_op cur_op(&work_cv, &work_done, workload, cpu, info, cache_path, g_fxo->get().sem); + + if (try_lock_thread(thread_count, cur_op)) + { + // Recycle current thread: reduce overall thrread count + cur_op(); + } threads.join(); - + + thread_ctrl::set_name(old_name); g_watchdog_hold_ctr--; } // Initialize compiler instance while (jits.size() < utils::aligned_div(module_counter, c_moudles_per_jit) && is_being_used_in_emulation) { - jits.emplace_back(std::make_shared(s_link_table, g_cfg.core.llvm_cpu, 0, symbols_cement)); + jits.emplace_back(std::make_shared(s_link_table, g_cfg.core.llvm_cpu.to_string(), 0, symbols_cement)); for (const auto& [addr, func] : *shared_map) { diff --git a/rpcs3/Emu/Cell/PPUThread.h b/rpcs3/Emu/Cell/PPUThread.h index cf5b91c487..9a135c00c7 100644 --- a/rpcs3/Emu/Cell/PPUThread.h +++ b/rpcs3/Emu/Cell/PPUThread.h @@ -436,20 +436,6 @@ struct ppu_gpr_cast_impl> } }; -template -struct ppu_gpr_cast_impl> -{ - static inline u64 to(const vm::_ref_base& value) - { - return ppu_gpr_cast_impl::to(value.addr()); - } - - static inline vm::_ref_base from(const u64 reg) - { - return vm::cast(ppu_gpr_cast_impl::from(reg)); - } -}; - template <> struct ppu_gpr_cast_impl { diff --git a/rpcs3/Emu/Cell/PPUTranslator.cpp b/rpcs3/Emu/Cell/PPUTranslator.cpp index 0205715328..7cba32d9f7 100644 --- a/rpcs3/Emu/Cell/PPUTranslator.cpp +++ b/rpcs3/Emu/Cell/PPUTranslator.cpp @@ -961,7 +961,7 @@ void PPUTranslator::WriteMemory(Value* addr, Value* value, bool is_be, u32 align m_ir->CreateAlignedStore(value, GetMemory(addr), llvm::MaybeAlign{align})->setVolatile(true); } -void PPUTranslator::CompilationError(const std::string& error) +void PPUTranslator::CompilationError(std::string_view error) { ppu_log.error("LLVM: [0x%08x] Error: %s", m_addr + (m_reloc ? m_reloc->addr : 0), error); } @@ -1642,6 +1642,16 @@ void PPUTranslator::VPERM(ppu_opcode_t op) { const auto [a, b, c] = get_vrs(op.va, op.vb, op.vc); +#ifdef ARCH_ARM64 + if (op.ra == op.rb) + { + set_vr(op.vd, tbl(a, (~c & 0xf))); + return; + } + + set_vr(op.vd, tbl2(b, a, (~c & 0x1f))); + return; +#else if (op.ra == op.rb) { set_vr(op.vd, pshufb(a, ~c & 0xf)); @@ -1657,6 +1667,7 @@ void PPUTranslator::VPERM(ppu_opcode_t op) const auto i = eval(~c & 0x1f); set_vr(op.vd, select(noncast(c << 3) >= 0, pshufb(a, i), pshufb(b, i))); +#endif } void PPUTranslator::VPKPX(ppu_opcode_t op) diff --git a/rpcs3/Emu/Cell/PPUTranslator.h b/rpcs3/Emu/Cell/PPUTranslator.h index 010945656e..be84d8061e 100644 --- a/rpcs3/Emu/Cell/PPUTranslator.h +++ b/rpcs3/Emu/Cell/PPUTranslator.h @@ -334,7 +334,7 @@ public: } // Handle compilation errors - void CompilationError(const std::string& error); + void CompilationError(std::string_view error); PPUTranslator(llvm::LLVMContext& context, llvm::Module* _module, const ppu_module& info, llvm::ExecutionEngine& engine); ~PPUTranslator(); diff --git a/rpcs3/Emu/Cell/RawSPUThread.cpp b/rpcs3/Emu/Cell/RawSPUThread.cpp index 81bb6fd89e..1d1f659be0 100644 --- a/rpcs3/Emu/Cell/RawSPUThread.cpp +++ b/rpcs3/Emu/Cell/RawSPUThread.cpp @@ -427,6 +427,12 @@ void spu_load_exec(const spu_exec_object& elf) { if (prog.p_type == 0x1u /* LOAD */ && prog.p_memsz) { + if (prog.p_vaddr >= SPU_LS_SIZE || prog.p_filesz > SPU_LS_SIZE - prog.p_vaddr) + { + spu_log.error("spu_load_exec: skipping segment with vaddr=0x%x filesz=0x%x (exceeds LS=0x%x)", prog.p_vaddr, prog.p_filesz, static_cast(SPU_LS_SIZE)); + continue; + } + std::memcpy(spu->_ptr(prog.p_vaddr), prog.bin.data(), prog.p_filesz); } } @@ -496,6 +502,12 @@ void spu_load_rel_exec(const spu_rel_object& elf) { if (shdr.sh_type == sec_type::sht_progbits && shdr.sh_flags().all_of(sh_flag::shf_alloc)) { + if (offs >= SPU_LS_SIZE || shdr.sh_size > SPU_LS_SIZE - offs) + { + spu_log.error("spu_load_rel_exec: skipping section at offs=0x%x sh_size=0x%x (exceeds LS=0x%x)", offs, shdr.sh_size, static_cast(SPU_LS_SIZE)); + break; + } + std::memcpy(spu->_ptr(offs), shdr.get_bin().data(), shdr.sh_size); offs = utils::align(offs + shdr.sh_size, 4); } diff --git a/rpcs3/Emu/Cell/SPUASMJITRecompiler.cpp b/rpcs3/Emu/Cell/SPUASMJITRecompiler.cpp index 5100bc3ded..9c59469904 100644 --- a/rpcs3/Emu/Cell/SPUASMJITRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUASMJITRecompiler.cpp @@ -1863,10 +1863,18 @@ void spu_recompiler::SHL(spu_opcode_t op) void spu_recompiler::ROTH(spu_opcode_t op) //nf { + const XmmLink& va = XmmGet(op.ra, XmmType::Int); + const XmmLink& vb = XmmGet(op.rb, XmmType::Int); + + if (utils::has_avx512_icl()) + { + c->vpshldvw(va, va, vb); + c->vmovdqa(SPU_OFF_128(gpr, op.rt), va); + return; + } + if (utils::has_avx512()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); const XmmLink& v4 = XmmAlloc(); c->vmovdqa(v4, XmmConst(v128::from32r(0x0d0c0d0c, 0x09080908, 0x05040504, 0x01000100))); @@ -1883,29 +1891,42 @@ void spu_recompiler::ROTH(spu_opcode_t op) //nf if (utils::has_xop()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); c->vprotw(vt, va, vb); c->movdqa(SPU_OFF_128(gpr, op.rt), vt); return; } - for (u32 i = 0; i < 8; i++) // unrolled loop - { - c->movzx(qw0->r32(), SPU_OFF_16(gpr, op.ra, &v128::_u16, i)); - c->movzx(asmjit::x86::ecx, SPU_OFF_16(gpr, op.rb, &v128::_u16, i)); - c->rol(qw0->r16(), asmjit::x86::cl); - c->mov(SPU_OFF_16(gpr, op.rt, &v128::_u16, i), qw0->r16()); - } + // Turn shift amount into power of 2 using a float's exponent + // Calculating a negative power makes truncation cheaper + const XmmLink& v1 = XmmAlloc(); + const XmmLink& v2 = XmmAlloc(); + c->psllw(vb, 12); + c->psrlw(vb, 5); + c->paddw(vb, XmmConst(v128::from16p(std::bit_cast(-1.0f) >> 16))); + c->pxor(v1, v1); + c->pxor(v2, v2); + c->punpcklwd(v1, vb); // "shifts" the amount into the exponent + c->punpckhwd(v2, vb); + c->cvttps2dq(v1, v1); + c->cvttps2dq(v2, v2); + c->packssdw(v1, v2); // positive power (1<<15) would have saturated + c->pxor(vb, vb); + c->psubw(vb, v1); + c->movdqa(v2, va); + c->pmullw(v2, vb); + c->pmulhuw(va, vb); + c->por(va, v2); + c->vmovdqa(SPU_OFF_128(gpr, op.rt), va); } void spu_recompiler::ROTHM(spu_opcode_t op) { + const XmmLink& va = XmmGet(op.ra, XmmType::Int); + const XmmLink& vb = XmmGet(op.rb, XmmType::Int); + if (utils::has_avx512()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); c->psubw(vb, XmmConst(v128::from16p(1))); c->pandn(vb, XmmConst(v128::from16p(0x1f))); @@ -1916,8 +1937,6 @@ void spu_recompiler::ROTHM(spu_opcode_t op) if (utils::has_avx2()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); const XmmLink& v4 = XmmAlloc(); const XmmLink& v5 = XmmAlloc(); @@ -1936,8 +1955,6 @@ void spu_recompiler::ROTHM(spu_opcode_t op) if (utils::has_xop()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); c->psubw(vb, XmmConst(v128::from16p(1))); c->pandn(vb, XmmConst(v128::from16p(0x1f))); @@ -1950,22 +1967,40 @@ void spu_recompiler::ROTHM(spu_opcode_t op) return; } - for (u32 i = 0; i < 8; i++) // unrolled loop - { - c->movzx(qw0->r32(), SPU_OFF_16(gpr, op.ra, &v128::_u16, i)); - c->movzx(asmjit::x86::ecx, SPU_OFF_16(gpr, op.rb, &v128::_u16, i)); - c->neg(asmjit::x86::ecx); - c->shr(qw0->r32(), asmjit::x86::cl); - c->mov(SPU_OFF_16(gpr, op.rt, &v128::_u16, i), qw0->r16()); - } + // Turn shift amount into power of 2 using a float's exponent + // Amount is `-amt % 32`, so make power zero when < 16 + const float exp_offset = -std::exp2(-16.0f); + + const XmmLink& vt = XmmAlloc(); + const XmmLink& v1 = XmmAlloc(); + const XmmLink& v2 = XmmAlloc(); + c->psllw(vb, 11); + c->psrlw(vb, 4); + c->movdqa(vt, vb); + c->paddw(vb, XmmConst(v128::from16p(std::bit_cast(exp_offset) >> 16))); + c->pxor(v1, v1); + c->pxor(v2, v2); + c->punpcklwd(v1, vb); // "shifts" the amount into the exponent + c->punpckhwd(v2, vb); + c->cvttps2dq(v1, v1); + c->cvttps2dq(v2, v2); + c->packssdw(v1, v2); // positive power (1<<15) would have saturated + c->pxor(vb, vb); + c->pcmpeqw(vt, vb); + c->pand(vt, va); + c->psubw(vb, v1); + c->pmulhuw(va, vb); // high multiply "shifts" right by 16 + c->por(va, vt); // (amt == 0)? x : (x >> (32 - amt)) + c->vmovdqa(SPU_OFF_128(gpr, op.rt), va); } void spu_recompiler::ROTMAH(spu_opcode_t op) { + const XmmLink& va = XmmGet(op.ra, XmmType::Int); + const XmmLink& vb = XmmGet(op.rb, XmmType::Int); + if (utils::has_avx512()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); c->psubw(vb, XmmConst(v128::from16p(1))); c->pandn(vb, XmmConst(v128::from16p(0x1f))); @@ -1976,8 +2011,6 @@ void spu_recompiler::ROTMAH(spu_opcode_t op) if (utils::has_avx2()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); const XmmLink& v4 = XmmAlloc(); const XmmLink& v5 = XmmAlloc(); @@ -1998,8 +2031,6 @@ void spu_recompiler::ROTMAH(spu_opcode_t op) if (utils::has_xop()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); c->psubw(vb, XmmConst(v128::from16p(1))); c->pandn(vb, XmmConst(v128::from16p(0x1f))); @@ -2011,22 +2042,44 @@ void spu_recompiler::ROTMAH(spu_opcode_t op) return; } - for (u32 i = 0; i < 8; i++) // unrolled loop - { - c->movsx(qw0->r32(), SPU_OFF_16(gpr, op.ra, &v128::_u16, i)); - c->movzx(asmjit::x86::ecx, SPU_OFF_16(gpr, op.rb, &v128::_u16, i)); - c->neg(asmjit::x86::ecx); - c->sar(qw0->r32(), asmjit::x86::cl); - c->mov(SPU_OFF_16(gpr, op.rt, &v128::_u16, i), qw0->r16()); - } + // Turn shift amount into power of 2 using a float's exponent + // Amount is `-amt % 32`, so clamp to sign fill when < 16 + const float exp_offset = -std::exp2(-17.0f); // source is pre-shifted later + + const XmmLink& vt = XmmAlloc(); + const XmmLink& v1 = XmmAlloc(); + const XmmLink& v2 = XmmAlloc(); + c->psllw(vb, 11); + c->psrlw(vb, 4); + c->movdqa(vt, vb); + c->pmaxsw(vb, XmmConst(v128::from16p(17 << (23-16)))); // large shifts fill with sign + c->paddw(vb, XmmConst(v128::from16p(std::bit_cast(exp_offset) >> 16))); + c->pxor(v1, v1); + c->pxor(v2, v2); + c->pcmpeqw(vt, v1); + c->punpcklwd(v1, vb); // "shifts" the amount into the exponent + c->punpckhwd(v2, vb); + c->cvttps2dq(v1, v1); + c->cvttps2dq(v2, v2); + c->packssdw(v1, v2); // positive power (1<<15) would have saturated + c->psrlw(vt, 1); + c->psubw(vt, v1); // (amt == 0)? (1<<15) : -negPow2 + c->movdqa(vb, va); + c->psraw(vb, 15); + c->paddw(va, va); // pre-shift so `mulhi(x, 1<<15) = x >> 0` (already have sign bit) + c->pmullw(vb, vt); + c->pmulhuw(va, vt); + c->por(va, vb); // fshr((x>>15), x, amt) + c->vmovdqa(SPU_OFF_128(gpr, op.rt), va); } void spu_recompiler::SHLH(spu_opcode_t op) { + const XmmLink& va = XmmGet(op.ra, XmmType::Int); + const XmmLink& vb = XmmGet(op.rb, XmmType::Int); + if (utils::has_avx512()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); c->pand(vb, XmmConst(v128::from16p(0x1f))); c->vpsllvw(vt, va, vb); @@ -2036,8 +2089,6 @@ void spu_recompiler::SHLH(spu_opcode_t op) if (utils::has_avx2()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); const XmmLink& v4 = XmmAlloc(); const XmmLink& v5 = XmmAlloc(); @@ -2055,8 +2106,6 @@ void spu_recompiler::SHLH(spu_opcode_t op) if (utils::has_xop()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); - const XmmLink& vb = XmmGet(op.rb, XmmType::Int); const XmmLink& vt = XmmAlloc(); c->pand(vb, XmmConst(v128::from16p(0x1f))); c->vpcmpgtw(vt, vb, XmmConst(v128::from16p(15))); @@ -2066,13 +2115,24 @@ void spu_recompiler::SHLH(spu_opcode_t op) return; } - for (u32 i = 0; i < 8; i++) // unrolled loop - { - c->movzx(qw0->r32(), SPU_OFF_16(gpr, op.ra, &v128::_u16, i)); - c->movzx(asmjit::x86::ecx, SPU_OFF_16(gpr, op.rb, &v128::_u16, i)); - c->shl(qw0->r32(), asmjit::x86::cl); - c->mov(SPU_OFF_16(gpr, op.rt, &v128::_u16, i), qw0->r16()); - } + // Turn shift amount into power of 2 using a float's exponent + // Arithmetic shift can make power smaller than one + const XmmLink& v1 = XmmAlloc(); + const XmmLink& v2 = XmmAlloc(); + c->psllw(vb, 11); + c->psraw(vb, 4); // when amt > 15, exp -= 16 + c->paddw(vb, XmmConst(v128::from16p(std::bit_cast(-1.0f) >> 16))); + c->pxor(v1, v1); + c->pxor(v2, v2); + c->punpcklwd(v1, vb); + c->punpckhwd(v2, vb); + c->cvttps2dq(v1, v1); + c->cvttps2dq(v2, v2); + c->packssdw(v1, v2); // positive power (1<<15) would have saturated + c->pxor(vb, vb); + c->psubw(vb, va); // can negate either + c->pmullw(vb, v1); + c->movdqa(SPU_OFF_128(gpr, op.rt), vb); } void spu_recompiler::ROTI(spu_opcode_t op) @@ -2799,32 +2859,35 @@ void spu_recompiler::FREST(spu_opcode_t op) void spu_recompiler::FRSQEST(spu_opcode_t op) { const XmmLink& va = XmmGet(op.ra, XmmType::Float); + const XmmLink& vz = XmmAlloc(); + const XmmLink& v1 = XmmAlloc(); const XmmLink& v_fraction = XmmAlloc(); const XmmLink& v_exponent = XmmAlloc(); c->movdqa(v_fraction, va); c->movdqa(v_exponent, va); + // (exponent==0)? 0xFF : 190 - (exponent + 1) / 2 + c->paddd(v_exponent, v_exponent); + c->pxor(vz, vz); + c->pavgb(v_exponent, vz); + c->movdqa(v1, XmmConst(v128::from32p(190 << 24))); + c->psubb(v1, v_exponent); + c->pcmpeqb(v_exponent, vz); + c->por(v_exponent, v1); + c->psrld(v_exponent, 1); + c->pand(v_exponent, XmmConst(v128::from32p(0xFF << 23))); + c->psrld(v_fraction, 18); - c->psrld(v_exponent, 23); - - c->andps(v_fraction, XmmConst(v128::from32p(0x3F))); - c->andps(v_exponent, XmmConst(v128::from32p(0xFF))); - + c->pand(v_fraction, XmmConst(v128::from32p(0x3F))); const u64 fraction_lut_addr = reinterpret_cast(spu_frsqest_fraction_lut); - const u64 exponent_lut_addr = reinterpret_cast(spu_frsqest_exponent_lut); c->movabs(*arg0, fraction_lut_addr); - c->movabs(*arg1, exponent_lut_addr); for (u32 index = 0; index < 4; index++) { c->pextrd(*qw0, v_fraction, index); c->mov(*qw1, asmjit::x86::dword_ptr(*arg0, *qw0, 2)); c->pinsrd(v_fraction, *qw1, index); - - c->pextrd(*qw0, v_exponent, index); - c->mov(*qw1, asmjit::x86::dword_ptr(*arg1, *qw0, 2)); - c->pinsrd(v_exponent, *qw1, index); } c->orps(v_fraction, v_exponent); @@ -3343,23 +3406,31 @@ void spu_recompiler::HGT(spu_opcode_t op) void spu_recompiler::CLZ(spu_opcode_t op) { + const XmmLink& va = XmmGet(op.ra, XmmType::Int); + if (utils::has_avx512()) { - const XmmLink& va = XmmGet(op.ra, XmmType::Int); const XmmLink& vt = XmmAlloc(); c->vplzcntd(vt, va); c->movdqa(SPU_OFF_128(gpr, op.rt), vt); return; } - c->mov(qw0->r32(), 32 + 31); - for (u32 i = 0; i < 4; i++) // unrolled loop - { - c->bsr(*addr, SPU_OFF_32(gpr, op.ra, &v128::_u32, i)); - c->cmovz(*addr, qw0->r32()); - c->xor_(*addr, 31); - c->mov(SPU_OFF_32(gpr, op.rt, &v128::_u32, i), *addr); - } + // Use signed conversion to float, as exponent is ilog2 + // "Negative" values are zeroed due to saturation subtract + constexpr u32 exp_bias = 127; + + const XmmLink& vf = XmmAlloc(); + const XmmLink& v1 = XmmAlloc(); + c->cvtdq2ps(vf, va); // only correct with round-towards-zero + c->psrld(vf, 23); + c->pxor(v1, v1); + c->pcmpeqd(v1, va); + c->pand(v1, XmmConst(v128::from32p(32 ^ (31 + exp_bias)))); + c->pxor(v1, XmmConst(v128::from32p(31 + exp_bias))); + c->psubusw(v1, vf); // (x==0)? 32 : 31 - (exponent - exp_bias) + c->movdqa(SPU_OFF_128(gpr, op.rt), v1); + return; } void spu_recompiler::XSWD(spu_opcode_t op) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 7c21ad7aab..dbb7e048f8 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -82,6 +82,94 @@ void fmt_class_string::format(std::strin }); } +#ifdef ARCH_ARM64 +constexpr const char s_spu_llvm_reg_scavenge_error[] = "Cannot scavenge register without an emergency spill slot"; + +class spu_llvm_compile_scope +{ +public: + spu_llvm_compile_scope(spu_llvm_compile_context& context, bool use_tbl2) noexcept + { + context = {}; + context.use_tbl2 = use_tbl2; + spu_llvm_set_compile_context(&context); + } + + ~spu_llvm_compile_scope() noexcept + { + spu_llvm_set_compile_context(nullptr); + } +}; + +static spu_program analyse_spu_llvm_program(spu_recompiler_base& compiler, const spu_program& program) +{ + std::vector> ls(SPU_LS_SIZE / sizeof(be_t)); + + for (u32 i = 0, pos = program.lower_bound; i < program.data.size(); i++, pos += 4) + { + ls[pos / 4] = std::bit_cast>(program.data[i]); + } + + return compiler.analyse(ls.data(), program.entry_point); +} + +static spu_function_t compile_spu_llvm_with_retry(std::unique_ptr& compiler, const spu_program& program) +{ + spu_llvm_compile_context context; + + { + spu_llvm_compile_scope scope(context, true); + + if (const auto result = compiler->compile(spu_program{program})) + { + return result; + } + } + + if (context.llvm_error.find(s_spu_llvm_reg_scavenge_error) == std::string::npos) + { + if (!context.llvm_error.empty()) + { + spu_log.error("LLVM failed to compile SPU block 0x%x: %s", program.entry_point, context.llvm_error); + } + + return nullptr; + } + + spu_log.warning("LLVM failed to compile SPU block 0x%x with TBL2/TBX2: %s. Retrying without TBL2/TBX2.", program.entry_point, context.llvm_error); + + // LLVM fatal recovery does not unwind MCJIT state. Abandon the failed + // compiler and retry from a fresh analysis/JIT instance. + static_cast(compiler.release()); + compiler = spu_recompiler_base::make_llvm_recompiler(); + compiler->init(); + + const auto retry_program = analyse_spu_llvm_program(*compiler, program); + + if (retry_program != program) + { + spu_log.error("[0x%05x] SPU analyser failed during TBL2/TBX2 retry, %u vs %u", retry_program.entry_point, retry_program.data.size(), program.data.size()); + return nullptr; + } + + spu_llvm_compile_context retry_context; + spu_llvm_compile_scope scope(retry_context, false); + + const auto result = compiler->compile(spu_program{retry_program}); + + if (result) + { + spu_log.notice("SPU LLVM block 0x%x compiled successfully without TBL2/TBX2.", program.entry_point); + } + else if (!retry_context.llvm_error.empty()) + { + spu_log.error("LLVM failed to compile SPU block 0x%x without TBL2/TBX2: %s", program.entry_point, retry_context.llvm_error); + } + + return result; +} +#endif + // Move 4 args for calling native function from a GHC calling convention function #if defined(ARCH_X64) static u8* move_args_ghc_to_native(u8* raw) @@ -558,18 +646,32 @@ spu_cache::~spu_cache() extern void utilize_spu_data_segment(u32 vaddr, const void* ls_data_vaddr, u32 size) { - if (vaddr % 4) + if (u32 rem = vaddr % 4) { - return; + // The remainder that it needs to be aligned up to the next DWORD + rem = 4 - rem; + + if (size < rem) + { + return; + } + + vaddr += rem; + ls_data_vaddr = reinterpret_cast(ls_data_vaddr) + rem; + size -= rem; } size &= -4; - if (!size || vaddr + size > SPU_LS_SIZE) + if (!size || vaddr >= SPU_LS_SIZE) { return; } + // Let SPU block search mistakes pass through + // SPU code mining is too important + size = std::min(SPU_LS_SIZE - vaddr, size); + if (!g_cfg.core.llvm_precompilation) { return; @@ -857,7 +959,20 @@ void spu_cache::initialize(bool build_existing_cache) named_thread_group workers("SPU Worker ", worker_count, [&]() -> uint { #ifdef __APPLE__ + // Apple Silicon W^X: enable JIT write mode for this worker and + // pair it with an RAII guard so execute mode is restored on + // every exit path (return, exception, etc.). Leaving a worker + // in write mode at teardown can leave per-thread state + // inconsistent on AArch64. pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; #endif // Set low priority thread_ctrl::scoped_priority low_prio(-1); @@ -893,6 +1008,15 @@ void spu_cache::initialize(bool build_existing_cache) compiler->init(); + auto compile_program = [&](spu_program&& program) -> spu_function_t + { +#ifdef ARCH_ARM64 + return compile_spu_llvm_with_retry(compiler, program); +#else + return compiler->compile(std::move(program)); +#endif + }; + // Counter for error reporting u32 logged_error = 0; @@ -964,7 +1088,7 @@ void spu_cache::initialize(bool build_existing_cache) logged_error++; } } - else if (!compiler->compile(std::move(func2))) + else if (!compile_program(std::move(func2))) { // Likely, out of JIT memory. Signal to prevent further building. fail_flag |= 1; @@ -1062,7 +1186,7 @@ void spu_cache::initialize(bool build_existing_cache) const u32 last_inst = std::bit_cast>(func2.data.back()); const u32 prog_size = ::size32(func2.data); - if (!compiler->compile(std::move(func2))) + if (!compile_program(std::move(func2))) { // Likely, out of JIT memory. Signal to prevent further building. fail_flag |= 1; @@ -1209,7 +1333,7 @@ void spu_cache::initialize(bool build_existing_cache) } // Initialize global cache instance - if (g_cfg.core.spu_cache && cache) + if (g_cfg.core.spu_cache && !spu_precompilation_enabled && cache) { g_fxo->get() = std::move(cache); } @@ -1269,8 +1393,8 @@ spu_runtime::spu_runtime() fs::remove_all(m_cache_path + "llvm/", false); } - fs::file(m_cache_path + "spu.log", fs::rewrite); - fs::file(m_cache_path + "spu-ir.log", fs::rewrite); + fs::write_file(m_cache_path + "spu.log", fs::rewrite); + fs::write_file(m_cache_path + "spu-ir.log", fs::rewrite); } } @@ -2083,7 +2207,12 @@ void spu_recompiler_base::dispatch(spu_thread& spu, void*, u8* rip) return; } - const auto func = spu.jit->compile(spu.jit->analyse(spu._ptr(0), spu.pc)); + auto program = spu.jit->analyse(spu._ptr(0), spu.pc); +#ifdef ARCH_ARM64 + const auto func = compile_spu_llvm_with_retry(spu.jit, program); +#else + const auto func = spu.jit->compile(std::move(program)); +#endif if (!func) { @@ -2249,11 +2378,22 @@ std::vector spu_thread::discover_functions(u32 base_addr, std::span(base_addr, 0x10); i < std::min(base_addr + ::size32(ls), 0x3FFF0); i += 0x10) + for (u32 i = base_addr, end_ls = std::min(base_addr + ::size32(ls), SPU_LS_SIZE); i < end_ls; i = utils::align(i + 1, 0x10)) { // Search for BRSL LR and BRASL LR or BR // TODO: BISL - const v128 inst = read_from_ptr>(ls.data(), i - base_addr); + be_t inst_be{}; + + if (end_ls - i < 16) + { + std::memcpy(&inst_be, ls.data() + (i - base_addr), end_ls - i); + } + else + { + inst_be = read_from_ptr>(ls, i - base_addr); + } + + const v128 inst = inst_be; const v128 cleared_i16 = gv_and32(inst, v128::from32p(std::rotl(~0xffff, 7))); const v128 eq_brsl = gv_eq32(cleared_i16, v128::from32p(0x66u << 23)); const v128 eq_brasl = gv_eq32(cleared_i16, brasl_mask); @@ -2855,10 +2995,6 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s u32 lsa = entry_point; u32 limit = SPU_LS_SIZE; - if (g_cfg.core.spu_block_size == spu_block_size_type::giga) - { - } - // Weak constant propagation context (for guessing branch targets) std::array, 128> vflags{}; @@ -2892,7 +3028,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Check for redundancy if (!m_block_info[target / 4]) { - m_block_info[target / 4] = true; + m_block_info.set(target / 4, true); workload.push_back(target); } @@ -3067,7 +3203,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } else { - m_entry_info[target / 4] = true; + m_entry_info.set(target / 4, true); add_block(target); } } @@ -3078,8 +3214,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (!is_no_return && sl && g_cfg.core.spu_block_size != spu_block_size_type::safe) { - m_ret_info[pos / 4 + 1] = true; - m_entry_info[pos / 4 + 1] = true; + m_ret_info.set(pos / 4 + 1, true); + m_entry_info.set(pos / 4 + 1, true); m_targets[pos].push_back(pos + 4); add_block(pos + 4); } @@ -3202,10 +3338,13 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s jt_abs.clear(); } + // If this fails, this is a TODO to compare them in another way ensure(jt_abs.size() != jt_rel.size()); } - if (jt_abs.size() >= jt_rel.size()) + const bool abs_domainates = jt_abs.size() > jt_rel.size(); + + if (abs_domainates) { const u32 new_size = (start - lsa) / 4 + ::size32(jt_abs); @@ -3223,8 +3362,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s m_targets.emplace(pos, std::move(jt_abs)); } - - if (jt_rel.size() >= jt_abs.size()) + else { const u32 new_size = (start - lsa) / 4 + ::size32(jt_rel); @@ -3292,8 +3430,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } else { - m_ret_info[pos / 4 + 1] = true; - m_entry_info[pos / 4 + 1] = true; + m_ret_info.set(pos / 4 + 1, true); + m_entry_info.set(pos / 4 + 1, true); m_targets[pos].push_back(pos + 4); add_block(pos + 4); } @@ -3360,15 +3498,15 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (!is_no_return && g_cfg.core.spu_block_size != spu_block_size_type::safe) { - m_ret_info[pos / 4 + 1] = true; - m_entry_info[pos / 4 + 1] = true; + m_ret_info.set(pos / 4 + 1, true); + m_entry_info.set(pos / 4 + 1, true); m_targets[pos].push_back(pos + 4); add_block(pos + 4); } if (!is_no_return && g_cfg.core.spu_block_size == spu_block_size_type::giga && !sync) { - m_entry_info[target / 4] = true; + m_entry_info.set(target / 4, true); add_block(target); } else @@ -3394,7 +3532,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (g_cfg.core.spu_block_size == spu_block_size_type::giga && !sync) { - m_entry_info[target / 4] = true; + m_entry_info.set(target / 4, true); } else { @@ -3699,7 +3837,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Bit array used to deduplicate workload list workload.push_back(pair.first); - m_bits[pair.first / 4] = true; + m_bits.set(pair.first / 4, true); for (usz i = 0; !reachable && i < workload.size(); i++) { @@ -3731,7 +3869,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (new_pred >= lsa && new_pred < limit && !m_bits[new_pred / 4]) { workload.push_back(new_pred); - m_bits[new_pred / 4] = true; + m_bits.set(new_pred / 4, true); } } } @@ -3754,7 +3892,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 pred : workload) { - m_bits[pred / 4] = false; + m_bits.set(pred / 4, false); } if (!reachable && pair.first < limit) @@ -3813,9 +3951,9 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (addr < lsa || addr >= limit || !result.data[(addr - lsa) / 4]) { - m_block_info[addr / 4] = false; - m_entry_info[addr / 4] = false; - m_ret_info[addr / 4] = false; + m_block_info.set(addr / 4, false); + m_entry_info.set(addr / 4, false); + m_ret_info.set(addr / 4, false); m_preds.erase(addr); } } @@ -3841,7 +3979,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (it->second.empty() && !m_entry_info[it->first / 4]) { // If not an entry point, remove the block completely - m_block_info[it->first / 4] = false; + m_block_info.set(it->first / 4, false); it = m_preds.erase(it); continue; } @@ -3860,11 +3998,6 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s it++; } - if (out_target_list) - { - out_target_list->insert(m_targets.begin(), m_targets.end()); - } - // Remove unnecessary target lists for (auto it = m_targets.begin(); it != m_targets.end();) { @@ -3878,7 +4011,13 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (auto it2 = it->second.begin(); it2 != it->second.end();) { - if (*it2 < lsa || *it2 >= limit) + // Drop targets out of range, OR pointing at a block that cleanup + // removed above (m_block_info cleared) - the dead in-range edges + // that otherwise leave dangling targets in m_bbs. Pruning them here + // keeps m_targets self-consistent. The pre-existing get_block_targets + // / get_block_preds guards are retained, plus a belt-and-suspenders + // initiate_patterns guard added below - all defense in depth. + if (*it2 < lsa || *it2 >= limit || !m_block_info[*it2 / 4]) { it2 = it->second.erase(it2); removed = true; @@ -3897,6 +4036,14 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s it++; } + // Export the now-pruned, self-consistent target map AFTER the prune above, + // so callers don't see the dead in-range edges (or stale out-of-range keys) + // we just removed from m_targets. + if (out_target_list) + { + out_target_list->insert(m_targets.begin(), m_targets.end()); + } + // Fill holes which contain only NOP and LNOP instructions (TODO: compile) for (u32 i = 0, nnop = 0, vsize = 0; i <= result.data.size(); i++) { @@ -3949,7 +4096,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (type == spu_itype::STQD && op.ra == s_reg_sp && !block.reg_mod[op.rt] && !block.reg_use[op.rt]) { // Register saved onto the stack before use - block.reg_save_dom[op.rt] = true; + block.reg_save_dom.set(op.rt, true); reg_save = op.rt; } @@ -3979,7 +4126,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (reg_save != reg && block.reg_save_dom[reg]) { // Register is still used after saving; probably not eligible for optimization - block.reg_save_dom[reg] = false; + block.reg_save_dom.set(reg, false); } } } @@ -3990,7 +4137,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Expand MFC_Cmd reg use for (u8 reg : {s_reg_mfc_lsa, s_reg_mfc_tag, s_reg_mfc_size}) { - if (!block.reg_mod[reg]) + if (!block.reg_mod.test_unsafe(reg)) block.reg_use[reg]++; } } @@ -3998,11 +4145,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Register reg modification if (u8 reg = m_regmod[ia / 4]; reg < s_reg_max) { - block.reg_mod.set(reg); - block.reg_mod_xf.set(reg, type & spu_itype::xfloat); + block.reg_mod.set_unsafe(reg); + block.reg_mod_xf.set_unsafe(reg, type & spu_itype::xfloat); if (type == spu_itype::SELB && (block.reg_mod_xf[op.ra] || block.reg_mod_xf[op.rb])) - block.reg_mod_xf.set(reg); + block.reg_mod_xf.set_unsafe(reg); // Possible post-dominating register load if (type == spu_itype::LQD && op.ra == s_reg_sp) @@ -4044,13 +4191,13 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { if (i == s_reg_lr || (i >= 2 && i < s_reg_80) || i > s_reg_127) { - if (!block.reg_mod[i]) + if (!block.reg_mod.test_unsafe(i)) block.reg_use[i]++; if (!is_tail) { - block.reg_mod.set(i); - block.reg_mod_xf[i] = false; + block.reg_mod.set_unsafe(i); + block.reg_mod_xf.set_unsafe(i, false); } } } @@ -4091,8 +4238,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { const u32 addr = emp.first->first; spu_log.error("[0x%05x] Fixed first function at 0x%05x", entry_point, addr); - m_entry_info[addr / 4] = true; - m_ret_info[addr / 4] = false; + m_entry_info.set(addr / 4, true); + m_ret_info.set(addr / 4, false); } } @@ -4131,9 +4278,9 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (!m_entry_info[target / 4] || m_ret_info[target / 4]) { // Create new function entry (likely a tail call) - m_entry_info[target / 4] = true; + m_entry_info.set(target / 4, true); - m_ret_info[target / 4] = false; + m_ret_info.set(target / 4, false); m_funcs.try_emplace(target); @@ -4231,10 +4378,10 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 entry : new_entries) { - m_entry_info[entry / 4] = true; + m_entry_info.set(entry / 4, true); // Acknowledge artificial (reversible) chunk entry point - m_ret_info[entry / 4] = true; + m_ret_info.set(entry / 4, true); } for (auto& bb : m_bbs) @@ -4332,7 +4479,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { if (tb.chunk == block.chunk && tb.reg_origin[i] + 1) { - const u32 expected = block.reg_mod[i] ? addr : block.reg_origin[i]; + const u32 expected = block.reg_mod.test_unsafe(i) ? addr : block.reg_origin[i]; if (tb.reg_origin[i] == 0x80000000) { @@ -4349,7 +4496,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (g_cfg.core.spu_block_size == spu_block_size_type::giga && tb.func == block.func && tb.reg_origin_abs[i] + 2) { - const u32 expected = block.reg_mod[i] ? addr : block.reg_origin_abs[i]; + const u32 expected = block.reg_mod.test_unsafe(i) ? addr : block.reg_origin_abs[i]; if (tb.reg_origin_abs[i] == 0x80000000) { @@ -4419,12 +4566,12 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (orig < 0x40000) { - auto& src = ::at32(m_bbs, orig); - bb.reg_const[i] = src.reg_const[i]; + const auto& src = ::at32(m_bbs, orig); + bb.reg_const.set_unsafe(i, src.reg_const.test_unsafe(i)); bb.reg_val32[i] = src.reg_val32[i]; } - if (!bb.reg_save_dom[i] && bb.reg_use[i] && (orig == SPU_LS_SIZE || orig + 2 == 0)) + if (!bb.reg_save_dom.test_unsafe(i) && bb.reg_use[i] && (orig == SPU_LS_SIZE || orig + 2 == 0)) { // Destroy offset if external reg value is used func.reg_save_off[i] = -1; @@ -4451,7 +4598,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 ia = addr; ia < addr + bb.size * 4; ia += 4) { // Decode instruction again - op.opcode = std::bit_cast>(result.data[(ia - lsa) / 41]); + op.opcode = std::bit_cast>(result.data[(ia - lsa) / 4]); last_inst = g_spu_itype.decode(op.opcode); // Propagate some constants @@ -4459,25 +4606,25 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { case spu_itype::IL: { - bb.reg_const[op.rt] = true; + bb.reg_const.set(op.rt, true); bb.reg_val32[op.rt] = op.si16; break; } case spu_itype::ILA: { - bb.reg_const[op.rt] = true; + bb.reg_const.set(op.rt, true); bb.reg_val32[op.rt] = op.i18; break; } case spu_itype::ILHU: { - bb.reg_const[op.rt] = true; + bb.reg_const.set(op.rt, true); bb.reg_val32[op.rt] = op.i16 << 16; break; } case spu_itype::ILH: { - bb.reg_const[op.rt] = true; + bb.reg_const.set(op.rt, true); bb.reg_val32[op.rt] = op.i16 << 16 | op.i16; break; } @@ -4488,37 +4635,37 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } case spu_itype::ORI: { - bb.reg_const[op.rt] = bb.reg_const[op.ra]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra]); bb.reg_val32[op.rt] = bb.reg_val32[op.ra] | op.si10; break; } case spu_itype::OR: { - bb.reg_const[op.rt] = bb.reg_const[op.ra] && bb.reg_const[op.rb]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra] && bb.reg_const[op.rb]); bb.reg_val32[op.rt] = bb.reg_val32[op.ra] | bb.reg_val32[op.rb]; break; } case spu_itype::AI: { - bb.reg_const[op.rt] = bb.reg_const[op.ra]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra]); bb.reg_val32[op.rt] = bb.reg_val32[op.ra] + op.si10; break; } case spu_itype::A: { - bb.reg_const[op.rt] = bb.reg_const[op.ra] && bb.reg_const[op.rb]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra] && bb.reg_const[op.rb]); bb.reg_val32[op.rt] = bb.reg_val32[op.ra] + bb.reg_val32[op.rb]; break; } case spu_itype::SFI: { - bb.reg_const[op.rt] = bb.reg_const[op.ra]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra]); bb.reg_val32[op.rt] = op.si10 - bb.reg_val32[op.ra]; break; } case spu_itype::SF: { - bb.reg_const[op.rt] = bb.reg_const[op.ra] && bb.reg_const[op.rb]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra] && bb.reg_const[op.rb]); bb.reg_val32[op.rt] = bb.reg_val32[op.rb] - bb.reg_val32[op.ra]; break; } @@ -4551,14 +4698,14 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } // Clear const - bb.reg_const[op.rt] = false; + bb.reg_const.set(op.rt, false); break; } default: { // Clear const if reg is modified here if (u8 reg = m_regmod[ia / 4]; reg < s_reg_max) - bb.reg_const[reg] = false; + bb.reg_const.set_unsafe(reg, false); break; } } @@ -4566,7 +4713,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // $SP is modified if (m_regmod[ia / 4] == s_reg_sp) { - if (bb.reg_const[s_reg_sp]) + if (bb.reg_const.test_unsafe(s_reg_sp)) { // Making $SP a constant is a funny thing too. bb.stack_sub = 0x80000000; @@ -4762,7 +4909,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Check $LR (alternative return registers are currently not supported) if (u32 lr_orig = bb.reg_mod[s_reg_lr] ? addr : bb.reg_origin_abs[s_reg_lr]; lr_orig < SPU_LS_SIZE) { - auto& src = ::at32(m_bbs, lr_orig); + const auto& src = ::at32(m_bbs, lr_orig); if (src.reg_load_mod[s_reg_lr] != func.reg_save_off[s_reg_lr]) { @@ -4784,9 +4931,9 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Check $80..$127 (should be restored or unmodified) for (u32 i = s_reg_80; is_ok && i <= s_reg_127; i++) { - if (u32 orig = bb.reg_mod[i] ? addr : bb.reg_origin_abs[i]; orig < SPU_LS_SIZE) + if (u32 orig = bb.reg_mod.test_unsafe(i) ? addr : bb.reg_origin_abs[i]; orig < SPU_LS_SIZE) { - auto& src = ::at32(m_bbs, orig); + const auto& src = ::at32(m_bbs, orig); if (src.reg_load_mod[i] != func.reg_save_off[i]) { @@ -4940,6 +5087,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s struct reduced_statistics_t : stats_t { + atomic_t secret_compatible = 0; }; // Pattern structures @@ -5143,6 +5291,14 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s const auto initiate_patterns = [&](block_reg_state_iterator& block_state_it, u32 bpc, bool is_multi_block) { + // Defense in depth: cleanup now prunes dead in-range edges from m_targets, + // but still skip a bpc whose block was removed (matches get_block_targets) + // so no consumer can ever deref a stale target (e.g. a stop-trap return). + if (!m_block_info[bpc / 4] || !m_bbs.count(bpc)) + { + return; + } + // Initiate patterns (that are initiated on block start) const auto& bb_body = ::at32(m_bbs, bpc); @@ -5170,7 +5326,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Check loop connector block (must jump to block-next or to loop-start) u32 targets_count = 0; - for (u32 target : get_block_targets(first_pred_of_loop)) + for (u32 target : get_block_targets(!invalid ? first_pred_of_loop : bpc)) { valid = true; targets_count++; @@ -5205,7 +5361,19 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { targets_count = 0; - const u32 cond_next = block_pc + ::at32(m_bbs, block_pc).size * 4; + // Belt-and-suspenders (PR #18935): block_pc is the previous block's + // fall-through (cond_next), a live m_bbs key only because the m_targets + // prune keeps dead edges out of block.targets. If that ever regressed, + // the ::at32 below would abort with the same "Range check failed" as + // the bug we fixed - bail cleanly instead of dereferencing a non-block. + const auto block_it = m_bbs.find(block_pc); + if (block_it == m_bbs.end() || !m_block_info[block_pc / 4]) + { + invalid = true; + break; + } + + const u32 cond_next = block_pc + block_it->second.size * 4; valid = false; bool is_end = false; @@ -6087,17 +6255,17 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s u32 ra = s_reg_max, rb = s_reg_max, rc = s_reg_max; - if (::at32(m_use_ra, pos / 4)) + if (m_use_ra.test(pos / 4)) { ra = op.ra; } - if (::at32(m_use_rb, pos / 4)) + if (m_use_rb.test(pos / 4)) { rb = op.rb; } - if (::at32(m_use_rc, pos / 4)) + if (m_use_rc.test(pos / 4)) { rc = op.rc; } @@ -6357,14 +6525,14 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } std::array reg_use{}; - std::bitset reg_maybe_float{}; - std::bitset reg_mod{}; + bit_set reg_maybe_float{}; + bit_set reg_mod{}; for (auto it = m_bbs.find(reduced_loop->loop_pc); it != m_bbs.end() && it->first <= bpc; it++) { for (u32 i = 0; i < s_reg_max; i++) { - if (!reg_mod[i]) + if (!reg_mod.test_unsafe(i)) { reg_use[i] += it->second.reg_use[i]; } @@ -6382,31 +6550,48 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (!::at32(reduced_loop->loop_dicts, i)) + if (!reduced_loop->loop_dicts.test(i)) { - if (reg_use[i] && reg_mod[i]) + if (reg_use[i] && reg_mod.test_unsafe(i)) { reduced_loop->is_constant_expression = false; - reduced_loop->loop_writes.set(i); - reduced_loop->loop_may_update.reset(i); + reduced_loop->loop_writes.set_unsafe(i); + reduced_loop->loop_may_update.reset_unsafe(i); } else if (reg_use[i]) { - reduced_loop->loop_args.set(i); + reduced_loop->loop_args.set_unsafe(i); - if (reg_use[i] >= 3 && reg_maybe_float[i]) + if (reg_use[i] >= 3 && reg_maybe_float.test_unsafe(i)) { - reduced_loop->gpr_not_nans.set(i); + reduced_loop->gpr_not_nans.set_unsafe(i); } } } else { // Cleanup - reduced_loop->loop_may_update.reset(i); + reduced_loop->loop_may_update.reset_unsafe(i); } } + bool is_secret = true; + + for (u32 i = 0; i < s_reg_max; i++) + { + if (reduced_loop->loop_dicts.test_unsafe(i) || reduced_loop->loop_writes.test_unsafe(i)) + { + if (const auto reg_it = reduced_loop->find_reg(i)) + { + if (reg_it->regs.test_unsafe(s_reg_max)) + { + is_secret = false; + } + } + } + } + + reduced_loop->is_secret = is_secret; reduced_loop_all.emplace(reduced_loop->loop_pc, *reduced_loop); reduced_loop->discard(); } @@ -6625,11 +6810,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s bool found_loop_dictator = false; bool found_loop_argument_for_dictator = false; - u32 null_regs_found = 0; + //u32 null_regs_found = 0; for (u32 i = 0; i < reg->regs.size() && reduced_loop->active; i++) { - if (::at32(reg->regs, i)) + if (reg->regs.test(i)) { if (0) if (i == op_rt || reg->modified == 0) { @@ -6675,11 +6860,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s auto reg_org = reduced_loop->find_reg(i); u32 reg_index = i; - if (reg_org && !cond_val_incr_before_cond && reg_org->modified == 0 && reg_org->regs.count() - 1u <= 1u && !::at32(reg_org->regs, i)) + if (reg_org && !cond_val_incr_before_cond && reg_org->modified == 0 && reg_org->regs.count() - 1u <= 1u && !reg_org->regs.test(i)) { for (u32 j = 0; j <= s_reg_127; j++) { - if (::at32(reg_org->regs, j)) + if (reg_org->regs.test(j)) { if (const auto reg_found = reduced_loop->find_reg(j)) { @@ -6730,7 +6915,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // break; // } - null_regs_found++; + //null_regs_found++; continue; } @@ -6748,7 +6933,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s break; } - if (reg_index != i && ::at32(reg->regs, reg_index)) + if (reg_index != i && reg->regs.test(reg_index)) { // Unimplemented break_reduced_loop_pattern(30, reduced_loop->discard()); @@ -7071,14 +7256,14 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } std::array reg_use{}; - std::bitset reg_maybe_float{}; - std::bitset reg_mod{}; + bit_set reg_maybe_float{}; + bit_set reg_mod{}; for (auto it = m_bbs.find(reduced_loop->loop_pc); it != m_bbs.end() && it->first <= bpc; it++) { for (u32 i = 0; i < s_reg_max; i++) { - if (!reg_mod[i]) + if (!reg_mod.test_unsafe(i)) { reg_use[i] += it->second.reg_use[i]; } @@ -7096,31 +7281,48 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (!::at32(reduced_loop->loop_dicts, i)) + if (!reduced_loop->loop_dicts.test(i)) { - if (reg_use[i] && reg_mod[i]) + if (reg_use[i] && reg_mod.test_unsafe(i)) { reduced_loop->is_constant_expression = false; - reduced_loop->loop_writes.set(i); - reduced_loop->loop_may_update.reset(i); + reduced_loop->loop_writes.set_unsafe(i); + reduced_loop->loop_may_update.reset_unsafe(i); } else if (reg_use[i]) { - reduced_loop->loop_args.set(i); + reduced_loop->loop_args.set_unsafe(i); - if (reg_use[i] >= 3 && reg_maybe_float[i]) + if (reg_use[i] >= 3 && reg_maybe_float.test_unsafe(i)) { - reduced_loop->gpr_not_nans.set(i); + reduced_loop->gpr_not_nans.set_unsafe(i); } } } else { // Cleanup - reduced_loop->loop_may_update.reset(i); + reduced_loop->loop_may_update.reset_unsafe(i); } } + bool is_secret = true; + + for (u32 i = 0; i < s_reg_max; i++) + { + if (reduced_loop->loop_dicts.test_unsafe(i) || reduced_loop->loop_writes.test_unsafe(i)) + { + if (const auto reg_it = reduced_loop->find_reg(i)) + { + if (reg_it->regs.test_unsafe(s_reg_max)) + { + is_secret = false; + } + } + } + } + + reduced_loop->is_secret = is_secret; reduced_loop_all.emplace(reduced_loop->loop_pc, *reduced_loop); reduced_loop->discard(); } @@ -8362,17 +8564,17 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s u32 ra = s_reg_max, rb = s_reg_max, rc = s_reg_max; - if (::at32(m_use_ra, pos / 4)) + if (m_use_ra.test(pos / 4)) { ra = op.ra; } - if (::at32(m_use_rb, pos / 4)) + if (m_use_rb.test(pos / 4)) { rb = op.rb; } - if (::at32(m_use_rc, pos / 4)) + if (m_use_rc.test(pos / 4)) { rc = op.rc; } @@ -8573,7 +8775,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // This difference cannot be known at analyzer time but from observing callers. static constexpr std::initializer_list allowed_patterns = { - "620oYSe8uQqq9eTkhWfMqoEXX0us"sv, // CellSpurs JobChain acquire pattern + "disabled_620oYSe8uQqq9eTkhWfMqoEXX0us"sv, // CellSpurs JobChain acquire pattern (disabled for now) }; allow_pattern = std::any_of(allowed_patterns.begin(), allowed_patterns.end(), FN(pattern_hash == x)); @@ -8611,6 +8813,10 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (const auto& [loop_pc, pattern] : reduced_loop_all) { + auto& stats = g_fxo->get(); + + stats.all++; + if (!pattern.active || pattern.loop_pc == SPU_LS_SIZE) { continue; @@ -8618,26 +8824,28 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (inst_attr attr = m_inst_attrs[(loop_pc - entry_point) / 4]; attr == inst_attr::none) { + stats.single++; + add_pattern(inst_attr::reduced_loop, loop_pc - result.entry_point, 0, std::make_shared(pattern)); std::string regs = "{"; - for (const auto& [reg_num, reg] : pattern.regs) + for (u32 i = 0; i < s_reg_max; i++) { - if (reg.is_loop_dictator(reg_num)) + if (pattern.loop_dicts.test_unsafe(i)) { if (regs.size() != 1) { regs += ","; } - fmt::append(regs, " r%u", reg_num); + fmt::append(regs, " r%u", i); } } for (u32 i = 0; i < s_reg_max; i++) { - if (::at32(pattern.loop_writes, i)) + if (pattern.loop_writes.test_unsafe(i)) { if (regs.size() != 1) { @@ -8647,7 +8855,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s fmt::append(regs, " r%u-w", i); } - if (::at32(pattern.loop_args, i)) + if (pattern.loop_args.test_unsafe(i)) { if (regs.size() != 1) { @@ -8657,7 +8865,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s fmt::append(regs, " r%u-r", i); } - if (::at32(pattern.loop_may_update, i)) + if (pattern.loop_may_update.test_unsafe(i)) { if (regs.size() != 1) { @@ -8670,10 +8878,10 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s regs += " }"; - spu_log.success("Reduced Loop Pattern Detected! (REGS: %s, DICT: r%d, ARG: %s, Incr: %s (%s), CMP/Size: %s/%u, loop_pc=0x%x, 0x%x-%s)", regs, pattern.cond_val_register_idx + spu_log.success("Reduced Loop Pattern Detected! (REGS: %s, DICT: r%d, ARG: %s, Incr: %s (%s), CMP/Size: %s/%u, loop_pc=0x%x, 0x%x-%s) [All=%u/%u, Secret=%s/%u]", regs, pattern.cond_val_register_idx , pattern.cond_val_is_immediate ? fmt::format("0x%x", pattern.cond_val_min) : fmt::format("r%d", pattern.cond_val_register_argument_idx) , pattern.cond_val_incr_is_immediate ? fmt::format("%d", static_cast(pattern.cond_val_incr)) : fmt::format("r%d", pattern.cond_val_incr), pattern.cond_val_incr_before_cond ? "BEFORE" : "AFTER" - , pattern.cond_val_compare, std::popcount(pattern.cond_val_mask), loop_pc, entry_point, func_hash); + , pattern.cond_val_compare, std::popcount(pattern.cond_val_mask), loop_pc, entry_point, func_hash, +stats.single, +stats.all, pattern.is_secret ? "true" : "false", stats.secret_compatible.add_fetch(pattern.is_secret ? 1 : 0)); } } @@ -8712,6 +8920,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s void spu_recompiler_base::dump(const spu_program& result, std::string& out, u32 block_min, u32 block_max) { + block_max = std::min(block_max, SPU_LS_SIZE); + SPUDisAsm dis_asm(cpu_disasm_mode::dump, reinterpret_cast(result.data.data()), result.lower_bound); std::string hash; @@ -8890,8 +9100,20 @@ struct spu_llvm_worker { spu_log.error("[0x%05x] SPU Analyser failed, %u vs %u", func2.entry_point, func2.data.size(), size0); } - else if (const auto target = compiler->compile(std::move(func2))) + else { +#ifdef ARCH_ARM64 + const auto target = compile_spu_llvm_with_retry(compiler, func2); +#else + const auto target = compiler->compile(std::move(func2)); +#endif + + if (!target) + { + spu_log.fatal("[0x%05x] Compilation failed.", func.entry_point); + break; + } + // Redirect old function (TODO: patch in multiple places) const s64 rel = reinterpret_cast(target) - prog->first - 5; @@ -8909,11 +9131,6 @@ struct spu_llvm_worker atomic_storage::release(*reinterpret_cast(prog->first), result); } - else - { - spu_log.fatal("[0x%05x] Compilation failed.", func.entry_point); - break; - } // Clear fake LS std::memset(ls.data() + start / 4, 0, 4 * (size0 - 1)); diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 5b63ca80cc..73396eb08a 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -62,12 +62,22 @@ const extern spu_decoder g_spu_iflag; #ifdef ARCH_ARM64 #include "Emu/CPU/Backends/AArch64/AArch64JIT.h" + +namespace +{ + thread_local spu_llvm_compile_context* g_spu_llvm_compile_context = nullptr; +} + +void spu_llvm_set_compile_context(spu_llvm_compile_context* context) noexcept +{ + g_spu_llvm_compile_context = context; +} #endif class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator { // JIT Instance - jit_compiler m_jit{{}, jit_compiler::cpu(g_cfg.core.llvm_cpu)}; + jit_compiler m_jit{{}, jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string())}; // Interpreter table size power const u8 m_interp_magn; @@ -76,10 +86,10 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator u32 m_op_const_mask = -1; // Current function chunk entry point - u32 m_entry; + u32 m_entry = 0; // Main entry point offset - u32 m_base; + u32 m_base = 0; // Module name std::string m_hash; @@ -91,26 +101,26 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator u32 m_next_op = 0; // Current function (chunk) - llvm::Function* m_function; + llvm::Function* m_function{}; - llvm::Value* m_thread; - llvm::Value* m_lsptr; - llvm::Value* m_interp_op; - llvm::Value* m_interp_pc; - llvm::Value* m_interp_table; - llvm::Value* m_interp_7f0; - llvm::Value* m_interp_regs; + llvm::Value* m_thread{}; + llvm::Value* m_lsptr{}; + llvm::Value* m_interp_op{}; + llvm::Value* m_interp_pc{}; + llvm::Value* m_interp_table{}; + llvm::Value* m_interp_7f0{}; + llvm::Value* m_interp_regs{}; // Helpers - llvm::Value* m_base_pc; - llvm::Value* m_interp_pc_next; - llvm::BasicBlock* m_interp_bblock; + llvm::Value* m_base_pc{}; + llvm::Value* m_interp_pc_next{}; + llvm::BasicBlock* m_interp_bblock{}; // i8*, contains constant vm::g_base_addr value - llvm::Value* m_memptr; + llvm::Value* m_memptr{}; // Pointers to registers in the thread context - std::array m_reg_addr; + std::array m_reg_addr{}; // Global variable (function table) llvm::GlobalVariable* m_function_table{}; @@ -118,7 +128,6 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator // Global LUTs llvm::GlobalVariable* m_spu_frest_fraction_lut{}; llvm::GlobalVariable* m_spu_frsqest_fraction_lut{}; - llvm::GlobalVariable* m_spu_frsqest_exponent_lut{}; // Helpers (interpreter) llvm::GlobalVariable* m_scale_float_to{}; @@ -130,10 +139,10 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator // Chunk for external tail call (dispatch) llvm::Function* m_dispatch{}; - llvm::MDNode* m_md_unlikely; - llvm::MDNode* m_md_likely; - llvm::MDNode* m_md_spu_memory_domain; - llvm::MDNode* m_md_spu_context_domain; + llvm::MDNode* m_md_unlikely{}; + llvm::MDNode* m_md_likely{}; + llvm::MDNode* m_md_spu_memory_domain{}; + llvm::MDNode* m_md_spu_context_domain{}; struct block_info { @@ -1582,7 +1591,7 @@ public: clear_transforms(); #ifdef ARCH_ARM64 { - auto should_exclude_function = [](const std::string& fn_name) + auto should_exclude_function = [](std::string_view fn_name) { return fn_name.starts_with("spu_") || fn_name.starts_with("tr_"); }; @@ -1613,7 +1622,6 @@ public: // LUTs for some instructions m_spu_frest_fraction_lut = new llvm::GlobalVariable(*m_module, llvm::ArrayType::get(GetType(), 32), true, llvm::GlobalValue::PrivateLinkage, llvm::ConstantDataArray::get(m_context, spu_frest_fraction_lut)); m_spu_frsqest_fraction_lut = new llvm::GlobalVariable(*m_module, llvm::ArrayType::get(GetType(), 64), true, llvm::GlobalValue::PrivateLinkage, llvm::ConstantDataArray::get(m_context, spu_frsqest_fraction_lut)); - m_spu_frsqest_exponent_lut = new llvm::GlobalVariable(*m_module, llvm::ArrayType::get(GetType(), 256), true, llvm::GlobalValue::PrivateLinkage, llvm::ConstantDataArray::get(m_context, spu_frsqest_exponent_lut)); } virtual spu_function_t compile(spu_program&& _func) override @@ -1669,6 +1677,15 @@ public: m_hash_start = hash_start; } +#ifdef ARCH_ARM64 + m_use_tbl2 = !g_spu_llvm_compile_context || g_spu_llvm_compile_context->use_tbl2; + + if (g_spu_llvm_compile_context) + { + g_spu_llvm_compile_context->llvm_error.clear(); + } +#endif + spu_log.notice("Building function 0x%x... (size %u, %s)", func.entry_point, func.data.size(), m_hash); m_pos = func.lower_bound; @@ -1818,16 +1835,94 @@ public: llvm::Value* starta_pc = m_ir->CreateAnd(get_pc(starta), 0x3fffc); llvm::Value* data_addr = _ptr(m_lsptr, starta_pc); +#ifndef ARCH_ARM64 llvm::Value* acc0 = nullptr; llvm::Value* acc1 = nullptr; bool toggle = true; +#endif // Use a 512bit simple checksum to verify integrity if size is atleast 512b * 3 // This code uses a 512bit vector for all hardware to ensure behavior matches. // The checksum path is still faster even on narrow hardware. if ((end - starta) >= 192 && !g_cfg.core.precise_spu_verification) { - for (u32 j = starta; j < end; j += 64) +#ifdef ARCH_ARM64 + // Loop if there is at least 288 bytes of data to checksum on ARM. + // Each ARM checksum block consumes 6 NEON vectors: 2 direct adds and 2 UABD accumulates. + constexpr u32 checksum_block_size = 96; +#else + // Loop if there is atleast (16 * stride) bytes of data to checksum to save some instruction cache + constexpr u32 checksum_block_size = 64; +#endif + constexpr u32 checksum_loop_vectors = 16; + const u32 checksum_vectors_per_block = checksum_block_size / stride; + const u32 checksum_loop_blocks = (checksum_loop_vectors + checksum_vectors_per_block - 1) / checksum_vectors_per_block; + const u32 checksum_loop_size = checksum_block_size * checksum_loop_blocks; + const u32 checksum_loop_end = starta + ((end - starta) / checksum_loop_size) * checksum_loop_size; + + bool use_checksum_loop = (checksum_loop_end - starta) >= checksum_loop_size * 2; + + for (u32 j = starta; use_checksum_loop && j < checksum_loop_end; j += 4) + { + if (!func.data[(j - start) / 4]) + { + use_checksum_loop = false; + break; + } + } + +#ifndef ARCH_ARM64 + if (use_checksum_loop) + { + const auto acc_init = ConstantAggregateZero::get(get_type()); + const auto loop_block = BasicBlock::Create(m_context, "spu_checksum_loop", m_function); + const auto loop_next = BasicBlock::Create(m_context, "spu_checksum_next", m_function); + const auto loop_preheader = m_ir->GetInsertBlock(); + m_ir->CreateBr(loop_block); + + m_ir->SetInsertPoint(loop_block); + const auto offset = m_ir->CreatePHI(get_type(), 2); + const auto acc0_phi = m_ir->CreatePHI(get_type(), 2); + const auto acc1_phi = m_ir->CreatePHI(get_type(), 2); + + offset->addIncoming(m_ir->getInt32(0), loop_preheader); + acc0_phi->addIncoming(acc_init, loop_preheader); + acc1_phi->addIncoming(acc_init, loop_preheader); + + const auto offset64 = m_ir->CreateZExt(offset, get_type()); + llvm::Value* next_acc0 = acc0_phi; + llvm::Value* next_acc1 = acc1_phi; + + for (u32 block = 0; block < checksum_loop_blocks; block++) + { + const auto vls = m_ir->CreateAlignedLoad(get_type(), _ptr(data_addr, m_ir->CreateAdd(offset64, m_ir->getInt64(block * checksum_block_size))), llvm::MaybeAlign{4}); + + if (block & 1) + { + next_acc1 = m_ir->CreateAdd(next_acc1, vls); + } + else + { + next_acc0 = m_ir->CreateAdd(next_acc0, vls); + } + } + + const auto next_offset = m_ir->CreateAdd(offset, m_ir->getInt32(checksum_loop_size)); + const auto loop_again = m_ir->CreateICmpULT(next_offset, m_ir->getInt32(checksum_loop_end - starta)); + m_ir->CreateCondBr(loop_again, loop_block, loop_next); + + offset->addIncoming(next_offset, loop_block); + acc0_phi->addIncoming(next_acc0, loop_block); + acc1_phi->addIncoming(next_acc1, loop_block); + acc0 = next_acc0; + acc1 = next_acc1; + + check_iterations += (checksum_loop_end - starta) / checksum_block_size; + + m_ir->SetInsertPoint(loop_next); + } + + for (u32 j = use_checksum_loop ? checksum_loop_end : starta; j < end; j += checksum_block_size) { int indices[16]; bool holes = false; @@ -1911,7 +2006,319 @@ public: // Compare result with zero const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); +#else + // Very cursed "checksumming" code + // 96 bytes per ARM checksum step + //vls[0] -> add + //vls[1], vls[2] -> uaba + //vls[3] -> add + //vls[4], vls[5] -> uaba + //This allows us to save on some ALU ops relative to load instructions + const auto acc_init = ConstantAggregateZero::get(get_type()); + llvm::Value* checksum_parts[4] = {acc_init, acc_init, acc_init, acc_init}; + u32 checksum[16] = {0}; + + const auto update_checksum = [&](const u32* words) + { + for (u32 i = 0; i < 4; i++) + { + checksum[i] += words[i]; + checksum[4 + i] += words[4 + i] > words[8 + i] ? words[4 + i] - words[8 + i] : words[8 + i] - words[4 + i]; + checksum[8 + i] += words[12 + i]; + checksum[12 + i] += words[16 + i] > words[20 + i] ? words[16 + i] - words[20 + i] : words[20 + i] - words[16 + i]; + } + }; + + if (use_checksum_loop) + { + for (u32 j = starta; j < checksum_loop_end; j += checksum_block_size) + { + u32 words[24]; + + for (u32 i = 0; i < 24; i++) + { + words[i] = func.data[(j + i * 4 - start) / 4]; + } + + update_checksum(words); + } + + const auto loop_block = BasicBlock::Create(m_context, "spu_checksum_loop", m_function); + const auto loop_next = BasicBlock::Create(m_context, "spu_checksum_next", m_function); + const auto loop_preheader = m_ir->GetInsertBlock(); + m_ir->CreateBr(loop_block); + + m_ir->SetInsertPoint(loop_block); + const auto offset = m_ir->CreatePHI(get_type(), 2); + llvm::PHINode* acc_phi[4]; + llvm::Value* next_acc[4]; + + for (u32 part = 0; part < 4; part++) + { + acc_phi[part] = m_ir->CreatePHI(get_type(), 2); + acc_phi[part]->addIncoming(checksum_parts[part], loop_preheader); + next_acc[part] = acc_phi[part]; + } + + offset->addIncoming(m_ir->getInt32(0), loop_preheader); + + const auto offset64 = m_ir->CreateZExt(offset, get_type()); + + for (u32 block = 0; block < checksum_loop_blocks; block++) + { + llvm::Value* vls[6]; + + for (u32 part = 0; part < 6; part++) + { + vls[part] = m_ir->CreateAlignedLoad(get_type(), _ptr(data_addr, m_ir->CreateAdd(offset64, m_ir->getInt64(block * checksum_block_size + part * 16))), llvm::MaybeAlign{4}); + } + + next_acc[0] = m_ir->CreateAdd(next_acc[0], vls[0]); + next_acc[1] = m_ir->CreateAdd(next_acc[1], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[1], vls[2]})); + next_acc[2] = m_ir->CreateAdd(next_acc[2], vls[3]); + next_acc[3] = m_ir->CreateAdd(next_acc[3], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[4], vls[5]})); + } + + const auto next_offset = m_ir->CreateAdd(offset, m_ir->getInt32(checksum_loop_size)); + const auto loop_again = m_ir->CreateICmpULT(next_offset, m_ir->getInt32(checksum_loop_end - starta)); + m_ir->CreateCondBr(loop_again, loop_block, loop_next); + + offset->addIncoming(next_offset, loop_block); + + for (u32 part = 0; part < 4; part++) + { + acc_phi[part]->addIncoming(next_acc[part], loop_block); + checksum_parts[part] = next_acc[part]; + } + + check_iterations += (checksum_loop_end - starta) / checksum_block_size; + + m_ir->SetInsertPoint(loop_next); + } + + for (u32 j = use_checksum_loop ? checksum_loop_end : starta; j < end; j += checksum_block_size) + { + llvm::Value* vls[6] = {}; + u32 words[24] = {}; + bool any_data = false; + + for (u32 part = 0; part < 6; part++) + { + int indices[4]; + bool holes = false; + bool data = false; + + for (u32 i = 0; i < 4; i++) + { + const u32 k = j + (part * 4 + i) * 4; + + if (k < start || k >= end || !func.data[(k - start) / 4]) + { + indices[i] = 4; + holes = true; + } + else + { + indices[i] = i; + data = true; + words[part * 4 + i] = func.data[(k - start) / 4]; + } + } + + if (!data) + { + vls[part] = acc_init; + continue; + } + + any_data = true; + + // Load unaligned code block from LS + vls[part] = m_ir->CreateAlignedLoad(get_type(), _ptr(data_addr, j + part * 16 - starta), llvm::MaybeAlign{4}); + + // Mask if necessary + if (holes) + { + vls[part] = m_ir->CreateShuffleVector(vls[part], acc_init, llvm::ArrayRef(indices, 4)); + } + } + + if (!any_data) + { + // Skip full-sized holes + continue; + } + + checksum_parts[0] = m_ir->CreateAdd(checksum_parts[0], vls[0]); + checksum_parts[1] = m_ir->CreateAdd(checksum_parts[1], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[1], vls[2]})); + checksum_parts[2] = m_ir->CreateAdd(checksum_parts[2], vls[3]); + checksum_parts[3] = m_ir->CreateAdd(checksum_parts[3], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[4], vls[5]})); + + update_checksum(words); + + check_iterations++; + } + + llvm::Value* elem = nullptr; + + for (u32 part = 0; part < 4; part++) + { + auto* const_vector = ConstantDataVector::get(m_context, llvm::ArrayRef(checksum + part * 4, 4)); + llvm::Value* acc = m_ir->CreateXor(checksum_parts[part], const_vector); + acc = m_ir->CreateBitCast(acc, get_type()); + + for (u32 i = 0; i < 2; i++) + { + const auto lane = m_ir->CreateExtractElement(acc, i); + elem = elem ? m_ir->CreateOr(elem, lane) : lane; + } + } + + // Compare result with zero + const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); + m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); +#endif } +#ifdef ARCH_ARM64 + else + { + const auto acc_init = m_use_dotprod ? ConstantAggregateZero::get(get_type()) : ConstantAggregateZero::get(get_type()); + llvm::Value* acc0 = acc_init; + llvm::Value* acc1 = acc_init; + llvm::Value* acc2 = acc_init; + llvm::Value* acc3 = acc_init; + llvm::Value** accs[4] = {&acc0, &acc1, &acc2, &acc3}; + u32 acc_index = 0; + llvm::Value* pending_cmp = nullptr; + u32 expected_hits = 0; + + const auto make_cmp = [&](u32 j) -> llvm::Value* + { + int indices[4]; + bool holes = false; + bool data = false; + + for (u32 i = 0; i < 4; i++) + { + const u32 k = j + i * 4; + + if (k < start || k >= end || !func.data[(k - start) / 4]) + { + indices[i] = 4; + holes = true; + } + else + { + indices[i] = i; + data = true; + } + } + + if (!data) + { + return nullptr; + } + + llvm::Value* vls = m_ir->CreateAlignedLoad(get_type(), _ptr(data_addr, j - starta), llvm::MaybeAlign{4}); + + if (holes) + { + vls = m_ir->CreateShuffleVector(vls, ConstantAggregateZero::get(vls->getType()), llvm::ArrayRef(indices, 4)); + } + + u32 words[4]; + + for (u32 i = 0; i < 4; i++) + { + const u32 k = j + i * 4; + words[i] = k >= start && k < end ? func.data[(k - start) / 4] : 0; + } + + const auto expected = ConstantDataVector::get(m_context, llvm::ArrayRef(words, 4)); + + if (m_use_dotprod) + { + return m_ir->CreateSExt(m_ir->CreateICmpEQ( + m_ir->CreateBitCast(vls, get_type()), + m_ir->CreateBitCast(expected, get_type())), get_type()); + } + + return m_ir->CreateSExt(m_ir->CreateICmpEQ( + m_ir->CreateBitCast(vls, get_type()), + m_ir->CreateBitCast(expected, get_type())), get_type()); + }; + + // Multiply accumulate based comparison + // See comment above cmp16_pair_accum_arm64 in SPUThread.cpp + // Dotproduct instructions have slightly higher throughput on many common ARM cores + const auto accumulate_pair = [&](llvm::Value* lhs, llvm::Value* rhs) + { + llvm::Value*& acc = *accs[acc_index]; + + if (m_use_dotprod) + { + acc = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_udot), {acc, lhs, rhs}); + } + else + { + acc = m_ir->CreateAdd(acc, m_ir->CreateMul(lhs, rhs)); + } + + acc_index = (acc_index + 1) & 3; + expected_hits++; + }; + + for (u32 j = starta; j < end; j += 16) + { + if (const auto cmp = make_cmp(j)) + { + if (pending_cmp) + { + accumulate_pair(pending_cmp, cmp); + pending_cmp = nullptr; + } + else + { + pending_cmp = cmp; + } + + check_iterations++; + } + } + + if (pending_cmp) + { + accumulate_pair(pending_cmp, llvm::ConstantInt::get(pending_cmp->getType(), -1, true)); + } + + if (m_use_dotprod) + { + llvm::Value* acc = m_ir->CreateAdd(m_ir->CreateAdd(acc0, acc1), m_ir->CreateAdd(acc2, acc3)); + acc = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uaddv), {acc}); + + constexpr u64 dot_match_value = 0xff * 0xff; + const u32 expected = static_cast(expected_hits * 16 * dot_match_value); + const auto cond = m_ir->CreateICmpNE(acc, m_ir->getInt32(expected)); + m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); + } + else + { + u16 expected_words[8]; + std::fill_n(expected_words, 8, static_cast(expected_hits)); + const auto expected = ConstantDataVector::get(m_context, llvm::ArrayRef(expected_words, 8)); + + llvm::Value* acc = m_ir->CreateAdd(m_ir->CreateAdd(acc0, acc1), m_ir->CreateAdd(acc2, acc3)); + acc = m_ir->CreateXor(acc, expected); + acc = m_ir->CreateBitCast(acc, get_type()); + + llvm::Value* elem = m_ir->CreateExtractElement(acc, u64{0}); + elem = m_ir->CreateOr(elem, m_ir->CreateExtractElement(acc, u64{1})); + + const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); + m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); + } + } +#else else { for (u32 j = starta; j < end; j += stride) @@ -2014,6 +2421,7 @@ public: const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); } +#endif } // Increase block counter with statistics @@ -2175,7 +2583,7 @@ public: if (src > 0x40000) { // Use the xfloat hint to create 256-bit (4x double) PHI - llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf[i] ? get_type() : get_reg_type(i); + llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf.test_unsafe(i) ? get_type() : get_reg_type(i); const auto _phi = m_ir->CreatePHI(type, ::size32(bb.preds), fmt::format("phi0x%05x_r%u", baddr, i)); m_block->phi[i] = _phi; @@ -2581,7 +2989,7 @@ public: { for (u32 i = 0; i < s_reg_max; i++) { - llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf[i] ? get_type() : get_reg_type(i); + llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf.test_unsafe(i) ? get_type() : get_reg_type(i); if (i < m_reduced_loop_info->loop_dicts.size() && (m_reduced_loop_info->loop_dicts.test(i) || m_reduced_loop_info->loop_writes.test(i))) { @@ -2637,7 +3045,7 @@ public: for (u32 iteration_emit = 0; is_reduced_loop; m_pos += 4) { - if (m_pos != baddr && m_block_info[m_pos / 4] && m_reduced_loop_info->loop_end < m_pos) + if (m_pos != baddr && m_pos != SPU_LS_SIZE && m_block_info[m_pos / 4] && m_reduced_loop_info->loop_end < m_pos) { fmt::throw_exception("LLVM: Reduced Loop Pattern: Exit(1) too early at 0x%x", m_pos); } @@ -3217,7 +3625,7 @@ public: for (u32 target : m_bbs[cur].targets) { - if (!m_block_info[target / 4]) + if (target == SPU_LS_SIZE || !m_block_info[target / 4]) { continue; } @@ -3463,20 +3871,78 @@ public: } #if defined(__APPLE__) + // Apple Silicon W^X: enter write mode for JIT memory and pair + // it with an RAII guard so execute mode is restored on every + // exit path (the early "return nullptr" below would otherwise + // leave the thread in write mode permanently). pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; #endif - if (g_cfg.core.spu_debug) { - // Testing only - m_jit.add(std::move(_module), m_spurt->get_cache_path() + "llvm/"); - } - else - { - m_jit.add(std::move(_module)); - } +#ifdef ARCH_ARM64 + const bool recoverable = !!g_spu_llvm_compile_context; - m_jit.fin(); + if (recoverable) + { + bool added = false; + std::string& llvm_error = g_spu_llvm_compile_context->llvm_error; + + if (g_cfg.core.spu_debug) + { + // Testing only + added = m_jit.try_add(std::move(_module), m_spurt->get_cache_path() + "llvm/", llvm_error); + } + else + { + added = m_jit.try_add(std::move(_module), llvm_error); + } + + if (!added || !m_jit.try_fin(llvm_error)) + { + if (add_to_file) + { + add_loc->cached = 0; + } + + return nullptr; + } + } + else + { + if (g_cfg.core.spu_debug) + { + // Testing only + m_jit.add(std::move(_module), m_spurt->get_cache_path() + "llvm/"); + } + else + { + m_jit.add(std::move(_module)); + } + + m_jit.fin(); + } +#else + if (g_cfg.core.spu_debug) + { + // Testing only + m_jit.add(std::move(_module), m_spurt->get_cache_path() + "llvm/"); + } + else + { + m_jit.add(std::move(_module)); + } + + m_jit.fin(); +#endif + } // Register function pointer const spu_function_t fn = reinterpret_cast(m_jit.get_engine().getPointerToFunction(main_func)); @@ -4197,13 +4663,13 @@ public: } case SPU_RdDec: { -#if defined(ARCH_X64) +#if defined(ARCH_X64) || defined(ARCH_ARM64) if (utils::get_tsc_freq() && !(g_cfg.core.spu_loop_detection) && (g_cfg.core.clocks_scale == 100)) { const auto timebase_offs = m_ir->CreateLoad(get_type(), m_ir->CreateIntToPtr(m_ir->getInt64(reinterpret_cast(&g_timebase_offs)), get_type())); const auto timestamp = m_ir->CreateLoad(get_type(), spu_ptr(&spu_thread::ch_dec_start_timestamp)); const auto dec_value = m_ir->CreateLoad(get_type(), spu_ptr(&spu_thread::ch_dec_value)); - const auto tsc = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_rdtsc)); + const auto tsc = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::readcyclecounter)); const auto tscx = m_ir->CreateMul(m_ir->CreateUDiv(tsc, m_ir->getInt64(utils::get_tsc_freq())), m_ir->getInt64(80000000)); const auto tscm = m_ir->CreateUDiv(m_ir->CreateMul(m_ir->CreateURem(tsc, m_ir->getInt64(utils::get_tsc_freq())), m_ir->getInt64(80000000)), m_ir->getInt64(utils::get_tsc_freq())); const auto tsctb = m_ir->CreateSub(m_ir->CreateAdd(tscx, tscm), timebase_offs); @@ -5020,11 +5486,11 @@ public: { call("spu_get_events", &exec_get_events, m_thread, m_ir->getInt32(SPU_EVENT_TM)); -#if defined(ARCH_X64) +#if defined(ARCH_X64) || defined(ARCH_ARM64) if (utils::get_tsc_freq() && !(g_cfg.core.spu_loop_detection) && (g_cfg.core.clocks_scale == 100)) { const auto timebase_offs = m_ir->CreateLoad(get_type(), m_ir->CreateIntToPtr(m_ir->getInt64(reinterpret_cast(&g_timebase_offs)), get_type())); - const auto tsc = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_rdtsc)); + const auto tsc = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::readcyclecounter)); const auto tscx = m_ir->CreateMul(m_ir->CreateUDiv(tsc, m_ir->getInt64(utils::get_tsc_freq())), m_ir->getInt64(80000000)); const auto tscm = m_ir->CreateUDiv(m_ir->CreateMul(m_ir->CreateURem(tsc, m_ir->getInt64(utils::get_tsc_freq())), m_ir->getInt64(80000000)), m_ir->getInt64(utils::get_tsc_freq())); const auto tsctb = m_ir->CreateSub(m_ir->CreateAdd(tscx, tscm), timebase_offs); @@ -5447,6 +5913,44 @@ public: const auto a = get_vr(op.ra); #ifdef ARCH_ARM64 + if (m_use_i8mm) + { + if (match_vr(op.ra, [&](auto c, auto MP) + { + using VT = typename decltype(MP)::type; + + if (auto [ok, x] = match_expr(c, sext(match]>())); ok) + { + const auto zeroes = splat(0); + const auto es = zshuffle(bitcast(a), 16, 16, 16, 16, 16, 16, 16, 16, 0, 2, 4, 6, 8, 10, 12, 14); + + set_vr(op.rt, smmla(zeroes, es, build( + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + -0x01, -0x02, -0x04, -0x08, + -0x10, -0x20, -0x40, -0x80 + ))); + return true; + } + return false; + })) + { + return; + } + + const auto zeroes = splat(0); + const auto masked = a & 0x01; + const auto es = zshuffle(bitcast(masked), 16, 16, 16, 16, 16, 16, 16, 16, 0, 2, 4, 6, 8, 10, 12, 14); + + set_vr(op.rt, ummla(zeroes, es, build( + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80 + ))); + return; + } + // Use dot product instructions with special values to shift then sum results into the preferred slot if (m_use_dotprod) { @@ -5502,6 +6006,48 @@ public: const auto a = get_vr(op.ra); #ifdef ARCH_ARM64 + if (m_use_i8mm) + { + if (match_vr(op.ra, [&](auto c, auto MP) + { + using VT = typename decltype(MP)::type; + + if (auto [ok, x] = match_expr(c, sext(match]>())); ok) + { + const auto zeroes = splat(0); + + const auto extracted = smmla(zeroes, a, build( + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + -0x01, -0x02, -0x04, -0x08, + -0x10, -0x20, -0x40, -0x80 + )); + + const auto es = zshuffle(bitcast(extracted), 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 4, 12, 16, 16); + set_vr(op.rt, bitcast(es)); + return true; + } + return false; + })) + { + return; + } + + const auto zeroes = splat(0); + const auto masked = a & 0x01; + + const auto extracted = ummla(zeroes, masked, build( + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80 + )); + + const auto es = zshuffle(bitcast(extracted), 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 4, 12, 16, 16); + set_vr(op.rt, bitcast(es)); + return; + } + // Use dot product instructions with special values to shift then sum results into the preferred slot if (m_use_dotprod) { @@ -5580,22 +6126,44 @@ public: } const auto v = extract(get_vr(op.ra), 3); +#ifdef ARCH_ARM64 +// Workaround for bad codegen via LLVM +// More idiomatic version that compiles to 2 neon instructions +// Remove me when addressed by upstream llvm: https://github.com/llvm/llvm-project/issues/200325 - Whatcookie + const auto masks = build(1, 2, 4, 8); + const auto bits = vsplat(zext(trunc(v))); + set_vr(op.rt, sext((bits & masks) == masks)); +#else const auto m = bitcast(trunc(v)); set_vr(op.rt, sext(m)); +#endif } void FSMH(spu_opcode_t op) { const auto v = extract(get_vr(op.ra), 3); +#ifdef ARCH_ARM64 + const auto masks = build(1, 2, 4, 8, 16, 32, 64, 128); + const auto bits = vsplat(zext(trunc(v))); + set_vr(op.rt, sext((bits & masks) == masks)); +#else const auto m = bitcast(trunc(v)); set_vr(op.rt, sext(m)); +#endif } void FSMB(spu_opcode_t op) { const auto v = extract(get_vr(op.ra), 3); +#ifdef ARCH_ARM64 + const auto masks = build(1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128); + const auto bytes = bitcast(vsplat(trunc(v))); + const auto bits = zshuffle(bytes, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1); + set_vr(op.rt, sext((bits & masks) == masks)); +#else const auto m = bitcast(trunc(v)); set_vr(op.rt, sext(m)); +#endif } template @@ -5604,6 +6172,45 @@ public: return zshuffle(std::forward(a), 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); } + static auto rotqby_reverse_base() + { + return build(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + } + + static auto rotqby_forward_base() + { + return build(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + } + + static auto rotqby_zero_base() + { +#ifdef ARCH_ARM64 + return rotqby_forward_base(); +#else + return build(112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127); +#endif + } + + static auto rotqby_reverse_zero_base() + { +#ifdef ARCH_ARM64 + return rotqby_reverse_base(); +#else + return build(127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112); +#endif + } + + // For use in rotqby family of instructions only + template + auto pshufb_for_x86_and_tbl_for_aarch64(T&& a, U&& b) + { +#ifdef ARCH_ARM64 + return tbl(std::forward(a), std::forward(b)); +#else + return pshufb(std::forward(a), std::forward(b)); +#endif + } + template static llvm_calli rotqbybi(T&& a, U&& b) { @@ -5620,7 +6227,7 @@ public: // Data with swapped endian from a load instruction if (auto [ok, as] = match_expr(a, byteswap(match())); ok) { - const auto sc = build(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + const auto sc = rotqby_reverse_base(); const auto sh = sc + (splat_scalar(b) >> 3); if (m_use_avx512_icl) @@ -5628,9 +6235,9 @@ public: return eval(vpermb(as, sh)); } - return eval(pshufb(as, (sh & 0xf))); + return eval(pshufb_for_x86_and_tbl_for_aarch64(as, (sh & 0xf))); } - const auto sc = build(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + const auto sc = rotqby_forward_base(); const auto sh = sc - (splat_scalar(b) >> 3); if (m_use_avx512_icl) @@ -5638,7 +6245,7 @@ public: return eval(vpermb(a, sh)); } - return eval(pshufb(a, (sh & 0xf))); + return eval(pshufb_for_x86_and_tbl_for_aarch64(a, (sh & 0xf))); }); set_vr(op.rt, rotqbybi(get_vr(op.ra), get_vr(op.rb))); @@ -5666,15 +6273,15 @@ public: // Data with swapped endian from a load instruction if (auto [ok, as] = match_expr(a, byteswap(match())); ok) { - const auto sc = build(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + const auto sc = rotqby_reverse_base(); const auto sh = sc - splat_scalar(minusbx); - set_vr(op.rt, pshufb(as, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, sh)); return; } - const auto sc = build(112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127); + const auto sc = rotqby_zero_base(); const auto sh = sc + splat_scalar(minusbx); - set_vr(op.rt, pshufb(a, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh)); } void SHLQBYBI(spu_opcode_t op) @@ -5685,15 +6292,15 @@ public: // Data with swapped endian from a load instruction if (auto [ok, as] = match_expr(a, byteswap(match())); ok) { - const auto sc = build(127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112); + const auto sc = rotqby_reverse_zero_base(); const auto sh = sc + (splat_scalar(b) >> 3); - set_vr(op.rt, pshufb(as, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, sh)); return; } - const auto sc = build(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + const auto sc = rotqby_forward_base(); const auto sh = sc - (splat_scalar(b) >> 3); - set_vr(op.rt, pshufb(a, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh)); } template @@ -5847,7 +6454,7 @@ public: // Data with swapped endian from a load instruction if (auto [ok, as] = match_expr(a, byteswap(match())); ok) { - const auto sc = build(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + const auto sc = rotqby_reverse_base(); const auto sh = eval(sc + splat_scalar(b)); if (m_use_avx512_icl) @@ -5856,11 +6463,11 @@ public: return; } - set_vr(op.rt, pshufb(as, (sh & 0xf))); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, (sh & 0xf))); return; } - const auto sc = build(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + const auto sc = rotqby_forward_base(); const auto sh = eval(sc - splat_scalar(b)); if (m_use_avx512_icl) @@ -5869,7 +6476,7 @@ public: return; } - set_vr(op.rt, pshufb(a, (sh & 0xf))); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, (sh & 0xf))); } void ROTQMBY(spu_opcode_t op) @@ -5888,15 +6495,15 @@ public: // Data with swapped endian from a load instruction if (auto [ok, as] = match_expr(a, byteswap(match())); ok) { - const auto sc = build(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + const auto sc = rotqby_reverse_base(); const auto sh = sc - (splat_scalar(minusbx) & 0x1f); - set_vr(op.rt, pshufb(as, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, sh)); return; } - const auto sc = build(112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127); + const auto sc = rotqby_zero_base(); const auto sh = sc + (splat_scalar(minusbx) & 0x1f); - set_vr(op.rt, pshufb(a, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh)); } void SHLQBY(spu_opcode_t op) @@ -5907,15 +6514,15 @@ public: // Data with swapped endian from a load instruction if (auto [ok, as] = match_expr(a, byteswap(match())); ok) { - const auto sc = build(127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112); + const auto sc = rotqby_reverse_zero_base(); const auto sh = sc + (splat_scalar(b) & 0x1f); - set_vr(op.rt, pshufb(as, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, sh)); return; } - const auto sc = build(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + const auto sc = rotqby_forward_base(); const auto sh = sc - (splat_scalar(b) & 0x1f); - set_vr(op.rt, pshufb(a, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh)); } template @@ -6013,26 +6620,26 @@ public: void ROTQBYI(spu_opcode_t op) { const auto a = get_vr(op.ra); - const auto sc = build(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + const auto sc = rotqby_forward_base(); const auto sh = (sc - get_imm(op.i7, false)) & 0xf; - set_vr(op.rt, pshufb(a, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh)); } void ROTQMBYI(spu_opcode_t op) { const auto a = get_vr(op.ra); - const auto sc = build(112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127); + const auto sc = rotqby_zero_base(); const auto sh = sc + (-get_imm(op.i7, false) & 0x1f); - set_vr(op.rt, pshufb(a, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh)); } void SHLQBYI(spu_opcode_t op) { if (get_reg_raw(op.ra) && !op.i7) return set_reg_fixed(op.rt, get_reg_raw(op.ra), false); // For expressions matching const auto a = get_vr(op.ra); - const auto sc = build(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + const auto sc = rotqby_forward_base(); const auto sh = sc - (get_imm(op.i7, false) & 0x1f); - set_vr(op.rt, pshufb(a, sh)); + set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh)); } void CGT(spu_opcode_t op) @@ -6111,7 +6718,30 @@ public: void CLZ(spu_opcode_t op) { +#ifdef ARCH_ARM64 set_vr(op.rt, ctlz(get_vr(op.ra))); +#else + if (m_use_avx512) + { + set_vr(op.rt, ctlz(get_vr(op.ra))); + return; + } + + // Implement manually since LLVM can't take advantage of round-towards-zero. + // Helpful as when converting to a float the exponent is always floor(ilog2) + + constexpr u32 exp_bias = 127; + value_t flt; + + const auto a = get_vr(op.ra); + flt.value = m_ir->CreateSIToFP(a.value, get_type()); // only correct with round-towards-zero! + const auto exp = bitcast(flt) >> 23; + + // "Negative" values cause saturation due to float's sign bit + const auto offset = select(a == 0, splat(32), splat(exp_bias + 31)); + const auto lzcnt = sub_sat(bitcast(offset), bitcast(exp)); + set_vr(op.rt, bitcast(lzcnt)); +#endif } void XSWD(spu_opcode_t op) @@ -6166,6 +6796,15 @@ public: void MPYHHU(spu_opcode_t op) { +#ifdef ARCH_ARM64 + const auto [a, b] = get_vrs(op.ra, op.rb); + + if (m_use_sve2_128) + { + set_vr(op.rt, sve_umullt(bitcast(a), bitcast(b))); + return; + } +#endif set_vr(op.rt, (get_vr(op.ra) >> 16) * (get_vr(op.rb) >> 16)); } @@ -6196,17 +6835,48 @@ public: void MPYHHA(spu_opcode_t op) { +#ifdef ARCH_ARM64 + const auto [a, b] = get_vrs(op.ra, op.rb); + + if (m_use_sve2_128) + { + set_vr(op.rt, sve_smlalt(get_vr(op.rt), bitcast(a), bitcast(b))); + return; + } +#endif set_vr(op.rt, (get_vr(op.ra) >> 16) * (get_vr(op.rb) >> 16) + get_vr(op.rt)); } void MPYHHAU(spu_opcode_t op) { +#ifdef ARCH_ARM64 + const auto [a, b] = get_vrs(op.ra, op.rb); + + if (m_use_sve2_128) + { + set_vr(op.rt, sve_umlalt(get_vr(op.rt), bitcast(a), bitcast(b))); + return; + } +#endif set_vr(op.rt, (get_vr(op.ra) >> 16) * (get_vr(op.rb) >> 16) + get_vr(op.rt)); } void MPY(spu_opcode_t op) { +#ifdef ARCH_ARM64 + const auto [a, b] = get_vrs(op.ra, op.rb); + + if (m_use_sve2_128) + { + set_vr(op.rt, sve_smullb(bitcast(a), bitcast(b))); + } + else + { + set_vr(op.rt, smull(zshuffle(bitcast(a), 0, 2, 4, 6), zshuffle(bitcast(b), 0, 2, 4, 6))); + } +#else set_vr(op.rt, (get_vr(op.ra) << 16 >> 16) * (get_vr(op.rb) << 16 >> 16)); +#endif } void MPYH(spu_opcode_t op) @@ -6216,12 +6886,34 @@ public: void MPYHH(spu_opcode_t op) { +#ifdef ARCH_ARM64 + const auto [a, b] = get_vrs(op.ra, op.rb); + + if (m_use_sve2_128) + { + set_vr(op.rt, sve_smullt(bitcast(a), bitcast(b))); + return; + } +#endif set_vr(op.rt, (get_vr(op.ra) >> 16) * (get_vr(op.rb) >> 16)); } void MPYS(spu_opcode_t op) { +#ifdef ARCH_ARM64 + const auto [a, b] = get_vrs(op.ra, op.rb); + + if (m_use_sve2_128) + { + set_vr(op.rt, sve_smullb(bitcast(a), bitcast(b)) >> 16); + } + else + { + set_vr(op.rt, smull(zshuffle(bitcast(a), 0, 2, 4, 6), zshuffle(bitcast(b), 0, 2, 4, 6)) >> 16); + } +#else set_vr(op.rt, (get_vr(op.ra) << 16 >> 16) * (get_vr(op.rb) << 16 >> 16) >> 16); +#endif } void CEQH(spu_opcode_t op) @@ -6231,7 +6923,20 @@ public: void MPYU(spu_opcode_t op) { +#ifdef ARCH_ARM64 + const auto [a, b] = get_vrs(op.ra, op.rb); + + if (m_use_sve2_128) + { + set_vr(op.rt, sve_umullb(bitcast(a), bitcast(b))); + } + else + { + set_vr(op.rt, umull(zshuffle(bitcast(a), 0, 2, 4, 6), zshuffle(bitcast(b), 0, 2, 4, 6))); + } +#else set_vr(op.rt, mpyu(get_vr(op.ra), get_vr(op.rb))); +#endif } void CEQB(spu_opcode_t op) @@ -6241,8 +6946,15 @@ public: void FSMBI(spu_opcode_t op) { +#ifdef ARCH_ARM64 + const auto masks = build(1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128); + const auto bytes = bitcast(vsplat(get_imm(op.i16))); + const auto bits = zshuffle(bytes, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1); + set_vr(op.rt, sext((bits & masks) == masks)); +#else const auto m = bitcast(get_imm(op.i16)); set_vr(op.rt, sext(m)); +#endif } void IL(spu_opcode_t op) @@ -6373,12 +7085,34 @@ public: void MPYI(spu_opcode_t op) { +#ifdef ARCH_ARM64 + if (m_use_sve2_128) + { + set_vr(op.rt, sve_smullb(bitcast(get_vr(op.ra)), get_imm(op.si10))); + } + else + { + set_vr(op.rt, smull(zshuffle(bitcast(get_vr(op.ra)), 0, 2, 4, 6), get_imm(op.si10))); + } +#else set_vr(op.rt, (get_vr(op.ra) << 16 >> 16) * get_imm(op.si10)); +#endif } void MPYUI(spu_opcode_t op) { +#ifdef ARCH_ARM64 + if (m_use_sve2_128) + { + set_vr(op.rt, sve_umullb(bitcast(get_vr(op.ra)), get_imm(op.si10))); + } + else + { + set_vr(op.rt, umull(zshuffle(bitcast(get_vr(op.ra)), 0, 2, 4, 6), get_imm(op.si10))); + } +#else set_vr(op.rt, (get_vr(op.ra) << 16 >> 16) * (get_imm(op.si10) & 0xffff)); +#endif } void CEQI(spu_opcode_t op) @@ -6735,164 +7469,224 @@ public: } // Check whether shuffle mask doesn't contain fixed value selectors - bool perm_only = false; - - if (auto k = get_known_bits(c); !!(k.Zero & 0x80)) - { - perm_only = true; - } + const auto known_idx = get_known_bits(c); + const bool perm_only = known_idx.Zero[7]; + const bool perm_or_zero_only = known_idx.Zero[6]; + const bool idx_selects_single = known_idx.extractBits(1, 4).isConstant(); const auto a = get_vr(op.ra); const auto b = get_vr(op.rb); // Data with swapped endian from a load instruction - if (auto [ok, as] = match_expr(a, byteswap(match())); ok) + auto [a_was_swapped, a_swap] = match_expr(a, byteswap(match())); + auto [b_was_swapped, b_swap] = match_expr(b, byteswap(match())); + + const auto [a_is_const, a_data] = get_const_vector(a.value, m_pos); + const auto [b_is_const, b_data] = get_const_vector(b.value, m_pos); + + const bool a_is_splat = a_is_const && a_data == v128::from8p(a_data._u8[0]); + const bool b_is_splat = b_is_const && b_data == v128::from8p(b_data._u8[0]); + + + auto get_swap_from_const = [this](v128 data, bool is_splat) { + // Splats are their own byteswap + if (!is_splat) + std::reverse(std::begin(data._bytes), std::end(data._bytes)); + + return make_const_vector(data, get_type()); + }; + + if (a_is_const) + a_swap.value = get_swap_from_const(a_data, a_is_splat); + + if (b_is_const) + b_swap.value = get_swap_from_const(b_data, b_is_splat); + + // Shuffle index reversal is equivalent to a byteswap + value_t av, bv, cv; + if ((a_was_swapped || a_is_const) && (b_was_swapped || b_is_const)) { - if (auto [ok, bs] = match_expr(b, byteswap(match())); ok) + av = eval(a_swap); + bv = eval(b_swap); + cv = c; + } + else + { + av = a; + bv = b; + cv = eval(c ^ 0xf); + } + + // When single source, either indicated by KnownBits or both are the same + const std::optional> single_src = (idx_selects_single || (op.ra == op.rb && !m_interp_magn)) + ? std::make_optional(known_idx.One[4] ? bv : av) + : std::nullopt; + + const bool only_src_is_splat = known_idx.One[4] ? b_is_splat : a_is_splat; + + // Can combine the special index constants + splat selection into one LUT + const u8 sp_a = a_is_splat ? a_data._u8[0] : 0; + const u8 sp_b = b_is_splat ? b_data._u8[0] : 0; + const auto splat_lut = build(sp_a, sp_b, sp_a, sp_b, sp_a, sp_b, sp_a, sp_b, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x80); + +#ifdef ARCH_ARM64 + + // NOTE: LLVM doesn't emit BCAX (llvm-project/issues/200699) + // Verify if `(x ^ 0x0F) & 0x?F` is reassociated when upstreamed + + if (single_src) + { + const auto only_src = single_src.value(); + + if (only_src_is_splat && perm_or_zero_only) { - // Undo endian swapping, and rely on pshufb/vperm2b to re-reverse endianness - if (m_use_avx512_icl && (op.ra != op.rb)) - { - if (perm_only) - { - set_vr(op.rt4, vperm2b(as, bs, c)); - return; - } - - const auto m = gf2p8affineqb(c, build(0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20), 0x7f); - const auto mm = select(noncast(m) >= 0, splat(0), m); - const auto ab = vperm2b(as, bs, c); - set_vr(op.rt4, select(noncast(c) >= 0, ab, mm)); - return; - } - - const auto x = pshufb(build(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x80), (c >> 4)); - const auto ax = pshufb(as, c); - const auto bx = pshufb(bs, c); - - if (perm_only) - set_vr(op.rt4, select_by_bit4(c, ax, bx)); - else - set_vr(op.rt4, select_by_bit4(c, ax, bx) | x); + set_vr(op.rt4, select(noncast(c) >= 0, only_src, splat(0))); return; } - if (auto [ok, data] = get_const_vector(b.value, m_pos); ok) + if (only_src_is_splat) { - if (data == v128::from8p(data._u8[0])) - { - if (m_use_avx512_icl) - { - if (perm_only) - { - set_vr(op.rt4, vperm2b(as, b, c)); - return; - } - - const auto m = gf2p8affineqb(c, build(0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20), 0x7f); - const auto mm = select(noncast(m) >= 0, splat(0), m); - const auto ab = vperm2b(as, b, c); - set_vr(op.rt4, select(noncast(c) >= 0, ab, mm)); - return; - } - // See above - const auto x = pshufb(build(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x80), (c >> 4)); - const auto ax = pshufb(as, c); - - if (perm_only) - set_vr(op.rt4, select_by_bit4(c, ax, b)); - else - set_vr(op.rt4, select_by_bit4(c, ax, b) | x); - return; - } - } - } - - if (auto [ok, bs] = match_expr(b, byteswap(match())); ok) - { - if (auto [ok, data] = get_const_vector(a.value, m_pos); ok) - { - if (data == v128::from8p(data._u8[0])) - { - // See above - const auto x = pshufb(build(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x80), (c >> 4)); - const auto bx = pshufb(bs, c); - - if (perm_only) - set_vr(op.rt4, select_by_bit4(c, a, bx)); - else - set_vr(op.rt4, select_by_bit4(c, a, bx) | x); - return; - } - } - } - - if (m_use_avx512_icl && (op.ra != op.rb || m_interp_magn)) - { - if (auto [ok, data] = get_const_vector(b.value, m_pos); ok) - { - if (data == v128::from8p(data._u8[0])) - { - if (perm_only) - { - set_vr(op.rt4, vperm2b(a, b, eval(c ^ 0xf))); - return; - } - - const auto m = gf2p8affineqb(c, build(0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20), 0x7f); - const auto mm = select(noncast(m) >= 0, splat(0), m); - const auto ab = vperm2b(a, b, eval(c ^ 0xf)); - set_vr(op.rt4, select(noncast(c) >= 0, ab, mm)); - return; - } - } - - if (auto [ok, data] = get_const_vector(a.value, m_pos); ok) - { - if (data == v128::from8p(data._u8[0])) - { - if (perm_only) - { - set_vr(op.rt4, vperm2b(b, a, eval(c ^ 0x1f))); - return; - } - - const auto m = gf2p8affineqb(c, build(0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20), 0x7f); - const auto mm = select(noncast(m) >= 0, splat(0), m); - const auto ab = vperm2b(b, a, eval(c ^ 0x1f)); - set_vr(op.rt4, select(noncast(c) >= 0, ab, mm)); - return; - } + const auto lut = build(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x80, 0x80, 0x80); + set_vr(op.rt4, tbx(only_src, lut, (c >> 3) ^ 0x10)); + return; } if (perm_only) { - set_vr(op.rt4, vperm2b(a, b, eval(c ^ 0xf))); + const auto cm = eval(cv & 0x0f); + set_vr(op.rt4, tbl(only_src, cm)); return; } - const auto m = gf2p8affineqb(c, build(0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20), 0x7f); - const auto mm = select(noncast(m) >= 0, splat(0), m); - const auto cr = eval(c ^ 0xf); - const auto ab = vperm2b(a, b, cr); - set_vr(op.rt4, select(noncast(c) >= 0, ab, mm)); + const auto x = tbl(build(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x80), (c >> 4)); + const auto xv = perm_or_zero_only ? eval(splat(0)) : x; + const auto cm = eval(cv & 0x8f); + set_vr(op.rt4, tbx(xv, only_src, cm)); return; } - const auto x = pshufb(build(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x80), (c >> 4)); - const auto cr = eval(c ^ 0xf); - const auto ax = pshufb(a, cr); - const auto bx = pshufb(b, cr); + if (a_is_splat && b_is_splat) + { + if (perm_only) + { + set_vr(op.rt4, select_by_bit4(c, av, bv)); + return; + } + + set_vr(op.rt4, tbl(splat_lut, (c >> 4))); + return; + } if (perm_only) - set_vr(op.rt4, select_by_bit4(cr, ax, bx)); + { + const auto cm = eval(cv & 0x1f); + set_vr(op.rt4, tbl2(av, bv, cm)); + return; + } + + const auto x = tbl(build(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x80), (c >> 4)); + const auto xv = perm_or_zero_only ? eval(splat(0)) : x; + const auto cm = eval(cv & 0x9f); + set_vr(op.rt4, tbx2(xv, av, bv, cm)); + return; +#else + + // Calculate shuffle + + bool shuf_zero_when_msb = false; + + value_t ab_shuf; + if (single_src) + { + if (only_src_is_splat) + { + ab_shuf = single_src.value(); + } + else + { + ab_shuf = eval(pshufb(single_src.value(), cv)); + shuf_zero_when_msb = true; + } + } + else if (a_is_splat && b_is_splat) + { + if (perm_only) + { + set_vr(op.rt4, eval(select_by_bit4(c, av, bv))); + return; + } + + // Low index selects the element within a vector, which are all the same + if (m_use_avx512_icl) + set_vr(op.rt4, vpermb(splat_lut, bitcast(bitcast(c) >> 4))); + else + set_vr(op.rt4, eval(pshufb(splat_lut, (c >> 4)))); + return; + } + else if (m_use_avx512_icl) + { + // TODO: Swap source order to allow for a memory operand using a XOR (when free) + ab_shuf = vperm2b(av, bv, cv); + } else - set_vr(op.rt4, select_by_bit4(cr, ax, bx) | x); + { + const auto a_shuf = a_is_splat ? av : eval(pshufb(av, cv)); + const auto b_shuf = b_is_splat ? bv : eval(pshufb(bv, cv)); + ab_shuf = eval(select_by_bit4(c, a_shuf, b_shuf)); + + // pshufb zeros when the MSB is set + shuf_zero_when_msb = !(a_is_splat || b_is_splat); + } + + if (perm_only) + { + set_vr(op.rt4, ab_shuf); + return; + } + + // Calculate special index constants + + value_t idx_consts; + if (perm_or_zero_only) + { + idx_consts = eval(splat(0)); + } + else if (m_use_avx512_icl) + { + const auto gfni = gf2p8affineqb(c, build(0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20), 0x7f); + idx_consts = eval(select(noncast(gfni) >= 0, splat(0), gfni)); + } + else + { + const auto pshufb_lut = build(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x80); + idx_consts = eval(pshufb(pshufb_lut, (c >> 4))); + } + + // Combine shuffle and special index constants + + if (shuf_zero_when_msb) + set_vr(op.rt4, ab_shuf | idx_consts); + else + set_vr(op.rt4, select(noncast(c) >= 0, ab_shuf, idx_consts)); +#endif } void MPYA(spu_opcode_t op) { +#ifdef ARCH_ARM64 + const auto [a, b] = get_vrs(op.ra, op.rb); + + if (m_use_sve2_128) + { + set_vr(op.rt4, sve_smlalb(get_vr(op.rc), bitcast(a), bitcast(b))); + } + else + { + set_vr(op.rt4, smull(zshuffle(bitcast(a), 0, 2, 4, 6), zshuffle(bitcast(b), 0, 2, 4, 6)) + get_vr(op.rc)); + } +#else set_vr(op.rt4, (get_vr(op.ra) << 16 >> 16) * (get_vr(op.rb) << 16 >> 16) + get_vr(op.rc)); +#endif } void FSCRRD(spu_opcode_t op) // @@ -7074,13 +7868,23 @@ public: const auto a_sign = (a & splat(0x80000000)); value_t final_result = eval(splat(0)); - for (u32 i = 0; i < 4; i++) + if (m_use_avx512) { - const auto eval_fraction = eval(extract(a_fraction, i)); + value_t lo_lut; + value_t hi_lut; + lo_lut.value = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(spu_frest_fraction_lut, 16)); + hi_lut.value = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(spu_frest_fraction_lut + 16, 16)); - value_t r_fraction = load_const(m_spu_frest_fraction_lut, eval_fraction); - - final_result = eval(insert(final_result, i, r_fraction)); + final_result = vperm2d128From512(lo_lut, a_fraction, hi_lut); + } + else + { + for (u32 i = 0; i < 4; i++) + { + const auto eval_fraction = eval(extract(a_fraction, i)); + value_t r_fraction = load_const(m_spu_frest_fraction_lut, eval_fraction); + final_result = eval(insert(final_result, i, r_fraction)); + } } //final_result = eval(select(final_result != (0), final_result, bitcast(pshufb(splat(0), bitcast(final_result))))); @@ -7103,22 +7907,23 @@ public: { const auto a = bitcast(value(ci->getOperand(0))); + // (exponent==0)? 0xFF : 190 - (exponent + 1) / 2 + const auto a_exponent = a & splat(0xFF << 23); + const auto h_exponent = (a_exponent + (a_exponent & splat(1 << 23))) >> splat(1); + const auto r_exponent = splat(190 << 23) - h_exponent; + const auto final_exponent = select(a_exponent == 0, splat(0xFF << 23), r_exponent); + const auto a_fraction = (a >> splat(18)) & splat(0x3F); - const auto a_exponent = (a >> splat(23)) & splat(0xFF); - value_t final_result = eval(splat(0)); + value_t final_fraction = eval(splat(0)); for (u32 i = 0; i < 4; i++) { const auto eval_fraction = eval(extract(a_fraction, i)); - const auto eval_exponent = eval(extract(a_exponent, i)); - value_t r_fraction = load_const(m_spu_frsqest_fraction_lut, eval_fraction); - value_t r_exponent = load_const(m_spu_frsqest_exponent_lut, eval_exponent); - - final_result = eval(insert(final_result, i, eval(r_fraction | r_exponent))); + final_fraction = eval(insert(final_fraction, i, r_fraction)); } - return bitcast(final_result); + return bitcast(final_fraction | final_exponent); }); set_vr(op.rt, frsqest(get_vr(op.ra))); @@ -7136,15 +7941,15 @@ public: { const value_t ab[2]{a, b}; - std::bitset<2> safe_int_compare(0); - std::bitset<2> safe_finite_compare(0); + bit_set<2> safe_int_compare(0); + bit_set<2> safe_finite_compare(0); for (u32 i = 0; i < 2; i++) { if (auto [ok, data] = get_const_vector(ab[i].value, m_pos, __LINE__ + i); ok) { - safe_int_compare.set(i); - safe_finite_compare.set(i); + safe_int_compare.set_unsafe(i); + safe_finite_compare.set_unsafe(i); for (u32 j = 0; j < 4; j++) { @@ -7158,8 +7963,8 @@ public: // Note: Technically this optimization is accurate for any positive value, but due to the fact that // we don't produce "extended range" values the same way as real hardware, it's not safe to apply // this optimization for values outside of the range of x86 floating point hardware. - safe_int_compare.reset(i); - if ((value & 0x7fffffffu) >= 0x7f7ffffeu) safe_finite_compare.reset(i); + safe_int_compare.reset_unsafe(i); + if ((value & 0x7fffffffu) >= 0x7f7ffffeu) safe_finite_compare.reset_unsafe(i); } } } @@ -7167,12 +7972,12 @@ public: if (m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.ra)) { - safe_finite_compare.set(0); + safe_finite_compare.set_unsafe(0); } if (m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.rb)) { - safe_finite_compare.set(1); + safe_finite_compare.set_unsafe(1); } if (safe_int_compare.any()) @@ -7180,12 +7985,12 @@ public: return eval(sext(bitcast(a) > bitcast(b))); } - if (safe_finite_compare.test(1)) + if (safe_finite_compare.test(1u)) { return eval(sext(fcmp_uno(clamp_negative_smax(a) > b))); } - if (safe_finite_compare.test(0)) + if (safe_finite_compare.test(0u)) { return eval(sext(fcmp_ord(a > clamp_smax(b)))); } @@ -7193,7 +7998,22 @@ public: const auto ai = eval(bitcast(a)); const auto bi = eval(bitcast(b)); +// Awful workaround to some awful LLVM codegen via inline assembly +// Once it is solved upstream we should remove it - Whatcookie +// https://github.com/llvm/llvm-project/issues/197360 +#if defined(ARCH_ARM64) + const auto select_bsl = [&](auto mask, auto t, auto f) + { + const auto asm_type = llvm::FunctionType::get(get_type(), {get_type(), get_type(), get_type()}, false); + const auto bsl_asm = llvm::InlineAsm::get(asm_type, "bsl $0.16b, $1.16b, $2.16b", "=w,w,w,0", false); + + return value(m_ir->CreateCall(asm_type, bsl_asm, {eval(sext(t)).value, eval(sext(f)).value, eval(sext(mask)).value})); + }; + + return eval(sext(fcmp_uno(a != b)) & select_bsl((ai & bi) >= 0, ai > bi, ai < bi)); +#else return eval(sext(fcmp_uno(a != b) & select((ai & bi) >= 0, ai > bi, ai < bi))); +#endif }; set_vr(op.rt, fcgt(get_vr(op.ra), get_vr(op.rb))); @@ -7220,13 +8040,13 @@ public: const value_t ab[2]{a, b}; - std::bitset<2> safe_int_compare(0); + bit_set<2> safe_int_compare(0); for (u32 i = 0; i < 2; i++) { if (auto [ok, data] = get_const_vector(ab[i].value, m_pos, __LINE__ + i); ok) { - safe_int_compare.set(i); + safe_int_compare.set_unsafe(i); for (u32 j = 0; j < 4; j++) { @@ -7236,7 +8056,7 @@ public: if ((value & 0x7fffffffu) >= 0x7f7fffffu || !exponent) { // See above - safe_int_compare.reset(i); + safe_int_compare.reset_unsafe(i); } } } @@ -7494,15 +8314,15 @@ public: const value_t ab[2]{a, b}; - std::bitset<2> safe_float_compare(0); - std::bitset<2> safe_int_compare(0); + bit_set<2> safe_float_compare(0); + bit_set<2> safe_int_compare(0); for (u32 i = 0; i < 2; i++) { if (auto [ok, data] = get_const_vector(ab[i].value, m_pos, __LINE__ + i); ok) { - safe_float_compare.set(i); - safe_int_compare.set(i); + safe_float_compare.set_unsafe(i); + safe_int_compare.set_unsafe(i); for (u32 j = 0; j < 4; j++) { @@ -7512,13 +8332,13 @@ public: // unsafe if nan if (exponent == 255) { - safe_float_compare.reset(i); + safe_float_compare.reset_unsafe(i); } // unsafe if denormal or 0 if (!exponent) { - safe_int_compare.reset(i); + safe_int_compare.reset_unsafe(i); } } } @@ -7568,15 +8388,15 @@ public: const value_t ab[2]{a, b}; - std::bitset<2> safe_float_compare(0); - std::bitset<2> safe_int_compare(0); + bit_set<2> safe_float_compare(0); + bit_set<2> safe_int_compare(0); for (u32 i = 0; i < 2; i++) { if (auto [ok, data] = get_const_vector(ab[i].value, m_pos, __LINE__ + i); ok) { - safe_float_compare.set(i); - safe_int_compare.set(i); + safe_float_compare.set_unsafe(i); + safe_int_compare.set_unsafe(i); for (u32 j = 0; j < 4; j++) { @@ -7586,13 +8406,13 @@ public: // unsafe if nan if (exponent == 255) { - safe_float_compare.reset(i); + safe_float_compare.reset_unsafe(i); } // unsafe if denormal or 0 if (!exponent) { - safe_int_compare.reset(i); + safe_int_compare.reset_unsafe(i); } } } @@ -8032,10 +8852,26 @@ public: if (g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::approximate) { +#ifdef ARCH_ARM64 + if (m_use_sve2_128) + { + const auto ca = eval(clamp_smax(a)); + const auto cb = eval(clamp_smax(b)); + return value(sve_fnmls(c.value, ca.value, cb.value)); + } +#endif + return fma32x4(clamp_smax(a), clamp_smax(b), eval(-c)); } else { +#ifdef ARCH_ARM64 + if (m_use_sve2_128) + { + return value(sve_fnmls(c.value, a.value, b.value)); + } +#endif + return fma32x4(a, b, eval(-c)); } }); @@ -8095,13 +8931,23 @@ public: const auto a_sign = (a & splat(0x80000000)); value_t b = eval(splat(0)); - for (u32 i = 0; i < 4; i++) + if (m_use_avx512) { - const auto eval_fraction = eval(extract(a_fraction, i)); + value_t lo_lut; + value_t hi_lut; + lo_lut.value = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(spu_frest_fraction_lut, 16)); + hi_lut.value = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(spu_frest_fraction_lut + 16, 16)); - value_t r_fraction = load_const(m_spu_frest_fraction_lut, eval_fraction); - - b = eval(insert(b, i, r_fraction)); + b = vperm2d128From512(lo_lut, a_fraction, hi_lut); + } + else + { + for (u32 i = 0; i < 4; i++) + { + const auto eval_fraction = eval(extract(a_fraction, i)); + value_t r_fraction = load_const(m_spu_frest_fraction_lut, eval_fraction); + b = eval(insert(b, i, r_fraction)); + } } b = eval(b | fix_exponent | a_sign); @@ -8118,21 +8964,25 @@ public: register_intrinsic("spu_rsqrte", [&](llvm::CallInst* ci) { const auto a = bitcast(value(ci->getOperand(0))); + + // (exponent==0)? 0xFF : 190 - (exponent + 1) / 2 + const auto a_exponent = a & splat(0xFF << 23); + const auto h_exponent = (a_exponent + (a_exponent & splat(1 << 23))) >> splat(1); + const auto r_exponent = splat(190 << 23) - h_exponent; + const auto final_exponent = select(a_exponent == 0, splat(0xFF << 23), r_exponent); + const auto a_fraction = (a >> splat(18)) & splat(0x3F); - const auto a_exponent = (a >> splat(23)) & splat(0xFF); - value_t b = eval(splat(0)); + value_t final_fraction = eval(splat(0)); for (u32 i = 0; i < 4; i++) { const auto eval_fraction = eval(extract(a_fraction, i)); - const auto eval_exponent = eval(extract(a_exponent, i)); - value_t r_fraction = load_const(m_spu_frsqest_fraction_lut, eval_fraction); - value_t r_exponent = load_const(m_spu_frsqest_exponent_lut, eval_exponent); - - b = eval(insert(b, i, eval(r_fraction | r_exponent))); + final_fraction = eval(insert(final_fraction, i, r_fraction)); } + const auto b = eval(final_fraction | final_exponent); + const auto base = (b & 0x007ffc00u) << 9; // Base fraction const auto ymul = (b & 0x3ff) * (a & 0x7ffff); // Step fraction * Y fraction (fixed point at 2^-32) const auto comparison = (ymul > base); // Should exponent be adjusted? @@ -9039,7 +9889,7 @@ public: for (u32 target : tfound->second) { - if (m_block_info[target / 4]) + if (target != SPU_LS_SIZE && m_block_info[target / 4]) { targets.emplace(target, nullptr); } diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index fc74bcec90..e2b2f2c81e 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -3,25 +3,14 @@ #include "Utilities/File.h" #include "Utilities/lockless.h" #include "Utilities/address_range.h" +#include "util/bit_set.hpp" #include "SPUThread.h" #include "SPUAnalyser.h" #include -#include #include #include #include -// std::bitset -template - requires requires(std::remove_cvref_t& x, T&& y) { x.count(); x.test(y); x.flip(y); } -[[nodiscard]] constexpr bool at32(CT&& container, T&& index, std::source_location src_loc = std::source_location::current()) -{ - const usz csv = container.size(); - if (csv <= std::forward(index)) [[unlikely]] - fmt::raw_range_error(src_loc, format_object_simplified(index), csv); - return container[std::forward(index)]; -} - // Helper class class spu_cache { @@ -77,6 +66,16 @@ struct spu_program bool operator<(const spu_program& rhs) const noexcept; }; +#ifdef ARCH_ARM64 +struct spu_llvm_compile_context +{ + bool use_tbl2 = true; + std::string llvm_error; +}; + +void spu_llvm_set_compile_context(spu_llvm_compile_context* context) noexcept; +#endif + class spu_item { public: @@ -358,22 +357,22 @@ public: } } - std::bitset loop_args; - std::bitset loop_dicts; - std::bitset loop_writes; - std::bitset loop_may_update; - std::bitset gpr_not_nans; + bit_set loop_args; + bit_set loop_dicts; + bit_set loop_writes; + bit_set loop_may_update; + bit_set gpr_not_nans; struct origin_t { - std::bitset regs{}; + bit_set regs{}; u32 modified = 0; spu_itype_t mod1_type = spu_itype::UNK; spu_itype_t mod2_type = spu_itype::UNK; spu_itype_t mod3_type = spu_itype::UNK; u32 IMM = 0; -private: + private: // Internal, please access using fixed order spu_itype_t access_type(u32 i) const { @@ -392,7 +391,7 @@ private: return spu_itype::UNK; } -public: + public: spu_itype_t reverse1_type() { @@ -434,7 +433,7 @@ public: return true; } - return regs.count() == 1 && ::at32(regs, reg_val); + return regs.count() == 1 && regs.test(reg_val); } bool is_loop_dictator(u32 reg_val, bool test_predictable = false, bool should_predictable = true) const @@ -444,7 +443,7 @@ public: return false; } - if (regs.count() >= 1 && ::at32(regs, reg_val)) + if (regs.count() >= 1 && regs.test(reg_val)) { if (!test_predictable) { @@ -503,7 +502,7 @@ public: return false; } - if (regs.count() - (::at32(regs, reg_val) ? 1 : 0)) + if (regs.count() - (regs.test(reg_val) ? 1 : 0)) { return false; } @@ -686,7 +685,7 @@ public: bool is_gpr_not_NaN_hint(u32 i) const noexcept { - return ::at32(gpr_not_nans, i); + return gpr_not_nans.test(i); } origin_t get_reg(u32 reg_val) noexcept @@ -709,19 +708,19 @@ public: protected: spu_runtime* m_spurt{}; - u32 m_pos; - u32 m_size; - u64 m_hash_start; + u32 m_pos = 0; + u32 m_size = 0; + u64 m_hash_start = 0; // Bit indicating start of the block - std::bitset<0x10000> m_block_info; + bit_set m_block_info; // GPR modified by the instruction (-1 = not set) - std::array m_regmod; + std::array m_regmod {}; - std::bitset<0x10000> m_use_ra; - std::bitset<0x10000> m_use_rb; - std::bitset<0x10000> m_use_rc; + bit_set m_use_ra; + bit_set m_use_rb; + bit_set m_use_rc; // List of possible targets for the instruction (entry shouldn't exist for simple instructions) std::unordered_map, value_hash> m_targets; @@ -730,10 +729,10 @@ protected: std::unordered_map, value_hash> m_preds; // List of function entry points and return points (set after BRSL, BRASL, BISL, BISLED) - std::bitset<0x10000> m_entry_info; + bit_set m_entry_info; // Set after return points and disjoint chunks - std::bitset<0x10000> m_ret_info; + bit_set m_ret_info; // Basic block information struct block_info @@ -751,28 +750,28 @@ protected: term_type terminator; // Bit mask of the registers modified in the block - std::bitset reg_mod{}; + bit_set reg_mod{}; // Set if last modifying instruction produces xfloat - std::bitset reg_mod_xf{}; + bit_set reg_mod_xf{}; // Set if the initial register value in this block may be xfloat - std::bitset reg_maybe_xf{}; + bit_set reg_maybe_xf{}; // Set if register is used in floating pont instruction - std::bitset reg_maybe_float{}; + bit_set reg_maybe_float{}; // Set if register is used as shuffle mask - std::bitset reg_maybe_shuffle_mask{}; + bit_set reg_maybe_shuffle_mask{}; // Number of times registers are used (before modified) std::array reg_use{}; // Bit mask of the trivial (u32 x 4) constant value resulting in this block - std::bitset reg_const{}; + bit_set reg_const{}; // Bit mask of register saved onto the stack before use - std::bitset reg_save_dom{}; + bit_set reg_save_dom{}; // Address of the function u32 func = 0x40000; @@ -850,7 +849,7 @@ protected: private: // For private use - std::bitset<0x10000> m_bits; + bit_set<0x10000> m_bits; // For private use std::vector workload; diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 855d95a44e..dee3f15309 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -245,6 +245,26 @@ static FORCE_INLINE bool cmp_rdata_avx(const __m256i* lhs, const __m256i* rhs) } #endif +// Insane idea to accelerate comparisons on Neon with a fixed length +// Common ARM chips like the a78 and a715 can Perform 3 128b loads/clock +// But only execute 2 128b instructions on the ALU per clock +// To consume data any faster, we need to use ALU instructions that take 3 inputs +// Idea: compare data, filling each lane with either -1 or 0 +// Then multiply each pair of comparisons together, resulting in 1 if both pairs were -1 +// Accummulate those results, and compare the accumulated value to the expected count +// Benchmarks showed this to be faster even on arm machines that aren't capable of more loads than ALU operations +// Tested on Tensor G1, Snapdragon 8 gen 2, and the Snapdragon 8 Elite gen 5 +#if defined(ARCH_ARM64) +static FORCE_INLINE int16x8_t cmp16_pair_accum_arm64( + int16x8_t acc, const v128& lhs0, const v128& rhs0, const v128& lhs1, const v128& rhs1) +{ + const int16x8_t eq0 = vreinterpretq_s16_u16(vceqq_u16(static_cast(lhs0), static_cast(rhs0))); + const int16x8_t eq1 = vreinterpretq_s16_u16(vceqq_u16(static_cast(lhs1), static_cast(rhs1))); + return vmlaq_s16(acc, eq0, eq1); +} + +#endif + #ifdef _MSC_VER __forceinline #endif @@ -261,12 +281,22 @@ extern bool cmp_rdata(const spu_rdata_t& _lhs, const spu_rdata_t& _rhs) const auto lhs = reinterpret_cast(_lhs); const auto rhs = reinterpret_cast(_rhs); +#if defined(ARCH_ARM64) + int16x8_t hits = vdupq_n_s16(0); + hits = cmp16_pair_accum_arm64(hits, lhs[0], rhs[0], lhs[1], rhs[1]); + hits = cmp16_pair_accum_arm64(hits, lhs[2], rhs[2], lhs[3], rhs[3]); + hits = cmp16_pair_accum_arm64(hits, lhs[4], rhs[4], lhs[5], rhs[5]); + hits = cmp16_pair_accum_arm64(hits, lhs[6], rhs[6], lhs[7], rhs[7]); + + return vaddvq_s16(hits) == 32; +#else const v128 a = (lhs[0] ^ rhs[0]) | (lhs[1] ^ rhs[1]); const v128 c = (lhs[4] ^ rhs[4]) | (lhs[5] ^ rhs[5]); const v128 b = (lhs[2] ^ rhs[2]) | (lhs[3] ^ rhs[3]); const v128 d = (lhs[6] ^ rhs[6]) | (lhs[7] ^ rhs[7]); const v128 r = (a | b) | (c | d); return gv_testz(r); +#endif } #if defined(ARCH_X64) @@ -1757,7 +1787,7 @@ void spu_thread::init_spu_decoder() } spu_thread::spu_thread(lv2_spu_group* group, u32 index, std::string_view name, u32 lv2_id, bool is_isolated, u32 option) - : cpu_thread(idm::last_id()) + : cpu_thread(idm::last_id()) , group(group) , index(index) , thread_type(group ? spu_type::threaded : is_isolated ? spu_type::isolated : spu_type::raw) @@ -1812,14 +1842,14 @@ void spu_thread::serialize_common(utils::serial& ar) } else { - const u8 count = ar; + const u8 count{ar}; ar(std::span(vals, count)); ch_in_mbox.set_values(count, vals[0], vals[1], vals[2], vals[3]); } } spu_thread::spu_thread(utils::serial& ar, lv2_spu_group* group) - : cpu_thread(idm::last_id()) + : cpu_thread(idm::last_id()) , group(group) , index(ar) , thread_type(group ? spu_type::threaded : ar.pop() ? spu_type::isolated : spu_type::raw) @@ -6430,8 +6460,6 @@ extern void resume_spu_thread_group_from_waiting(spu_thread& spu, std::array= spu_type::raw) { + spu_log.warning("stop_and_signal(code=0x%x)", code); + // Save next PC and current SPU Interrupt Status state += cpu_flag::stop + cpu_flag::wait + cpu_flag::ret; set_status_npc(); @@ -7200,11 +7230,13 @@ s64 spu_channel::pop_wait(cpu_thread& spu, bool pop) lv2_obj::notify_all(); - const u32 wait_on_val = static_cast(((pop ? bit_occupy : 0) | bit_wait) >> 32); + old = (pop ? bit_occupy : 0) | bit_wait; while (true) { - thread_ctrl::wait_on(utils::bless>(&data)[1], wait_on_val); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + thread_ctrl::wait_on(utils::bless>(&data)[is_le], read_from_ptr(reinterpret_cast(&old), is_le * 4)); + old = data; if (!(old & bit_wait)) @@ -7248,7 +7280,7 @@ bool spu_channel::push_wait(cpu_thread& spu, u32 value, bool push) { if (data & bit_count) [[unlikely]] { - jostling_value.release(push ? (bit_occupy | value) : static_cast(data)); + jostling_value.release(push ? value : static_cast(data)); data |= (push ? bit_occupy : 0) | bit_wait; } else if (push) @@ -7288,7 +7320,8 @@ bool spu_channel::push_wait(cpu_thread& spu, u32 value, bool push) return !data.bit_test_reset(off_wait); } - thread_ctrl::wait_on(utils::bless>(&data)[1], u32(state >> 32)); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + thread_ctrl::wait_on(utils::bless>(&data)[is_le], u32(state >> 32)); state = data; } } diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index eb4e936c3b..30455e57cd 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -269,7 +269,8 @@ public: { if (!postpone_notify) { - utils::bless>(&data)[1].notify_one(); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + utils::bless>(&data)[is_le].notify_one(); } } @@ -280,7 +281,8 @@ public: void notify() { - utils::bless>(&data)[1].notify_one(); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + utils::bless>(&data)[is_le].notify_one(); } // Returns true on success @@ -334,7 +336,8 @@ public: if (old & bit_wait) { - utils::bless>(&data)[1].notify_one(); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + utils::bless>(&data)[is_le].notify_one(); } return static_cast(old); diff --git a/rpcs3/Emu/Cell/lv2/sys_cond.cpp b/rpcs3/Emu/Cell/lv2/sys_cond.cpp index f66146ae01..fcfd6824d1 100644 --- a/rpcs3/Emu/Cell/lv2/sys_cond.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_cond.cpp @@ -114,7 +114,7 @@ error_code sys_cond_create(ppu_thread& ppu, vm::ptr cond_id, u32 mutex_id, } ppu.check_state(); - *cond_id = idm::last_id(); + *cond_id = idm::last_id(); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_config.cpp b/rpcs3/Emu/Cell/lv2/sys_config.cpp index 54cb2d6ce8..d6810de684 100644 --- a/rpcs3/Emu/Cell/lv2/sys_config.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_config.cpp @@ -319,7 +319,7 @@ error_code sys_config_open(u32 equeue_hdl, vm::ptr out_config_hdl) const auto config = lv2_config_handle::create(std::move(queue)); if (config) { - *out_config_hdl = idm::last_id(); + *out_config_hdl = idm::last_id(); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_event.cpp b/rpcs3/Emu/Cell/lv2/sys_event.cpp index c5fa15cf55..b405788ca8 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_event.cpp @@ -15,7 +15,7 @@ LOG_CHANNEL(sys_event); lv2_event_queue::lv2_event_queue(u32 protocol, s32 type, s32 size, u64 name, u64 ipc_key) noexcept - : id(idm::last_id()) + : id(idm::last_id()) , protocol{static_cast(protocol)} , type(static_cast(type)) , size(static_cast(size)) @@ -25,7 +25,7 @@ lv2_event_queue::lv2_event_queue(u32 protocol, s32 type, s32 size, u64 name, u64 } lv2_event_queue::lv2_event_queue(utils::serial& ar) noexcept - : id(idm::last_id()) + : id(idm::last_id()) , protocol(ar) , type(ar) , size(ar) @@ -260,7 +260,7 @@ error_code sys_event_queue_create(cpu_thread& cpu, vm::ptr equeue_id, vm::p } cpu.check_state(); - *equeue_id = idm::last_id(); + *equeue_id = idm::last_id(); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp b/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp index 89a6c42ac5..19df83c75c 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp @@ -90,7 +90,7 @@ error_code sys_event_flag_create(ppu_thread& ppu, vm::ptr id, vm::ptr(); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 92e2011dc6..61f8ab45f1 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -465,7 +465,7 @@ lv2_fs_object::lv2_fs_object(std::string_view filename) } lv2_fs_object::lv2_fs_object(utils::serial& ar, bool) - : name(ar) + : name(ar.pop()) , mp(g_fxo->get().lookup(name.data())) { } @@ -569,7 +569,7 @@ lv2_file::lv2_file(utils::serial& ar) if (ar.pop()) // see lv2_file::save in_mem { - const fs::stat_t stat = ar; + const fs::stat_t stat = ar.pop(); std::vector buf(stat.size); ar(std::span(buf.data(), buf.size())); @@ -589,7 +589,7 @@ lv2_file::lv2_file(utils::serial& ar) sys_fs.success("Loaded file descriptor \'%s\' file for savestates (vpath=\'%s\', type=%s, flags=0x%x, id=%d)", name.data(), retrieve_real, type, flags, idm::last_id()); } - file.seek(ar); + file.seek(ar.pop()); } void lv2_file::save(utils::serial& ar) @@ -658,7 +658,7 @@ void lv2_file::save(utils::serial& ar) sys_fs.error("Read less than expected! (new-size=0x%x)", read_size); stats.size = read_size; ar.data.resize(old_end + stats.size); - write_to_ptr(&ar.data[patch_stats_pos], stats); + write_to_ptr(ar.data, patch_stats_pos, stats); } } @@ -682,7 +682,7 @@ lv2_dir::lv2_dir(utils::serial& ar) return entries; }()) - , pos(ar) + , pos(ar.pop()) { } @@ -1101,7 +1101,7 @@ lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s3 return {.error = {}, .file = std::move(file)}; } -lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mode, const void* arg, u64 size) +lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 /*mode*/, const void* arg, u64 size) { if (vpath.empty()) { @@ -1304,7 +1304,7 @@ error_code sys_fs_write(ppu_thread& ppu, u32 fd, vm::cptr buf, u64 nbytes, if (file->type != lv2_file_type::regular) { - sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, nbytes, file->name.data()); + sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, nbytes, fd, file->name.data()); } if (file->mp.read_only) diff --git a/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp b/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp index 3109f042f9..72d13879cb 100644 --- a/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp @@ -13,16 +13,16 @@ LOG_CHANNEL(sys_interrupt); lv2_int_tag::lv2_int_tag() noexcept : lv2_obj(1) - , id(idm::last_id()) + , id(idm::last_id()) { } lv2_int_tag::lv2_int_tag(utils::serial& ar) noexcept : lv2_obj(1) - , id(idm::last_id()) + , id(idm::last_id()) , handler([&]() { - const u32 id = ar; + const u32 id{ar}; auto ptr = idm::get_unlocked(id); @@ -46,7 +46,7 @@ void lv2_int_tag::save(utils::serial& ar) lv2_int_serv::lv2_int_serv(shared_ptr> thread, u64 arg1, u64 arg2) noexcept : lv2_obj(1) - , id(idm::last_id()) + , id(idm::last_id()) , thread(thread) , arg1(arg1) , arg2(arg2) @@ -55,8 +55,8 @@ lv2_int_serv::lv2_int_serv(shared_ptr> thread, u64 arg1 lv2_int_serv::lv2_int_serv(utils::serial& ar) noexcept : lv2_obj(1) - , id(idm::last_id()) - , thread(idm::get_unlocked>(ar)) + , id(idm::last_id()) + , thread(idm::get_unlocked>(ar.pop())) , arg1(ar) , arg2(ar) { diff --git a/rpcs3/Emu/Cell/lv2/sys_memory.cpp b/rpcs3/Emu/Cell/lv2/sys_memory.cpp index 0f6fb09865..01b36d8eed 100644 --- a/rpcs3/Emu/Cell/lv2/sys_memory.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_memory.cpp @@ -36,7 +36,7 @@ lv2_memory_container::lv2_memory_container(u32 size, bool from_idm) noexcept lv2_memory_container::lv2_memory_container(utils::serial& ar, bool from_idm) noexcept : size(ar) , id{from_idm ? idm::last_id() : SYS_MEMORY_CONTAINER_ID_INVALID} - , used(ar) + , used(ar.pop()) { } diff --git a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp b/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp index 0723564a5e..6c7ca748da 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp @@ -56,7 +56,7 @@ lv2_memory::lv2_memory(utils::serial& ar) return null_ptr; }(ar.pop())) - , counter(ar) + , counter(ar.pop()) { } @@ -235,7 +235,7 @@ error_code sys_mmapper_allocate_shared_memory(ppu_thread& ppu, u64 ipc_key, u64 } ppu.check_state(); - *mem_id = idm::last_id(); + *mem_id = idm::last_id(); return CELL_OK; } @@ -291,7 +291,7 @@ error_code sys_mmapper_allocate_shared_memory_from_container(ppu_thread& ppu, u6 } ppu.check_state(); - *mem_id = idm::last_id(); + *mem_id = idm::last_id(); return CELL_OK; } @@ -390,7 +390,7 @@ error_code sys_mmapper_allocate_shared_memory_ext(ppu_thread& ppu, u64 ipc_key, } ppu.check_state(); - *mem_id = idm::last_id(); + *mem_id = idm::last_id(); return CELL_OK; } @@ -493,7 +493,7 @@ error_code sys_mmapper_allocate_shared_memory_from_container_ext(ppu_thread& ppu } ppu.check_state(); - *mem_id = idm::last_id(); + *mem_id = idm::last_id(); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp index 9f436ae4d3..2f657616e5 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp @@ -99,7 +99,7 @@ error_code sys_mutex_create(ppu_thread& ppu, vm::ptr mutex_id, vm::ptr(); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_net.cpp b/rpcs3/Emu/Cell/lv2/sys_net.cpp index a860d43b12..31a3c6ea07 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net.cpp @@ -257,7 +257,7 @@ lv2_socket::lv2_socket(utils::serial& ar, lv2_socket_type _type) ar(so_rcvtimeo, so_sendtimeo); - lv2_id = idm::last_id(); + lv2_id = idm::last_id(); ar(last_bound_addr); } diff --git a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp b/rpcs3/Emu/Cell/lv2/sys_overlay.cpp index 33475a8553..2fe1a83b3e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_overlay.cpp @@ -70,7 +70,7 @@ static error_code overlay_load_module(vm::ptr ovlmid, const std::string& vp sys_overlay.success("Loaded overlay: \"%s\" (id=0x%x)", vpath, idm::last_id()); - *ovlmid = idm::last_id(); + *ovlmid = idm::last_id(); *entry = ovlm->entry; return CELL_OK; diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.cpp b/rpcs3/Emu/Cell/lv2/sys_prx.cpp index 6f930e79dd..564943db11 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_prx.cpp @@ -8,6 +8,7 @@ #include "Crypto/unself.h" #include "Loader/ELF.h" +#include "Emu/Cell/PPUFunction.h" #include "Emu/Cell/PPUThread.h" #include "Emu/Cell/ErrorCodes.h" #include "Crypto/unedat.h" @@ -115,7 +116,7 @@ extern const std::map g_prx_list { "libssl.sprx", 0 }, { "libsvc1d.sprx", 0 }, { "libsync2.sprx", 0 }, - { "libsysmodule.sprx", 0 }, + { "libsysmodule.sprx", 1 }, { "libsysutil.sprx", 1 }, { "libsysutil_ap.sprx", 1 }, { "libsysutil_authdialog.sprx", 1 }, @@ -177,6 +178,9 @@ extern const std::map g_prx_list bool ppu_register_library_lock(std::string_view libname, bool lock_lib); +extern error_code sysmoduleModuleStart(ppu_thread& ppu, u32 args, vm::ptr argp); +extern error_code sysmoduleModuleStop(ppu_thread& ppu); + static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr /*pOpt*/, fs::file src = {}, s64 file_offset = 0) { if (flags != 0) @@ -232,12 +236,18 @@ static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr(); + if (name == "libsysmodule.sprx") + { + prx->start = vm::cast(g_fxo->get().func_addr(FIND_FUNC(sysmoduleModuleStart))); + prx->stop = vm::cast(g_fxo->get().func_addr(FIND_FUNC(sysmoduleModuleStop))); + } + prx->name = std::move(name); prx->path = std::move(path); sys_prx.warning("Ignored module: \"%s\" (id=0x%x)", vpath, idm::last_id()); - return not_an_error(idm::last_id()); + return not_an_error(idm::last_id()); }; if (ignore) @@ -300,7 +310,7 @@ static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr()); } fs::file make_file_view(fs::file&& file, u64 offset, u64 size); @@ -310,8 +320,8 @@ std::function lv2_prx::load(utils::serial& ar) [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_prx_overlay); const std::string path = vfs::get(ar.pop()); - const s64 offset = ar; - const u32 state = ar; + const s64 offset{ar}; + const u32 state{ar}; usz seg_count = 0; ar.deserialize_vle(seg_count); @@ -358,7 +368,7 @@ std::function lv2_prx::load(utils::serial& ar) for (usz i = 0; i < seg_count; i++) { auto& seg = prx->segs.emplace_back(); - seg.addr = ar; + ar(seg.addr); seg.size = 1; // TODO } } diff --git a/rpcs3/Emu/Cell/lv2/sys_rsx.cpp b/rpcs3/Emu/Cell/lv2/sys_rsx.cpp index 4ef4e04d85..c91a1aa1aa 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rsx.cpp @@ -18,7 +18,7 @@ LOG_CHANNEL(sys_rsx); // Unknown error code returned by sys_rsx_context_attribute enum sys_rsx_error : s32 { - SYS_RSX_CONTEXT_ATTRIBUTE_ERROR = -17 + LV1_ILLEGAL_PARAMETER_VALUE = -17 }; template<> @@ -28,7 +28,7 @@ void fmt_class_string::format(std::string& out, u64 arg) { switch (error) { - STR_CASE(SYS_RSX_CONTEXT_ATTRIBUTE_ERROR); + STR_CASE(LV1_ILLEGAL_PARAMETER_VALUE); } return unknown; @@ -169,12 +169,28 @@ error_code sys_rsx_device_close(cpu_thread& cpu) * @param a6 (IN): E.g. Immediate value passed in cellGcmSys is 16. * @param a7 (IN): E.g. Immediate value passed in cellGcmSys is 8. */ -error_code sys_rsx_memory_allocate(cpu_thread& cpu, vm::ptr mem_handle, vm::ptr mem_addr, u32 size, u64 flags, u64 a5, u64 a6, u64 a7) +error_code sys_rsx_memory_allocate(cpu_thread& cpu, vm::ptr mem_handle, vm::ptr mem_addr, u64 size, u64 flags, u64 a5, u64 a6, u64 a7) { - cpu.state += cpu_flag::wait; - sys_rsx.warning("sys_rsx_memory_allocate(mem_handle=*0x%x, mem_addr=*0x%x, size=0x%x, flags=0x%llx, a5=0x%llx, a6=0x%llx, a7=0x%llx)", mem_handle, mem_addr, size, flags, a5, a6, a7); + // size == 0 yields available size, unimplemented + ensure(size != 0); + + if (size & 0xFFFFF) + { + return LV1_ILLEGAL_PARAMETER_VALUE; + } + + // This is a result from how the argument is treated internally + size %= 0x100000 * 0x1'0000'00000; + + if (size > 0x1000'0000) + { + return LV1_ILLEGAL_PARAMETER_VALUE; + } + + cpu.state += cpu_flag::wait; + if (vm::falloc(rsx::constants::local_mem_base, size, vm::video)) { rsx::get_current_renderer()->local_mem_size = size; @@ -395,7 +411,7 @@ error_code sys_rsx_context_free(ppu_thread& ppu, u32 context_id) * @param size (IN): Size of mapping area in bytes. E.g. 0x00200000 * @param flags (IN): */ -error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u32 io, u32 ea, u32 size, u64 flags) +error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u64 io, u64 ea, u64 size, u64 flags) { cpu.state += cpu_flag::wait; @@ -403,8 +419,12 @@ error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u32 io, u32 ea const auto render = rsx::get_current_renderer(); - if (!size || io & 0xFFFFF || ea + u64{size} > rsx::constants::local_mem_base || ea & 0xFFFFF || size & 0xFFFFF || - context_id != 0x55555555 || render->main_mem_size < io + u64{size}) + if (!size || io & 0xFFFFF || size > 0x200'00000 || size > std::min(~io, ~size) || ea & 0xFFFFF || size & 0xFFFFF) + { + return CELL_EINVAL; + } + + if (context_id != 0x55555555 || render->main_mem_size < io + size) { return CELL_EINVAL; } @@ -419,7 +439,7 @@ error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u32 io, u32 ea vm::writer_lock rlock; - for (u32 addr = ea, end = ea + size; addr < end; addr += 0x100000) + for (u64 addr = ea, end = ea + size; addr < end; addr += 0x100000) { if (!vm::check_addr(addr, vm::page_readable | (addr < 0x20000000 ? 0 : vm::page_1m_size))) { @@ -458,7 +478,7 @@ error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u32 io, u32 ea * @param io (IN): IO address. E.g. 0x00600000 (Start page 6) * @param size (IN): Size to unmap in byte. E.g. 0x00200000 */ -error_code sys_rsx_context_iounmap(cpu_thread& cpu, u32 context_id, u32 io, u32 size) +error_code sys_rsx_context_iounmap(cpu_thread& cpu, u32 context_id, u64 io, u64 size) { cpu.state += cpu_flag::wait; @@ -466,8 +486,12 @@ error_code sys_rsx_context_iounmap(cpu_thread& cpu, u32 context_id, u32 io, u32 const auto render = rsx::get_current_renderer(); - if (!size || size & 0xFFFFF || io & 0xFFFFF || context_id != 0x55555555 || - render->main_mem_size < io + u64{size}) + if (!size || size & 0xFFFFF || io & 0xFFFFF || size > 0x200'00000 || size > ~io) + { + return CELL_EINVAL; + } + + if (context_id != 0x55555555 || render->main_mem_size < io + size) { return CELL_EINVAL; } @@ -481,7 +505,7 @@ error_code sys_rsx_context_iounmap(cpu_thread& cpu, u32 context_id, u32 io, u32 std::scoped_lock lock(render->sys_rsx_mtx); - for (const u32 end = (io >>= 20) + (size >>= 20); io < end;) + for (const u64 end = (io >>= 20) + (size >>= 20); io < end;) { auto& table = render->iomap_table; @@ -626,7 +650,7 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 const u8 id = a3 & 0xFF; if (id > 7) { - return SYS_RSX_CONTEXT_ATTRIBUTE_ERROR; + return LV1_ILLEGAL_PARAMETER_VALUE; } std::lock_guard lock(render->sys_rsx_mtx); @@ -676,7 +700,7 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 { if (a3 > 7) { - return SYS_RSX_CONTEXT_ATTRIBUTE_ERROR; + return LV1_ILLEGAL_PARAMETER_VALUE; } // NOTE: There currently seem to only be 2 active heads on PS3 @@ -784,7 +808,7 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 if (a3 >= std::size(render->zculls)) { - return SYS_RSX_CONTEXT_ATTRIBUTE_ERROR; + return LV1_ILLEGAL_PARAMETER_VALUE; } if (!render->is_fifo_idle()) diff --git a/rpcs3/Emu/Cell/lv2/sys_rsx.h b/rpcs3/Emu/Cell/lv2/sys_rsx.h index 44f02a697d..9b06b88ea4 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsx.h +++ b/rpcs3/Emu/Cell/lv2/sys_rsx.h @@ -127,12 +127,12 @@ struct RsxDisplayInfo // SysCalls error_code sys_rsx_device_open(cpu_thread& cpu); error_code sys_rsx_device_close(cpu_thread& cpu); -error_code sys_rsx_memory_allocate(cpu_thread& cpu, vm::ptr mem_handle, vm::ptr mem_addr, u32 size, u64 flags, u64 a5, u64 a6, u64 a7); +error_code sys_rsx_memory_allocate(cpu_thread& cpu, vm::ptr mem_handle, vm::ptr mem_addr, u64 size, u64 flags, u64 a5, u64 a6, u64 a7); error_code sys_rsx_memory_free(cpu_thread& cpu, u32 mem_handle); error_code sys_rsx_context_allocate(cpu_thread& cpu, vm::ptr context_id, vm::ptr lpar_dma_control, vm::ptr lpar_driver_info, vm::ptr lpar_reports, u64 mem_ctx, u64 system_mode); error_code sys_rsx_context_free(ppu_thread& ppu, u32 context_id); -error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u32 io, u32 ea, u32 size, u64 flags); -error_code sys_rsx_context_iounmap(cpu_thread& cpu, u32 context_id, u32 io, u32 size); +error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u64 io, u64 ea, u64 size, u64 flags); +error_code sys_rsx_context_iounmap(cpu_thread& cpu, u32 context_id, u64 io, u64 size); error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 a4, u64 a5, u64 a6); error_code sys_rsx_device_map(cpu_thread& cpu, vm::ptr dev_addr, vm::ptr a2, u32 dev_id); error_code sys_rsx_device_unmap(cpu_thread& cpu, u32 dev_id); diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp index 29a1fa3501..95ce7803e2 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp @@ -4,12 +4,12 @@ #include "Emu/System.h" #include "Emu/system_config.h" #include "Emu//Audio/audio_utils.h" +#include "util/bit_set.hpp" #include "util/video_provider.h" #include "sys_rsxaudio.h" #include -#include #include #ifdef __linux__ @@ -1240,7 +1240,9 @@ bool rsxaudio_data_thread::enqueue_data(RsxaudioPort dst, bool silence, const vo { auto& backend_thread = g_fxo->get(); - if (dst == RsxaudioPort::SERIAL) + switch (dst) + { + case RsxaudioPort::SERIAL: { if (!silence) { @@ -1258,7 +1260,7 @@ bool rsxaudio_data_thread::enqueue_data(RsxaudioPort dst, bool silence, const vo backend_thread.add_data(cont); return cont.data_was_used(); } - else if (dst == RsxaudioPort::SPDIF_0) + case RsxaudioPort::SPDIF_0: { if (!silence) { @@ -1273,7 +1275,7 @@ bool rsxaudio_data_thread::enqueue_data(RsxaudioPort dst, bool silence, const vo backend_thread.add_data(cont); return cont.data_was_used(); } - else if (dst == RsxaudioPort::SPDIF_1) + case RsxaudioPort::SPDIF_1: { if (!silence) { @@ -1288,6 +1290,11 @@ bool rsxaudio_data_thread::enqueue_data(RsxaudioPort dst, bool silence, const vo backend_thread.add_data(cont); return cont.data_was_used(); } + case RsxaudioPort::INVALID: + { + break; + } + } return false; } @@ -1361,7 +1368,7 @@ u8 rsxaudio_backend_thread::get_channel_count() const rsxaudio_backend_thread::emu_audio_cfg rsxaudio_backend_thread::get_emu_cfg() { // Get max supported channel count - AudioChannelCnt out_ch_cnt = AudioBackend::get_max_channel_count(0); // CELL_AUDIO_OUT_PRIMARY + const AudioChannelCnt out_ch_cnt = AudioBackend::get_max_channel_count(0); // CELL_AUDIO_OUT_PRIMARY emu_audio_cfg cfg = { @@ -1413,6 +1420,20 @@ void rsxaudio_backend_thread::operator()() { lock.unlock(); backend_stop(); + + // Destroy the backend on this thread, the one that created it in backend_init(). + // The backend's ctor calls CoInitializeEx here on Windows; if it is instead released + // by ~rsxaudio_backend_thread() (which runs on the GUI thread via g_fxo->clear() + // during Kill()), the matching CoUninitialize lands on the GUI thread, draining its + // OLE reference and silently breaking the main window's file drag&drop. Keep COM + // init/teardown balanced on this thread. + if (backend) + { + backend->Close(); + backend->SetWriteCallback(nullptr); + backend->SetStateCallback(nullptr); + backend = nullptr; + } return; } @@ -1651,13 +1672,13 @@ void rsxaudio_backend_thread::set_mute_state(avport_bit muted_avports) u8 rsxaudio_backend_thread::gen_mute_state(avport_bit avports) { - std::bitset mute_state{0}; + bit_set mute_state{0}; - if (avports.hdmi_0) mute_state[static_cast(RsxaudioAvportIdx::HDMI_0)] = true; - if (avports.hdmi_1) mute_state[static_cast(RsxaudioAvportIdx::HDMI_1)] = true; - if (avports.avmulti) mute_state[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; - if (avports.spdif_0) mute_state[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; - if (avports.spdif_1) mute_state[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; + if (avports.hdmi_0) mute_state.set(static_cast(RsxaudioAvportIdx::HDMI_0), true); + if (avports.hdmi_1) mute_state.set(static_cast(RsxaudioAvportIdx::HDMI_1), true); + if (avports.avmulti) mute_state.set(static_cast(RsxaudioAvportIdx::AVMULTI), true); + if (avports.spdif_0) mute_state.set(static_cast(RsxaudioAvportIdx::SPDIF_0), true); + if (avports.spdif_1) mute_state.set(static_cast(RsxaudioAvportIdx::SPDIF_1), true); return static_cast(mute_state.to_ulong()); } @@ -1832,7 +1853,7 @@ u32 rsxaudio_backend_thread::write_data_callback(u32 bytes, void* buf) return val; }); - const std::bitset mute_state{cb_cfg.mute_state}; + const bit_set mute_state{cb_cfg.mute_state}; if (cb_cfg.ready && !mute_state[static_cast(cb_cfg.avport_idx)] && Emu.IsRunning()) { @@ -2271,9 +2292,9 @@ void rsxaudio_periodic_tmr::cancel_wait() void rsxaudio_periodic_tmr::enable_vtimer(u32 vtimer_id, u32 rate, u64 crnt_time) { - ensure(vtimer_id < VTIMER_MAX && rate); + ensure(rate); - vtimer& vtimer = vtmr_pool[vtimer_id]; + vtimer& vtimer = ::at32(vtmr_pool, vtimer_id); const f64 new_blk_time = get_blk_time(rate); // Avoid timer reset when possible @@ -2288,26 +2309,20 @@ void rsxaudio_periodic_tmr::enable_vtimer(u32 vtimer_id, u32 rate, u64 crnt_time void rsxaudio_periodic_tmr::disable_vtimer(u32 vtimer_id) { - ensure(vtimer_id < VTIMER_MAX); - - vtimer& vtimer = vtmr_pool[vtimer_id]; + vtimer& vtimer = ::at32(vtmr_pool, vtimer_id); vtimer.active = false; } bool rsxaudio_periodic_tmr::is_vtimer_behind(u32 vtimer_id, u64 crnt_time) const { - ensure(vtimer_id < VTIMER_MAX); - - const vtimer& vtimer = vtmr_pool[vtimer_id]; + const vtimer& vtimer = ::at32(vtmr_pool, vtimer_id); return is_vtimer_behind(vtimer, crnt_time); } void rsxaudio_periodic_tmr::vtimer_skip_periods(u32 vtimer_id, u64 crnt_time) { - ensure(vtimer_id < VTIMER_MAX); - - vtimer& vtimer = vtmr_pool[vtimer_id]; + vtimer& vtimer = ::at32(vtmr_pool, vtimer_id); if (is_vtimer_behind(vtimer, crnt_time)) { @@ -2317,9 +2332,7 @@ void rsxaudio_periodic_tmr::vtimer_skip_periods(u32 vtimer_id, u64 crnt_time) void rsxaudio_periodic_tmr::vtimer_incr(u32 vtimer_id, u64 crnt_time) { - ensure(vtimer_id < VTIMER_MAX); - - vtimer& vtimer = vtmr_pool[vtimer_id]; + vtimer& vtimer = ::at32(vtmr_pool, vtimer_id); if (is_vtimer_behind(vtimer, crnt_time)) { @@ -2329,18 +2342,14 @@ void rsxaudio_periodic_tmr::vtimer_incr(u32 vtimer_id, u64 crnt_time) bool rsxaudio_periodic_tmr::is_vtimer_active(u32 vtimer_id) const { - ensure(vtimer_id < VTIMER_MAX); - - const vtimer& vtimer = vtmr_pool[vtimer_id]; + const vtimer& vtimer = ::at32(vtmr_pool, vtimer_id); return vtimer.active; } u64 rsxaudio_periodic_tmr::vtimer_get_sched_time(u32 vtimer_id) const { - ensure(vtimer_id < VTIMER_MAX); - - const vtimer& vtimer = vtmr_pool[vtimer_id]; + const vtimer& vtimer = ::at32(vtmr_pool, vtimer_id); return static_cast(vtimer.blk_cnt * vtimer.blk_time); } diff --git a/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp b/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp index e60d4895cc..6e30e15e08 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp @@ -61,7 +61,7 @@ error_code sys_rwlock_create(ppu_thread& ppu, vm::ptr rw_lock_id, vm::ptr(); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp b/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp index b6ca578977..dea96fcf5d 100644 --- a/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp @@ -74,7 +74,7 @@ error_code sys_semaphore_create(ppu_thread& ppu, vm::ptr sem_id, vm::ptr(); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.cpp b/rpcs3/Emu/Cell/lv2/sys_spu.cpp index 263d871206..ee0e070508 100644 --- a/rpcs3/Emu/Cell/lv2/sys_spu.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_spu.cpp @@ -233,10 +233,10 @@ lv2_spu_group::lv2_spu_group(utils::serial& ar) noexcept , max_num(ar) , mem_size(ar) , type(ar) // SPU Thread Group Type - , ct(lv2_memory_container::search(ar)) + , ct(lv2_memory_container::search(ar.pop())) , has_scheduler_context(ar.pop()) , max_run(ar) - , init(ar) + , init(ar.pop()) , prio([&ar]() { std::common_type_t prio{}; @@ -246,7 +246,7 @@ lv2_spu_group::lv2_spu_group(utils::serial& ar) noexcept return prio; }()) , run_state(ar.pop()) - , exit_status(ar) + , exit_status(ar.pop()) { for (auto& thread : threads) { @@ -833,7 +833,7 @@ error_code sys_spu_thread_initialize(ppu_thread& ppu, vm::ptr thread, u32 g if (auto state = +group->run_state; state != SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED) { lock.unlock(); - idm::remove>(idm::last_id()); + ensure(idm::remove>(idm::last_id())); if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { @@ -846,7 +846,7 @@ error_code sys_spu_thread_initialize(ppu_thread& ppu, vm::ptr thread, u32 g if (group->threads_map[spu_num] != -1) { lock.unlock(); - idm::remove>(idm::last_id()); + ensure(idm::remove>(idm::last_id())); return CELL_EBUSY; } @@ -1137,7 +1137,7 @@ error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr id, u32 num sys_spu.warning("sys_spu_thread_group_create(): Thread group \"%s\" created (id=0x%x)", group->name, idm::last_id()); ppu.check_state(); - *id = idm::last_id(); + *id = idm::last_id(); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_timer.cpp b/rpcs3/Emu/Cell/lv2/sys_timer.cpp index 5e6c3d20cb..e65fa10432 100644 --- a/rpcs3/Emu/Cell/lv2/sys_timer.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_timer.cpp @@ -32,13 +32,13 @@ struct lv2_timer_thread lv2_timer::lv2_timer(utils::serial& ar) : lv2_obj(1) - , state(ar) + , state(ar.pop()) , port(lv2_event_queue::load_ptr(ar, port, "timer")) , source(ar) , data1(ar) , data2(ar) - , expire(ar) - , period(ar) + , expire(ar.pop()) + , period(ar.pop()) { } diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index dca61f3be8..1362d106b5 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -44,7 +44,13 @@ #include "Emu/Io/LogitechG27.h" #endif -#include +#ifdef _WIN32 +#if LIBUSB_WINDOWS_HOTPLUG && LIBUSB_API_VERSION >= 0x0100010C +#define SYS_USBD_HOTPLUG_SUPPORTED 1 +#endif +#elif LIBUSB_API_VERSION >= 0x01000102 +#define SYS_USBD_HOTPLUG_SUPPORTED 1 +#endif LOG_CHANNEL(sys_usbd); @@ -56,6 +62,8 @@ cfg_guncon3 g_cfg_guncon3; cfg_topshotelite g_cfg_topshotelite; cfg_topshotfearmaster g_cfg_topshotfearmaster; +extern atomic_t libusbd_active; + template <> void fmt_class_string::format(std::string& out, u64 arg) { @@ -152,6 +160,7 @@ public: ppu_thread* sq{}; atomic_t usb_hotplug_timeout = umax; + atomic_t hotplug_supported = false; static constexpr auto thread_name = "Usb Manager Thread"sv; @@ -215,7 +224,6 @@ private: {0x12BA, 0x04A1, 0x04A1, "Top Shot Fearmaster", nullptr, nullptr}, {0x12BA, 0x04B0, 0x04B0, "Rapala Fishing Rod", nullptr, nullptr}, - // Wheels #ifdef HAVE_SDL3 {0x046D, 0xC283, 0xC29B, "lgFF_c283_c29b", &usb_device_logitech_g27::get_num_emu_devices, &usb_device_logitech_g27::make_instance}, @@ -292,13 +300,9 @@ private: libusb_context* ctx = nullptr; -#ifndef _WIN32 -#if LIBUSB_API_VERSION >= 0x01000102 +#if SYS_USBD_HOTPLUG_SUPPORTED libusb_hotplug_callback_handle callback_handle {}; #endif -#endif - - bool hotplug_supported = false; }; void LIBUSB_CALL callback_transfer(struct libusb_transfer* transfer) @@ -311,15 +315,13 @@ void LIBUSB_CALL callback_transfer(struct libusb_transfer* transfer) usbh.transfer_complete(transfer); } -#ifndef _WIN32 -#if LIBUSB_API_VERSION >= 0x01000102 -static int LIBUSB_CALL hotplug_callback(libusb_context* /*ctx*/, libusb_device * /*dev*/, libusb_hotplug_event event, void * /*user_data*/) +#if SYS_USBD_HOTPLUG_SUPPORTED +static int LIBUSB_CALL hotplug_callback(libusb_context* /*ctx*/, libusb_device* /*dev*/, libusb_hotplug_event event, void* /*user_data*/) { - handle_hotplug_event(event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED); + handle_hotplug_event(event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, true); return 0; } #endif -#endif #if LIBUSB_API_VERSION >= 0x0100010A static void LIBUSB_CALL log_cb(libusb_context* /*ctx*/, enum libusb_log_level level, const char* str) @@ -353,7 +355,7 @@ void usb_handler_thread::perform_scan() { // look if any device which we could be interested in is actually connected libusb_device** list = nullptr; - const ssize_t ndev = libusb_get_device_list(ctx, &list); + const auto ndev = libusb_get_device_list(ctx, &list); std::set seen_usb_devices; if (ndev < 0) @@ -362,7 +364,7 @@ void usb_handler_thread::perform_scan() return; } - for (ssize_t index = 0; index < ndev; index++) + for (auto index = 0; index < ndev; index++) { libusb_device* dev = list[index]; libusb_device_descriptor desc; @@ -389,6 +391,17 @@ void usb_handler_thread::perform_scan() && desc.idProduct >= entry.id_product_min && desc.idProduct <= entry.id_product_max) { +#ifdef __APPLE__ + // On macOS, libusb cannot claim HID interfaces, so passing through a real + // controller that also has an emulated implementation yields a non-functional + // device and silently overrides the user's emulated-device setting (the emulated + // setup below only fills slots not already passed through). Prefer the emulated + // implementation whenever the user has enabled it. (e.g. DJ Hero Turntable) + if (entry.make_instance && entry.max_device_count && entry.max_device_count() > 0) + { + continue; + } +#endif sys_usbd.success("Found device: %s", std::basic_string(entry.device_name)); libusb_ref_device(dev); std::shared_ptr usb_dev = std::make_shared(dev, desc, get_new_location()); @@ -461,9 +474,7 @@ usb_handler_thread::usb_handler_thread() return; } -#ifdef _WIN32 - hotplug_supported = true; -#elif LIBUSB_API_VERSION >= 0x01000102 +#if SYS_USBD_HOTPLUG_SUPPORTED if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { if (int res = libusb_hotplug_register_callback(ctx, static_cast(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | @@ -478,6 +489,8 @@ usb_handler_thread::usb_handler_thread() hotplug_supported = true; } } +#elif defined(_WIN32) + hotplug_supported = true; #endif for (u32 index = 0; index < MAX_SYS_USBD_TRANSFERS; index++) @@ -625,11 +638,9 @@ usb_handler_thread::~usb_handler_thread() libusb_free_transfer(transfers[index].transfer); } -#ifndef _WIN32 -#if LIBUSB_API_VERSION >= 0x01000102 +#if SYS_USBD_HOTPLUG_SUPPORTED if (ctx && hotplug_supported) libusb_hotplug_deregister_callback(ctx, callback_handle); -#endif #endif if (ctx) @@ -652,7 +663,7 @@ void usb_handler_thread::operator()() // every 4 seconds. // On systems where hotplug is native, we wait a little bit for devices to settle before we start the scan perform_scan(); - usb_hotplug_timeout = hotplug_supported ? umax : get_system_time() + 4'000'000ull; + usb_hotplug_timeout = hotplug_supported ? umax : (get_system_time() + 4'000'000ull); } // Process asynchronous requests that are pending @@ -661,7 +672,7 @@ void usb_handler_thread::operator()() u64 delay = 1'000; // Process fake transfers - if (!fake_transfers.empty()) + if (libusbd_active && !fake_transfers.empty()) { std::lock_guard lock_tf(mutex_transfers); u64 timestamp = get_system_time() - Emu.GetPauseTime(); @@ -1104,15 +1115,18 @@ void reconnect_usb(u32 assigned_number) usbh->reconnect_usb_device(assigned_number); } -void handle_hotplug_event(bool connected) +void handle_hotplug_event(bool connected, bool source_is_libusb) { if (auto usbh = g_fxo->try_get>()) { + if (usbh->hotplug_supported && !source_is_libusb) return; + + sys_usbd.notice("handle_hotplug_event: connected=%d", connected); + usbh->usb_hotplug_timeout = get_system_time() + (connected ? 1'000'000ull : 0); } } - error_code sys_usbd_initialize(ppu_thread& ppu, vm::ptr handle) { ppu.state += cpu_flag::wait; diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.h b/rpcs3/Emu/Cell/lv2/sys_usbd.h index 45e9214e82..01a7891bbc 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.h +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.h @@ -90,4 +90,4 @@ error_code sys_usbd_unregister_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr())) , addr(ar) , size(ar) - , psize(ar) + , psize(ar.pop()) { g_ids[addr >> 28].release(idm::last_id()); g_fxo->need(); diff --git a/rpcs3/Emu/GDB.cpp b/rpcs3/Emu/GDB.cpp index 20e6c82cd4..5c6eb11396 100644 --- a/rpcs3/Emu/GDB.cpp +++ b/rpcs3/Emu/GDB.cpp @@ -102,7 +102,7 @@ std::string u32_to_padded_hex(u32 value) template T hex_to(std::string_view val) { - T result; + T result {}; auto [ptr, err] = std::from_chars(val.data(), val.data() + val.size(), result, 16); if (err != std::errc()) { @@ -361,7 +361,7 @@ void gdb_thread::ack(bool accepted) send_char(accepted ? '+' : '-'); } -void gdb_thread::send_cmd(const std::string& cmd) +void gdb_thread::send_cmd(std::string_view cmd) { u8 checksum = 0; std::string buf; @@ -376,7 +376,7 @@ void gdb_thread::send_cmd(const std::string& cmd) send(buf.c_str(), static_cast(buf.length())); } -bool gdb_thread::send_cmd_ack(const std::string& cmd) +bool gdb_thread::send_cmd_ack(std::string_view cmd) { while (true) { @@ -465,7 +465,7 @@ std::string gdb_thread::get_reg(ppu_thread* thread, u32 rid) } } -bool gdb_thread::set_reg(ppu_thread* thread, u32 rid, const std::string& value) +bool gdb_thread::set_reg(ppu_thread* thread, u32 rid, std::string_view value) { switch (rid) { diff --git a/rpcs3/Emu/GDB.h b/rpcs3/Emu/GDB.h index 75d272f9d7..0de02ffe25 100644 --- a/rpcs3/Emu/GDB.h +++ b/rpcs3/Emu/GDB.h @@ -44,10 +44,10 @@ class gdb_thread //acknowledge packet, either as accepted or declined void ack(bool accepted); //sends command body cmd to client - void send_cmd(const std::string& cmd); + void send_cmd(std::string_view cmd); //sends command to client until receives positive acknowledgement //returns false in case some error happened, and command wasn't sent - bool send_cmd_ack(const std::string& cmd); + bool send_cmd_ack(std::string_view cmd); //appends encoded char c to string str, and returns checksum. encoded byte can occupy 2 bytes static u8 append_encoded_char(char c, std::string& str); //convert u8 to 2 byte hexademical representation @@ -57,7 +57,7 @@ class gdb_thread //returns register value as hex string by register id (in gdb), in case of wrong id returns empty string static std::string get_reg(ppu_thread* thread, u32 rid); //sets register value to hex string by register id (in gdb), in case of wrong id returns false - static bool set_reg(ppu_thread* thread, u32 rid, const std::string& value); + static bool set_reg(ppu_thread* thread, u32 rid, std::string_view value); //returns size of register with id rid in bytes, zero if invalid rid is provided static u32 get_reg_size(ppu_thread* thread, u32 rid); //send reason of stop, returns false if sending response failed diff --git a/rpcs3/Emu/GameInfo.h b/rpcs3/Emu/GameInfo.h index da8b2638ba..a99e708521 100644 --- a/rpcs3/Emu/GameInfo.h +++ b/rpcs3/Emu/GameInfo.h @@ -9,6 +9,7 @@ struct GameInfo std::string icon_path; std::string movie_path; std::string audio_path; + std::string game_dir; std::string name; std::string serial; diff --git a/rpcs3/Emu/IdManager.h b/rpcs3/Emu/IdManager.h index b3c898c23b..e8dde8fb8d 100644 --- a/rpcs3/Emu/IdManager.h +++ b/rpcs3/Emu/IdManager.h @@ -591,6 +591,20 @@ public: return id_manager::g_id; } + // Get last ID with type validation + template + static inline u32 last_id(std::source_location src = std::source_location::current()) + { + const u32 last = id_manager::g_id; + + if (get_index(last) >= T::id_count) + { + fmt::raw_range_error(src, last, T::id_base); + } + + return last; + } + // Add a new ID of specified type with specified constructor arguments (returns object or null_ptr) template requires (std::is_constructible_v) static inline stx::shared_ptr make_ptr(Args&&... args) diff --git a/rpcs3/Emu/Io/Buzz.cpp b/rpcs3/Emu/Io/Buzz.cpp index 840220b967..00fe857b7a 100644 --- a/rpcs3/Emu/Io/Buzz.cpp +++ b/rpcs3/Emu/Io/Buzz.cpp @@ -179,12 +179,12 @@ void usb_device_buzz::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/ } const auto& cfg = g_cfg_buzz.players[i]; - cfg->handle_input(pad, true, [&buf, &index](buzz_btn btn, pad_button /*pad_btn*/, u16 /*value*/, bool pressed, bool& /*abort*/) + cfg->handle_input(pad, true, [&buf, &index](const auto& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { case buzz_btn::red: buf[2 + (0 + 5 * index) / 8] |= 1 << ((0 + 5 * index) % 8); // Red diff --git a/rpcs3/Emu/Io/Dimensions.cpp b/rpcs3/Emu/Io/Dimensions.cpp index 84a604a86e..179be8ef3c 100644 --- a/rpcs3/Emu/Io/Dimensions.cpp +++ b/rpcs3/Emu/Io/Dimensions.cpp @@ -214,7 +214,7 @@ u32 dimensions_toypad::scramble(const std::array& uid, u8 count) } ::at32(to_scramble, count * 4 - 1) = 0xaa; - return read_from_ptr>(dimensions_randomize(to_scramble, count).data()); + return read_from_ptr>(dimensions_randomize(to_scramble, count)); } std::array dimensions_toypad::dimensions_randomize(const std::vector& key, u8 count) @@ -522,7 +522,7 @@ bool dimensions_toypad::create_blank_character(std::array& buf, else { // Page 38 is used as verification for blank tags - write_to_ptr>(buf.data(), 38 * 4, 1); + write_to_ptr>(buf, 38 * 4, 1); } return true; @@ -530,11 +530,10 @@ bool dimensions_toypad::create_blank_character(std::array& buf, std::array dimensions_toypad::pwd_generate(const std::array& uid) { - std::vector pwd_calc = {PWD_CONSTANT.begin(), PWD_CONSTANT.end() - 1}; - for (u8 i = 0; i < uid.size(); i++) - { - pwd_calc.insert(pwd_calc.begin() + i, uid[i]); - } + std::vector pwd_calc; + pwd_calc.reserve(uid.size() + PWD_CONSTANT.size()); + pwd_calc.insert(pwd_calc.end(), uid.begin(), uid.end()); + pwd_calc.insert(pwd_calc.end(), PWD_CONSTANT.begin(), PWD_CONSTANT.end()); return dimensions_randomize(pwd_calc, 8); } diff --git a/rpcs3/Emu/Io/GHLtar.cpp b/rpcs3/Emu/Io/GHLtar.cpp index 0c99c6aabf..d00f647602 100644 --- a/rpcs3/Emu/Io/GHLtar.cpp +++ b/rpcs3/Emu/Io/GHLtar.cpp @@ -158,12 +158,12 @@ void usb_device_ghltar::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint } const auto& cfg = ::at32(g_cfg_ghltar.players, m_controller_index); - cfg->handle_input(pad, true, [&buf](ghltar_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/) + cfg->handle_input(pad, true, [&buf](const auto& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { case ghltar_btn::w1: buf[0] += 0x01; // W1 @@ -205,17 +205,18 @@ void usb_device_ghltar::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint buf[1] += 0x04; // GHTV Button break; case ghltar_btn::whammy: - buf[6] = ~(value) + 0x01; // Whammy + buf[6] = ~(value.to_8bit()) + 0x01; // Whammy break; case ghltar_btn::tilt: - buf[19] = static_cast(value); // Tilt - if (buf[19] >= 0xF0) - buf[5] = 0xFF; - else if (buf[19] <= 0x10) - buf[5] = 0x00; + buf[19] = value.to_8bit(); // Tilt break; case ghltar_btn::count: break; } }); + + if (buf[19] >= 0xF0) + buf[5] = 0xFF; + else if (buf[19] <= 0x10) + buf[5] = 0x00; } diff --git a/rpcs3/Emu/Io/GameTablet.cpp b/rpcs3/Emu/Io/GameTablet.cpp index 1d0fcf24fe..f72c108ae1 100644 --- a/rpcs3/Emu/Io/GameTablet.cpp +++ b/rpcs3/Emu/Io/GameTablet.cpp @@ -200,7 +200,7 @@ void usb_device_gametablet::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endp const auto& pad = ::at32(pads, m_controller_index); if (pad->is_connected() && !pad->is_copilot()) { - for (Button& button : pad->m_buttons_external) + for (ButtonExternal& button : pad->m_buttons_external) { if (!button.m_pressed) { diff --git a/rpcs3/Emu/Io/GunCon3.cpp b/rpcs3/Emu/Io/GunCon3.cpp index 522369d8a4..547cbf53e5 100644 --- a/rpcs3/Emu/Io/GunCon3.cpp +++ b/rpcs3/Emu/Io/GunCon3.cpp @@ -227,12 +227,12 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, return; } - const auto input_callback = [&gc](guncon3_btn btn, pad_button /*pad_button*/, u16 value, bool pressed, bool& /*abort*/) + const auto input_callback = [&gc](const emulated_pad_config::input_value& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { case guncon3_btn::trigger: gc.btn_trigger |= 1; break; case guncon3_btn::a1: gc.btn_a1 |= 1; break; @@ -243,10 +243,10 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, case guncon3_btn::b3: gc.btn_b3 |= 1; break; case guncon3_btn::c1: gc.btn_c1 |= 1; break; case guncon3_btn::c2: gc.btn_c2 |= 1; break; - case guncon3_btn::as_x: gc.stick_ax = static_cast(value); break; - case guncon3_btn::as_y: gc.stick_ay = static_cast(value); break; - case guncon3_btn::bs_x: gc.stick_bx = static_cast(value); break; - case guncon3_btn::bs_y: gc.stick_by = static_cast(value); break; + case guncon3_btn::as_x: gc.stick_ax = static_cast(value.value); break; + case guncon3_btn::as_y: gc.stick_ay = static_cast(value.value); break; + case guncon3_btn::bs_x: gc.stick_bx = static_cast(value.value); break; + case guncon3_btn::bs_y: gc.stick_by = static_cast(value.value); break; case guncon3_btn::count: break; } }; diff --git a/rpcs3/Emu/Io/KeyboardHandler.h b/rpcs3/Emu/Io/KeyboardHandler.h index 594f7be3f8..2a5261ff8b 100644 --- a/rpcs3/Emu/Io/KeyboardHandler.h +++ b/rpcs3/Emu/Io/KeyboardHandler.h @@ -128,9 +128,9 @@ public: KbInfo& GetInfo() { return m_info; } std::vector& GetKeyboards() { return m_keyboards; } - KbData& GetData(const u32 keyboard) { return m_keyboards[keyboard].m_data; } - KbExtraData& GetExtraData(const u32 keyboard) { return m_keyboards[keyboard].m_extra_data; } - KbConfig& GetConfig(const u32 keyboard) { return m_keyboards[keyboard].m_config; } + KbData& GetData(const u32 keyboard) { return ::at32(m_keyboards, keyboard).m_data; } + KbExtraData& GetExtraData(const u32 keyboard) { return ::at32(m_keyboards, keyboard).m_extra_data; } + KbConfig& GetConfig(const u32 keyboard) { return ::at32(m_keyboards, keyboard).m_config; } identifier id() const { return m_id; } void ReleaseAllKeys(); diff --git a/rpcs3/Emu/Io/LogitechG27.cpp b/rpcs3/Emu/Io/LogitechG27.cpp index 3f07e92560..2d2662ea5e 100644 --- a/rpcs3/Emu/Io/LogitechG27.cpp +++ b/rpcs3/Emu/Io/LogitechG27.cpp @@ -266,12 +266,28 @@ static const std::map> 4) & 1; @@ -1405,7 +1421,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp { // Damper/High resolution damper new_effect.type = SDL_HAPTIC_DAMPER; - new_effect.condition.direction = STEERING_DIRECTION; + new_effect.condition.direction = make_steering_direction(); new_effect.condition.length = SDL_HAPTIC_INFINITY; const u8 s1 = buf[3] & 1; const u8 s2 = buf[5] & 1; @@ -1445,7 +1461,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp { // Friction new_effect.type = SDL_HAPTIC_FRICTION; - new_effect.condition.direction = STEERING_DIRECTION; + new_effect.condition.direction = make_steering_direction(); new_effect.condition.length = SDL_HAPTIC_INFINITY; const u8 k1 = buf[2]; const u8 k2 = buf[3]; @@ -1472,7 +1488,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp { // Auto center spring/High resolution auto center spring new_effect.type = SDL_HAPTIC_SPRING; - new_effect.condition.direction = STEERING_DIRECTION; + new_effect.condition.direction = make_steering_direction(); new_effect.condition.length = SDL_HAPTIC_INFINITY; const u16 saturation = logitech_g27_clip_to_saturation(buf[4]); constexpr u16 deadband = 2 * 0xFFFF / 255; @@ -1518,7 +1534,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp else new_effect.type = m_reverse_effects ? SDL_HAPTIC_SAWTOOTHUP : SDL_HAPTIC_SAWTOOTHDOWN; new_effect.type = buf[1] == 0x04 ? SDL_HAPTIC_SAWTOOTHUP : SDL_HAPTIC_SAWTOOTHDOWN; - new_effect.periodic.direction = STEERING_DIRECTION; + new_effect.periodic.direction = make_steering_direction(); new_effect.periodic.length = SDL_HAPTIC_INFINITY; const u8 l1 = buf[2]; const u8 l2 = buf[3]; @@ -1558,7 +1574,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp { // Trapezoid, convert to SDL_HAPTIC_SQUARE or SDL_HAPTIC_TRIANGLE // TODO full accuracy will need some kind of rendering thread, cannot be represented with a single effect - new_effect.periodic.direction = STEERING_DIRECTION; + new_effect.periodic.direction = make_steering_direction(); new_effect.periodic.length = SDL_HAPTIC_INFINITY; const u8 l1 = buf[2]; const u8 l2 = buf[3]; @@ -1587,7 +1603,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp // Rectangle, convert to SDL_HAPTIC_SQUARE // TODO full accuracy will need some kind of rendering thread, cannot be represented with a single effect new_effect.type = SDL_HAPTIC_SQUARE; - new_effect.periodic.direction = STEERING_DIRECTION; + new_effect.periodic.direction = make_steering_direction(); new_effect.periodic.length = SDL_HAPTIC_INFINITY; const u8 l1 = buf[2]; const u8 l2 = buf[3]; @@ -1630,7 +1646,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp const u8 d = i == 0 ? d1 : d2; const u8 l = i == 0 ? l1 : l2; new_effect.constant.length = SDL_HAPTIC_INFINITY; - new_effect.constant.direction = STEERING_DIRECTION; + new_effect.constant.direction = make_steering_direction(); if (s == 0 || t == 0) { // gran turismo 6 does this, gives a variable force with no step so it just behaves as constant force @@ -1655,7 +1671,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp else { new_effect.type = SDL_HAPTIC_RAMP; - new_effect.ramp.direction = STEERING_DIRECTION; + new_effect.ramp.direction = make_steering_direction(); if (l2 > l1) logitech_g27_log.error("min force is larger than max force in ramp effect, l1 %u l2 %u", l1, l2); const s16 l1_converted = logitech_g27_force_to_level(l1, m_reverse_effects); @@ -1674,7 +1690,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp { // Square new_effect.type = SDL_HAPTIC_SQUARE; - new_effect.periodic.direction = STEERING_DIRECTION; + new_effect.periodic.direction = make_steering_direction(); const u8 a = buf[2]; const u8 tl = buf[3]; const u8 th = buf[4]; diff --git a/rpcs3/Emu/Io/LogitechG27.h b/rpcs3/Emu/Io/LogitechG27.h index ccb2f58908..e991472878 100644 --- a/rpcs3/Emu/Io/LogitechG27.h +++ b/rpcs3/Emu/Io/LogitechG27.h @@ -126,6 +126,7 @@ private: void transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer) const; void transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer) const; void transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer) const; + SDL_HapticDirection make_steering_direction() const; u32 m_controller_index = 0; diff --git a/rpcs3/Emu/Io/LogitechG27Config.cpp b/rpcs3/Emu/Io/LogitechG27Config.cpp index aa1ebf2af7..4f84988651 100644 --- a/rpcs3/Emu/Io/LogitechG27Config.cpp +++ b/rpcs3/Emu/Io/LogitechG27Config.cpp @@ -39,6 +39,22 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](g27_ffb_direction_type value) + { + switch (value) + { + case g27_ffb_direction_type::steering_axis: return "steering_axis"; + case g27_ffb_direction_type::cartesian: return "cartesian"; + case g27_ffb_direction_type::polar: return "polar"; + } + + return unknown; + }); +} + emulated_logitech_g27_config g_cfg_logitech_g27; LOG_CHANNEL(cfg_log, "CFG"); diff --git a/rpcs3/Emu/Io/LogitechG27Config.h b/rpcs3/Emu/Io/LogitechG27Config.h index 1a46a0fcc2..4f9ea45117 100644 --- a/rpcs3/Emu/Io/LogitechG27Config.h +++ b/rpcs3/Emu/Io/LogitechG27Config.h @@ -20,6 +20,13 @@ enum class hat_component right }; +enum class g27_ffb_direction_type +{ + steering_axis = 0, + cartesian = 1, + polar = 2, +}; + // this was a bitfield, but juggling at least 3 compilers and OSes means no bitfield // num_buttons:10 << 52 | num_hats:10 << 42 | num_axes:10 << 32 | vendor_id:16 << 16 | product_id:16 struct emulated_g27_device_type_id @@ -111,6 +118,7 @@ public: emulated_logitech_g27_mapping shifter_r{this, "shifter_r", 0, sdl_mapping_type::hat, 0, hat_component::left, false}; cfg::_bool reverse_effects{this, "reverse_effects", false}; + cfg::_enum ffb_direction_type{this, "ffb_direction_type", g27_ffb_direction_type::steering_axis}; cfg::uint<0, 4> compatibility_limit{this, "compatibility_limit", 4}; cfg::uint<0, 0xFFFFFFFFFFFFFFFF> ffb_device_type_id{this, "ffb_device_type_id", 0}; cfg::uint<0, 0xFFFFFFFFFFFFFFFF> led_device_type_id{this, "led_device_type_id", 0}; diff --git a/rpcs3/Emu/Io/Null/null_music_handler.h b/rpcs3/Emu/Io/Null/null_music_handler.h index fe022dc79e..f8f2258171 100644 --- a/rpcs3/Emu/Io/Null/null_music_handler.h +++ b/rpcs3/Emu/Io/Null/null_music_handler.h @@ -7,11 +7,11 @@ class null_music_handler final : public music_handler_base public: null_music_handler() : music_handler_base() {} - void stop() override { m_state = 0; } // CELL_MUSIC_PB_STATUS_STOP - void pause() override { m_state = 2; } // CELL_MUSIC_PB_STATUS_PAUSE - void play(const std::string& /*path*/) override { m_state = 1; } // CELL_MUSIC_PB_STATUS_PLAY - void fast_forward(const std::string& /*path*/) override { m_state = 3; } // CELL_MUSIC_PB_STATUS_FASTFORWARD - void fast_reverse(const std::string& /*path*/) override { m_state = 4; } // CELL_MUSIC_PB_STATUS_FASTREVERSE + void stop() override { set_state(0); } // CELL_MUSIC_PB_STATUS_STOP + void pause() override { set_state(2); } // CELL_MUSIC_PB_STATUS_PAUSE + void play(const std::string& /*path*/, bool automatic = false) override { set_state(1, automatic); } // CELL_MUSIC_PB_STATUS_PLAY + void fast_forward(const std::string& /*path*/) override { set_state(3); } // CELL_MUSIC_PB_STATUS_FASTFORWARD + void fast_reverse(const std::string& /*path*/) override { set_state(4); } // CELL_MUSIC_PB_STATUS_FASTREVERSE void set_volume(f32 volume) override { m_volume = volume; } f32 get_volume() const override { return m_volume; } diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index ccd81d2805..178ae9f4fa 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -11,7 +11,7 @@ PadHandlerBase::PadHandlerBase(pad_handler type) : m_type(type) { } -std::vector> PadHandlerBase::find_key_combos(const std::unordered_map& map, const std::string& cfg_string) +std::vector> PadHandlerBase::find_key_combos(const std::unordered_map& map, std::string_view cfg_string) { std::vector> key_codes; @@ -232,7 +232,7 @@ pad_capabilities PadHandlerBase::get_capabilities(const std::string& /*pad_id*/) }; } -cfg_pad* PadHandlerBase::get_config(const std::string& pad_id) +cfg_pad* PadHandlerBase::get_config(std::string_view pad_id) { int index = 0; @@ -595,52 +595,52 @@ std::array>, PadHandlerBase::button::button_count> Pad if (!device || !cfg) return mapping; - mapping[button::up] = find_key_combos(button_list, cfg->up); - mapping[button::down] = find_key_combos(button_list, cfg->down); - mapping[button::left] = find_key_combos(button_list, cfg->left); - mapping[button::right] = find_key_combos(button_list, cfg->right); - mapping[button::cross] = find_key_combos(button_list, cfg->cross); - mapping[button::square] = find_key_combos(button_list, cfg->square); - mapping[button::circle] = find_key_combos(button_list, cfg->circle); - mapping[button::triangle] = find_key_combos(button_list, cfg->triangle); - mapping[button::start] = find_key_combos(button_list, cfg->start); - mapping[button::select] = find_key_combos(button_list, cfg->select); - mapping[button::l1] = find_key_combos(button_list, cfg->l1); - mapping[button::l2] = find_key_combos(button_list, cfg->l2); - mapping[button::l3] = find_key_combos(button_list, cfg->l3); - mapping[button::r1] = find_key_combos(button_list, cfg->r1); - mapping[button::r2] = find_key_combos(button_list, cfg->r2); - mapping[button::r3] = find_key_combos(button_list, cfg->r3); - mapping[button::ls_left] = find_key_combos(button_list, cfg->ls_left); - mapping[button::ls_right] = find_key_combos(button_list, cfg->ls_right); - mapping[button::ls_down] = find_key_combos(button_list, cfg->ls_down); - mapping[button::ls_up] = find_key_combos(button_list, cfg->ls_up); - mapping[button::rs_left] = find_key_combos(button_list, cfg->rs_left); - mapping[button::rs_right] = find_key_combos(button_list, cfg->rs_right); - mapping[button::rs_down] = find_key_combos(button_list, cfg->rs_down); - mapping[button::rs_up] = find_key_combos(button_list, cfg->rs_up); - mapping[button::ps] = find_key_combos(button_list, cfg->ps); + mapping[button::up] = find_key_combos(button_list, cfg->up.to_string()); + mapping[button::down] = find_key_combos(button_list, cfg->down.to_string()); + mapping[button::left] = find_key_combos(button_list, cfg->left.to_string()); + mapping[button::right] = find_key_combos(button_list, cfg->right.to_string()); + mapping[button::cross] = find_key_combos(button_list, cfg->cross.to_string()); + mapping[button::square] = find_key_combos(button_list, cfg->square.to_string()); + mapping[button::circle] = find_key_combos(button_list, cfg->circle.to_string()); + mapping[button::triangle] = find_key_combos(button_list, cfg->triangle.to_string()); + mapping[button::start] = find_key_combos(button_list, cfg->start.to_string()); + mapping[button::select] = find_key_combos(button_list, cfg->select.to_string()); + mapping[button::l1] = find_key_combos(button_list, cfg->l1.to_string()); + mapping[button::l2] = find_key_combos(button_list, cfg->l2.to_string()); + mapping[button::l3] = find_key_combos(button_list, cfg->l3.to_string()); + mapping[button::r1] = find_key_combos(button_list, cfg->r1.to_string()); + mapping[button::r2] = find_key_combos(button_list, cfg->r2.to_string()); + mapping[button::r3] = find_key_combos(button_list, cfg->r3.to_string()); + mapping[button::ls_left] = find_key_combos(button_list, cfg->ls_left.to_string()); + mapping[button::ls_right] = find_key_combos(button_list, cfg->ls_right.to_string()); + mapping[button::ls_down] = find_key_combos(button_list, cfg->ls_down.to_string()); + mapping[button::ls_up] = find_key_combos(button_list, cfg->ls_up.to_string()); + mapping[button::rs_left] = find_key_combos(button_list, cfg->rs_left.to_string()); + mapping[button::rs_right] = find_key_combos(button_list, cfg->rs_right.to_string()); + mapping[button::rs_down] = find_key_combos(button_list, cfg->rs_down.to_string()); + mapping[button::rs_up] = find_key_combos(button_list, cfg->rs_up.to_string()); + mapping[button::ps] = find_key_combos(button_list, cfg->ps.to_string()); - mapping[button::skateboard_ir_nose] = find_key_combos(button_list, cfg->ir_nose); - mapping[button::skateboard_ir_tail] = find_key_combos(button_list, cfg->ir_tail); - mapping[button::skateboard_ir_left] = find_key_combos(button_list, cfg->ir_left); - mapping[button::skateboard_ir_right] = find_key_combos(button_list, cfg->ir_right); - mapping[button::skateboard_tilt_left] = find_key_combos(button_list, cfg->tilt_left); - mapping[button::skateboard_tilt_right] = find_key_combos(button_list, cfg->tilt_right); + mapping[button::skateboard_ir_nose] = find_key_combos(button_list, cfg->ir_nose.to_string()); + mapping[button::skateboard_ir_tail] = find_key_combos(button_list, cfg->ir_tail.to_string()); + mapping[button::skateboard_ir_left] = find_key_combos(button_list, cfg->ir_left.to_string()); + mapping[button::skateboard_ir_right] = find_key_combos(button_list, cfg->ir_right.to_string()); + mapping[button::skateboard_tilt_left] = find_key_combos(button_list, cfg->tilt_left.to_string()); + mapping[button::skateboard_tilt_right] = find_key_combos(button_list, cfg->tilt_right.to_string()); if (b_has_pressure_intensity_button) { - mapping[button::pressure_intensity_button] = find_key_combos(button_list, cfg->pressure_intensity_button); + mapping[button::pressure_intensity_button] = find_key_combos(button_list, cfg->pressure_intensity_button.to_string()); } if (b_has_analog_limiter_button) { - mapping[button::analog_limiter_button] = find_key_combos(button_list, cfg->analog_limiter_button); + mapping[button::analog_limiter_button] = find_key_combos(button_list, cfg->analog_limiter_button.to_string()); } if (b_has_orientation) { - mapping[button::orientation_reset_button] = find_key_combos(button_list, cfg->orientation_reset_button); + mapping[button::orientation_reset_button] = find_key_combos(button_list, cfg->orientation_reset_button.to_string()); } return mapping; diff --git a/rpcs3/Emu/Io/PadHandler.h b/rpcs3/Emu/Io/PadHandler.h index 0f38f0cb3b..4db058e597 100644 --- a/rpcs3/Emu/Io/PadHandler.h +++ b/rpcs3/Emu/Io/PadHandler.h @@ -193,7 +193,7 @@ protected: std::shared_ptr m_pad_for_pad_settings; // Search an unordered map for a string value and return the found combos - static std::vector> find_key_combos(const std::unordered_map& map, const std::string& cfg_string); + static std::vector> find_key_combos(const std::unordered_map& map, std::string_view cfg_string); // Search an unordered map for a combo and return the found key codes static std::set find_key_codes(const std::unordered_map& map, const pad::combo& combo); @@ -324,7 +324,7 @@ protected: virtual void get_mapping(const pad_ensemble& binding); void TranslateButtonPress(const std::shared_ptr& device, u32 keyCode, bool& pressed, u16& val, bool use_stick_multipliers, bool ignore_stick_threshold = false, bool ignore_trigger_threshold = false); void init_configs(); - cfg_pad* get_config(const std::string& pad_id); + cfg_pad* get_config(std::string_view pad_id); static void set_raw_orientation(ps_move_data& move_data, f32 accel_x, f32 accel_y, f32 accel_z, f32 gyro_x, f32 gyro_y, f32 gyro_z); static void set_raw_orientation(Pad& pad); diff --git a/rpcs3/Emu/Io/RB3MidiDrums.cpp b/rpcs3/Emu/Io/RB3MidiDrums.cpp index 61b172bbdd..88ccb17178 100644 --- a/rpcs3/Emu/Io/RB3MidiDrums.cpp +++ b/rpcs3/Emu/Io/RB3MidiDrums.cpp @@ -313,7 +313,7 @@ usb_device_rb3_midi_drums::Definition::Definition(std::string name, const std::s , create_state{create_state} {} -usb_device_rb3_midi_drums::usb_device_rb3_midi_drums(const std::array& location, const std::string& device_name) +usb_device_rb3_midi_drums::usb_device_rb3_midi_drums(const std::array& location, std::string_view device_name) : usb_device_emulated(location) { m_id_to_note_mapping = midi::create_id_to_note_mapping(); @@ -412,7 +412,10 @@ usb_device_rb3_midi_drums::usb_device_rb3_midi_drums(const std::array& lo usb_device_rb3_midi_drums::~usb_device_rb3_midi_drums() { - rtmidi_in_free(midi_in); + if (midi_in) + { + rtmidi_in_free(midi_in); + } } static const std::array disabled_response = { @@ -560,8 +563,8 @@ void usb_device_rb3_midi_drums::interrupt_transfer(u32 buf_size, u8* buf, u32 /* } else { - bool is_cancel = kit_state.snare >= midi::min_velocity(); - bool is_accept = kit_state.floor_tom >= midi::min_velocity(); + const bool is_cancel = kit_state.snare >= midi::min_velocity(); + const bool is_accept = kit_state.floor_tom >= midi::min_velocity(); if (hold_kick && (is_cancel || is_accept)) { // Hold kick brings up the song category selector menu, which can be dismissed using accept/cancel buttons. diff --git a/rpcs3/Emu/Io/RB3MidiDrums.h b/rpcs3/Emu/Io/RB3MidiDrums.h index 9d13a81812..8d71ce93ef 100644 --- a/rpcs3/Emu/Io/RB3MidiDrums.h +++ b/rpcs3/Emu/Io/RB3MidiDrums.h @@ -109,7 +109,7 @@ enum class Note : u8 class usb_device_rb3_midi_drums : public usb_device_emulated { public: - usb_device_rb3_midi_drums(const std::array& location, const std::string& device_name); + usb_device_rb3_midi_drums(const std::array& location, std::string_view device_name); ~usb_device_rb3_midi_drums(); void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; diff --git a/rpcs3/Emu/Io/RB3MidiGuitar.cpp b/rpcs3/Emu/Io/RB3MidiGuitar.cpp index 1d6d10bb76..573944dd52 100644 --- a/rpcs3/Emu/Io/RB3MidiGuitar.cpp +++ b/rpcs3/Emu/Io/RB3MidiGuitar.cpp @@ -6,7 +6,7 @@ LOG_CHANNEL(rb3_midi_guitar_log); -usb_device_rb3_midi_guitar::usb_device_rb3_midi_guitar(const std::array& location, const std::string& device_name, bool twentytwo_fret) +usb_device_rb3_midi_guitar::usb_device_rb3_midi_guitar(const std::array& location, std::string_view device_name, bool twentytwo_fret) : usb_device_emulated(location) { // For the 22-fret guitar (Fender Squier), the only thing that's different @@ -100,7 +100,10 @@ usb_device_rb3_midi_guitar::usb_device_rb3_midi_guitar(const std::array& usb_device_rb3_midi_guitar::~usb_device_rb3_midi_guitar() { - rtmidi_in_free(midi_in); + if (midi_in) + { + rtmidi_in_free(midi_in); + } } static const std::array disabled_response = { diff --git a/rpcs3/Emu/Io/RB3MidiGuitar.h b/rpcs3/Emu/Io/RB3MidiGuitar.h index 3a93f95d61..6c20aa5a2a 100644 --- a/rpcs3/Emu/Io/RB3MidiGuitar.h +++ b/rpcs3/Emu/Io/RB3MidiGuitar.h @@ -36,7 +36,7 @@ private: void write_state(u8* buf); public: - usb_device_rb3_midi_guitar(const std::array& location, const std::string& device_name, bool twentytwo_fret); + usb_device_rb3_midi_guitar(const std::array& location, std::string_view device_name, bool twentytwo_fret); ~usb_device_rb3_midi_guitar(); void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; diff --git a/rpcs3/Emu/Io/RB3MidiKeyboard.cpp b/rpcs3/Emu/Io/RB3MidiKeyboard.cpp index ccfc2dbdc8..f22ad800b7 100644 --- a/rpcs3/Emu/Io/RB3MidiKeyboard.cpp +++ b/rpcs3/Emu/Io/RB3MidiKeyboard.cpp @@ -6,7 +6,7 @@ LOG_CHANNEL(rb3_midi_keyboard_log); -usb_device_rb3_midi_keyboard::usb_device_rb3_midi_keyboard(const std::array& location, const std::string& device_name) +usb_device_rb3_midi_keyboard::usb_device_rb3_midi_keyboard(const std::array& location, std::string_view device_name) : usb_device_emulated(location) { device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 64, 0x12ba, 0x2338, 0x01, 0x01, 0x02, 0x00, 0x01}); @@ -86,7 +86,10 @@ usb_device_rb3_midi_keyboard::usb_device_rb3_midi_keyboard(const std::array disabled_response = { diff --git a/rpcs3/Emu/Io/RB3MidiKeyboard.h b/rpcs3/Emu/Io/RB3MidiKeyboard.h index c5042af239..131fedc0c3 100644 --- a/rpcs3/Emu/Io/RB3MidiKeyboard.h +++ b/rpcs3/Emu/Io/RB3MidiKeyboard.h @@ -41,7 +41,7 @@ private: void write_state(u8* buf); public: - usb_device_rb3_midi_keyboard(const std::array& location, const std::string& device_name); + usb_device_rb3_midi_keyboard(const std::array& location, std::string_view device_name); ~usb_device_rb3_midi_keyboard(); void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; diff --git a/rpcs3/Emu/Io/TopShotElite.cpp b/rpcs3/Emu/Io/TopShotElite.cpp index ed4042fdaa..7a8fcc1a34 100644 --- a/rpcs3/Emu/Io/TopShotElite.cpp +++ b/rpcs3/Emu/Io/TopShotElite.cpp @@ -280,12 +280,12 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en } bool up = false, right = false, down = false, left = false; - const auto input_callback = [&ts, &up, &down, &left, &right](topshotelite_btn btn, pad_button /*pad_button*/, u16 value, bool pressed, bool& /*abort*/) + const auto input_callback = [&ts, &up, &down, &left, &right](const emulated_pad_config::input_value& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { case topshotelite_btn::trigger: ts.btn_trigger |= 1; break; case topshotelite_btn::reload: ts.btn_reload |= 1; break; @@ -302,11 +302,11 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en case topshotelite_btn::dpad_down: down = true; break; case topshotelite_btn::dpad_left: left = true; break; case topshotelite_btn::dpad_right: right = true; break; - case topshotelite_btn::ls_x: ts.stick_lx = static_cast(value); break; + case topshotelite_btn::ls_x: ts.stick_lx = static_cast(value.value); break; // you know you have a «Top» controller when the games are programmed to ignore a perfect controller, so we have to simulate a drift - case topshotelite_btn::ls_y: ts.stick_ly = std::min(0xff, 1 + static_cast(value)); break; - case topshotelite_btn::rs_x: ts.stick_rx = static_cast(value); break; - case topshotelite_btn::rs_y: ts.stick_ry = static_cast(value); break; + case topshotelite_btn::ls_y: ts.stick_ly = std::min(0xff, 1 + static_cast(value.value)); break; + case topshotelite_btn::rs_x: ts.stick_rx = static_cast(value.value); break; + case topshotelite_btn::rs_y: ts.stick_ry = static_cast(value.value); break; case topshotelite_btn::count: break; } }; diff --git a/rpcs3/Emu/Io/TopShotFearmaster.cpp b/rpcs3/Emu/Io/TopShotFearmaster.cpp index 12d56b921c..a1b776ed53 100644 --- a/rpcs3/Emu/Io/TopShotFearmaster.cpp +++ b/rpcs3/Emu/Io/TopShotFearmaster.cpp @@ -308,12 +308,12 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32 } bool up = false, right = false, down = false, left = false; - const auto input_callback = [&ts, &up, &down, &left, &right](topshotfearmaster_btn btn, pad_button /*pad_button*/, u16 value, bool pressed, bool& /*abort*/) + const auto input_callback = [&ts, &up, &down, &left, &right](const emulated_pad_config::input_value& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { case topshotfearmaster_btn::trigger: ts.btn_trigger |= 1; break; case topshotfearmaster_btn::heartrate: ts.btn_heartrate |= 1; break; @@ -329,8 +329,8 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32 case topshotfearmaster_btn::dpad_down: down = true; break; case topshotfearmaster_btn::dpad_left: left = true; break; case topshotfearmaster_btn::dpad_right: right = true; break; - case topshotfearmaster_btn::ls_x: ts.stick_lx = static_cast(value); break; - case topshotfearmaster_btn::ls_y: ts.stick_ly = static_cast(value); break; + case topshotfearmaster_btn::ls_x: ts.stick_lx = static_cast(value.value); break; + case topshotfearmaster_btn::ls_y: ts.stick_ly = static_cast(value.value); break; case topshotfearmaster_btn::count: break; } }; diff --git a/rpcs3/Emu/Io/Turntable.cpp b/rpcs3/Emu/Io/Turntable.cpp index 261af88d16..812c7a098c 100644 --- a/rpcs3/Emu/Io/Turntable.cpp +++ b/rpcs3/Emu/Io/Turntable.cpp @@ -170,12 +170,12 @@ void usb_device_turntable::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpo return; const auto& cfg = ::at32(g_cfg_turntable.players, m_controller_index); - cfg->handle_input(pad, true, [&buf](turntable_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/) + cfg->handle_input(pad, true, [&buf](const auto& value, bool& /*abort*/) { - if (!pressed) + if (!value.pressed) return; - switch (btn) + switch (value.btn) { case turntable_btn::blue: buf[0] |= 0x01; // Square Button @@ -276,7 +276,7 @@ void usb_device_turntable::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpo break; case turntable_btn::right_turntable: // DJ Hero does not register input if the turntable is 0, so force it to 1. - buf[6] = std::max(1, 255 - value); // Right Turntable + buf[6] = std::max(1, 255 - value.to_8bit()); // Right Turntable // DJ Hero requires turntables to be centered at 128. // If this axis ends up centered at 127, force it to 128. if (buf[6] == 127) @@ -285,13 +285,19 @@ void usb_device_turntable::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpo } break; case turntable_btn::crossfader: - buf[21] = ((255 - value) & 0x3F) << 2; // Crossfader, lower 6 bits - buf[22] = ((255 - value) & 0xC0) >> 6; // Crossfader, upper 2 bits + { + const u16 val = value.to_10bit(); // ensure 10-bit range + buf[21] = (val & 0xFF); // Crossfader, lower 8 bits + buf[22] = (val >> 8) & 0x03; // Crossfader, upper 2 bits break; + } case turntable_btn::effects_dial: - buf[19] = (value & 0x3F) << 2; // Effects Dial, lower 6 bits - buf[20] = (value & 0xC0) >> 6; // Effects Dial, upper 2 bits + { + const u16 val = value.to_10bit(); // ensure 10-bit range + buf[19] = (val & 0xFF); // Effects Dial, lower 8 bits + buf[20] = (val >> 8) & 0x03; // Effects Dial, upper 2 bits break; + } case turntable_btn::count: break; } diff --git a/rpcs3/Emu/Io/camera_config.cpp b/rpcs3/Emu/Io/camera_config.cpp index 4016447321..f1fabe5531 100644 --- a/rpcs3/Emu/Io/camera_config.cpp +++ b/rpcs3/Emu/Io/camera_config.cpp @@ -77,7 +77,7 @@ void cfg_camera::camera_setting::from_string(std::string_view text) return; } - const std::vector list = fmt::split(text, { "," }); + const std::vector list = fmt::split_sv(text, { "," }); if (list.size() != member_count) { @@ -85,9 +85,15 @@ void cfg_camera::camera_setting::from_string(std::string_view text) return; } - const auto to_integer = [](const std::string& str, int& out) -> bool + const auto to_integer = [](std::string_view str, int& out) -> bool { - auto [ptr, ec] = std::from_chars(str.c_str(), str.c_str() + str.size(), out); + if (str.empty()) + { + camera_log.error("Empty string cannot be interpreted as integer."); + return false; + } + + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), out); if (ec != std::errc{}) { camera_log.error("String '%s' cannot be interpreted as integer.", str); @@ -96,11 +102,17 @@ void cfg_camera::camera_setting::from_string(std::string_view text) return true; }; - const auto to_double = [](const std::string& str, double& out) -> bool + const auto to_double = [](std::string_view str, double& out) -> bool { + if (str.empty()) + { + camera_log.error("Empty string cannot be interpreted as double."); + return false; + } + char* end{}; - out = std::strtod(str.c_str(), &end); - if (end != str.c_str() + str.size()) + out = std::strtod(str.data(), &end); + if (end != str.data() + str.size()) { camera_log.error("String '%s' cannot be interpreted as double.", str); return false; diff --git a/rpcs3/Emu/Io/emulated_pad_config.h b/rpcs3/Emu/Io/emulated_pad_config.h index b9bf68a223..c4a85dc2d7 100644 --- a/rpcs3/Emu/Io/emulated_pad_config.h +++ b/rpcs3/Emu/Io/emulated_pad_config.h @@ -88,32 +88,65 @@ public: button_map.clear(); } - void handle_input(std::shared_ptr pad, bool press_only, const std::function& func) const + struct input_value { - if (!pad || pad->is_copilot()) + public: + T btn = {}; + pad_button pad_btn = pad_button::pad_button_max_enum; + u16 value = 0; + u16 max_value = 255; + bool pressed = false; + + u8 to_8bit() const + { + if (max_value == 255) return static_cast(value & 0x00FF); + return static_cast(std::clamp(255.0f * (static_cast(std::min(value, max_value)) / ensure(max_value)), 0.0f, 255.0f)); + } + + u16 to_10bit() const + { + if (max_value == 1023) return value & 0x03FF; + return static_cast(std::clamp(1023.0f * (static_cast(std::min(value, max_value)) / ensure(max_value)), 0.0f, 1023.0f)); + } + + private: + + }; + + void handle_input(std::shared_ptr pad, bool press_only, const std::function& func) const + { + if (!func || !pad || pad->is_copilot()) return; - for (const Button& button : pad->m_buttons_external) + for (const ButtonExternal& button : pad->m_buttons_external) { if (button.m_pressed || !press_only) { - if (handle_input(func, button.m_offset, button.m_outKeyCode, button.m_value, button.m_pressed, true)) + if (handle_input_internal(func, button.m_offset, button.m_outKeyCode, button.m_value, 255, button.m_pressed, true)) { return; } } } - for (const AnalogStick& stick : pad->m_sticks_external) + for (const AnalogStickExternal& stick : pad->m_sticks_external) { - if (handle_input(func, stick.m_offset, get_axis_keycode(stick.m_offset, stick.m_value), stick.m_value, true, true)) + if (handle_input_internal(func, stick.m_offset, get_axis_keycode(stick.m_offset, stick.m_value), stick.m_value, 255, true, true)) + { + return; + } + } + + for (const AnalogSensor& sensor : pad->m_sensors) + { + if (handle_input_internal(func, sensor.m_offset, sensor.m_keyCode, sensor.m_value, 1023, true, false)) { return; } } } - void handle_input(const Mouse& mouse, const std::function& func) const + void handle_input(const Mouse& mouse, const std::function& func) const { for (int i = 0; i < 8; i++) { @@ -124,7 +157,7 @@ public: const u32 offset = pad_button_offset(button); const u32 keycode = pad_button_keycode(button); - if (handle_input(func, offset, keycode, 255, true, true)) + if (handle_input_internal(func, offset, keycode, 255, 255, true, true)) { return; } @@ -173,11 +206,11 @@ protected: return empty_set; } - bool handle_input(const std::function& func, u32 offset, u32 keycode, u16 value, bool pressed, bool check_axis) const + bool handle_input_internal(const std::function& func, u32 offset, u32 keycode, u16 value, u16 max_value, bool pressed, bool check_axis) const { - m_mutex.lock(); + if (!func) return false; - bool abort = false; + m_mutex.lock(); const auto& btns = find_button(offset, keycode); if (btns.empty()) @@ -192,20 +225,29 @@ protected: case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y: case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X: case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y: - abort = handle_input(func, offset, static_cast(axis_direction::both), value, pressed, false); - break; + return handle_input_internal(func, offset, static_cast(axis_direction::both), value, max_value, pressed, false); default: break; } } - return abort; + return false; } + bool abort = false; + for (const auto& btn : btns) { - if (btn && func) + if (btn) { - func(btn->btn_id(), btn->get(), value, pressed, abort); + const input_value params + { + .btn = btn->btn_id(), + .pad_btn = btn->get(), + .value = value, + .max_value = max_value, + .pressed = pressed + }; + func(params, abort); if (abort) break; } } diff --git a/rpcs3/Emu/Io/gem_config.cpp b/rpcs3/Emu/Io/gem_config.cpp new file mode 100644 index 0000000000..6151d1d081 --- /dev/null +++ b/rpcs3/Emu/Io/gem_config.cpp @@ -0,0 +1,83 @@ +#include "stdafx.h" +#include "gem_config.h" + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](gem_ext_id value) + { + switch (value) + { + case gem_ext_id::disconnected: return "Disconnected"; + case gem_ext_id::sharpshooter: return "Sharpshooter"; + case gem_ext_id::racing_wheel: return "Racing Wheel"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](gem_btn value) + { + switch (value) + { + case gem_btn::start: return "Start"; + case gem_btn::select: return "Select"; + case gem_btn::triangle: return "Triangle"; + case gem_btn::circle: return "Circle"; + case gem_btn::cross: return "Cross"; + case gem_btn::square: return "Square"; + case gem_btn::move: return "Move"; + case gem_btn::t: return "T"; + case gem_btn::x_axis: return "X-Axis"; + case gem_btn::y_axis: return "Y-Axis"; + case gem_btn::combo: return "Combo"; + case gem_btn::combo_start: return "Combo Start"; + case gem_btn::combo_select: return "Combo Select"; + case gem_btn::combo_triangle: return "Combo Triangle"; + case gem_btn::combo_circle: return "Combo Circle"; + case gem_btn::combo_cross: return "Combo Cross"; + case gem_btn::combo_square: return "Combo Square"; + case gem_btn::combo_move: return "Combo Move"; + case gem_btn::combo_t: return "Combo T"; + case gem_btn::sharpshooter_firing_mode_1: return "Firing Mode 1"; + case gem_btn::sharpshooter_firing_mode_2: return "Firing Mode 2"; + case gem_btn::sharpshooter_firing_mode_3: return "Firing Mode 3"; + case gem_btn::sharpshooter_trigger: return "Trigger"; + case gem_btn::sharpshooter_reload: return "Reload"; + case gem_btn::racing_wheel_d_pad_up: return "Up"; + case gem_btn::racing_wheel_d_pad_right: return "Right"; + case gem_btn::racing_wheel_d_pad_down: return "Down"; + case gem_btn::racing_wheel_d_pad_left: return "Left"; + case gem_btn::racing_wheel_throttle: return "Throttle"; + case gem_btn::racing_wheel_l1: return "L1"; + case gem_btn::racing_wheel_r1: return "R1"; + case gem_btn::racing_wheel_l2: return "L2"; + case gem_btn::racing_wheel_r2: return "R2"; + case gem_btn::racing_wheel_paddle_l: return "Paddle L"; + case gem_btn::racing_wheel_paddle_r: return "Paddle R"; + case gem_btn::combo_sharpshooter_firing_mode_1: return "Combo Firing Mode 1"; + case gem_btn::combo_sharpshooter_firing_mode_2: return "Combo Firing Mode 2"; + case gem_btn::combo_sharpshooter_firing_mode_3: return "Combo Firing Mode 3"; + case gem_btn::combo_sharpshooter_trigger: return "Combo Trigger"; + case gem_btn::combo_sharpshooter_reload: return "Combo Reload"; + case gem_btn::combo_racing_wheel_d_pad_up: return "Combo Up"; + case gem_btn::combo_racing_wheel_d_pad_right: return "Combo Right"; + case gem_btn::combo_racing_wheel_d_pad_down: return "Combo Down"; + case gem_btn::combo_racing_wheel_d_pad_left: return "Combo Left"; + case gem_btn::combo_racing_wheel_throttle: return "Combo Throttle"; + case gem_btn::combo_racing_wheel_l1: return "Combo L1"; + case gem_btn::combo_racing_wheel_r1: return "Combo R1"; + case gem_btn::combo_racing_wheel_l2: return "Combo L2"; + case gem_btn::combo_racing_wheel_r2: return "Combo R2"; + case gem_btn::combo_racing_wheel_paddle_l: return "Combo Paddle L"; + case gem_btn::combo_racing_wheel_paddle_r: return "Combo Paddle R"; + case gem_btn::count: return "Count"; + } + + return unknown; + }); +} diff --git a/rpcs3/Emu/Io/gem_config.h b/rpcs3/Emu/Io/gem_config.h index 9cfca88ddb..a87e98593d 100644 --- a/rpcs3/Emu/Io/gem_config.h +++ b/rpcs3/Emu/Io/gem_config.h @@ -2,6 +2,13 @@ #include "emulated_pad_config.h" +enum class gem_ext_id : u32 +{ + disconnected, + sharpshooter, + racing_wheel +}; + enum class gem_btn : u32 { start, @@ -17,7 +24,9 @@ enum class gem_btn : u32 combo_begin, combo = combo_begin, - combo_start, + + combo_buttons_begin, + combo_start = combo_buttons_begin, combo_select, combo_triangle, combo_circle, @@ -25,7 +34,52 @@ enum class gem_btn : u32 combo_square, combo_move, combo_t, - combo_end = combo_t, + combo_buttons_end = combo_t, + combo_end = combo_buttons_end, + + sharpshooter_begin, + sharpshooter_firing_mode_1 = sharpshooter_begin, + sharpshooter_firing_mode_2, + sharpshooter_firing_mode_3, + sharpshooter_trigger, + sharpshooter_reload, + sharpshooter_end = sharpshooter_reload, + + combo_sharpshooter_begin, + combo_sharpshooter_firing_mode_1 = combo_sharpshooter_begin, + combo_sharpshooter_firing_mode_2, + combo_sharpshooter_firing_mode_3, + combo_sharpshooter_trigger, + combo_sharpshooter_reload, + combo_sharpshooter_end = combo_sharpshooter_reload, + + racing_wheel_begin, + racing_wheel_d_pad_up = racing_wheel_begin, + racing_wheel_d_pad_right, + racing_wheel_d_pad_down, + racing_wheel_d_pad_left, + racing_wheel_throttle, + racing_wheel_l1, + racing_wheel_r1, + racing_wheel_l2, + racing_wheel_r2, + racing_wheel_paddle_l, + racing_wheel_paddle_r, + racing_wheel_end = racing_wheel_paddle_r, + + combo_racing_wheel_begin, + combo_racing_wheel_d_pad_up = combo_racing_wheel_begin, + combo_racing_wheel_d_pad_right, + combo_racing_wheel_d_pad_down, + combo_racing_wheel_d_pad_left, + combo_racing_wheel_throttle, + combo_racing_wheel_l1, + combo_racing_wheel_r1, + combo_racing_wheel_l2, + combo_racing_wheel_r2, + combo_racing_wheel_paddle_l, + combo_racing_wheel_paddle_r, + combo_racing_wheel_end = combo_racing_wheel_paddle_r, count }; @@ -44,6 +98,26 @@ struct cfg_fake_gem final : public emulated_pad_config cfg_pad_btn t{ this, "T", gem_btn::t, pad_button::R2 }; cfg_pad_btn x_axis{ this, "X-Axis", gem_btn::x_axis, pad_button::ls_x }; cfg_pad_btn y_axis{ this, "Y-Axis", gem_btn::y_axis, pad_button::ls_y }; + + cfg_pad_btn sharpshooter_firing_mode_1{ this, "Sharpshooter Firing Mode 1", gem_btn::sharpshooter_firing_mode_1, pad_button::dpad_left }; + cfg_pad_btn sharpshooter_firing_mode_2{ this, "Sharpshooter Firing Mode 2", gem_btn::sharpshooter_firing_mode_2, pad_button::dpad_up }; + cfg_pad_btn sharpshooter_firing_mode_3{ this, "Sharpshooter Firing Mode 3", gem_btn::sharpshooter_firing_mode_3, pad_button::dpad_right }; + cfg_pad_btn sharpshooter_trigger{ this, "Sharpshooter Trigger", gem_btn::sharpshooter_trigger, pad_button::L1 }; + cfg_pad_btn sharpshooter_reload{ this, "Sharpshooter Reload", gem_btn::sharpshooter_reload, pad_button::L2 }; + + cfg_pad_btn racing_wheel_d_pad_up{ this, "Racing Wheel D-Pad Up", gem_btn::racing_wheel_d_pad_up, pad_button::dpad_up }; + cfg_pad_btn racing_wheel_d_pad_right{ this, "Racing Wheel D-Pad Right", gem_btn::racing_wheel_d_pad_right, pad_button::dpad_right }; + cfg_pad_btn racing_wheel_d_pad_down{ this, "Racing Wheel D-Pad Down", gem_btn::racing_wheel_d_pad_down, pad_button::dpad_down }; + cfg_pad_btn racing_wheel_d_pad_left{ this, "Racing Wheel D-Pad Left", gem_btn::racing_wheel_d_pad_left, pad_button::dpad_left }; + cfg_pad_btn racing_wheel_throttle{ this, "Racing Wheel Throttle", gem_btn::racing_wheel_throttle, pad_button::rs_up }; + cfg_pad_btn racing_wheel_l1{ this, "Racing Wheel L1", gem_btn::racing_wheel_l1, pad_button::L1 }; + cfg_pad_btn racing_wheel_r1{ this, "Racing Wheel R1", gem_btn::racing_wheel_r1, pad_button::R1 }; + cfg_pad_btn racing_wheel_l2{ this, "Racing Wheel L2", gem_btn::racing_wheel_l2, pad_button::L2 }; + cfg_pad_btn racing_wheel_r2{ this, "Racing Wheel R2", gem_btn::racing_wheel_r2, pad_button::R2 }; + cfg_pad_btn racing_wheel_paddle_l{ this, "Racing Wheel Paddle L", gem_btn::racing_wheel_paddle_l, pad_button::L3 }; + cfg_pad_btn racing_wheel_paddle_r{ this, "Racing Wheel Paddle R", gem_btn::racing_wheel_paddle_r, pad_button::R3 }; + + cfg::_enum external_device{ this, "External Device", gem_ext_id::disconnected }; }; struct cfg_fake_gems final : public emulated_pads_config @@ -72,6 +146,42 @@ struct cfg_mouse_gem final : public emulated_pad_config cfg_pad_btn combo_square{ this, "Combo Square", gem_btn::combo_square, pad_button::pad_button_max_enum }; cfg_pad_btn combo_move{ this, "Combo Move", gem_btn::combo_move, pad_button::pad_button_max_enum }; cfg_pad_btn combo_t{ this, "Combo T", gem_btn::combo_t, pad_button::pad_button_max_enum }; + + cfg_pad_btn sharpshooter_firing_mode_1{ this, "Sharpshooter Firing Mode 1", gem_btn::sharpshooter_firing_mode_1, pad_button::pad_button_max_enum }; + cfg_pad_btn sharpshooter_firing_mode_2{ this, "Sharpshooter Firing Mode 2", gem_btn::sharpshooter_firing_mode_2, pad_button::pad_button_max_enum }; + cfg_pad_btn sharpshooter_firing_mode_3{ this, "Sharpshooter Firing Mode 3", gem_btn::sharpshooter_firing_mode_3, pad_button::pad_button_max_enum }; + cfg_pad_btn sharpshooter_trigger{ this, "Sharpshooter Trigger", gem_btn::sharpshooter_trigger, pad_button::pad_button_max_enum }; + cfg_pad_btn sharpshooter_reload{ this, "Sharpshooter Reload", gem_btn::sharpshooter_reload, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_sharpshooter_firing_mode_1{ this, "Combo Sharpshooter Firing Mode 1", gem_btn::combo_sharpshooter_firing_mode_1, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_sharpshooter_firing_mode_2{ this, "Combo Sharpshooter Firing Mode 2", gem_btn::combo_sharpshooter_firing_mode_2, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_sharpshooter_firing_mode_3{ this, "Combo Sharpshooter Firing Mode 3", gem_btn::combo_sharpshooter_firing_mode_3, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_sharpshooter_trigger{ this, "Combo Sharpshooter Trigger", gem_btn::combo_sharpshooter_trigger, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_sharpshooter_reload{ this, "Combo Sharpshooter Reload", gem_btn::combo_sharpshooter_reload, pad_button::pad_button_max_enum }; + + cfg_pad_btn racing_wheel_d_pad_up{ this, "Racing Wheel D-Pad Up", gem_btn::racing_wheel_d_pad_up, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_d_pad_right{ this, "Racing Wheel D-Pad Right", gem_btn::racing_wheel_d_pad_right, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_d_pad_down{ this, "Racing Wheel D-Pad Down", gem_btn::racing_wheel_d_pad_down, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_d_pad_left{ this, "Racing Wheel D-Pad Left", gem_btn::racing_wheel_d_pad_left, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_throttle{ this, "Racing Wheel Throttle", gem_btn::racing_wheel_throttle, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_l1{ this, "Racing Wheel L1", gem_btn::racing_wheel_l1, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_r1{ this, "Racing Wheel R1", gem_btn::racing_wheel_r1, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_l2{ this, "Racing Wheel L2", gem_btn::racing_wheel_l2, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_r2{ this, "Racing Wheel R2", gem_btn::racing_wheel_r2, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_paddle_l{ this, "Racing Wheel Paddle L", gem_btn::racing_wheel_paddle_l, pad_button::pad_button_max_enum }; + cfg_pad_btn racing_wheel_paddle_r{ this, "Racing Wheel Paddle R", gem_btn::racing_wheel_paddle_r, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_d_pad_up{ this, "Combo Racing Wheel D-Pad Up", gem_btn::combo_racing_wheel_d_pad_up, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_d_pad_right{ this, "Combo Racing Wheel D-Pad Right", gem_btn::combo_racing_wheel_d_pad_right, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_d_pad_down{ this, "Combo Racing Wheel D-Pad Down", gem_btn::combo_racing_wheel_d_pad_down, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_d_pad_left{ this, "Combo Racing Wheel D-Pad Left", gem_btn::combo_racing_wheel_d_pad_left, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_throttle{ this, "Combo Racing Wheel Throttle", gem_btn::combo_racing_wheel_throttle, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_l1{ this, "Combo Racing Wheel L1", gem_btn::combo_racing_wheel_l1, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_r1{ this, "Combo Racing Wheel R1", gem_btn::combo_racing_wheel_r1, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_l2{ this, "Combo Racing Wheel L2", gem_btn::combo_racing_wheel_l2, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_r2{ this, "Combo Racing Wheel R2", gem_btn::combo_racing_wheel_r2, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_paddle_l{ this, "Combo Racing Wheel Paddle L", gem_btn::combo_racing_wheel_paddle_l, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_racing_wheel_paddle_r{ this, "Combo Racing Wheel Paddle R", gem_btn::combo_racing_wheel_paddle_r, pad_button::pad_button_max_enum }; + + cfg::_enum external_device{ this, "External Device", gem_ext_id::disconnected }; }; struct cfg_mouse_gems final : public emulated_pads_config diff --git a/rpcs3/Emu/Io/music_handler_base.h b/rpcs3/Emu/Io/music_handler_base.h index 8af59a938d..28079c508a 100644 --- a/rpcs3/Emu/Io/music_handler_base.h +++ b/rpcs3/Emu/Io/music_handler_base.h @@ -15,23 +15,41 @@ public: virtual void stop() = 0; virtual void pause() = 0; - virtual void play(const std::string& path) = 0; + virtual void play(const std::string& path, bool automatic = false) = 0; virtual void fast_forward(const std::string& path) = 0; virtual void fast_reverse(const std::string& path) = 0; virtual void set_volume(f32 volume) = 0; virtual f32 get_volume() const = 0; - s32 get_state() const + void set_state(u32 state, bool automatic = false) + { + const bool changed = m_state.exchange(state) != state; + + if (m_event_status_callback && (changed || !automatic)) + { + m_event_status_callback(state); + } + } + + u32 get_state() const { return m_state; } - void set_status_callback(std::function status_callback) + void set_event_status_callback(std::function status_callback) { - m_status_callback = std::move(status_callback); + m_event_status_callback = std::move(status_callback); } + void set_playback_status_callback(std::function status_callback) + { + m_playback_status_callback = std::move(status_callback); + } + +private: + atomic_t m_state{0}; + std::function m_event_status_callback; + protected: - atomic_t m_state{0}; - std::function m_status_callback; + std::function m_playback_status_callback; }; diff --git a/rpcs3/Emu/Io/pad_types.cpp b/rpcs3/Emu/Io/pad_types.cpp index d5ef26f0d7..aa374a7fd8 100644 --- a/rpcs3/Emu/Io/pad_types.cpp +++ b/rpcs3/Emu/Io/pad_types.cpp @@ -40,6 +40,11 @@ void fmt_class_string::format(std::string& out, u64 arg) case pad_button::rs_x: return "Right Stick X-Axis"; case pad_button::rs_y: return "Right Stick Y-Axis"; case pad_button::pad_button_max_enum: return ""; + case pad_button::motion_x: return "Motion X"; + case pad_button::motion_y: return "Motion Y"; + case pad_button::motion_z: return "Motion Z"; + case pad_button::motion_g: return "Motion G"; + case pad_button::pad_motion_max_enum: return ""; case pad_button::mouse_button_1: return "Mouse Button 1"; case pad_button::mouse_button_2: return "Mouse Button 2"; case pad_button::mouse_button_3: return "Mouse Button 3"; @@ -87,6 +92,11 @@ u32 pad_button_offset(pad_button button) case pad_button::rs_right: return CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X; case pad_button::rs_x: return CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X; case pad_button::rs_y: return CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y; + case pad_button::motion_x: return CELL_PAD_BTN_OFFSET_SENSOR_X; + case pad_button::motion_y: return CELL_PAD_BTN_OFFSET_SENSOR_Y; + case pad_button::motion_z: return CELL_PAD_BTN_OFFSET_SENSOR_Z; + case pad_button::motion_g: return CELL_PAD_BTN_OFFSET_SENSOR_G; + case pad_button::pad_motion_max_enum: case pad_button::pad_button_max_enum: case pad_button::mouse_button_1: case pad_button::mouse_button_2: @@ -135,6 +145,11 @@ u32 pad_button_keycode(pad_button button) case pad_button::rs_x: return static_cast(axis_direction::both); case pad_button::rs_y: return static_cast(axis_direction::both); case pad_button::pad_button_max_enum: return 0; + case pad_button::motion_x: return 0; + case pad_button::motion_y: return 0; + case pad_button::motion_z: return 0; + case pad_button::motion_g: return 0; + case pad_button::pad_motion_max_enum: return 0; case pad_button::mouse_button_1: return 1; case pad_button::mouse_button_2: return 2; case pad_button::mouse_button_3: return 3; diff --git a/rpcs3/Emu/Io/pad_types.h b/rpcs3/Emu/Io/pad_types.h index d2b1535a1a..b7f844a22e 100644 --- a/rpcs3/Emu/Io/pad_types.h +++ b/rpcs3/Emu/Io/pad_types.h @@ -44,6 +44,13 @@ enum class pad_button : u8 pad_button_max_enum, + motion_x, + motion_y, + motion_z, + motion_g, + + pad_motion_max_enum, + // Special buttons for mouse input mouse_button_1, mouse_button_2, @@ -421,6 +428,14 @@ struct Button } }; +struct ButtonExternal +{ + u32 m_offset = 0; + u32 m_outKeyCode = 0; + u16 m_value = 0; + bool m_pressed = false; +}; + struct AnalogStick { u32 m_offset = 0; @@ -433,6 +448,9 @@ struct AnalogStick std::map m_pressed_keys_max; // only used in keyboard_pad_handler std::map m_pressed_combos_min; // only used in keyboard_pad_handler std::map m_pressed_combos_max; // only used in keyboard_pad_handler + u8 m_stick_min = 0; // only used in keyboard_pad_handler + u8 m_stick_max = 128; // only used in keyboard_pad_handler + u8 m_stick_val = 128; // only used in keyboard_pad_handler AnalogStick() {} AnalogStick(u32 offset, std::vector> key_combos_min, std::vector> key_combos_max) @@ -442,6 +460,12 @@ struct AnalogStick {} }; +struct AnalogStickExternal +{ + u32 m_offset = 0; + u16 m_value = 128; +}; + struct AnalogSensor { u32 m_offset = 0; @@ -519,8 +543,8 @@ struct Pad std::array m_sensors{}; std::array m_vibrate_motors{}; - std::vector