2020-12-05 13:08:24 +01:00
# include <QPushButton>
2020-06-02 10:39:24 +02:00
# include <QDialogButtonBox>
# include <QGuiApplication>
# include <QScreen>
# include <QDir>
# include <QMenu>
# include <QAction>
2020-06-11 15:12:44 +02:00
# include <QCheckBox>
2020-06-12 21:09:08 +02:00
# include <QMessageBox>
2020-06-30 21:35:15 +02:00
# include <QTimer>
2020-09-06 11:47:45 +02:00
# include <QJsonObject>
# include <QJsonDocument>
2020-06-02 10:39:24 +02:00
# include "ui_patch_manager_dialog.h"
# include "patch_manager_dialog.h"
# include "table_item_delegate.h"
2020-06-25 18:26:47 +02:00
# include "gui_settings.h"
2020-09-06 11:47:45 +02:00
# include "downloader.h"
2020-06-02 10:39:24 +02:00
# include "qt_utils.h"
# include "Utilities/File.h"
# include "util/logs.hpp"
2020-09-06 11:47:45 +02:00
LOG_CHANNEL ( patch_log , " PAT " ) ;
2020-06-02 10:39:24 +02:00
enum patch_column : int
{
enabled ,
title ,
serials ,
description ,
patch_version ,
author ,
notes
} ;
enum patch_role : int
{
hash_role = Qt : : UserRole ,
2020-06-26 02:58:06 +02:00
title_role ,
serial_role ,
app_version_role ,
2020-06-19 19:00:06 +02:00
description_role ,
2020-06-26 02:58:06 +02:00
patch_group_role ,
2020-06-25 20:53:41 +02:00
persistance_role ,
node_level_role
} ;
enum node_level : int
{
title_level ,
serial_level ,
patch_level
2020-06-02 10:39:24 +02:00
} ;
2021-03-07 19:55:40 +01:00
patch_manager_dialog : : patch_manager_dialog ( std : : shared_ptr < gui_settings > gui_settings , std : : unordered_map < std : : string , std : : set < std : : string > > games , const std : : string & title_id , const std : : string & version , QWidget * parent )
2020-06-02 10:39:24 +02:00
: QDialog ( parent )
2021-04-07 23:05:18 +02:00
, m_gui_settings ( std : : move ( gui_settings ) )
2021-03-07 19:55:40 +01:00
, m_expand_current_match ( ! title_id . empty ( ) & & ! version . empty ( ) ) // Expand first search results
, m_search_version ( QString : : fromStdString ( version ) )
2020-06-26 18:31:42 +02:00
, m_owned_games ( std : : move ( games ) )
2020-06-02 10:39:24 +02:00
, ui ( new Ui : : patch_manager_dialog )
{
ui - > setupUi ( this ) ;
setModal ( true ) ;
2020-06-11 15:12:44 +02:00
// Load config for special settings
2021-01-08 18:36:12 +01:00
patch_engine : : load_config ( ) ;
2020-07-03 17:38:54 +02:00
// Load gui settings
m_show_owned_games_only = m_gui_settings - > GetValue ( gui : : pm_show_owned ) . toBool ( ) ;
// Initialize gui controls
2021-03-07 19:55:40 +01:00
ui - > patch_filter - > setText ( QString : : fromStdString ( title_id ) ) ;
2020-07-03 17:38:54 +02:00
ui - > cb_owned_games_only - > setChecked ( m_show_owned_games_only ) ;
2020-06-11 15:12:44 +02:00
2020-09-06 11:47:45 +02:00
ui - > buttonBox - > button ( QDialogButtonBox : : RestoreDefaults ) - > setText ( tr ( " Download latest patches " ) ) ;
2020-09-10 08:13:19 +02:00
m_downloader = new downloader ( this ) ;
2020-09-06 11:47:45 +02:00
2020-07-03 17:38:54 +02:00
// Create connects
2020-06-02 10:39:24 +02:00
connect ( ui - > patch_filter , & QLineEdit : : textChanged , this , & patch_manager_dialog : : filter_patches ) ;
2020-08-02 17:36:22 +02:00
connect ( ui - > patch_tree , & QTreeWidget : : currentItemChanged , this , & patch_manager_dialog : : handle_item_selected ) ;
connect ( ui - > patch_tree , & QTreeWidget : : itemChanged , this , & patch_manager_dialog : : handle_item_changed ) ;
connect ( ui - > patch_tree , & QTreeWidget : : customContextMenuRequested , this , & patch_manager_dialog : : handle_custom_context_menu_requested ) ;
connect ( ui - > cb_owned_games_only , & QCheckBox : : stateChanged , this , & patch_manager_dialog : : handle_show_owned_games_only ) ;
2020-06-02 10:39:24 +02:00
connect ( ui - > buttonBox , & QDialogButtonBox : : rejected , this , & QWidget : : close ) ;
connect ( ui - > buttonBox , & QDialogButtonBox : : clicked , [ this ] ( QAbstractButton * button )
{
if ( button = = ui - > buttonBox - > button ( QDialogButtonBox : : Save ) )
{
2020-06-12 21:09:08 +02:00
save_config ( ) ;
2020-06-02 10:39:24 +02:00
accept ( ) ;
}
else if ( button = = ui - > buttonBox - > button ( QDialogButtonBox : : Apply ) )
{
2020-06-12 21:09:08 +02:00
save_config ( ) ;
2020-06-02 10:39:24 +02:00
}
2020-09-06 11:47:45 +02:00
else if ( button = = ui - > buttonBox - > button ( QDialogButtonBox : : RestoreDefaults ) )
{
2021-11-18 20:47:17 +01:00
download_update ( false , true ) ;
2020-09-06 11:47:45 +02:00
}
} ) ;
connect ( m_downloader , & downloader : : signal_download_error , this , [ this ] ( const QString & /*error*/ )
{
QMessageBox : : warning ( this , tr ( " Patch downloader " ) , tr ( " An error occurred during the download process. \n Check the log for more information. " ) ) ;
2021-11-18 20:47:17 +01:00
ui - > buttonBox - > button ( QDialogButtonBox : : RestoreDefaults ) - > setEnabled ( true ) ;
2020-09-06 11:47:45 +02:00
} ) ;
connect ( m_downloader , & downloader : : signal_download_finished , this , [ this ] ( const QByteArray & data )
{
const bool result_json = handle_json ( data ) ;
if ( ! result_json )
{
2021-11-18 20:47:17 +01:00
if ( ! m_download_automatic )
{
QMessageBox : : warning ( this , tr ( " Patch downloader " ) , tr ( " An error occurred during the download process. \n Check the log for more information. " ) ) ;
}
2020-09-06 11:47:45 +02:00
}
2021-11-18 20:47:17 +01:00
ui - > buttonBox - > button ( QDialogButtonBox : : RestoreDefaults ) - > setEnabled ( true ) ;
2020-06-02 10:39:24 +02:00
} ) ;
2021-11-18 20:47:17 +01:00
download_update ( true , false ) ;
2020-06-02 10:39:24 +02:00
}
patch_manager_dialog : : ~ patch_manager_dialog ( )
{
2020-06-25 18:26:47 +02:00
// Save gui settings
m_gui_settings - > SetValue ( gui : : pm_geometry , saveGeometry ( ) ) ;
m_gui_settings - > SetValue ( gui : : pm_splitter_state , ui - > splitter - > saveState ( ) ) ;
2020-06-02 10:39:24 +02:00
delete ui ;
}
2020-06-25 18:26:47 +02:00
int patch_manager_dialog : : exec ( )
{
show ( ) ;
refresh ( true ) ;
return QDialog : : exec ( ) ;
}
void patch_manager_dialog : : refresh ( bool restore_layout )
2020-06-12 21:09:08 +02:00
{
2020-06-30 21:35:15 +02:00
load_patches ( restore_layout ) ;
2020-06-12 21:09:08 +02:00
populate_tree ( ) ;
2020-06-26 18:31:42 +02:00
filter_patches ( ui - > patch_filter - > text ( ) ) ;
2020-06-25 18:26:47 +02:00
if ( restore_layout )
{
if ( ! restoreGeometry ( m_gui_settings - > GetValue ( gui : : pm_geometry ) . toByteArray ( ) ) )
{
resize ( QGuiApplication : : primaryScreen ( ) - > availableSize ( ) * 0.7 ) ;
}
if ( ! ui - > splitter - > restoreState ( m_gui_settings - > GetValue ( gui : : pm_splitter_state ) . toByteArray ( ) ) )
{
const int width_left = ui - > splitter - > width ( ) * 0.7 ;
const int width_right = ui - > splitter - > width ( ) - width_left ;
ui - > splitter - > setSizes ( { width_left , width_right } ) ;
}
}
2020-06-12 21:09:08 +02:00
}
2020-06-30 21:35:15 +02:00
void patch_manager_dialog : : load_patches ( bool show_error )
2020-06-02 10:39:24 +02:00
{
2020-06-19 19:00:06 +02:00
m_map . clear ( ) ;
2020-06-20 13:44:56 +02:00
// NOTE: Make sure these paths are loaded in the same order as they are applied on boot
2020-06-19 22:06:34 +02:00
2020-06-30 21:35:15 +02:00
const std : : string patches_path = patch_engine : : get_patches_path ( ) ;
2020-06-19 22:06:34 +02:00
const QStringList filters = QStringList ( ) < < " *_patch.yml " ;
2020-06-02 10:39:24 +02:00
2020-06-20 13:44:56 +02:00
QStringList path_list ;
path_list < < " patch.yml " ;
path_list < < " imported_patch.yml " ;
path_list < < QDir ( QString : : fromStdString ( patches_path ) ) . entryList ( filters ) ;
path_list . removeDuplicates ( ) ; // make sure to load patch.yml and imported_patch.yml only once
2020-06-19 22:06:34 +02:00
2020-06-30 21:35:15 +02:00
bool has_errors = false ;
2020-06-02 10:39:24 +02:00
for ( const auto & path : path_list )
{
2020-06-30 21:35:15 +02:00
if ( ! patch_engine : : load ( m_map , patches_path + path . toStdString ( ) ) )
{
has_errors = true ;
}
}
if ( show_error & & has_errors )
{
// Open a warning dialog after the patch manager was opened
QTimer : : singleShot ( 100 , [ this , patches_path ] ( )
{
QMessageBox : : warning ( this , tr ( " Incompatible patches detected " ) ,
tr ( " Some of your patches are not compatible with the current version of RPCS3's Patch Manager. \n \n Make sure that all the patches located in \" %0 \" contain the proper formatting that is required for the Patch Manager Version %1. " )
. arg ( QString : : fromStdString ( patches_path ) ) . arg ( QString : : fromStdString ( patch_engine_version ) ) ) ;
} ) ;
2020-06-02 10:39:24 +02:00
}
}
void patch_manager_dialog : : populate_tree ( )
{
2020-06-19 19:00:06 +02:00
// "Reset" currently used items. Items that aren't persisted will be removed later.
// Using this logic instead of clearing the tree here should persist the expanded status of items.
2021-01-07 11:18:48 +01:00
for ( auto item : ui - > patch_tree - > findItems ( " .* " , Qt : : MatchFlag : : MatchRegularExpression | Qt : : MatchFlag : : MatchRecursive ) )
2020-06-19 19:00:06 +02:00
{
if ( item )
{
item - > setData ( 0 , persistance_role , false ) ;
}
}
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ hash , container ] : m_map )
2020-06-02 10:39:24 +02:00
{
2020-06-19 13:46:56 +02:00
const QString q_hash = QString : : fromStdString ( hash ) ;
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
// Add patch items
for ( const auto & [ description , patch ] : container . patch_info_map )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
const QString q_patch_group = QString : : fromStdString ( patch . patch_group ) ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
for ( const auto & [ title , serials ] : patch . titles )
2020-06-19 13:46:56 +02:00
{
2020-06-26 02:58:06 +02:00
if ( serials . empty ( ) )
{
continue ;
}
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
const QString q_title = QString : : fromStdString ( title ) ;
2020-06-27 15:26:14 +02:00
const QString visible_title = title = = patch_key : : all ? tr_all_titles : q_title ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
QTreeWidgetItem * title_level_item = nullptr ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
// Find top level item for this title
2021-04-07 23:05:18 +02:00
if ( const auto list = ui - > patch_tree - > findItems ( visible_title , Qt : : MatchFlag : : MatchExactly , 0 ) ; ! list . empty ( ) )
2020-06-26 02:58:06 +02:00
{
title_level_item = list [ 0 ] ;
}
2020-06-19 13:46:56 +02:00
2020-06-26 02:58:06 +02:00
// Add a top level item for this title if it doesn't exist yet
if ( ! title_level_item )
{
title_level_item = new QTreeWidgetItem ( ) ;
2020-06-27 15:26:14 +02:00
title_level_item - > setText ( 0 , visible_title ) ;
2020-06-26 02:58:06 +02:00
title_level_item - > setData ( 0 , title_role , q_title ) ;
title_level_item - > setData ( 0 , node_level_role , node_level : : title_level ) ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
ui - > patch_tree - > addTopLevelItem ( title_level_item ) ;
}
2020-12-09 16:04:52 +01:00
ensure ( title_level_item ) ;
2020-06-02 10:39:24 +02:00
2020-06-30 02:13:18 +02:00
title_level_item - > setData ( 0 , persistance_role , true ) ;
2020-06-26 02:58:06 +02:00
for ( const auto & [ serial , app_versions ] : serials )
{
if ( app_versions . empty ( ) )
{
continue ;
}
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
const QString q_serial = QString : : fromStdString ( serial ) ;
2020-06-27 10:32:00 +02:00
const QString visible_serial = serial = = patch_key : : all ? tr_all_serials : q_serial ;
2020-06-19 19:00:06 +02:00
2020-06-26 02:58:06 +02:00
for ( const auto & [ app_version , enabled ] : app_versions )
{
const QString q_app_version = QString : : fromStdString ( app_version ) ;
2020-06-27 10:32:00 +02:00
const QString q_version_suffix = app_version = = patch_key : : all ? ( QStringLiteral ( " - " ) + tr_all_versions ) : ( QStringLiteral ( " v. " ) + q_app_version ) ;
const QString q_serial_and_version = visible_serial + q_version_suffix ;
2020-06-19 19:00:06 +02:00
2020-06-26 18:31:42 +02:00
// Find out if there is a node item for this serial
QTreeWidgetItem * serial_level_item = gui : : utils : : find_child ( title_level_item , q_serial_and_version ) ;
2020-06-26 02:58:06 +02:00
2020-06-26 18:31:42 +02:00
// Add a node item for this serial if it doesn't exist yet
if ( ! serial_level_item )
2020-06-26 02:58:06 +02:00
{
2020-06-26 18:31:42 +02:00
serial_level_item = new QTreeWidgetItem ( ) ;
serial_level_item - > setText ( 0 , q_serial_and_version ) ;
serial_level_item - > setData ( 0 , title_role , q_title ) ;
serial_level_item - > setData ( 0 , serial_role , q_serial ) ;
serial_level_item - > setData ( 0 , app_version_role , q_app_version ) ;
serial_level_item - > setData ( 0 , node_level_role , node_level : : serial_level ) ;
title_level_item - > addChild ( serial_level_item ) ;
2020-06-26 02:58:06 +02:00
}
2020-12-09 16:04:52 +01:00
ensure ( serial_level_item ) ;
2020-06-26 02:58:06 +02:00
2020-06-30 02:13:18 +02:00
serial_level_item - > setData ( 0 , persistance_role , true ) ;
2020-06-26 02:58:06 +02:00
// Add a checkable leaf item for this patch
const QString q_description = QString : : fromStdString ( description ) ;
QString visible_description = q_description ;
const auto match_criteria = QList < QPair < int , QVariant > > ( ) < < QPair ( description_role , q_description ) < < QPair ( persistance_role , true ) ;
// Add counter to leafs if the name already exists due to different hashes of the same game (PPU, SPU, PRX, OVL)
2020-06-26 18:31:42 +02:00
if ( const auto matches = gui : : utils : : find_children_by_data ( serial_level_item , match_criteria , false ) ; matches . count ( ) > 0 )
2020-06-26 02:58:06 +02:00
{
if ( auto only_match = matches . count ( ) = = 1 ? matches [ 0 ] : nullptr )
{
2020-06-29 20:04:44 +02:00
only_match - > setText ( 0 , q_description + QStringLiteral ( " (01) " ) ) ;
2020-06-26 02:58:06 +02:00
}
2020-06-29 20:04:44 +02:00
const int counter = matches . count ( ) + 1 ;
visible_description + = QStringLiteral ( " ( " ) ;
if ( counter < 10 ) visible_description + = ' 0 ' ;
visible_description + = QString : : number ( counter ) + ' ) ' ;
2020-06-26 02:58:06 +02:00
}
QTreeWidgetItem * patch_level_item = new QTreeWidgetItem ( ) ;
patch_level_item - > setText ( 0 , visible_description ) ;
patch_level_item - > setCheckState ( 0 , enabled ? Qt : : CheckState : : Checked : Qt : : CheckState : : Unchecked ) ;
patch_level_item - > setData ( 0 , hash_role , q_hash ) ;
patch_level_item - > setData ( 0 , title_role , q_title ) ;
patch_level_item - > setData ( 0 , serial_role , q_serial ) ;
patch_level_item - > setData ( 0 , app_version_role , q_app_version ) ;
patch_level_item - > setData ( 0 , description_role , q_description ) ;
patch_level_item - > setData ( 0 , patch_group_role , q_patch_group ) ;
patch_level_item - > setData ( 0 , node_level_role , node_level : : patch_level ) ;
patch_level_item - > setData ( 0 , persistance_role , true ) ;
2020-06-26 18:31:42 +02:00
serial_level_item - > addChild ( patch_level_item ) ;
2020-06-26 02:58:06 +02:00
}
}
}
2020-06-02 10:39:24 +02:00
}
2020-06-19 13:46:56 +02:00
}
2020-06-26 02:58:06 +02:00
const auto match_criteria = QList < QPair < int , QVariant > > ( ) < < QPair ( persistance_role , true ) ;
2020-06-02 10:39:24 +02:00
2020-06-19 19:00:06 +02:00
for ( int i = ui - > patch_tree - > topLevelItemCount ( ) - 1 ; i > = 0 ; i - - )
2020-06-19 13:46:56 +02:00
{
2021-04-07 23:05:18 +02:00
if ( const auto title_level_item = ui - > patch_tree - > topLevelItem ( i ) )
2020-06-02 10:39:24 +02:00
{
2020-06-19 19:00:06 +02:00
if ( ! title_level_item - > data ( 0 , persistance_role ) . toBool ( ) )
{
delete ui - > patch_tree - > takeTopLevelItem ( i ) ;
continue ;
}
2020-06-26 02:58:06 +02:00
gui : : utils : : remove_children ( title_level_item , match_criteria , true ) ;
2020-06-02 10:39:24 +02:00
}
}
2020-06-26 02:58:06 +02:00
gui : : utils : : sort_tree ( ui - > patch_tree , Qt : : SortOrder : : AscendingOrder , true ) ;
2020-06-27 15:26:14 +02:00
// Move "All titles" to the top
// NOTE: "All serials" is only allowed as the only node in "All titles", so there is no need to move it up
// NOTE: "All versions" will be above valid numerical versions through sorting anyway
2021-04-07 23:05:18 +02:00
if ( const auto all_title_items = ui - > patch_tree - > findItems ( tr_all_titles , Qt : : MatchExactly ) ; ! all_title_items . empty ( ) )
2020-06-27 15:26:14 +02:00
{
2020-06-30 02:13:18 +02:00
const auto item = all_title_items [ 0 ] ;
2020-12-09 16:04:52 +01:00
ensure ( item & & all_title_items . size ( ) = = 1 ) ;
2020-06-27 15:26:14 +02:00
if ( const int index = ui - > patch_tree - > indexOfTopLevelItem ( item ) ; index > = 0 )
{
2020-06-30 02:13:18 +02:00
const bool all_titles_expanded = item - > isExpanded ( ) ;
const auto all_serials_item = item - > child ( 0 ) ;
const bool all_serials_expanded = all_serials_item & & all_serials_item - > isExpanded ( ) ;
2020-06-27 15:26:14 +02:00
ui - > patch_tree - > takeTopLevelItem ( index ) ;
ui - > patch_tree - > insertTopLevelItem ( 0 , item ) ;
2020-06-30 02:13:18 +02:00
item - > setExpanded ( all_titles_expanded ) ;
if ( all_serials_item )
{
all_serials_item - > setExpanded ( all_serials_expanded ) ;
}
2020-06-27 15:26:14 +02:00
}
}
2020-06-02 10:39:24 +02:00
}
2021-04-07 23:05:18 +02:00
void patch_manager_dialog : : save_config ( ) const
2020-06-02 10:39:24 +02:00
{
2021-01-08 18:36:12 +01:00
patch_engine : : save_config ( m_map ) ;
2020-06-02 10:39:24 +02:00
}
void patch_manager_dialog : : filter_patches ( const QString & term )
{
// Recursive function to show all matching items and their children.
// @return number of visible children of item, including item
std : : function < int ( QTreeWidgetItem * , bool ) > show_matches ;
2020-06-26 18:31:42 +02:00
show_matches = [ this , & show_matches , search_text = term . toLower ( ) ] ( QTreeWidgetItem * item , bool parent_visible ) - > int
2020-06-02 10:39:24 +02:00
{
if ( ! item ) return 0 ;
2020-06-26 18:31:42 +02:00
const node_level level = static_cast < node_level > ( item - > data ( 0 , node_level_role ) . toInt ( ) ) ;
// Hide nodes that aren't in the game list
if ( m_show_owned_games_only )
{
if ( level = = node_level : : serial_level )
{
const std : : string serial = item - > data ( 0 , serial_role ) . toString ( ) . toStdString ( ) ;
const std : : string app_version = item - > data ( 0 , app_version_role ) . toString ( ) . toStdString ( ) ;
2020-06-27 14:41:07 +02:00
if ( serial ! = patch_key : : all & &
2020-06-28 15:16:09 +02:00
( m_owned_games . find ( serial ) = = m_owned_games . end ( ) | | ( app_version ! = patch_key : : all & & ! m_owned_games . at ( serial ) . contains ( app_version ) ) ) )
2020-06-26 18:31:42 +02:00
{
item - > setHidden ( true ) ;
return 0 ;
}
}
}
2020-06-02 10:39:24 +02:00
// Only try to match if the parent is not visible
parent_visible = parent_visible | | item - > text ( 0 ) . toLower ( ) . contains ( search_text ) ;
2020-06-26 18:31:42 +02:00
int visible_items = 0 ;
2020-06-02 10:39:24 +02:00
// Get the number of visible children recursively
for ( int i = 0 ; i < item - > childCount ( ) ; i + + )
{
visible_items + = show_matches ( item - > child ( i ) , parent_visible ) ;
}
2020-06-26 18:31:42 +02:00
if ( parent_visible )
{
// Only show the title node if it has visible children when filtering for game list entries
if ( ! m_show_owned_games_only | | level ! = node_level : : title_level | | visible_items > 0 )
{
visible_items + + ;
}
}
2020-06-02 10:39:24 +02:00
// Only show item if itself or any of its children is visible
item - > setHidden ( visible_items < = 0 ) ;
return visible_items ;
} ;
2021-03-07 19:55:40 +01:00
bool found_version = false ;
2020-06-02 10:39:24 +02:00
// Go through each top level item and try to find matches
2021-01-07 11:18:48 +01:00
for ( auto top_level_item : ui - > patch_tree - > findItems ( " .* " , Qt : : MatchRegularExpression ) )
2020-06-02 10:39:24 +02:00
{
2021-03-07 19:55:40 +01:00
if ( ! top_level_item )
continue ;
const int matches = show_matches ( top_level_item , false ) ;
if ( matches < = 0 | | ! m_expand_current_match )
continue ;
// Expand only items that match the serial and version
for ( int i = 0 ; i < top_level_item - > childCount ( ) ; i + + )
{
if ( const auto item = top_level_item - > child ( i ) ;
item & & ! item - > isHidden ( ) & & item - > data ( 0 , app_version_role ) . toString ( ) = = m_search_version )
{
// This should always be a serial level item
ensure ( item - > data ( 0 , node_level_role ) = = node_level : : serial_level ) ;
top_level_item - > setExpanded ( true ) ;
item - > setExpanded ( true ) ;
found_version = true ;
break ;
}
}
2020-06-02 10:39:24 +02:00
}
2021-03-07 19:55:40 +01:00
if ( m_expand_current_match & & ! found_version )
{
// Expand all matching top_level items if the correct version wasn't found
for ( auto top_level_item : ui - > patch_tree - > findItems ( " .* " , Qt : : MatchRegularExpression ) )
{
if ( ! top_level_item | | top_level_item - > isHidden ( ) )
continue ;
top_level_item - > setExpanded ( true ) ;
// Expand the "All Versions" item
for ( int i = 0 ; i < top_level_item - > childCount ( ) ; i + + )
{
if ( const auto item = top_level_item - > child ( i ) ;
item & & ! item - > isHidden ( ) & & item - > data ( 0 , app_version_role ) . toString ( ) . toStdString ( ) = = patch_key : : all )
{
// This should always be a serial level item
ensure ( item - > data ( 0 , node_level_role ) = = node_level : : serial_level ) ;
item - > setExpanded ( true ) ;
break ;
}
}
}
}
m_expand_current_match = false ;
2020-06-02 10:39:24 +02:00
}
2021-04-07 23:05:18 +02:00
void patch_manager_dialog : : update_patch_info ( const patch_manager_dialog : : gui_patch_info & info ) const
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
ui - > label_hash - > setText ( info . hash ) ;
ui - > label_author - > setText ( info . author ) ;
ui - > label_notes - > setText ( info . notes ) ;
ui - > label_description - > setText ( info . description ) ;
ui - > label_patch_version - > setText ( info . patch_version ) ;
ui - > label_serial - > setText ( info . serial ) ;
ui - > label_title - > setText ( info . title ) ;
ui - > label_app_version - > setText ( info . app_version ) ;
2020-06-02 10:39:24 +02:00
}
2020-08-02 17:36:22 +02:00
void patch_manager_dialog : : handle_item_selected ( QTreeWidgetItem * current , QTreeWidgetItem * /*previous*/ )
2020-06-02 10:39:24 +02:00
{
if ( ! current )
{
2020-06-26 02:58:06 +02:00
// Clear patch info if no item is selected
update_patch_info ( { } ) ;
2020-06-02 10:39:24 +02:00
return ;
}
2020-06-25 20:53:41 +02:00
const node_level level = static_cast < node_level > ( current - > data ( 0 , node_level_role ) . toInt ( ) ) ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
patch_manager_dialog : : gui_patch_info info { } ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
switch ( level )
{
case node_level : : patch_level :
{
// Get patch identifiers stored in item data
info . hash = current - > data ( 0 , hash_role ) . toString ( ) ;
const std : : string hash = info . hash . toStdString ( ) ;
const std : : string description = current - > data ( 0 , description_role ) . toString ( ) . toStdString ( ) ;
2020-06-25 20:53:41 +02:00
2020-06-26 02:58:06 +02:00
// Find the patch for this item and get its metadata
if ( m_map . find ( hash ) ! = m_map . end ( ) )
2020-06-25 20:53:41 +02:00
{
2020-06-26 02:58:06 +02:00
const auto & container = m_map . at ( hash ) ;
2020-06-25 20:53:41 +02:00
2021-01-08 18:36:12 +01:00
if ( container . patch_info_map . find ( description ) ! = container . patch_info_map . end ( ) )
2020-06-25 20:53:41 +02:00
{
2020-06-26 02:58:06 +02:00
const auto & found_info = container . patch_info_map . at ( description ) ;
info . author = QString : : fromStdString ( found_info . author ) ;
info . notes = QString : : fromStdString ( found_info . notes ) ;
info . description = QString : : fromStdString ( found_info . description ) ;
info . patch_version = QString : : fromStdString ( found_info . patch_version ) ;
2020-06-25 20:53:41 +02:00
}
}
2020-06-26 02:58:06 +02:00
[[fallthrough]] ;
}
case node_level : : serial_level :
{
2020-06-27 10:32:00 +02:00
const QString serial = current - > data ( 0 , serial_role ) . toString ( ) ;
info . serial = serial . toStdString ( ) = = patch_key : : all ? tr_all_serials : serial ;
const QString app_version = current - > data ( 0 , app_version_role ) . toString ( ) ;
info . app_version = app_version . toStdString ( ) = = patch_key : : all ? tr_all_versions : app_version ;
2020-06-26 02:58:06 +02:00
[[fallthrough]] ;
}
case node_level : : title_level :
2020-06-27 10:32:00 +02:00
{
const QString title = current - > data ( 0 , title_role ) . toString ( ) ;
info . title = title . toStdString ( ) = = patch_key : : all ? tr_all_titles : title ;
2020-06-26 02:58:06 +02:00
break ;
}
2020-06-02 10:39:24 +02:00
}
update_patch_info ( info ) ;
}
2020-08-02 17:36:22 +02:00
void patch_manager_dialog : : handle_item_changed ( QTreeWidgetItem * item , int /*column*/ )
2020-06-02 10:39:24 +02:00
{
if ( ! item )
{
return ;
}
// Get checkstate of the item
const bool enabled = item - > checkState ( 0 ) = = Qt : : CheckState : : Checked ;
// Get patch identifiers stored in item data
2020-06-26 02:58:06 +02:00
const node_level level = static_cast < node_level > ( item - > data ( 0 , node_level_role ) . toInt ( ) ) ;
2020-06-02 10:39:24 +02:00
const std : : string hash = item - > data ( 0 , hash_role ) . toString ( ) . toStdString ( ) ;
2020-06-26 02:58:06 +02:00
const std : : string title = item - > data ( 0 , title_role ) . toString ( ) . toStdString ( ) ;
const std : : string serial = item - > data ( 0 , serial_role ) . toString ( ) . toStdString ( ) ;
const std : : string app_version = item - > data ( 0 , app_version_role ) . toString ( ) . toStdString ( ) ;
2020-06-02 10:39:24 +02:00
const std : : string description = item - > data ( 0 , description_role ) . toString ( ) . toStdString ( ) ;
2020-06-26 02:58:06 +02:00
const std : : string patch_group = item - > data ( 0 , patch_group_role ) . toString ( ) . toStdString ( ) ;
// Uncheck other patches with the same patch_group if this patch was enabled
if ( const auto node = item - > parent ( ) ; node & & enabled & & ! patch_group . empty ( ) & & level = = node_level : : patch_level )
{
for ( int i = 0 ; i < node - > childCount ( ) ; i + + )
{
if ( const auto other = node - > child ( i ) ; other & & other ! = item )
{
const std : : string other_patch_group = other - > data ( 0 , patch_group_role ) . toString ( ) . toStdString ( ) ;
if ( other_patch_group = = patch_group )
{
other - > setCheckState ( 0 , Qt : : CheckState : : Unchecked ) ;
}
}
}
}
2020-06-02 10:39:24 +02:00
// Enable/disable the patch for this item and show its metadata
if ( m_map . find ( hash ) ! = m_map . end ( ) )
{
2020-06-19 13:46:56 +02:00
auto & container = m_map [ hash ] ;
2020-06-02 10:39:24 +02:00
2021-01-08 18:36:12 +01:00
if ( container . patch_info_map . find ( description ) ! = container . patch_info_map . end ( ) )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
m_map [ hash ] . patch_info_map [ description ] . titles [ title ] [ serial ] [ app_version ] = enabled ;
2020-08-02 17:36:22 +02:00
handle_item_selected ( item , nullptr ) ;
2020-06-02 10:39:24 +02:00
return ;
}
}
}
2020-08-02 17:36:22 +02:00
void patch_manager_dialog : : handle_custom_context_menu_requested ( const QPoint & pos )
2020-06-02 10:39:24 +02:00
{
QTreeWidgetItem * item = ui - > patch_tree - > itemAt ( pos ) ;
if ( ! item )
{
return ;
}
2020-06-27 00:23:04 +02:00
const node_level level = static_cast < node_level > ( item - > data ( 0 , node_level_role ) . toInt ( ) ) ;
2020-06-26 18:31:42 +02:00
2020-06-27 00:23:04 +02:00
QMenu * menu = new QMenu ( this ) ;
2020-06-02 10:39:24 +02:00
2020-06-27 00:23:04 +02:00
if ( level = = node_level : : patch_level )
2020-06-19 16:43:17 +02:00
{
// Find the patch for this item and add menu items accordingly
const std : : string hash = item - > data ( 0 , hash_role ) . toString ( ) . toStdString ( ) ;
const std : : string description = item - > data ( 0 , description_role ) . toString ( ) . toStdString ( ) ;
if ( m_map . find ( hash ) ! = m_map . end ( ) )
{
const auto & container = m_map . at ( hash ) ;
2021-01-08 18:36:12 +01:00
if ( container . patch_info_map . find ( description ) ! = container . patch_info_map . end ( ) )
2020-06-19 16:43:17 +02:00
{
2021-01-08 18:36:12 +01:00
const auto & info = container . patch_info_map . at ( description ) ;
2020-06-19 16:43:17 +02:00
2020-06-26 18:31:42 +02:00
QAction * open_filepath = new QAction ( tr ( " Show Patch File " ) ) ;
2020-06-19 16:43:17 +02:00
menu - > addAction ( open_filepath ) ;
connect ( open_filepath , & QAction : : triggered , this , [ info ] ( bool )
{
gui : : utils : : open_dir ( info . source_path ) ;
} ) ;
2020-06-19 19:47:51 +02:00
2020-06-27 00:23:04 +02:00
menu - > addSeparator ( ) ;
2020-06-19 19:47:51 +02:00
if ( info . source_path = = patch_engine : : get_imported_patch_path ( ) )
{
2020-06-26 18:31:42 +02:00
QAction * remove_patch = new QAction ( tr ( " Remove Patch " ) ) ;
2020-06-19 19:47:51 +02:00
menu - > addAction ( remove_patch ) ;
connect ( remove_patch , & QAction : : triggered , this , [ info , this ] ( bool )
{
const auto answer = QMessageBox : : question ( this , tr ( " Remove Patch? " ) ,
tr ( " Do you really want to remove the selected patch? \n This action is immediate and irreversible! " ) ) ;
if ( answer ! = QMessageBox : : StandardButton : : Yes )
{
return ;
}
if ( patch_engine : : remove_patch ( info ) )
{
patch_log . success ( " Successfully removed patch %s: %s from %s " , info . hash , info . description , info . source_path ) ;
refresh ( ) ; // Refresh before showing the dialog
QMessageBox : : information ( this , tr ( " Success " ) , tr ( " The patch was successfully removed! " ) ) ;
}
else
{
patch_log . error ( " Could not remove patch %s: %s from %s " , info . hash , info . description , info . source_path ) ;
refresh ( ) ; // Refresh before showing the dialog
2020-06-29 20:34:40 +02:00
QMessageBox : : critical ( this , tr ( " Failure " ) , tr ( " The patch could not be removed! " ) ) ;
2020-06-19 19:47:51 +02:00
}
} ) ;
2020-06-27 00:23:04 +02:00
menu - > addSeparator ( ) ;
2020-06-19 19:47:51 +02:00
}
2020-06-19 16:43:17 +02:00
}
}
}
2020-06-02 10:39:24 +02:00
2020-06-27 00:23:04 +02:00
if ( item - > childCount ( ) > 0 )
{
if ( item - > isExpanded ( ) )
{
QAction * collapse = new QAction ( tr ( " Collapse " ) ) ;
menu - > addAction ( collapse ) ;
connect ( collapse , & QAction : : triggered , this , [ & item ] ( bool )
{
item - > setExpanded ( false ) ;
} ) ;
if ( level < ( node_level : : patch_level - 1 ) )
{
menu - > addSeparator ( ) ;
QAction * expand_children = new QAction ( tr ( " Expand Children " ) ) ;
menu - > addAction ( expand_children ) ;
connect ( expand_children , & QAction : : triggered , this , [ & item ] ( bool )
{
for ( int i = 0 ; i < item - > childCount ( ) ; i + + )
{
item - > child ( i ) - > setExpanded ( true ) ;
}
} ) ;
QAction * collapse_children = new QAction ( tr ( " Collapse Children " ) ) ;
menu - > addAction ( collapse_children ) ;
connect ( collapse_children , & QAction : : triggered , this , [ & item ] ( bool )
{
for ( int i = 0 ; i < item - > childCount ( ) ; i + + )
{
item - > child ( i ) - > setExpanded ( false ) ;
}
} ) ;
}
}
else
{
QAction * expand = new QAction ( tr ( " Expand " ) ) ;
menu - > addAction ( expand ) ;
connect ( expand , & QAction : : triggered , this , [ & item ] ( bool )
{
item - > setExpanded ( true ) ;
} ) ;
}
menu - > addSeparator ( ) ;
}
QAction * expand_all = new QAction ( tr ( " Expand All " ) ) ;
menu - > addAction ( expand_all ) ;
connect ( expand_all , & QAction : : triggered , ui - > patch_tree , & QTreeWidget : : expandAll ) ;
QAction * collapse_all = new QAction ( tr ( " Collapse All " ) ) ;
menu - > addAction ( collapse_all ) ;
connect ( collapse_all , & QAction : : triggered , ui - > patch_tree , & QTreeWidget : : collapseAll ) ;
2020-06-02 10:39:24 +02:00
menu - > exec ( ui - > patch_tree - > viewport ( ) - > mapToGlobal ( pos ) ) ;
}
2020-06-11 15:12:44 +02:00
2020-06-12 21:09:08 +02:00
bool patch_manager_dialog : : is_valid_file ( const QMimeData & md , QStringList * drop_paths )
{
const QList < QUrl > list = md . urls ( ) ; // Get list of all the dropped file urls
if ( list . size ( ) ! = 1 ) // We only accept one file for now
{
return false ;
}
for ( auto & & url : list ) // Check each file in url list for valid type
{
2022-04-21 20:54:02 +02:00
const QString path = url . toLocalFile ( ) ; // Convert url to filepath
const QFileInfo info ( path ) ;
2020-06-12 21:09:08 +02:00
if ( ! info . fileName ( ) . endsWith ( " patch.yml " ) )
{
return false ;
}
if ( drop_paths )
{
drop_paths - > append ( path ) ;
}
}
return true ;
}
void patch_manager_dialog : : dropEvent ( QDropEvent * event )
{
QStringList drop_paths ;
if ( ! is_valid_file ( * event - > mimeData ( ) , & drop_paths ) )
{
return ;
}
2020-06-30 01:14:37 +02:00
QMessageBox box ( QMessageBox : : Icon : : Question , tr ( " Patch Manager " ) , tr ( " What do you want to do with the patch file? " ) , QMessageBox : : StandardButton : : Cancel , this ) ;
QPushButton * button_yes = box . addButton ( tr ( " Import " ) , QMessageBox : : YesRole ) ;
QPushButton * button_no = box . addButton ( tr ( " Validate " ) , QMessageBox : : NoRole ) ;
box . setDefaultButton ( button_yes ) ;
2020-06-12 21:09:08 +02:00
box . exec ( ) ;
const bool do_import = box . clickedButton ( ) = = button_yes ;
const bool do_validate = do_import | | box . clickedButton ( ) = = button_no ;
if ( ! do_validate )
{
return ;
}
2020-06-19 19:47:51 +02:00
for ( const QString & drop_path : drop_paths )
2020-06-12 21:09:08 +02:00
{
const auto path = drop_path . toStdString ( ) ;
patch_engine : : patch_map patches ;
std : : stringstream log_message ;
2020-09-06 11:47:45 +02:00
if ( patch_engine : : load ( patches , path , " " , true , & log_message ) )
2020-06-12 21:09:08 +02:00
{
patch_log . success ( " Successfully validated patch file %s " , path ) ;
if ( do_import )
{
2020-06-19 19:47:51 +02:00
static const std : : string imported_patch_yml_path = patch_engine : : get_imported_patch_path ( ) ;
2020-06-12 21:09:08 +02:00
2020-06-19 15:38:51 +02:00
log_message . clear ( ) ;
2020-12-18 08:39:54 +01:00
usz count = 0 ;
usz total = 0 ;
2020-06-19 21:48:59 +02:00
if ( patch_engine : : import_patches ( patches , imported_patch_yml_path , count , total , & log_message ) )
2020-06-12 21:09:08 +02:00
{
refresh ( ) ;
2020-06-30 00:16:24 +02:00
const std : : string message = log_message . str ( ) ;
const QString msg = message . empty ( ) ? " " : tr ( " \n \n Log: \n %0 " ) . arg ( QString : : fromStdString ( message ) ) ;
2020-06-19 21:48:59 +02:00
if ( count = = 0 )
{
2020-06-30 00:16:24 +02:00
QMessageBox : : warning ( this , tr ( " Nothing to import " ) , tr ( " None of the found %0 patches were imported.%1 " )
2020-06-19 21:48:59 +02:00
. arg ( total ) . arg ( msg ) ) ;
}
else
{
2020-06-30 00:16:24 +02:00
QMessageBox : : information ( this , tr ( " Import successful " ) , tr ( " Imported %0/%1 patches to: \n %2%3 " )
2020-06-19 21:48:59 +02:00
. arg ( count ) . arg ( total ) . arg ( QString : : fromStdString ( imported_patch_yml_path ) ) . arg ( msg ) ) ;
}
2020-06-12 21:09:08 +02:00
}
else
{
2020-06-29 20:34:40 +02:00
QMessageBox : : critical ( this , tr ( " Import failed " ) , tr ( " The patch file could not be imported. \n \n Log: \n %0 " ) . arg ( QString : : fromStdString ( log_message . str ( ) ) ) ) ;
2020-06-12 21:09:08 +02:00
}
}
else
{
QMessageBox : : information ( this , tr ( " Validation successful " ) , tr ( " The patch file passed the validation. " ) ) ;
}
}
else
{
patch_log . error ( " Errors found in patch file %s " , path ) ;
2022-04-09 20:54:35 +02:00
QString summary = QString : : fromStdString ( log_message . str ( ) ) ;
if ( summary . count ( QLatin1Char ( ' \n ' ) ) < 5 )
{
QMessageBox : : critical ( this , tr ( " Validation failed " ) , tr ( " Errors were found in the patch file. \n \n Log: \n %0 " ) . arg ( summary ) ) ;
}
else
{
QString message = tr ( " Errors were found in the patch file. " ) ;
QMessageBox mb ( QMessageBox : : Icon : : Critical , tr ( " Validation failed " ) , message , QMessageBox : : Ok , this ) ;
mb . setInformativeText ( tr ( " To see the error log, please click \" Show Details \" . " ) ) ;
mb . setDetailedText ( tr ( " %0 " ) . arg ( summary ) ) ;
// Smartass hack to make the unresizeable message box wide enough for the changelog
const int log_width = QLabel ( summary ) . sizeHint ( ) . width ( ) ;
while ( QLabel ( message ) . sizeHint ( ) . width ( ) < log_width )
{
message + = " " ;
}
mb . setText ( message ) ;
mb . exec ( ) ;
}
2020-06-12 21:09:08 +02:00
}
}
}
2020-08-02 17:36:22 +02:00
void patch_manager_dialog : : handle_show_owned_games_only ( int state )
2020-06-26 18:31:42 +02:00
{
m_show_owned_games_only = state = = Qt : : CheckState : : Checked ;
2020-07-03 17:38:54 +02:00
m_gui_settings - > SetValue ( gui : : pm_show_owned , m_show_owned_games_only ) ;
2020-06-26 18:31:42 +02:00
filter_patches ( ui - > patch_filter - > text ( ) ) ;
}
2020-06-12 21:09:08 +02:00
void patch_manager_dialog : : dragEnterEvent ( QDragEnterEvent * event )
{
if ( is_valid_file ( * event - > mimeData ( ) ) )
{
event - > accept ( ) ;
}
}
void patch_manager_dialog : : dragMoveEvent ( QDragMoveEvent * event )
{
if ( is_valid_file ( * event - > mimeData ( ) ) )
{
event - > accept ( ) ;
}
}
void patch_manager_dialog : : dragLeaveEvent ( QDragLeaveEvent * event )
{
event - > accept ( ) ;
}
2020-09-06 11:47:45 +02:00
2021-11-18 20:47:17 +01:00
void patch_manager_dialog : : download_update ( bool automatic , bool auto_accept )
2020-09-06 11:47:45 +02:00
{
2021-11-18 20:47:17 +01:00
patch_log . notice ( " Patch download triggered (automatic=%d, auto_accept=%d) " , automatic , auto_accept ) ;
ui - > buttonBox - > button ( QDialogButtonBox : : RestoreDefaults ) - > setEnabled ( false ) ;
m_download_automatic = automatic ;
m_download_auto_accept = auto_accept ;
2020-09-07 14:10:57 +02:00
const std : : string path = patch_engine : : get_patches_path ( ) + " patch.yml " ;
std : : string url = " https://rpcs3.net/compatibility?patch&api=v1&v= " + patch_engine_version ;
if ( fs : : is_file ( path ) )
{
2021-04-07 23:05:18 +02:00
if ( const fs : : file patch_file { path } )
2020-09-07 14:10:57 +02:00
{
const std : : string hash = downloader : : get_hash ( patch_file . to_string ( ) . c_str ( ) , patch_file . size ( ) , true ) ;
url + = " &sha256= " + hash ;
}
else
{
2020-09-09 22:25:02 +02:00
patch_log . error ( " Could not open patch file: %s (%s) " , path , fs : : g_tls_error ) ;
2020-09-07 14:10:57 +02:00
return ;
}
}
2021-11-18 20:47:17 +01:00
m_downloader - > start ( url , true , ! m_download_automatic , tr ( " Downloading latest patches " ) ) ;
2020-09-06 11:47:45 +02:00
}
bool patch_manager_dialog : : handle_json ( const QByteArray & data )
{
const QJsonObject json_data = QJsonDocument : : fromJson ( data ) . object ( ) ;
const int return_code = json_data [ " return_code " ] . toInt ( - 255 ) ;
if ( return_code < 0 )
{
std : : string error_message ;
switch ( return_code )
{
2020-09-07 14:10:57 +02:00
case - 1 : error_message = " No patches found for the specified version " ; break ;
2020-09-06 11:47:45 +02:00
case - 2 : error_message = " Server Error - Maintenance Mode " ; break ;
2020-09-07 14:10:57 +02:00
case - 3 : error_message = " Server Error - Illegal Search " ; break ;
2020-09-06 11:47:45 +02:00
case - 255 : error_message = " Server Error - Return code not found " ; break ;
default : error_message = " Server Error - Unknown Error " ; break ;
}
if ( return_code ! = - 1 )
patch_log . error ( " Patch download error: %s return code: %d " , error_message , return_code ) ;
else
patch_log . warning ( " Patch download error: %s return code: %d " , error_message , return_code ) ;
return false ;
}
2020-09-07 14:10:57 +02:00
if ( return_code = = 1 )
{
patch_log . notice ( " Patch download: No newer patches found " ) ;
2021-11-18 20:47:17 +01:00
if ( ! m_download_automatic )
{
QMessageBox : : information ( this , tr ( " Download successful " ) , tr ( " Your patch file is already up to date. " ) ) ;
}
2020-09-07 14:10:57 +02:00
return true ;
}
if ( return_code ! = 0 )
{
patch_log . error ( " Patch download error: unknown return code: %d " , return_code ) ;
return false ;
}
2021-11-18 20:47:17 +01:00
// TODO: check for updates first instead of loading the whole file immediately
if ( ! m_download_auto_accept )
{
const QMessageBox : : StandardButton answer = QMessageBox : : question ( this , tr ( " Update patches? " ) , tr ( " New patches are available. \n \n Do you want to update? " ) ) ;
if ( answer ! = QMessageBox : : StandardButton : : Yes )
{
return true ;
}
}
2020-09-06 11:47:45 +02:00
const QJsonValue & version_obj = json_data [ " version " ] ;
if ( ! version_obj . isString ( ) )
{
patch_log . error ( " JSON doesn't contain version " ) ;
return false ;
}
if ( const std : : string version = version_obj . toString ( ) . toStdString ( ) ;
version ! = patch_engine_version )
{
patch_log . error ( " JSON contains wrong version: %s (needed: %s) " , version , patch_engine_version ) ;
return false ;
}
2020-09-07 14:10:57 +02:00
const QJsonValue & hash_obj = json_data [ " sha256 " ] ;
if ( ! hash_obj . isString ( ) )
{
patch_log . error ( " JSON doesn't contain sha256 " ) ;
return false ;
}
2020-09-06 11:47:45 +02:00
const QJsonValue & patch = json_data [ " patch " ] ;
if ( ! patch . isString ( ) | | patch . toString ( ) . isEmpty ( ) )
{
patch_log . error ( " JSON doesn't contain patch " ) ;
return false ;
}
patch_engine : : patch_map patches ;
std : : stringstream log_message ;
const std : : string content = patch . toString ( ) . toStdString ( ) ;
2020-09-07 14:10:57 +02:00
if ( hash_obj . toString ( ) . toStdString ( ) ! = downloader : : get_hash ( content . c_str ( ) , content . size ( ) , true ) )
2020-09-06 11:47:45 +02:00
{
2020-09-07 14:10:57 +02:00
patch_log . error ( " JSON content does not match the provided checksum " ) ;
return false ;
}
2020-09-06 11:47:45 +02:00
2020-09-07 14:10:57 +02:00
if ( patch_engine : : load ( patches , " From Download " , content , true , & log_message ) )
{
patch_log . notice ( " Successfully validated downloaded patch file " ) ;
const std : : string path = patch_engine : : get_patches_path ( ) + " patch.yml " ;
2020-09-06 11:47:45 +02:00
2020-09-07 14:10:57 +02:00
// Back up current patch file if possible
if ( fs : : is_file ( path ) )
2020-09-06 11:47:45 +02:00
{
2020-09-10 01:16:30 +02:00
if ( const std : : string back_up_path = path + " .old " ;
! fs : : rename ( path , back_up_path , true ) )
2020-09-07 14:10:57 +02:00
{
2020-09-10 01:16:30 +02:00
patch_log . error ( " Could not back up current patches to %s (%s) " , back_up_path , fs : : g_tls_error ) ;
2020-09-07 14:10:57 +02:00
return false ;
}
2020-09-06 11:47:45 +02:00
}
// Overwrite current patch file
2021-10-09 19:56:50 +02:00
fs : : pending_file patch_file ( path ) ;
if ( ! patch_file . file | | ( patch_file . file . write ( content ) , ! patch_file . commit ( ) ) )
2020-09-06 11:47:45 +02:00
{
2020-09-09 22:25:02 +02:00
patch_log . error ( " Could not save new patches to %s (%s) " , path , fs : : g_tls_error ) ;
2020-09-07 14:10:57 +02:00
return false ;
2020-09-06 11:47:45 +02:00
}
refresh ( ) ;
2020-09-09 20:02:23 +02:00
patch_log . success ( " Successfully downloaded latest patch file " ) ;
QMessageBox : : information ( this , tr ( " Download successful " ) , tr ( " Your patch file is now up to date " ) ) ;
2020-09-06 11:47:45 +02:00
}
else
{
patch_log . error ( " Errors found in downloaded patch file " ) ;
2022-04-09 20:54:35 +02:00
QString summary = QString : : fromStdString ( log_message . str ( ) ) ;
if ( summary . count ( QLatin1Char ( ' \n ' ) ) < 5 )
{
QMessageBox : : critical ( this , tr ( " Validation failed " ) , tr ( " Errors were found in the downloaded patch file. \n \n Log: \n %0 " ) . arg ( summary ) ) ;
}
else
{
QString message = tr ( " Errors were found in the downloaded patch file. " ) ;
QMessageBox mb ( QMessageBox : : Icon : : Critical , tr ( " Validation failed " ) , message , QMessageBox : : Ok , this ) ;
mb . setInformativeText ( tr ( " To see the error log, please click \" Show Details \" . " ) ) ;
mb . setDetailedText ( tr ( " %0 " ) . arg ( summary ) ) ;
// Smartass hack to make the unresizeable message box wide enough for the changelog
const int log_width = QLabel ( message ) . sizeHint ( ) . width ( ) ;
while ( QLabel ( message ) . sizeHint ( ) . width ( ) < log_width )
{
message + = " " ;
}
mb . setText ( message ) ;
mb . exec ( ) ;
}
2020-09-06 11:47:45 +02:00
}
return true ;
}