From 96c6dc01eb7c5f4dc6755f918bc7a8cf0c497867 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 21 Dec 2022 22:07:06 +0100 Subject: [PATCH] Qt: Implement config check in Utilities and log viewer --- Utilities/Config.h | 44 ++++++- rpcs3/rpcs3.vcxproj | 17 +++ rpcs3/rpcs3.vcxproj.filters | 12 ++ rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/config_checker.cpp | 213 +++++++++++++++++++++++++++++++ rpcs3/rpcs3qt/config_checker.h | 13 ++ rpcs3/rpcs3qt/gui_settings.h | 1 + rpcs3/rpcs3qt/log_viewer.cpp | 23 ++-- rpcs3/rpcs3qt/main_window.cpp | 24 ++++ rpcs3/rpcs3qt/main_window.ui | 6 + 10 files changed, 341 insertions(+), 13 deletions(-) create mode 100644 rpcs3/rpcs3qt/config_checker.cpp create mode 100644 rpcs3/rpcs3qt/config_checker.h diff --git a/Utilities/Config.h b/Utilities/Config.h index 57f2896b42..d5f530fea3 100644 --- a/Utilities/Config.h +++ b/Utilities/Config.h @@ -80,7 +80,13 @@ namespace cfg // Convert to string (optional) virtual std::string to_string() const { - return{}; + return {}; + } + + // Convert default to string (optional) + virtual std::string def_to_string() const + { + return {}; } // Try to convert from string (optional) @@ -89,7 +95,7 @@ namespace cfg // Get string list (optional) virtual std::vector to_list() const { - return{}; + return {}; } // Set multiple values. Implementation-specific, optional. @@ -163,6 +169,11 @@ namespace cfg return m_value ? "true" : "false"; } + std::string def_to_string() const override + { + return def ? "true" : "false"; + } + bool from_string(std::string_view value, bool /*dynamic*/ = false) override { if (value.size() != 4 && value.size() != 5) @@ -232,6 +243,13 @@ namespace cfg return result; // TODO: ??? } + std::string def_to_string() const override + { + std::string result; + fmt_class_string::format(result, fmt_unveil::get(def)); + return result; // TODO: ??? + } + bool from_string(std::string_view value, bool /*dynamic*/ = false) override { u64 result; @@ -297,6 +315,11 @@ namespace cfg return std::to_string(m_value); } + std::string def_to_string() const override + { + return std::to_string(def); + } + bool from_string(std::string_view value, bool /*dynamic*/ = false) override { s64 result; @@ -363,6 +386,11 @@ namespace cfg return std::to_string(m_value); } + std::string def_to_string() const override + { + return std::to_string(def); + } + bool from_string(std::string_view value, bool /*dynamic*/ = false) override { f64 result; @@ -437,6 +465,11 @@ namespace cfg return std::to_string(m_value); } + std::string def_to_string() const override + { + return std::to_string(def); + } + bool from_string(std::string_view value, bool /*dynamic*/ = false) override { u64 result; @@ -508,6 +541,11 @@ namespace cfg return *m_value.load().get(); } + std::string def_to_string() const override + { + return def; + } + bool from_string(std::string_view value, bool /*dynamic*/ = false) override { m_value = std::string(value); @@ -541,7 +579,7 @@ namespace cfg std::vector to_list() const override { - return{ m_set.begin(), m_set.end() }; + return { m_set.begin(), m_set.end() }; } bool from_list(std::vector&& list) override diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 96c5a60163..b3550f04b3 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -209,6 +209,9 @@ true + + true + true @@ -431,6 +434,9 @@ true + + true + true @@ -627,6 +633,7 @@ + @@ -960,6 +967,16 @@ + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-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" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DHAVE_SDL2 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\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" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index fa8881660a..1d2af89d15 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -882,6 +882,15 @@ Io\SDL + + Gui\log + + + Generated Files\Debug + + + Generated Files\Release + @@ -1306,6 +1315,9 @@ Form Files + + Gui\log + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 3ac4639e23..32d63b4eaf 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRC_FILES cg_disasm_window.cpp cheat_manager.cpp config_adapter.cpp + config_checker.cpp curl_handle.cpp custom_dialog.cpp custom_table_widget_item.cpp diff --git a/rpcs3/rpcs3qt/config_checker.cpp b/rpcs3/rpcs3qt/config_checker.cpp new file mode 100644 index 0000000000..15c6eccf90 --- /dev/null +++ b/rpcs3/rpcs3qt/config_checker.cpp @@ -0,0 +1,213 @@ +#include "stdafx.h" +#include "config_checker.h" +#include "Emu/system_config.h" + +#include +#include +#include +#include +#include +#include + +LOG_CHANNEL(gui_log, "GUI"); + +config_checker::config_checker(QWidget* parent, const QString& path, bool is_log) : QDialog(parent) +{ + setObjectName("config_checker"); + setAttribute(Qt::WA_DeleteOnClose); + + QVBoxLayout* layout = new QVBoxLayout(); + QLabel* label = new QLabel(this); + layout->addWidget(label); + + QString result; + + if (check_config(path, result, is_log)) + { + setWindowTitle(tr("Interesting!")); + + if (result.isEmpty()) + { + label->setText(tr("Found config.\nIt seems to match the default config.")); + } + else + { + label->setText(tr("Found config.\nSome settings seem to deviate from the default config:")); + + QTextEdit* text_box = new QTextEdit(); + text_box->setReadOnly(true); + text_box->setHtml(result); + layout->addWidget(text_box); + + resize(400, 600); + } + } + else + { + setWindowTitle(tr("Ooops!")); + label->setText(result); + } + + QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Close); + connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject); + layout->addWidget(box); + + setLayout(layout); +} + +bool config_checker::check_config(QString content, QString& result, bool is_log) +{ + cfg_root config{}; + + if (is_log) + { + const QString start_token = "SYS: Used configuration:\n"; + const QString end_token = "\n·"; + + int start = content.indexOf(start_token); + int end = -1; + + if (start >= 0) + { + start += start_token.count(); + end = content.indexOf(end_token, start); + } + + if (end < 0) + { + result = tr("Cannot find any config!"); + return false; + } + + content = content.mid(start, end - start); + } + + if (!config.from_string(content.toStdString())) + { + gui_log.error("log_viewer: Failed to parse config:\n%s", content.toStdString()); + result = tr("Cannot find any config!"); + return false; + } + + std::function print_diff_recursive; + print_diff_recursive = [&print_diff_recursive](const cfg::_base* base, std::string& diff, int indentation) -> void + { + if (!base) + { + return; + } + + const auto indent = [](std::string& str, int indentation) + { + for (int i = 0; i < indentation * 2; i++) + { + str += " "; + } + }; + + switch (base->get_type()) + { + case cfg::type::node: + { + if (const auto& node = static_cast(base)) + { + std::string diff_tmp; + + for (const auto& n : node->get_nodes()) + { + print_diff_recursive(n, diff_tmp, indentation + 1); + } + + if (!diff_tmp.empty()) + { + indent(diff, indentation); + + if (!base->get_name().empty()) + { + fmt::append(diff, "%s:
", base->get_name()); + } + + fmt::append(diff, "%s", diff_tmp); + } + } + break; + } + case cfg::type::_bool: + case cfg::type::_enum: + case cfg::type::_int: + case cfg::type::uint: + case cfg::type::string: + { + const std::string val = base->to_string(); + const std::string def = base->def_to_string(); + + if (val != def) + { + indent(diff, indentation); + + if (def.empty()) + { + fmt::append(diff, "%s: %s
", base->get_name(), val); + } + else + { + fmt::append(diff, "%s: %s default: %s
", base->get_name(), val, def); + } + } + break; + } + case cfg::type::set: + { + if (const auto& node = static_cast(base)) + { + const std::vector set_entries = node->to_list(); + + if (!set_entries.empty()) + { + indent(diff, indentation); + fmt::append(diff, "%s:
", base->get_name()); + + for (const std::string& entry : set_entries) + { + indent(diff, indentation + 1); + fmt::append(diff, "- %s
", entry); + } + } + } + break; + } + case cfg::type::log: + { + if (const auto& node = static_cast(base)) + { + const auto& log_entries = node->get_map(); + + if (!log_entries.empty()) + { + indent(diff, indentation); + fmt::append(diff, "%s:
", base->get_name()); + + for (const auto& entry : log_entries) + { + indent(diff, indentation + 1); + fmt::append(diff, "%s: %s
", entry.first, entry.second); + } + } + } + break; + } + case cfg::type::map: + case cfg::type::device: + { + // Ignored + break; + } + } + }; + + std::string diff; + print_diff_recursive(&config, diff, 0); + result = QString::fromStdString(diff); + + return true; +} diff --git a/rpcs3/rpcs3qt/config_checker.h b/rpcs3/rpcs3qt/config_checker.h new file mode 100644 index 0000000000..900fb30351 --- /dev/null +++ b/rpcs3/rpcs3qt/config_checker.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class config_checker : public QDialog +{ + Q_OBJECT + +public: + config_checker(QWidget* parent, const QString& path, bool is_log); + + bool check_config(QString content, QString& result, bool is_log); +}; diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index 6cc376eee1..39c6493743 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -145,6 +145,7 @@ namespace gui const gui_save fd_ext_mself = gui_save(main_window, "lastExplorePathExMSELF", ""); const gui_save fd_ext_tar = gui_save(main_window, "lastExplorePathExTAR", ""); const gui_save fd_insert_disc = gui_save(main_window, "lastExplorePathDISC", ""); + const gui_save fd_cfg_check = gui_save(main_window, "lastExplorePathCfgChk", ""); const gui_save mw_debugger = gui_save(main_window, "debuggerVisible", false); const gui_save mw_logger = gui_save(main_window, "loggerVisible", true); diff --git a/rpcs3/rpcs3qt/log_viewer.cpp b/rpcs3/rpcs3qt/log_viewer.cpp index 8cf0cded96..ca902d3bf1 100644 --- a/rpcs3/rpcs3qt/log_viewer.cpp +++ b/rpcs3/rpcs3qt/log_viewer.cpp @@ -4,6 +4,7 @@ #include "gui_settings.h" #include "syntax_highlighter.h" #include "find_dialog.h" +#include "config_checker.h" #include #include @@ -22,13 +23,6 @@ LOG_CHANNEL(gui_log, "GUI"); -[[maybe_unused]] constexpr auto qstr = QString::fromStdString; - -inline std::string sstr(const QString& _in) -{ - return _in.toStdString(); -} - log_viewer::log_viewer(std::shared_ptr gui_settings) : m_gui_settings(std::move(gui_settings)) { @@ -70,6 +64,7 @@ void log_viewer::show_context_menu(const QPoint& pos) QAction* open = new QAction(tr("&Open log file")); QAction* save = new QAction(tr("&Save filtered log")); QAction* filter = new QAction(tr("&Filter log")); + QAction* config = new QAction(tr("&Check config")); QAction* timestamps = new QAction(tr("&Show Timestamps")); timestamps->setCheckable(true); @@ -119,6 +114,8 @@ void log_viewer::show_context_menu(const QPoint& pos) menu.addSeparator(); menu.addAction(save); menu.addSeparator(); + menu.addAction(config); + menu.addSeparator(); menu.addAction(filter); menu.addSeparator(); menu.addAction(timestamps); @@ -156,14 +153,20 @@ void log_viewer::show_context_menu(const QPoint& pos) { log_file.write(m_log_text->toPlainText().toUtf8()); log_file.close(); - gui_log.success("Exported filtered log to file '%s'", sstr(file_path)); + gui_log.success("Exported filtered log to file '%s'", file_path.toStdString()); } else { - gui_log.error("Failed to export filtered log to file '%s'", sstr(file_path)); + gui_log.error("Failed to export filtered log to file '%s'", file_path.toStdString()); } }); + connect(config, &QAction::triggered, this, [this]() + { + config_checker* dlg = new config_checker(this, m_full_log, true); + dlg->exec(); + }); + connect(filter, &QAction::triggered, this, [this]() { m_filter_term = QInputDialog::getText(this, tr("Filter log"), tr("Enter text"), QLineEdit::EchoMode::Normal, m_filter_term); @@ -230,7 +233,7 @@ void log_viewer::show_log() } else { - gui_log.error("log_viewer: Failed to open %s", sstr(m_path_last)); + gui_log.error("log_viewer: Failed to open %s", m_path_last.toStdString()); m_log_text->setPlainText(tr("Failed to open '%0'").arg(m_path_last)); } diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 6e7b6ffc75..d66f2ac750 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -31,6 +31,7 @@ #include "camera_settings_dialog.h" #include "ipc_settings_dialog.h" #include "shortcut_utils.h" +#include "config_checker.h" #include #include @@ -2375,6 +2376,29 @@ void main_window::CreateConnects() viewer->show_log(); }); + connect(ui->toolsCheckConfigAct, &QAction::triggered, this, [this] + { + const QString path_last_cfg = m_gui_settings->GetValue(gui::fd_cfg_check).toString(); + const QString file_path = QFileDialog::getOpenFileName(this, tr("Select rpcs3.log or config.yml"), path_last_cfg, tr("Log files (*.log);;Config Files (*.yml);;All files (*.*)")); + if (file_path.isEmpty()) + { + // Aborted + return; + } + + QFile file(file_path); + if (!file.exists() || !file.open(QIODevice::ReadOnly)) + { + QMessageBox::warning(this, tr("Failed to open file"), tr("The file could not be opened:\n%0").arg(file_path)); + return; + } + + m_gui_settings->SetValue(gui::fd_cfg_check, QFileInfo(file_path).path()); + + config_checker* dlg = new config_checker(this, file.readAll(), file_path.endsWith(".log")); + dlg->exec(); + }); + connect(ui->toolskernel_explorerAct, &QAction::triggered, this, [this] { if (!m_kernel_explorer) diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 585e642b41..2eb7379348 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -268,6 +268,7 @@ Utilities + @@ -1233,6 +1234,11 @@ Insert Disc + + + Check Config + +