diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 5e435daf2..ef862bb20 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -134,7 +134,7 @@ jobs: runs-on: ${{ matrix.runs-on }} env: CCACHE_DIR: /tmp/ccache_dir - QT_VER: '6.11.1' + QT_VER: '6.11.2' QT_VER_MAIN: '6' LLVM_COMPILER_VER: '22' RELEASE_MESSAGE: ../GitHubReleaseMessage.txt @@ -212,9 +212,9 @@ jobs: env: COMPILER: msvc QT_VER_MAIN: '6' - QT_VER: '6.11.1' + QT_VER: '6.11.2' QT_VER_MSVC: 'msvc2022' - QT_DATE: '202605090529' + QT_DATE: '202608131017' LLVM_VER: '22.1.8' VULKAN_VER: '1.4.341.1' VULKAN_SDK_SHA: 'bcf2d75aa9556889ab974858666e20b3655b6055a0db704ccb47279ff33b5bfe' diff --git a/3rdparty/opencv/opencv b/3rdparty/opencv/opencv index 6900a1265..d4dde8afe 160000 --- a/3rdparty/opencv/opencv +++ b/3rdparty/opencv/opencv @@ -1 +1 @@ -Subproject commit 6900a1265b8e1b0a19d7955b5bd6599a4a8b8576 +Subproject commit d4dde8afe3098f33f253278e25d45d6554afb10c diff --git a/buildfiles/msvc/rpcs3_default.props b/buildfiles/msvc/rpcs3_default.props index e9a5b4e56..c4f3cf248 100644 --- a/buildfiles/msvc/rpcs3_default.props +++ b/buildfiles/msvc/rpcs3_default.props @@ -9,8 +9,8 @@ $(SolutionDir)build\tmp\$(ProjectName)-$(Configuration)-$(Platform)\ $(SolutionDir)packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.8\build\native\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.targets true - $(SolutionDir)3rdparty\opencv\opencv\opencv413\build - opencv_world4130 + $(SolutionDir)3rdparty\opencv\opencv\opencv414\build + opencv_world4140 diff --git a/rpcs3/Crypto/unedat.cpp b/rpcs3/Crypto/unedat.cpp index 728b6fc9f..cc577392c 100644 --- a/rpcs3/Crypto/unedat.cpp +++ b/rpcs3/Crypto/unedat.cpp @@ -839,7 +839,7 @@ bool EDATADecrypter::ReadHeader() // Type 2: Use key from RAP file (RIF key). (also used for type 1 at the moment) else { - const std::string rap_path = rpcs3::utils::get_rap_file_path(npdHeader.content_id); + const std::string rap_path = rpcs3::utils::get_rap_file_path(npdHeader.get_content_id()); if (fs::file rap{rap_path}; rap && rap.size() >= sizeof(dec_key)) { diff --git a/rpcs3/Crypto/unedat.h b/rpcs3/Crypto/unedat.h index e049c44aa..bebe77799 100644 --- a/rpcs3/Crypto/unedat.h +++ b/rpcs3/Crypto/unedat.h @@ -49,6 +49,12 @@ struct NPD_HEADER u8 dev_hash[0x10]; s64 activate_time; s64 expire_time; + + std::string get_content_id() const + { + const std::string_view id{content_id, sizeof(content_id)}; + return std::string{id.substr(0, id.find_first_of('\0'))}; + } }; struct EDAT_HEADER diff --git a/rpcs3/Crypto/unself.cpp b/rpcs3/Crypto/unself.cpp index 4a2e1b7c7..bd550372d 100644 --- a/rpcs3/Crypto/unself.cpp +++ b/rpcs3/Crypto/unself.cpp @@ -323,7 +323,7 @@ void supplemental_header::Show() const self_log.notice("Version: 0x%08x", PS3_npdrm_header.npd.version); self_log.notice("License: 0x%08x", PS3_npdrm_header.npd.license); self_log.notice("Type: 0x%08x", PS3_npdrm_header.npd.type); - self_log.notice("ContentID: %s", PS3_npdrm_header.npd.content_id); + self_log.notice("ContentID: %s", PS3_npdrm_header.npd.get_content_id()); self_log.notice("Digest: %s", PS3_npdrm_header.npd.digest); self_log.notice("Inverse digest: %s", PS3_npdrm_header.npd.title_hash); self_log.notice("XOR digest: %s", PS3_npdrm_header.npd.dev_hash); @@ -1074,7 +1074,7 @@ bool SELFDecrypter::DecryptNPDRM(u8 *metadata, u32 metadata_size) if (npd->license == 1) // Network license. { // Try to find a RAP file to get the key. - if (!GetKeyFromRap(npd->content_id, npdrm_key)) + if (!GetKeyFromRap(npd->get_content_id(), npdrm_key)) { self_log.error("Can't decrypt network NPDRM!"); return false; @@ -1083,7 +1083,7 @@ bool SELFDecrypter::DecryptNPDRM(u8 *metadata, u32 metadata_size) else if (npd->license == 2) // Local license. { // Try to find a RAP file to get the key. - if (!GetKeyFromRap(npd->content_id, npdrm_key)) + if (!GetKeyFromRap(npd->get_content_id(), npdrm_key)) { self_log.error("Can't find RAP file for NPDRM decryption!"); return false; @@ -1301,14 +1301,13 @@ fs::file SELFDecrypter::MakeElf(bool isElf32) return e; } -bool SELFDecrypter::GetKeyFromRap(const char* content_id, u8* npdrm_key) +bool SELFDecrypter::GetKeyFromRap(std::string_view content_id, u8* npdrm_key) { // Set empty RAP key. std::array rap_key {}; // Try to find a matching RAP file under exdata folder. - const std::string ci_str = content_id; - const std::string rap_path = rpcs3::utils::get_rap_file_path(ci_str); + const std::string rap_path = rpcs3::utils::get_rap_file_path(content_id); // Open the RAP file and read the key. const fs::file rap_file(rap_path); @@ -1321,7 +1320,7 @@ bool SELFDecrypter::GetKeyFromRap(const char* content_id, u8* npdrm_key) return false; } - self_log.notice("Loading RAP file %s.rap", ci_str); + self_log.notice("Loading RAP file %s.rap", content_id); if (rap_file.read(rap_key.data(), rap_key.size()) != rap_key.size()) { diff --git a/rpcs3/Crypto/unself.h b/rpcs3/Crypto/unself.h index 22eafb074..beae02500 100644 --- a/rpcs3/Crypto/unself.h +++ b/rpcs3/Crypto/unself.h @@ -478,7 +478,7 @@ public: bool DecryptData(); bool DecryptNPDRM(u8 *metadata, u32 metadata_size); const NPD_HEADER* GetNPDHeader() const; - static bool GetKeyFromRap(const char *content_id, u8 *npdrm_key); + static bool GetKeyFromRap(std::string_view content_id, u8 *npdrm_key); private: template diff --git a/rpcs3/Emu/Cell/Modules/cellGame.cpp b/rpcs3/Emu/Cell/Modules/cellGame.cpp index f6ae27ec8..9349d3339 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGame.cpp @@ -1344,7 +1344,7 @@ error_code cellGameDeleteGameData(vm::cptr dirName) { cellGame.warning("cellGameDeleteGameData(dirName=%s)", dirName); - if (!dirName) + if (!dirName || sysutil_check_name_string(dirName.get_ptr(), 1, CELL_GAME_DIRNAME_SIZE) != 0) { return CELL_GAME_ERROR_PARAM; } diff --git a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp index 6ce57e294..1030dbc59 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp @@ -17,7 +17,22 @@ bool music_selection_context::set(const CellMusicSelectionContext& in) } constexpr u32 pos = sizeof(magic); - hash = &in.data[pos]; + + const std::string_view data{&in.data[pos], sizeof(in.data) - pos}; + const std::string_view new_hash = data.substr(0, data.find_first_of('\0')); + + const bool is_valid_hash = !new_hash.empty() && std::all_of(new_hash.begin(), new_hash.end(), [](char c) + { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_'; + }); + + if (!is_valid_hash) + { + cellMusicSelectionContext.error("Invalid music selection context hash, context = %s", context_to_hex(in)); + return false; + } + + hash = std::string{new_hash}; return load_playlist(); } @@ -35,7 +50,7 @@ CellMusicSelectionContext music_selection_context::get() const std::memset(out.data, 0, CELL_MUSIC_SELECTION_CONTEXT_SIZE); std::memcpy(out.data, magic, sizeof(magic)); pos += sizeof(magic); - std::memcpy(&out.data[pos], hash.c_str(), hash.size()); + std::memcpy(&out.data[pos], hash.data(), hash.size()); return out; } diff --git a/rpcs3/Emu/Cell/Modules/cellRec.cpp b/rpcs3/Emu/Cell/Modules/cellRec.cpp index 511cc7c85..cef43e4f7 100644 --- a/rpcs3/Emu/Cell/Modules/cellRec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellRec.cpp @@ -1333,13 +1333,13 @@ error_code cellRecClose(s32 isDiscard) rec.sink->stop(true); } - if (fs::is_file(rec.param.filename)) + if (const std::string local_path = vfs::get(rec.param.filename); fs::is_file(local_path)) { cellRec.warning("cellRecClose: removing discarded recording '%s'", rec.param.filename); - if (!fs::remove_file(rec.param.filename)) + if (!fs::remove_file(local_path)) { - cellRec.error("cellRecClose: failed to remove recording '%s'", rec.param.filename); + cellRec.error("cellRecClose: failed to remove recording '%s' (%s)", rec.param.filename, fs::g_tls_error); } } } diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index 22086bdf5..5f6fd0eb7 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -214,6 +214,16 @@ int check_filename(std::string_view file_path, bool disallow_system_files, bool return 0; } +static bool is_valid_dir_name(const std::string& dir_name) +{ + if (dir_name.empty() || dir_name.find_first_of('\0') != umax) + { + return false; + } + + return sysutil_check_name_string(dir_name.c_str(), 1, CELL_SAVEDATA_DIRNAME_SIZE) == 0; +} + static std::vector get_save_entries(const std::string& base_dir, const std::string& prefix) { std::vector save_entries; @@ -251,6 +261,12 @@ static std::vector get_save_entries(const std::string& base_dir, save_entry.subtitle = psf::get_string(psf, "SUB_TITLE"); save_entry.details = psf::get_string(psf, "DETAIL"); + if (!is_valid_dir_name(save_entry.dirName)) + { + cellSaveData.error("Savedata '%s' has an invalid SAVEDATA_DIRECTORY entry ('%s')", entry.name, save_entry.dirName); + continue; + } + for (const auto& entry2 : fs::dir(base_dir + entry.name)) { if (entry2.is_directory || check_filename(vfs::unescape(entry2.name), false, true)) @@ -848,6 +864,12 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v save_entry2.subtitle = psf::get_string(psf, "SUB_TITLE"); save_entry2.details = psf::get_string(psf, "DETAIL"); + if (!is_valid_dir_name(save_entry2.dirName)) + { + cellSaveData.error("Savedata '%s' has an invalid SAVEDATA_DIRECTORY entry ('%s')", entry.name, save_entry2.dirName); + break; + } + for (const auto& entry2 : fs::dir(base_dir + entry.name)) { if (entry2.is_directory || check_filename(vfs::unescape(entry2.name), false, true)) @@ -1468,6 +1490,12 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v save_entry.escaped = vfs::escape(save_entry.dirName); } + if (!is_valid_dir_name(save_entry.dirName)) + { + cellSaveData.error("savedata_op(): invalid savedata directory name ('%s')", save_entry.dirName); + return {CELL_SAVEDATA_ERROR_BROKEN, save_entry.dirName}; + } + const std::string dir_path = base_dir + save_entry.escaped + "/"; const std::string old_path = base_dir + ".backup_" + save_entry.escaped + "/"; const std::string new_path = base_dir + ".working_" + save_entry.escaped + "/"; @@ -2215,7 +2243,7 @@ static NEVER_INLINE error_code savedata_get_list_item(vm::cptr dirName, vm return {CELL_SAVEDATA_ERROR_PARAM, "107"}; } - switch (sysutil_check_name_string(dirName.get_ptr(), 1, CELL_SAVEDATA_DIRLIST_MAX)) + switch (sysutil_check_name_string(dirName.get_ptr(), 1, CELL_SAVEDATA_DIRNAME_SIZE)) { case -1: { diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index 1e09b8f49..8fe1f7d5f 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -699,7 +699,7 @@ error_code npDrmIsAvailable(vm::cptr k_licensee_addr, vm::cptr drm_pat } else { - const std::string rap_file = rpcs3::utils::get_rap_file_path(npd.content_id); + const std::string rap_file = rpcs3::utils::get_rap_file_path(npd.get_content_id()); if (fs::file rap_fd{rap_file}) { diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 91824ac95..216276102 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -460,13 +460,13 @@ lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename, std::string filename.remove_prefix(cell_fs_path.size()); const bool is_path = filename.starts_with("/"sv); - std::string mp_name = is_path ? std::string{get_path_root_and_trail(filename).first} : std::string(filename); + std::string mp_name = is_path ? "/" + std::string{get_path_root_and_trail(filename).first} : std::string(filename); const auto check_mp = [&]() { for (auto mp = &g_mp_sys_dev_root; mp; mp = mp->next) { - const auto& device_alias_check = !is_path && ( + const bool device_alias_check = !is_path && ( (mp == &g_mp_sys_dev_hdd0 && mp_name == "CELL_FS_IOS:PATA0_HDD_DRIVE"sv) || (mp == &g_mp_sys_dev_hdd1 && mp_name == "CELL_FS_IOS:PATA1_HDD_DRIVE"sv) || (mp == &g_mp_sys_dev_flash && mp_name == "CELL_FS_IOS:BUILTIN_FLASH"sv) || @@ -521,9 +521,6 @@ lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename, std::string *vfs_path = g_cfg_vfs.get_dev_flash3(); else *vfs_path = {}; - - if (is_path && !is_cell_fs_path && !vfs_path->empty()) - vfs_path->append(filename.substr(mp_name.size())); } return result; diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.h b/rpcs3/Emu/Cell/lv2/sys_fs.h index 39198a04b..df34a0c5f 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.h +++ b/rpcs3/Emu/Cell/lv2/sys_fs.h @@ -246,8 +246,8 @@ public: lv2_fs_object& operator=(const lv2_fs_object&) = delete; - // Get the device's root path (e.g. "/dev_hdd0") from a given path - // Cut the trail and return it in seccond argument + // Get the device's root path (e.g. "dev_hdd0") from a given path + // Cut the trail and return it in second argument static std::pair get_path_root_and_trail(std::string_view path); // Get the device's root path (e.g. "/dev_hdd0") from a given path @@ -264,14 +264,7 @@ public: static std::array get_name(std::string_view filename) { std::array name; - - if (filename.size() >= 0x420) - { - filename = filename.substr(0, 0x420 - 1); - } - - filename.copy(name.data(), filename.size()); - name[filename.size()] = 0; + strcpy_trunc(name, filename); return name; } diff --git a/rpcs3/Emu/Io/PadHandler.h b/rpcs3/Emu/Io/PadHandler.h index 4db058e59..fc5b134de 100644 --- a/rpcs3/Emu/Io/PadHandler.h +++ b/rpcs3/Emu/Io/PadHandler.h @@ -17,6 +17,7 @@ extern "C" { #endif #include +#include #include #include #include @@ -292,16 +293,14 @@ public: virtual void get_motion_sensors(const std::string& pad_id, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array& sensors); virtual std::unordered_map get_motion_axis_list() const { return {}; } - static constexpr f32 PI = 3.14159265f; - static f32 degree_to_rad(f32 degree) { - return degree * PI / 180.0f; + return degree * std::numbers::pi_v / 180.0f; } static f32 rad_to_degree(f32 radians) { - return radians * 180.0f / PI; + return radians * 180.0f / std::numbers::pi_v; }; private: diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index d018f9973..e7953e88d 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -682,6 +682,16 @@ namespace rpcn break; } + if (command == CommandType::UnlockTrophy) + { + const ErrorType err = static_cast(data[0]); + + if (err != ErrorType::NoError) + rpcn_log.error("UnlockTrophy failed with %s", err); + + break; + } + // Those commands are handled synchronously and won't be forwarded to NP Handler if (command == CommandType::Login || command == CommandType::GetServerList || command == CommandType::Create || command == CommandType::Delete || command == CommandType::AddFriend || command == CommandType::RemoveFriend || @@ -703,7 +713,7 @@ namespace rpcn } else { - rpcn_log.error("Tried to forward a reply whose packet_id marks it as internal to RPCN"); + rpcn_log.error("Tried to forward a reply whose packet_id marks it as internal to RPCN: %s:0x%x", command, packet_id); } } diff --git a/rpcs3/Emu/RSX/Core/RSXContext.cpp b/rpcs3/Emu/RSX/Core/RSXContext.cpp index aa5962c74..0ba4e880c 100644 --- a/rpcs3/Emu/RSX/Core/RSXContext.cpp +++ b/rpcs3/Emu/RSX/Core/RSXContext.cpp @@ -29,7 +29,10 @@ namespace rsx const auto tile_base_address = iomap_table.get_addr(tile.offset); const auto tile_range = utils::address_range32::start_length(tile_base_address, tile.size); - if (range.inside(tile_range)) + // NOTE: Some games will use contiguous ranges over multiple tiles. + // For such reasons, checking range.inside(tile_range) is not good enough. + // Instead, check for the first tile in the sequence. + if (range.overlaps(tile_range) && tile_range.overlaps(range.start)) { ensure(tile_base_address + 1); return { .base_address = tile_base_address, .tile = &tile }; diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index d8d412911..da04a1b6c 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -50,6 +50,8 @@ GLGSRender::GLGSRender(utils::serial* ar) noexcept : GSRender(ar) backend_config.supports_multidraw = true; backend_config.supports_normalized_barycentrics = true; backend_config.supports_hw_instanced_rendering = true; + // OpenGL 3.2+ defaults to GL_LAST_VERTEX_CONVENTION. + backend_config.supports_last_provoking_vertex = true; if (g_cfg.video.antialiasing_level != msaa_level::none) { diff --git a/rpcs3/Emu/RSX/GL/GLVertexProgram.cpp b/rpcs3/Emu/RSX/GL/GLVertexProgram.cpp index d275a55bc..b02238a48 100644 --- a/rpcs3/Emu/RSX/GL/GLVertexProgram.cpp +++ b/rpcs3/Emu/RSX/GL/GLVertexProgram.cpp @@ -156,8 +156,12 @@ void GLVertexDecompilerThread::insertOutputs(std::stringstream& OS, const std::v { if (i.need_declare) { - // All outputs must be declared always to allow setting default values - OS << "layout(location=" << gl::get_varying_register_location(i.name) << ") out vec4 " << i.name << ";\n"; + // All outputs must be declared always to allow setting default values. + // NV4097_SET_SHADE_MODE applies to the front/back diffuse and specular colors. + const bool flat_color = (m_prog.ctrl & RSX_SHADER_CONTROL_FLAT_SHADING) && + (i.name.starts_with("diff_color"sv) || i.name.starts_with("spec_color"sv)); + OS << "layout(location=" << gl::get_varying_register_location(i.name) << ") out " + << (flat_color ? "flat " : "") << "vec4 " << i.name << ";\n"; } } } diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp index ae132a6cd..deebcc5c2 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp @@ -710,6 +710,23 @@ namespace rsx RSX(ctx)->enable_conditional_rendering(vm::cast(address_ptr)); } + void set_shading_mode(context* ctx, u32 reg, u32 arg) + { + if (arg == REGS(ctx)->latch) + { + return; + } + + if (to_shading_mode(arg)) + { + RSX(ctx)->m_graphics_state |= rsx::vertex_program_state_dirty | rsx::fragment_program_state_dirty; + return; + } + + // Rollback + REGS(ctx)->decode(reg, REGS(ctx)->latch); + } + void set_zcull_render_enable(context* ctx, u32, u32) { RSX(ctx)->notify_zcull_info_changed(); diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.h b/rpcs3/Emu/RSX/NV47/HW/nv4097.h index a5a434e47..3e28ac224 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.h +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.h @@ -55,6 +55,8 @@ namespace rsx void set_render_mode(context* ctx, u32, u32 arg); + void set_shading_mode(context* ctx, u32 reg, u32 arg); + void set_zcull_render_enable(context* ctx, u32, u32); void set_zcull_stats_enable(context* ctx, u32, u32); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp index 21cdd6993..ef997595b 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp @@ -7,6 +7,8 @@ #include "Utilities/File.h" #include "Emu/Cell/timers.hpp" +#include + #ifndef _WIN32 #include #include @@ -350,9 +352,8 @@ namespace rsx { if (sinus_modifier >= 0) { - static constexpr f32 PI = 3.14159265f; const f32 pulse_sinus_x = static_cast(get_system_time() / 1000) * pulse_speed_modifier; - pulse_sinus_offset = fmod(pulse_sinus_x + sinus_modifier * PI, 2.0f * PI); + pulse_sinus_offset = fmod(pulse_sinus_x + sinus_modifier * std::numbers::pi_v, 2.0f * std::numbers::pi_v); } } diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index c3413a008..1d94690d8 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -1088,7 +1088,9 @@ bool FragmentProgramDecompiler::handle_sct_scb(u32 opcode) properties.has_divsq = true; return true; case RSX_FP_OPCODE_DP2: SetDst(getFunction(FUNCTION::DP2), OPFLAGS::op_extern); return true; - case RSX_FP_OPCODE_DP3: SetDst(getFunction(FUNCTION::DP3), OPFLAGS::op_extern); return true; + case RSX_FP_OPCODE_DP3: + SetDst(getFunction(dst.prec == RSX_FP_PRECISION_REAL && g_cfg.video.shader_precision == gpu_preset_level::ultra ? FUNCTION::DP3_PRECISE : FUNCTION::DP3), OPFLAGS::op_extern); + return true; case RSX_FP_OPCODE_DP4: SetDst(getFunction(FUNCTION::DP4), OPFLAGS::op_extern); return true; case RSX_FP_OPCODE_DP2A: SetDst(getFunction(FUNCTION::DP2A), OPFLAGS::op_extern); return true; case RSX_FP_OPCODE_MAD: SetDst("fma($0, $1, $2)", OPFLAGS::src_cast_f32); return true; diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp index d1ce2164a..daa2ad9fc 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp @@ -505,6 +505,8 @@ namespace glsl return "$Ty(dot($0.xy, $1.xy) + $2.x)"; case FUNCTION::DP3: return "$Ty(dot($0.xyz, $1.xyz))"; + case FUNCTION::DP3_PRECISE: + return "$Ty(fma($0.x, $1.x, fma($0.y, $1.y, $0.z * $1.z)))"; case FUNCTION::DP4: return "$Ty(dot($0, $1))"; case FUNCTION::DPH: @@ -685,12 +687,18 @@ namespace glsl // Make the output a little nicer std::sort(varying_list.begin(), varying_list.end(), FN(x.location < y.location)); + const auto is_flat_color = [&prog](const _varying_register_config& reg) + { + return (prog.ctrl & RSX_SHADER_CONTROL_FLAT_SHADING) && + (reg.name.starts_with("diff_color"sv) || reg.name.starts_with("spec_color"sv)); + }; if (!(prog.ctrl & RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION)) { for (const auto& reg : varying_list) { - OS << "layout(location=" << reg.location << ") in " << reg.type << " " << reg.name << ";\n"; + OS << "layout(location=" << reg.location << ") in " << (is_flat_color(reg) ? "flat " : "") + << reg.type << " " << reg.name << ";\n"; } OS << "\n"; @@ -718,7 +726,14 @@ namespace glsl for (const auto& reg : varying_list) { - OS << "vec4 " << reg.name << " = _interpolate_varying3(" << reg.name << "_raw);\n"; + if (is_flat_color(reg)) + { + OS << "vec4 " << reg.name << " = " << reg.name << "_raw[2];\n"; + } + else + { + OS << "vec4 " << reg.name << " = _interpolate_varying3(" << reg.name << "_raw);\n"; + } } OS << "\n"; diff --git a/rpcs3/Emu/RSX/Program/ShaderParam.h b/rpcs3/Emu/RSX/Program/ShaderParam.h index bf78b8f49..e0655ce9c 100644 --- a/rpcs3/Emu/RSX/Program/ShaderParam.h +++ b/rpcs3/Emu/RSX/Program/ShaderParam.h @@ -14,6 +14,7 @@ enum class FUNCTION DP2, DP2A, DP3, + DP3_PRECISE, DP4, DPH, SFL, // Set zero diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 4db620d9d..ba01c3567 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -3001,7 +3001,7 @@ namespace rsx { if (m_graphics_state.test(rsx::pipeline_state::xform_instancing_state_dirty)) { - current_vertex_program.ctrl = 0; + current_vertex_program.ctrl &= ~RSX_SHADER_CONTROL_INSTANCED_CONSTANTS; if (rsx::method_registers.current_draw_clause.is_trivial_instanced_draw) { current_vertex_program.ctrl |= RSX_SHADER_CONTROL_INSTANCED_CONSTANTS; @@ -3021,6 +3021,13 @@ namespace rsx ensure(!m_graphics_state.test(rsx::pipeline_state::vertex_program_ucode_dirty)); current_vertex_program.output_mask = rsx::method_registers.vertex_attrib_output_mask(); + current_vertex_program.ctrl &= ~RSX_SHADER_CONTROL_FLAT_SHADING; + if (rsx::method_registers.shade_mode() == rsx::shading_mode::flat && + backend_config.supports_last_provoking_vertex) + { + current_vertex_program.ctrl |= RSX_SHADER_CONTROL_FLAT_SHADING; + } + for (u32 textures_ref = current_vp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i) { if (!(textures_ref & 1)) continue; @@ -3060,6 +3067,12 @@ namespace rsx current_fragment_program.two_sided_lighting = m_ctx->register_state->two_side_light_en(); current_fragment_program.mrt_buffers_count = rsx::utility::get_mrt_buffers_count(m_ctx->register_state->surface_color_target()); + if (m_ctx->register_state->shade_mode() == rsx::shading_mode::flat && + backend_config.supports_last_provoking_vertex) + { + current_fragment_program.ctrl |= RSX_SHADER_CONTROL_FLAT_SHADING; + } + if (m_ctx->register_state->current_draw_clause.classify_mode() == primitive_class::polygon) { if (!backend_config.supports_normalized_barycentrics) diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 024c72a20..21f8a5bc4 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -92,6 +92,7 @@ namespace rsx bool supports_asynchronous_compute; // Async compute bool supports_host_gpu_labels; // Advanced host synchronization bool supports_normalized_barycentrics; // Basically all GPUs except NVIDIA have properly normalized barycentrics + bool supports_last_provoking_vertex; // Flat shading using RSX's last-vertex convention }; struct desync_fifo_cmd_info diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 00404da15..85c6c7a94 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -667,6 +667,12 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) backend_config.supports_multidraw = true; backend_config.supports_hw_instanced_rendering = true; + backend_config.supports_last_provoking_vertex = m_device->get_provoking_vertex_last_support(); + if (!backend_config.supports_last_provoking_vertex) + { + rsx_log.warning("VK_EXT_provoking_vertex with provokingVertexLast is unavailable; RSX flat shading will fall back to smooth interpolation."); + } + // NVIDIA has broken attribute interpolation backend_config.supports_normalized_barycentrics = ( !vk::is_NVIDIA(vk::get_driver_vendor()) || diff --git a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp index b62dac029..738e961bd 100644 --- a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp +++ b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp @@ -247,11 +247,26 @@ namespace vk VkPipelineColorBlendStateCreateInfo cs = create_info.state.cs; cs.pAttachments = create_info.state.att_state; + // Flat shading, from upstream b97f4bd8d. Kept alongside our dynamic-state work rather + // than either side of the merge replacing the other: this rebases the rasterization + // state so the provoking-vertex extension can be chained onto it, which is independent + // of the topology-class collapsing above. + VkPipelineRasterizationStateCreateInfo rs = create_info.state.rs; + VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex_state{}; + if (flags & USE_LAST_PROVOKING_VERTEX) + { + ensure(m_device->get_provoking_vertex_last_support()); + provoking_vertex_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT; + provoking_vertex_state.pNext = rs.pNext; + provoking_vertex_state.provokingVertexMode = VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT; + rs.pNext = &provoking_vertex_state; + } + VkGraphicsPipelineCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; info.pVertexInputState = &vi; info.pInputAssemblyState = &create_info.state.ia; - info.pRasterizationState = &create_info.state.rs; + info.pRasterizationState = &rs; info.pColorBlendState = &cs; info.pMultisampleState = pmss; info.pViewportState = &vp; diff --git a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.h b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.h index 9e0b1f887..30bf66157 100644 --- a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.h +++ b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.h @@ -70,7 +70,8 @@ namespace vk COMPILE_DEFAULT = 0, COMPILE_INLINE = 1, COMPILE_DEFERRED = 2, - SEPARATE_SHADER_OBJECTS = 4 + SEPARATE_SHADER_OBJECTS = 4, + USE_LAST_PROVOKING_VERTEX = 8 }; using op_flags = rsx::flags32_t; diff --git a/rpcs3/Emu/RSX/VK/VKProgramBuffer.h b/rpcs3/Emu/RSX/VK/VKProgramBuffer.h index 3e4ee000d..ad7d5a055 100644 --- a/rpcs3/Emu/RSX/VK/VKProgramBuffer.h +++ b/rpcs3/Emu/RSX/VK/VKProgramBuffer.h @@ -50,6 +50,10 @@ namespace vk { vk::pipe_compiler::op_flags compiler_flags = compile_async ? vk::pipe_compiler::COMPILE_DEFERRED : vk::pipe_compiler::COMPILE_INLINE; compiler_flags |= vk::pipe_compiler::SEPARATE_SHADER_OBJECTS; + if (vertexProgramData.use_last_provoking_vertex) + { + compiler_flags |= vk::pipe_compiler::USE_LAST_PROVOKING_VERTEX; + } auto compiler = vk::get_pipe_compiler(); auto result = compiler->compile( diff --git a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp index 73eaf978a..ecbabe1b7 100644 --- a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp @@ -314,8 +314,12 @@ void VKVertexDecompilerThread::insertOutputs(std::stringstream& OS, const std::v { if (i.need_declare) { - // All outputs must be declared always to allow setting default values - OS << "layout(location=" << vk::get_varying_register_location(i.name) << ") out vec4 " << i.name << ";\n"; + // All outputs must be declared always to allow setting default values. + // NV4097_SET_SHADE_MODE applies to the front/back diffuse and specular colors. + const bool flat_color = (m_prog.ctrl & RSX_SHADER_CONTROL_FLAT_SHADING) && + (i.name.starts_with("diff_color"sv) || i.name.starts_with("spec_color"sv)); + OS << "layout(location=" << vk::get_varying_register_location(i.name) << ") out " + << (flat_color ? "flat " : "") << "vec4 " << i.name << ";\n"; } } @@ -500,6 +504,8 @@ VKVertexProgram::~VKVertexProgram() void VKVertexProgram::Decompile(const RSXVertexProgram& prog) { + use_last_provoking_vertex = !!(prog.ctrl & RSX_SHADER_CONTROL_FLAT_SHADING); + std::string source; VKVertexDecompilerThread decompiler(prog, source, parr, *this); decompiler.Task(); diff --git a/rpcs3/Emu/RSX/VK/VKVertexProgram.h b/rpcs3/Emu/RSX/VK/VKVertexProgram.h index e5aeee0c3..68fb99ccf 100644 --- a/rpcs3/Emu/RSX/VK/VKVertexProgram.h +++ b/rpcs3/Emu/RSX/VK/VKVertexProgram.h @@ -63,6 +63,7 @@ public: VkShaderModule handle = nullptr; vk::glsl::shader shader; std::vector uniforms; + bool use_last_provoking_vertex = false; // Quick attribute indices struct diff --git a/rpcs3/Emu/RSX/VK/vkutils/device.cpp b/rpcs3/Emu/RSX/VK/vkutils/device.cpp index 5750de3e8..aa523507c 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/device.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/device.cpp @@ -42,6 +42,7 @@ namespace vk VkPhysicalDeviceBorderColorSwizzleFeaturesEXT border_color_swizzle_info{}; VkPhysicalDeviceFaultFeaturesEXT device_fault_info{}; VkPhysicalDeviceMultiDrawFeaturesEXT multidraw_info{}; + VkPhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex_info{}; // Core features shader_support_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES; @@ -135,6 +136,14 @@ namespace vk } #endif + + if (device_extensions.is_supported(VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME)) + { + provoking_vertex_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT; + provoking_vertex_info.pNext = features2.pNext; + features2.pNext = &provoking_vertex_info; + } + vkGetPhysicalDeviceFeatures2(dev, &features2); shader_types_support.allow_float64 = !!features2.features.shaderFloat64; @@ -152,6 +161,7 @@ namespace vk optional_features_support.framebuffer_loops = !!fbo_loops_info.attachmentFeedbackLoopLayout; optional_features_support.extended_device_fault = !!device_fault_info.deviceFault; optional_features_support.extended_dynamic_state = !!extended_dynamic_state_info.extendedDynamicState; + optional_features_support.provoking_vertex_last = !!provoking_vertex_info.provokingVertexLast; features = features2.features; @@ -877,6 +887,11 @@ namespace vk { requested_extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); } + + if (pgpu->optional_features_support.provoking_vertex_last) + { + requested_extensions.push_back(VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); + } #ifdef __APPLE__ if (pgpu->optional_features_support.portability) @@ -1175,6 +1190,15 @@ namespace vk device.pNext = &shader_barycentric_info; } + VkPhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex_info{}; + if (pgpu->optional_features_support.provoking_vertex_last) + { + provoking_vertex_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT; + provoking_vertex_info.pNext = const_cast(device.pNext); + provoking_vertex_info.provokingVertexLast = VK_TRUE; + device.pNext = &provoking_vertex_info; + } + if (auto error = vkCreateDevice(*pgpu, &device, nullptr, &dev)) { dump_debug_info(requested_extensions, enabled_features); diff --git a/rpcs3/Emu/RSX/VK/vkutils/device.h b/rpcs3/Emu/RSX/VK/vkutils/device.h index de4a39e9b..66bdb9826 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/device.h +++ b/rpcs3/Emu/RSX/VK/vkutils/device.h @@ -126,6 +126,7 @@ namespace vk bool extended_device_fault = false; bool texture_compression_bc = false; bool portability = false; + bool provoking_vertex_last = false; } optional_features_support; friend class render_device; @@ -274,6 +275,7 @@ namespace vk bool get_synchronization2_support() const { return pgpu->optional_features_support.synchronization_2; } bool get_extended_device_fault_support() const { return pgpu->optional_features_support.extended_device_fault; } bool get_texture_compression_bc_support() const { return pgpu->optional_features_support.texture_compression_bc; } + bool get_provoking_vertex_last_support() const { return pgpu->optional_features_support.provoking_vertex_last; } u64 get_descriptor_update_after_bind_support() const { return pgpu->descriptor_indexing_support.update_after_bind_mask; } u32 get_descriptor_max_draw_calls() const { return pgpu->descriptor_max_draw_calls; } diff --git a/rpcs3/Emu/RSX/gcm_enums.h b/rpcs3/Emu/RSX/gcm_enums.h index 727f0b153..dd5e54fbd 100644 --- a/rpcs3/Emu/RSX/gcm_enums.h +++ b/rpcs3/Emu/RSX/gcm_enums.h @@ -455,6 +455,7 @@ namespace gcm RSX_SHADER_CONTROL_UNKNOWN1 = 0x00008000, // seemingly set when srgb packer is used?? // Custom + RSX_SHADER_CONTROL_FLAT_SHADING = 0x00000800, // Interpolate front/back colors using the provoking vertex RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION = 0x00010000, // Rasterizing triangles and not lines or points RSX_SHADER_CONTROL_INSTANCED_CONSTANTS = 0x00020000, // Support instance ID offsets when loading constants RSX_SHADER_CONTROL_INTERPRETER_MODEL = 0x00040000, // Compile internals expecting interpreter diff --git a/rpcs3/Emu/RSX/rsx_methods.cpp b/rpcs3/Emu/RSX/rsx_methods.cpp index 894dafbc6..76732b77e 100644 --- a/rpcs3/Emu/RSX/rsx_methods.cpp +++ b/rpcs3/Emu/RSX/rsx_methods.cpp @@ -1698,6 +1698,7 @@ namespace rsx bind(NV4097_WAIT_FOR_IDLE, nv4097::sync); bind(NV4097_INVALIDATE_L2, nv4097::set_shader_program_dirty); bind(NV4097_SET_SHADER_PROGRAM, nv4097::set_shader_program_dirty); + bind(NV4097_SET_SHADE_MODE, nv4097::set_shading_mode); bind(NV4097_SET_TRANSFORM_PROGRAM_START, nv4097::set_transform_program_start); bind(NV4097_SET_VERTEX_ATTRIB_OUTPUT_MASK, nv4097::set_vertex_attribute_output_mask); diff --git a/rpcs3/Emu/RSX/rsx_methods.h b/rpcs3/Emu/RSX/rsx_methods.h index f065aa393..660b1333b 100644 --- a/rpcs3/Emu/RSX/rsx_methods.h +++ b/rpcs3/Emu/RSX/rsx_methods.h @@ -503,6 +503,11 @@ namespace rsx return decode().line_smooth_enabled(); } + shading_mode shade_mode() const + { + return decode().shading(); + } + bool poly_offset_point_enabled() const { return decode().poly_offset_point_enabled(); diff --git a/rpcs3/Emu/system_utils.cpp b/rpcs3/Emu/system_utils.cpp index ad6008b5e..997325c23 100644 --- a/rpcs3/Emu/system_utils.cpp +++ b/rpcs3/Emu/system_utils.cpp @@ -378,8 +378,27 @@ namespace rpcs3::utils return file_list; } + static bool is_valid_content_id(std::string_view content_id) + { + if (content_id.empty() || content_id.size() > 0x30) + { + return false; + } + + return std::all_of(content_id.begin(), content_id.end(), [](char c) + { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_'; + }); + } + std::string get_rap_file_path(const std::string_view& rap) { + if (!is_valid_content_id(rap)) + { + sys_log.error("get_rap_file_path(): invalid content id '%s'", rap); + return {}; + } + const std::string home_dir = get_hdd0_dir() + "home"; std::string rap_path; @@ -402,6 +421,12 @@ namespace rpcs3::utils std::string get_c00_unlock_edat_path(const std::string_view& content_id) { + if (!is_valid_content_id(content_id)) + { + sys_log.error("get_c00_unlock_edat_path(): invalid content id '%s'", content_id); + return {}; + } + const std::string home_dir = get_hdd0_dir() + "home"; std::string edat_path; @@ -448,7 +473,7 @@ namespace rpcs3::utils return false; } - std::string edat_content_id = npd.content_id; + std::string edat_content_id{npd.get_content_id()}; if (edat_content_id != content_id) { diff --git a/rpcs3/Emu/vfs_config.cpp b/rpcs3/Emu/vfs_config.cpp index 0958c01b2..94e9acb6f 100644 --- a/rpcs3/Emu/vfs_config.cpp +++ b/rpcs3/Emu/vfs_config.cpp @@ -36,10 +36,12 @@ std::string cfg_vfs::get(std::string_view _cfg, std::string_view def, std::strin if (_emu_dir.empty()) { - _emu_dir = fs::get_config_dir() + '/'; + _emu_dir = fs::get_config_dir(); + ensure(!_emu_dir.empty()); } + // Check if path does not end with a delimiter - else if (_emu_dir.back() != fs::delim[0] && _emu_dir.back() != fs::delim[1]) + if (_emu_dir.back() != fs::delim[0] && _emu_dir.back() != fs::delim[1]) { _emu_dir += '/'; } diff --git a/rpcs3/Input/ps_move_calibration.cpp b/rpcs3/Input/ps_move_calibration.cpp index bac042de6..2c74f0815 100644 --- a/rpcs3/Input/ps_move_calibration.cpp +++ b/rpcs3/Input/ps_move_calibration.cpp @@ -1,6 +1,8 @@ #include "stdafx.h" #include "ps_move_calibration.h" +#include + LOG_CHANNEL(move_log, "Move"); // This is basically the same as in ps move api @@ -176,8 +178,7 @@ void psmove_calibration_get_usb_gyro_values(const reports::ps_move_calibration_b { const u8* data = calibration.data.data(); - constexpr f32 PI = 3.14159265f; - constexpr f32 rpm_to_rad_per_sec = (2.0f * PI) / 60.0f; + constexpr f32 rpm_to_rad_per_sec = (2.0f * std::numbers::pi_v) / 60.0f; switch (device.model) { diff --git a/rpcs3/Input/ps_move_tracker.cpp b/rpcs3/Input/ps_move_tracker.cpp index 5ed69b630..7e9346643 100644 --- a/rpcs3/Input/ps_move_tracker.cpp +++ b/rpcs3/Input/ps_move_tracker.cpp @@ -6,13 +6,11 @@ #include #ifdef HAVE_OPENCV -#include - -// OpenCV 5.x moved some functions to this header -#if __has_include() +#include +#include +#if CV_VERSION_MAJOR >= 5 #include #endif - #endif LOG_CHANNEL(ps_move); @@ -388,7 +386,7 @@ void ps_move_tracker::process_contours(ps_move_info& info, u // Simply drop dark and colorless pixels as well as pixels that don't match our hue if ((wrapped_hue ? (hue < config.min_hue && hue > config.max_hue) : (hue < config.min_hue || hue > config.max_hue)) || saturation < config.saturation_threshold_u8 || saturation > 200 || - value < 150 || value > 255) + value < 150) { dst[x] = 0; } @@ -436,19 +434,24 @@ void ps_move_tracker::process_contours(ps_move_info& info, u } std::vector> contours; - contours.reserve(all_contours.size()); - std::vector centers; - centers.reserve(all_contours.size()); - std::vector radii; - radii.reserve(all_contours.size()); + + if constexpr (DiagnosticsEnabled) + { + contours.reserve(all_contours.size()); + centers.reserve(all_contours.size()); + radii.reserve(all_contours.size()); + } const f32 min_radius = m_min_radius * width; const f32 max_radius = m_max_radius * width; usz best_index = umax; f32 best_area = 0.0f; + f32 best_radius = 0.0f; + cv::Point2f best_center {}; + for (usz i = 0; i < all_contours.size(); i++) { const std::vector& contour = all_contours[i]; @@ -465,18 +468,27 @@ void ps_move_tracker::process_contours(ps_move_info& info, u if (radius < min_radius || radius > max_radius) continue; - contours.push_back(std::move(all_contours[i])); - centers.push_back(std::move(center)); - radii.push_back(std::move(radius)); + if constexpr (DiagnosticsEnabled) + { + contours.push_back(std::move(all_contours[i])); + centers.push_back(center); + radii.push_back(radius); + } if (area > best_area) { best_area = area; - best_index = contours.size() - 1; + best_center = center; + best_radius = radius; + + if constexpr (DiagnosticsEnabled) + { + best_index = contours.size() - 1; + } } } - if (best_index == umax) + if (best_area <= 0.0f) { set_valid(info, index, false); @@ -488,7 +500,7 @@ void ps_move_tracker::process_contours(ps_move_info& info, u } // Calculate distance from sphere to camera - const f32 sphere_radius_pixels = radii[best_index]; + const f32 sphere_radius_pixels = best_radius; constexpr f32 focal_length_mm = 3.5f; // Based on common webcam specs constexpr f32 sensor_width_mm = 3.6f; // Based on common webcam specs const f32 image_width_pixels = static_cast(width); @@ -498,16 +510,20 @@ void ps_move_tracker::process_contours(ps_move_info& info, u // Set results set_valid(info, index, true); - const u32 x_pos = std::clamp(static_cast(centers[best_index].x), 0u, width); - const u32 y_pos = std::clamp(static_cast(centers[best_index].y), 0u, height); + const u32 x_pos = std::clamp(static_cast(best_center.x), 0u, width); + const u32 y_pos = std::clamp(static_cast(best_center.y), 0u, height); // Only set new values if the new shape and position are relatively similar to the old ones. - const auto distance_travelled = [](int x1, int y1, int x2, int y2) + const auto distance_squared = [](int x1, int y1, int x2, int y2) { - return std::sqrt(std::pow(x2 - x1, 2) + pow(y2 - y1, 2)); + const int dx = x2 - x1; + const int dy = y2 - y1; + return dx * dx + dy * dy; }; + const f32 max_distance = info.radius * 8.0f; + const f32 max_distance_squared = max_distance * max_distance; const bool shape_matches = std::abs(info.radius - sphere_radius_pixels) < (info.radius * 2) && - distance_travelled(info.x_pos, info.y_pos, x_pos, y_pos) < (info.radius * 8); + distance_squared(info.x_pos, info.y_pos, x_pos, y_pos) < max_distance_squared; if (shape_matches || ++m_shape_fail_count[index] >= 3) { @@ -534,8 +550,8 @@ void ps_move_tracker::process_contours(ps_move_info& info, u { std::vector contour = std::move(contours[best_index]); contours = { std::move(contour) }; - centers = { centers[best_index] }; - radii = { radii[best_index] }; + centers = { best_center }; + radii = { best_radius }; } static const cv::Scalar contour_color(255, 0, 0, 255); diff --git a/rpcs3/Loader/TRP.cpp b/rpcs3/Loader/TRP.cpp index 452b7c938..541b4d95f 100644 --- a/rpcs3/Loader/TRP.cpp +++ b/rpcs3/Loader/TRP.cpp @@ -1,11 +1,18 @@ #include "stdafx.h" #include "Emu/VFS.h" +#include "Emu/System.h" #include "TRP.h" #include "Crypto/sha1.h" #include "Utilities/StrUtil.h" LOG_CHANNEL(trp_log, "Trophy"); +static std::string get_entry_name(const TRPEntry& entry) +{ + const std::string_view name{entry.name, sizeof(entry.name)}; + return std::string{name.substr(0, name.find_first_of('\0'))}; +} + TRPLoader::TRPLoader(const fs::file& f) : m_file(f) { @@ -51,7 +58,16 @@ bool TRPLoader::Install(std::string_view dest, bool /*show*/) } // Create the file in the temporary directory - const std::string filename = temp + '/' + vfs::escape(entry.name); + const std::string entry_name = get_entry_name(entry); + const std::string filename = temp + '/' + vfs::escape(entry_name, true); + + if (!Emu.IsPathInsideDir(filename, temp, false)) + { + trp_log.error("Error extracting %s from TRP: target path '%s' would be extracted outside of '%s'", entry_name, filename, temp); + success = false; + break; + } + success = fs::write_file(filename, fs::create + fs::excl, buffer); if (!success) { @@ -160,7 +176,7 @@ bool TRPLoader::LoadHeader(bool show) { for (const auto& entry : m_entries) { - trp_log.notice("TRP entry #%u: %s", &entry - m_entries.data(), entry.name); + trp_log.notice("TRP entry #%u: %s", &entry - m_entries.data(), get_entry_name(entry)); } } @@ -184,7 +200,7 @@ bool TRPLoader::ContainsEntry(std::string_view filename) for (const TRPEntry& entry : m_entries) { - if (entry.name == filename) + if (get_entry_name(entry) == filename) { return true; } @@ -202,7 +218,7 @@ void TRPLoader::RemoveEntry(std::string_view filename) std::vector::iterator i = m_entries.begin(); while (i != m_entries.end()) { - if (i->name == filename) + if (get_entry_name(*i) == filename) { i = m_entries.erase(i); } @@ -222,7 +238,7 @@ void TRPLoader::RenameEntry(std::string_view oldname, std::string_view newname) for (TRPEntry& entry : m_entries) { - if (entry.name == oldname) + if (get_entry_name(entry) == oldname) { strcpy_trunc(entry.name, newname); } diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index ca4e3a3ab..08b3cb942 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -4309,7 +4309,7 @@ main_window::drop_type main_window::IsValidFile(const QMimeData& md, QStringList { return set_result(drop_type::drop_error); } - else if (info.suffix() == "PUP") + else if (suffix_lo == "pup") { if (m_drop_file_url_list.size() != 1) { diff --git a/rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp b/rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp index 218ccfc80..d2c81d2bc 100644 --- a/rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp +++ b/rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp @@ -14,6 +14,7 @@ #include #include #include +#include LOG_CHANNEL(ps_move); @@ -252,6 +253,8 @@ ps_move_tracker_dialog::ps_move_tracker_dialog(QWidget* parent) QMessageBox::warning(this, QObject::tr("Tracking not supported!"), QObject::tr("The PS Move tracking is not yet supported on this operating system.")); }); } + + ui->imageLabel->installEventFilter(this); } ps_move_tracker_dialog::~ps_move_tracker_dialog() @@ -273,6 +276,69 @@ ps_move_tracker_dialog::~ps_move_tracker_dialog() m_input_thread.reset(); } +bool ps_move_tracker_dialog::eventFilter(QObject* object, QEvent* event) +{ + if (event && object == ui->imageLabel && event->type() == QEvent::Type::MouseButtonRelease && m_view_mode == view_mode::contours) + { + const QMouseEvent* me = reinterpret_cast(event); + + if (me->button() == Qt::MouseButton::LeftButton) + { + ps_move.notice("Selecting color with mouse click."); + + f32 r = 0.0f; + f32 g = 0.0f; + f32 b = 0.0f; + u32 count = 0; + + { + std::lock_guard lock(m_image_mutex); + + const auto pos = me->localPos().toPoint(); + const QPixmap pixmap = ui->imageLabel->pixmap(); + + if (pixmap.rect().contains(pos)) + { + const QImage image = pixmap.toImage(); + constexpr int radius = 3; + + for (int y = pos.y() - radius; y <= pos.y() + radius; ++y) + { + for (int x = pos.x() - radius; x <= pos.x() + radius; ++x) + { + if (!image.rect().contains(x, y)) + continue; + + const QColor color = image.pixelColor(x, y); + + r += color.redF(); + g += color.greenF(); + b += color.blueF(); + count++; + } + } + } + } + + if (count > 0) + { + r /= count; + g /= count; + b /= count; + + ps_move.notice("Selected new color with mouse click (r=%f, b=%f, g=%f)", r, g, b); + + cfg_ps_move* config = ::at32(g_cfg_move.move, m_index); + const auto [hue, saturation, value] = ps_move_tracker::rgb_to_hsv(r, g, b); + config->hue.set(std::clamp(hue, config->hue.min, config->hue.max)); + update_hue(true); + } + } + } + + return QDialog::eventFilter(object, event); +} + void ps_move_tracker_dialog::update_color(bool update_sliders) { cfg_ps_move* config = ::at32(g_cfg_move.move, m_index); diff --git a/rpcs3/rpcs3qt/ps_move_tracker_dialog.h b/rpcs3/rpcs3qt/ps_move_tracker_dialog.h index fe3b043b2..d01d72981 100644 --- a/rpcs3/rpcs3qt/ps_move_tracker_dialog.h +++ b/rpcs3/rpcs3qt/ps_move_tracker_dialog.h @@ -26,6 +26,8 @@ public: virtual ~ps_move_tracker_dialog(); private: + bool eventFilter(QObject* object, QEvent* event) override; + void update_color(bool update_sliders = false); void update_hue(bool update_slider = false); void update_hue_threshold(bool update_slider = false);