#include "patchinstaller.h" #include "legacyapplication.h" #include "models/patchdownloader.h" #include "models/filesystem.h" #include "models/settings.h" #include "models/datoriginalfilesdatabase.h" #include #include #include #include QString getComponentNameFromId(int id) { switch (id) { case 100: return "texts_main"; case 101: return "texts_items"; case 102: return "texts_emotes"; case 200: return "maps"; case 201: return "loadscreens"; case 202: return "textures"; case 300: return "sounds"; case 301: return "videos"; case 500: return "fonts"; } return "none"; } void PatchInstaller::createDatFile(int id, QString name, int component, QString path){ datfiles[id] = *new datfile; datfiles[id].id = id; datfiles[id].name = name; datfiles[id].path = path; datfiles[id].file = new LOTRO_DAT::DatFile(component); datfiles[id].file->Init(path.toStdString());// инициализируем datfiles[id].init = datfiles[id].file->Initialized(); // проверяем инициализацию if(datfiles[id].init){ datfiles[id].maxiter = datfiles[id].file->GetDatFileMaxIteration(); qInfo() << "Init" << datfiles[id].name << ", result" << datfiles[id].init << ", iterations - " << datfiles[id].maxiter; } else { qCritical() << "Failed CheckAppPrerequisities: initialization of " + name + " dat file failed!"; } } QMap PatchInstaller::getDatFiles(){ return this->datfiles; } PatchInstaller::PatchInstaller(QObject *parent) : QObject(parent){ connect(&PatchDownloader::instance(), &PatchDownloader::finished, this, [this](){startPatchInstallationChain();}); } bool PatchInstaller::initialised(){ foreach(datfile file, datfiles){ if(file.init == false) return false; } return true; } void PatchInstaller::checkIfUpdatedByGame(){ const int client_local_saved_maxiter = _current_applied_patches_info.client_local_header_maxiter; const int client_general_saved_maxiter = _current_applied_patches_info.client_general_header_maxiter; const int client_surface_saved_maxiter = _current_applied_patches_info.client_surface_header_maxiter; emit updatedByGameStatusChanged( (client_local_saved_maxiter != -1 && datfiles[0].maxiter != client_local_saved_maxiter) || (client_general_saved_maxiter != -1 && datfiles[1].maxiter != client_general_saved_maxiter) || (client_surface_saved_maxiter != -1 && datfiles[3].maxiter != client_surface_saved_maxiter) ); } PatchInstaller::~PatchInstaller() { deinit(); delete datfiles[0].file; delete datfiles[1].file; delete datfiles[3].file; } // ############## PRIVATE ############## // bool PatchInstaller::datPathIsRelevant() { QString game_folder = Settings::getValue("Lotro/game_path").toString(); QString locale_prefix = Settings::getValue("Lotro/original_locale").toString(); QString client_local_filepath = game_folder + "/client_local_" + locale_prefix + ".dat"; QString client_general_filepath = game_folder + "/client_general.dat"; QString client_surface_filepath = game_folder + "/client_surface.dat"; QString client_local_current_path = QString::fromStdString(datfiles[0].file->GetFilename()); QString client_general_current_path = QString::fromStdString(datfiles[1].file->GetFilename()); QString client_surface_current_path = QString::fromStdString(datfiles[3].file->GetFilename()); return QFileInfo(client_local_filepath) != QFileInfo(client_local_current_path) || QFileInfo(client_general_filepath) != QFileInfo(client_general_current_path) || QFileInfo(client_surface_filepath) != QFileInfo(client_surface_current_path); } void PatchInstaller::deinit() { datfiles[0].file->Deinit(); datfiles[1].file->Deinit(); datfiles[3].file->Deinit(); emit deinitialized(); } PatchInstaller::AppliedPatchesInfo PatchInstaller::getPatchesInfo() { return _current_applied_patches_info; } void PatchInstaller::installPatch(QString patch_name, LOTRO_DAT::Database* database) { qInfo() << "PatchInstaller: Installing patch " << patch_name; const QString locale_prefix = Settings::getValue("Lotro/original_locale").toString(); const QString database_path = QApplication::applicationDirPath() + "/data/" + patch_name + "_" + locale_prefix + "_orig.db"; const QString database_path_old = QApplication::applicationDirPath() + "/data/" + patch_name + "_orig.db"; if (FileSystem::fileExists(database_path_old)) { if (!FileSystem::fileExists(database_path)) { QFile::rename(database_path_old, database_path); } else { QFile::remove(database_path_old); } } DatOriginalFilesDatabase orig_files_db(database_path); LOTRO_DAT::SubfileData file; qDebug() << "Total files in database " << database->CountRows(); qInfo() << "Patching all files from database..." << database_path; while (!(file = database->GetNextFile()).Empty()) { if (!file.options["fid"]) { continue; } current_status.finished_parts++; if (current_status.finished_parts * 100 / current_status.total_parts != (current_status.finished_parts - 1) * 100 * 10 / current_status.total_parts) { // emitting if changed at least on 0.1% emit progressChanged(current_status); } const int dat_id = file.options["did"] ? file.options["did"].as() : E_CLIENT_LOCAL; if (dat_id != E_CLIENT_LOCAL && dat_id != E_CLIENT_GENERAL && dat_id != E_CLIENT_SURFACE) { qWarning() << "Unknown dat id parameter for file " << file.options["fid"].as() << " (dat id value = " << dat_id << "), SKIPPING!"; continue; } const int file_id = file.options["fid"].as(); int file_version = -1; int file_iteration = -1; file_version = datfiles[dat_id].file->GetFileVersion(file_id); file_iteration = datfiles[dat_id].file->getSubfileInfo(file_id).iteration; // qDebug() << "File" << file_id << "version:" << file_version << "iteration:" << file_iteration; if (file_version != _patch_files_versions && file_version != 0) { LOTRO_DAT::SubfileData data; data = datfiles[dat_id].file->GetFile(file_id); // тут у нас только текстуры шрифтов, их отменять не нужно if (dat_id != E_CLIENT_SURFACE){ orig_files_db.addFile(file_id, dat_id, file_version, file_iteration, data); } } datfiles[dat_id].file->PatchFile(file, _patch_files_versions); } const QString hashsum = Settings::getValue("PatchDatabases/" + patch_name + "/hashsum").toString(); if (patch_name == "text") { _current_applied_patches_info.has_no_patch_mark = false; _current_applied_patches_info.texts_patch_hashsum = hashsum; } else if (patch_name == "image") { _current_applied_patches_info.has_no_patch_mark = false; _current_applied_patches_info.images_patch_hashsum = hashsum; } else if (patch_name == "sound") { _current_applied_patches_info.has_no_patch_mark = false; _current_applied_patches_info.sounds_patch_hashsum = hashsum; } else if (patch_name == "font") { _current_applied_patches_info.has_no_patch_mark = false; _current_applied_patches_info.fonts_patch_hashsum = hashsum; } else if (patch_name == "texture") { _current_applied_patches_info.has_no_patch_mark = false; _current_applied_patches_info.textures_patch_hashsum = hashsum; } orig_files_db.closeDatabase(); insertPatchesInfoInDatFile(_current_applied_patches_info); if (patch_name == "video") { installVideos(database); } if (patch_name == "loadscreen") { installLoadscreens(database); } qInfo() << "Successfully installed patch " << patch_name; } void PatchInstaller::installOriginalPatch(QString patch_name) { qInfo() << "PatchInstaller: Installing original version of patch " << patch_name; const QString locale_prefix = Settings::getValue("Lotro/original_locale").toString(); const QString database_path = QApplication::applicationDirPath() + "/data/" + patch_name + "_" + locale_prefix + "_orig.db"; const QString database_path_old = QApplication::applicationDirPath() + "/data/" + patch_name + "_orig.db"; if (FileSystem::fileExists(database_path_old)) { if (!FileSystem::fileExists(database_path)) { QFile::rename(database_path_old, database_path); } else { QFile::remove(database_path_old); } } DatOriginalFilesDatabase orig_db(database_path); qDebug() << "Opened original database " << database_path << ", beginning installation"; qDebug() << "Files in database: " << orig_db.getRowsCount(); DatOriginalFilesDatabase::FileOperation operation = [this](int file_id, int dat_id, int version, int iteration, const LOTRO_DAT::SubfileData& data) { int original_file_version = 0; original_file_version = datfiles[dat_id].file->GetFileVersion(file_id); if (original_file_version == _patch_files_versions){ datfiles[dat_id].file->PatchFile(data, version, iteration); } current_status.finished_parts++; if (current_status.finished_parts * 100 / current_status.total_parts != (current_status.finished_parts - 1) * 100 * 10 / current_status.total_parts) { // emitting if changed at least on 0.1% emit progressChanged(current_status); } }; orig_db.performOperationOnAllFiles(operation); if (patch_name == "text") { _current_applied_patches_info.texts_patch_hashsum = ""; } else if (patch_name == "image") { _current_applied_patches_info.images_patch_hashsum = ""; } else if (patch_name == "sound") { _current_applied_patches_info.sounds_patch_hashsum = ""; } else if (patch_name == "texture") { _current_applied_patches_info.textures_patch_hashsum = ""; } else if (patch_name == "font") { _current_applied_patches_info.fonts_patch_hashsum = ""; } else if (patch_name == "video") { _current_applied_patches_info.videos_patch_hashsum = ""; } else if (patch_name == "loadscreen") { _current_applied_patches_info.loadscreens_patch_hashsum = ""; } insertPatchesInfoInDatFile(_current_applied_patches_info); qInfo() << "Successfully installed original version of patch" << patch_name; } void PatchInstaller::installLoadscreens(LOTRO_DAT::Database* database) { QString locale_prefix = Settings::getValue("Lotro/original_locale").toString(); const QStringList loadscreens_filenames = { locale_prefix == "English" ? "lotro_ad_pregame.jpg" : "lotro_ad_pregame_" + locale_prefix + ".jpg", "lotro_generic_teleport_screen_01.jpg", "lotro_generic_teleport_screen_02.jpg", "lotro_generic_teleport_screen_03.jpg", "lotro_generic_teleport_screen_04.jpg", "lotro_generic_teleport_screen_05.jpg", "lotro_generic_teleport_screen_06.jpg", "lotro_generic_teleport_screen_07.jpg", "lotro_generic_teleport_screen_08.jpg", "lotro_generic_teleport_screen_09.jpg", "lotro_generic_teleport_screen_10.jpg" }; LOTRO_DAT::SubfileData data; QString logo_path = Settings::getValue("Lotro/game_path").toString() + "/raw/" + (locale_prefix == "English" ? "en" : locale_prefix) + "/logo/"; for (size_t i = 0; i < qMin(size_t(loadscreens_filenames.size()), database->CountRows()); ++i) { data = database->GetNextFile(); if (data.options["did"].as() != -1) { continue; } QFile::remove(logo_path + loadscreens_filenames[i]); if (!data.binary_data.WriteToFile((logo_path + loadscreens_filenames[i]).toLocal8Bit())) { qWarning() << "InstallLoadscreens: Cannot write to file " << logo_path + loadscreens_filenames[i]; } current_status.finished_parts++; if (current_status.finished_parts * 100 / current_status.total_parts != (current_status.finished_parts - 1) * 100 * 10 / current_status.total_parts) { // emitting if changed at least on 0.1% emit progressChanged(current_status); } } _current_applied_patches_info.has_no_patch_mark = false; _current_applied_patches_info.loadscreens_patch_hashsum = Settings::getValue("PatchDatabases/loadscreen/hashsum").toString(); insertPatchesInfoInDatFile(_current_applied_patches_info); } void PatchInstaller::installVideos(LOTRO_DAT::Database* database) { qInfo() << "PatchInstaller: Starting downloading videos"; current_status.finished_parts += database->CountRows(); emit progressChanged(current_status); download_video_total_videos = database->CountRows(); download_video_finished_videos = 0; LOTRO_DAT::SubfileData file; while (!(file = database->GetNextFile()).Empty()) { if (!file.options["name"] || !file.options["url"] || !file.options["hash"] || !file.options["folder"]) { continue; } const QString filename = QString::fromStdString(file.options["name"].as()); const QString url = QString::fromStdString(file.options["url"].as()); const QString hash = QString::fromStdString(file.options["hash"].as()); const QString folder = QString::fromStdString(file.options["folder"].as()); const QString full_filename = Settings::getValue("Lotro/game_path").toString() + "/" + folder + "/" + filename; qInfo() << "PatchInstaller::installVideos: Processing file " << filename << " with patch hash " << hash; FileSystem::createFilePath(full_filename); if (FileSystem::fileExists(full_filename) && FileSystem::fileHash(full_filename) == hash) { download_video_finished_videos++; qInfo() << "PatchInstaller::installVideos: File " << filename << " hash matches patch, skipping download"; continue; } QFile* target_file = new QFile(full_filename); target_file->open(QIODevice::WriteOnly); Downloader* video_downloader = new Downloader(this); connect(video_downloader, &Downloader::progressChanged, this, &PatchInstaller::onDownloaderProgressChanged); video_downloader->setUrl(url); video_downloader->targetFile = target_file; qInfo() << "PatchInstaller::installVideos: Starting download of file " << filename; video_downloader->start(); video_downloader->waitForDownloaded(); video_downloader->targetFile->close(); video_downloader->targetFile->deleteLater(); video_downloader->targetFile = nullptr; video_downloader->deleteLater(); qInfo() << "PatchInstaller::installVideos: Finished download of file " << filename; download_video_finished_videos++; } _current_applied_patches_info.has_no_patch_mark = false; _current_applied_patches_info.videos_patch_hashsum = Settings::getValue("PatchDatabases/video/hashsum").toString(); insertPatchesInfoInDatFile(_current_applied_patches_info); qInfo() << "PatchInstaller: finished downloading videos"; } PatchInstaller::AppliedPatchesInfo PatchInstaller::getAppliedPatchesInfoFromDatFile(){ PatchInstaller::AppliedPatchesInfo result; const LOTRO_DAT::SubfileData patch_versions_file = datfiles[0].file->GetFile(_applied_patches_file_id); result.has_no_patch_mark = true; std::string schar = (char*)patch_versions_file.binary_data.data(); QString sstr = QString::fromStdString(schar); qInfo() << sstr; if(!patch_versions_file.Empty() && sstr != "" && sstr.indexOf("HI_FROM_ENDEVIR")) { const QString text_data = QString::fromUtf8(reinterpret_cast(patch_versions_file.binary_data.data() + 4), patch_versions_file.binary_data.size() - 4); const QStringList text_data_splitted = text_data.split("\n"); qInfo() << text_data; if (text_data_splitted.length() < 0 || text_data_splitted[0] != _patch_mark_header){ return result; } for (const QString& str: text_data_splitted) { if (str.startsWith("TEXTS:")) { result.texts_patch_hashsum = QString(str).remove(0, 6); } else if (str.startsWith("IMAGES:")) { result.images_patch_hashsum = QString(str).remove(0, 7); } else if (str.startsWith("SOUNDS:")) { result.sounds_patch_hashsum = QString(str).remove(0, 7); } else if (str.startsWith("LOADSCREENS:")) { result.loadscreens_patch_hashsum = QString(str).remove(0, 12); } else if (str.startsWith("TEXTURES:")) { result.textures_patch_hashsum = QString(str).remove(0, 9); } else if (str.startsWith("FONTS:")) { result.fonts_patch_hashsum = QString(str).remove(0, 6); } else if (str.startsWith("VIDEOS:")) { result.videos_patch_hashsum = QString(str).remove(0, 7); } else if (str.startsWith("CLIENT_LOCAL_HEADER_MAXITER:")) { result.client_local_header_maxiter = QString(str).remove(0, 28).toInt(); } else if (str.startsWith("CLIENT_GENERAL_HEADER_MAXITER:")) { result.client_general_header_maxiter = QString(str).remove(0, 30).toInt(); } else if (str.startsWith("CLIENT_SURFACE_HEADER_MAXITER:")) { result.client_surface_header_maxiter = QString(str).remove(0, 30).toInt(); } } if (result.texts_patch_hashsum != "" || result.images_patch_hashsum != "" || result.textures_patch_hashsum != "" || result.sounds_patch_hashsum != "" || result.videos_patch_hashsum != "" || result.fonts_patch_hashsum != "" || result.loadscreens_patch_hashsum != "") { result.has_no_patch_mark = false; } } qInfo() << "Dat is modified: " << result.has_no_patch_mark; return result; } void PatchInstaller::insertPatchesInfoInDatFile(const PatchInstaller::AppliedPatchesInfo& info){ LOTRO_DAT::SubfileData file_data; file_data.options["fid"] = _applied_patches_file_id; file_data.options["ext"] = LOTRO_DAT::StringFromFileType(LOTRO_DAT::TEXT); QString patches_info_data = _patch_mark_header + "\n"; patches_info_data += "TEXTS:" + info.texts_patch_hashsum + "\n"; patches_info_data += "IMAGES:" + info.images_patch_hashsum + "\n"; patches_info_data += "SOUNDS:" + info.sounds_patch_hashsum + "\n"; patches_info_data += "LOADSCREENS:" + info.loadscreens_patch_hashsum + "\n"; patches_info_data += "TEXTURES:" + info.textures_patch_hashsum + "\n"; patches_info_data += "FONTS:" + info.fonts_patch_hashsum + "\n"; patches_info_data += "VIDEOS:" + info.videos_patch_hashsum + "\n"; patches_info_data += "CLIENT_LOCAL_HEADER_MAXITER:" + QString::number(datfiles[0].file->GetDatFileMaxIteration()) + "\n"; patches_info_data += "CLIENT_GENERAL_HEADER_MAXITER:" + QString::number(datfiles[1].file->GetDatFileMaxIteration()) + "\n"; patches_info_data += "CLIENT_SURFACE_HEADER_MAXITER:" + QString::number(datfiles[3].file->GetDatFileMaxIteration()) + "\n"; QByteArray data = QByteArray((char*)&_applied_patches_file_id, 4) + patches_info_data.toUtf8(); qInfo() << "Set info to dat-file: " << data; file_data.binary_data = LOTRO_DAT::BinaryData(data.data(), data.size()); datfiles[0].file->PatchFile(file_data, _patch_files_versions, 1, true); } // ############## PUBLIC SLOTS ############## // void PatchInstaller::init() { emit initializationStarted(); qInfo() << "PatchInstaller: Starting initialisation of LotroDatManager"; qRegisterMetaType(); // Создаем массив нужных нам dat-файлов QString game_folder = Settings::getValue("Lotro/game_path").toString(); QString locale_prefix = Settings::getValue("Lotro/original_locale").toString(); createDatFile(0, "client_local", 100, game_folder + "/client_local_" + locale_prefix + ".dat"); createDatFile(1, "client_general", 101, game_folder + "/client_general.dat"); createDatFile(3, "client_surface", 0, game_folder + "/client_surface.dat"); // Создаем массив нужных нам dat-файлов _current_applied_patches_info = getAppliedPatchesInfoFromDatFile(); checkIfUpdatedByGame(); emit successfullyInitialized(); } void PatchInstaller::startGame(bool remove_dat_files) { QString game_folder = Settings::getValue("Lotro/game_path").toString(); if (game_folder == "none") { qCritical() << __FUNCTION__ << "Starting game FAILED - game folder wasnt set!"; return; } if (!FileSystem::fileExists(game_folder + "/LotroLauncher.exe")) { qCritical() << __FUNCTION__ << "Starting game FAILED - LotroLauncher.exe not found!"; return; } QStringList args; args << "-nosplashscreen"; if (_current_applied_patches_info.loadscreens_patch_hashsum != "") { args << "-skiprawdownload"; } datfiles[0].file->Deinit(); datfiles[1].file->Deinit(); datfiles[3].file->Deinit(); if(remove_dat_files){ QFile::remove(datfiles[0].path); QFile::remove(datfiles[1].path); } QString username = Settings::getValue("Account/username").toString(); QString password = Settings::getValue("Account/password").toString(); if (!username.isEmpty() && !password.isEmpty()) { args << "-username" << username << "-password" << password; } qInfo() << "Starting LOTRO. Path =" << game_folder; deinit(); QProcess process; process.startDetached(game_folder + "/LotroLauncher.exe", args, game_folder); process.waitForFinished(-1); QMetaObject::invokeMethod(&LegacyApplication::instance(), &LegacyApplication::close, Qt::QueuedConnection); } void PatchInstaller::startPatchInstallationChain(bool force_reinstall_patches) { emit started(); qInfo() << "PatchInstaller: Starting installation chain..."; const QVector patches = {"text", "font", "image", "loadscreen", "texture", "sound", "video", "micro"}; QMap patch_databases; current_status.total_parts = 0; current_status.finished_parts = 0; for (const QString& patch: patches) { if (!Settings::getValue("DatabaseDownload/" + patch).toBool()) { continue; } const QString patch_hashsum = Settings::getValue("PatchDatabases/" + patch + "/hashsum").toString(); const QString patch_filename = Settings::getValue("PatchDatabases/" + patch + "/path").toString(); QString hashsum_in_dat_file = ""; if (patch == "text") { hashsum_in_dat_file = _current_applied_patches_info.texts_patch_hashsum; } else if (patch == "image") { hashsum_in_dat_file = _current_applied_patches_info.images_patch_hashsum; } else if (patch == "sound") { hashsum_in_dat_file = _current_applied_patches_info.sounds_patch_hashsum; } else if (patch == "texture") { hashsum_in_dat_file = _current_applied_patches_info.textures_patch_hashsum; } else if (patch == "font") { hashsum_in_dat_file = _current_applied_patches_info.fonts_patch_hashsum; } else if (patch == "video") { hashsum_in_dat_file = _current_applied_patches_info.videos_patch_hashsum; } else if (patch == "loadscreen") { hashsum_in_dat_file = _current_applied_patches_info.loadscreens_patch_hashsum; } if (!force_reinstall_patches && patch_hashsum == hashsum_in_dat_file) { qInfo() << "PatchInstaller: Skipping patch " << patch << " because its hashsum (" << patch_hashsum << ") is equal to hashsum in dat file (" << hashsum_in_dat_file << ")"; continue; } if (force_reinstall_patches) { qInfo() << "PatchInstaller: Force installing patch " << patch << ". Its hashsum (" << patch_hashsum << "). Hashsum in dat file (" << hashsum_in_dat_file << ")"; } const QString real_file_hashsum = FileSystem::fileHash(patch_filename); if (!FileSystem::fileExists(patch_filename) || real_file_hashsum != patch_hashsum) { qCritical() << "PatchInstaller: Incorrect patch file: " << patch_filename << ", hashsum: " << real_file_hashsum << ", expected: " << patch_hashsum; continue; } LOTRO_DAT::Database* db = new LOTRO_DAT::Database(); if (!db->InitDatabase(patch_filename.toStdString())) { qCritical() << "PatchInstaller: failed to initialize db " << patch_filename; continue; } patch_databases[patch] = db; current_status.total_parts += db->CountRows(); qInfo() << "Adding patch " << patch << " to install queue. Total parts to be installed: " << current_status.total_parts; } // Parsing info about patches which original versions should be installed QStringList patches_to_be_installed_orig_versions; for (const QString& patch: patches) { if (Settings::getValue("DatabaseDownload/" + patch).toBool()) { continue; } QString hashsum_in_dat_file = ""; if (patch == "text") { hashsum_in_dat_file = _current_applied_patches_info.texts_patch_hashsum; } else if (patch == "image") { hashsum_in_dat_file = _current_applied_patches_info.images_patch_hashsum; } else if (patch == "sound") { hashsum_in_dat_file = _current_applied_patches_info.sounds_patch_hashsum; } else if (patch == "texture") { hashsum_in_dat_file = _current_applied_patches_info.textures_patch_hashsum; } else if (patch == "font") { hashsum_in_dat_file = _current_applied_patches_info.fonts_patch_hashsum; } else if (patch == "video") { hashsum_in_dat_file = _current_applied_patches_info.videos_patch_hashsum; } else if (patch == "loadscreen") { hashsum_in_dat_file = _current_applied_patches_info.loadscreens_patch_hashsum; } if (hashsum_in_dat_file == "") { qInfo() << "PatchInstaller: Skipping installing original version of patch " << patch << " because hashsum in dat file is already empty"; continue; } patches_to_be_installed_orig_versions.append(patch); const QString locale_prefix = Settings::getValue("Lotro/original_locale").toString(); const QString database_path = QApplication::applicationDirPath() + "/data/" + patch + "_" + locale_prefix + "_orig.db"; DatOriginalFilesDatabase db(database_path); current_status.total_parts += db.getRowsCount(); qInfo() << "Adding patch " << patch << " to original versions install queue. Total parts to be installed: " << current_status.total_parts; } emit progressChanged(current_status); for (const QString& patch_name: patch_databases.keys()) { installPatch(patch_name, patch_databases[patch_name]); patch_databases[patch_name]->CloseDatabase(); delete patch_databases[patch_name]; } for (const QString& patch_name : patches_to_be_installed_orig_versions) { installOriginalPatch(patch_name); } insertPatchesInfoInDatFile(_current_applied_patches_info); qInfo() << "PatchInstaller: Finished installation chain..."; if (force_reinstall_patches) { emit updatedByGameStatusChanged(false); } emit finished(); } // ############## PRIVATE SLOTS ############## // void PatchInstaller::onDownloaderProgressChanged(Downloader*, Downloader::Status progress) { emit videosDownloadProgressChanged(download_video_finished_videos, download_video_total_videos, progress); }