diff --git a/.ci/build-mac-arm64.sh b/.ci/build-mac-arm64.sh index 49b82737c7..043e421d80 100755 --- a/.ci/build-mac-arm64.sh +++ b/.ci/build-mac-arm64.sh @@ -6,7 +6,7 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 -brew install -f --overwrite --quiet googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader +brew install -f --overwrite --quiet googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" sdl3 vulkan-headers vulkan-loader brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index 6328ce05d3..e391e3e575 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -10,7 +10,7 @@ brew install -f --overwrite --quiet ccache "llvm@$LLVM_COMPILER_VER" brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" # shellcheck disable=SC3009 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 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader +arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet python@3.14 opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" sdl3 vulkan-headers vulkan-loader arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 diff --git a/.ci/deploy-mac-arm64.sh b/.ci/deploy-mac-arm64.sh index 8a27d04676..a4661dc0dd 100755 --- a/.ci/deploy-mac-arm64.sh +++ b/.ci/deploy-mac-arm64.sh @@ -57,6 +57,10 @@ else rm -f translations.zip fi +# Copy Qt translations manually +QT_TRANS="$WORKDIR/qt-downloader/$QT_VER/clang_64/translations" +cp $QT_TRANS/qt*.qm rpcs3.app/Contents/translations + # Hack install_name_tool -delete_rpath /opt/homebrew/lib RPCS3.app/Contents/MacOS/rpcs3 || echo "Hack for deleting rpath /opt/homebrew/lib not needed" install_name_tool -delete_rpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib RPCS3.app/Contents/MacOS/rpcs3 || echo "Hack for deleting rpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib not needed" diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index ec71fe0262..b0bfb4b455 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -58,6 +58,10 @@ else rm -f translations.zip fi +# Copy Qt translations manually +QT_TRANS="$WORKDIR/qt-downloader/$QT_VER/clang_64/translations" +cp $QT_TRANS/qt*.qm rpcs3.app/Contents/translations + # Need to do this rename hack due to case insensitive filesystem mv rpcs3.app RPCS3_.app mv RPCS3_.app RPCS3.app diff --git a/.ci/deploy-windows-clang.sh b/.ci/deploy-windows-clang.sh index c95f82e7b8..0bf731e7c8 100644 --- a/.ci/deploy-windows-clang.sh +++ b/.ci/deploy-windows-clang.sh @@ -38,7 +38,7 @@ else echo "Failed to download translations.zip. Continuing without translations." exit 0 } - unzip -o translations.zip -d "./bin/share/qt6/translations" >/dev/null 2>&1 || \ + 7z x translations.zip -o"./bin/share/qt6/translations" >/dev/null 2>&1 || \ echo "Failed to extract translations.zip. Continuing without translations." rm -f translations.zip fi diff --git a/.ci/setup-windows.sh b/.ci/setup-windows.sh index f637cec9ad..d874c7a7f0 100755 --- a/.ci/setup-windows.sh +++ b/.ci/setup-windows.sh @@ -14,6 +14,7 @@ QT_DECL_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qtdeclarative${QT_SUFFIX}" QT_TOOL_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qttools${QT_SUFFIX}" QT_MM_URL="${QT_HOST}${QT_PREFIX}addons.qtmultimedia.${QT_PREFIX_2}qtmultimedia${QT_SUFFIX}" 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" CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.11.2/ccache-4.11.2-windows-x86_64.zip" @@ -24,6 +25,7 @@ DEP_URLS=" \ $QT_TOOL_URL \ $QT_MM_URL \ $QT_SVG_URL \ + $QT_TRANSLATIONS_URL \ $LLVMLIBS_URL \ $VULKAN_SDK_URL\ $CCACHE_URL" diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 7bb68f1f03..e5c4e6ec61 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -528,7 +528,11 @@ jobs: env: CCACHE_DIR: ${{ github.workspace }}/ccache QT_VER_MAIN: '6' - LLVM_COMPILER_VER: '19' + LLVM_COMPILER_VER: '-devel' + CC: 'clang-devel' + CXX: 'clang++-devel' + LLVM_CONFIG: 'llvm-config-devel' + steps: - name: Checkout repository uses: actions/checkout@main @@ -547,8 +551,10 @@ jobs: id: root uses: vmactions/freebsd-vm@v1 with: - envs: 'QT_VER_MAIN LLVM_COMPILER_VER CCACHE_DIR' + envs: 'QT_VER_MAIN LLVM_COMPILER_VER CCACHE_DIR CC CXX LLVM_CONFIG' usesh: true + copyback: false + release: "14.3" run: .ci/install-freebsd.sh && .ci/build-freebsd.sh - name: Save Build Ccache diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index a800ba1dd5..3b2dc05f1f 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -107,7 +107,7 @@ add_subdirectory(yaml-cpp) # OpenGL -if (NOT ANDROID) +if (NOT ANDROID AND NOT APPLE) find_package(OpenGL REQUIRED OPTIONAL_COMPONENTS EGL) add_library(3rdparty_opengl INTERFACE) @@ -119,8 +119,6 @@ if (NOT ANDROID) else() target_link_libraries(3rdparty_opengl INTERFACE dxgi.lib d2d1.lib dwrite.lib) endif() - elseif(APPLE) - target_link_libraries(3rdparty_opengl INTERFACE OpenGL::GL OpenGL::GLU) else() target_link_libraries(3rdparty_opengl INTERFACE OpenGL::GL OpenGL::GLU OpenGL::GLX) endif() @@ -335,7 +333,7 @@ endif() # GLEW add_library(3rdparty_glew INTERFACE) -if(NOT MSVC AND NOT ANDROID) +if(NOT MSVC AND NOT ANDROID AND NOT APPLE) find_package(GLEW REQUIRED) target_link_libraries(3rdparty_glew INTERFACE GLEW::GLEW) endif() diff --git a/3rdparty/OpenAL/openal-soft b/3rdparty/OpenAL/openal-soft index 0e5e98e4ac..75c0059630 160000 --- a/3rdparty/OpenAL/openal-soft +++ b/3rdparty/OpenAL/openal-soft @@ -1 +1 @@ -Subproject commit 0e5e98e4ac8adae92e4f7653dd6eee17aa9c8791 +Subproject commit 75c00596307bf05ba7bbc8c7022836bf52f17477 diff --git a/3rdparty/libpng/libpng b/3rdparty/libpng/libpng index 49363adcfa..4e3f57d50f 160000 --- a/3rdparty/libpng/libpng +++ b/3rdparty/libpng/libpng @@ -1 +1 @@ -Subproject commit 49363adcfaf098748d7a4c8c624ad8c45a8c3a86 +Subproject commit 4e3f57d50f552841550a36eabbb3fbcecacb7750 diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL index 7f3ae3d574..a962f40bbb 160000 --- a/3rdparty/libsdl-org/SDL +++ b/3rdparty/libsdl-org/SDL @@ -1 +1 @@ -Subproject commit 7f3ae3d57459e59943a4ecfefc8f6277ec6bf540 +Subproject commit a962f40bbba175e9716557a25d5d7965f134a3d3 diff --git a/3rdparty/libsdl-org/SDL.vcxproj b/3rdparty/libsdl-org/SDL.vcxproj index f0b38ca09f..fd2bcf2f03 100644 --- a/3rdparty/libsdl-org/SDL.vcxproj +++ b/3rdparty/libsdl-org/SDL.vcxproj @@ -23,6 +23,7 @@ + @@ -102,6 +103,7 @@ + @@ -130,6 +132,8 @@ + + @@ -140,7 +144,11 @@ + + + + @@ -156,6 +164,7 @@ + @@ -175,7 +184,6 @@ - @@ -184,20 +192,35 @@ + + + + + + + + + + + + + + + @@ -241,6 +264,7 @@ + @@ -256,13 +280,14 @@ + + - @@ -303,7 +328,6 @@ - @@ -333,7 +357,6 @@ - @@ -393,7 +416,6 @@ - @@ -464,6 +486,7 @@ + @@ -471,12 +494,11 @@ - + - diff --git a/3rdparty/libsdl-org/SDL.vcxproj.filters b/3rdparty/libsdl-org/SDL.vcxproj.filters index 5839899c0d..8b7f293ef3 100644 --- a/3rdparty/libsdl-org/SDL.vcxproj.filters +++ b/3rdparty/libsdl-org/SDL.vcxproj.filters @@ -214,6 +214,9 @@ {000028b2ea36d7190d13777a4dc70000} + + {695ffc61-5497-4227-b415-15e9bdd5b6bf} + @@ -699,9 +702,6 @@ video\yuv2rgb - - video\windows - video\windows @@ -831,9 +831,6 @@ render\software - - render\software - render\software @@ -911,12 +908,6 @@ - - - - - - render\vulkan @@ -950,6 +941,60 @@ + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video + + + video + + + video + + + misc + + + haptic\hidapi + + + haptic\hidapi + + + core + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + API Headers + @@ -1037,9 +1082,6 @@ core - - core\windows - core\windows @@ -1166,9 +1208,6 @@ joystick\dummy - - joystick\gdk - joystick\hidapi @@ -1328,9 +1367,6 @@ video\dummy - - video\windows - video\windows @@ -1343,9 +1379,6 @@ video\windows - - video\windows - video\windows @@ -1508,9 +1541,6 @@ render\software - - render\software - render\software @@ -1535,9 +1565,6 @@ - - - render\vulkan @@ -1579,6 +1606,66 @@ + + video\windows + + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video + + + misc + + + haptic\hidapi + + + haptic\hidapi + + + core\windows + + + core\windows + + + joystick\gdk + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + diff --git a/BUILDING.md b/BUILDING.md index 60b7046cb2..32cdc6cc03 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -108,9 +108,10 @@ Clone and initialize the repository ```bash git clone --recurse-submodules https://github.com/RPCS3/rpcs3.git cd rpcs3 +git submodule sync # This is automatically done by `git clone --recurse-submodules`, # but in case you forgot it, you can manually fetch submodules this way: -git submodule update --init +git submodule update --init --recursive ``` ### Windows diff --git a/Utilities/cheat_info.cpp b/Utilities/cheat_info.cpp index cc8934f15a..7745d26732 100644 --- a/Utilities/cheat_info.cpp +++ b/Utilities/cheat_info.cpp @@ -34,7 +34,7 @@ bool cheat_info::from_str(std::string_view cheat_line) s64 val64 = 0; if (cheat_vec.size() != 5 || !try_to_int64(&val64, cheat_vec[2], 0, cheat_type_max - 1)) { - log_cheat.fatal("Failed to parse cheat line"); + log_cheat.error("Failed to parse cheat line: '%s'", cheat_line); return false; } diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index 249aca9910..6e5c2cd635 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -151,8 +151,9 @@ if (NOT ANDROID) get_target_property(WINDEPLOYQT_EXECUTABLE Qt6::windeployqt IMPORTED_LOCATION) add_custom_command(TARGET rpcs3 POST_BUILD COMMAND ${WINDEPLOYQT_EXECUTABLE} --no-compiler-runtime --no-opengl-sw --no-patchqt - --no-translations --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import + --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import --plugindir "$,$/qt6/plugins,$/share/qt6/plugins>" + --translationdir "$,$/qt6/translations,$/share/qt6/translations>" --verbose 0 $ ) diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 8bcd0f5215..8775144360 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -546,7 +546,7 @@ target_sources(rpcs3_emu PRIVATE RSX/rsx_vertex_data.cpp ) -if(NOT ANDROID) +if(NOT ANDROID AND NOT APPLE) target_sources(rpcs3_emu PRIVATE RSX/GL/GLCommonDecompiler.cpp RSX/GL/GLCompute.cpp @@ -667,6 +667,7 @@ target_link_libraries(rpcs3_emu 3rdparty::yaml-cpp 3rdparty::zlib 3rdparty::zstd + 3rdparty::libcurl ) if(APPLE) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index 2de2d2cd3c..3c7b299af7 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -1501,8 +1501,15 @@ void gem_config_data::operator()() vc = vc_attribute; } - if (g_cfg.io.camera != camera_handler::qt) + switch (g_cfg.io.camera) { +#ifdef HAVE_SDL3 + case camera_handler::sdl: +#endif + case camera_handler::qt: + break; + case camera_handler::fake: + case camera_handler::null: video_conversion_in_progress = false; done(); continue; diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.cpp b/rpcs3/Emu/Cell/Modules/cellMusic.cpp index 83937f7d16..c23bf274b0 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusic.cpp @@ -215,7 +215,7 @@ error_code cell_music_select_contents() const std::string vfs_dir_path = vfs::get("/dev_hdd0/music"); const std::string title = get_localized_string(localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_TITLE); - error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, vfs_dir_path, title, + error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, music_selection_context::max_depth, vfs_dir_path, title, [&music](s32 status, utils::media_info info) { sysutil_register_cb([&music, info = std::move(info), status](ppu_thread& ppu) -> s32 diff --git a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp index c938f723b1..b7f21b90ad 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp @@ -134,7 +134,7 @@ error_code cell_music_decode_select_contents() const std::string vfs_dir_path = vfs::get("/dev_hdd0/music"); const std::string title = get_localized_string(localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_TITLE); - error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, vfs_dir_path, title, + error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, music_selection_context::max_depth, vfs_dir_path, title, [&dec](s32 status, utils::media_info info) { sysutil_register_cb([&dec, info = std::move(info), status](ppu_thread& ppu) -> s32 diff --git a/rpcs3/Emu/Cell/Modules/cellPhotoExport.cpp b/rpcs3/Emu/Cell/Modules/cellPhotoExport.cpp index 8a264bc721..473bd435e7 100644 --- a/rpcs3/Emu/Cell/Modules/cellPhotoExport.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPhotoExport.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" +#include "Emu/System.h" #include "Emu/IdManager.h" #include "Emu/VFS.h" #include "cellSysutil.h" @@ -107,30 +108,16 @@ bool check_photo_path(const std::string& file_path) return true; } -std::string get_available_photo_path(const std::string& filename) +std::string get_available_photo_path(std::string_view filename) { - const std::string photo_dir = "/dev_hdd0/photo/"; - std::string dst_path = vfs::get(photo_dir + filename); - - // Do not overwrite existing files. Add a suffix instead. - for (u32 i = 0; fs::exists(dst_path); i++) + std::string_view extension = ".png"; + if (const auto extension_start = filename.find_last_of('.'); + extension_start != umax) { - const std::string suffix = fmt::format("_%d", i); - std::string new_filename = filename; - - if (const usz pos = new_filename.find_last_of('.'); pos != std::string::npos) - { - new_filename.insert(pos, suffix); - } - else - { - new_filename.append(suffix); - } - - dst_path = vfs::get(photo_dir + new_filename); + extension = filename.substr(extension_start); } - return dst_path; + return Emu.GetCallbacks().get_photo_path(fmt::format("%s%s", Emu.GetTitle(), extension)); } diff --git a/rpcs3/Emu/Cell/Modules/cellPhotoImport.cpp b/rpcs3/Emu/Cell/Modules/cellPhotoImport.cpp index d56db1f060..03f6e147ee 100644 --- a/rpcs3/Emu/Cell/Modules/cellPhotoImport.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPhotoImport.cpp @@ -142,7 +142,7 @@ error_code select_photo(std::string dst_dir) const std::string vfs_dir_path = vfs::get("/dev_hdd0/photo"); const std::string title = get_localized_string(localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_TITLE_PHOTO_IMPORT); - error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::photo, vfs_dir_path, title, + error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::photo, umax, vfs_dir_path, title, [&pi_manager, dst_dir](s32 status, utils::media_info info) { sysutil_register_cb([&pi_manager, dst_dir, info, status](ppu_thread& ppu) -> s32 @@ -176,10 +176,29 @@ error_code select_photo(std::string dst_dir) const std::string filename = info.path.substr(info.path.find_last_of(fs::delim) + 1); const std::string title = info.get_metadata("title", filename); - const std::string dst_path = dst_dir + "/" + filename; + std::string dst_path = dst_dir + "/"; std::string sub_type = info.sub_type; - strcpy_trunc(g_filedata->dstFileName, filename); + // Try to find a unique filename (TODO: how does the PS3 copy the files exactly?) + std::string extension; + std::string dst_filename = filename; + if (const auto extension_start = filename.find_last_of('.'); + extension_start != umax) + { + extension = filename.substr(extension_start); + dst_filename = filename.substr(0, extension_start); + } + + std::string suffix = extension; + u32 counter = 0; + while (!Emu.IsStopped() && fs::is_file(dst_path + dst_filename + suffix)) + { + suffix = fmt::format(" %d%s", ++counter, extension); + } + dst_filename += std::move(suffix); + dst_path += dst_filename; + + strcpy_trunc(g_filedata->dstFileName, dst_filename); strcpy_trunc(g_filedata->photo_title, title); strcpy_trunc(g_filedata->game_title, Emu.GetTitle()); strcpy_trunc(g_filedata->game_comment, ""); // TODO diff --git a/rpcs3/Emu/Cell/Modules/cellScreenshot.cpp b/rpcs3/Emu/Cell/Modules/cellScreenshot.cpp index b899155dde..060683bdea 100644 --- a/rpcs3/Emu/Cell/Modules/cellScreenshot.cpp +++ b/rpcs3/Emu/Cell/Modules/cellScreenshot.cpp @@ -33,18 +33,12 @@ std::string screenshot_info::get_overlay_path() const std::string screenshot_info::get_photo_title() const { - std::string photo = photo_title; - if (photo.empty()) - photo = Emu.GetTitle(); - return photo; + return photo_title.empty() ? Emu.GetTitle() : photo_title; } std::string screenshot_info::get_game_title() const { - std::string game = game_title; - if (game.empty()) - game = Emu.GetTitle(); - return game; + return game_title.empty() ? Emu.GetTitle() : game_title; } std::string screenshot_info::get_game_comment() const @@ -52,20 +46,6 @@ std::string screenshot_info::get_game_comment() const return game_comment; } -std::string screenshot_info::get_screenshot_path(const std::string& date_path) const -{ - u32 counter = 0; - std::string path = vfs::get("/dev_hdd0/photo/" + date_path + "/" + get_photo_title()); - std::string suffix = ".png"; - - while (!Emu.IsStopped() && fs::is_file(path + suffix)) - { - suffix = fmt::format("_%d.png", ++counter); - } - - return path + suffix; -} - error_code cellScreenShotSetParameter(vm::cptr param) { diff --git a/rpcs3/Emu/Cell/Modules/cellScreenshot.h b/rpcs3/Emu/Cell/Modules/cellScreenshot.h index 20a8d41cc5..e581400dcd 100644 --- a/rpcs3/Emu/Cell/Modules/cellScreenshot.h +++ b/rpcs3/Emu/Cell/Modules/cellScreenshot.h @@ -44,7 +44,6 @@ struct screenshot_info std::string get_photo_title() const; std::string get_game_title() const; std::string get_game_comment() const; - std::string get_screenshot_path(const std::string& date_path) const; }; struct screenshot_manager : public screenshot_info diff --git a/rpcs3/Emu/Cell/Modules/sceNp.h b/rpcs3/Emu/Cell/Modules/sceNp.h index 6f29a2f8a9..88dd2d816b 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.h +++ b/rpcs3/Emu/Cell/Modules/sceNp.h @@ -1265,7 +1265,7 @@ struct SceNpCommunicationId // OnlineId structure struct SceNpOnlineId { - char data[16 + 1]; // char term; + char data[SCE_NET_NP_ONLINEID_MAX_LENGTH + 1]; // char term; char dummy[3]; }; diff --git a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp index 64ac967d8e..1a9491ae6e 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp @@ -144,9 +144,9 @@ error_code sceNpClansCreateRequest(vm::ptr handle, u64 } auto& clans_manager = g_fxo->get(); - + s32 reqId = 0; - SceNpClansError res = clans_manager.client->create_request(&reqId); + const SceNpClansError res = clans_manager.client->create_request(reqId); if (res != SCE_NP_CLANS_SUCCESS) { return res; @@ -167,8 +167,8 @@ error_code sceNpClansDestroyRequest(SceNpClansRequestHandle handle) } auto& clans_manager = g_fxo->get(); - - SceNpClansError res = clans_manager.client->destroy_request(handle); + + const SceNpClansError res = clans_manager.client->destroy_request(handle); if (res != SCE_NP_CLANS_SUCCESS) { return res; @@ -220,7 +220,7 @@ error_code sceNpClansCreateClan(SceNpClansRequestHandle handle, vm::cptr n std::string tag_str; vm::read_string(tag.addr(), SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH, tag_str); - SceNpClansError res = clans_manager.client->create_clan(nph, handle, name_str, tag_str, clanId); + const SceNpClansError res = clans_manager.client->create_clan(nph, handle, name_str, tag_str, clanId); if (res != SCE_NP_CLANS_SUCCESS) { return res; @@ -246,7 +246,7 @@ error_code sceNpClansDisbandClan(SceNpClansRequestHandle handle, SceNpClanId cla auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpClansError res = clans_manager.client->disband_dlan(nph, handle, clanId); + const SceNpClansError res = clans_manager.client->disband_dlan(nph, handle, clanId); if (res != SCE_NP_CLANS_SUCCESS) { return res; @@ -283,13 +283,13 @@ error_code sceNpClansGetClanList(SceNpClansRequestHandle handle, vm::cptr host_clanList(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX); SceNpClansPagingResult host_pageResult = {}; - SceNpClansError ret = clans_manager.client->get_clan_list(nph, handle, &host_paging, host_clanList, &host_pageResult); + const SceNpClansError ret = clans_manager.client->get_clan_list(nph, handle, host_paging, host_clanList, host_pageResult); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -297,9 +297,9 @@ error_code sceNpClansGetClanList(SceNpClansRequestHandle handle, vm::cptr 0) { - std::memcpy(clanList.get_ptr(), host_clanList, sizeof(SceNpClansEntry) * host_pageResult.count); + std::memcpy(clanList.get_ptr(), host_clanList.data(), sizeof(SceNpClansEntry) * host_pageResult.count); } - std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + *pageResult = host_pageResult; return CELL_OK; } @@ -383,16 +383,15 @@ error_code sceNpClansSearchByName(SceNpClansRequestHandle handle, vm::cptr host_results(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX); SceNpClansPagingResult host_pageResult = {}; - SceNpClansError ret = clans_manager.client->clan_search(handle, &host_paging, &host_search, host_results, &host_pageResult); + const SceNpClansError ret = clans_manager.client->clan_search(handle, host_paging, host_search, host_results, host_pageResult); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -400,9 +399,9 @@ error_code sceNpClansSearchByName(SceNpClansRequestHandle handle, vm::cptr 0) { - std::memcpy(results.get_ptr(), host_results, sizeof(SceNpClansClanBasicInfo) * host_pageResult.count); + std::memcpy(results.get_ptr(), host_results.data(), sizeof(SceNpClansClanBasicInfo) * host_pageResult.count); } - std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + *pageResult = host_pageResult; return CELL_OK; } @@ -425,14 +424,14 @@ error_code sceNpClansGetClanInfo(SceNpClansRequestHandle handle, SceNpClanId cla auto& clans_manager = g_fxo->get(); SceNpClansClanInfo host_info = {}; - - SceNpClansError ret = clans_manager.client->get_clan_info(handle, clanId, &host_info); + + const SceNpClansError ret = clans_manager.client->get_clan_info(handle, clanId, host_info); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; } - - std::memcpy(info.get_ptr(), &host_info, sizeof(SceNpClansClanInfo)); + + *info = host_info; return CELL_OK; } @@ -455,10 +454,9 @@ error_code sceNpClansUpdateClanInfo(SceNpClansRequestHandle handle, SceNpClanId auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpClansUpdatableClanInfo host_info = {}; - std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableClanInfo)); + const SceNpClansUpdatableClanInfo host_info = *info; - SceNpClansError ret = clans_manager.client->update_clan_info(nph, handle, clanId, &host_info); + const SceNpClansError ret = clans_manager.client->update_clan_info(nph, handle, clanId, host_info); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -495,13 +493,13 @@ error_code sceNpClansGetMemberList(SceNpClansRequestHandle handle, SceNpClanId c SceNpClansPagingRequest host_paging = {}; if (paging) { - std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + host_paging = *paging; } - SceNpClansMemberEntry host_memList_addr[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + std::vector host_memList_addr(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX); SceNpClansPagingResult host_pageResult = {}; - SceNpClansError ret = clans_manager.client->get_member_list(nph, handle, clanId, &host_paging, status, host_memList_addr, &host_pageResult); + const SceNpClansError ret = clans_manager.client->get_member_list(nph, handle, clanId, host_paging, status, host_memList_addr, host_pageResult); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -509,9 +507,9 @@ error_code sceNpClansGetMemberList(SceNpClansRequestHandle handle, SceNpClanId c if (memList && host_pageResult.count > 0) { - std::memcpy(memList.get_ptr(), host_memList_addr, sizeof(SceNpClansMemberEntry) * host_pageResult.count); + std::memcpy(memList.get_ptr(), host_memList_addr.data(), sizeof(SceNpClansMemberEntry) * host_pageResult.count); } - std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + *pageResult = host_pageResult; return CELL_OK; } @@ -533,18 +531,17 @@ error_code sceNpClansGetMemberInfo(SceNpClansRequestHandle handle, SceNpClanId c auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpId host_npid = {}; - std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + const SceNpId host_npid = *npid; SceNpClansMemberEntry host_memInfo = {}; - SceNpClansError ret = clans_manager.client->get_member_info(nph, handle, clanId, host_npid, &host_memInfo); + const SceNpClansError ret = clans_manager.client->get_member_info(nph, handle, clanId, host_npid, host_memInfo); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; } - std::memcpy(memInfo.get_ptr(), &host_memInfo, sizeof(SceNpClansMemberEntry)); + *memInfo = host_memInfo; return CELL_OK; } @@ -566,10 +563,9 @@ error_code sceNpClansUpdateMemberInfo(SceNpClansRequestHandle handle, SceNpClanI auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpClansUpdatableMemberInfo host_info = {}; - std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableMemberInfo)); + const SceNpClansUpdatableMemberInfo host_info = *info; - SceNpClansError ret = clans_manager.client->update_member_info(nph, handle, clanId, &host_info); + const SceNpClansError ret = clans_manager.client->update_member_info(nph, handle, clanId, host_info); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -595,10 +591,9 @@ error_code sceNpClansChangeMemberRole(SceNpClansRequestHandle handle, SceNpClanI auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpId host_npid = {}; - std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + const SceNpId host_npid = *npid; - SceNpClansError ret = clans_manager.client->change_member_role(nph, handle, clanId, host_npid, static_cast(role)); + const SceNpClansError ret = clans_manager.client->change_member_role(nph, handle, clanId, host_npid, static_cast(role)); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -650,7 +645,7 @@ error_code sceNpClansJoinClan(SceNpClansRequestHandle handle, SceNpClanId clanId auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpClansError ret = clans_manager.client->join_clan(nph, handle, clanId); + const SceNpClansError ret = clans_manager.client->join_clan(nph, handle, clanId); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -671,7 +666,7 @@ error_code sceNpClansLeaveClan(SceNpClansRequestHandle handle, SceNpClanId clanI auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpClansError ret = clans_manager.client->leave_clan(nph, handle, clanId); + const SceNpClansError ret = clans_manager.client->leave_clan(nph, handle, clanId); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -705,16 +700,15 @@ error_code sceNpClansKickMember(SceNpClansRequestHandle handle, SceNpClanId clan auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpId host_npid = {}; - std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + const SceNpId host_npid = *npid; SceNpClansMessage host_message = {}; if (message) { - std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + host_message = *message; } - SceNpClansError ret = clans_manager.client->kick_member(nph, handle, clanId, host_npid, &host_message); + const SceNpClansError ret = clans_manager.client->kick_member(nph, handle, clanId, host_npid, host_message); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -748,16 +742,15 @@ error_code sceNpClansSendInvitation(SceNpClansRequestHandle handle, SceNpClanId auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpId host_npid = {}; - std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + const SceNpId host_npid = *npid; SceNpClansMessage host_message = {}; if (message) { - std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + host_message = *message; } - SceNpClansError ret = clans_manager.client->send_invitation(nph, handle, clanId, host_npid, &host_message); + const SceNpClansError ret = clans_manager.client->send_invitation(nph, handle, clanId, host_npid, host_message); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -783,10 +776,9 @@ error_code sceNpClansCancelInvitation(SceNpClansRequestHandle handle, SceNpClanI auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpId host_npid = {}; - std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + const SceNpId host_npid = *npid; - SceNpClansError ret = clans_manager.client->cancel_invitation(nph, handle, clanId, host_npid); + const SceNpClansError ret = clans_manager.client->cancel_invitation(nph, handle, clanId, host_npid); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -818,15 +810,10 @@ error_code sceNpClansSendInvitationResponse(SceNpClansRequestHandle handle, SceN SceNpClansMessage host_message = {}; if (message) { - std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + host_message = *message; } - if (message) - { - std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); - } - - SceNpClansError ret = clans_manager.client->send_invitation_response(nph, handle, clanId, &host_message, accept); + const SceNpClansError ret = clans_manager.client->send_invitation_response(nph, handle, clanId, host_message, accept); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -858,10 +845,10 @@ error_code sceNpClansSendMembershipRequest(SceNpClansRequestHandle handle, u32 c SceNpClansMessage host_message = {}; if (message) { - std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + host_message = *message; } - SceNpClansError ret = clans_manager.client->request_membership(nph, handle, clanId, &host_message); + const SceNpClansError ret = clans_manager.client->request_membership(nph, handle, clanId, host_message); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -882,8 +869,7 @@ error_code sceNpClansCancelMembershipRequest(SceNpClansRequestHandle handle, Sce auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpClansError ret = clans_manager.client->cancel_request_membership(nph, handle, clanId); - + const SceNpClansError ret = clans_manager.client->cancel_request_membership(nph, handle, clanId); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -917,16 +903,15 @@ error_code sceNpClansSendMembershipResponse(SceNpClansRequestHandle handle, SceN auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpId host_npid = {}; - std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + const SceNpId host_npid = *npid; SceNpClansMessage host_message = {}; if (message) { - std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + host_message = *message; } - SceNpClansError ret = clans_manager.client->send_membership_response(nph, handle, clanId, host_npid, &host_message, allow); + const SceNpClansError ret = clans_manager.client->send_membership_response(nph, handle, clanId, host_npid, host_message, allow); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -963,13 +948,13 @@ error_code sceNpClansGetBlacklist(SceNpClansRequestHandle handle, SceNpClanId cl SceNpClansPagingRequest host_paging = {}; if (paging) { - std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + host_paging = *paging; } - SceNpClansBlacklistEntry host_blacklist[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + std::vector host_blacklist(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX); SceNpClansPagingResult host_pageResult = {}; - SceNpClansError ret = clans_manager.client->get_blacklist(nph, handle, clanId, &host_paging, host_blacklist, &host_pageResult); + const SceNpClansError ret = clans_manager.client->get_blacklist(nph, handle, clanId, host_paging, host_blacklist, host_pageResult); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -977,9 +962,9 @@ error_code sceNpClansGetBlacklist(SceNpClansRequestHandle handle, SceNpClanId cl if (bl && host_pageResult.count > 0) { - std::memcpy(bl.get_ptr(), host_blacklist, sizeof(SceNpClansBlacklistEntry) * host_pageResult.count); + std::memcpy(bl.get_ptr(), host_blacklist.data(), sizeof(SceNpClansBlacklistEntry) * host_pageResult.count); } - std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + *pageResult = host_pageResult; return CELL_OK; } @@ -1001,10 +986,9 @@ error_code sceNpClansAddBlacklistEntry(SceNpClansRequestHandle handle, SceNpClan auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpId host_member = {}; - std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId)); + const SceNpId host_member = *member; - SceNpClansError ret = clans_manager.client->add_blacklist_entry(nph, handle, clanId, host_member); + const SceNpClansError ret = clans_manager.client->add_blacklist_entry(nph, handle, clanId, host_member); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -1030,10 +1014,9 @@ error_code sceNpClansRemoveBlacklistEntry(SceNpClansRequestHandle handle, SceNpC auto& nph = g_fxo->get>(); auto& clans_manager = g_fxo->get(); - SceNpId host_member = {}; - std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId)); + const SceNpId host_member = *member; - SceNpClansError ret = clans_manager.client->remove_blacklist_entry(nph, handle, clanId, host_member); + const SceNpClansError ret = clans_manager.client->remove_blacklist_entry(nph, handle, clanId, host_member); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -1070,13 +1053,13 @@ error_code sceNpClansRetrieveAnnouncements(SceNpClansRequestHandle handle, SceNp SceNpClansPagingRequest host_paging = {}; if (paging) { - std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + host_paging = *paging; } - SceNpClansMessageEntry host_announcements[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + std::vector host_announcements(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX); SceNpClansPagingResult host_pageResult = {}; - SceNpClansError ret = clans_manager.client->retrieve_announcements(nph, handle, clanId, &host_paging, host_announcements, &host_pageResult); + const SceNpClansError ret = clans_manager.client->retrieve_announcements(nph, handle, clanId, host_paging, host_announcements, host_pageResult); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -1084,9 +1067,9 @@ error_code sceNpClansRetrieveAnnouncements(SceNpClansRequestHandle handle, SceNp if (mlist && host_pageResult.count > 0) { - std::memcpy(mlist.get_ptr(), host_announcements, sizeof(SceNpClansMessageEntry) * host_pageResult.count); + std::memcpy(mlist.get_ptr(), host_announcements.data(), sizeof(SceNpClansMessageEntry) * host_pageResult.count); } - std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + *pageResult = host_pageResult; return CELL_OK; } @@ -1113,17 +1096,16 @@ error_code sceNpClansPostAnnouncement(SceNpClansRequestHandle handle, SceNpClanI auto& clans_manager = g_fxo->get(); auto& nph = g_fxo->get>(); - SceNpClansMessage host_announcement = {}; - std::memcpy(&host_announcement, message.get_ptr(), sizeof(SceNpClansMessage)); + const SceNpClansMessage host_announcement = *message; SceNpClansMessageData host_data = {}; if (data) { - std::memcpy(&host_data, data.get_ptr(), sizeof(SceNpClansMessageData)); + host_data = *data; } SceNpClansMessageId host_announcementId = 0; - SceNpClansError ret = clans_manager.client->post_announcement(nph, handle, clanId, &host_announcement, &host_data, duration, &host_announcementId); + const SceNpClansError ret = clans_manager.client->post_announcement(nph, handle, clanId, host_announcement, host_data, duration, host_announcementId); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; @@ -1146,7 +1128,7 @@ error_code sceNpClansRemoveAnnouncement(SceNpClansRequestHandle handle, SceNpCla auto& clans_manager = g_fxo->get(); auto& nph = g_fxo->get>(); - SceNpClansError ret = clans_manager.client->delete_announcement(nph, handle, clanId, mId); + const SceNpClansError ret = clans_manager.client->delete_announcement(nph, handle, clanId, mId); if (ret != SCE_NP_CLANS_SUCCESS) { return ret; diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index 78183293e7..463ec7a68d 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -246,7 +246,7 @@ private: {0x054C, 0x01C8, 0x01C8, "PSP Type A", nullptr, nullptr}, {0x054C, 0x01C9, 0x01C9, "PSP Type B", nullptr, nullptr}, {0x054C, 0x01CA, 0x01CA, "PSP Type C", nullptr, nullptr}, - {0x054C, 0x01CB, 0x01CB, "PSP Type D", nullptr, nullptr}, + {0x054C, 0x01CB, 0x01CB, "PSP Type D", nullptr, nullptr}, // UsbPspCm {0x054C, 0x02D2, 0x02D2, "PSP Slim", nullptr, nullptr}, // 0x0900: "H050 USJ(C) PCB rev00", 0x0910: "USIO PCB rev00" @@ -261,9 +261,6 @@ private: // Tony Hawk RIDE Skateboard {0x12BA, 0x0400, 0x0400, "Tony Hawk RIDE Skateboard Controller", nullptr, nullptr}, - // PSP in UsbPspCm mode - {0x054C, 0x01CB, 0x01CB, "UsbPspcm", nullptr, nullptr}, - // Sony Stereo Headsets {0x12BA, 0x0032, 0x0032, "Wireless Stereo Headset", nullptr, nullptr}, {0x12BA, 0x0042, 0x0042, "Wireless Stereo Headset", nullptr, nullptr}, @@ -636,6 +633,8 @@ void usb_handler_thread::operator()() // Process asynchronous requests that are pending libusb_handle_events_timeout_completed(ctx, &lusb_tv, nullptr); + u64 delay = 1'000; + // Process fake transfers if (!fake_transfers.empty()) { @@ -650,6 +649,13 @@ void usb_handler_thread::operator()() if (transfer->expected_time > timestamp) { + const u64 diff_time = transfer->expected_time - timestamp; + + if (diff_time < delay) + { + delay = diff_time; + } + ++it; continue; } @@ -668,7 +674,7 @@ void usb_handler_thread::operator()() if (handled_devices.empty()) thread_ctrl::wait_for(500'000); else - thread_ctrl::wait_for(1'000); + thread_ctrl::wait_for(delay); } } @@ -878,7 +884,9 @@ std::pair usb_handler_thread::get_free_transfer() u32 transfer_id = get_free_transfer_id(); auto& transfer = get_transfer(transfer_id); - transfer.busy = true; + + libusb_transfer* const transfer_buf = transfer.transfer; + transfer = {.transfer_id = transfer_id, .transfer = transfer_buf, .busy = true}; return {transfer_id, transfer}; } @@ -1046,6 +1054,27 @@ void connect_usb_controller(u8 index, input::product_type type) } } +void reconnect_usb(u32 assigned_number) +{ + auto usbh = g_fxo->try_get>(); + if (!usbh) + { + return; + } + + std::lock_guard lock(usbh->mutex); + for (auto& [nr, pair] : usbh->handled_devices) + { + auto& [internal_dev, dev] = pair; + if (nr == assigned_number) + { + usbh->disconnect_usb_device(dev, false); + usbh->connect_usb_device(dev, false); + break; + } + } +} + void handle_hotplug_event(bool connected) { if (auto usbh = g_fxo->try_get>()) diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.h b/rpcs3/Emu/Cell/lv2/sys_usbd.h index a2fd911e35..76f5f0b061 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.h +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.h @@ -89,4 +89,5 @@ error_code sys_usbd_register_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr s_product, u16 slen_product); void connect_usb_controller(u8 index, input::product_type); +void reconnect_usb(u32 assigned_number); void handle_hotplug_event(bool connected); diff --git a/rpcs3/Emu/Io/Dimensions.cpp b/rpcs3/Emu/Io/Dimensions.cpp index e80a64c0c9..84a604a86e 100644 --- a/rpcs3/Emu/Io/Dimensions.cpp +++ b/rpcs3/Emu/Io/Dimensions.cpp @@ -700,8 +700,3 @@ void usb_device_dimensions::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoi break; } } - -void usb_device_dimensions::isochronous_transfer(UsbTransfer* transfer) -{ - usb_device_emulated::isochronous_transfer(transfer); -} diff --git a/rpcs3/Emu/Io/Dimensions.h b/rpcs3/Emu/Io/Dimensions.h index e2bfbd1e7f..d25fb8ed2e 100644 --- a/rpcs3/Emu/Io/Dimensions.h +++ b/rpcs3/Emu/Io/Dimensions.h @@ -76,7 +76,6 @@ public: void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override; - void isochronous_transfer(UsbTransfer* transfer) override; protected: std::queue> m_queries; diff --git a/rpcs3/Emu/Io/KamenRider.cpp b/rpcs3/Emu/Io/KamenRider.cpp index aaa4836f08..df7b86a07f 100644 --- a/rpcs3/Emu/Io/KamenRider.cpp +++ b/rpcs3/Emu/Io/KamenRider.cpp @@ -39,7 +39,7 @@ kamen_rider_figure& rider_gate::get_figure_by_uid(const std::array uid) return figures[7]; } -void rider_gate::get_blank_response(u8 command, u8 sequence, std::array& reply_buf) +void rider_gate::get_blank_response(std::array& reply_buf, u8 command, u8 sequence) { reply_buf = {0x55, 0x02, command, sequence}; reply_buf[4] = generate_checksum(reply_buf, 4); @@ -93,7 +93,7 @@ void rider_gate::query_block(std::array& reply_buf, u8 command, u8 seque reply_buf[21] = generate_checksum(reply_buf, 21); } -void rider_gate::write_block(std::array& replyBuf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf) +void rider_gate::write_block(std::array& reply_buf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf) { std::lock_guard lock(kamen_mutex); @@ -108,7 +108,7 @@ void rider_gate::write_block(std::array& replyBuf, u8 command, u8 sequen } } - get_blank_response(command, sequence, replyBuf); + get_blank_response(reply_buf, command, sequence); } std::optional> rider_gate::pop_added_removed_response() @@ -190,11 +190,50 @@ u8 rider_gate::load_figure(const std::array& buf, fs::file in_f usb_device_kamen_rider::usb_device_kamen_rider(const std::array& location) : usb_device_emulated(location) { - device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x200, 0x0, 0x0, 0x0, 0x40, 0x0E6F, 0x200A, 0x100, 0x1, 0x2, 0x3, 0x1}); - auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{0x29, 0x1, 0x1, 0x0, 0x80, 0xFA})); - config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0})); - config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x81, 0x3, 0x40, 0x1})); - config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x1, 0x3, 0x40, 0x1})); + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{ + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x40, + .idVendor = 0x0E6F, + .idProduct = 0x200A, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01}); + auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{ + .wTotalLength = 0x0029, + .bNumInterfaces = 0x01, + .bConfigurationValue = 0x01, + .iConfiguration = 0x00, + .bmAttributes = 0x80, + .bMaxPower = 0xFA})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{ + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x02, + .bInterfaceClass = 0x03, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_HID, UsbDeviceHID{ + .bcdHID = 0x0100, + .bCountryCode = 0x00, + .bNumDescriptors = 0x01, + .bDescriptorType = 0x22, + .wDescriptorLength = 0x001d})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{ + .bEndpointAddress = 0x81, + .bmAttributes = 0x03, + .wMaxPacketSize = 0x0040, + .bInterval = 0x1})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{ + .bEndpointAddress = 0x01, + .bmAttributes = 0x03, + .wMaxPacketSize = 0x0040, + .bInterval = 0x1})); } usb_device_kamen_rider::~usb_device_kamen_rider() @@ -227,7 +266,7 @@ void usb_device_kamen_rider::interrupt_transfer(u32 buf_size, u8* buf, u32 endpo if (endpoint == 0x81) { // Respond after FF command - transfer->expected_time = get_timestamp() + 1000; + transfer->expected_time = get_timestamp() + 22000; std::optional> response = g_ridergate.pop_added_removed_response(); if (response) { @@ -246,6 +285,7 @@ void usb_device_kamen_rider::interrupt_transfer(u32 buf_size, u8* buf, u32 endpo } else if (endpoint == 0x01) { + transfer->expected_time = get_timestamp() + 10; const u8 command = buf[2]; const u8 sequence = buf[3]; @@ -261,7 +301,7 @@ void usb_device_kamen_rider::interrupt_transfer(u32 buf_size, u8* buf, u32 endpo case 0xC0: case 0xC3: // Color Commands { - g_ridergate.get_blank_response(command, sequence, q_result); + g_ridergate.get_blank_response(q_result, command, sequence); break; } case 0xD0: // Tag List diff --git a/rpcs3/Emu/Io/KamenRider.h b/rpcs3/Emu/Io/KamenRider.h index 0e30024b06..6c4bea29ee 100644 --- a/rpcs3/Emu/Io/KamenRider.h +++ b/rpcs3/Emu/Io/KamenRider.h @@ -18,11 +18,11 @@ struct kamen_rider_figure class rider_gate { public: - void get_blank_response(u8 command, u8 sequence, std::array& reply_buf); - void wake_rider_gate(std::array& replyBuf, u8 command, u8 sequence); - void get_list_tags(std::array& replyBuf, u8 command, u8 sequence); - void query_block(std::array& replyBuf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block); - void write_block(std::array& replyBuf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf); + void get_blank_response(std::array& reply_buf, u8 command, u8 sequence); + void wake_rider_gate(std::array& reply_buf, u8 command, u8 sequence); + void get_list_tags(std::array& reply_buf, u8 command, u8 sequence); + void query_block(std::array& reply_buf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block); + void write_block(std::array& reply_buf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf); std::optional> pop_added_removed_response(); bool remove_figure(u8 position); diff --git a/rpcs3/Emu/Io/LogitechG27.cpp b/rpcs3/Emu/Io/LogitechG27.cpp index 5503c606fc..c8aa453592 100644 --- a/rpcs3/Emu/Io/LogitechG27.cpp +++ b/rpcs3/Emu/Io/LogitechG27.cpp @@ -17,7 +17,254 @@ #include "Input/pad_thread.h" #include "Input/sdl_instance.h" -LOG_CHANNEL(logitech_g27_log, "LOGIG27"); +LOG_CHANNEL(logitech_g27_log, "logitech_g27"); + +#pragma pack(push, 1) +struct DFEX_data +{ + u8 square : 1; + u8 cross : 1; + u8 circle : 1; + u8 triangle : 1; + u8 l1 : 1; // Left_paddle + u8 r1 : 1; // Right_paddle + u8 l2 : 1; + u8 r2 : 1; + + u8 select : 1; // Share + u8 start : 1; // Options + u8 l3 : 1; + u8 r3 : 1; + u8 ps : 1; + u8 : 3; + + u8 dpad; // 00=N, 01=NE, 02=E, 03=SW, 04=S, 05=SW, 06=W, 07=NW, 08=IDLE + u8 steering; // 00=Left, ff=Right + u8 brake_throttle; // 00=ThrottlePressed, 7f=BothReleased/BothPressed, ff=BrakePressed + u8 const1; + u8 const2; + u32 : 32; + u32 : 32; + u16 : 16; + u8 brake; // 00=Released, ff=Pressed + u8 throttle; // 00=Released, ff=Pressed + u32 : 32; + u32 : 32; +}; + +struct DFP_data +{ + u16 steering : 14; // 0000=Left, 1fff=Mid, 3fff=Right + u16 cross: 1; + u16 square : 1; + + u8 circle : 1; + u8 triangle : 1; + u8 r1 : 1; // Right_paddle + u8 l1 : 1; // Left_paddle + u8 r2 : 1; + u8 l2 : 1; + u8 select : 1; // Share + u8 start : 1; // Options + + u8 r3 : 1; + u8 l3 : 1; + u8 r3_2 : 1; + u8 l3_2 : 1; + u8 dpad : 4; // 0=N, 1=NE, 2=E, 3=SW, 4=S, 5=SW, 6=W, 7=NW, 8=IDLE + + u8 brake_throttle; // 00=ThrottlePressed, 7f=BothReleased/BothPressed, ff=BrakePressed + u8 throttle; // 00=Pressed, ff=Released + u8 brake; // 00=Pressed, ff=Released + + u8 pedals_attached : 1; + u8 powered : 1; + u8 : 1; + u8 self_check_done : 1; + u8 set1 : 1; // always set + u8 : 3; +}; + +struct DFGT_data +{ + u8 dpad : 4; // 0=N, 1=NE, 2=E, 3=SW, 4=S, 5=SW, 6=W, 7=NW, 8=IDLE + u8 cross: 1; + u8 square : 1; + u8 circle : 1; + u8 triangle : 1; + + u8 r1 : 1; // Right_paddle + u8 l1 : 1; // Left_paddle + u8 r2 : 1; + u8 l2 : 1; + u8 select : 1; // Share + u8 start : 1; // Options + u8 r3 : 1; + u8 l3 : 1; + + u8 : 2; + u8 dial_center : 1; + u8 plus : 1; + u8 dial_cw : 1; + u8 dial_ccw : 1; + u8 minus : 1; + u8 : 1; + + u8 ps : 1; + u8 pedals_attached : 1; + u8 powered : 1; + u8 self_check_done : 1; + u8 set1 : 1; + u8 : 1; + u8 set2 : 1; + u8 : 1; + + u16 steering : 14; // 0000=Left, 1fff=Mid, 3fff=Right + u16 : 2; + u8 throttle; // 00=Pressed, ff=Released + u8 brake; // 00=Pressed, ff=Released +}; + +struct G25_data +{ + u8 dpad : 4; // 0=N, 1=NE, 2=E, 3=SW, 4=S, 5=SW, 6=W, 7=NW, 8=IDLE + u8 cross: 1; + u8 square : 1; + u8 circle : 1; + u8 triangle : 1; + + u8 r1 : 1; // Right_paddle + u8 l1 : 1; // Left_paddle + u8 r2 : 1; // + dial_center + u8 l2 : 1; + u8 select : 1; // Share + u8 start : 1; // Options + u8 r3 : 1; // + dial_cw + plus + u8 l3 : 1; // + dial_ccw + minus + + u8 gear1 : 1; + u8 gear2 : 1; + u8 gear3 : 1; + u8 gear4 : 1; + u8 gear5 : 1; + u8 gear6 : 1; + u8 gearR : 1; + u8 : 1; + + u16 pedals_detached : 1; + u16 powered : 1; + u16 steering : 14; // 0000=Left, 1fff=Mid, 3fff=Right + + u8 throttle; // 00=Pressed, ff=Released + u8 brake; // 00=Pressed, ff=Released + u8 clutch; // 00=Pressed, ff=Released + + u8 shifter_x; // 30=left(1,2), 7a=middle(3,4), b2=right(5,6) + u8 shifter_y; // 32=bottom(2,4,6), b7=top(1,3,5) + + u8 shifter_attached : 1; + u8 set1 : 1; + u8 : 1; + u8 shifter_pressed : 1; + u8 : 4; +}; + +struct G27_data +{ + u8 dpad : 4; // 0=N, 1=NE, 2=E, 3=SW, 4=S, 5=SW, 6=W, 7=NW, 8=IDLE + u8 cross: 1; + u8 square : 1; + u8 circle : 1; + u8 triangle : 1; + + u8 r1 : 1; // Right_paddle + u8 l1 : 1; // Left_paddle + u8 r2 : 1; + u8 l2 : 1; + u8 select : 1; // Share + u8 start : 1; // Options + u8 r3 : 1; // + dial_center + u8 l3 : 1; + + u8 gear1 : 1; + u8 gear2 : 1; + u8 gear3 : 1; + u8 gear4 : 1; + u8 gear5 : 1; + u8 gear6 : 1; + u8 dial_cw : 1; + u8 dial_ccw : 1; + + u16 plus : 1; + u16 minus : 1; + u16 steering : 14; // 0000=Left, 1fff=Mid, 3fff=Right + + u8 throttle; // 00=Pressed, ff=Released + u8 brake; // 00=Pressed, ff=Released + u8 clutch; // 00=Pressed, ff=Released + + u8 shifter_x; // 30=left(1,2), 7a=middle(3,4), b2=right(5,6) + u8 shifter_y; // 32=bottom(2,4,6), b7=top(1,3,5) + + u8 gearR : 1; + u8 pedals_detached : 1; + u8 powered : 1; + u8 shifter_attached : 1; + u8 set1 : 1; + u8 : 1; + u8 shifter_pressed : 1; + u8 range : 1; +}; +#pragma pack(pop) + +static const std::map>> s_logitech_personality = { +{ + logitech_personality::driving_force_ex, + { + UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC294, 0x1350, 0x01, 0x02, 0x00, 0x01}, + {0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x04, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, + 0x00, 0x00, 0x09, 0x21, 0x00, 0x01, 0x21, 0x01, 0x22, 0x9D, 0x00, 0x07, 0x05, 0x81, 0x03, 0x40, + 0x00, 0x0A, 0x07, 0x05, 0x01, 0x03, 0x10, 0x00, 0x0A} + } +}, +{ + logitech_personality::driving_force_pro, + { + UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC298, 0x1350, 0x01, 0x02, 0x00, 0x01}, + {0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x04, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, + 0x00, 0x00, 0x09, 0x21, 0x00, 0x01, 0x21, 0x01, 0x22, 0x61, 0x00, 0x07, 0x05, 0x81, 0x03, 0x08, + 0x00, 0x0A, 0x07, 0x05, 0x01, 0x03, 0x08, 0x00, 0x0A} + } +}, +{ + logitech_personality::g25, + { + UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC299, 0x1350, 0x01, 0x02, 0x00, 0x01}, + {0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x04, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, + 0x00, 0x00, 0x09, 0x21, 0x11, 0x01, 0x21, 0x01, 0x22, 0x6F, 0x00, 0x07, 0x05, 0x81, 0x03, 0x10, + 0x00, 0x02, 0x07, 0x05, 0x01, 0x03, 0x10, 0x00, 0x02} + } +}, +{ + logitech_personality::driving_force_gt, + { + UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC29A, 0x1350, 0x00, 0x02, 0x00, 0x01}, + {0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x00, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, + 0x00, 0xFE, 0x09, 0x21, 0x11, 0x01, 0x21, 0x01, 0x22, 0x73, 0x00, 0x07, 0x05, 0x81, 0x03, 0x10, + 0x00, 0x02, 0x07, 0x05, 0x01, 0x03, 0x10, 0x00, 0x02} + } +}, +{ + logitech_personality::g27, + { + UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC29B, 0x1350, 0x01, 0x02, 0x00, 0x01}, + {0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x04, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, + 0x00, 0x00, 0x09, 0x21, 0x11, 0x01, 0x21, 0x01, 0x22, 0x85, 0x00, 0x07, 0x05, 0x81, 0x03, 0x10, + 0x00, 0x02, 0x07, 0x05, 0x01, 0x03, 0x10, 0x00, 0x02} + } +} +}; // ref: https://github.com/libsdl-org/SDL/issues/7941, need to use SDL_HAPTIC_STEERING_AXIS for some windows drivers static const SDL_HapticDirection STEERING_DIRECTION = @@ -26,20 +273,31 @@ static const SDL_HapticDirection STEERING_DIRECTION = .dir = {0, 0, 0} }; -usb_device_logitech_g27::usb_device_logitech_g27(u32 controller_index, const std::array& location) - : usb_device_emulated(location), m_controller_index(controller_index) +void usb_device_logitech_g27::set_personality(logitech_personality personality, bool reconnect) { - device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x0200, 0, 0, 0, 16, 0x046d, 0xc29b, 0x1350, 1, 2, 0, 1}); + m_personality = personality; + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, ::at32(s_logitech_personality, personality).first); // parse the raw response like with passthrough device - static constexpr u8 raw_config[] = {0x9, 0x2, 0x29, 0x0, 0x1, 0x1, 0x4, 0x80, 0x31, 0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0, 0x9, 0x21, 0x11, 0x1, 0x21, 0x1, 0x22, 0x85, 0x0, 0x7, 0x5, 0x81, 0x3, 0x10, 0x0, 0x2, 0x7, 0x5, 0x1, 0x3, 0x10, 0x0, 0x2}; + const u8* raw_config = ::at32(s_logitech_personality, personality).second.data(); auto& conf = device.add_node(UsbDescriptorNode(raw_config[0], raw_config[1], &raw_config[2])); - for (unsigned int index = raw_config[0]; index < sizeof(raw_config);) + for (unsigned int index = raw_config[0]; index < raw_config[2];) { conf.add_node(UsbDescriptorNode(raw_config[index], raw_config[index + 1], &raw_config[index + 2])); index += raw_config[index]; } + if (reconnect) + { + reconnect_usb(assigned_number); + } +} + +usb_device_logitech_g27::usb_device_logitech_g27(u32 controller_index, const std::array& location) + : usb_device_emulated(location), m_controller_index(controller_index) +{ + set_personality(logitech_personality::driving_force_ex); + m_default_spring_effect.type = SDL_HAPTIC_SPRING; m_default_spring_effect.condition.direction = STEERING_DIRECTION; m_default_spring_effect.condition.length = SDL_HAPTIC_INFINITY; @@ -63,7 +321,14 @@ usb_device_logitech_g27::usb_device_logitech_g27(u32 controller_index, const std while (thread_ctrl::state() != thread_state::aborting) { sdl_refresh(); - thread_ctrl::wait_for(5'000'000); + thread_ctrl::wait_for(1'000'000); + + std::unique_lock lock(g_cfg_logitech_g27.m_mutex); + if (logitech_personality::invalid != m_next_personality && m_personality != m_next_personality) + { + set_personality(m_next_personality, true); + m_next_personality = logitech_personality::invalid; + } } }); } @@ -115,7 +380,7 @@ u16 usb_device_logitech_g27::get_num_emu_devices() void usb_device_logitech_g27::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) { - logitech_g27_log.todo("control transfer bmRequestType %02x, bRequest %02x, wValue %04x, wIndex %04x, wLength %04x, %s", bmRequestType, bRequest, wValue, wIndex, wLength, fmt::buf_to_hexstring(buf, buf_size)); + logitech_g27_log.notice("control transfer bmRequestType %02x, bRequest %02x, wValue %04x, wIndex %04x, wLength %04x, %s", bmRequestType, bRequest, wValue, wIndex, wLength, fmt::buf_to_hexstring(buf, buf_size)); usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer); } @@ -194,9 +459,11 @@ static inline logitech_g27_sdl_mapping get_runtime_mapping() convert_mapping(cfg.dial_clockwise, mapping.dial_clockwise); convert_mapping(cfg.dial_anticlockwise, mapping.dial_anticlockwise); + convert_mapping(cfg.dial_center, mapping.dial_center); convert_mapping(cfg.select, mapping.select); - convert_mapping(cfg.pause, mapping.pause); + convert_mapping(cfg.start, mapping.start); + convert_mapping(cfg.ps, mapping.ps); convert_mapping(cfg.shifter_1, mapping.shifter_1); convert_mapping(cfg.shifter_2, mapping.shifter_2); @@ -205,7 +472,6 @@ static inline logitech_g27_sdl_mapping get_runtime_mapping() convert_mapping(cfg.shifter_5, mapping.shifter_5); convert_mapping(cfg.shifter_6, mapping.shifter_6); convert_mapping(cfg.shifter_r, mapping.shifter_r); - convert_mapping(cfg.shifter_press, mapping.shifter_press); return mapping; } @@ -467,6 +733,50 @@ static u8 hat_components_to_logitech_g27_hat(bool up, bool down, bool left, bool return sdl_hat_to_logitech_g27_hat(sdl_hat); } +static std::pair shifter_to_coord_xy(bool shifter_1, bool shifter_2, bool shifter_3, bool shifter_4, + bool shifter_5, bool shifter_6, bool shifter_r) +{ + // rough analog values recorded in https://github.com/RPCS3/rpcs3/pull/17199#issuecomment-2883934412 + constexpr u8 coord_center = 0x80; + constexpr u8 coord_top = 0xb7; + constexpr u8 coord_bottom = 0x32; + constexpr u8 coord_left = 0x30; + constexpr u8 coord_right = 0xb3; + constexpr u8 coord_right_reverse = 0xaa; + if (shifter_1) + { + return {coord_left, coord_top}; + } + else if (shifter_2) + { + return {coord_left, coord_bottom}; + } + else if (shifter_3) + { + return {coord_center, coord_top}; + } + else if (shifter_4) + { + return {coord_center, coord_bottom}; + } + else if (shifter_5) + { + return {coord_right, coord_top}; + } + else if (shifter_6) + { + return {coord_right, coord_bottom}; + } + else if (shifter_r) + { + return {coord_right_reverse, coord_bottom}; + } + else + { + return {coord_center, coord_center}; + } +} + static bool fetch_sdl_as_button(SDL_Joystick* joystick, const sdl_mapping& mapping) { switch (mapping.type) @@ -646,6 +956,220 @@ static inline void set_bit(u8* buf, int bit_num, bool set) buf[byte_num] = buf[byte_num] & (~mask); } +void usb_device_logitech_g27::transfer_dfex(u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + DFEX_data data{}; + ensure(buf_size >= sizeof(data)); + transfer->expected_count = sizeof(data); + + const std::lock_guard lock(m_sdl_handles_mutex); + data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square); + data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross); + data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle); + data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle); + data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down); + data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up); + data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2); + data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2); + data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select); + data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start); + data.l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3); + data.r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3); + data.dpad = hat_components_to_logitech_g27_hat( + sdl_to_logitech_g27_button(m_joysticks, m_mapping.up), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.down), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.left), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.right) + ); + data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering) >> 6; + data.brake_throttle = 0x7f; + data.const1 = 0x7f; + data.const2 = 0x7f; + data.brake = 0xff - sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake); + data.throttle = 0xff - sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle); + std::memcpy(buf, &data, sizeof(data)); +} + +void usb_device_logitech_g27::transfer_dfp(u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + DFP_data data{}; + ensure(buf_size >= sizeof(data)); + transfer->expected_count = sizeof(data); + + const std::lock_guard lock(m_sdl_handles_mutex); + data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering); + data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross); + data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square); + data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle); + data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle); + data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up); + data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down); + data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2); + data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2); + data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select); + data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start); + data.r3 = data.r3_2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3); + data.l3 = data.l3_2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3); + data.dpad = hat_components_to_logitech_g27_hat( + sdl_to_logitech_g27_button(m_joysticks, m_mapping.up), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.down), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.left), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.right) + ); + data.brake_throttle = 0x7f; + data.throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle); + data.brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake); + data.pedals_attached = 1; + data.powered = 1; + data.self_check_done = 1; + data.set1 = 1; + std::memcpy(buf, &data, sizeof(data)); +} + +void usb_device_logitech_g27::transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + DFGT_data data{}; + ensure(buf_size >= sizeof(data)); + transfer->expected_count = sizeof(data); + + const std::lock_guard lock(m_sdl_handles_mutex); + data.dpad = hat_components_to_logitech_g27_hat( + sdl_to_logitech_g27_button(m_joysticks, m_mapping.up), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.down), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.left), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.right) + ); + data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross); + data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square); + data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle); + data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle); + data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up); + data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down); + data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2); + data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2); + data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select); + data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start); + data.r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3); + data.l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3); + data.dial_center = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_center); + data.plus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.plus); + data.dial_cw = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_clockwise); + data.dial_ccw = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_anticlockwise); + data.minus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.minus); + data.ps = sdl_to_logitech_g27_button(m_joysticks, m_mapping.ps); + data.pedals_attached = 1; + data.powered = 1; + data.self_check_done = 1; + data.set1 = 1; + data.set2 = 1; + data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering); + data.throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle); + data.brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake); + std::memcpy(buf, &data, sizeof(data)); +} + +void usb_device_logitech_g27::transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + G25_data data{}; + ensure(buf_size >= sizeof(data)); + transfer->expected_count = sizeof(data); + + const std::lock_guard lock(m_sdl_handles_mutex); + data.dpad = hat_components_to_logitech_g27_hat( + sdl_to_logitech_g27_button(m_joysticks, m_mapping.up), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.down), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.left), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.right) + ); + data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross); + data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square); + data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle); + data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle); + data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up); + data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down); + data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2); + data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2); + data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select); + data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start); + data.r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3); + data.l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3); + data.gear1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_1); + data.gear2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_2); + data.gear3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_3); + data.gear4 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_4); + data.gear5 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_5); + data.gear6 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_6); + data.gearR = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_r); + data.pedals_detached = 0; + data.powered = 1; + data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering); + data.throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle); + data.brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake); + data.clutch = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.clutch); + auto [shifter_x, shifter_y] = shifter_to_coord_xy(data.gear1, data.gear2, + data.gear3, data.gear4, data.gear5, data.gear6, data.gearR); + data.shifter_x = shifter_x; + data.shifter_y = shifter_y; + data.shifter_attached = 1; + data.set1 = 1; + data.shifter_pressed = data.gearR; + std::memcpy(buf, &data, sizeof(data)); +} + +void usb_device_logitech_g27::transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + G27_data data{}; + ensure(buf_size >= sizeof(data)); + transfer->expected_count = sizeof(data); + + const std::lock_guard lock(m_sdl_handles_mutex); + data.dpad = hat_components_to_logitech_g27_hat( + sdl_to_logitech_g27_button(m_joysticks, m_mapping.up), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.down), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.left), + sdl_to_logitech_g27_button(m_joysticks, m_mapping.right) + ); + data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross); + data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square); + data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle); + data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle); + data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up); + data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down); + data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2); + data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2); + data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select); + data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start); + data.r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3); + data.l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3); + data.gear1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_1); + data.gear2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_2); + data.gear3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_3); + data.gear4 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_4); + data.gear5 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_5); + data.gear6 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_6); + const bool shifter_r = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_r); + data.dial_cw = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_clockwise); + data.dial_ccw = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_anticlockwise); + data.plus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.plus); + data.minus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.minus); + data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering); + data.throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle); + data.brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake); + data.clutch = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.clutch); + auto [shifter_x, shifter_y] = shifter_to_coord_xy(data.gear1, data.gear2, + data.gear3, data.gear4, data.gear5, data.gear6, shifter_r); + data.shifter_x = shifter_x; + data.shifter_y = shifter_y; + data.gearR = shifter_r; + data.pedals_detached = 0; + data.powered = 1; + data.shifter_attached = 1; + data.set1 = 1; + data.shifter_pressed = shifter_r; + data.range = (m_wheel_range > 360); + std::memcpy(buf, &data, sizeof(data)); +} + void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) { transfer->fake = true; @@ -655,168 +1179,31 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp if (endpoint & (1 << 7)) { - if (buf_size < 11) - { - logitech_g27_log.error("Not populating input buffer with a buffer of the size of %u", buf_size); - return; - } - - ensure(buf_size >= 11); memset(buf, 0, buf_size); - - transfer->expected_count = 11; - sdl_instance::get_instance().pump_events(); - // Fetch input states from SDL - m_sdl_handles_mutex.lock(); - const u16 steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering); - const u8 throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle); - const u8 brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake); - const u8 clutch = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.clutch); - const bool shift_up = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up); - const bool shift_down = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down); - - const bool up = sdl_to_logitech_g27_button(m_joysticks, m_mapping.up); - const bool down = sdl_to_logitech_g27_button(m_joysticks, m_mapping.down); - const bool left = sdl_to_logitech_g27_button(m_joysticks, m_mapping.left); - const bool right = sdl_to_logitech_g27_button(m_joysticks, m_mapping.right); - - const bool triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle); - const bool cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross); - const bool square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square); - const bool circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle); - - const bool l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2); - const bool l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3); - const bool r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2); - const bool r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3); - - const bool plus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.plus); - const bool minus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.minus); - - const bool dial_clockwise = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_clockwise); - const bool dial_anticlockwise = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_anticlockwise); - - const bool select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select); - const bool pause = sdl_to_logitech_g27_button(m_joysticks, m_mapping.pause); - - const bool shifter_1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_1); - const bool shifter_2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_2); - const bool shifter_3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_3); - const bool shifter_4 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_4); - const bool shifter_5 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_5); - const bool shifter_6 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_6); - const bool shifter_r = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_r); - const bool shifter_press = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_press); - m_sdl_handles_mutex.unlock(); - - // populate buffer - buf[0] = hat_components_to_logitech_g27_hat(up, down, left, right); - set_bit(buf, 8, shift_up); - set_bit(buf, 9, shift_down); - - set_bit(buf, 7, triangle); - set_bit(buf, 4, cross); - set_bit(buf, 5, square); - set_bit(buf, 6, circle); - - set_bit(buf, 11, l2); - set_bit(buf, 15, l3); - set_bit(buf, 10, r2); - set_bit(buf, 14, r3); - - set_bit(buf, 22, dial_clockwise); - set_bit(buf, 23, dial_anticlockwise); - - set_bit(buf, 24, plus); - set_bit(buf, 25, minus); - - set_bit(buf, 12, select); - set_bit(buf, 13, pause); - - set_bit(buf, 16, shifter_1); - set_bit(buf, 17, shifter_2); - set_bit(buf, 18, shifter_3); - set_bit(buf, 19, shifter_4); - set_bit(buf, 20, shifter_5); - set_bit(buf, 21, shifter_6); - set_bit(buf, 80, shifter_r); - - // calibrated, unsure - set_bit(buf, 82, true); - // shifter connected - set_bit(buf, 83, true); - /* - * shifter pressed/down bit - * mechanical references: - * - G29 shifter mechanical explanation https://youtu.be/d7qCn3o8K98?t=1124 - * - same mechanism on the G27 https://youtu.be/rdjejtIfkVA?t=760 - * - same mechanism on the G25 https://youtu.be/eCyt_4luwF0?t=130 - * on healthy G29/G27/G25 shifters, shifter is mechnically kept pressed in reverse, the bit should be set - * the shifter_press mapping alone captures instead a shifter press without going into reverse, ie. neutral press, just in case there are games using it for input - */ - set_bit(buf, 86, shifter_press | shifter_r); - - buf[3] = (steering << 2) | buf[3]; - buf[4] = steering >> 6; - buf[5] = throttle; - buf[6] = brake; - buf[7] = clutch; - - // rough analog values recorded in https://github.com/RPCS3/rpcs3/pull/17199#issuecomment-2883934412 - // buf[8] shifter x - // buf[9] shifter y - constexpr u8 shifter_coord_center = 0x80; - constexpr u8 shifter_coord_top = 0xb7; - constexpr u8 shifter_coord_bottom = 0x32; - constexpr u8 shifter_coord_left = 0x30; - constexpr u8 shifter_coord_right = 0xb3; - constexpr u8 shifter_coord_right_reverse = 0xaa; - if (shifter_1) + switch (m_personality) { - buf[8] = shifter_coord_left; - buf[9] = shifter_coord_top; - } - else if (shifter_2) - { - buf[8] = shifter_coord_left; - buf[9] = shifter_coord_bottom; - } - else if (shifter_3) - { - buf[8] = shifter_coord_center; - buf[9] = shifter_coord_top; - } - else if (shifter_4) - { - buf[8] = shifter_coord_center; - buf[9] = shifter_coord_bottom; - } - else if (shifter_5) - { - buf[8] = shifter_coord_right; - buf[9] = shifter_coord_top; - } - else if (shifter_6) - { - buf[8] = shifter_coord_right; - buf[9] = shifter_coord_bottom; - } - else if (shifter_r) - { - buf[8] = shifter_coord_right_reverse; - buf[9] = shifter_coord_bottom; - } - else - { - buf[8] = shifter_coord_center; - buf[9] = shifter_coord_center; + case logitech_personality::driving_force_ex: + transfer_dfex(buf_size, buf, transfer); + break; + case logitech_personality::driving_force_pro: + transfer_dfp(buf_size, buf, transfer); + break; + case logitech_personality::g25: + transfer_g25(buf_size, buf, transfer); + break; + case logitech_personality::driving_force_gt: + transfer_dfgt(buf_size, buf, transfer); + break; + case logitech_personality::g27: + transfer_g27(buf_size, buf, transfer); + break; + case logitech_personality::invalid: + fmt::throw_exception("unreachable"); } - buf[10] = buf[10] | (m_wheel_range > 360 ? 0x90 : 0x10); - - // logitech_g27_log.error("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10]); + // logitech_g27_log.error("dev=%d, ep in : %s", static_cast(m_personality), fmt::buf_to_hexstring(buf, buf_size)); return; } @@ -831,8 +1218,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp transfer->expected_count = buf_size; - // logitech_g27_log.error("%02x %02x %02x %02x %02x %02x %02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); - // printf("%02x %02x %02x %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); + // logitech_g27_log.error("ep out : %s", fmt::buf_to_hexstring(buf, buf_size)); // TODO maybe force clipping from cfg @@ -843,9 +1229,44 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp switch (buf[1]) { case 0x01: + case 0x09: + case 0x10: { - // Change to DFP - logitech_g27_log.error("Drive Force Pro mode switch command ignored"); + // Change device mode + u8 cmd = buf[1]; + u8 arg = buf[2]; + if (buf[8] == 0xf8) // we have 2 commands back to back + { + cmd = buf[9]; + arg = buf[10]; + } + + m_next_personality = logitech_personality::invalid; + if (cmd == 0x09 && arg == 0x04) + m_next_personality = logitech_personality::g27; + else if (cmd == 0x09 && arg == 0x03) + m_next_personality = logitech_personality::driving_force_gt; + else if ((cmd == 0x09 && arg == 0x02) || cmd == 0x10) + m_next_personality = logitech_personality::g25; + else if ((cmd == 0x09 && arg == 0x01) || cmd == 0x01) + m_next_personality = logitech_personality::driving_force_pro; + else if (cmd == 0x09 && arg == 0x00) + m_next_personality = logitech_personality::driving_force_ex; + + if (logitech_personality limit = static_cast(g_cfg_logitech_g27.compatibility_limit.get()); + limit < m_next_personality) + { + m_next_personality = limit; + } + + logitech_g27_log.success("Change device mode : buf=[%s], cmd=0x%x, arg=0x%x, lim=%d -> pers=%d(%s)", + fmt::buf_to_hexstring(buf, buf_size), cmd, arg, g_cfg_logitech_g27.compatibility_limit.get(), + static_cast(m_next_personality), + m_next_personality == logitech_personality::g27 ? "G27" + : m_next_personality == logitech_personality::driving_force_gt ? "Driving Force GT" + : m_next_personality == logitech_personality::g25 ? "G25" + : m_next_personality == logitech_personality::driving_force_pro ? "Driving Force Pro" + : m_next_personality == logitech_personality::driving_force_ex ? "Driving Force EX" : "Invalid"); break; } case 0x02: @@ -862,24 +1283,12 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp m_wheel_range = 900; break; } - case 0x09: - { - // Change device mode - logitech_g27_log.error("Change device mode to %d %s detaching command ignored", buf[2], buf[3] ? "with" : "without"); - break; - } case 0x0a: { // Revert indentity logitech_g27_log.error("Revert device identity after reset %s command ignored", buf[2] ? "enable" : "disable"); break; } - case 0x10: - { - // Switch to G25 with detach - logitech_g27_log.error("Switch to G25 with detach command ignored"); - break; - } case 0x11: { // Switch to G25 without detach diff --git a/rpcs3/Emu/Io/LogitechG27.h b/rpcs3/Emu/Io/LogitechG27.h index 52735a253d..4ec0d35c0b 100644 --- a/rpcs3/Emu/Io/LogitechG27.h +++ b/rpcs3/Emu/Io/LogitechG27.h @@ -16,6 +16,16 @@ #include #include +enum class logitech_personality +{ + driving_force_ex, + driving_force_pro, + g25, + driving_force_gt, + g27, + invalid, +}; + enum class logitech_g27_ffb_state { inactive, @@ -83,9 +93,11 @@ struct logitech_g27_sdl_mapping sdl_mapping dial_clockwise {}; sdl_mapping dial_anticlockwise {}; + sdl_mapping dial_center {}; sdl_mapping select {}; - sdl_mapping pause {}; + sdl_mapping start {}; + sdl_mapping ps {}; sdl_mapping shifter_1 {}; sdl_mapping shifter_2 {}; @@ -94,7 +106,6 @@ struct logitech_g27_sdl_mapping sdl_mapping shifter_5 {}; sdl_mapping shifter_6 {}; sdl_mapping shifter_r {}; - sdl_mapping shifter_press {}; }; class usb_device_logitech_g27 : public usb_device_emulated @@ -112,9 +123,17 @@ public: private: void sdl_refresh(); + void set_personality(logitech_personality personality, bool reconnect = false); + void transfer_dfex(u32 buf_size, u8* buf, UsbTransfer* transfer); + void transfer_dfp(u32 buf_size, u8* buf, UsbTransfer* transfer); + void transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer); + void transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer); + void transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer); u32 m_controller_index = 0; + logitech_personality m_personality = logitech_personality::invalid; + logitech_personality m_next_personality = logitech_personality::invalid; logitech_g27_sdl_mapping m_mapping {}; bool m_reverse_effects = false; diff --git a/rpcs3/Emu/Io/LogitechG27Config.h b/rpcs3/Emu/Io/LogitechG27Config.h index 82a2e89dea..1a46a0fcc2 100644 --- a/rpcs3/Emu/Io/LogitechG27Config.h +++ b/rpcs3/Emu/Io/LogitechG27Config.h @@ -96,9 +96,11 @@ public: emulated_logitech_g27_mapping dial_clockwise{this, "dial_clockwise", 0, sdl_mapping_type::button, 21, hat_component::none, false}; emulated_logitech_g27_mapping dial_anticlockwise{this, "dial_anticlockwise", 0, sdl_mapping_type::button, 22, hat_component::none, false}; + emulated_logitech_g27_mapping dial_center{this, "dial_center", 0, sdl_mapping_type::button, 23, hat_component::none, false}; emulated_logitech_g27_mapping select{this, "select", 0, sdl_mapping_type::button, 8, hat_component::none, false}; - emulated_logitech_g27_mapping pause{this, "pause", 0, sdl_mapping_type::button, 9, hat_component::none, false}; + emulated_logitech_g27_mapping start{this, "pause", 0, sdl_mapping_type::button, 9, hat_component::none, false}; + emulated_logitech_g27_mapping ps{this, "ps", 0, sdl_mapping_type::button, 24, hat_component::none, false}; emulated_logitech_g27_mapping shifter_1{this, "shifter_1", 0, sdl_mapping_type::button, 3, hat_component::none, false}; emulated_logitech_g27_mapping shifter_2{this, "shifter_2", 0, sdl_mapping_type::button, 0, hat_component::none, false}; @@ -107,9 +109,9 @@ public: emulated_logitech_g27_mapping shifter_5{this, "shifter_5", 0, sdl_mapping_type::hat, 0, hat_component::up, false}; emulated_logitech_g27_mapping shifter_6{this, "shifter_6", 0, sdl_mapping_type::hat, 0, hat_component::down, false}; emulated_logitech_g27_mapping shifter_r{this, "shifter_r", 0, sdl_mapping_type::hat, 0, hat_component::left, false}; - emulated_logitech_g27_mapping shifter_press{this, "shifter_press", 0, sdl_mapping_type::hat, 0, hat_component::right, false}; cfg::_bool reverse_effects{this, "reverse_effects", false}; + 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/TopShotElite.cpp b/rpcs3/Emu/Io/TopShotElite.cpp index c241fe1ed4..ed4042fdaa 100644 --- a/rpcs3/Emu/Io/TopShotElite.cpp +++ b/rpcs3/Emu/Io/TopShotElite.cpp @@ -219,7 +219,7 @@ void usb_device_topshotelite::control_transfer(u8 bmRequestType, u8 bRequest, u1 extern bool is_input_allowed(); -static void set_sensor_pos(struct TopShotElite_data* ts, s32 led_lx, s32 led_ly, s32 led_rx, s32 led_ry, s32 detect_l, s32 detect_r) +static void set_sensor_pos(TopShotElite_data* ts, s32 led_lx, s32 led_ly, s32 led_rx, s32 led_ry, s32 detect_l, s32 detect_r) { ts->led_lx_hi = led_lx >> 2; ts->led_lx_lo = led_lx & 0x3; @@ -250,7 +250,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en transfer->expected_result = HC_CC_NOERR; transfer->expected_time = get_timestamp() + 4000; - struct TopShotElite_data ts{}; + TopShotElite_data ts{}; ts.dpad = Dpad_None; ts.stick_lx = ts.stick_ly = ts.stick_rx = ts.stick_ry = 0x7f; if (m_mode) @@ -274,7 +274,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en if (m_controller_index >= g_cfg_topshotelite.players.size()) { - topshotelite_log.warning("Top Shot Fearmaster controllers are only supported for Player1 to Player%d", g_cfg_topshotelite.players.size()); + topshotelite_log.warning("Top Shot Elite controllers are only supported for Player1 to Player%d", g_cfg_topshotelite.players.size()); prepare_data(&ts, buf); return; } diff --git a/rpcs3/Emu/Io/TopShotFearmaster.cpp b/rpcs3/Emu/Io/TopShotFearmaster.cpp index eed7ade977..12d56b921c 100644 --- a/rpcs3/Emu/Io/TopShotFearmaster.cpp +++ b/rpcs3/Emu/Io/TopShotFearmaster.cpp @@ -247,7 +247,7 @@ static int get_heartrate_sensor_value(u8 heartrate) return sensor_data[heartrate - 30]; } -static void set_sensor_pos(struct TopShotFearmaster_data* ts, s32 led_lx, s32 led_ly, s32 led_rx, s32 led_ry, s32 detect_l, s32 detect_r) +static void set_sensor_pos(TopShotFearmaster_data* ts, s32 led_lx, s32 led_ly, s32 led_rx, s32 led_ry, s32 detect_l, s32 detect_r) { ts->led_lx_hi = led_lx >> 2; ts->led_lx_lo = led_lx & 0x3; @@ -278,7 +278,7 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32 transfer->expected_result = HC_CC_NOERR; transfer->expected_time = get_timestamp() + 4000; - struct TopShotFearmaster_data ts{}; + TopShotFearmaster_data ts{}; ts.dpad = Dpad_None; ts.stick_lx = ts.stick_ly = ts.stick_rx = ts.stick_ry = 0x7f; if (m_mode) diff --git a/rpcs3/Emu/Io/camera_config.cpp b/rpcs3/Emu/Io/camera_config.cpp index f498de72b1..4016447321 100644 --- a/rpcs3/Emu/Io/camera_config.cpp +++ b/rpcs3/Emu/Io/camera_config.cpp @@ -36,32 +36,38 @@ void cfg_camera::save() const } } -cfg_camera::camera_setting cfg_camera::get_camera_setting(std::string_view camera, bool& success) +cfg_camera::camera_setting cfg_camera::get_camera_setting(std::string_view handler, std::string_view camera, bool& success) { - camera_setting setting; - const std::string value = cameras.get_value(camera); + camera_setting setting {}; + const std::string value = cameras.get_value(fmt::format("%s-%s", handler, camera)); success = !value.empty(); if (success) { - setting.from_string(cameras.get_value(camera)); + setting.from_string(value); } return setting; } -void cfg_camera::set_camera_setting(const std::string& camera, const camera_setting& setting) +void cfg_camera::set_camera_setting(std::string_view handler, std::string_view camera, const camera_setting& setting) { + if (handler.empty()) + { + camera_log.error("String '%s' cannot be used as handler key.", handler); + return; + } + if (camera.empty()) { camera_log.error("String '%s' cannot be used as camera key.", camera); return; } - cameras.set_value(camera, setting.to_string()); + cameras.set_value(fmt::format("%s-%s", handler, camera), setting.to_string()); } std::string cfg_camera::camera_setting::to_string() const { - return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format); + return fmt::format("%d,%d,%f,%f,%d,%d", width, height, min_fps, max_fps, format, colorspace); } void cfg_camera::camera_setting::from_string(std::string_view text) @@ -102,16 +108,19 @@ void cfg_camera::camera_setting::from_string(std::string_view text) return true; }; - if (!to_integer(::at32(list, 0), width) || - !to_integer(::at32(list, 1), height) || - !to_double(::at32(list, 2), min_fps) || - !to_double(::at32(list, 3), max_fps) || - !to_integer(::at32(list, 4), format)) + usz pos = 0; + if (!to_integer(::at32(list, pos++), width) || + !to_integer(::at32(list, pos++), height) || + !to_double(::at32(list, pos++), min_fps) || + !to_double(::at32(list, pos++), max_fps) || + !to_integer(::at32(list, pos++), format) || + !to_integer(::at32(list, pos++), colorspace)) { width = 0; height = 0; min_fps = 0; max_fps = 0; format = 0; + colorspace = 0; } } diff --git a/rpcs3/Emu/Io/camera_config.h b/rpcs3/Emu/Io/camera_config.h index 1c576e32c1..862d87dd63 100644 --- a/rpcs3/Emu/Io/camera_config.h +++ b/rpcs3/Emu/Io/camera_config.h @@ -15,18 +15,20 @@ struct cfg_camera final : cfg::node double min_fps = 0; double max_fps = 0; int format = 0; + int colorspace = 0; - static constexpr u32 member_count = 5; + static constexpr u32 member_count = 6; std::string to_string() const; void from_string(std::string_view text); }; - camera_setting get_camera_setting(std::string_view camera, bool& success); - void set_camera_setting(const std::string& camera, const camera_setting& setting); + + camera_setting get_camera_setting(std::string_view handler, std::string_view camera, bool& success); + void set_camera_setting(std::string_view handler, std::string_view camera, const camera_setting& setting); const std::string path; - cfg::map_entry cameras{ this, "Cameras" }; // : ,,,, + cfg::map_entry cameras{ this, "Cameras" }; // : ,,,,, }; extern cfg_camera g_cfg_camera; diff --git a/rpcs3/Emu/NP/clans_client.cpp b/rpcs3/Emu/NP/clans_client.cpp index 7883901476..da3f506c91 100644 --- a/rpcs3/Emu/NP/clans_client.cpp +++ b/rpcs3/Emu/NP/clans_client.cpp @@ -128,18 +128,18 @@ void fmt_class_string::format(std::string& out, u64 arg namespace clan { - struct curl_memory - { + struct curl_memory + { char* response; size_t size; - }; + }; size_t clans_client::curl_write_callback(void* data, size_t size, size_t nmemb, void* clientp) { - size_t realsize = size * nmemb; + const size_t realsize = size * nmemb; std::vector* mem = static_cast*>(clientp); - size_t old_size = mem->size(); + const size_t old_size = mem->size(); mem->resize(old_size + realsize); memcpy(mem->data() + old_size, data, realsize); @@ -169,11 +169,11 @@ namespace clan CURL* curl = nullptr; // TODO: this was arbitrarily chosen -- see if there's a real amount - static const u32 SCE_NP_CLANS_MAX_CTX_NUM = 16; - - static const u32 id_base = 0xA001; - static const u32 id_step = 1; - static const u32 id_count = SCE_NP_CLANS_MAX_CTX_NUM; + static constexpr u32 SCE_NP_CLANS_MAX_CTX_NUM = 16; + + static constexpr u32 id_base = 0xA001; + static constexpr u32 id_step = 1; + static constexpr u32 id_count = SCE_NP_CLANS_MAX_CTX_NUM; SAVESTATE_INIT_POS(55); }; @@ -187,7 +187,7 @@ namespace clan idm::clear(); } - SceNpClansError clans_client::create_request(s32* req_id) + SceNpClansError clans_client::create_request(s32& req_id) { if (!g_cfg.net.clans_enabled) { @@ -201,14 +201,14 @@ namespace clan return SceNpClansError::SCE_NP_CLANS_ERROR_EXCEEDS_MAX; } - auto ctx = idm::get_unlocked(id); + const auto ctx = idm::get_unlocked(id); if (!ctx || !ctx->curl) { idm::remove(id); return SceNpClansError::SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - *req_id = id; + req_id = id; return SceNpClansError::SCE_NP_CLANS_SUCCESS; } @@ -221,7 +221,7 @@ namespace clan return SceNpClansError::SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - SceNpClansError clans_client::send_request(u32 req_id, ClanRequestAction action, ClanManagerOperationType op_type, pugi::xml_document* xml_body, pugi::xml_document* out_response) + SceNpClansError clans_client::send_request(u32 req_id, ClanRequestAction action, ClanManagerOperationType op_type, const pugi::xml_document& xml_body, pugi::xml_document& out_response) { auto ctx = idm::get_unlocked(req_id); @@ -231,20 +231,20 @@ namespace clan CURL* curl = ctx->curl; ClanRequestType req_type = ClanRequestType::FUNC; - pugi::xml_node clan = xml_body->child("clan"); + const pugi::xml_node clan = xml_body.child("clan"); if (clan && clan.child("ticket")) { req_type = ClanRequestType::SEC; } - std::string host = g_cfg_clans.get_host(); - std::string protocol = g_cfg_clans.get_use_https() ? "https" : "http"; - std::string url = fmt::format("%s://%s/clan_manager_%s/%s/%s", protocol, host, op_type, req_type, action); + const std::string host = g_cfg_clans.get_host(); + const std::string protocol = g_cfg_clans.get_use_https() ? "https" : "http"; + const std::string url = fmt::format("%s://%s/clan_manager_%s/%s/%s", protocol, host, op_type, req_type, action); std::ostringstream oss; - xml_body->save(oss, "\t", 8U); + xml_body.save(oss, "\t", 8U); - std::string xml = oss.str(); + const std::string xml = oss.str(); char err_buf[CURL_ERROR_SIZE]; err_buf[0] = '\0'; @@ -264,11 +264,10 @@ namespace clan curl_easy_setopt(curl, CURLOPT_POSTFIELDS, xml.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, xml.size()); - CURLcode res = curl_easy_perform(curl); + const CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { - out_response = nullptr; clan_log.error("curl_easy_perform() failed: %s", curl_easy_strerror(res)); clan_log.error("Error buffer: %s", err_buf); return SCE_NP_CLANS_ERROR_BAD_REQUEST; @@ -276,22 +275,22 @@ namespace clan response_buffer.push_back('\0'); - pugi::xml_parse_result xml_res = out_response->load_string(response_buffer.data()); + const pugi::xml_parse_result xml_res = out_response.load_string(response_buffer.data()); if (!xml_res) { clan_log.error("XML parsing failed: %s", xml_res.description()); return SCE_NP_CLANS_ERROR_BAD_RESPONSE; } - pugi::xml_node clan_result = out_response->child("clan"); + const pugi::xml_node clan_result = out_response.child("clan"); if (!clan_result) return SCE_NP_CLANS_ERROR_BAD_RESPONSE; - pugi::xml_attribute result = clan_result.attribute("result"); + const pugi::xml_attribute result = clan_result.attribute("result"); if (!result) return SCE_NP_CLANS_ERROR_BAD_RESPONSE; - std::string result_str = result.as_string(); + const std::string result_str = result.as_string(); if (result_str != "00") return static_cast(0x80022800 | std::stoul(result_str, nullptr, 16)); @@ -331,16 +330,16 @@ namespace clan std::vector ticket_bytes(1024); uint32_t ticket_size = UINT32_MAX; - Base64_Encode_NoNl(clan_ticket.data(), clan_ticket.size(), ticket_bytes.data(), &ticket_size); - std::string ticket_str = std::string(reinterpret_cast(ticket_bytes.data()), ticket_size); + Base64_Encode_NoNl(clan_ticket.data(), static_cast(clan_ticket.size()), ticket_bytes.data(), &ticket_size); + const std::string ticket_str = std::string(reinterpret_cast(ticket_bytes.data()), ticket_size); return ticket_str; } #pragma region Outgoing API Requests - SceNpClansError clans_client::get_clan_list(np::np_handler& nph, u32 req_id, SceNpClansPagingRequest* paging, SceNpClansEntry* clan_list, SceNpClansPagingResult* page_result) + SceNpClansError clans_client::get_clan_list(np::np_handler& nph, u32 req_id, const SceNpClansPagingRequest& paging, std::vector& clan_list, SceNpClansPagingResult& page_result) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -348,47 +347,47 @@ namespace clan pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); - clan.append_child("start").text().set(paging->startPos); - clan.append_child("max").text().set(paging->max); + clan.append_child("start").text().set(paging.startPos); + clan.append_child("max").text().set(paging.max); pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetClanList, ClanManagerOperationType::VIEW, &doc, &response); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetClanList, ClanManagerOperationType::VIEW, doc, response); if (clan_res != SCE_NP_CLANS_SUCCESS) return clan_res; - pugi::xml_node clan_result = response.child("clan"); - pugi::xml_node list = clan_result.child("list"); + const pugi::xml_node clan_result = response.child("clan"); + const pugi::xml_node list = clan_result.child("list"); - pugi::xml_attribute results = list.attribute("results"); - uint32_t results_count = results.as_uint(); + const pugi::xml_attribute results = list.attribute("results"); + const uint32_t results_count = results.as_uint(); - pugi::xml_attribute total = list.attribute("total"); - uint32_t total_count = total.as_uint(); + const pugi::xml_attribute total = list.attribute("total"); + const uint32_t total_count = total.as_uint(); int i = 0; for (pugi::xml_node info = list.child("info"); info; info = info.next_sibling("info"), i++) { - pugi::xml_attribute id = info.attribute("id"); - uint32_t clan_id = id.as_uint(); + const pugi::xml_attribute id = info.attribute("id"); + const uint32_t clan_id = id.as_uint(); - pugi::xml_node name = info.child("name"); - std::string name_str = name.text().as_string(); + const pugi::xml_node name = info.child("name"); + const std::string name_str = name.text().as_string(); - pugi::xml_node tag = info.child("tag"); - std::string tag_str = tag.text().as_string(); + const pugi::xml_node tag = info.child("tag"); + const std::string tag_str = tag.text().as_string(); - pugi::xml_node role = info.child("role"); - int32_t role_int = role.text().as_uint(); + const pugi::xml_node role = info.child("role"); + const int32_t role_int = role.text().as_uint(); - pugi::xml_node status = info.child("status"); - uint32_t status_int = status.text().as_uint(); + const pugi::xml_node status = info.child("status"); + const uint32_t status_int = status.text().as_uint(); - pugi::xml_node onlinename = info.child("onlinename"); - std::string onlinename_str = onlinename.text().as_string(); + const pugi::xml_node onlinename = info.child("onlinename"); + const std::string onlinename_str = onlinename.text().as_string(); - pugi::xml_node members = info.child("members"); - uint32_t members_int = members.text().as_uint(); + const pugi::xml_node members = info.child("members"); + const uint32_t members_int = members.text().as_uint(); SceNpClansEntry entry = SceNpClansEntry{ .info = SceNpClansClanBasicInfo{ @@ -402,41 +401,41 @@ namespace clan .status = static_cast(status_int)}; strcpy_trunc(entry.info.name, name_str); - strcpy_trunc(entry.info.tag, tag_str); + strcpy_trunc(entry.info.tag, tag_str); - clan_list[i] = entry; + ::at32(clan_list, i) = std::move(entry); i++; } - *page_result = SceNpClansPagingResult{ + page_result = SceNpClansPagingResult{ .count = results_count, .total = total_count}; return SCE_NP_CLANS_SUCCESS; } - SceNpClansError clans_client::get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo* clan_info) + SceNpClansError clans_client::get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo& clan_info) { pugi::xml_document doc = pugi::xml_document(); pugi::xml_node clan = doc.append_child("clan"); clan.append_child("id").text().set(clan_id); pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetClanInfo, ClanManagerOperationType::VIEW, &doc, &response); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetClanInfo, ClanManagerOperationType::VIEW, doc, response); if (clan_res != SCE_NP_CLANS_SUCCESS) return clan_res; - pugi::xml_node clan_result = response.child("clan"); - pugi::xml_node info = clan_result.child("info"); + const pugi::xml_node clan_result = response.child("clan"); + const pugi::xml_node info = clan_result.child("info"); - std::string name_str = info.child("name").text().as_string(); - std::string tag_str = info.child("tag").text().as_string(); - uint32_t members_int = info.child("members").text().as_uint(); - std::string date_created_str = info.child("date-created").text().as_string(); - std::string description_str = info.child("description").text().as_string(); + const std::string name_str = info.child("name").text().as_string(); + const std::string tag_str = info.child("tag").text().as_string(); + const uint32_t members_int = info.child("members").text().as_uint(); + const std::string date_created_str = info.child("date-created").text().as_string(); + const std::string description_str = info.child("description").text().as_string(); - *clan_info = SceNpClansClanInfo{ + clan_info = SceNpClansClanInfo{ .info = SceNpClansClanBasicInfo{ .clanId = clan_id, .numMembers = members_int, @@ -447,16 +446,16 @@ namespace clan .description = "", }}; - strcpy_trunc(clan_info->info.name, name_str); - strcpy_trunc(clan_info->info.tag, tag_str); - strcpy_trunc(clan_info->updatable.description, description_str); + strcpy_trunc(clan_info.info.name, name_str); + strcpy_trunc(clan_info.info.tag, tag_str); + strcpy_trunc(clan_info.updatable.description, description_str); return SCE_NP_CLANS_SUCCESS; } - SceNpClansError clans_client::get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberEntry* mem_info) + SceNpClansError clans_client::get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMemberEntry& mem_info) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -464,22 +463,22 @@ namespace clan pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - - std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); - clan.append_child("jid").text().set(jid_str.c_str()); + + const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberInfo, ClanManagerOperationType::VIEW, &doc, &response); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberInfo, ClanManagerOperationType::VIEW, doc, response); if (clan_res != SCE_NP_CLANS_SUCCESS) return clan_res; - pugi::xml_node clan_result = response.child("clan"); - pugi::xml_node member_info = clan_result.child("info"); + const pugi::xml_node clan_result = response.child("clan"); + const pugi::xml_node member_info = clan_result.child("info"); - pugi::xml_attribute member_jid = member_info.attribute("jid"); - std::string member_jid_str = member_jid.as_string(); - std::string member_username = fmt::split(member_jid_str, {"@"})[0]; + const pugi::xml_attribute member_jid = member_info.attribute("jid"); + const std::string member_jid_str = member_jid.as_string(); + const std::string member_username = fmt::split(member_jid_str, {"@"})[0]; SceNpId member_npid; if (strncmp(member_username.c_str(), nph.get_npid().handle.data, 16) == 0) @@ -491,18 +490,18 @@ namespace clan np::string_to_npid(member_username, member_npid); } - pugi::xml_node role = member_info.child("role"); - uint32_t role_int = role.text().as_uint(); + const pugi::xml_node role = member_info.child("role"); + const uint32_t role_int = role.text().as_uint(); - pugi::xml_node status = member_info.child("status"); - uint32_t status_int = status.text().as_uint(); + const pugi::xml_node status = member_info.child("status"); + const uint32_t status_int = status.text().as_uint(); - pugi::xml_node description = member_info.child("description"); - std::string description_str = description.text().as_string(); + const pugi::xml_node description = member_info.child("description"); + const std::string description_str = description.text().as_string(); - *mem_info = SceNpClansMemberEntry + mem_info = SceNpClansMemberEntry { - .npid = member_npid, + .npid = std::move(member_npid), .role = static_cast(role_int), .status = static_cast(status_int), .updatable = SceNpClansUpdatableMemberInfo{ @@ -510,47 +509,47 @@ namespace clan } }; - strcpy_trunc(mem_info->updatable.description, description_str); + strcpy_trunc(mem_info.updatable.description, description_str); return SCE_NP_CLANS_SUCCESS; } - SceNpClansError clans_client::get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMemberStatus /*status*/, SceNpClansMemberEntry* mem_list, SceNpClansPagingResult* page_result) - { - std::string ticket = get_clan_ticket(nph); + SceNpClansError clans_client::get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansPagingRequest& paging, SceNpClansMemberStatus /*status*/, std::vector& mem_list, SceNpClansPagingResult& page_result) + { + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; - pugi::xml_document doc = pugi::xml_document(); + pugi::xml_document doc = pugi::xml_document(); pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - clan.append_child("start").text().set(paging->startPos); - clan.append_child("max").text().set(paging->max); + clan.append_child("start").text().set(paging.startPos); + clan.append_child("max").text().set(paging.max); pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberList, ClanManagerOperationType::VIEW, &doc, &response); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberList, ClanManagerOperationType::VIEW, doc, response); if (clan_res != SCE_NP_CLANS_SUCCESS) return clan_res; - pugi::xml_node clan_result = response.child("clan"); - pugi::xml_node list = clan_result.child("list"); + const pugi::xml_node clan_result = response.child("clan"); + const pugi::xml_node list = clan_result.child("list"); - pugi::xml_attribute results = list.attribute("results"); - uint32_t results_count = results.as_uint(); + const pugi::xml_attribute results = list.attribute("results"); + const uint32_t results_count = results.as_uint(); - pugi::xml_attribute total = list.attribute("total"); - uint32_t total_count = total.as_uint(); - - int i = 0; - for (pugi::xml_node member_info = list.child("info"); member_info; member_info = member_info.next_sibling("info")) - { - std::string member_jid = member_info.attribute("jid").as_string(); - std::string member_username = fmt::split(member_jid, {"@"})[0]; + const pugi::xml_attribute total = list.attribute("total"); + const uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node member_info = list.child("info"); member_info; member_info = member_info.next_sibling("info")) + { + const std::string member_jid = member_info.attribute("jid").as_string(); + const std::string member_username = fmt::split(member_jid, {"@"})[0]; SceNpId member_npid; - if (strncmp(member_username.c_str(), nph.get_npid().handle.data, 16) == 0) + if (strncmp(member_username.c_str(), nph.get_npid().handle.data, SCE_NET_NP_ONLINEID_MAX_LENGTH) == 0) { member_npid = nph.get_npid(); } @@ -559,67 +558,67 @@ namespace clan np::string_to_npid(member_username, member_npid); } - uint32_t role_int = member_info.child("role").text().as_uint(); - uint32_t status_int = member_info.child("status").text().as_uint(); - std::string description_str = member_info.child("description").text().as_string(); + const uint32_t role_int = member_info.child("role").text().as_uint(); + const uint32_t status_int = member_info.child("status").text().as_uint(); + const std::string description_str = member_info.child("description").text().as_string(); - SceNpClansMemberEntry entry = SceNpClansMemberEntry - { - .npid = member_npid, - .role = static_cast(role_int), - .status = static_cast(status_int), - }; + SceNpClansMemberEntry entry = SceNpClansMemberEntry + { + .npid = std::move(member_npid), + .role = static_cast(role_int), + .status = static_cast(status_int), + }; strcpy_trunc(entry.updatable.description, description_str); - - mem_list[i] = entry; - i++; - } - *page_result = SceNpClansPagingResult - { + ::at32(mem_list, i) = std::move(entry); + i++; + } + + page_result = SceNpClansPagingResult + { .count = results_count, .total = total_count - }; + }; return SCE_NP_CLANS_SUCCESS; } - SceNpClansError clans_client::get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* page_result) - { - std::string ticket = get_clan_ticket(nph); + SceNpClansError clans_client::get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansPagingRequest& paging, std::vector& bl, SceNpClansPagingResult& page_result) + { + const std::string ticket = get_clan_ticket(nph); - pugi::xml_document doc = pugi::xml_document(); - pugi::xml_node clan = doc.append_child("clan"); - clan.append_child("ticket").text().set(ticket.c_str()); - clan.append_child("id").text().set(clan_id); - clan.append_child("start").text().set(paging->startPos); - clan.append_child("max").text().set(paging->max); + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + clan.append_child("start").text().set(paging.startPos); + clan.append_child("max").text().set(paging.max); - pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetBlacklist, ClanManagerOperationType::VIEW, &doc, &response); + pugi::xml_document response = pugi::xml_document(); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetBlacklist, ClanManagerOperationType::VIEW, doc, response); - if (clan_res != SCE_NP_CLANS_SUCCESS) - return clan_res; + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; - pugi::xml_node clan_result = response.child("clan"); - pugi::xml_node list = clan_result.child("list"); + const pugi::xml_node clan_result = response.child("clan"); + const pugi::xml_node list = clan_result.child("list"); - pugi::xml_attribute results = list.attribute("results"); - uint32_t results_count = results.as_uint(); + const pugi::xml_attribute results = list.attribute("results"); + const uint32_t results_count = results.as_uint(); - pugi::xml_attribute total = list.attribute("total"); - uint32_t total_count = total.as_uint(); + const pugi::xml_attribute total = list.attribute("total"); + const uint32_t total_count = total.as_uint(); - int i = 0; - for (pugi::xml_node member = list.child("entry"); member; member = member.next_sibling("entry")) - { - pugi::xml_node member_jid = member.child("jid"); - std::string member_jid_str = member_jid.text().as_string(); - std::string member_username = fmt::split(member_jid_str, {"@"})[0]; + int i = 0; + for (pugi::xml_node member = list.child("entry"); member; member = member.next_sibling("entry")) + { + const pugi::xml_node member_jid = member.child("jid"); + const std::string member_jid_str = member_jid.text().as_string(); + const std::string member_username = fmt::split(member_jid_str, {"@"})[0]; SceNpId member_npid = {}; - if (strncmp(member_username.c_str(), nph.get_npid().handle.data, 16) == 0) + if (strncmp(member_username.c_str(), nph.get_npid().handle.data, SCE_NET_NP_ONLINEID_MAX_LENGTH) == 0) { member_npid = nph.get_npid(); } @@ -628,27 +627,27 @@ namespace clan np::string_to_npid(member_username.c_str(), member_npid); } - SceNpClansBlacklistEntry entry = SceNpClansBlacklistEntry - { - .entry = member_npid, - }; + SceNpClansBlacklistEntry entry = SceNpClansBlacklistEntry + { + .entry = std::move(member_npid), + }; - bl[i] = entry; - i++; - } + ::at32(bl, i) = std::move(entry); + i++; + } - *page_result = SceNpClansPagingResult - { - .count = results_count, - .total = total_count - }; + page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; - return SCE_NP_CLANS_SUCCESS; - } + return SCE_NP_CLANS_SUCCESS; + } - SceNpClansError clans_client::add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + SceNpClansError clans_client::add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -657,14 +656,14 @@ namespace clan clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::RecordBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::RecordBlacklistEntry, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + SceNpClansError clans_client::remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id) { std::string ticket = get_clan_ticket(nph); if (ticket.empty()) @@ -674,50 +673,50 @@ namespace clan pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - - std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); - clan.append_child("jid").text().set(jid_str.c_str()); + + const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::DeleteBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::DeleteBlacklistEntry, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::clan_search(u32 req_id, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clan_list, SceNpClansPagingResult* page_result) + SceNpClansError clans_client::clan_search(u32 req_id, const SceNpClansPagingRequest& paging, const SceNpClansSearchableName& search, std::vector& clan_list, SceNpClansPagingResult& page_result) { pugi::xml_document doc = pugi::xml_document(); - pugi::xml_node clan = doc.append_child("clan"); - clan.append_child("start").text().set(paging->startPos); - clan.append_child("max").text().set(paging->max); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("start").text().set(paging.startPos); + clan.append_child("max").text().set(paging.max); pugi::xml_node filter = clan.append_child("filter"); pugi::xml_node name = filter.append_child("name"); - - std::string op_name = fmt::format("%s", static_cast(static_cast(search->nameSearchOp))); + + const std::string op_name = fmt::format("%s", static_cast(static_cast(search.nameSearchOp))); name.append_attribute("op").set_value(op_name.c_str()); - name.append_attribute("value").set_value(search->name); + name.append_attribute("value").set_value(search.name); - pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::ClanSearch, ClanManagerOperationType::VIEW, &doc, &response); + pugi::xml_document response = pugi::xml_document(); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::ClanSearch, ClanManagerOperationType::VIEW, doc, response); - if (clan_res != SCE_NP_CLANS_SUCCESS) - return clan_res; + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; - pugi::xml_node clan_result = response.child("clan"); - pugi::xml_node list = clan_result.child("list"); + const pugi::xml_node clan_result = response.child("clan"); + const pugi::xml_node list = clan_result.child("list"); - pugi::xml_attribute results = list.attribute("results"); - uint32_t results_count = results.as_uint(); + const pugi::xml_attribute results = list.attribute("results"); + const uint32_t results_count = results.as_uint(); - pugi::xml_attribute total = list.attribute("total"); - uint32_t total_count = total.as_uint(); + const pugi::xml_attribute total = list.attribute("total"); + const uint32_t total_count = total.as_uint(); - int i = 0; - for (pugi::xml_node node = list.child("info"); node; node = node.next_sibling("info")) - { - uint32_t clan_id = node.attribute("id").as_uint(); - std::string name_str = node.child("name").text().as_string(); - std::string tag_str = node.child("tag").text().as_string(); - uint32_t members_int = node.child("members").text().as_uint(); + int i = 0; + for (pugi::xml_node node = list.child("info"); node; node = node.next_sibling("info")) + { + const uint32_t clan_id = node.attribute("id").as_uint(); + const std::string name_str = node.child("name").text().as_string(); + const std::string tag_str = node.child("tag").text().as_string(); + const uint32_t members_int = node.child("members").text().as_uint(); SceNpClansClanBasicInfo entry = SceNpClansClanBasicInfo { @@ -731,22 +730,22 @@ namespace clan strcpy_trunc(entry.name, name_str); strcpy_trunc(entry.tag, tag_str); - clan_list[i] = entry; - i++; - } + ::at32(clan_list, i) = std::move(entry); + i++; + } - *page_result = SceNpClansPagingResult - { - .count = results_count, - .total = total_count - }; + page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; - return SCE_NP_CLANS_SUCCESS; + return SCE_NP_CLANS_SUCCESS; } SceNpClansError clans_client::create_clan(np::np_handler& nph, u32 req_id, std::string_view name, std::string_view tag, vm::ptr clan_id) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -758,14 +757,14 @@ namespace clan clan.append_child("tag").text().set(tag.data()); pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::CreateClan, ClanManagerOperationType::UPDATE, &doc, &response); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::CreateClan, ClanManagerOperationType::UPDATE, doc, response); if (clan_res != SCE_NP_CLANS_SUCCESS) return clan_res; - pugi::xml_node clan_result = response.child("clan"); - pugi::xml_attribute id = clan_result.attribute("id"); - uint32_t clan_id_int = id.as_uint(); + const pugi::xml_node clan_result = response.child("clan"); + const pugi::xml_attribute id = clan_result.attribute("id"); + const uint32_t clan_id_int = id.as_uint(); *clan_id = clan_id_int; @@ -774,7 +773,7 @@ namespace clan SceNpClansError clans_client::disband_dlan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -784,7 +783,7 @@ namespace clan clan.append_child("id").text().set(clan_id); pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::DisbandClan, ClanManagerOperationType::UPDATE, &doc, &response); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::DisbandClan, ClanManagerOperationType::UPDATE, doc, response); if (clan_res != SCE_NP_CLANS_SUCCESS) return clan_res; @@ -792,9 +791,9 @@ namespace clan return SCE_NP_CLANS_SUCCESS; } - SceNpClansError clans_client::request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* /*message*/) + SceNpClansError clans_client::request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage& /*message*/) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -804,12 +803,12 @@ namespace clan clan.append_child("id").text().set(clan_id); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::RequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::RequestMembership, ClanManagerOperationType::UPDATE, doc, response); } SceNpClansError clans_client::cancel_request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -819,12 +818,12 @@ namespace clan clan.append_child("id").text().set(clan_id); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::CancelRequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::CancelRequestMembership, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/, b8 allow) + SceNpClansError clans_client::send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMessage& /*message*/, b8 allow) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -832,17 +831,17 @@ namespace clan pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - - std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); - clan.append_child("jid").text().set(jid_str.c_str()); + + const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, allow ? ClanRequestAction::AcceptMembershipRequest : ClanRequestAction::DeclineMembershipRequest, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, allow ? ClanRequestAction::AcceptMembershipRequest : ClanRequestAction::DeclineMembershipRequest, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/) + SceNpClansError clans_client::send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMessage& /*message*/) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -850,17 +849,17 @@ namespace clan pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - - std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); - clan.append_child("jid").text().set(jid_str.c_str()); + + const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::SendInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::SendInvitation, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + SceNpClansError clans_client::cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -868,17 +867,17 @@ namespace clan pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - - std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); - clan.append_child("jid").text().set(jid_str.c_str()); + + const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::CancelInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::CancelInvitation, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* /*message*/, b8 accept) + SceNpClansError clans_client::send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage& /*message*/, b8 accept) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -888,12 +887,12 @@ namespace clan clan.append_child("id").text().set(clan_id); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, accept ? ClanRequestAction::AcceptInvitation : ClanRequestAction::DeclineInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, accept ? ClanRequestAction::AcceptInvitation : ClanRequestAction::DeclineInvitation, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableMemberInfo* info) + SceNpClansError clans_client::update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansUpdatableMemberInfo& info) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -906,33 +905,33 @@ namespace clan role.text().set(nph.get_npid().handle.data); pugi::xml_node description = clan.append_child("description"); - description.text().set(info->description); - + description.text().set(info.description); + pugi::xml_node status = clan.append_child("bin-attr1"); byte bin_attr_1[SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE * 2 + 1] = {0}; uint32_t bin_attr_1_size = UINT32_MAX; - Base64_Encode_NoNl(info->binAttr1, info->binData1Size, bin_attr_1, &bin_attr_1_size); + Base64_Encode_NoNl(info.binAttr1, info.binData1Size, bin_attr_1, &bin_attr_1_size); if (bin_attr_1_size == UINT32_MAX) return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; - - // `reinterpret_cast` used to let the compiler select the correct overload of `set` + + // `reinterpret_cast` used to let the compiler select the correct overload of `set` status.text().set(reinterpret_cast(bin_attr_1)); pugi::xml_node allow_msg = clan.append_child("allow-msg"); - allow_msg.text().set(static_cast(info->allowMsg)); + allow_msg.text().set(static_cast(info.allowMsg)); pugi::xml_node size = clan.append_child("size"); - size.text().set(info->binData1Size); + size.text().set(info.binData1Size); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::UpdateMemberInfo, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::UpdateMemberInfo, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableClanInfo* info) + SceNpClansError clans_client::update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansUpdatableClanInfo& info) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -944,15 +943,15 @@ namespace clan // TODO: implement binary and integer attributes (not implemented in server yet) pugi::xml_node description = clan.append_child("description"); - description.text().set(info->description); + description.text().set(info.description); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::UpdateClanInfo, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::UpdateClanInfo, ClanManagerOperationType::UPDATE, doc, response); } SceNpClansError clans_client::join_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -962,12 +961,12 @@ namespace clan clan.append_child("id").text().set(clan_id); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::JoinClan, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::JoinClan, ClanManagerOperationType::UPDATE, doc, response); } SceNpClansError clans_client::leave_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -977,12 +976,12 @@ namespace clan clan.append_child("id").text().set(clan_id); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::LeaveClan, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::LeaveClan, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/) + SceNpClansError clans_client::kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMessage& /*message*/) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -990,17 +989,17 @@ namespace clan pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - - std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); - clan.append_child("jid").text().set(jid_str.c_str()); + + const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::KickMember, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::KickMember, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberRole role) + SceNpClansError clans_client::change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMemberRole role) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -1008,20 +1007,20 @@ namespace clan pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - - std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); - clan.append_child("jid").text().set(jid_str.c_str()); + + const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_node role_node = clan.append_child("role"); role_node.text().set(static_cast(role)); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::ChangeMemberRole, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::ChangeMemberRole, ClanManagerOperationType::UPDATE, doc, response); } - SceNpClansError clans_client::retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* page_result) + SceNpClansError clans_client::retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansPagingRequest& paging, std::vector& announcements, SceNpClansPagingResult& page_result) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -1029,39 +1028,39 @@ namespace clan pugi::xml_node clan = doc.append_child("clan"); clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - clan.append_child("start").text().set(paging->startPos); - clan.append_child("max").text().set(paging->max); + clan.append_child("start").text().set(paging.startPos); + clan.append_child("max").text().set(paging.max); pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::RetrieveAnnouncements, ClanManagerOperationType::VIEW, &doc, &response); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::RetrieveAnnouncements, ClanManagerOperationType::VIEW, doc, response); if (clan_res != SCE_NP_CLANS_SUCCESS) return clan_res; - pugi::xml_node clan_result = response.child("clan"); - pugi::xml_node list = clan_result.child("list"); + const pugi::xml_node clan_result = response.child("clan"); + const pugi::xml_node list = clan_result.child("list"); - pugi::xml_attribute results = list.attribute("results"); - uint32_t results_count = results.as_uint(); + const pugi::xml_attribute results = list.attribute("results"); + const uint32_t results_count = results.as_uint(); - pugi::xml_attribute total = list.attribute("total"); - uint32_t total_count = total.as_uint(); + const pugi::xml_attribute total = list.attribute("total"); + const uint32_t total_count = total.as_uint(); int i = 0; for (pugi::xml_node node = list.child("msg-info"); node; node = node.next_sibling("msg-info")) { - pugi::xml_attribute id = node.attribute("id"); - uint32_t msg_id = id.as_uint(); + const pugi::xml_attribute id = node.attribute("id"); + const uint32_t msg_id = id.as_uint(); - std::string subject_str = node.child("subject").text().as_string(); - std::string msg_str = node.child("msg").text().as_string(); - std::string author_jid = node.child("jid").text().as_string(); - std::string msg_date = node.child("msg-date").text().as_string(); + const std::string subject_str = node.child("subject").text().as_string(); + const std::string msg_str = node.child("msg").text().as_string(); + const std::string author_jid = node.child("jid").text().as_string(); + const std::string msg_date = node.child("msg-date").text().as_string(); SceNpId author_npid; - std::string author_username = fmt::split(author_jid, {"@"})[0]; + const std::string author_username = fmt::split(author_jid, {"@"})[0]; - if (strncmp(author_username.c_str(), nph.get_npid().handle.data, 16) == 0) + if (strncmp(author_username.c_str(), nph.get_npid().handle.data, SCE_NET_NP_ONLINEID_MAX_LENGTH) == 0) { author_npid = nph.get_npid(); } @@ -1079,18 +1078,18 @@ namespace clan .subject = "", .body = "", }, - .npid = author_npid, + .npid = std::move(author_npid), .postedBy = clan_id, }; strcpy_trunc(entry.message.subject, subject_str); strcpy_trunc(entry.message.body, msg_str); - announcements[i] = entry; + ::at32(announcements, i) = std::move(entry); i++; } - *page_result = SceNpClansPagingResult + page_result = SceNpClansPagingResult { .count = results_count, .total = total_count @@ -1099,9 +1098,9 @@ namespace clan return SCE_NP_CLANS_SUCCESS; } - SceNpClansError clans_client::post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* announcement, SceNpClansMessageData* /*data*/, u32 duration, SceNpClansMessageId* msg_id) + SceNpClansError clans_client::post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansMessage& announcement, const SceNpClansMessageData& /*data*/, u32 duration, SceNpClansMessageId& msg_id) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -1111,30 +1110,30 @@ namespace clan clan.append_child("id").text().set(clan_id); pugi::xml_node subject = clan.append_child("subject"); - subject.text().set(announcement->subject); + subject.text().set(announcement.subject); pugi::xml_node msg = clan.append_child("msg"); - msg.text().set(announcement->body); + msg.text().set(announcement.body); pugi::xml_node expire_date = clan.append_child("expire-date"); expire_date.text().set(duration); pugi::xml_document response = pugi::xml_document(); - SceNpClansError clan_res = send_request(req_id, ClanRequestAction::PostAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + const SceNpClansError clan_res = send_request(req_id, ClanRequestAction::PostAnnouncement, ClanManagerOperationType::UPDATE, doc, response); if (clan_res != SCE_NP_CLANS_SUCCESS) return clan_res; - pugi::xml_node clan_result = response.child("clan"); - pugi::xml_node msg_id_node = clan_result.child("id"); - *msg_id = msg_id_node.text().as_uint(); + const pugi::xml_node clan_result = response.child("clan"); + const pugi::xml_node msg_id_node = clan_result.child("id"); + msg_id = msg_id_node.text().as_uint(); return SCE_NP_CLANS_SUCCESS; } SceNpClansError clans_client::delete_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessageId announcement_id) { - std::string ticket = get_clan_ticket(nph); + const std::string ticket = get_clan_ticket(nph); if (ticket.empty()) return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; @@ -1145,7 +1144,7 @@ namespace clan clan.append_child("msg-id").text().set(announcement_id); pugi::xml_document response = pugi::xml_document(); - return send_request(req_id, ClanRequestAction::DeleteAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + return send_request(req_id, ClanRequestAction::DeleteAnnouncement, ClanManagerOperationType::UPDATE, doc, response); } } #pragma endregion diff --git a/rpcs3/Emu/NP/clans_client.h b/rpcs3/Emu/NP/clans_client.h index 68ed4a1cf1..d56fb4bbb9 100644 --- a/rpcs3/Emu/NP/clans_client.h +++ b/rpcs3/Emu/NP/clans_client.h @@ -25,15 +25,15 @@ namespace clan }; enum class ClanSearchFilterOperator : u8 - { - Equal, - NotEqual, - GreaterThan, - GreaterThanOrEqual, - LessThan, - LessThanOrEqual, - Like, - }; + { + Equal, + NotEqual, + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, + Like, + }; enum class ClanRequestAction { @@ -70,9 +70,8 @@ namespace clan { private: - static size_t curl_write_callback(void* data, size_t size, size_t nmemb, void* clientp); - SceNpClansError send_request(u32 reqId, ClanRequestAction action, ClanManagerOperationType type, pugi::xml_document* xml_body, pugi::xml_document* out_response); + SceNpClansError send_request(u32 reqId, ClanRequestAction action, ClanManagerOperationType type, const pugi::xml_document& xml_body, pugi::xml_document& out_response); /// @brief Forge and get a V2.1 Ticket for clan operations std::string get_clan_ticket(np::np_handler& nph); @@ -81,43 +80,43 @@ namespace clan clans_client(); ~clans_client(); - SceNpClansError create_request(s32* req_id); + SceNpClansError create_request(s32& req_id); SceNpClansError destroy_request(u32 req_id); - SceNpClansError clan_search(u32 req_id, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clan_list, SceNpClansPagingResult* page_result); + SceNpClansError clan_search(u32 req_id, const SceNpClansPagingRequest& paging, const SceNpClansSearchableName& search, std::vector& clan_list, SceNpClansPagingResult& page_result); SceNpClansError create_clan(np::np_handler& nph, u32 req_id, std::string_view name, std::string_view tag, vm::ptr clan_id); SceNpClansError disband_dlan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); - SceNpClansError get_clan_list(np::np_handler& nph, u32 req_id, SceNpClansPagingRequest* paging, SceNpClansEntry* clan_list, SceNpClansPagingResult* page_result); - SceNpClansError get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo* clan_info); + SceNpClansError get_clan_list(np::np_handler& nph, u32 req_id, const SceNpClansPagingRequest&, std::vector& clan_list, SceNpClansPagingResult& page_result); + SceNpClansError get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo& clan_info); - SceNpClansError get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberEntry* mem_info); - SceNpClansError get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMemberStatus status, SceNpClansMemberEntry* mem_list, SceNpClansPagingResult* page_result); + SceNpClansError get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMemberEntry& mem_info); + SceNpClansError get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansPagingRequest& paging, SceNpClansMemberStatus status, std::vector& mem_list, SceNpClansPagingResult& page_result); - SceNpClansError get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* page_result); - SceNpClansError add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); - SceNpClansError remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + SceNpClansError get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansPagingRequest& paging, std::vector& bl, SceNpClansPagingResult& page_result); + SceNpClansError add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id); + SceNpClansError remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id); - SceNpClansError request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message); + SceNpClansError request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage& message); SceNpClansError cancel_request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); - SceNpClansError send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message, b8 allow); + SceNpClansError send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMessage& message, b8 allow); - SceNpClansError send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message); - SceNpClansError cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); - SceNpClansError send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message, b8 accept); + SceNpClansError send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMessage& message); + SceNpClansError cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id); + SceNpClansError send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage& message, b8 accept); SceNpClansError join_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); SceNpClansError leave_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); - SceNpClansError update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableMemberInfo* info); - SceNpClansError update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableClanInfo* info); + SceNpClansError update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansUpdatableMemberInfo& info); + SceNpClansError update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansUpdatableClanInfo& info); - SceNpClansError kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message); - SceNpClansError change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberRole role); + SceNpClansError kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMessage& message); + SceNpClansError change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMemberRole role); - SceNpClansError retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* page_result); - SceNpClansError post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* announcement, SceNpClansMessageData* data, u32 duration, SceNpClansMessageId* announcement_id); + SceNpClansError retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansPagingRequest& paging, std::vector& announcements, SceNpClansPagingResult& page_result); + SceNpClansError post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansMessage& announcement, const SceNpClansMessageData& data, u32 duration, SceNpClansMessageId& announcement_id); SceNpClansError delete_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessageId announcement_id); }; } // namespace clan diff --git a/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp b/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp index 449900cef1..53f6ce31e7 100644 --- a/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp +++ b/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp @@ -680,79 +680,9 @@ namespace rsx ROP_control_t rop_control{}; alignas(16) fragment_context_t payload{}; - if (REGS(m_ctx)->alpha_test_enabled()) - { - const u32 alpha_func = static_cast(REGS(m_ctx)->alpha_func()); - rop_control.set_alpha_test_func(alpha_func); - rop_control.enable_alpha_test(); - } - - if (REGS(m_ctx)->polygon_stipple_enabled()) - { - rop_control.enable_polygon_stipple(); - } - - auto can_use_hw_a2c = [&]() -> bool - { - const auto& config = RSX(m_ctx)->get_backend_config(); - if (!config.supports_hw_a2c) - { - return false; - } - - if (config.supports_hw_a2c_1spp) - { - return true; - } - - return REGS(m_ctx)->surface_antialias() != rsx::surface_antialiasing::center_1_sample; - }; - - if (REGS(m_ctx)->msaa_alpha_to_coverage_enabled() && !can_use_hw_a2c()) - { - // TODO: Properly support alpha-to-coverage and alpha-to-one behavior in shaders - // Alpha values generate a coverage mask for order independent blending - // Requires hardware AA to work properly (or just fragment sample stage in fragment shaders) - // Simulated using combined alpha blend and alpha test - rop_control.enable_alpha_to_coverage(); - if (REGS(m_ctx)->msaa_sample_mask()) - { - rop_control.enable_MSAA_writes(); - } - - // Sample configuration bits - switch (REGS(m_ctx)->surface_antialias()) - { - case rsx::surface_antialiasing::center_1_sample: - break; - case rsx::surface_antialiasing::diagonal_centered_2_samples: - rop_control.set_msaa_control(1u); - break; - default: - rop_control.set_msaa_control(3u); - break; - } - } - - // Check if framebuffer is actually an XRGB format and not a WZYX format - switch (REGS(m_ctx)->surface_color()) - { - case rsx::surface_color_format::w16z16y16x16: - case rsx::surface_color_format::w32z32y32x32: - case rsx::surface_color_format::x32: - // These behave very differently from "normal" formats. - break; - default: - // Integer framebuffer formats. - rop_control.enable_framebuffer_INT(); - - // Check if we want sRGB conversion. - if (REGS(m_ctx)->framebuffer_srgb_enabled()) - { - rop_control.enable_framebuffer_sRGB(); - } - break; - } + // Always encode the alpha function. Toggling alpha-test is not guaranteed to trigger context param reload anymore. + const u32 alpha_func = static_cast(REGS(m_ctx)->alpha_func()); + rop_control.set_alpha_test_func(alpha_func); // Generate wpos coefficients // wpos equation is now as follows (ignoring pixel center offset): @@ -766,7 +696,6 @@ namespace rsx payload.rop_control = rop_control.value; payload.alpha_ref = REGS(m_ctx)->alpha_ref(); - const auto window_origin = REGS(m_ctx)->shader_window_origin(); const u32 window_height = REGS(m_ctx)->shader_window_height(); const auto pixel_center = REGS(m_ctx)->pixel_center(); diff --git a/rpcs3/Emu/RSX/Core/RSXDriverState.h b/rpcs3/Emu/RSX/Core/RSXDriverState.h index 16dd08c78d..9e7bc2cd68 100644 --- a/rpcs3/Emu/RSX/Core/RSXDriverState.h +++ b/rpcs3/Emu/RSX/Core/RSXDriverState.h @@ -40,6 +40,9 @@ namespace rsx xform_instancing_state_dirty = (1 << 25), // Transform instancing state has changed + zeta_address_is_cyclic = (1 << 26), // The currently bound Z buffer is active for R/W in a cyclic manner + zeta_address_cyclic_barrier = (1 << 27), // A memory barrier is required to "end" the Z buffer cyclic state + // TODO - Should signal that we simply need to do a FP compare before the next draw call and invalidate the ucode if the content has changed. // Marking as dirty to invalidate hot cache also works, it's not like there's tons of barriers per frame anyway. fragment_program_needs_rehash = fragment_program_ucode_dirty, diff --git a/rpcs3/Emu/RSX/GL/GLDraw.cpp b/rpcs3/Emu/RSX/GL/GLDraw.cpp index 51bf257aef..e8ea4bc714 100644 --- a/rpcs3/Emu/RSX/GL/GLDraw.cpp +++ b/rpcs3/Emu/RSX/GL/GLDraw.cpp @@ -715,7 +715,10 @@ void GLGSRender::end() m_frame_stats.textures_upload_time += m_profiler.duration(); gl::command_context cmd{ gl_state }; - if (auto ds = std::get<1>(m_rtts.m_bound_depth_stencil)) ds->write_barrier(cmd); + if (auto ds = std::get<1>(m_rtts.m_bound_depth_stencil)) + { + ds->write_barrier(cmd); + } for (auto &rtt : m_rtts.m_bound_render_targets) { @@ -725,6 +728,8 @@ void GLGSRender::end() } } + m_graphics_state.clear(rsx::zeta_address_cyclic_barrier); + update_draw_state(); if (g_cfg.video.debug_output) diff --git a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp index 4cbd92eecd..f8cc046569 100644 --- a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp @@ -93,18 +93,29 @@ void GLFragmentDecompilerThread::insertOutputs(std::stringstream & OS) { const std::pair table[] = { - { "ocol0", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r0" : "h0" }, - { "ocol1", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r2" : "h4" }, - { "ocol2", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r3" : "h6" }, - { "ocol3", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r4" : "h8" }, + { "ocol0", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r0" : "h0" }, + { "ocol1", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r2" : "h4" }, + { "ocol2", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r3" : "h6" }, + { "ocol3", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r4" : "h8" }, }; - const bool float_type = (m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) || !device_props.has_native_half_support; + const bool float_type = (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) || !device_props.has_native_half_support; const auto reg_type = float_type ? "vec4" : getHalfTypeName(4); for (uint i = 0; i < std::size(table); ++i) { - if (m_parr.HasParam(PF_PARAM_NONE, reg_type, table[i].second)) - OS << "layout(location=" << i << ") out vec4 " << table[i].first << ";\n"; + if (!m_parr.HasParam(PF_PARAM_NONE, reg_type, table[i].second)) + { + continue; + } + + if (i >= m_prog.mrt_buffers_count) + { + // Dead writes. Declare as temp variables for DCE to clean up. + OS << "vec4 " << table[i].first << "; // Unused\n"; + continue; + } + + OS << "layout(location=" << i << ") out vec4 " << table[i].first << ";\n"; } } @@ -190,7 +201,8 @@ void GLFragmentDecompilerThread::insertConstants(std::stringstream & OS) " uvec4 stipple_pattern[8];\n" "};\n\n" - "#define texture_base_index 0\n\n"; + "#define texture_base_index 0\n" + "#define TEX_PARAM(index) texture_parameters[index]\n\n"; } void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) @@ -207,16 +219,24 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_srgb_to_linear = properties.has_upg; m_shader_props.require_linear_to_srgb = properties.has_pkg; m_shader_props.require_fog_read = properties.in_register_mask & in_fogc; - m_shader_props.emulate_coverage_tests = !rsx::get_renderer_backend_config().supports_hw_a2c_1spp; m_shader_props.emulate_shadow_compare = device_props.emulate_depth_compare; + m_shader_props.low_precision_tests = ::gl::get_driver_caps().vendor_NVIDIA && !(m_prog.ctrl & RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION); m_shader_props.disable_early_discard = !::gl::get_driver_caps().vendor_NVIDIA; m_shader_props.supports_native_fp16 = device_props.has_native_half_support; - m_shader_props.ROP_output_rounding = g_cfg.video.shader_precision != gpu_preset_level::low; + + m_shader_props.ROP_output_rounding = (g_cfg.video.shader_precision != gpu_preset_level::low) && !!(m_prog.ctrl & RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER); + m_shader_props.ROP_sRGB_packing = !!(m_prog.ctrl & RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER); + m_shader_props.ROP_alpha_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TEST); + m_shader_props.ROP_alpha_to_coverage_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE); + m_shader_props.ROP_polygon_stipple_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_POLYGON_STIPPLE); + m_shader_props.ROP_discard = !!(m_prog.ctrl & RSX_SHADER_CONTROL_USES_KIL); + m_shader_props.require_tex1D_ops = properties.has_tex1D; m_shader_props.require_tex2D_ops = properties.has_tex2D; m_shader_props.require_tex3D_ops = properties.has_tex3D; m_shader_props.require_shadowProj_ops = properties.shadow_sampler_mask != 0 && properties.has_texShadowProj; + m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL); glsl::insert_glsl_legacy_function(OS, m_shader_props); } @@ -224,7 +244,7 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) void GLFragmentDecompilerThread::insertMainStart(std::stringstream & OS) { std::set output_registers; - if (m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) + if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) { output_registers = { "r0", "r2", "r3", "r4" }; } @@ -233,7 +253,7 @@ void GLFragmentDecompilerThread::insertMainStart(std::stringstream & OS) output_registers = { "h0", "h4", "h6", "h8" }; } - if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) + if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) { output_registers.insert("r1"); } @@ -315,20 +335,28 @@ void GLFragmentDecompilerThread::insertMainEnd(std::stringstream & OS) OS << "\n" << " fs_main();\n\n"; + if (m_prog.ctrl & RSX_SHADER_CONTROL_DISABLE_EARLY_Z) + { + // This is effectively pointless code, but good enough to trick the GPU to skip early Z + OS << + " // Insert pseudo-barrier sequence to disable early-Z\n" + " gl_FragDepth = gl_FragCoord.z;\n\n"; + } + glsl::insert_rop(OS, m_shader_props); - if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) + if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) { if (m_parr.HasParam(PF_PARAM_NONE, "vec4", "r1")) { - //Depth writes are always from a fp32 register. See issues section on nvidia's NV_fragment_program spec - //https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program.txt + // Depth writes are always from a fp32 register. See issues section on nvidia's NV_fragment_program spec + // https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program.txt OS << " gl_FragDepth = r1.z;\n"; } else { - //Input not declared. Leave commented to assist in debugging the shader - OS << " //gl_FragDepth = r1.z;\n"; + // Input not declared. Leave commented to assist in debugging the shader + OS << " // gl_FragDepth = r1.z;\n"; } } diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp index c222262699..4e5e61dcba 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp @@ -301,6 +301,7 @@ void GLGSRender::init_buffers(rsx::framebuffer_creation_context context, bool /* } m_graphics_state.set(rsx::rtt_config_valid); + on_framebuffer_layout_updated(); check_zcull_status(true); set_viewport(); diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index 9606ca7b16..c7f9ec2622 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -300,6 +300,7 @@ namespace gl } builder << "\n" + "#define TEX_PARAM(index) texture_parameters[index + texture_base_index]\n" "#define IS_TEXTURE_RESIDENT(index) (texture_handles[index] < 0xFF)\n" "#define SAMPLER1D(index) sampler1D_array[texture_handles[index]]\n" "#define SAMPLER2D(index) sampler2D_array[texture_handles[index]]\n" diff --git a/rpcs3/Emu/RSX/GL/OpenGL.cpp b/rpcs3/Emu/RSX/GL/OpenGL.cpp index 78241f557a..7381eb990d 100644 --- a/rpcs3/Emu/RSX/GL/OpenGL.cpp +++ b/rpcs3/Emu/RSX/GL/OpenGL.cpp @@ -38,9 +38,6 @@ void gl::init() #ifdef __unix__ glewExperimental = true; glewInit(); -#ifdef HAVE_X11 - glxewInit(); -#endif #endif } diff --git a/rpcs3/Emu/RSX/NV47/HW/nv308a.cpp b/rpcs3/Emu/RSX/NV47/HW/nv308a.cpp index 163ddb9a4c..ab811eb2b9 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv308a.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv308a.cpp @@ -89,27 +89,25 @@ namespace rsx rsx::reservation_lock rsx_lock(dst_address, data_length); if (RSX(ctx)->fifo_ctrl->last_cmd() & RSX_METHOD_NON_INCREMENT_CMD_MASK) [[unlikely]] - { - // Move last 32 bits - reinterpret_cast(dst)[0] = reinterpret_cast(src)[count - 1]; - RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, 4); - } - else { - if (dst_dma & CELL_GCM_LOCATION_MAIN) - { - // May overlap - std::memmove(dst, src, data_length); - } - else - { - // Never overlaps - std::memcpy(dst, src, data_length); - } - - RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, count * 4); + // Move last 32 bits + reinterpret_cast(dst)[0] = reinterpret_cast(src)[count - 1]; + RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, 4); + return; } + if (dst_dma & CELL_GCM_LOCATION_MAIN) + { + // May overlap + std::memmove(dst, src, data_length); + } + else + { + // Never overlaps + std::memcpy(dst, src, data_length); + } + + RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, count * 4); break; } case blit_engine::transfer_destination_format::r5g6b5: @@ -129,33 +127,33 @@ namespace rsx rsx::reservation_lock rsx_lock(dst_address, data_length); auto convert = [](u32 input) -> u16 - { - // Input is considered to be ARGB8 - u32 r = (input >> 16) & 0xFF; - u32 g = (input >> 8) & 0xFF; - u32 b = input & 0xFF; + { + // Input is considered to be ARGB8 + u32 r = (input >> 16) & 0xFF; + u32 g = (input >> 8) & 0xFF; + u32 b = input & 0xFF; - r = (r * 32) / 255; - g = (g * 64) / 255; - b = (b * 32) / 255; - return static_cast((r << 11) | (g << 5) | b); - }; + r = (r * 32) / 255; + g = (g * 64) / 255; + b = (b * 32) / 255; + return static_cast((r << 11) | (g << 5) | b); + }; if (RSX(ctx)->fifo_ctrl->last_cmd() & RSX_METHOD_NON_INCREMENT_CMD_MASK) [[unlikely]] - { - // Move last 16 bits - dst[0] = convert(src[count - 1]); - RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, 2); - break; - } - - for (u32 i = 0; i < count; i++) - { - dst[i] = convert(src[i]); - } - - RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, count * 2); + { + // Move last 16 bits + dst[0] = convert(src[count - 1]); + RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, 2); break; + } + + for (u32 i = 0; i < count; i++) + { + dst[i] = convert(src[i]); + } + + RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, count * 2); + break; } default: { diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp index d0298cf359..929925bcb1 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp @@ -250,8 +250,12 @@ namespace rsx const auto current = REGS(ctx)->decode(arg); const auto previous = REGS(ctx)->decode(REGS(ctx)->latch); - if (*current.antialias() != *previous.antialias() || // Antialias control has changed, update ROP parameters - current.is_integer_color_format() != previous.is_integer_color_format()) // The type of color format also requires ROP control update + if (current.is_integer_color_format() != previous.is_integer_color_format()) // Different ROP emulation + { + RSX(ctx)->m_graphics_state |= rsx::pipeline_state::fragment_program_state_dirty; + } + + if (*current.antialias() != *previous.antialias()) // Antialias control has changed, update ROP parameters { RSX(ctx)->m_graphics_state |= rsx::pipeline_state::fragment_state_dirty; } @@ -302,6 +306,34 @@ namespace rsx REGS(ctx)->decode(reg, REGS(ctx)->latch); } + void set_aa_control(context* ctx, u32 reg, u32 arg) + { + const auto latch = REGS(ctx)->latch; + if (arg == latch) + { + return; + } + + // Reconfigure pipeline. + RSX(ctx)->m_graphics_state |= rsx::pipeline_config_dirty; + + // If we support A2C in hardware, leave the rest upto the hardware. The pipeline config should take care of it. + const auto& backend_config = RSX(ctx)->get_backend_config(); + if (backend_config.supports_hw_a2c && + backend_config.supports_hw_a2c_1spp) + { + return; + } + + // No A2C hardware support or partial hardware support. Invalidate the current program if A2C state changed. + const auto a2c_old = REGS(ctx)->decode(latch).msaa_alpha_to_coverage(); + const auto a2c_new = REGS(ctx)->decode(arg).msaa_alpha_to_coverage(); + if (a2c_old != a2c_new) + { + RSX(ctx)->m_graphics_state |= rsx::fragment_program_state_dirty; + } + } + ///// Draw call setup (vertex, etc) void set_array_element16(context* ctx, u32, u32 arg) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.h b/rpcs3/Emu/RSX/NV47/HW/nv4097.h index db736396b9..a5a434e47d 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.h +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.h @@ -87,6 +87,8 @@ namespace rsx void set_transform_constant_load(context* ctx, u32 reg, u32 arg); + void set_aa_control(context* ctx, u32 reg, u32 arg); + #define RSX(ctx) ctx->rsxthr #define REGS(ctx) (&rsx::method_registers) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp index 4ff3bd7eae..c0e3a72b6f 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp @@ -80,9 +80,17 @@ namespace rsx add_checkbox(&g_cfg.io.keep_pads_connected, localized_string_id::HOME_MENU_SETTINGS_INPUT_KEEP_PADS_CONNECTED); add_checkbox(&g_cfg.io.show_move_cursor, localized_string_id::HOME_MENU_SETTINGS_INPUT_SHOW_PS_MOVE_CURSOR); - if (g_cfg.io.camera == camera_handler::qt) + switch (g_cfg.io.camera) { + #ifdef HAVE_SDL3 + case camera_handler::sdl: + #endif + case camera_handler::qt: add_dropdown(&g_cfg.io.camera_flip_option, localized_string_id::HOME_MENU_SETTINGS_INPUT_CAMERA_FLIP); + break; + case camera_handler::fake: + case camera_handler::null: + break; } add_dropdown(&g_cfg.io.pad_mode, localized_string_id::HOME_MENU_SETTINGS_INPUT_PAD_MODE); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp index cc7affc983..bedbf652c7 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp @@ -8,6 +8,24 @@ #include "Utilities/StrUtil.h" #include "Utilities/Thread.h" +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](rsx::overlays::media_list_dialog::media_type arg) + { + switch (arg) + { + case rsx::overlays::media_list_dialog::media_type::invalid: return "invalid"; + case rsx::overlays::media_list_dialog::media_type::directory: return "directory"; + case rsx::overlays::media_list_dialog::media_type::audio: return "audio"; + case rsx::overlays::media_list_dialog::media_type::video: return "video"; + case rsx::overlays::media_list_dialog::media_type::photo: return "photo"; + } + + return unknown; + }); +} + namespace rsx { namespace overlays @@ -203,7 +221,7 @@ namespace rsx return result; } - s32 media_list_dialog::show(media_entry* root, media_entry& result, const std::string& title, u32 focused, bool enable_overlay) + s32 media_list_dialog::show(std::shared_ptr root, media_entry& result, const std::string& title, u32 focused, bool enable_overlay) { auto ref = g_fxo->get().get(uid); @@ -237,7 +255,7 @@ namespace rsx { focused = 0; ensure(static_cast(return_code) < m_media->children.size()); - m_media = &m_media->children[return_code]; + m_media = m_media->children[return_code]; rsx_log.notice("Media dialog: selected entry: %d ('%s')", return_code, m_media->path); continue; } @@ -287,9 +305,10 @@ namespace rsx m_list = std::make_unique(virtual_width - 2 * 20, 540); m_list->set_pos(20, 85); - for (const media_entry& child : m_media->children) + for (const auto& child : m_media->children) { - std::unique_ptr entry = std::make_unique(child); + ensure(!!child); + std::unique_ptr entry = std::make_unique(*child); m_list->add_entry(entry); } @@ -321,9 +340,11 @@ namespace rsx static constexpr auto thread_name = "MediaList Thread"sv; }; - void parse_media_recursive(u32 depth, const std::string& media_path, const std::string& name, media_list_dialog::media_type type, media_list_dialog::media_entry& current_entry) + void parse_media_recursive(u32 depth, u32 max_depth, const std::string& media_path, const std::string& name, media_list_dialog::media_type type, std::shared_ptr current_entry) { - if (depth++ > music_selection_context::max_depth) + ensure(!!current_entry); + + if (depth++ > max_depth && max_depth != umax) { return; } @@ -339,26 +360,27 @@ namespace rsx const std::string unescaped_name = vfs::unescape(dir_entry.name); - media_list_dialog::media_entry new_entry{}; - parse_media_recursive(depth, media_path + "/" + dir_entry.name, unescaped_name, type, new_entry); - if (new_entry.type != media_list_dialog::media_type::invalid) + auto new_entry = std::make_shared(); + parse_media_recursive(depth, max_depth, media_path + "/" + dir_entry.name, unescaped_name, type, new_entry); + if (new_entry->type != media_list_dialog::media_type::invalid) { - new_entry.parent = ¤t_entry; - new_entry.index = ::narrow(current_entry.children.size()); - current_entry.children.emplace_back(std::move(new_entry)); + rsx_log.notice("parse_media_recursive: found '%s' (type=%s)", dir_entry.name, new_entry->type); + new_entry->parent = current_entry; + new_entry->index = ::narrow(current_entry->children.size()); + current_entry->children.emplace_back(std::move(new_entry)); } } // Only keep directories that contain valid entries - if (current_entry.children.empty()) + if (current_entry->children.empty()) { rsx_log.notice("parse_media_recursive: No matches in directory '%s'", media_path); } else { - rsx_log.notice("parse_media_recursive: Found %d matches in directory '%s'", current_entry.children.size(), media_path); - current_entry.type = media_list_dialog::media_type::directory; - current_entry.info.path = media_path; + rsx_log.notice("parse_media_recursive: Found %d matches in directory '%s'", current_entry->children.size(), media_path); + current_entry->type = media_list_dialog::media_type::directory; + current_entry->info.path = media_path; } } else @@ -370,20 +392,20 @@ namespace rsx auto [success, info] = utils::get_media_info(media_path, av_media_type); if (success) { - current_entry.type = type; - current_entry.info = std::move(info); + current_entry->type = type; + current_entry->info = std::move(info); rsx_log.notice("parse_media_recursive: Found media '%s'", media_path); } } - if (current_entry.type != media_list_dialog::media_type::invalid) + if (current_entry->type != media_list_dialog::media_type::invalid) { - current_entry.path = media_path; - current_entry.name = name; + current_entry->path = media_path; + current_entry->name = name; } } - error_code show_media_list_dialog(media_list_dialog::media_type type, const std::string& path, const std::string& title, std::function on_finished) + error_code show_media_list_dialog(media_list_dialog::media_type type, u32 max_depth, const std::string& path, const std::string& title, std::function on_finished) { rsx_log.todo("show_media_list_dialog(type=%d, path='%s', title='%s', on_finished=%d)", static_cast(type), path, title, !!on_finished); @@ -394,12 +416,12 @@ namespace rsx g_fxo->get>()([=]() { - media_list_dialog::media_entry root_media_entry{}; - root_media_entry.type = media_list_dialog::media_type::directory; + auto root_media_entry = std::make_shared(); + root_media_entry->type = media_list_dialog::media_type::directory; if (fs::is_dir(path)) { - parse_media_recursive(0, path, title, type, root_media_entry); + parse_media_recursive(0, max_depth, path, title, type, root_media_entry); } else { @@ -412,7 +434,7 @@ namespace rsx if (auto manager = g_fxo->try_get()) { - result = manager->create()->show(&root_media_entry, media, title, focused, true); + result = manager->create()->show(root_media_entry, media, title, focused, true); } else { diff --git a/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.h b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.h index 6a5d39b3fa..e2656d728f 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.h @@ -29,8 +29,8 @@ namespace rsx utils::media_info info; u32 index = 0; - media_entry* parent = nullptr; - std::vector children; + std::shared_ptr parent; + std::vector> children; }; media_list_dialog(); @@ -39,7 +39,7 @@ namespace rsx compiled_resource get_compiled() override; - s32 show(media_entry* root, media_entry& result, const std::string& title, u32 focused, bool enable_overlay); + s32 show(std::shared_ptr root, media_entry& result, const std::string& title, u32 focused, bool enable_overlay); private: void reload(const std::string& title, u32 focused); @@ -53,7 +53,7 @@ namespace rsx std::unique_ptr icon_data; }; - media_entry* m_media = nullptr; + std::shared_ptr m_media; std::unique_ptr m_dim_background; std::unique_ptr m_list; @@ -61,6 +61,6 @@ namespace rsx std::unique_ptr @@ -627,6 +627,8 @@ + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 17ccca3792..7e2c398090 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1396,6 +1396,12 @@ Emu\Io + + Emu\NP + + + Emu\NP + @@ -2806,6 +2812,12 @@ Emu\Io + + Emu\NP + + + Emu\NP + diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index 58047671a6..135a61c491 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -102,6 +102,9 @@ void headless_application::InitializeCallbacks() return std::make_shared(); } case camera_handler::qt: +#ifdef HAVE_SDL3 + case camera_handler::sdl: +#endif { fmt::throw_exception("Headless mode can not be used with this camera handler. Current handler: %s", g_cfg.io.camera.get()); } diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index a1bc443cd0..ff3b70f952 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -18,6 +18,7 @@ #include "Emu/Io/Null/NullMouseHandler.h" #include "Emu/Io/KeyboardHandler.h" #include "Emu/Io/MouseHandler.h" +#include "Emu/VFS.h" #include "Input/basic_keyboard_handler.h" #include "Input/basic_mouse_handler.h" #include "Input/raw_mouse_handler.h" @@ -36,6 +37,7 @@ #include "Emu/Audio/FAudio/faudio_enumerator.h" #endif +#include #include // This shouldn't be outside rpcs3qt... #include // This shouldn't be outside rpcs3qt... #include // This shouldn't be outside rpcs3qt... @@ -377,5 +379,33 @@ EmuCallbacks main_application::CreateCallbacks() callbacks.enable_gamemode = [](bool enabled){ enable_gamemode(enabled); }; + callbacks.get_photo_path = [](std::string_view title) + { + const QDateTime date_time = QDateTime::currentDateTime(); + const QDate date = date_time.date(); + const QTime time = date_time.time(); + + std::string_view extension = ".png"; + if (const auto extension_start = title.find_last_of('.'); + extension_start != umax) + { + extension = title.substr(extension_start); + title = title.substr(0, extension_start); + } + + std::string suffix = std::string(extension); + const std::string path = vfs::get(fmt::format("/dev_hdd0/photo/%04d/%02d/%02d/%s %02d-%02d-%04d %02d-%02d-%02d", + date.year(), date.month(), date.day(), vfs::escape(title, true), + date.day(), date.month(), date.year(), time.hour(), time.minute(), time.second())); + + u32 counter = 0; + while (!Emu.IsStopped() && fs::is_file(path + suffix)) + { + suffix = fmt::format(" %d%s", ++counter, extension); + } + + return path + suffix; + }; + return callbacks; } diff --git a/rpcs3/resources.qrc b/rpcs3/resources.qrc index ebace85372..006c71fc8c 100644 --- a/rpcs3/resources.qrc +++ b/rpcs3/resources.qrc @@ -17,5 +17,6 @@ Icons/combo_config_bordered.png rpcs3.svg Icons/DualShock_3.svg + Icons/rpcn.png diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index d8d5053179..9bef8696fe 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -77,7 +77,7 @@ 4577;4467;4281;%(DisableSpecificWarnings) $(IntDir) MaxSpeed - _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_OPENCV;CV_IGNORE_DEBUG_BUILD_GUARD;MINIUPNP_STATICLIB;ZLIB_CONST;AL_LIBTYPE_STATIC;WOLFSSL_USER_SETTINGS;HAVE_SDL3;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_OPENCV;CV_IGNORE_DEBUG_BUILD_GUARD;MINIUPNP_STATICLIB;ZLIB_CONST;AL_LIBTYPE_STATIC;WOLFSSL_USER_SETTINGS;HAVE_SDL3;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;CURL_STATICLIB;%(PreprocessorDefinitions) false $(IntDir)vc$(PlatformToolsetVersion).pdb true @@ -110,7 +110,7 @@ - $(QTDIR)\bin\windeployqt6 --no-compiler-runtime --no-opengl-sw --no-patchqt --no-translations --no-quick --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import --plugindir "$(TargetDir)qt6\plugins" --release "$(TargetPath)" + $(QTDIR)\bin\windeployqt6 --no-compiler-runtime --no-opengl-sw --no-patchqt --no-quick --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import --plugindir "$(TargetDir)qt6\plugins" --translationdir "$(TargetDir)qt6\translations" --release "$(TargetPath)" xcopy /y /d "$(SolutionDir)3rdparty\opencv\opencv\opencv412\build\x64\bin\opencv_world4120.dll" "$(OutDir)" @@ -138,7 +138,7 @@ 4577;4467;4281;%(DisableSpecificWarnings) $(IntDir) Disabled - _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_OPENCV;CV_IGNORE_DEBUG_BUILD_GUARD;MINIUPNP_STATICLIB;ZLIB_CONST;AL_LIBTYPE_STATIC;WOLFSSL_USER_SETTINGS;HAVE_SDL3;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_OPENCV;CV_IGNORE_DEBUG_BUILD_GUARD;MINIUPNP_STATICLIB;ZLIB_CONST;AL_LIBTYPE_STATIC;WOLFSSL_USER_SETTINGS;HAVE_SDL3;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;CURL_STATICLIB;%(PreprocessorDefinitions) false true true @@ -169,7 +169,7 @@ - $(QTDIR)\bin\windeployqt6 --no-compiler-runtime --no-opengl-sw --no-patchqt --no-translations --no-quick --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import --plugindir "$(TargetDir)qt6\plugins" --debug "$(TargetPath)" + $(QTDIR)\bin\windeployqt6 --no-compiler-runtime --no-opengl-sw --no-patchqt --no-quick --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import --plugindir "$(TargetDir)qt6\plugins" --translationdir "$(TargetDir)qt6\translations" --debug "$(TargetPath)" xcopy /y /d "$(SolutionDir)3rdparty\opencv\opencv\opencv412\build\x64\bin\opencv_world4120.dll" "$(OutDir)" @@ -190,6 +190,7 @@ + @@ -200,6 +201,8 @@ + + @@ -944,6 +947,7 @@ + @@ -1068,6 +1072,8 @@ + + @@ -2230,4 +2236,4 @@ - \ No newline at end of file + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 2e9340b01b..5d9f844e63 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -915,9 +915,6 @@ Gui\rpcn - - Gui\clans - Gui\message dialog @@ -1245,6 +1242,18 @@ rpcs3 + + Gui\rpcn + + + Io\camera + + + Io\camera + + + Io\camera + @@ -1475,6 +1484,15 @@ Gui\widgets + + Io\camera + + + Io\camera + + + Io\camera + diff --git a/rpcs3/rpcs3_version.cpp b/rpcs3/rpcs3_version.cpp index ab18b56caf..e7666e7b81 100644 --- a/rpcs3/rpcs3_version.cpp +++ b/rpcs3/rpcs3_version.cpp @@ -28,7 +28,7 @@ namespace rpcs3 // Currently accessible by Windows and Linux build scripts, see implementations when doing MACOSX const utils::version& get_version() { - static constexpr utils::version version{ 0, 0, 38, utils::version_type::alpha, 1, RPCS3_GIT_VERSION }; + static constexpr utils::version version{ 0, 0, 39, utils::version_type::alpha, 1, RPCS3_GIT_VERSION }; return version; } diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 98be856a25..9352f58823 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -146,6 +146,7 @@ add_library(rpcs3_ui STATIC ../Input/basic_keyboard_handler.cpp ../Input/basic_mouse_handler.cpp + ../Input/camera_video_sink.cpp ../Input/ds3_pad_handler.cpp ../Input/ds4_pad_handler.cpp ../Input/dualsense_pad_handler.cpp @@ -162,8 +163,10 @@ add_library(rpcs3_ui STATIC ../Input/ps_move_tracker.cpp ../Input/raw_mouse_config.cpp ../Input/raw_mouse_handler.cpp - ../Input/sdl_pad_handler.cpp + ../Input/sdl_camera_handler.cpp + ../Input/sdl_camera_video_sink.cpp ../Input/sdl_instance.cpp + ../Input/sdl_pad_handler.cpp ../Input/skateboard_pad_handler.cpp ../Input/xinput_pad_handler.cpp diff --git a/rpcs3/rpcs3qt/camera_settings_dialog.cpp b/rpcs3/rpcs3qt/camera_settings_dialog.cpp index da3576183a..9bbac21ea4 100644 --- a/rpcs3/rpcs3qt/camera_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/camera_settings_dialog.cpp @@ -3,11 +3,19 @@ #include "ui_camera_settings_dialog.h" #include "permissions.h" #include "Emu/Io/camera_config.h" +#include "Emu/System.h" +#include "Emu/system_config.h" #include #include #include #include +#include + +#ifdef HAVE_SDL3 +#include "Input/sdl_instance.h" +#include "Input/sdl_camera_handler.h" +#endif LOG_CHANNEL(camera_log, "Camera"); @@ -53,6 +61,81 @@ void fmt_class_string::format(std::string& out, }); } +#ifdef HAVE_SDL3 +static QString sdl_pixelformat_to_string(SDL_PixelFormat format) +{ + switch (format) + { + case SDL_PixelFormat::SDL_PIXELFORMAT_UNKNOWN: return "UNKNOWN"; + case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX1LSB: return "INDEX1LSB"; + case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX1MSB: return "INDEX1MSB"; + case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX2LSB: return "INDEX2LSB"; + case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX2MSB: return "INDEX2MSB"; + case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX4LSB: return "INDEX4LSB"; + case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX4MSB: return "INDEX4MSB"; + case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX8: return "INDEX8"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGB332: return "RGB332"; + case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB4444: return "XRGB4444"; + case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR4444: return "XBGR4444"; + case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB1555: return "XRGB1555"; + case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR1555: return "XBGR1555"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB4444: return "ARGB4444"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA4444: return "RGBA4444"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR4444: return "ABGR4444"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA4444: return "BGRA4444"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB1555: return "ARGB1555"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA5551: return "RGBA5551"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR1555: return "ABGR1555"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA5551: return "BGRA5551"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGB565: return "RGB565"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGR565: return "BGR565"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGB24: return "RGB24"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGR24: return "BGR24"; + case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB8888: return "XRGB8888"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGBX8888: return "RGBX8888"; + case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR8888: return "XBGR8888"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGRX8888: return "BGRX8888"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB8888: return "ARGB8888"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA8888: return "RGBA8888"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR8888: return "ABGR8888"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA8888: return "BGRA8888"; + case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB2101010: return "XRGB2101010"; + case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR2101010: return "XBGR2101010"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB2101010: return "ARGB2101010"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR2101010: return "ABGR2101010"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGB48: return "RGB48"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGR48: return "BGR48"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA64: return "RGBA64"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB64: return "ARGB64"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA64: return "BGRA64"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR64: return "ABGR64"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGB48_FLOAT: return "RGB48_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGR48_FLOAT: return "BGR48_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA64_FLOAT: return "RGBA64_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB64_FLOAT: return "ARGB64_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA64_FLOAT: return "BGRA64_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR64_FLOAT: return "ABGR64_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGB96_FLOAT: return "RGB96_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGR96_FLOAT: return "BGR96_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA128_FLOAT: return "RGBA128_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB128_FLOAT: return "ARGB128_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA128_FLOAT: return "BGRA128_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR128_FLOAT: return "ABGR128_FLOAT"; + case SDL_PixelFormat::SDL_PIXELFORMAT_YV12: return "YV12"; + case SDL_PixelFormat::SDL_PIXELFORMAT_IYUV: return "IYUV"; + case SDL_PixelFormat::SDL_PIXELFORMAT_YUY2: return "YUY2"; + case SDL_PixelFormat::SDL_PIXELFORMAT_UYVY: return "UYVY"; + case SDL_PixelFormat::SDL_PIXELFORMAT_YVYU: return "YVYU"; + case SDL_PixelFormat::SDL_PIXELFORMAT_NV12: return "NV12"; + case SDL_PixelFormat::SDL_PIXELFORMAT_NV21: return "NV21"; + case SDL_PixelFormat::SDL_PIXELFORMAT_P010: return "P010"; + case SDL_PixelFormat::SDL_PIXELFORMAT_EXTERNAL_OES: return "EXTERNAL_OES"; + case SDL_PixelFormat::SDL_PIXELFORMAT_MJPG: return "MJPG"; + default: return QObject::tr("Unknown: %0").arg(static_cast(format)); + } +} +#endif + Q_DECLARE_METATYPE(QCameraDevice); camera_settings_dialog::camera_settings_dialog(QWidget* parent) @@ -61,15 +144,16 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent) { ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + load_config(); - for (const QCameraDevice& camera_info : QMediaDevices::videoInputs()) - { - if (camera_info.isNull()) continue; - ui->combo_camera->addItem(camera_info.description(), QVariant::fromValue(camera_info)); - camera_log.notice("Found camera: '%s'", camera_info.description()); - } + ui->combo_handlers->addItem("Qt", QVariant::fromValue(static_cast(camera_handler::qt))); +#ifdef HAVE_SDL3 + ui->combo_handlers->addItem("SDL", QVariant::fromValue(static_cast(camera_handler::sdl))); +#endif + connect(ui->combo_handlers, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_handler_change); connect(ui->combo_camera, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_camera_change); connect(ui->combo_settings, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_settings_change); connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) @@ -85,33 +169,183 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent) } }); - if (ui->combo_camera->count() == 0) - { - ui->combo_camera->setEnabled(false); - ui->combo_settings->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); - } - else + const int handler_index = ui->combo_handlers->findData(static_cast(g_cfg.io.camera.get())); + ui->combo_handlers->setCurrentIndex(std::max(0, handler_index)); +} + +camera_settings_dialog::~camera_settings_dialog() +{ + reset_cameras(); +} + +void camera_settings_dialog::enable_combos() +{ + const bool is_enabled = ui->combo_camera->count() > 0; + + ui->combo_camera->setEnabled(is_enabled); + ui->combo_settings->setEnabled(is_enabled); + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(is_enabled); + ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(is_enabled); + + if (is_enabled) { // TODO: show camera ID somewhere ui->combo_camera->setCurrentIndex(0); } } -camera_settings_dialog::~camera_settings_dialog() +void camera_settings_dialog::reset_cameras() { + m_media_capture_session.reset(); + m_camera.reset(); + +#ifdef HAVE_SDL3 + m_video_frame_input.reset(); + + if (m_sdl_thread) + { + auto& thread = *m_sdl_thread; + thread = thread_state::aborting; + thread(); + m_sdl_thread.reset(); + } + + if (m_sdl_camera) + { + SDL_CloseCamera(m_sdl_camera); + m_sdl_camera = nullptr; + } +#endif +} + +void camera_settings_dialog::handle_handler_change(int index) +{ + reset_cameras(); + + if (index < 0 || !ui->combo_handlers->itemData(index).canConvert()) + { + ui->combo_settings->clear(); + ui->combo_camera->clear(); + enable_combos(); + return; + } + + m_handler = static_cast(ui->combo_handlers->itemData(index).value()); + + ui->combo_settings->blockSignals(true); + ui->combo_camera->blockSignals(true); + + ui->combo_settings->clear(); + ui->combo_camera->clear(); + + switch (m_handler) + { + case camera_handler::qt: + { + for (const QCameraDevice& camera_info : QMediaDevices::videoInputs()) + { + if (camera_info.isNull()) continue; + ui->combo_camera->addItem(camera_info.description(), QVariant::fromValue(camera_info)); + camera_log.notice("Found camera: '%s'", camera_info.description()); + } + break; + } +#ifdef HAVE_SDL3 + case camera_handler::sdl: + { + if (!sdl_instance::get_instance().initialize()) + { + camera_log.error("Could not initialize SDL"); + break; + } + + // Log camera drivers + sdl_camera_handler::get_drivers(); + + // Get cameras + const std::map cameras = sdl_camera_handler::get_cameras(); + + // Add cameras + for (const auto& [camera_id, name] : cameras) + { + ui->combo_camera->addItem(QString::fromStdString(name), QVariant::fromValue(static_cast(camera_id))); + } + break; + } +#endif + default: + fmt::throw_exception("Unexpected camera handler %d", static_cast(m_handler)); + } + + ui->combo_settings->blockSignals(false); + ui->combo_camera->blockSignals(false); + + enable_combos(); } void camera_settings_dialog::handle_camera_change(int index) { - if (index < 0 || !ui->combo_camera->itemData(index).canConvert()) + if (index < 0) { ui->combo_settings->clear(); return; } - const QCameraDevice camera_info = ui->combo_camera->itemData(index).value(); + reset_cameras(); + + switch (m_handler) + { + case camera_handler::qt: + handle_qt_camera_change(ui->combo_camera->itemData(index)); + break; +#ifdef HAVE_SDL3 + case camera_handler::sdl: + handle_sdl_camera_change(ui->combo_camera->itemText(index), ui->combo_camera->itemData(index)); + break; +#endif + default: + fmt::throw_exception("Unexpected camera handler %d", static_cast(m_handler)); + } +} + +void camera_settings_dialog::handle_settings_change(int index) +{ + if (index < 0) + { + return; + } + + if (!gui::utils::check_camera_permission(this, + [this, index](){ handle_settings_change(index); }, + [this](){ QMessageBox::warning(this, tr("Camera permissions denied!"), tr("RPCS3 has no permissions to access cameras on this device.")); })) + { + return; + } + + switch (m_handler) + { + case camera_handler::qt: + handle_qt_settings_change(ui->combo_settings->itemData(index)); + break; +#ifdef HAVE_SDL3 + case camera_handler::sdl: + handle_sdl_settings_change(ui->combo_settings->itemData(index)); + break; +#endif + default: + fmt::throw_exception("Unexpected camera handler %d", static_cast(m_handler)); + } +} + +void camera_settings_dialog::handle_qt_camera_change(const QVariant& item_data) +{ + if (!item_data.canConvert()) + { + ui->combo_settings->clear(); + return; + } + + const QCameraDevice camera_info = item_data.value(); if (camera_info.isNull()) { @@ -119,10 +353,10 @@ void camera_settings_dialog::handle_camera_change(int index) return; } - m_camera.reset(new QCamera(camera_info)); - m_media_capture_session.reset(new QMediaCaptureSession(nullptr)); + m_camera = std::make_unique(camera_info); + m_media_capture_session = std::make_unique(nullptr); m_media_capture_session->setCamera(m_camera.get()); - m_media_capture_session->setVideoSink(ui->videoWidget->videoSink()); + m_media_capture_session->setVideoOutput(ui->videoWidget); if (!m_camera->isAvailable()) { @@ -173,14 +407,14 @@ void camera_settings_dialog::handle_camera_change(int index) int index = 0; bool success = false; const std::string key = camera_info.id().toStdString(); - cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(key, success); + const cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::qt), key, success); if (success) { camera_log.notice("Found config entry for camera \"%s\"", key); - // Select matching drowdown entry - const double epsilon = 0.001; + // Select matching dropdown entry + constexpr double epsilon = 0.001; for (int i = 0; i < ui->combo_settings->count(); i++) { @@ -202,19 +436,10 @@ void camera_settings_dialog::handle_camera_change(int index) ui->combo_settings->setCurrentIndex(std::max(0, index)); ui->combo_settings->setEnabled(true); - - // Update config to match user interface outcome - const QCameraFormat setting = ui->combo_settings->currentData().value(); - cfg_setting.width = setting.resolution().width(); - cfg_setting.height = setting.resolution().height(); - cfg_setting.min_fps = setting.minFrameRate(); - cfg_setting.max_fps = setting.maxFrameRate(); - cfg_setting.format = static_cast(setting.pixelFormat()); - g_cfg_camera.set_camera_setting(key, cfg_setting); } } -void camera_settings_dialog::handle_settings_change(int index) +void camera_settings_dialog::handle_qt_settings_change(const QVariant& item_data) { if (!m_camera) { @@ -227,33 +452,251 @@ void camera_settings_dialog::handle_settings_change(int index) return; } - if (!gui::utils::check_camera_permission(this, - [this, index](){ handle_settings_change(index); }, - [this](){ QMessageBox::warning(this, tr("Camera permissions denied!"), tr("RPCS3 has no permissions to access cameras on this device.")); })) + if (item_data.canConvert() && ui->combo_camera->currentData().canConvert()) { - return; - } - - if (index >= 0 && ui->combo_settings->itemData(index).canConvert() && ui->combo_camera->currentData().canConvert()) - { - const QCameraFormat setting = ui->combo_settings->itemData(index).value(); + const QCameraFormat setting = item_data.value(); if (!setting.isNull()) { m_camera->setCameraFormat(setting); } - cfg_camera::camera_setting cfg_setting; + cfg_camera::camera_setting cfg_setting {}; cfg_setting.width = setting.resolution().width(); cfg_setting.height = setting.resolution().height(); cfg_setting.min_fps = setting.minFrameRate(); cfg_setting.max_fps = setting.maxFrameRate(); cfg_setting.format = static_cast(setting.pixelFormat()); - g_cfg_camera.set_camera_setting(ui->combo_camera->currentData().value().id().toStdString(), cfg_setting); + cfg_setting.colorspace = 0; + g_cfg_camera.set_camera_setting(fmt::format("%s", camera_handler::qt), ui->combo_camera->currentData().value().id().toStdString(), cfg_setting); } m_camera->start(); } +#ifdef HAVE_SDL3 +void camera_settings_dialog::handle_sdl_camera_change(const QString& name, const QVariant& item_data) +{ + if (!item_data.canConvert()) + { + ui->combo_settings->clear(); + return; + } + + const u32 camera_id = item_data.value(); + + if (!camera_id) + { + ui->combo_settings->clear(); + return; + } + + ui->combo_settings->blockSignals(true); + ui->combo_settings->clear(); + + std::vector settings; + + int num_formats = 0; + if (SDL_CameraSpec** specs = SDL_GetCameraSupportedFormats(camera_id, &num_formats)) + { + if (num_formats <= 0) + { + camera_log.error("No SDL camera specs found"); + } + else + { + for (int i = 0; i < num_formats; i++) + { + if (!specs[i]) continue; + settings.push_back(*specs[i]); + } + } + SDL_free(specs); + } + else + { + camera_log.error("No SDL camera specs found. SDL Error: %s", SDL_GetError()); + } + + std::sort(settings.begin(), settings.end(), [](const SDL_CameraSpec& l, const SDL_CameraSpec& r) -> bool + { + const f32 l_fps = l.framerate_numerator / static_cast(l.framerate_denominator); + const f32 r_fps = r.framerate_numerator / static_cast(r.framerate_denominator); + + if (l.width > r.width) return true; + if (l.width < r.width) return false; + if (l.height > r.height) return true; + if (l.height < r.height) return false; + if (l_fps > r_fps) return true; + if (l_fps < r_fps) return false; + if (l.format > r.format) return true; + if (l.format < r.format) return false; + if (l.colorspace > r.colorspace) return true; + if (l.colorspace < r.colorspace) return false; + return false; + }); + + for (const SDL_CameraSpec& setting : settings) + { + const f32 fps = setting.framerate_numerator / static_cast(setting.framerate_denominator); + const QString description = tr("%0x%1, %2 FPS, Format=%3") + .arg(setting.width) + .arg(setting.height) + .arg(fps) + .arg(sdl_pixelformat_to_string(setting.format)); + ui->combo_settings->addItem(description, QVariant::fromValue(setting)); + } + ui->combo_settings->blockSignals(false); + + if (ui->combo_settings->count() == 0) + { + ui->combo_settings->setEnabled(false); + return; + } + + // Load selected settings from config file + int index = 0; + bool success = false; + cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::sdl), name.toStdString(), success); + + if (success) + { + camera_log.notice("Found config entry for camera \"%s\"", name); + + // Select matching dropdown entry + constexpr double epsilon = 0.001; + + for (int i = 0; i < ui->combo_settings->count(); i++) + { + const QVariant var = ui->combo_settings->itemData(i); + if (!var.canConvert()) + { + camera_log.error("Failed to convert itemData to SDL_CameraSpec"); + continue; + } + + const SDL_CameraSpec tmp = var.value(); + const f32 fps = tmp.framerate_numerator / static_cast(tmp.framerate_denominator); + + if (tmp.width == cfg_setting.width && + tmp.height == cfg_setting.height && + fps >= (cfg_setting.min_fps - epsilon) && + fps <= (cfg_setting.min_fps + epsilon) && + fps >= (cfg_setting.max_fps - epsilon) && + fps <= (cfg_setting.max_fps + epsilon) && + tmp.format == static_cast(cfg_setting.format) && + tmp.colorspace == static_cast(cfg_setting.colorspace)) + { + index = i; + break; + } + } + } + + m_sdl_camera_id = camera_id; + + ui->combo_settings->setCurrentIndex(std::max(0, index)); + ui->combo_settings->setEnabled(true); +} + +void camera_settings_dialog::handle_sdl_settings_change(const QVariant& item_data) +{ + reset_cameras(); + + if (item_data.canConvert()) + { + // TODO: SDL converts the image for us. We would have to do this manually if we want to use other formats. + const SDL_CameraSpec setting = item_data.value(); + const SDL_CameraSpec used_spec + { + .format = SDL_PixelFormat::SDL_PIXELFORMAT_RGBA32, + .colorspace = SDL_Colorspace::SDL_COLORSPACE_RGB_DEFAULT, + .width = setting.width, + .height = setting.height, + .framerate_numerator = setting.framerate_numerator, + .framerate_denominator = setting.framerate_denominator + }; + + m_sdl_camera = SDL_OpenCamera(m_sdl_camera_id, &used_spec); + + m_video_frame_input = std::make_unique(); + + m_media_capture_session = std::make_unique(nullptr); + m_media_capture_session->setVideoFrameInput(m_video_frame_input.get()); + m_media_capture_session->setVideoOutput(ui->videoWidget); + + connect(this, &camera_settings_dialog::sdl_frame_ready, m_video_frame_input.get(), [this]() + { + // It was observed that connecting sendVideoFrame directly can soft-lock the software. + // So let's just create the video frame here and call it manually. + std::unique_lock lock(m_sdl_image_mutex, std::defer_lock); + if (lock.try_lock() && m_video_frame_input && !m_sdl_image.isNull()) + { + const QVideoFrame video_frame(m_sdl_image); + if (video_frame.isValid()) + { + m_video_frame_input->sendVideoFrame(video_frame); + } + } + }); + + const f32 fps = setting.framerate_numerator / static_cast(setting.framerate_denominator); + + cfg_camera::camera_setting cfg_setting {}; + cfg_setting.width = setting.width; + cfg_setting.height = setting.height; + cfg_setting.min_fps = fps; + cfg_setting.max_fps = fps; + cfg_setting.format = static_cast(setting.format); + cfg_setting.colorspace = static_cast(setting.colorspace); + g_cfg_camera.set_camera_setting(fmt::format("%s", camera_handler::sdl), ui->combo_camera->currentText().toStdString(), cfg_setting); + } + + if (!m_sdl_camera) + { + camera_log.error("Failed to open SDL camera %d. SDL Error: %s", m_sdl_camera_id, SDL_GetError()); + QMessageBox::warning(this, tr("Camera not available"), tr("The selected camera is not available.\nIt might be blocked by another application.")); + return; + } + + m_sdl_thread = std::make_unique>>("GUI SDL Capture Thread", [this](){ run_sdl(); }); +} + +void camera_settings_dialog::run_sdl() +{ + camera_log.notice("GUI SDL Capture Thread started"); + + while (thread_ctrl::state() != thread_state::aborting) + { + // Copy latest image into out buffer. + u64 timestamp_ns = 0; + SDL_Surface* frame = SDL_AcquireCameraFrame(m_sdl_camera, ×tamp_ns); + if (!frame) + { + // No new frame + thread_ctrl::wait_for(1000); + continue; + } + + { + // Map image + const QImage::Format format = SDL_ISPIXELFORMAT_ALPHA(frame->format) ? QImage::Format_RGBA8888 : QImage::Format_RGB888; + const QImage image = QImage(reinterpret_cast(frame->pixels), frame->w, frame->h, format); + + // Copy image to prevent memory access violations + { + std::lock_guard lock(m_sdl_image_mutex); + m_sdl_image = image.copy(); + } + + // Notify UI + Q_EMIT sdl_frame_ready(); + } + + SDL_ReleaseCameraFrame(m_sdl_camera, frame); + } +} +#endif + void camera_settings_dialog::load_config() { if (!g_cfg_camera.load()) diff --git a/rpcs3/rpcs3qt/camera_settings_dialog.h b/rpcs3/rpcs3qt/camera_settings_dialog.h index da18f64cca..879686722f 100644 --- a/rpcs3/rpcs3qt/camera_settings_dialog.h +++ b/rpcs3/rpcs3qt/camera_settings_dialog.h @@ -1,8 +1,25 @@ #pragma once +#include "Emu/system_config_types.h" +#include "Utilities/Thread.h" + #include #include #include +#include + +#include + +#ifdef HAVE_SDL3 +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +#include "SDL3/SDL.h" +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif +#endif namespace Ui { @@ -17,15 +34,40 @@ public: camera_settings_dialog(QWidget* parent = nullptr); virtual ~camera_settings_dialog(); +Q_SIGNALS: + void sdl_frame_ready(); + private Q_SLOTS: + void handle_handler_change(int index); void handle_camera_change(int index); void handle_settings_change(int index); private: + void enable_combos(); + void reset_cameras(); + void load_config(); void save_config(); + void handle_qt_camera_change(const QVariant& item_data); + void handle_qt_settings_change(const QVariant& item_data); + +#ifdef HAVE_SDL3 + void handle_sdl_camera_change(const QString& name, const QVariant& item_data); + void handle_sdl_settings_change(const QVariant& item_data); + + void run_sdl(); + + SDL_Camera* m_sdl_camera = nullptr; + SDL_CameraID m_sdl_camera_id = 0; + QImage m_sdl_image; + std::mutex m_sdl_image_mutex; + std::unique_ptr>> m_sdl_thread; + std::unique_ptr m_video_frame_input; +#endif + std::unique_ptr ui; std::unique_ptr m_camera; std::unique_ptr m_media_capture_session; + camera_handler m_handler = camera_handler::qt; }; diff --git a/rpcs3/rpcs3qt/camera_settings_dialog.ui b/rpcs3/rpcs3qt/camera_settings_dialog.ui index 8afe262f22..dfdf6beed2 100644 --- a/rpcs3/rpcs3qt/camera_settings_dialog.ui +++ b/rpcs3/rpcs3qt/camera_settings_dialog.ui @@ -15,7 +15,23 @@ - + + + + + Handler + + + + + + No handlers found + + + + + + @@ -75,10 +91,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Save + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save diff --git a/rpcs3/rpcs3qt/cheat_manager.cpp b/rpcs3/rpcs3qt/cheat_manager.cpp index a24a2cd5fc..fa8d0b4d58 100644 --- a/rpcs3/rpcs3qt/cheat_manager.cpp +++ b/rpcs3/rpcs3qt/cheat_manager.cpp @@ -129,16 +129,27 @@ void cheat_engine::save() const cheat_file.write(out.c_str(), out.size()); } -void cheat_engine::import_cheats_from_str(std::string_view str_cheats) +bool cheat_engine::import_cheats_from_str(std::string_view str_cheats) { const auto cheats_vec = fmt::split_sv(str_cheats, {"^^^"}); + std::vector valid_cheats; + for (const auto& cheat_line : cheats_vec) { cheat_info new_cheat; - if (new_cheat.from_str(cheat_line)) - cheats[new_cheat.game][new_cheat.offset] = new_cheat; + if (!new_cheat.from_str(cheat_line)) + return false; + + valid_cheats.push_back(std::move(new_cheat)); } + + for (const cheat_info& new_cheat : valid_cheats) + { + cheats[new_cheat.game][new_cheat.offset] = new_cheat; + } + + return true; } std::string cheat_engine::export_cheats_to_str() const @@ -677,7 +688,7 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent) { const int row = sel->row(); - if (rows.count(row)) + if (rows.contains(row)) continue; g_cheat.erase(tbl_cheats->item(row, cheat_table_columns::title)->text().toStdString(), tbl_cheats->item(row, cheat_table_columns::offset)->data(Qt::UserRole).toUInt()); @@ -690,7 +701,11 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent) connect(import_cheats, &QAction::triggered, [this]() { QClipboard* clipboard = QGuiApplication::clipboard(); - g_cheat.import_cheats_from_str(clipboard->text().toStdString()); + if (!g_cheat.import_cheats_from_str(clipboard->text().toStdString())) + { + QMessageBox::warning(this, tr("Failure"), tr("Failed to import cheats.")); + return; + } update_cheat_list(); }); diff --git a/rpcs3/rpcs3qt/cheat_manager.h b/rpcs3/rpcs3qt/cheat_manager.h index b915faa8a2..c09f47428f 100644 --- a/rpcs3/rpcs3qt/cheat_manager.h +++ b/rpcs3/rpcs3qt/cheat_manager.h @@ -25,7 +25,7 @@ public: cheat_info* get(const std::string& game, const u32 offset); bool erase(const std::string& game, const u32 offset); - void import_cheats_from_str(std::string_view str_cheats); + bool import_cheats_from_str(std::string_view str_cheats); std::string export_cheats_to_str() const; void save() const; diff --git a/rpcs3/rpcs3qt/curl_handle.h b/rpcs3/rpcs3qt/curl_handle.h index e0d4db22d3..090c198215 100644 --- a/rpcs3/rpcs3qt/curl_handle.h +++ b/rpcs3/rpcs3qt/curl_handle.h @@ -2,9 +2,6 @@ #include -#ifndef CURL_STATICLIB -#define CURL_STATICLIB -#endif #include namespace rpcs3::curl diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 935bfd2cc8..20bb58ba93 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -1119,6 +1119,9 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case camera_handler::null: return tr("Null", "Camera handler"); case camera_handler::fake: return tr("Fake", "Camera handler"); case camera_handler::qt: return tr("Qt", "Camera handler"); +#ifdef HAVE_SDL3 + case camera_handler::sdl: return tr("SDL", "Camera handler"); +#endif } break; case emu_settings_type::MusicHandler: diff --git a/rpcs3/rpcs3qt/emulated_logitech_g27_settings_dialog.cpp b/rpcs3/rpcs3qt/emulated_logitech_g27_settings_dialog.cpp index a65f62648b..5955e91b72 100644 --- a/rpcs3/rpcs3qt/emulated_logitech_g27_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/emulated_logitech_g27_settings_dialog.cpp @@ -18,7 +18,7 @@ #include #include -LOG_CHANNEL(logitech_g27_cfg_log, "LOGIG27"); +LOG_CHANNEL(logitech_g27_cfg_log, "logitech_g27"); static const QString DEFAULT_STATUS = " "; @@ -57,9 +57,11 @@ enum class mapping_device DIAL_CLOCKWISE, DIAL_ANTICLOCKWISE, + DIAL_CENTER, SELECT, - PAUSE, + START, + PS, SHIFTER_1, SHIFTER_2, @@ -68,7 +70,6 @@ enum class mapping_device SHIFTER_5, SHIFTER_6, SHIFTER_R, - SHIFTER_PRESS, // Enum count COUNT @@ -97,12 +98,14 @@ QString device_name(mapping_device dev) case mapping_device::L3: return QObject::tr("L3"); case mapping_device::R2: return QObject::tr("R2"); case mapping_device::R3: return QObject::tr("R3"); - case mapping_device::PLUS: return QObject::tr("L4"); - case mapping_device::MINUS: return QObject::tr("L5"); - case mapping_device::DIAL_CLOCKWISE: return QObject::tr("R4"); - case mapping_device::DIAL_ANTICLOCKWISE: return QObject::tr("R5"); + case mapping_device::PLUS: return QObject::tr("L4\nPlus"); + case mapping_device::MINUS: return QObject::tr("L5\nMinus"); + case mapping_device::DIAL_CLOCKWISE: return QObject::tr("R4\nDial CW"); + case mapping_device::DIAL_ANTICLOCKWISE: return QObject::tr("R5\nDial CCW"); + case mapping_device::DIAL_CENTER: return QObject::tr("Dial Center"); case mapping_device::SELECT: return QObject::tr("Select"); - case mapping_device::PAUSE: return QObject::tr("Pause"); + case mapping_device::START: return QObject::tr("Start"); + case mapping_device::PS: return QObject::tr("PS"); case mapping_device::SHIFTER_1: return QObject::tr("Gear 1"); case mapping_device::SHIFTER_2: return QObject::tr("Gear 2"); case mapping_device::SHIFTER_3: return QObject::tr("Gear 3"); @@ -110,7 +113,6 @@ QString device_name(mapping_device dev) case mapping_device::SHIFTER_5: return QObject::tr("Gear 5"); case mapping_device::SHIFTER_6: return QObject::tr("Gear 6"); case mapping_device::SHIFTER_R: return QObject::tr("Gear R"); - case mapping_device::SHIFTER_PRESS: return QObject::tr("Shifter press"); case mapping_device::COUNT: return ""; } return ""; @@ -144,8 +146,10 @@ emulated_logitech_g27_mapping& device_cfg(mapping_device dev) case mapping_device::MINUS: return cfg.minus; case mapping_device::DIAL_CLOCKWISE: return cfg.dial_clockwise; case mapping_device::DIAL_ANTICLOCKWISE: return cfg.dial_anticlockwise; + case mapping_device::DIAL_CENTER: return cfg.dial_center; case mapping_device::SELECT: return cfg.select; - case mapping_device::PAUSE: return cfg.pause; + case mapping_device::START: return cfg.start; + case mapping_device::PS: return cfg.ps; case mapping_device::SHIFTER_1: return cfg.shifter_1; case mapping_device::SHIFTER_2: return cfg.shifter_2; case mapping_device::SHIFTER_3: return cfg.shifter_3; @@ -153,7 +157,6 @@ emulated_logitech_g27_mapping& device_cfg(mapping_device dev) case mapping_device::SHIFTER_5: return cfg.shifter_5; case mapping_device::SHIFTER_6: return cfg.shifter_6; case mapping_device::SHIFTER_R: return cfg.shifter_r; - case mapping_device::SHIFTER_PRESS: return cfg.shifter_press; default: fmt::throw_exception("Unexpected mapping_device %d", static_cast(dev)); } } @@ -165,6 +168,7 @@ public: : QWidget(parent) { auto layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); QLabel* label = new QLabel(this); @@ -245,6 +249,7 @@ public: QWidget* horizontal_container = new QWidget(this); QHBoxLayout* horizontal_layout = new QHBoxLayout(horizontal_container); + horizontal_layout->setContentsMargins(0, 0, 0, 0); horizontal_container->setLayout(horizontal_layout); layout->addWidget(horizontal_container); @@ -542,6 +547,7 @@ void emulated_logitech_g27_settings_dialog::save_ui_state_to_config() g_cfg_logitech_g27.enabled.set(m_enabled->isChecked()); g_cfg_logitech_g27.reverse_effects.set(m_reverse_effects->isChecked()); + g_cfg_logitech_g27.compatibility_limit.set(m_compatibility_limit->currentData().toInt()); if (m_ffb_device->get_device_choice() == mapping_device::NONE) { @@ -591,6 +597,7 @@ void emulated_logitech_g27_settings_dialog::load_ui_state_from_config() m_enabled->setChecked(g_cfg_logitech_g27.enabled.get()); m_reverse_effects->setChecked(g_cfg_logitech_g27.reverse_effects.get()); + m_compatibility_limit->setCurrentIndex(4 - g_cfg_logitech_g27.compatibility_limit.get()); } emulated_logitech_g27_settings_dialog::emulated_logitech_g27_settings_dialog(QWidget* parent) @@ -651,6 +658,18 @@ emulated_logitech_g27_settings_dialog::emulated_logitech_g27_settings_dialog(QWi m_reverse_effects = new QCheckBox(tr("Reverse force feedback effects"), this); v_layout->addWidget(m_reverse_effects); + QHBoxLayout* compat_layout = new QHBoxLayout(this); + compat_layout->setContentsMargins(0, 0, 0, 0); + QLabel* compatibility_label = new QLabel(tr("Compatibility limit:"), this); + compat_layout->addWidget(compatibility_label); + m_compatibility_limit = new QComboBox(this); + m_compatibility_limit->addItem(tr("G27 (Default)"), static_cast(logitech_personality::g27)); + m_compatibility_limit->addItem(tr("Driving Force GT"), static_cast(logitech_personality::driving_force_gt)); + m_compatibility_limit->addItem(tr("G25"), static_cast(logitech_personality::g25)); + m_compatibility_limit->addItem(tr("Driving Force Pro"), static_cast(logitech_personality::driving_force_pro)); + compat_layout->addWidget(m_compatibility_limit); + v_layout->addLayout(compat_layout); + m_state_text = new QLabel(DEFAULT_STATUS, this); v_layout->addWidget(m_state_text); diff --git a/rpcs3/rpcs3qt/emulated_logitech_g27_settings_dialog.h b/rpcs3/rpcs3qt/emulated_logitech_g27_settings_dialog.h index f06f1308c1..efd5cb589d 100644 --- a/rpcs3/rpcs3qt/emulated_logitech_g27_settings_dialog.h +++ b/rpcs3/rpcs3qt/emulated_logitech_g27_settings_dialog.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #ifndef _MSC_VER @@ -57,6 +58,7 @@ private: QCheckBox* m_enabled = nullptr; QCheckBox* m_reverse_effects = nullptr; + QComboBox* m_compatibility_limit = nullptr; std::map m_mappings; diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index f68bc44896..677b61a44c 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -2501,13 +2501,11 @@ void game_list_frame::BatchCreateCPUCaches(const std::vector& game_da if (total == 0) { QMessageBox::information(this, tr("LLVM Cache Batch Creation"), tr("No titles found"), QMessageBox::Ok); - Q_EMIT NotifyBatchedGameActionFinished(); return; } if (!m_gui_settings->GetBootConfirmation(this)) { - Q_EMIT NotifyBatchedGameActionFinished(); return; } @@ -2531,7 +2529,6 @@ void game_list_frame::BatchCreateCPUCaches(const std::vector& game_da { if (serial.empty()) { - Q_EMIT NotifyBatchedGameActionFinished(); return false; } diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index c31e6aa66a..bcdd643ca2 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -96,7 +96,6 @@ Q_SIGNALS: void FocusToSearchBar(); void Refreshed(); void RequestSaveStateManager(const game_info& game); - void NotifyBatchedGameActionFinished(); public: template diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index df6aac317a..483f5affdc 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -1040,7 +1040,7 @@ void gs_frame::take_screenshot(std::vector&& data, u32 sshot_width, u32 ssho } } - const std::string cell_sshot_filename = manager.get_screenshot_path(date_time.toString("yyyy/MM/dd").toStdString()); + const std::string cell_sshot_filename = Emu.GetCallbacks().get_photo_path(manager.get_photo_title() + ".png"); const std::string cell_sshot_dir = fs::get_parent_dir(cell_sshot_filename); screenshot_log.notice("Saving cell screenshot to %s", cell_sshot_filename); diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index e028aaefa8..113f5a6ebb 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -20,6 +20,10 @@ #include "_discord_utils.h" #endif +#ifdef HAVE_SDL3 +#include "Input/sdl_camera_handler.h" +#endif + #include "Emu/Audio/audio_utils.h" #include "Emu/Cell/Modules/cellSysutil.h" #include "Emu/Io/Null/null_camera_handler.h" @@ -216,29 +220,74 @@ bool gui_application::Init() return true; } -void gui_application::SwitchTranslator(QTranslator& translator, const QString& filename, const QString& language_code) +void gui_application::SwitchTranslator(const QString& language_code) { // remove the old translator - removeTranslator(&translator); + removeTranslator(&m_translator); + for (QTranslator* qt_translator : m_qt_translators) + { + removeTranslator(qt_translator); + qt_translator->deleteLater(); + } + m_qt_translators.clear(); + const QString default_code = QLocale(QLocale::English).bcp47Name(); const QString lang_path = QLibraryInfo::path(QLibraryInfo::TranslationsPath) + QStringLiteral("/"); - const QString file_path = lang_path + filename; + // Load qt translation files + const QDir dir(lang_path); + if (dir.exists()) + { + QStringList qm_files = dir.entryList(QStringList() << QStringLiteral("qt*_%1.qm").arg(language_code), QDir::Files | QDir::Readable); + if (qm_files.empty()) + { + qm_files = dir.entryList(QStringList() << QStringLiteral("qt*_%1.qm").arg(QLocale::languageToCode(QLocale(language_code).language())), QDir::Files | QDir::Readable); + } + + for (const QString& qm_file : qm_files) + { + const QString file_path = lang_path + qm_file; + QTranslator* qt_translator = new QTranslator(this); + + if (qt_translator->load(file_path)) + { + gui_log.notice("Installing translation: '%s'", file_path); + installTranslator(qt_translator); + m_qt_translators.push_back(std::move(qt_translator)); + } + else + { + gui_log.error("Failed to load translation: '%s'", file_path); + qt_translator->deleteLater(); + } + } + } + else + { + gui_log.error("Qt translation dir '%s' does not exist", lang_path); + } + + const QString file_path = lang_path + QStringLiteral("rpcs3_%1.qm").arg(language_code); if (QFileInfo(file_path).isFile()) { // load the new translator - if (translator.load(file_path)) + if (m_translator.load(file_path)) { - installTranslator(&translator); + gui_log.notice("Installing translation: '%s'", file_path); + installTranslator(&m_translator); + } + else + { + gui_log.error("Failed to load translation: '%s'", file_path); } } - else if (QString default_code = QLocale(QLocale::English).bcp47Name(); language_code != default_code) + else if (language_code != default_code) { // show error, but ignore default case "en", since it is handled in source code - gui_log.error("No translation file found in: %s", file_path); + gui_log.error("No translation file found in: '%s'", file_path); // reset current language to default "en" - set_language_code(std::move(default_code)); + set_language_code(default_code); } } @@ -260,7 +309,7 @@ void gui_application::LoadLanguage(const QString& language_code) // As per QT recommendations to avoid conflicts for POSIX functions std::setlocale(LC_NUMERIC, "C"); - SwitchTranslator(m_translator, QStringLiteral("rpcs3_%1.qm").arg(language_code), language_code); + SwitchTranslator(language_code); if (m_main_window) { @@ -285,6 +334,7 @@ QStringList gui_application::GetAvailableLanguageCodes() QStringList language_codes; const QString language_path = QLibraryInfo::path(QLibraryInfo::TranslationsPath); + gui_log.notice("Checking languages in '%s'", language_path); if (QFileInfo(language_path).isDir()) { @@ -303,10 +353,15 @@ QStringList gui_application::GetAvailableLanguageCodes() } else { + gui_log.notice("Found language '%s' (%s)", language_code, filename); language_codes << language_code; } } } + else + { + gui_log.error("Language dir not found: '%s'", language_path); + } return language_codes; } @@ -645,6 +700,12 @@ void gui_application::InitializeCallbacks() { return std::make_shared(); } +#ifdef HAVE_SDL3 + case camera_handler::sdl: + { + return std::make_shared(); + } +#endif } return nullptr; }; diff --git a/rpcs3/rpcs3qt/gui_application.h b/rpcs3/rpcs3qt/gui_application.h index bec8424da4..948965ef47 100644 --- a/rpcs3/rpcs3qt/gui_application.h +++ b/rpcs3/rpcs3qt/gui_application.h @@ -81,7 +81,7 @@ private: return thread(); } - void SwitchTranslator(QTranslator& translator, const QString& filename, const QString& language_code); + void SwitchTranslator(const QString& language_code); void LoadLanguage(const QString& language_code); static QStringList GetAvailableLanguageCodes(); @@ -101,6 +101,7 @@ private: } m_native_event_filter; + std::vector m_qt_translators; QTranslator m_translator; QString m_language_code; static s32 m_language_id; diff --git a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp index 61932f4cd1..8a4ef3e930 100644 --- a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp +++ b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp @@ -31,7 +31,7 @@ static const std::map, const std::string> li {{0x12, 0x30}, "Kamen Rider Wizard Fire"}, {{0x13, 0x40}, "Kamen Rider Fourze Light"}, {{0x14, 0x20}, "Kamen Rider 000 Water"}, - {{0x15, 0x10}, "Kamen Rider Double Wind"}, + {{0x15, 0x10}, "Kamen Rider W (Double) Wind"}, {{0x16, 0x50}, "Kamen Rider Decade Dark"}, {{0x17, 0x50}, "Kamen Rider Kiva Dark"}, {{0x18, 0x40}, "Kamen Rider Den-O Light"}, @@ -409,7 +409,7 @@ void kamen_rider_dialog::update_edits() } else { - display_string = QString(tr("Unknown (Id:%1 Var:%2)")).arg(fig_id).arg(fig_type); + display_string = QString(tr("Unknown (Id:%1 Type:%2)")).arg(fig_id).arg(fig_type); } } else diff --git a/rpcs3/rpcs3qt/log_frame.cpp b/rpcs3/rpcs3qt/log_frame.cpp index a155cf215d..1d2739608d 100644 --- a/rpcs3/rpcs3qt/log_frame.cpp +++ b/rpcs3/rpcs3qt/log_frame.cpp @@ -293,7 +293,7 @@ void log_frame::CreateAndConnectActions() }); }); - m_perform_goto_on_debugger = new QAction(tr("Go-To On The Debugger"), this); + m_perform_goto_on_debugger = new QAction(tr("Go-To on Debugger"), this); connect(m_perform_goto_on_debugger, &QAction::triggered, [this]() { QPlainTextEdit* pte = (m_tabWidget->currentIndex() == 1 ? m_tty : m_log); @@ -315,7 +315,7 @@ void log_frame::CreateAndConnectActions() memory_viewer_panel::ShowAtPC(static_cast(pc)); }); - m_perform_goto_thread_on_debugger = new QAction(tr("Show Thread On The Debugger"), this); + m_perform_goto_thread_on_debugger = new QAction(tr("Show Thread on Debugger"), this); connect(m_perform_goto_thread_on_debugger, &QAction::triggered, [this]() { QPlainTextEdit* pte = (m_tabWidget->currentIndex() == 1 ? m_tty : m_log); @@ -433,12 +433,13 @@ void log_frame::CreateAndConnectActions() m_perform_goto_thread_on_debugger->setToolTip(tr("Show the thread that corresponds to the thread ID from the log text on the debugger.")); m_perform_show_in_mem_viewer->setToolTip(tr("Jump to the selected hexadecimal address from the log text on the memory viewer.")); - menu->addSeparator(); - menu->addActions(m_log_level_acts->actions()); menu->addSeparator(); menu->addAction(m_stack_act_log); menu->addAction(m_stack_act_err); menu->addAction(m_show_prefix_act); + menu->addSeparator(); + menu->addActions(m_log_level_acts->actions()); + menu->exec(m_log->viewport()->mapToGlobal(pos)); }); diff --git a/rpcs3/rpcs3qt/log_viewer.cpp b/rpcs3/rpcs3qt/log_viewer.cpp index ef45025b58..99961809c9 100644 --- a/rpcs3/rpcs3qt/log_viewer.cpp +++ b/rpcs3/rpcs3qt/log_viewer.cpp @@ -114,24 +114,19 @@ void log_viewer::show_context_menu(const QPoint& pos) init_action(trace_act, logs::level::trace); menu.addAction(copy); + menu.addAction(clear); + menu.addSeparator(); menu.addAction(open); - menu.addSeparator(); + menu.addAction(config); + menu.addAction(filter); menu.addAction(save); menu.addSeparator(); - menu.addAction(config); - menu.addSeparator(); - menu.addAction(filter); - menu.addSeparator(); menu.addAction(timestamps); - menu.addSeparator(); menu.addAction(threads); - menu.addSeparator(); menu.addAction(last_actions_only); menu.addSeparator(); menu.addActions(log_level_acts->actions()); - menu.addSeparator(); - menu.addAction(clear); connect(copy, &QAction::triggered, this, [this]() { diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 168d08a344..fea8dc8ca4 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -801,7 +801,7 @@ bool main_window::InstallPackages(QStringList file_paths, bool from_boot) if (file_paths.count() == 1) { - const QString file_path = file_paths.front(); + const QString& file_path = file_paths.front(); const QFileInfo file_info(file_path); if (file_info.isDir()) @@ -819,92 +819,6 @@ bool main_window::InstallPackages(QStringList file_paths, bool from_boot) return InstallPackages(dir_file_paths, from_boot); } - - if (file_info.suffix().compare("pkg", Qt::CaseInsensitive) == 0) - { - compat::package_info info = game_compatibility::GetPkgInfo(file_path, m_game_list_frame ? m_game_list_frame->GetGameCompatibility() : nullptr); - - if (!info.is_valid) - { - QMessageBox::warning(this, tr("Invalid package!"), tr("The selected package is invalid!\n\nPath:\n%0").arg(file_path)); - return false; - } - - if (info.type != compat::package_type::other) - { - if (info.type == compat::package_type::dlc) - { - info.local_cat = tr("\nDLC", "Block for package type (DLC)"); - } - else - { - info.local_cat = tr("\nUpdate", "Block for package type (Update)"); - } - } - else if (!info.local_cat.isEmpty()) - { - info.local_cat = tr("\n%0", "Block for package type").arg(info.local_cat); - } - - if (!info.title_id.isEmpty()) - { - info.title_id = tr("\n%0", "Block for Title ID").arg(info.title_id); - } - - if (!info.version.isEmpty()) - { - info.version = tr("\nVersion %0", "Block for Version").arg(info.version); - } - - if (!info.changelog.isEmpty()) - { - info.changelog = tr("Changelog:\n%0", "Block for Changelog").arg(info.changelog); - } - - u64 free_space = 0; - - // Retrieve disk space info on data path's drive - if (fs::device_stat stat{}; fs::statfs(rpcs3::utils::get_hdd0_dir(), stat)) - { - free_space = stat.avail_free; - } - - const QString installation_info = - tr("Installation path: %0\nAvailable disk space: %1%2\nRequired disk space: %3") - .arg(QString::fromStdString(rpcs3::utils::get_hdd0_game_dir())) - .arg(gui::utils::format_byte_size(free_space)) - .arg(info.data_size <= free_space ? QString() : tr(" - NOT ENOUGH SPACE")) - .arg(gui::utils::format_byte_size(info.data_size)); - - const QString info_string = QStringLiteral("%0\n\n%1%2%3%4").arg(file_info.fileName()).arg(info.title).arg(info.local_cat).arg(info.title_id).arg(info.version); - QString message = tr("Do you want to install this package?\n\n%0\n\n%1").arg(info_string).arg(installation_info); - - QMessageBox mb(QMessageBox::Icon::Question, tr("PKG Decrypter / Installer"), gui::utils::make_paragraph(message), QMessageBox::Yes | QMessageBox::No, this); - mb.setDefaultButton(QMessageBox::No); - mb.setTextFormat(Qt::RichText); // Support HTML tags - mb.button(QMessageBox::Yes)->setEnabled(info.data_size <= free_space); - - if (!info.changelog.isEmpty()) - { - mb.setInformativeText(tr("To see the changelog, please click \"Show Details\".")); - mb.setDetailedText(info.changelog); - - // Smartass hack to make the unresizeable message box wide enough for the changelog - const int log_width = QLabel(info.changelog).sizeHint().width(); - while (QLabel(message).sizeHint().width() < log_width) - { - message += " "; - } - - mb.setText(message); - } - - if (mb.exec() != QMessageBox::Yes) - { - gui_log.notice("PKG: Cancelled installation from drop.\n%s\n%s", info_string, info.changelog); - return true; - } - } } // Install rap files if available @@ -983,28 +897,22 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo } std::vector packages; + bool precompile_caches = false; + bool create_desktop_shortcuts = false; + bool create_app_shortcut = false; game_compatibility* compat = m_game_list_frame ? m_game_list_frame->GetGameCompatibility() : nullptr; - if (file_paths.size() > 1) + // Let the user choose the packages to install and select the order in which they shall be installed. + pkg_install_dialog dlg(file_paths, compat, this); + connect(&dlg, &QDialog::accepted, this, [&]() { - // Let the user choose the packages to install and select the order in which they shall be installed. - pkg_install_dialog dlg(file_paths, compat, this); - connect(&dlg, &QDialog::accepted, this, [&packages, &dlg]() - { - packages = dlg.GetPathsToInstall(); - }); - dlg.exec(); - } - else - { - packages.push_back(game_compatibility::GetPkgInfo(file_paths.front(), compat)); - } - - if (packages.empty()) - { - return true; - } + packages = dlg.get_paths_to_install(); + precompile_caches = dlg.precompile_caches(); + create_desktop_shortcuts = dlg.create_desktop_shortcuts(); + create_app_shortcut = dlg.create_app_shortcut(); + }); + dlg.exec(); if (!from_boot) { @@ -1178,9 +1086,9 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo if (!bootable_paths_installed.empty()) { - m_game_list_frame->AddRefreshedSlot([this, paths = std::move(bootable_paths_installed)](std::set& claimed_paths) mutable + m_game_list_frame->AddRefreshedSlot([this, create_desktop_shortcuts, precompile_caches, create_app_shortcut, paths = std::move(bootable_paths_installed)](std::set& claimed_paths) mutable { - // Try to claim operaions on ID + // Try to claim operations on ID for (auto it = paths.begin(); it != paths.end();) { std::string resolved_path = Emu.GetCallbacks().resolve_path(it->first); @@ -1196,13 +1104,12 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo } } - // Executes after PrecompileCachesFromInstalledPackages - m_notify_batch_game_action_cb = [this, paths]() mutable - { - ShowOptionalGamePreparations(tr("Success!"), tr("Successfully installed software from package(s)!"), std::move(paths)); - }; + CreateShortCuts(paths, create_desktop_shortcuts, create_app_shortcut); - PrecompileCachesFromInstalledPackages(paths); + if (precompile_caches) + { + PrecompileCachesFromInstalledPackages(paths); + } }); } @@ -1870,6 +1777,7 @@ void main_window::RepaintToolBarIcons() ui->toolbar_grid ->setIcon(icon(":/Icons/grid.png")); ui->toolbar_list ->setIcon(icon(":/Icons/list.png")); ui->toolbar_refresh ->setIcon(icon(":/Icons/refresh.png")); + ui->toolbar_rpcn ->setIcon(icon(":/Icons/rpcn.png")); ui->toolbar_stop ->setIcon(icon(":/Icons/stop.png")); ui->sysStopAct->setIcon(icon(":/Icons/stop.png")); @@ -2402,6 +2310,8 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri QVBoxLayout* vlayout = new QVBoxLayout(dlg); + QCheckBox* precompile_check = new QCheckBox(tr("Precompile caches")); + precompile_check->setChecked(true); QCheckBox* desk_check = new QCheckBox(tr("Add desktop shortcut(s)")); #ifdef _WIN32 QCheckBox* quick_check = new QCheckBox(tr("Add Start menu shortcut(s)")); @@ -2410,10 +2320,12 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri #else QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)")); #endif - QLabel* label = new QLabel(tr("%1\nWould you like to install shortcuts to the installed software? (%2 new software detected)\n\n").arg(message).arg(bootable_paths.size()), dlg); + QLabel* label = new QLabel(tr("%1\nWould you like to precompile caches and install shortcuts to the installed software? (%2 new software detected)\n\n").arg(message).arg(bootable_paths.size()), dlg); vlayout->addWidget(label); vlayout->addStretch(10); + vlayout->addWidget(precompile_check); + vlayout->addStretch(3); vlayout->addWidget(desk_check); vlayout->addStretch(3); vlayout->addWidget(quick_check); @@ -2426,32 +2338,46 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri connect(btn_box, &QDialogButtonBox::accepted, this, [=, this, paths = std::move(bootable_paths)]() { + const bool precompile_caches = precompile_check->isChecked(); const bool create_desktop_shortcuts = desk_check->isChecked(); const bool create_app_shortcut = quick_check->isChecked(); dlg->hide(); dlg->accept(); - std::set locations; + CreateShortCuts(paths, create_desktop_shortcuts, create_app_shortcut); + + if (precompile_caches) + { + PrecompileCachesFromInstalledPackages(paths); + } + }); + + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->open(); +} + +void main_window::CreateShortCuts(const std::map& paths, bool create_desktop_shortcuts, bool create_app_shortcut) +{ + if (paths.empty()) return; + + std::set locations; #ifdef _WIN32 - locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts); + locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts); #endif - if (create_desktop_shortcuts) - { - locations.insert(gui::utils::shortcut_location::desktop); - } + if (create_desktop_shortcuts) + { + locations.insert(gui::utils::shortcut_location::desktop); + } - if (create_app_shortcut) - { - locations.insert(gui::utils::shortcut_location::applications); - } - - if (locations.empty()) - { - return; - } + if (create_app_shortcut) + { + locations.insert(gui::utils::shortcut_location::applications); + } + if (!locations.empty()) + { std::vector game_data_shortcuts; for (const auto& [boot_path, title_id] : paths) @@ -2477,13 +2403,9 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri { m_game_list_frame->CreateShortcuts(game_data_shortcuts, locations); } - }); - - dlg->setAttribute(Qt::WA_DeleteOnClose); - dlg->open(); + } } - void main_window::PrecompileCachesFromInstalledPackages(const std::map& bootable_paths) { std::vector game_data; @@ -2538,8 +2460,8 @@ void main_window::CreateActions() m_icon_size_act_group->addAction(ui->setIconSizeLargeAct); m_list_mode_act_group = new QActionGroup(this); - m_list_mode_act_group->addAction(ui->setlistModeListAct); - m_list_mode_act_group->addAction(ui->setlistModeGridAct); + m_list_mode_act_group->addAction(ui->setListModeAct); + m_list_mode_act_group->addAction(ui->setGridModeAct); } void main_window::CreateConnects() @@ -2871,6 +2793,7 @@ void main_window::CreateConnects() connect(ui->confAudioAct, &QAction::triggered, this, [open_settings]() { open_settings(2); }); connect(ui->confIOAct, &QAction::triggered, this, [open_settings]() { open_settings(3); }); connect(ui->confSystemAct, &QAction::triggered, this, [open_settings]() { open_settings(4); }); + connect(ui->confNetwrkAct, &QAction::triggered, this, [open_settings]() { open_settings(5); }); connect(ui->confAdvAct, &QAction::triggered, this, [open_settings]() { open_settings(6); }); connect(ui->confEmuAct, &QAction::triggered, this, [open_settings]() { open_settings(7); }); connect(ui->confGuiAct, &QAction::triggered, this, [open_settings]() { open_settings(8); }); @@ -2982,15 +2905,17 @@ void main_window::CreateConnects() connect(ui->confCamerasAct, &QAction::triggered, this, [this]() { - camera_settings_dialog dlg(this); - dlg.exec(); + camera_settings_dialog* dlg = new camera_settings_dialog(this); + dlg->open(); }); - connect(ui->confRPCNAct, &QAction::triggered, this, [this]() + const auto open_rpcn_settings = [this] { rpcn_settings_dialog dlg(this); dlg.exec(); - }); + }; + + connect(ui->confRPCNAct, &QAction::triggered, this, open_rpcn_settings); connect(ui->confClansAct, &QAction::triggered, this, [this]() { @@ -2999,7 +2924,7 @@ void main_window::CreateConnects() QMessageBox::critical(this, tr("Error: Emulation Running"), tr("You need to stop the emulator before editing Clans connection information!"), QMessageBox::Ok); return; } - + clans_settings_dialog dlg(this); dlg.exec(); }); @@ -3410,7 +3335,7 @@ void main_window::CreateConnects() connect(m_list_mode_act_group, &QActionGroup::triggered, this, [this](QAction* act) { - const bool is_list_act = act == ui->setlistModeListAct; + const bool is_list_act = act == ui->setListModeAct; if (is_list_act == m_is_list_mode) return; @@ -3448,10 +3373,11 @@ void main_window::CreateConnects() } }); - connect(ui->toolbar_controls, &QAction::triggered, this, open_pad_settings); connect(ui->toolbar_config, &QAction::triggered, this, [=]() { open_settings(0); }); - connect(ui->toolbar_list, &QAction::triggered, this, [this]() { ui->setlistModeListAct->trigger(); }); - connect(ui->toolbar_grid, &QAction::triggered, this, [this]() { ui->setlistModeGridAct->trigger(); }); + connect(ui->toolbar_controls, &QAction::triggered, this, open_pad_settings); + connect(ui->toolbar_rpcn, &QAction::triggered, this, open_rpcn_settings); + connect(ui->toolbar_list, &QAction::triggered, this, [this]() { ui->setListModeAct->trigger(); }); + connect(ui->toolbar_grid, &QAction::triggered, this, [this]() { ui->setGridModeAct->trigger(); }); connect(ui->sizeSlider, &QSlider::valueChanged, this, &main_window::ResizeIcons); connect(ui->sizeSlider, &QSlider::sliderReleased, this, [this] @@ -3471,15 +3397,6 @@ void main_window::CreateConnects() connect(ui->mw_searchbar, &QLineEdit::textChanged, m_game_list_frame, &game_list_frame::SetSearchText); connect(ui->mw_searchbar, &QLineEdit::returnPressed, m_game_list_frame, &game_list_frame::FocusAndSelectFirstEntryIfNoneIs); connect(m_game_list_frame, &game_list_frame::FocusToSearchBar, this, [this]() { ui->mw_searchbar->setFocus(); }); - - connect(m_game_list_frame, &game_list_frame::NotifyBatchedGameActionFinished, this, [this]() mutable - { - if (m_notify_batch_game_action_cb) - { - m_notify_batch_game_action_cb(); - m_notify_batch_game_action_cb = {}; - } - }); } void main_window::CreateDockWindows() @@ -3710,9 +3627,9 @@ void main_window::ConfigureGuiFromSettings() // handle icon size options if (m_is_list_mode) - ui->setlistModeListAct->setChecked(true); + ui->setListModeAct->setChecked(true); else - ui->setlistModeGridAct->setChecked(true); + ui->setGridModeAct->setChecked(true); const int icon_size_index = m_gui_settings->GetValue(m_is_list_mode ? gui::gl_iconSize : gui::gl_iconSizeGrid).toInt(); m_other_slider_pos = m_gui_settings->GetValue(!m_is_list_mode ? gui::gl_iconSize : gui::gl_iconSizeGrid).toInt(); diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 960a70c722..8a82c125ce 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -49,7 +49,6 @@ class main_window : public QMainWindow bool m_save_slider_pos = false; bool m_requested_show_logs_on_exit = false; int m_other_slider_pos = 0; - std::function m_notify_batch_game_action_cb; QIcon m_app_icon; QIcon m_icon_play; @@ -149,6 +148,7 @@ private: static bool InstallFileInExData(const std::string& extension, const QString& path, const std::string& filename); bool HandlePackageInstallation(QStringList file_paths, bool from_boot); + void CreateShortCuts(const std::map& paths, bool create_desktop_shortcuts, bool create_app_shortcut); void HandlePupInstallation(const QString& file_path, const QString& dir_path = ""); void ExtractPup(); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index c9c8c0645d..39a3c014bb 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -218,7 +218,6 @@ - @@ -243,9 +242,25 @@ Configuration - + - USB Devices + Mice + + + + + + + Devices + + + + + + + + + Emulated Devices @@ -259,33 +274,21 @@ - - - Mice - - - - - - - - - - + - - - + + + @@ -296,18 +299,35 @@ - + + + Network Services + + + + + + + + + Portals and Gates + + + + + + + + + - - - - + + - @@ -315,24 +335,23 @@ - - - - - - + + - - + + + + + @@ -353,12 +372,18 @@ - + - Game List Mode + Game List View - - + + + + + + Game List in Grid Mode + + @@ -383,15 +408,14 @@ - - - - - + + + + @@ -403,11 +427,10 @@ - - - + + @@ -472,6 +495,7 @@ + @@ -600,6 +624,14 @@ Configure system + + + Network + + + Configure Network settings + + Advanced @@ -637,7 +669,7 @@ - Exit And Save Log + Exit and Save Log Exit RPCS3, move the log file to a custom location @@ -794,12 +826,12 @@ true - Show Game Compatibility in Grid Mode + Show Game Compatibility - Game List Refresh + Refresh Game List @@ -817,7 +849,7 @@ - View The Welcome Dialog + View Welcome Dialog @@ -873,7 +905,7 @@ Large - + true @@ -881,15 +913,15 @@ true - List View + List Mode - + true - Grid View + Grid Mode @@ -1012,6 +1044,18 @@ Configure the emulator + + + + :/Icons/rpcn.png:/Icons/rpcn.png + + + RPCN + + + Configure RPCN settings + + diff --git a/rpcs3/rpcs3qt/movie_item_base.cpp b/rpcs3/rpcs3qt/movie_item_base.cpp index fdf820ae5f..669aec515d 100644 --- a/rpcs3/rpcs3qt/movie_item_base.cpp +++ b/rpcs3/rpcs3qt/movie_item_base.cpp @@ -16,8 +16,8 @@ movie_item_base::~movie_item_base() void movie_item_base::init_pointers() { - m_icon_loading_aborted.reset(new atomic_t(false)); - m_size_on_disk_loading_aborted.reset(new atomic_t(false)); + m_icon_loading_aborted = std::make_shared>(false); + m_size_on_disk_loading_aborted = std::make_shared>(false); } void movie_item_base::call_icon_load_func(int index) diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.cpp b/rpcs3/rpcs3qt/pkg_install_dialog.cpp index 370f4d053c..9b0f926484 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.cpp +++ b/rpcs3/rpcs3qt/pkg_install_dialog.cpp @@ -7,12 +7,11 @@ #include "Emu/system_utils.hpp" #include "Utilities/File.h" -#include #include #include #include -#include #include +#include enum Roles { @@ -105,8 +104,6 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil item->setData(Roles::VersionRole, info.version); item->setData(Roles::DataSizeRole, static_cast(info.data_size)); item->setToolTip(tooltip); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(Qt::Checked); } m_dir_list->sortItems(); @@ -134,42 +131,79 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil } }); - connect(m_dir_list, &QListWidget::itemChanged, this, [this, installation_info, buttons](QListWidgetItem*) + QHBoxLayout* hbox = nullptr; + if (m_dir_list->count() > 1) { - UpdateInfo(installation_info, buttons); - }); + for (int i = 0; i < m_dir_list->count(); i++) + { + if (QListWidgetItem* item = m_dir_list->item(i)) + { + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Checked); + } + } + + connect(m_dir_list, &QListWidget::itemChanged, this, [this, installation_info, buttons](QListWidgetItem*) + { + update_info(installation_info, buttons); + }); - QToolButton* move_up = new QToolButton; - move_up->setArrowType(Qt::UpArrow); - move_up->setToolTip(tr("Move selected item up")); - connect(move_up, &QToolButton::clicked, this, [this]() { MoveItem(-1); }); + QToolButton* move_up = new QToolButton; + move_up->setArrowType(Qt::UpArrow); + move_up->setToolTip(tr("Move selected item up")); + connect(move_up, &QToolButton::clicked, this, [this]() { move_item(-1); }); - QToolButton* move_down = new QToolButton; - move_down->setArrowType(Qt::DownArrow); - move_down->setToolTip(tr("Move selected item down")); - connect(move_down, &QToolButton::clicked, this, [this]() { MoveItem(1); }); + QToolButton* move_down = new QToolButton; + move_down->setArrowType(Qt::DownArrow); + move_down->setToolTip(tr("Move selected item down")); + connect(move_down, &QToolButton::clicked, this, [this]() { move_item(1); }); - QHBoxLayout* hbox = new QHBoxLayout; - hbox->addStretch(); - hbox->addWidget(move_up); - hbox->addWidget(move_down); + hbox = new QHBoxLayout; + hbox->addStretch(); + hbox->addWidget(move_up); + hbox->addWidget(move_down); + } - QLabel* description = new QLabel(tr("You are about to install multiple packages.\nReorder and/or exclude them if needed, then click \"Install\" to proceed.")); + QLabel* description = new QLabel(m_dir_list->count() == 1 + ? tr("Do you want to install this package?") + : tr("You are about to install multiple packages.\nReorder and/or exclude them if needed, then click \"Install\" to proceed.") + ); + QLabel* label = new QLabel(tr("Would you like to precompile caches and install shortcuts to the installed software?")); + + QCheckBox* precompile_check = new QCheckBox(tr("Precompile caches")); + connect(precompile_check, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state){ m_precompile_caches = state != Qt::CheckState::Unchecked; }); + precompile_check->setChecked(true); + + QCheckBox* desk_check = new QCheckBox(tr("Add desktop shortcut(s)")); + connect(desk_check, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state){ m_create_desktop_shortcuts = state != Qt::CheckState::Unchecked; }); + +#ifdef _WIN32 + QCheckBox* quick_check = new QCheckBox(tr("Add Start menu shortcut(s)")); +#elif defined(__APPLE__) + QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)")); +#else + QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)")); +#endif + connect(quick_check, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state){ m_create_app_shortcut = state != Qt::CheckState::Unchecked; }); QVBoxLayout* vbox = new QVBoxLayout; vbox->addWidget(description); - vbox->addLayout(hbox); + if (hbox) vbox->addLayout(hbox); vbox->addWidget(m_dir_list); + vbox->addWidget(label); + vbox->addWidget(precompile_check); + vbox->addWidget(desk_check); + vbox->addWidget(quick_check); vbox->addWidget(installation_info); vbox->addWidget(buttons); setLayout(vbox); - setWindowTitle(tr("Batch PKG Installation")); + setWindowTitle(tr("PKG Installation")); setObjectName("pkg_install_dialog"); - UpdateInfo(installation_info, buttons); // Just to show and check available and required size + update_info(installation_info, buttons); // Just to show and check available and required size } -void pkg_install_dialog::UpdateInfo(QLabel* installation_info, QDialogButtonBox* buttons) const +void pkg_install_dialog::update_info(QLabel* installation_info, QDialogButtonBox* buttons) const { u64 data_size = 0; u64 free_space = 0; @@ -182,7 +216,7 @@ void pkg_install_dialog::UpdateInfo(QLabel* installation_info, QDialogButtonBox* for (int i = 0; i < m_dir_list->count(); i++) { - if (m_dir_list->item(i)->checkState() == Qt::Checked) + if (m_dir_list->count() == 1 || m_dir_list->item(i)->checkState() == Qt::Checked) { data_size += m_dir_list->item(i)->data(Roles::DataSizeRole).toULongLong(); } @@ -197,7 +231,7 @@ void pkg_install_dialog::UpdateInfo(QLabel* installation_info, QDialogButtonBox* buttons->button(QDialogButtonBox::Ok)->setEnabled(data_size && (data_size <= free_space)); } -void pkg_install_dialog::MoveItem(int offset) const +void pkg_install_dialog::move_item(int offset) const { const int src_index = m_dir_list->currentRow(); const int dest_index = src_index + offset; @@ -211,14 +245,14 @@ void pkg_install_dialog::MoveItem(int offset) const } } -std::vector pkg_install_dialog::GetPathsToInstall() const +std::vector pkg_install_dialog::get_paths_to_install() const { std::vector result; for (int i = 0; i < m_dir_list->count(); i++) { const QListWidgetItem* item = m_dir_list->item(i); - if (item && item->checkState() == Qt::Checked) + if (item && (m_dir_list->count() == 1 || item->checkState() == Qt::Checked)) { compat::package_info info; info.path = item->data(Roles::FullPathRole).toString(); diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.h b/rpcs3/rpcs3qt/pkg_install_dialog.h index 2122259a1d..f1a623009d 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.h +++ b/rpcs3/rpcs3qt/pkg_install_dialog.h @@ -18,11 +18,17 @@ class pkg_install_dialog : public QDialog public: explicit pkg_install_dialog(const QStringList& paths, game_compatibility* compat, QWidget* parent = nullptr); - std::vector GetPathsToInstall() const; + std::vector get_paths_to_install() const; + bool precompile_caches() const { return m_precompile_caches; } + bool create_desktop_shortcuts() const { return m_create_desktop_shortcuts; } + bool create_app_shortcut() const { return m_create_app_shortcut; } private: - void UpdateInfo(QLabel* installation_info, QDialogButtonBox* buttons) const; - void MoveItem(int offset) const; + void update_info(QLabel* installation_info, QDialogButtonBox* buttons) const; + void move_item(int offset) const; - QListWidget* m_dir_list; + QListWidget* m_dir_list = nullptr; + bool m_precompile_caches = false; + bool m_create_desktop_shortcuts = false; + bool m_create_app_shortcut = false; }; diff --git a/rpcs3/rpcs3qt/ps_move_tracker_dialog.ui b/rpcs3/rpcs3qt/ps_move_tracker_dialog.ui index 2272c989dc..3afa78b531 100644 --- a/rpcs3/rpcs3qt/ps_move_tracker_dialog.ui +++ b/rpcs3/rpcs3qt/ps_move_tracker_dialog.ui @@ -17,7 +17,7 @@ - Dialog + PS Move Tracker Settings diff --git a/rpcs3/rpcs3qt/qt_camera_handler.cpp b/rpcs3/rpcs3qt/qt_camera_handler.cpp index 5b0caeb642..91f0f1b05c 100644 --- a/rpcs3/rpcs3qt/qt_camera_handler.cpp +++ b/rpcs3/rpcs3qt/qt_camera_handler.cpp @@ -341,14 +341,14 @@ void qt_camera_handler::update_camera_settings() // Load selected settings from config file bool success = false; - cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(camera_id, success); + cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::qt), camera_id, success); if (success) { camera_log.notice("Found config entry for camera \"%s\" (m_camera_id='%s')", camera_id, m_camera_id); // List all available settings and choose the proper value if possible. - const double epsilon = 0.001; + constexpr double epsilon = 0.001; success = false; for (const QCameraFormat& supported_setting : m_camera->cameraDevice().videoFormats()) { diff --git a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp index d4736f01e2..3dee6fa6e3 100644 --- a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp +++ b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp @@ -1,7 +1,6 @@ #include "stdafx.h" #include "qt_camera_video_sink.h" -#include "Emu/Cell/Modules/cellCamera.h" #include "Emu/system_config.h" #include @@ -9,7 +8,7 @@ LOG_CHANNEL(camera_log, "Camera"); qt_camera_video_sink::qt_camera_video_sink(bool front_facing, QObject *parent) - : QVideoSink(parent), m_front_facing(front_facing) + : camera_video_sink(front_facing), QVideoSink(parent) { connect(this, &QVideoSink::videoFrameChanged, this, &qt_camera_video_sink::present); } @@ -35,18 +34,22 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) } // Get image. This usually also converts the image to ARGB32. - QImage image = frame.toImage(); + QImage image = tmp.toImage(); + u32 width = image.isNull() ? 0 : static_cast(image.width()); + u32 height = image.isNull() ? 0 : static_cast(image.height()); if (image.isNull()) { - camera_log.warning("Image is invalid: pixel_format=%s, format=%d", tmp.pixelFormat(), static_cast(QVideoFrameFormat::imageFormatFromPixelFormat(tmp.pixelFormat()))); + camera_log.warning("Image is invalid: pixel_format=%s, format=%d", tmp.pixelFormat(), static_cast(QVideoFrameFormat::imageFormatFromPixelFormat(tmp.pixelFormat()))); } else { // Scale image if necessary - if (m_width > 0 && m_height > 0 && m_width != static_cast(image.width()) && m_height != static_cast(image.height())) + if (m_width > 0 && m_height > 0 && m_width != width && m_height != height) { image = image.scaled(m_width, m_height, Qt::AspectRatioMode::IgnoreAspectRatio, Qt::SmoothTransformation); + width = static_cast(image.width()); + height = static_cast(image.height()); } // Determine image flip @@ -87,254 +90,10 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) } } - const u64 new_size = m_bytesize; - image_buffer& image_buffer = m_image_buffer[m_write_index]; - - // Reset buffer if necessary - if (image_buffer.data.size() != new_size) - { - image_buffer.data.clear(); - } - - // Create buffer if necessary - if (image_buffer.data.empty() && new_size > 0) - { - image_buffer.data.resize(new_size); - image_buffer.width = m_width; - image_buffer.height = m_height; - } - - if (!image_buffer.data.empty() && !image.isNull()) - { - // Convert image to proper layout - // TODO: check if pixel format and bytes per pixel match and convert if necessary - // TODO: implement or improve more conversions - - const u32 width = std::min(image_buffer.width, image.width()); - const u32 height = std::min(image_buffer.height, image.height()); - - switch (m_format) - { - case CELL_CAMERA_RAW8: // The game seems to expect BGGR - { - // Let's use a very simple algorithm to convert the image to raw BGGR - const auto convert_to_bggr = [this, &image_buffer, &image, width, height](u32 y_begin, u32 y_end) - { - u8* dst = &image_buffer.data[image_buffer.width * y_begin]; - - for (u32 y = y_begin; y < height && y < y_end; y++) - { - const u8* src = image.constScanLine(y); - const u8* srcu = image.constScanLine(std::max(0, y - 1)); - const u8* srcd = image.constScanLine(std::min(height - 1, y + 1)); - const bool is_top_pixel = (y % 2) == 0; - - // We apply gaussian blur to get better demosaicing results later when debayering again - const auto blurred = [&](s32 x, s32 c) - { - const s32 i = x * 4 + c; - const s32 il = std::max(0, x - 1) * 4 + c; - const s32 ir = std::min(width - 1, x + 1) * 4 + c; - const s32 sum = - srcu[i] + - src[il] + 4 * src[i] + src[ir] + - srcd[i]; - return static_cast(std::clamp((sum + 4) / 8, 0, 255)); - }; - - // Split loops (roughly twice the performance by removing one condition) - if (is_top_pixel) - { - for (u32 x = 0; x < width; x++, dst++) - { - const bool is_left_pixel = (x % 2) == 0; - - if (is_left_pixel) - { - *dst = blurred(x, 2); // Blue - } - else - { - *dst = blurred(x, 1); // Green - } - } - } - else - { - for (u32 x = 0; x < width; x++, dst++) - { - const bool is_left_pixel = (x % 2) == 0; - - if (is_left_pixel) - { - *dst = blurred(x, 1); // Green - } - else - { - *dst = blurred(x, 0); // Red - } - } - } - } - }; - - // Use a multithreaded workload. The faster we get this done, the better. - constexpr u32 thread_count = 4; - const u32 lines_per_thread = std::ceil(image_buffer.height / static_cast(thread_count)); - u32 y_begin = 0; - u32 y_end = lines_per_thread; - - QFutureSynchronizer synchronizer; - for (u32 i = 0; i < thread_count; i++) - { - synchronizer.addFuture(QtConcurrent::run(convert_to_bggr, y_begin, y_end)); - y_begin = y_end; - y_end += lines_per_thread; - } - synchronizer.waitForFinished(); - break; - } - //case CELL_CAMERA_YUV422: - case CELL_CAMERA_Y0_U_Y1_V: - case CELL_CAMERA_V_Y1_U_Y0: - { - // Simple RGB to Y0_U_Y1_V conversion from stackoverflow. - const auto convert_to_yuv422 = [&image_buffer, &image, width, height, format = m_format](u32 y_begin, u32 y_end) - { - constexpr s32 yuv_bytes_per_pixel = 2; - const s32 yuv_pitch = image_buffer.width * yuv_bytes_per_pixel; - - const s32 y0_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3; - const s32 u_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2; - const s32 y1_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1; - const s32 v_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0; - - for (u32 y = y_begin; y < height && y < y_end; y++) - { - const u8* src = image.constScanLine(y); - u8* yuv_row_ptr = &image_buffer.data[y * yuv_pitch]; - - for (u32 x = 0; x < width - 1; x += 2, src += 8) - { - const f32 r1 = src[0]; - const f32 g1 = src[1]; - const f32 b1 = src[2]; - const f32 r2 = src[4]; - const f32 g2 = src[5]; - const f32 b2 = src[6]; - - const s32 y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f; - const s32 u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f; - const s32 v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f; - const s32 y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f; - - const s32 yuv_index = x * yuv_bytes_per_pixel; - yuv_row_ptr[yuv_index + y0_offset] = static_cast(std::clamp(y0, 0, 255)); - yuv_row_ptr[yuv_index + u_offset] = static_cast(std::clamp( u, 0, 255)); - yuv_row_ptr[yuv_index + y1_offset] = static_cast(std::clamp(y1, 0, 255)); - yuv_row_ptr[yuv_index + v_offset] = static_cast(std::clamp( v, 0, 255)); - } - } - }; - - // Use a multithreaded workload. The faster we get this done, the better. - constexpr u32 thread_count = 4; - const u32 lines_per_thread = std::ceil(image_buffer.height / static_cast(thread_count)); - u32 y_begin = 0; - u32 y_end = lines_per_thread; - - QFutureSynchronizer synchronizer; - for (u32 i = 0; i < thread_count; i++) - { - synchronizer.addFuture(QtConcurrent::run(convert_to_yuv422, y_begin, y_end)); - y_begin = y_end; - y_end += lines_per_thread; - } - synchronizer.waitForFinished(); - break; - } - case CELL_CAMERA_JPG: - case CELL_CAMERA_RGBA: - case CELL_CAMERA_RAW10: - case CELL_CAMERA_YUV420: - case CELL_CAMERA_FORMAT_UNKNOWN: - default: - std::memcpy(image_buffer.data.data(), image.constBits(), std::min(image_buffer.data.size(), image.height() * image.bytesPerLine())); - break; - } - } + camera_video_sink::present(width, height, image.bytesPerLine(), 4, [&image](u32 y){ return image.constScanLine(y); }); // Unmap frame memory tmp.unmap(); - camera_log.trace("Wrote image to video surface. index=%d, m_frame_number=%d, width=%d, height=%d, bytesize=%d", - m_write_index, m_frame_number.load(), m_width, m_height, m_bytesize); - - // Toggle write/read index - std::lock_guard lock(m_mutex); - image_buffer.frame_number = m_frame_number++; - m_write_index = read_index(); - return true; } - -void qt_camera_video_sink::set_format(s32 format, u32 bytesize) -{ - camera_log.notice("Setting format: format=%d, bytesize=%d", format, bytesize); - - m_format = format; - m_bytesize = bytesize; -} - -void qt_camera_video_sink::set_resolution(u32 width, u32 height) -{ - camera_log.notice("Setting resolution: width=%d, height=%d", width, height); - - m_width = width; - m_height = height; -} - -void qt_camera_video_sink::set_mirrored(bool mirrored) -{ - camera_log.notice("Setting mirrored: mirrored=%d", mirrored); - - m_mirrored = mirrored; -} - -u64 qt_camera_video_sink::frame_number() const -{ - return m_frame_number.load(); -} - -void qt_camera_video_sink::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read) -{ - // Lock read buffer - std::lock_guard lock(m_mutex); - const image_buffer& image_buffer = m_image_buffer[read_index()]; - - width = image_buffer.width; - height = image_buffer.height; - frame_number = image_buffer.frame_number; - - // Copy to out buffer - if (buf && !image_buffer.data.empty()) - { - bytes_read = std::min(image_buffer.data.size(), size); - std::memcpy(buf, image_buffer.data.data(), bytes_read); - - if (image_buffer.data.size() != size) - { - camera_log.error("Buffer size mismatch: in=%d, out=%d. Cropping to incoming size. Please contact a developer.", size, image_buffer.data.size()); - } - } - else - { - bytes_read = 0; - } -} - -u32 qt_camera_video_sink::read_index() const -{ - // The read buffer index cannot be the same as the write index - return (m_write_index + 1u) % ::narrow(m_image_buffer.size()); -} diff --git a/rpcs3/rpcs3qt/qt_camera_video_sink.h b/rpcs3/rpcs3qt/qt_camera_video_sink.h index e3f405b55c..8f228bb94f 100644 --- a/rpcs3/rpcs3qt/qt_camera_video_sink.h +++ b/rpcs3/rpcs3qt/qt_camera_video_sink.h @@ -1,50 +1,15 @@ #pragma once -#include "util/atomic.hpp" -#include "util/types.hpp" +#include "Input/camera_video_sink.h" + #include #include -#include -#include -#include - -class qt_camera_video_sink final : public QVideoSink +class qt_camera_video_sink final : public camera_video_sink, public QVideoSink { public: qt_camera_video_sink(bool front_facing, QObject *parent = nullptr); virtual ~qt_camera_video_sink(); bool present(const QVideoFrame& frame); - - void set_format(s32 format, u32 bytesize); - void set_resolution(u32 width, u32 height); - void set_mirrored(bool mirrored); - - u64 frame_number() const; - - void get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read); - -private: - u32 read_index() const; - - bool m_front_facing = false; - bool m_mirrored = false; // Set by cellCamera - s32 m_format = 2; // CELL_CAMERA_RAW8, set by cellCamera - u32 m_bytesize = 0; - u32 m_width = 640; - u32 m_height = 480; - - std::mutex m_mutex; - atomic_t m_frame_number{0}; - u32 m_write_index{0}; - - struct image_buffer - { - u64 frame_number = 0; - u32 width = 0; - u32 height = 0; - std::vector data; - }; - std::array m_image_buffer; }; diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index fa5e3c250e..fc53dc542a 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -641,7 +641,7 @@ namespace gui usz byte_unit = 0; usz divisor = 1; #if defined(__APPLE__) - constexpr usz multiplier = 1000; + constexpr usz multiplier = 1000; static const QString s_units[]{"B", "kB", "MB", "GB", "TB", "PB"}; #else constexpr usz multiplier = 1024;