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