Compare commits

..

2 Commits

Author SHA1 Message Date
Elad 4f37124c1d LLVM: Add short fallback for get_known_bits
Supports: when value is the immediate result of either a bitwise OR operation or a bitwise AND when either operands is a constant.

Prevents some false positives when the value has PHI nodes in its ancestors.
2026-07-21 18:42:57 +03:00
RipleyTom 24e1ce4ceb Fix: get_known_bits fix 2026-07-21 17:44:40 +03:00
12 changed files with 74 additions and 678 deletions
-12
View File
@@ -26,7 +26,6 @@ std::string g_android_cache_dir;
#include <cwchar> #include <cwchar>
#include <Windows.h> #include <Windows.h>
#include <winioctl.h>
static std::unique_ptr<wchar_t[]> to_wchar(std::string_view source) static std::unique_ptr<wchar_t[]> to_wchar(std::string_view source)
{ {
@@ -1964,17 +1963,6 @@ fs::native_handle fs::file::get_handle() const
#endif #endif
} }
bool fs::set_sparse([[maybe_unused]] const fs::file& file)
{
#ifdef _WIN32
FILE_SET_SPARSE_BUFFER sparse{TRUE};
DWORD returned = 0;
return DeviceIoControl(file.get_handle(), FSCTL_SET_SPARSE, &sparse, sizeof(sparse), nullptr, 0, &returned, nullptr) != FALSE;
#else
return true;
#endif
}
fs::file_id fs::file::get_id() const fs::file_id fs::file::get_id() const
{ {
if (m_file) if (m_file)
-3
View File
@@ -496,9 +496,6 @@ namespace fs
} }
}; };
// Enable sparse-file semantics when required by the host platform.
bool set_sparse(const file& file);
class dir final class dir final
{ {
std::unique_ptr<dir_base> m_dir{}; std::unique_ptr<dir_base> m_dir{};
+24 -23
View File
@@ -317,9 +317,6 @@ llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type, std::so
template <> template <>
std::pair<bool, v128> cpu_translator::get_const_vector<v128>(llvm::Value* c, u32 _pos, u32 _line) std::pair<bool, v128> cpu_translator::get_const_vector<v128>(llvm::Value* c, u32 _pos, u32 _line)
{ {
// Bitcasts do not matter
c = peek_through_bitcasts(c);
v128 result{}; v128 result{};
if (!llvm::isa<llvm::Constant>(c)) if (!llvm::isa<llvm::Constant>(c))
@@ -621,7 +618,7 @@ llvm::KnownBits cpu_translator::get_known_bits_fallback(llvm::Value* value)
if (!type->isVectorTy()) if (!type->isVectorTy())
{ {
if (llvm::isa<llvm::IntegerType>(type)) if (const auto it = llvm::dyn_cast<llvm::IntegerType>(type))
{ {
if (auto bin_inst = llvm::dyn_cast<llvm::BinaryOperator>(value)) if (auto bin_inst = llvm::dyn_cast<llvm::BinaryOperator>(value))
{ {
@@ -663,7 +660,20 @@ llvm::KnownBits cpu_translator::get_known_bits_fallback(llvm::Value* value)
return llvm::KnownBits(type->getScalarSizeInBits()); return llvm::KnownBits(type->getScalarSizeInBits());
} }
const auto cv = llvm::dyn_cast<llvm::ConstantDataVector>(value);
if (!cv)
{
if (llvm::isa<llvm::ConstantAggregateZero>(value))
{
llvm::KnownBits ret(type->getScalarSizeInBits());
ret.Zero.setAllBits();
return ret;
}
}
const auto original_value = peek_through_bitcasts(value); const auto original_value = peek_through_bitcasts(value);
const auto original_type = original_value->getType();
auto bin_inst = llvm::dyn_cast<llvm::BinaryOperator>(original_value); auto bin_inst = llvm::dyn_cast<llvm::BinaryOperator>(original_value);
@@ -686,40 +696,31 @@ llvm::KnownBits cpu_translator::get_known_bits_fallback(llvm::Value* value)
ensure(ok); ensure(ok);
llvm::APInt all_lanes{}; llvm::APInt dest{};
llvm::APInt any_lanes{};
auto combine_bits = [&](const auto& array, u32 size) auto combine_bits = [&](const auto& array, u32 size)
{ {
auto all = +array[0]; auto first = +array[0];
auto any = +array[0];
for (u32 i = 1; i < size; i++) for (u32 i = 0; i < size; i++)
{ {
all &= +array[i]; first &= +array[i];
any |= +array[i];
} }
return std::make_pair(all, any); return first;
}; };
if (type->getScalarType()->isIntegerTy(8)) if (type->getScalarType()->isIntegerTy(8))
{ {
const auto [all, any] = combine_bits(v128_const._u8, 16); dest = llvm::APInt(8, combine_bits(v128_const._u8, 16));
all_lanes = llvm::APInt(8, all);
any_lanes = llvm::APInt(8, any);
} }
else if (type->getScalarType()->isIntegerTy(16)) else if (type->getScalarType()->isIntegerTy(16))
{ {
const auto [all, any] = combine_bits(v128_const._u16, 8); dest = llvm::APInt(16, combine_bits(v128_const._u16, 8));
all_lanes = llvm::APInt(16, all);
any_lanes = llvm::APInt(16, any);
} }
else if (type->getScalarType()->isIntegerTy(32)) else if (type->getScalarType()->isIntegerTy(32))
{ {
const auto [all, any] = combine_bits(v128_const._u32, 4); dest = llvm::APInt(32, combine_bits(v128_const._u32, 4));
all_lanes = llvm::APInt(32, all);
any_lanes = llvm::APInt(32, any);
} }
else // if (type->getScalarType()->isIntegerTy(64)) else // if (type->getScalarType()->isIntegerTy(64))
{ {
@@ -729,14 +730,14 @@ llvm::KnownBits cpu_translator::get_known_bits_fallback(llvm::Value* value)
if (bin_inst->getOpcode() == llvm::Instruction::Or) if (bin_inst->getOpcode() == llvm::Instruction::Or)
{ {
llvm::KnownBits ret(type->getScalarSizeInBits()); llvm::KnownBits ret(type->getScalarSizeInBits());
ret.One = all_lanes; ret.One = dest;
return ret; return ret;
} }
if (bin_inst->getOpcode() == llvm::Instruction::And) if (bin_inst->getOpcode() == llvm::Instruction::And)
{ {
llvm::KnownBits ret(type->getScalarSizeInBits()); llvm::KnownBits ret(type->getScalarSizeInBits());
ret.Zero = any_lanes; ret.Zero = dest;
ret.Zero.flipAllBits(); ret.Zero.flipAllBits();
return ret; return ret;
} }
+31 -49
View File
@@ -70,12 +70,9 @@ DYNAMIC_IMPORT("ntdll.dll", NtSetTimerResolution, NTSTATUS(ULONG DesiredResoluti
#include "Emu/System.h" #include "Emu/System.h"
#include "Emu/system_config.h" #include "Emu/system_config.h"
#include "Emu/system_utils.hpp" #include "Emu/system_utils.hpp"
#include "Emu/savestate_utils.hpp"
#include "Emu/RSX/Overlays/overlay_message.h" #include "Emu/RSX/Overlays/overlay_message.h"
#include <thread> #include <thread>
#include <charconv> #include <charconv>
#include <regex>
#include "util/sysinfo.hpp" #include "util/sysinfo.hpp"
@@ -387,37 +384,36 @@ private:
}; };
// Arguments that force a headless application (need to be checked in create_application) // Arguments that force a headless application (need to be checked in create_application)
constexpr auto arg_headless = "headless"; constexpr auto arg_headless = "headless";
constexpr auto arg_decrypt = "decrypt"; constexpr auto arg_decrypt = "decrypt";
// Arguments that can be used with a gui application // Arguments that can be used with a gui application
constexpr auto arg_no_gui = "no-gui"; constexpr auto arg_no_gui = "no-gui";
constexpr auto arg_fullscreen = "fullscreen"; // only useful with no-gui constexpr auto arg_fullscreen = "fullscreen"; // only useful with no-gui
constexpr auto arg_gs_screen = "game-screen"; constexpr auto arg_gs_screen = "game-screen";
constexpr auto arg_high_dpi = "hidpi"; constexpr auto arg_high_dpi = "hidpi";
constexpr auto arg_rounding = "dpi-rounding"; constexpr auto arg_rounding = "dpi-rounding";
constexpr auto arg_styles = "styles"; constexpr auto arg_styles = "styles";
constexpr auto arg_style = "style"; constexpr auto arg_style = "style";
constexpr auto arg_stylesheet = "stylesheet"; constexpr auto arg_stylesheet = "stylesheet";
constexpr auto arg_config = "config"; constexpr auto arg_config = "config";
constexpr auto arg_input_config = "input-config"; // only useful with no-gui constexpr auto arg_input_config = "input-config"; // only useful with no-gui
constexpr auto arg_q_debug = "qDebug"; constexpr auto arg_q_debug = "qDebug";
constexpr auto arg_error = "error"; constexpr auto arg_error = "error";
constexpr auto arg_updating = "updating"; constexpr auto arg_updating = "updating";
constexpr auto arg_user_id = "user-id"; constexpr auto arg_user_id = "user-id";
constexpr auto arg_installfw = "installfw"; constexpr auto arg_installfw = "installfw";
constexpr auto arg_installpkg = "installpkg"; constexpr auto arg_installpkg = "installpkg";
constexpr auto arg_savestate = "savestate"; constexpr auto arg_savestate = "savestate";
constexpr auto arg_last_savestate = "last-savestate"; constexpr auto arg_rsx_capture = "rsx-capture";
constexpr auto arg_rsx_capture = "rsx-capture"; constexpr auto arg_timer = "high-res-timer";
constexpr auto arg_timer = "high-res-timer"; constexpr auto arg_verbose_curl = "verbose-curl";
constexpr auto arg_verbose_curl = "verbose-curl"; constexpr auto arg_any_location = "allow-any-location";
constexpr auto arg_any_location = "allow-any-location"; constexpr auto arg_codecs = "codecs";
constexpr auto arg_codecs = "codecs";
#ifdef _WIN32 #ifdef _WIN32
constexpr auto arg_stdout = "stdout"; constexpr auto arg_stdout = "stdout";
constexpr auto arg_stderr = "stderr"; constexpr auto arg_stderr = "stderr";
#endif #endif
constexpr auto arg_emulation_barrier = ""; constexpr auto arg_emulation_barrier = "";
@@ -843,8 +839,6 @@ int run_rpcs3(int argc, char** argv)
parser.addOption(user_id_option); parser.addOption(user_id_option);
const QCommandLineOption savestate_option(arg_savestate, "Path for directly loading a savestate.", "path", ""); const QCommandLineOption savestate_option(arg_savestate, "Path for directly loading a savestate.", "path", "");
parser.addOption(savestate_option); parser.addOption(savestate_option);
const QCommandLineOption last_savestate_option(arg_last_savestate, "Loading the last savestate of a game.", "path", "Title-ID or path");
parser.addOption(last_savestate_option);
const QCommandLineOption rsx_capture_option(arg_rsx_capture, "Path for directly loading an rsx capture.", "path", ""); const QCommandLineOption rsx_capture_option(arg_rsx_capture, "Path for directly loading an rsx capture.", "path", "");
parser.addOption(rsx_capture_option); parser.addOption(rsx_capture_option);
parser.addOption(QCommandLineOption(arg_q_debug, "Log qDebug to RPCS3.log.")); parser.addOption(QCommandLineOption(arg_q_debug, "Log qDebug to RPCS3.log."));
@@ -1202,26 +1196,14 @@ int run_rpcs3(int argc, char** argv)
} }
} }
if (parser.isSet(arg_savestate) || parser.isSet(arg_last_savestate)) if (parser.isSet(arg_savestate))
{ {
std::string savestate_path; const std::string savestate_path = parser.value(savestate_option).toStdString();
sys_log.notice("Booting savestate from command line: %s", savestate_path);
if (parser.isSet(arg_savestate)) if (!fs::is_file(savestate_path))
{ {
savestate_path = parser.value(savestate_option).toStdString(); report_fatal_error(fmt::format("No savestate file found: %s", savestate_path));
sys_log.notice("Booting savestate from command line: path='%s'", savestate_path);
}
else
{
const std::string serial_or_path = parser.value(last_savestate_option).toStdString();
const bool is_serial = std::regex_match(serial_or_path, std::regex(R"(^[A-Z]{4}\d{5}$)"));
savestate_path = get_savestate_file(is_serial ? serial_or_path : "", is_serial ? "" : serial_or_path, 1);
sys_log.notice("Booting last savestate from command line: game='%s', path='%s'", serial_or_path, savestate_path);
}
if (!is_savestate_compatible(savestate_path))
{
report_fatal_error(fmt::format("No savestate file found or savestate not compatible: path='%s'", savestate_path));
} }
Emu.CallFromMainThread([path = savestate_path]() Emu.CallFromMainThread([path = savestate_path]()
-17
View File
@@ -230,9 +230,6 @@
<ClCompile Include="QTGeneratedFiles\Debug\moc_anaglyph_settings_dialog.cpp"> <ClCompile Include="QTGeneratedFiles\Debug\moc_anaglyph_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_guest_memory_dumper.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_breakpoint_list.cpp"> <ClCompile Include="QTGeneratedFiles\Debug\moc_breakpoint_list.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
@@ -545,9 +542,6 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_anaglyph_settings_dialog.cpp"> <ClCompile Include="QTGeneratedFiles\Release\moc_anaglyph_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_guest_memory_dumper.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_breakpoint_list.cpp"> <ClCompile Include="QTGeneratedFiles\Release\moc_breakpoint_list.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
@@ -851,7 +845,6 @@
<ClCompile Include="rpcs3qt\call_stack_list.cpp" /> <ClCompile Include="rpcs3qt\call_stack_list.cpp" />
<ClCompile Include="rpcs3qt\camera_settings_dialog.cpp" /> <ClCompile Include="rpcs3qt\camera_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\emu_settings_type.cpp" /> <ClCompile Include="rpcs3qt\emu_settings_type.cpp" />
<ClCompile Include="rpcs3qt\guest_memory_dumper.cpp" />
<ClCompile Include="rpcs3qt\gui_game_info.cpp" /> <ClCompile Include="rpcs3qt\gui_game_info.cpp" />
<ClCompile Include="rpcs3qt\log_level_dialog.cpp" /> <ClCompile Include="rpcs3qt\log_level_dialog.cpp" />
<ClCompile Include="rpcs3qt\permissions.cpp" /> <ClCompile Include="rpcs3qt\permissions.cpp" />
@@ -1215,16 +1208,6 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs> <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command> <Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
</CustomBuild> </CustomBuild>
<CustomBuild Include="rpcs3qt\guest_memory_dumper.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\breakpoint_handler.h" /> <ClInclude Include="rpcs3qt\breakpoint_handler.h" />
<CustomBuild Include="rpcs3qt\breakpoint_list.h"> <CustomBuild Include="rpcs3qt\breakpoint_list.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs> <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
+9 -19
View File
@@ -585,12 +585,6 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_anaglyph_settings_dialog.cpp"> <ClCompile Include="QTGeneratedFiles\Release\moc_anaglyph_settings_dialog.cpp">
<Filter>Generated Files\Release</Filter> <Filter>Generated Files\Release</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_guest_memory_dumper.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_guest_memory_dumper.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_breakpoint_list.cpp"> <ClCompile Include="QTGeneratedFiles\Debug\moc_breakpoint_list.cpp">
<Filter>Generated Files\Debug</Filter> <Filter>Generated Files\Debug</Filter>
</ClCompile> </ClCompile>
@@ -1320,9 +1314,6 @@
<ClCompile Include="rpcs3qt\anaglyph_settings_dialog.cpp"> <ClCompile Include="rpcs3qt\anaglyph_settings_dialog.cpp">
<Filter>Gui\settings</Filter> <Filter>Gui\settings</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="rpcs3qt\guest_memory_dumper.cpp">
<Filter>Gui\utils</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h"> <ClInclude Include="Input\ds4_pad_handler.h">
@@ -1574,7 +1565,7 @@
<ClInclude Include="rpcs3qt\steam_utils.h"> <ClInclude Include="rpcs3qt\steam_utils.h">
<Filter>Gui\utils</Filter> <Filter>Gui\utils</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="rpcs3qt\render_creator.h"> <ClInclude Include="rpcs3qt\anaglyph_settings_dialog.h">
<Filter>Gui\settings</Filter> <Filter>Gui\settings</Filter>
</ClInclude> </ClInclude>
</ItemGroup> </ItemGroup>
@@ -1792,6 +1783,9 @@
<CustomBuild Include="rpcs3qt\microphone_creator.h"> <CustomBuild Include="rpcs3qt\microphone_creator.h">
<Filter>Gui\settings</Filter> <Filter>Gui\settings</Filter>
</CustomBuild> </CustomBuild>
<CustomBuild Include="rpcs3qt\render_creator.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\patch_manager_dialog.h"> <CustomBuild Include="rpcs3qt\patch_manager_dialog.h">
<Filter>Gui\patch manager</Filter> <Filter>Gui\patch manager</Filter>
</CustomBuild> </CustomBuild>
@@ -1933,15 +1927,6 @@
<CustomBuild Include="rpcs3qt\game_list_actions.h"> <CustomBuild Include="rpcs3qt\game_list_actions.h">
<Filter>Gui\game list</Filter> <Filter>Gui\game list</Filter>
</CustomBuild> </CustomBuild>
<CustomBuild Include="rpcs3qt\content_integrity.h">
<Filter>Gui\game list</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\anaglyph_settings_dialog.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\guest_memory_dumper.h">
<Filter>Gui\utils</Filter>
</CustomBuild>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Image Include="rpcs3.ico" /> <Image Include="rpcs3.ico" />
@@ -2097,4 +2082,9 @@
<Filter>buildfiles\cmake</Filter> <Filter>buildfiles\cmake</Filter>
</Text> </Text>
</ItemGroup> </ItemGroup>
<ItemGroup>
<CustomBuild Include="rpcs3qt\content_integrity.h">
<Filter>Gui\game list</Filter>
</CustomBuild>
</ItemGroup>
</Project> </Project>
-1
View File
@@ -43,7 +43,6 @@ add_library(rpcs3_ui STATIC
game_list_grid.cpp game_list_grid.cpp
game_list_grid_item.cpp game_list_grid_item.cpp
game_list_table.cpp game_list_table.cpp
guest_memory_dumper.cpp
gui_application.cpp gui_application.cpp
gl_gs_frame.cpp gl_gs_frame.cpp
gs_frame.cpp gs_frame.cpp
-479
View File
@@ -1,479 +0,0 @@
#include "stdafx.h"
#include "guest_memory_dumper.h"
#include "progress_dialog.h"
#include "Emu/Cell/PPUThread.h"
#include "Emu/Cell/SPUThread.h"
#include "Emu/Memory/vm.h"
#include "Emu/RSX/RSXThread.h"
#include "Emu/System.h"
#include <QDateTime>
#include <QDir>
#include <QElapsedTimer>
#include <QFile>
#include <QFileDialog>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMessageBox>
#include <QPushButton>
#include <QStorageInfo>
LOG_CHANNEL(gui_log, "GUI");
extern void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wrapped_op);
guest_memory_dumper::guest_memory_dumper(QWidget* parent, bool delete_later)
: QObject(parent), m_parent(parent), m_delete_later(delete_later)
{
}
// Emu.Pause() only requests the pause: every emulated thread drains into its
// wait or stop state asynchronously. The dump must not begin until each PPU,
// SPU and RSX thread has actually settled, or it could capture torn state.
bool guest_memory_dumper::emulated_processors_quiesced()
{
bool quiesced = true;
const auto check_cpu = [&](u32, cpu_thread& cpu)
{
const auto state = +cpu.state;
if (!::is_stopped(state) && !(state & cpu_flag::wait))
{
quiesced = false;
}
};
if (g_fxo->is_init<id_manager::id_map<named_thread<ppu_thread>>>())
{
idm::select<named_thread<ppu_thread>>(check_cpu);
}
if (g_fxo->is_init<id_manager::id_map<named_thread<spu_thread>>>())
{
idm::select<named_thread<spu_thread>>(check_cpu);
}
if (const auto rsx = g_fxo->try_get<rsx::thread>())
{
check_cpu(0, *rsx);
}
return quiesced;
}
QString guest_memory_dumper::hex_u64(u64 value, int width)
{
return QStringLiteral("0x%1").arg(value, width, 16, QLatin1Char('0'));
}
void guest_memory_dumper::dump_guest_memory()
{
if (Emu.IsStopped() || Emu.IsStarting())
{
QMessageBox::warning(m_parent, tr("Dump Guest Memory"), tr("Start a game before creating a guest-memory dump."));
return;
}
const QString default_parent = QString::fromStdString(fs::get_config_dir() + "guest_memory_dumps");
QDir().mkpath(default_parent);
QString title_id = QString::fromStdString(Emu.GetTitleID());
if (title_id.isEmpty())
{
title_id = QStringLiteral("RPCS3");
}
// Remove the output folder again unless the dump ran to completion, so a
// failed or cancelled dump does not leave an empty folder or a partial
// image behind. This is declared before the output file so it destructs
// after the file is closed.
struct cleanup_guard
{
QDir dir;
bool enabled = false;
bool keep = false;
~cleanup_guard()
{
if (enabled && !keep && !dir.removeRecursively())
{
gui_log.error("Could not remove incomplete guest memory dump folder: %s", dir.absolutePath());
}
}
} cleanup;
QString picker_parent = default_parent;
QDir output;
fs::file guest_file;
bool sparse_image = false;
while (!sparse_image)
{
const QString selected_parent = QFileDialog::getExistingDirectory(m_parent, tr("Select Guest Memory Dump Folder"), picker_parent, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (selected_parent.isEmpty())
{
return;
}
// The native folder dialog runs its own event loop, so the game may have
// stopped while the user was choosing a destination.
if (Emu.IsStopped() || Emu.IsStarting())
{
QMessageBox::warning(m_parent, tr("Dump Guest Memory"), tr("The game stopped before the guest-memory dump could begin."));
return;
}
const QString timestamp = QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMdd_HHmmss_zzz"));
const QString folder_stem = QStringLiteral("%1_guest_memory_%2").arg(title_id, timestamp);
QDir parent(selected_parent);
QString folder_name = folder_stem;
for (u32 suffix = 2; parent.exists(folder_name); suffix++)
{
folder_name = QStringLiteral("%1_%2").arg(folder_stem).arg(suffix);
}
if (!parent.mkpath(folder_name))
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not create the output folder:\n%0").arg(parent.filePath(folder_name)));
return;
}
output.setPath(parent.filePath(folder_name));
cleanup.dir = output;
cleanup.enabled = true;
const QString guest_path = output.filePath(QStringLiteral("guest_memory.bin"));
if (!guest_file.open(guest_path.toStdString(), fs::rewrite))
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not create:\n%0").arg(guest_path));
return;
}
// POSIX file systems create holes for unwritten ranges by default; Windows
// needs the sparse attribute set explicitly.
sparse_image = fs::set_sparse(guest_file);
if (!sparse_image)
{
guest_file.close();
if (!output.removeRecursively())
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("The selected destination does not support sparse files, and the incomplete output folder could not be removed:\n%0").arg(output.absolutePath()));
return;
}
cleanup.enabled = false;
QMessageBox message(QMessageBox::Critical, tr("Dump Guest Memory"), tr("The selected destination does not support sparse files. Choose a location on an NTFS volume."), QMessageBox::NoButton, m_parent);
QPushButton* choose_button = message.addButton(tr("Choose Another Location"), QMessageBox::AcceptRole);
message.addButton(QMessageBox::Cancel);
message.setDefaultButton(choose_button);
message.exec();
if (message.clickedButton() != choose_button)
{
return;
}
picker_parent = selected_parent;
}
}
const bool resume_after_dump = Emu.IsRunning();
if (resume_after_dump && !Emu.Pause(false, false))
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not pause emulation for a consistent memory snapshot."));
return;
}
struct resume_guard
{
bool enabled = false;
~resume_guard()
{
if (enabled)
{
Emu.Resume();
}
}
} resume{resume_after_dump};
bool quiesced = false;
QElapsedTimer pause_timer;
pause_timer.start();
qt_events_aware_op(5, [&]()
{
quiesced = emulated_processors_quiesced();
return quiesced || pause_timer.elapsed() >= 5000;
});
if (!quiesced)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("One or more emulated processors did not stop within five seconds. No dump was created."));
return;
}
std::vector<guest_memory_region> regions;
guest_memory_region current{};
const auto finish_region = [&]()
{
if (current.size)
{
regions.emplace_back(current);
current = {};
}
};
// Walk the guest page table and coalesce contiguous pages with identical
// flags into regions, so the manifest mirrors the guest memory map instead
// of listing a million single pages.
for (u32 page = 0; page < guest_address_space_size / guest_page_size; page++)
{
const u32 address = static_cast<u32>(static_cast<u64>(page) * guest_page_size);
const auto [allocated, flags] = vm::get_addr_flags(address);
if (!allocated)
{
finish_region();
continue;
}
if (current.size && (current.flags != flags || static_cast<u64>(current.start) + current.size != address))
{
finish_region();
}
if (!current.size)
{
current.start = address;
current.flags = flags;
}
current.size += guest_page_size;
}
finish_region();
// SPU local store is also part of the guest map (at each thread's
// vm_offset), but a per thread copy with id, pc and type recorded in the
// manifest is much easier to analyze than digging it out of the main image.
std::vector<spu_local_store_dump> spu_dumps;
if (g_fxo->is_init<id_manager::id_map<named_thread<spu_thread>>>())
{
idm::select<named_thread<spu_thread>>([&](u32 id, spu_thread& spu)
{
spu_local_store_dump dump;
dump.id = id;
dump.lv2_id = spu.lv2_id;
dump.index = spu.index;
dump.pc = spu.pc;
dump.vm_offset = spu.vm_offset();
dump.type = static_cast<u32>(spu.get_type());
dump.name = spu.get_name();
spu_dumps.emplace_back(std::move(dump));
});
}
u64 guest_bytes = 0;
for (const auto& region : regions)
{
guest_bytes += region.size;
}
const u64 total_bytes = guest_bytes + static_cast<u64>(spu_dumps.size()) * SPU_LS_SIZE;
// Preflight the destination. A sparse image costs roughly the allocated
// bytes, so warn when even that plus some margin does not fit.
const QStorageInfo storage(output.absolutePath());
const u64 available = storage.bytesAvailable() > 0 ? static_cast<u64>(storage.bytesAvailable()) : 0;
if (const u64 needed = total_bytes + (64ull << 20); available < needed)
{
if (QMessageBox::question(m_parent, tr("Dump Guest Memory"), tr("The destination may not have enough free space (roughly %0 MiB needed, %1 MiB available).\n\nContinue anyway?").arg(needed >> 20).arg(available >> 20)) != QMessageBox::Yes)
{
return;
}
}
atomic_t<u64> completed_bytes{0};
progress_dialog progress(tr("Dump Guest Memory"), tr("Dumping PS3 guest memory..."), tr("Cancel"), 0, 1000, false, m_parent);
progress.setCancelButton(nullptr);
progress.setMinimumDuration(0);
if (!guest_file.trunc(guest_address_space_size))
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not size the 4 GB address image."));
return;
}
// Read through the sudo mirror: it maps every allocated page as read/write,
// so pages the guest keeps protected (reservation notification, no access
// areas) are captured as well instead of faulting or being skipped. Copy
// into ordinary host memory before passing data to fs::file, and perform all
// guest-memory reads and file writes off the UI thread.
enum class dump_write_error
{
none,
guest_seek,
guest_write,
spu_write,
};
dump_write_error write_error = dump_write_error::none;
u64 error_address = 0;
QString error_path;
const QString output_path = output.absolutePath();
named_thread dump_thread("Guest Memory Dumper", [&]()
{
std::vector<u8> middle_buffer(dump_io_chunk_size);
for (const auto& region : regions)
{
if (guest_file.seek(region.start) != region.start)
{
write_error = dump_write_error::guest_seek;
error_address = region.start;
return;
}
for (u64 offset = 0; offset < region.size;)
{
const u64 chunk_size = std::min(dump_io_chunk_size, region.size - offset);
const u64 address = static_cast<u64>(region.start) + offset;
std::memcpy(middle_buffer.data(), vm::g_sudo_addr + address, chunk_size);
if (guest_file.write(middle_buffer.data(), chunk_size) != chunk_size)
{
write_error = dump_write_error::guest_write;
error_address = address;
return;
}
offset += chunk_size;
completed_bytes.fetch_add(chunk_size);
}
}
guest_file.close();
QDir worker_output(output_path);
for (const auto& dump : spu_dumps)
{
const QString filename = QStringLiteral("spu_%1_ls.bin").arg(dump.id, 8, 16, QLatin1Char('0'));
const QString file_path = worker_output.filePath(filename);
std::memcpy(middle_buffer.data(), vm::g_sudo_addr + dump.vm_offset, SPU_LS_SIZE);
fs::file file(file_path.toStdString(), fs::rewrite);
if (!file || file.write(middle_buffer.data(), SPU_LS_SIZE) != SPU_LS_SIZE)
{
write_error = dump_write_error::spu_write;
error_path = file_path;
return;
}
completed_bytes.fetch_add(SPU_LS_SIZE);
}
});
qt_events_aware_op(10, [&]()
{
const u64 completed = completed_bytes.load();
progress.SetValue(total_bytes ? static_cast<int>(completed * 1000 / total_bytes) : 1000);
return static_cast<thread_state>(dump_thread) == thread_state::finished;
});
dump_thread();
if (write_error == dump_write_error::guest_seek)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not seek to guest address %0 in the output file.").arg(hex_u64(error_address)));
return;
}
if (write_error == dump_write_error::guest_write)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Writing failed at guest address %0. The dump is incomplete.").arg(hex_u64(error_address)));
return;
}
if (write_error == dump_write_error::spu_write)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not write SPU local store:\n%0").arg(error_path));
return;
}
QJsonArray spu_json;
for (const auto& dump : spu_dumps)
{
const QString filename = QStringLiteral("spu_%1_ls.bin").arg(dump.id, 8, 16, QLatin1Char('0'));
QJsonObject item;
item.insert(QStringLiteral("file"), filename);
item.insert(QStringLiteral("id"), hex_u64(dump.id));
item.insert(QStringLiteral("lv2_id"), hex_u64(dump.lv2_id));
item.insert(QStringLiteral("index"), static_cast<int>(dump.index));
item.insert(QStringLiteral("pc"), hex_u64(dump.pc, 5));
item.insert(QStringLiteral("vm_offset"), hex_u64(dump.vm_offset));
item.insert(QStringLiteral("type"), static_cast<int>(dump.type));
item.insert(QStringLiteral("name"), QString::fromStdString(dump.name));
item.insert(QStringLiteral("size"), static_cast<int>(SPU_LS_SIZE));
spu_json.append(item);
}
QJsonArray region_json;
for (const auto& region : regions)
{
QJsonObject item;
item.insert(QStringLiteral("start"), hex_u64(region.start));
item.insert(QStringLiteral("end_exclusive"), hex_u64(static_cast<u64>(region.start) + region.size));
item.insert(QStringLiteral("size"), static_cast<double>(region.size));
item.insert(QStringLiteral("file_offset"), hex_u64(region.start));
item.insert(QStringLiteral("raw_page_flags"), static_cast<int>(region.flags));
item.insert(QStringLiteral("readable"), !!(region.flags & vm::page_readable));
item.insert(QStringLiteral("writable"), !!(region.flags & vm::page_writable));
item.insert(QStringLiteral("executable"), !!(region.flags & vm::page_executable));
region_json.append(item);
}
QJsonObject manifest;
manifest.insert(QStringLiteral("format"), QStringLiteral("rpcs3-guest-memory-dump"));
manifest.insert(QStringLiteral("version"), 1);
manifest.insert(QStringLiteral("created_utc"), QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs));
manifest.insert(QStringLiteral("title"), QString::fromStdString(Emu.GetTitle()));
manifest.insert(QStringLiteral("title_id"), QString::fromStdString(Emu.GetTitleID()));
manifest.insert(QStringLiteral("address_space_file"), QStringLiteral("guest_memory.bin"));
manifest.insert(QStringLiteral("address_space_size"), hex_u64(guest_address_space_size, 9));
manifest.insert(QStringLiteral("page_size"), static_cast<int>(guest_page_size));
manifest.insert(QStringLiteral("allocated_bytes"), static_cast<double>(guest_bytes));
manifest.insert(QStringLiteral("file_offset_equals_guest_address"), true);
manifest.insert(QStringLiteral("sparse_image"), sparse_image);
manifest.insert(QStringLiteral("regions"), region_json);
manifest.insert(QStringLiteral("spu_local_stores"), spu_json);
QFile manifest_file(output.filePath(QStringLiteral("manifest.json")));
if (!manifest_file.open(QIODevice::WriteOnly | QIODevice::Truncate) || manifest_file.write(QJsonDocument(manifest).toJson(QJsonDocument::Indented)) < 0)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not write the dump manifest."));
return;
}
manifest_file.close();
cleanup.keep = true;
progress.SetValue(1000);
gui_log.success("Guest memory dump complete: %s (%u regions, %u SPU local stores, %u bytes allocated)", output.absolutePath(), regions.size(), spu_dumps.size(), guest_bytes);
if (resume.enabled)
{
Emu.Resume();
resume.enabled = false;
}
QMessageBox::information(m_parent, tr("Dump Guest Memory"), tr("Guest memory dump completed.\n\n%0\n\nThe 4 GB guest_memory.bin file is sparse: its file offset equals the PS3 guest address, while only allocated pages consume disk space.").arg(output.absolutePath()));
if (m_delete_later)
{
deleteLater();
}
}
-45
View File
@@ -1,45 +0,0 @@
#pragma once
#include "util/types.hpp"
#include <QWidget>
class guest_memory_dumper : QObject
{
Q_OBJECT
public:
guest_memory_dumper(QWidget* parent, bool delete_later = false);
virtual ~guest_memory_dumper() {}
void dump_guest_memory();
private:
QWidget* m_parent = nullptr;
bool m_delete_later = false;
static constexpr u64 guest_address_space_size = 0x1'0000'0000ull;
static constexpr u32 guest_page_size = 0x1000;
static constexpr u64 dump_io_chunk_size = 4 * 1024 * 1024;
struct guest_memory_region
{
u32 start = 0;
u64 size = 0;
u8 flags = 0;
};
struct spu_local_store_dump
{
u32 id = 0;
u32 lv2_id = 0;
u32 index = 0;
u32 pc = 0;
u32 vm_offset = 0;
u32 type = 0;
std::string name;
};
static bool emulated_processors_quiesced();
static QString hex_u64(u64 value, int width = 8);
};
-8
View File
@@ -48,7 +48,6 @@
#include "sound_effect_manager_dialog.h" #include "sound_effect_manager_dialog.h"
#include "recording_settings_dialog.h" #include "recording_settings_dialog.h"
#include "config_database.h" #include "config_database.h"
#include "guest_memory_dumper.h"
#include <thread> #include <thread>
#include <unordered_set> #include <unordered_set>
@@ -2193,7 +2192,6 @@ void main_window::EnableMenus(bool enabled) const
// Tools // Tools
ui->toolskernel_explorerAct->setEnabled(enabled); ui->toolskernel_explorerAct->setEnabled(enabled);
ui->toolsmemory_viewerAct->setEnabled(enabled); ui->toolsmemory_viewerAct->setEnabled(enabled);
ui->toolsDumpGuestMemoryAct->setEnabled(enabled);
ui->toolsRsxDebuggerAct->setEnabled(enabled); ui->toolsRsxDebuggerAct->setEnabled(enabled);
ui->toolsSystemCommandsAct->setEnabled(enabled); ui->toolsSystemCommandsAct->setEnabled(enabled);
ui->actionCreate_RSX_Capture->setEnabled(enabled); ui->actionCreate_RSX_Capture->setEnabled(enabled);
@@ -3389,12 +3387,6 @@ void main_window::CreateConnects()
idm::make<memory_viewer_handle>(this, make_basic_ppu_disasm()); idm::make<memory_viewer_handle>(this, make_basic_ppu_disasm());
}); });
connect(ui->toolsDumpGuestMemoryAct, &QAction::triggered, this, [this]()
{
guest_memory_dumper* dumper = new guest_memory_dumper(this, true);
dumper->dump_guest_memory();
});
connect(ui->toolsRsxDebuggerAct, &QAction::triggered, this, [this] connect(ui->toolsRsxDebuggerAct, &QAction::triggered, this, [this]
{ {
rsx_debugger* rsx = new rsx_debugger(m_gui_settings); rsx_debugger* rsx = new rsx_debugger(m_gui_settings);
-12
View File
@@ -365,7 +365,6 @@
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="toolsSystemCommandsAct"/> <addaction name="toolsSystemCommandsAct"/>
<addaction name="toolsmemory_viewerAct"/> <addaction name="toolsmemory_viewerAct"/>
<addaction name="toolsDumpGuestMemoryAct"/>
<addaction name="toolskernel_explorerAct"/> <addaction name="toolskernel_explorerAct"/>
<addaction name="toolsRsxDebuggerAct"/> <addaction name="toolsRsxDebuggerAct"/>
<addaction name="separator"/> <addaction name="separator"/>
@@ -760,17 +759,6 @@
<string>Memory Viewer</string> <string>Memory Viewer</string>
</property> </property>
</action> </action>
<action name="toolsDumpGuestMemoryAct">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Dump Guest Memory</string>
</property>
<property name="toolTip">
<string>Dump all allocated PS3 guest memory and live SPU local stores</string>
</property>
</action>
<action name="toolsRsxDebuggerAct"> <action name="toolsRsxDebuggerAct">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
+10 -10
View File
@@ -1079,21 +1079,21 @@ void trophy_manager_dialog::StartTrophyLoadThreads()
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
indices.append(i); indices.append(i);
QFutureWatcher<void> future_watcher; QFutureWatcher<void> futureWatcher;
progress_dialog progress_dlg(tr("Loading trophies"), tr("Loading trophy data, please wait..."), tr("Cancel"), 0, 1, false, this, Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint); progress_dialog progressDialog(tr("Loading trophies"), tr("Loading trophy data, please wait..."), tr("Cancel"), 0, 1, false, this, Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
connect(&future_watcher, &QFutureWatcher<void>::progressRangeChanged, &progress_dlg, &QProgressDialog::setRange); connect(&futureWatcher, &QFutureWatcher<void>::progressRangeChanged, &progressDialog, &QProgressDialog::setRange);
connect(&future_watcher, &QFutureWatcher<void>::progressValueChanged, &progress_dlg, &QProgressDialog::setValue); connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &progressDialog, &QProgressDialog::setValue);
connect(&future_watcher, &QFutureWatcher<void>::finished, this, [this]() { RepaintUI(true); }); connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [this]() { RepaintUI(true); });
connect(&progress_dlg, &QProgressDialog::canceled, this, [this, &future_watcher]() connect(&progressDialog, &QProgressDialog::canceled, this, [this, &futureWatcher]()
{ {
future_watcher.cancel(); futureWatcher.cancel();
close(); // It's pointless to show an empty window close(); // It's pointless to show an empty window
}); });
atomic_t<usz> error_count{}; atomic_t<usz> error_count{};
future_watcher.setFuture(QtConcurrent::map(indices, [this, &error_count, &folder_list](const int& i) futureWatcher.setFuture(QtConcurrent::map(indices, [this, &error_count, &folder_list](const int& i)
{ {
const std::string dir_name = folder_list.value(i).toStdString(); const std::string dir_name = folder_list.value(i).toStdString();
gui_log.trace("Loading trophy dir: %s", dir_name); gui_log.trace("Loading trophy dir: %s", dir_name);
@@ -1106,9 +1106,9 @@ void trophy_manager_dialog::StartTrophyLoadThreads()
} }
})); }));
progress_dlg.exec(); progressDialog.exec();
future_watcher.waitForFinished(); futureWatcher.waitForFinished();
if (error_count != 0) if (error_count != 0)
{ {