mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 10:42:36 +01:00
Qt: patch creator
This commit is contained in:
parent
0debcfed0a
commit
3c0681ad6d
@ -176,6 +176,14 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (main_key.empty())
|
||||||
|
{
|
||||||
|
append_log_message(log_messages, "Error: Skipping empty key");
|
||||||
|
patch_log.error("Skipping empty key (file: %s)", path);
|
||||||
|
is_valid = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip Anchors
|
// Skip Anchors
|
||||||
if (main_key == patch_key::anchors)
|
if (main_key == patch_key::anchors)
|
||||||
{
|
{
|
||||||
@ -223,10 +231,18 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st
|
|||||||
{
|
{
|
||||||
const std::string& title = game_node.first.Scalar();
|
const std::string& title = game_node.first.Scalar();
|
||||||
|
|
||||||
|
if (title.empty())
|
||||||
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Empty game title (key: %s, file: %s)", main_key, path));
|
||||||
|
patch_log.error("Empty game title (key: %s, file: %s)", main_key, path);
|
||||||
|
is_valid = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (const auto yml_type = game_node.second.Type(); yml_type != YAML::NodeType::Map)
|
if (const auto yml_type = game_node.second.Type(); yml_type != YAML::NodeType::Map)
|
||||||
{
|
{
|
||||||
append_log_message(log_messages, fmt::format("Error: Skipping %s: expected Map, found %s (patch: %s, key: %s)", title, yml_type, description, main_key));
|
append_log_message(log_messages, fmt::format("Error: Skipping game %s: expected Map, found %s (patch: %s, key: %s)", title, yml_type, description, main_key));
|
||||||
patch_log.error("Skipping %s: expected Map, found %s (patch: %s, key: %s, file: %s)", title, yml_type, description, main_key, path);
|
patch_log.error("Skipping game %s: expected Map, found %s (patch: %s, key: %s, file: %s)", title, yml_type, description, main_key, path);
|
||||||
is_valid = false;
|
is_valid = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -237,7 +253,14 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st
|
|||||||
{
|
{
|
||||||
const std::string& serial = serial_node.first.Scalar();
|
const std::string& serial = serial_node.first.Scalar();
|
||||||
|
|
||||||
if (serial == patch_key::all)
|
if (serial.empty())
|
||||||
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Using empty serial (title: %s, patch: %s, key: %s)", title, description, main_key));
|
||||||
|
patch_log.error("Using empty serial (title: %s, patch: %s, key: %s, file: %s)", title, description, main_key, path);
|
||||||
|
is_valid = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (serial == patch_key::all)
|
||||||
{
|
{
|
||||||
if (!title_is_all_key)
|
if (!title_is_all_key)
|
||||||
{
|
{
|
||||||
@ -363,11 +386,11 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st
|
|||||||
return is_valid;
|
return is_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
patch_type patch_engine::get_patch_type(YAML::Node node)
|
patch_type patch_engine::get_patch_type(const std::string& text)
|
||||||
{
|
{
|
||||||
u64 type_val = 0;
|
u64 type_val = 0;
|
||||||
|
|
||||||
if (!node || !node.IsScalar() || !cfg::try_to_enum_value(&type_val, &fmt_class_string<patch_type>::format, node.Scalar()))
|
if (!cfg::try_to_enum_value(&type_val, &fmt_class_string<patch_type>::format, text))
|
||||||
{
|
{
|
||||||
return patch_type::invalid;
|
return patch_type::invalid;
|
||||||
}
|
}
|
||||||
@ -375,6 +398,16 @@ patch_type patch_engine::get_patch_type(YAML::Node node)
|
|||||||
return static_cast<patch_type>(type_val);
|
return static_cast<patch_type>(type_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patch_type patch_engine::get_patch_type(YAML::Node node)
|
||||||
|
{
|
||||||
|
if (!node || !node.IsScalar())
|
||||||
|
{
|
||||||
|
return patch_type::invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_patch_type(node.Scalar());
|
||||||
|
}
|
||||||
|
|
||||||
bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifier, const YAML::Node& root, std::stringstream* log_messages)
|
bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifier, const YAML::Node& root, std::stringstream* log_messages)
|
||||||
{
|
{
|
||||||
if (!node || !node.IsSequence())
|
if (!node || !node.IsSequence())
|
||||||
|
@ -121,6 +121,9 @@ public:
|
|||||||
// Read and add a patch node to the patch info
|
// Read and add a patch node to the patch info
|
||||||
static bool read_patch_node(patch_info& info, YAML::Node node, const YAML::Node& root, std::stringstream* log_messages = nullptr);
|
static bool read_patch_node(patch_info& info, YAML::Node node, const YAML::Node& root, std::stringstream* log_messages = nullptr);
|
||||||
|
|
||||||
|
// Get the patch type from a string
|
||||||
|
static patch_type get_patch_type(const std::string& text);
|
||||||
|
|
||||||
// Get the patch type of a patch node
|
// Get the patch type of a patch node
|
||||||
static patch_type get_patch_type(YAML::Node node);
|
static patch_type get_patch_type(YAML::Node node);
|
||||||
|
|
||||||
|
@ -291,6 +291,9 @@
|
|||||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_pad_settings_dialog.cpp">
|
<ClCompile Include="QTGeneratedFiles\Debug\moc_pad_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_patch_creator_dialog.cpp">
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_patch_manager_dialog.cpp">
|
<ClCompile Include="QTGeneratedFiles\Debug\moc_patch_manager_dialog.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -480,6 +483,9 @@
|
|||||||
<ClCompile Include="QTGeneratedFiles\Release\moc_pad_settings_dialog.cpp">
|
<ClCompile Include="QTGeneratedFiles\Release\moc_pad_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_patch_creator_dialog.cpp">
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="QTGeneratedFiles\Release\moc_patch_manager_dialog.cpp">
|
<ClCompile Include="QTGeneratedFiles\Release\moc_patch_manager_dialog.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -572,6 +578,7 @@
|
|||||||
<ClCompile Include="rpcs3qt\microphone_creator.cpp" />
|
<ClCompile Include="rpcs3qt\microphone_creator.cpp" />
|
||||||
<ClCompile Include="rpcs3qt\osk_dialog_frame.cpp" />
|
<ClCompile Include="rpcs3qt\osk_dialog_frame.cpp" />
|
||||||
<ClCompile Include="rpcs3qt\pad_led_settings_dialog.cpp" />
|
<ClCompile Include="rpcs3qt\pad_led_settings_dialog.cpp" />
|
||||||
|
<ClCompile Include="rpcs3qt\patch_creator_dialog.cpp" />
|
||||||
<ClCompile Include="rpcs3qt\patch_manager_dialog.cpp" />
|
<ClCompile Include="rpcs3qt\patch_manager_dialog.cpp" />
|
||||||
<ClCompile Include="rpcs3qt\pkg_install_dialog.cpp" />
|
<ClCompile Include="rpcs3qt\pkg_install_dialog.cpp" />
|
||||||
<ClCompile Include="rpcs3qt\persistent_settings.cpp" />
|
<ClCompile Include="rpcs3qt\persistent_settings.cpp" />
|
||||||
@ -1046,6 +1053,16 @@
|
|||||||
</CustomBuild>
|
</CustomBuild>
|
||||||
<ClInclude Include="rpcs3qt\movie_item.h" />
|
<ClInclude Include="rpcs3qt\movie_item.h" />
|
||||||
<ClInclude Include="rpcs3qt\numbered_widget_item.h" />
|
<ClInclude Include="rpcs3qt\numbered_widget_item.h" />
|
||||||
|
<CustomBuild Include="rpcs3qt\patch_creator_dialog.h">
|
||||||
|
<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_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||||
|
<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_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||||
|
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||||
|
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||||
|
</CustomBuild>
|
||||||
<ClInclude Include="rpcs3qt\richtext_item_delegate.h" />
|
<ClInclude Include="rpcs3qt\richtext_item_delegate.h" />
|
||||||
<ClInclude Include="rpcs3qt\stylesheets.h" />
|
<ClInclude Include="rpcs3qt\stylesheets.h" />
|
||||||
<CustomBuild Include="rpcs3qt\skylander_dialog.h">
|
<CustomBuild Include="rpcs3qt\skylander_dialog.h">
|
||||||
@ -1398,6 +1415,16 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="QTGeneratedFiles\Debug\pkg_install_dialog.moc" />
|
<None Include="QTGeneratedFiles\Debug\pkg_install_dialog.moc" />
|
||||||
<None Include="QTGeneratedFiles\Release\pkg_install_dialog.moc" />
|
<None Include="QTGeneratedFiles\Release\pkg_install_dialog.moc" />
|
||||||
|
<CustomBuild Include="rpcs3qt\patch_creator_dialog.ui">
|
||||||
|
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||||
|
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Uic%27ing %(Identity)...</Message>
|
||||||
|
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||||
|
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||||
|
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||||
|
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Uic%27ing %(Identity)...</Message>
|
||||||
|
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||||
|
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||||
|
</CustomBuild>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets" />
|
<ImportGroup Label="ExtensionTargets" />
|
||||||
|
@ -747,6 +747,15 @@
|
|||||||
<ClCompile Include="rpcs3qt\game_list.cpp">
|
<ClCompile Include="rpcs3qt\game_list.cpp">
|
||||||
<Filter>Gui\game list</Filter>
|
<Filter>Gui\game list</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="QTGeneratedFiles\Debug\moc_patch_creator_dialog.cpp">
|
||||||
|
<Filter>Generated Files\Debug</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="rpcs3qt\patch_creator_dialog.cpp">
|
||||||
|
<Filter>Gui\patch manager</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="QTGeneratedFiles\Release\moc_patch_creator_dialog.cpp">
|
||||||
|
<Filter>Generated Files\Release</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Input\ds4_pad_handler.h">
|
<ClInclude Include="Input\ds4_pad_handler.h">
|
||||||
@ -1093,6 +1102,12 @@
|
|||||||
<CustomBuild Include="rpcs3qt\log_viewer.h">
|
<CustomBuild Include="rpcs3qt\log_viewer.h">
|
||||||
<Filter>Gui\log</Filter>
|
<Filter>Gui\log</Filter>
|
||||||
</CustomBuild>
|
</CustomBuild>
|
||||||
|
<CustomBuild Include="rpcs3qt\patch_creator_dialog.ui">
|
||||||
|
<Filter>Form Files</Filter>
|
||||||
|
</CustomBuild>
|
||||||
|
<CustomBuild Include="rpcs3qt\patch_creator_dialog.h">
|
||||||
|
<Filter>Gui\patch manager</Filter>
|
||||||
|
</CustomBuild>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="rpcs3.ico" />
|
<Image Include="rpcs3.ico" />
|
||||||
|
@ -44,6 +44,7 @@ set(SRC_FILES
|
|||||||
osk_dialog_frame.cpp
|
osk_dialog_frame.cpp
|
||||||
pad_led_settings_dialog.cpp
|
pad_led_settings_dialog.cpp
|
||||||
pad_settings_dialog.cpp
|
pad_settings_dialog.cpp
|
||||||
|
patch_creator_dialog.cpp
|
||||||
patch_manager_dialog.cpp
|
patch_manager_dialog.cpp
|
||||||
persistent_settings.cpp
|
persistent_settings.cpp
|
||||||
pkg_install_dialog.cpp
|
pkg_install_dialog.cpp
|
||||||
@ -80,6 +81,8 @@ set(UI_FILES
|
|||||||
main_window.ui
|
main_window.ui
|
||||||
pad_led_settings_dialog.ui
|
pad_led_settings_dialog.ui
|
||||||
pad_settings_dialog.ui
|
pad_settings_dialog.ui
|
||||||
|
patch_creator_dialog.ui
|
||||||
|
patch_manager_dialog.ui
|
||||||
settings_dialog.ui
|
settings_dialog.ui
|
||||||
welcome_dialog.ui
|
welcome_dialog.ui
|
||||||
)
|
)
|
||||||
|
@ -2211,7 +2211,7 @@ bool game_list_frame::eventFilter(QObject *object, QEvent *event)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (!key_event->isAutoRepeat())
|
||||||
{
|
{
|
||||||
if (key_event->key() == Qt::Key_Enter || key_event->key() == Qt::Key_Return)
|
if (key_event->key() == Qt::Key_Enter || key_event->key() == Qt::Key_Return)
|
||||||
{
|
{
|
||||||
|
@ -683,7 +683,7 @@ bool log_frame::eventFilter(QObject* object, QEvent* event)
|
|||||||
if (event->type() == QEvent::KeyPress)
|
if (event->type() == QEvent::KeyPress)
|
||||||
{
|
{
|
||||||
QKeyEvent* e = static_cast<QKeyEvent*>(event);
|
QKeyEvent* e = static_cast<QKeyEvent*>(event);
|
||||||
if (e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_F)
|
if (e && !e->isAutoRepeat() && e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_F)
|
||||||
{
|
{
|
||||||
if (m_find_dialog && m_find_dialog->isVisible())
|
if (m_find_dialog && m_find_dialog->isVisible())
|
||||||
m_find_dialog->close();
|
m_find_dialog->close();
|
||||||
|
@ -182,7 +182,7 @@ bool log_viewer::eventFilter(QObject* object, QEvent* event)
|
|||||||
if (event->type() == QEvent::KeyPress)
|
if (event->type() == QEvent::KeyPress)
|
||||||
{
|
{
|
||||||
QKeyEvent* e = static_cast<QKeyEvent*>(event);
|
QKeyEvent* e = static_cast<QKeyEvent*>(event);
|
||||||
if (e && e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_F)
|
if (e && !e->isAutoRepeat() && e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_F)
|
||||||
{
|
{
|
||||||
if (m_find_dialog && m_find_dialog->isVisible())
|
if (m_find_dialog && m_find_dialog->isVisible())
|
||||||
m_find_dialog->close();
|
m_find_dialog->close();
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "skylander_dialog.h"
|
#include "skylander_dialog.h"
|
||||||
#include "cheat_manager.h"
|
#include "cheat_manager.h"
|
||||||
#include "patch_manager_dialog.h"
|
#include "patch_manager_dialog.h"
|
||||||
|
#include "patch_creator_dialog.h"
|
||||||
#include "pkg_install_dialog.h"
|
#include "pkg_install_dialog.h"
|
||||||
#include "category.h"
|
#include "category.h"
|
||||||
#include "gui_settings.h"
|
#include "gui_settings.h"
|
||||||
@ -2111,6 +2112,12 @@ void main_window::CreateConnects()
|
|||||||
patch_manager.exec();
|
patch_manager.exec();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(ui->patchCreatorAct, &QAction::triggered, this, [this]
|
||||||
|
{
|
||||||
|
patch_creator_dialog patch_creator(this);
|
||||||
|
patch_creator.exec();
|
||||||
|
});
|
||||||
|
|
||||||
connect(ui->actionManage_Users, &QAction::triggered, this, [this]
|
connect(ui->actionManage_Users, &QAction::triggered, this, [this]
|
||||||
{
|
{
|
||||||
user_manager_dialog user_manager(m_gui_settings, m_persistent_settings, this);
|
user_manager_dialog user_manager(m_gui_settings, m_persistent_settings, this);
|
||||||
|
@ -255,6 +255,7 @@
|
|||||||
<addaction name="toolsmemory_viewerAct"/>
|
<addaction name="toolsmemory_viewerAct"/>
|
||||||
<addaction name="toolsRsxDebuggerAct"/>
|
<addaction name="toolsRsxDebuggerAct"/>
|
||||||
<addaction name="toolsStringSearchAct"/>
|
<addaction name="toolsStringSearchAct"/>
|
||||||
|
<addaction name="patchCreatorAct"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="toolsDecryptSprxLibsAct"/>
|
<addaction name="toolsDecryptSprxLibsAct"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
@ -1169,6 +1170,11 @@
|
|||||||
<string>Boot VSH/XMB</string>
|
<string>Boot VSH/XMB</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="patchCreatorAct">
|
||||||
|
<property name="text">
|
||||||
|
<string>Patch Creator</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<resources>
|
<resources>
|
||||||
|
484
rpcs3/rpcs3qt/patch_creator_dialog.cpp
Normal file
484
rpcs3/rpcs3qt/patch_creator_dialog.cpp
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
#include "ui_patch_creator_dialog.h"
|
||||||
|
#include "patch_creator_dialog.h"
|
||||||
|
#include "table_item_delegate.h"
|
||||||
|
#include "qt_utils.h"
|
||||||
|
#include "Utilities/Config.h"
|
||||||
|
|
||||||
|
#include <QCompleter>
|
||||||
|
#include <QFontDatabase>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QMenuBar>
|
||||||
|
|
||||||
|
LOG_CHANNEL(patch_log, "PAT");
|
||||||
|
|
||||||
|
constexpr auto qstr = QString::fromStdString;
|
||||||
|
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(patch_type)
|
||||||
|
|
||||||
|
enum patch_column : int
|
||||||
|
{
|
||||||
|
type = 0,
|
||||||
|
offset,
|
||||||
|
value,
|
||||||
|
comment
|
||||||
|
};
|
||||||
|
|
||||||
|
patch_creator_dialog::patch_creator_dialog(QWidget* parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
, ui(new Ui::patch_creator_dialog)
|
||||||
|
, mMonoFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
|
||||||
|
, mValidColor(gui::utils::get_label_color("log_level_success"))
|
||||||
|
, mInvalidColor(gui::utils::get_label_color("log_level_error"))
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
ui->patchEdit->setFont(mMonoFont);
|
||||||
|
ui->addPatchOffsetEdit->setFont(mMonoFont);
|
||||||
|
ui->addPatchValueEdit->setFont(mMonoFont);
|
||||||
|
ui->instructionTable->setFont(mMonoFont);
|
||||||
|
ui->instructionTable->setItemDelegate(new table_item_delegate(this, false));
|
||||||
|
ui->instructionTable->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Fixed);
|
||||||
|
ui->instructionTable->installEventFilter(this);
|
||||||
|
ui->versionMinorSpinBox->setValue(0);
|
||||||
|
ui->versionMajorSpinBox->setValue(0);
|
||||||
|
|
||||||
|
QMenuBar* menu_bar = new QMenuBar(this);
|
||||||
|
QMenu* file_menu = menu_bar->addMenu(tr("File"));
|
||||||
|
QAction* export_act = file_menu->addAction(tr("&Export Patch"));
|
||||||
|
export_act->setShortcut(QKeySequence("Ctrl+E"));
|
||||||
|
export_act->installEventFilter(this);
|
||||||
|
layout()->setMenuBar(menu_bar);
|
||||||
|
|
||||||
|
connect(export_act, &QAction::triggered, this, &patch_creator_dialog::export_patch);
|
||||||
|
connect(ui->hashEdit, &QLineEdit::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->authorEdit, &QLineEdit::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->patchNameEdit, &QLineEdit::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->gameEdit, &QLineEdit::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->gameVersionEdit, &QLineEdit::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->serialEdit, &QLineEdit::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->notesEdit, &QLineEdit::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->groupEdit, &QLineEdit::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->versionMinorSpinBox, &QSpinBox::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->versionMajorSpinBox, &QSpinBox::textChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
connect(ui->instructionTable, &QTableWidget::itemChanged, this, [this](QTableWidgetItem*){ generate_yml(); });
|
||||||
|
connect(ui->instructionTable, &QTableWidget::customContextMenuRequested, this, &patch_creator_dialog::show_table_menu);
|
||||||
|
connect(ui->addPatchButton, &QAbstractButton::clicked, this, [this]() { add_instruction(ui->instructionTable->rowCount()); });
|
||||||
|
|
||||||
|
init_patch_type_bombo_box(ui->addPatchTypeComboBox, patch_type::be32, false);
|
||||||
|
|
||||||
|
generate_yml();
|
||||||
|
}
|
||||||
|
|
||||||
|
patch_creator_dialog::~patch_creator_dialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_creator_dialog::init_patch_type_bombo_box(QComboBox* combo_box, patch_type set_type, bool searchable)
|
||||||
|
{
|
||||||
|
if (!combo_box) return;
|
||||||
|
|
||||||
|
combo_box->clear();
|
||||||
|
|
||||||
|
QStringList types;
|
||||||
|
|
||||||
|
for (const std::string& type : cfg::try_to_enum_list(&fmt_class_string<patch_type>::format))
|
||||||
|
{
|
||||||
|
if (const patch_type t = patch_engine::get_patch_type(type); t != patch_type::invalid)
|
||||||
|
{
|
||||||
|
types << qstr(type);
|
||||||
|
|
||||||
|
combo_box->addItem(types.last(), QVariant::fromValue<patch_type>(t));
|
||||||
|
|
||||||
|
if (t == set_type)
|
||||||
|
{
|
||||||
|
combo_box->setCurrentText(types.last());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchable)
|
||||||
|
{
|
||||||
|
QCompleter* completer = new QCompleter(types, combo_box);
|
||||||
|
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
||||||
|
completer->setCompletionMode(QCompleter::PopupCompletion);
|
||||||
|
completer->setFilterMode(Qt::MatchContains);
|
||||||
|
|
||||||
|
combo_box->setCompleter(completer);
|
||||||
|
combo_box->setEditable(true);
|
||||||
|
combo_box->setInsertPolicy(QComboBox::NoInsert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QComboBox* patch_creator_dialog::create_patch_type_bombo_box(patch_type set_type)
|
||||||
|
{
|
||||||
|
QComboBox* combo_box = new QComboBox;
|
||||||
|
init_patch_type_bombo_box(combo_box, set_type, true);
|
||||||
|
connect(combo_box, &QComboBox::currentTextChanged, this, &patch_creator_dialog::generate_yml);
|
||||||
|
return combo_box;
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_creator_dialog::show_table_menu(const QPoint& pos)
|
||||||
|
{
|
||||||
|
QMenu menu;
|
||||||
|
|
||||||
|
QModelIndexList selection = ui->instructionTable->selectionModel()->selectedRows();
|
||||||
|
|
||||||
|
if (selection.isEmpty())
|
||||||
|
{
|
||||||
|
QAction* act_add_instruction = menu.addAction(tr("&Add Instruction"));
|
||||||
|
connect(act_add_instruction, &QAction::triggered, [this]()
|
||||||
|
{
|
||||||
|
add_instruction(ui->instructionTable->rowCount());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (selection.count() == 1)
|
||||||
|
{
|
||||||
|
QAction* act_add_instruction_above = menu.addAction(tr("&Add Instruction Above"));
|
||||||
|
connect(act_add_instruction_above, &QAction::triggered, [this, row = selection.first().row()]()
|
||||||
|
{
|
||||||
|
add_instruction(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
QAction* act_add_instruction_below = menu.addAction(tr("&Add Instruction Below"));
|
||||||
|
connect(act_add_instruction_below, &QAction::triggered, [this, row = selection.first().row()]()
|
||||||
|
{
|
||||||
|
add_instruction(row + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool can_move_up = can_move_instructions(selection, move_direction::up);
|
||||||
|
const bool can_move_down = can_move_instructions(selection, move_direction::down);
|
||||||
|
|
||||||
|
if (can_move_up)
|
||||||
|
{
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
QAction* act_move_instruction_up = menu.addAction(tr("&Move Instruction(s) Up"));
|
||||||
|
connect(act_move_instruction_up, &QAction::triggered, [this, &selection]()
|
||||||
|
{
|
||||||
|
move_instructions(selection.first().row(), selection.count(), 1, move_direction::up);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (can_move_down)
|
||||||
|
{
|
||||||
|
if (!can_move_up)
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
QAction* act_move_instruction_down = menu.addAction(tr("&Move Instruction(s) Down"));
|
||||||
|
connect(act_move_instruction_down, &QAction::triggered, [this, &selection]()
|
||||||
|
{
|
||||||
|
move_instructions(selection.first().row(), selection.count(), 1, move_direction::down);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
QAction* act_remove_instruction = menu.addAction(tr("&Remove Instruction(s)"));
|
||||||
|
connect(act_remove_instruction, &QAction::triggered, [this]()
|
||||||
|
{
|
||||||
|
remove_instructions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
QAction* act_clear_table = menu.addAction(tr("&Clear Table"));
|
||||||
|
connect(act_clear_table, &QAction::triggered, [this]()
|
||||||
|
{
|
||||||
|
patch_log.notice("Patch Creator: Clearing instruction table...");
|
||||||
|
ui->instructionTable->clearContents();
|
||||||
|
ui->instructionTable->setRowCount(0);
|
||||||
|
generate_yml();
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.exec(ui->instructionTable->viewport()->mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_creator_dialog::add_instruction(int row)
|
||||||
|
{
|
||||||
|
const QString type = ui->addPatchTypeComboBox->currentText();
|
||||||
|
const QString offset = ui->addPatchOffsetEdit->text();
|
||||||
|
const QString value = ui->addPatchValueEdit->text();
|
||||||
|
const QString comment = ui->addPatchCommentEdit->text();
|
||||||
|
|
||||||
|
const patch_type t = patch_engine::get_patch_type(type.toStdString());
|
||||||
|
QComboBox* combo_box = create_patch_type_bombo_box(t);
|
||||||
|
|
||||||
|
ui->instructionTable->insertRow(std::max(0, std::min(row, ui->instructionTable->rowCount())));
|
||||||
|
ui->instructionTable->setCellWidget(row, patch_column::type, combo_box);
|
||||||
|
ui->instructionTable->setItem(row, patch_column::offset, new QTableWidgetItem(offset));
|
||||||
|
ui->instructionTable->setItem(row, patch_column::value, new QTableWidgetItem(value));
|
||||||
|
ui->instructionTable->setItem(row, patch_column::comment, new QTableWidgetItem(comment));
|
||||||
|
|
||||||
|
patch_log.notice("Patch Creator: Inserted instruction [ %s, %s, %s ] at row %d", sstr(combo_box->currentText()), sstr(offset), sstr(value), row);
|
||||||
|
generate_yml();
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_creator_dialog::remove_instructions()
|
||||||
|
{
|
||||||
|
QModelIndexList selection(ui->instructionTable->selectionModel()->selectedRows());
|
||||||
|
if (selection.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::sort(selection.rbegin(), selection.rend());
|
||||||
|
for (const QModelIndex& index : selection)
|
||||||
|
{
|
||||||
|
patch_log.notice("Patch Creator: Removing instruction in row %d...", index.row());
|
||||||
|
ui->instructionTable->removeRow(index.row());
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_yml();
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_creator_dialog::move_instructions(int src_row, int rows_to_move, int distance, move_direction dir)
|
||||||
|
{
|
||||||
|
patch_log.notice("Patch Creator: Moving %d instruction(s) from row %d %s by %d...", rows_to_move, src_row, dir == move_direction::up ? "up" : "down", distance);
|
||||||
|
|
||||||
|
if (src_row < 0 || src_row >= ui->instructionTable->rowCount() || distance < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
rows_to_move = std::max(0, std::min(rows_to_move, ui->instructionTable->rowCount() - src_row));
|
||||||
|
if (rows_to_move < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int dst_row = std::max(0, std::min(ui->instructionTable->rowCount() - rows_to_move, dir == move_direction::up ? src_row - distance : src_row + distance));
|
||||||
|
if (dir == move_direction::up ? dst_row >= src_row : dst_row <= src_row)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int friends_to_relocate = std::abs(dst_row - src_row);
|
||||||
|
const int friends_src_row = dir == move_direction::up ? dst_row : src_row + rows_to_move;
|
||||||
|
const int friends_dst_row = dir == move_direction::up ? dst_row + rows_to_move : src_row;
|
||||||
|
|
||||||
|
std::vector<patch_type> moving_types(rows_to_move);
|
||||||
|
std::vector<std::vector<QTableWidgetItem*>> moving_rows(rows_to_move);
|
||||||
|
|
||||||
|
std::vector<patch_type> friend_types(friends_to_relocate);
|
||||||
|
std::vector<std::vector<QTableWidgetItem*>> friend_rows(friends_to_relocate);
|
||||||
|
|
||||||
|
const auto get_row_type = [this](int i) -> patch_type
|
||||||
|
{
|
||||||
|
if (const QComboBox* type_item = qobject_cast<QComboBox*>(ui->instructionTable->cellWidget(i, patch_column::type)))
|
||||||
|
return type_item->currentData().value<patch_type>();
|
||||||
|
return patch_type::invalid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto set_row_type_widget = [this](int i, patch_type type) -> void
|
||||||
|
{
|
||||||
|
ui->instructionTable->setCellWidget(i, patch_column::type, create_patch_type_bombo_box(type));
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < rows_to_move; i++)
|
||||||
|
{
|
||||||
|
moving_types[i] = get_row_type(src_row + i);
|
||||||
|
moving_rows[i].push_back(ui->instructionTable->takeItem(src_row + i, patch_column::type));
|
||||||
|
moving_rows[i].push_back(ui->instructionTable->takeItem(src_row + i, patch_column::offset));
|
||||||
|
moving_rows[i].push_back(ui->instructionTable->takeItem(src_row + i, patch_column::value));
|
||||||
|
moving_rows[i].push_back(ui->instructionTable->takeItem(src_row + i, patch_column::comment));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < friends_to_relocate; i++)
|
||||||
|
{
|
||||||
|
friend_types[i] = get_row_type(friends_src_row + i);
|
||||||
|
friend_rows[i].push_back(ui->instructionTable->takeItem(friends_src_row + i, patch_column::type));
|
||||||
|
friend_rows[i].push_back(ui->instructionTable->takeItem(friends_src_row + i, patch_column::offset));
|
||||||
|
friend_rows[i].push_back(ui->instructionTable->takeItem(friends_src_row + i, patch_column::value));
|
||||||
|
friend_rows[i].push_back(ui->instructionTable->takeItem(friends_src_row + i, patch_column::comment));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < rows_to_move; i++)
|
||||||
|
{
|
||||||
|
int item_index = 0;
|
||||||
|
ui->instructionTable->setCellWidget(dst_row + i, patch_column::type, create_patch_type_bombo_box(moving_types[i]));
|
||||||
|
ui->instructionTable->setItem(dst_row + i, patch_column::type, moving_rows[i][item_index++]);
|
||||||
|
ui->instructionTable->setItem(dst_row + i, patch_column::offset, moving_rows[i][item_index++]);
|
||||||
|
ui->instructionTable->setItem(dst_row + i, patch_column::value, moving_rows[i][item_index++]);
|
||||||
|
ui->instructionTable->setItem(dst_row + i, patch_column::comment, moving_rows[i][item_index++]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < friends_to_relocate; i++)
|
||||||
|
{
|
||||||
|
int item_index = 0;
|
||||||
|
ui->instructionTable->setCellWidget(friends_dst_row + i, patch_column::type, create_patch_type_bombo_box(friend_types[i]));
|
||||||
|
ui->instructionTable->setItem(friends_dst_row + i, patch_column::type, friend_rows[i][item_index++]);
|
||||||
|
ui->instructionTable->setItem(friends_dst_row + i, patch_column::offset, friend_rows[i][item_index++]);
|
||||||
|
ui->instructionTable->setItem(friends_dst_row + i, patch_column::value, friend_rows[i][item_index++]);
|
||||||
|
ui->instructionTable->setItem(friends_dst_row + i, patch_column::comment, friend_rows[i][item_index++]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->instructionTable->clearSelection();
|
||||||
|
ui->instructionTable->setRangeSelected(QTableWidgetSelectionRange(dst_row, 0, dst_row + rows_to_move - 1, ui->instructionTable->columnCount() - 1), true);
|
||||||
|
|
||||||
|
generate_yml();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool patch_creator_dialog::can_move_instructions(QModelIndexList& selection, move_direction dir)
|
||||||
|
{
|
||||||
|
if (selection.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::sort(selection.begin(), selection.end());
|
||||||
|
|
||||||
|
// Check if there are any gaps in the selection
|
||||||
|
for (int i = 1, row = selection.first().row(); i < selection.count(); i++)
|
||||||
|
{
|
||||||
|
if (++row != selection[i].row())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir == move_direction::up)
|
||||||
|
return selection.first().row() > 0;
|
||||||
|
|
||||||
|
return selection.last().row() < ui->instructionTable->rowCount() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_creator_dialog::validate()
|
||||||
|
{
|
||||||
|
patch_engine::patch_map patches;
|
||||||
|
const std::string content = ui->patchEdit->toPlainText().toStdString();
|
||||||
|
const bool is_valid = patch_engine::load(patches, "From Patch Creator", content, true);
|
||||||
|
|
||||||
|
if (is_valid != m_valid)
|
||||||
|
{
|
||||||
|
QPalette palette = ui->validLabel->palette();
|
||||||
|
|
||||||
|
if (is_valid)
|
||||||
|
{
|
||||||
|
ui->validLabel->setText(tr("Valid Patch"));
|
||||||
|
palette.setColor(ui->validLabel->foregroundRole(), mValidColor);
|
||||||
|
patch_log.success("Patch Creator: Validation successful!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ui->validLabel->setText(tr("Validation Failed"));
|
||||||
|
palette.setColor(ui->validLabel->foregroundRole(), mInvalidColor);
|
||||||
|
patch_log.error("Patch Creator: Validation failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->validLabel->setPalette(palette);
|
||||||
|
m_valid = is_valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_creator_dialog::export_patch()
|
||||||
|
{
|
||||||
|
if (!m_valid)
|
||||||
|
{
|
||||||
|
QMessageBox::information(this, tr("Patch invalid!"), tr("The patch validation failed.\nThe export of invalid patches is not allowed."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString file_path = QFileDialog::getSaveFileName(this, tr("Select Patch File"), qstr(patch_engine::get_patches_path()), tr("patch.yml files (*.yml);;All files (*.*)"));
|
||||||
|
if (file_path.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QFile patch_file(file_path); patch_file.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||||
|
{
|
||||||
|
patch_file.write(ui->patchEdit->toPlainText().toUtf8());
|
||||||
|
patch_file.close();
|
||||||
|
patch_log.success("Exported patch to file '%s'", sstr(file_path));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
patch_log.fatal("Failed to export patch to file '%s'", sstr(file_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_creator_dialog::generate_yml(const QString& /*text*/)
|
||||||
|
{
|
||||||
|
QString patch;
|
||||||
|
patch.append(QString("%0: %1\n").arg(qstr(patch_key::version)).arg(qstr(patch_engine_version)));
|
||||||
|
patch.append("\n");
|
||||||
|
patch.append(QString("%0:\n").arg(ui->hashEdit->text()));
|
||||||
|
patch.append(QString(" \"%0\":\n").arg(ui->patchNameEdit->text()));
|
||||||
|
patch.append(QString(" %0:\n").arg(qstr(patch_key::games)));
|
||||||
|
patch.append(QString(" \"%0\":\n").arg(ui->gameEdit->text()));
|
||||||
|
patch.append(QString(" %0: [ %1 ]\n").arg(ui->serialEdit->text()).arg(ui->gameVersionEdit->text()));
|
||||||
|
patch.append(QString(" %0: \"%1\"\n").arg(qstr(patch_key::author)).arg(ui->authorEdit->text()));
|
||||||
|
patch.append(QString(" %0: %1.%2\n").arg(qstr(patch_key::patch_version)).arg(ui->versionMajorSpinBox->text()).arg(ui->versionMinorSpinBox->text()));
|
||||||
|
patch.append(QString(" %0: \"%1\"\n").arg(qstr(patch_key::group)).arg(ui->groupEdit->text()));
|
||||||
|
patch.append(QString(" %0: \"%1\"\n").arg(qstr(patch_key::notes)).arg(ui->notesEdit->text()));
|
||||||
|
patch.append(QString(" %0:\n").arg(qstr(patch_key::patch)));
|
||||||
|
|
||||||
|
for (int i = 0; i < ui->instructionTable->rowCount(); i++)
|
||||||
|
{
|
||||||
|
const QComboBox* type_item = qobject_cast<QComboBox*>(ui->instructionTable->cellWidget(i, patch_column::type));
|
||||||
|
const QTableWidgetItem* offset_item = ui->instructionTable->item(i, patch_column::offset);
|
||||||
|
const QTableWidgetItem* value_item = ui->instructionTable->item(i, patch_column::value);
|
||||||
|
const QTableWidgetItem* comment_item = ui->instructionTable->item(i, patch_column::comment);
|
||||||
|
|
||||||
|
const QString type = type_item ? type_item->currentText() : "";
|
||||||
|
const QString offset = offset_item ? offset_item->text() : "";
|
||||||
|
const QString value = value_item ? value_item->text() : "";
|
||||||
|
const QString comment = comment_item ? comment_item->text() : "";
|
||||||
|
|
||||||
|
if (patch_engine::get_patch_type(type.toStdString()) == patch_type::invalid)
|
||||||
|
{
|
||||||
|
ui->patchEdit->setText(tr("Instruction %0: Type '%1' is invalid!").arg(i + 1).arg(type));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch.append(QString(" - [ %0, %1, %2 ]%3\n").arg(type).arg(offset).arg(value).arg(comment.isEmpty() ? QStringLiteral("") : QString(" # %0").arg(comment)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->patchEdit->setText(patch);
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool patch_creator_dialog::eventFilter(QObject* object, QEvent* event)
|
||||||
|
{
|
||||||
|
if (object != ui->instructionTable)
|
||||||
|
{
|
||||||
|
return QDialog::eventFilter(object, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->type() == QEvent::KeyPress)
|
||||||
|
{
|
||||||
|
if (QKeyEvent* key_event = static_cast<QKeyEvent*>(event))
|
||||||
|
{
|
||||||
|
if (key_event->modifiers() == Qt::AltModifier)
|
||||||
|
{
|
||||||
|
switch (key_event->key())
|
||||||
|
{
|
||||||
|
case Qt::Key_Up:
|
||||||
|
{
|
||||||
|
QModelIndexList selection = ui->instructionTable->selectionModel()->selectedRows();
|
||||||
|
if (can_move_instructions(selection, move_direction::up))
|
||||||
|
move_instructions(selection.first().row(), selection.count(), 1, move_direction::up);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case Qt::Key_Down:
|
||||||
|
{
|
||||||
|
QModelIndexList selection = ui->instructionTable->selectionModel()->selectedRows();
|
||||||
|
if (can_move_instructions(selection, move_direction::down))
|
||||||
|
move_instructions(selection.first().row(), selection.count(), 1, move_direction::down);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!key_event->isAutoRepeat())
|
||||||
|
{
|
||||||
|
switch (key_event->key())
|
||||||
|
{
|
||||||
|
case Qt::Key_Delete:
|
||||||
|
{
|
||||||
|
remove_instructions();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QDialog::eventFilter(object, event);
|
||||||
|
}
|
51
rpcs3/rpcs3qt/patch_creator_dialog.h
Normal file
51
rpcs3/rpcs3qt/patch_creator_dialog.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Utilities/bin_patch.h"
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class patch_creator_dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class patch_creator_dialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit patch_creator_dialog(QWidget* parent = nullptr);
|
||||||
|
~patch_creator_dialog();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::patch_creator_dialog* ui;
|
||||||
|
QFont mMonoFont;
|
||||||
|
QColor mValidColor;
|
||||||
|
QColor mInvalidColor;
|
||||||
|
bool m_valid = true; // Will be invalidated immediately
|
||||||
|
|
||||||
|
enum class move_direction
|
||||||
|
{
|
||||||
|
up,
|
||||||
|
down
|
||||||
|
};
|
||||||
|
|
||||||
|
void add_instruction(int row);
|
||||||
|
void remove_instructions();
|
||||||
|
void move_instructions(int src_row, int rows_to_move, int distance, move_direction dir);
|
||||||
|
bool can_move_instructions(QModelIndexList& selection, move_direction dir);
|
||||||
|
|
||||||
|
static void init_patch_type_bombo_box(QComboBox* combo_box, patch_type set_type, bool searchable);
|
||||||
|
QComboBox* create_patch_type_bombo_box(patch_type set_type);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void show_table_menu(const QPoint& pos);
|
||||||
|
void validate();
|
||||||
|
void generate_yml(const QString& text = {});
|
||||||
|
void export_patch();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject* object, QEvent* event) override;
|
||||||
|
};
|
204
rpcs3/rpcs3qt/patch_creator_dialog.ui
Normal file
204
rpcs3/rpcs3qt/patch_creator_dialog.ui
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>patch_creator_dialog</class>
|
||||||
|
<widget class="QDialog" name="patch_creator_dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>1005</width>
|
||||||
|
<height>804</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Patch Creator</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="dialogLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QSplitter" name="splitter">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="verticalLayoutWidget">
|
||||||
|
<layout class="QVBoxLayout" name="leftLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="instructionTable">
|
||||||
|
<property name="contextMenuPolicy">
|
||||||
|
<enum>Qt::CustomContextMenu</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
<attribute name="horizontalHeaderStretchLastSection">
|
||||||
|
<bool>true</bool>
|
||||||
|
</attribute>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Type</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Offset</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Value</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Comment</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="addPatchLayout" stretch="1,1,1,1,0">
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="addPatchTypeComboBox"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="addPatchOffsetEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Offset</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="addPatchValueEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Value</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="addPatchCommentEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Comment</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="addPatchButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="verticalLayoutWidget_2">
|
||||||
|
<layout class="QVBoxLayout" name="rightLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLineEdit" name="hashEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Hash</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="versionLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Patch Version</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="versionMajorSpinBox"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="versionMinorSpinBox"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLineEdit" name="notesEdit">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Notes</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QLineEdit" name="authorEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Author</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="groupEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Group</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="patchNameEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Patch Name</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLineEdit" name="gameEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Game</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLineEdit" name="serialEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Title ID</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLineEdit" name="gameVersionEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Game Versions</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="validLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Valid Patch</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTextEdit" name="patchEdit">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -442,7 +442,7 @@ void save_manager_dialog::OnEntriesRemove()
|
|||||||
if (QMessageBox::question(this, tr("Delete Confirmation"), tr("Are you sure you want to delete these %n items?", "", selection.size()), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
|
if (QMessageBox::question(this, tr("Delete Confirmation"), tr("Are you sure you want to delete these %n items?", "", selection.size()), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
|
||||||
{
|
{
|
||||||
std::sort(selection.rbegin(), selection.rend());
|
std::sort(selection.rbegin(), selection.rend());
|
||||||
for (QModelIndex index : selection)
|
for (const QModelIndex& index : selection)
|
||||||
{
|
{
|
||||||
QTableWidgetItem* item = m_list->item(index.row(), 1);
|
QTableWidgetItem* item = m_list->item(index.row(), 1);
|
||||||
if (!item)
|
if (!item)
|
||||||
|
@ -935,7 +935,7 @@ bool trophy_manager_dialog::eventFilter(QObject *object, QEvent *event)
|
|||||||
{
|
{
|
||||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||||
|
|
||||||
if (keyEvent->modifiers() & Qt::ControlModifier && (is_trophy_table || is_game_table))
|
if (keyEvent && keyEvent->modifiers() == Qt::ControlModifier && (is_trophy_table || is_game_table))
|
||||||
{
|
{
|
||||||
if (keyEvent->key() == Qt::Key_Plus)
|
if (keyEvent->key() == Qt::Key_Plus)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user