#include "patchinstaller.h" #include "models/filesystem.h" #include "models/settings.h" #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"; } return "none"; } PatchInstaller::PatchInstaller(QObject *parent) : QObject(parent) { } bool PatchInstaller::initialised() { return client_general_file_->Initialized() && client_local_file_->Initialized(); } PatchInstaller::~PatchInstaller() { deinit(); } // ############## 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_local_current_path = QString::fromStdString(client_local_file_->GetFilename()); QString client_general_current_path = QString::fromStdString(client_general_file_->GetFilename()); return QFileInfo(client_local_filepath) != QFileInfo(client_local_current_path) || QFileInfo(client_general_filepath) != QFileInfo(client_general_current_path); } void PatchInstaller::deinit() { emit started(); client_local_file_->Deinit(); client_general_file_->Deinit(); emit finished(); } void PatchInstaller::installPatch(QString patch_name, LOTRO_DAT::Database* database) { if (!Settings::getValue("DatabaseNeedInstall/" + patch_name).toBool()) { return; } if (patch_name == "loadscreen") { installLoadscreens(database); return; } if (patch_name == "video") { installVideos(database); return; } if (patch_name == "micro" && !Settings::getValue("Components/micropatch").toBool()) { Settings::setValue("DatabaseNeedInstall/micropatch", false); return; } LOTRO_DAT::SubfileData file; qDebug() << "Total files in database " << database->CountRows(); qDebug() << "Patching all files from database..." << database; while (!(file = database->GetNextFile()).Empty()) { 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); } if (!file.options["fid"]) { continue; } const int category = file.options["cat"] ? file.options["cat"].as() : -1; QString component_name = getComponentNameFromId(category); if (category != -1 && !Settings::getValue("Components/" + component_name).toBool()) { continue; } const int dat_id = file.options["did"] ? file.options["did"].as() : 0; if (dat_id == E_CLIENT_LOCAL) { client_local_file_->PatchFile(file); } else if (dat_id == E_CLIENT_GENERAL) { client_general_file_->PatchFile(file); } else { qWarning() << "Unknown dat id parameter for file " << file.options["fid"].as() << " (dat id value = " << dat_id << "), SKIPPING!"; } } Settings::setValue("DatabaseNeedInstall/" + patch_name, false); return; } void PatchInstaller::installLoadscreens(LOTRO_DAT::Database* database) { if (!Settings::getValue("Components/loadscreens").toBool()) { current_status.finished_parts += database->CountRows(); emit progressChanged(current_status); Settings::setValue("DatabaseNeedInstall/loadscreen", false); return; } 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(); 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); } } Settings::setValue("DatabaseNeedInstall/loadscreen", false); } void PatchInstaller::installVideos(LOTRO_DAT::Database* database) { current_status.finished_parts += database->CountRows(); emit progressChanged(current_status); download_video_total_videos = database->CountRows(); download_video_finished_videos = 0; if (!Settings::getValue("Components/videos").toBool()) { Settings::setValue("DatabaseNeedInstall/video", false); return; } LOTRO_DAT::SubfileData file; while (!(file = database->GetNextFile()).Empty()) { if (!file.options["name"] || !file.options["url"] || !file.options["hash"] || !file.options["folder"]) { download_video_finished_videos++; 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; FileSystem::createFilePath(full_filename); if (FileSystem::fileExists(full_filename) && FileSystem::fileHash(full_filename) == hash) { download_video_finished_videos++; 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; video_downloader->start(); video_downloader->waitForDownloaded(); video_downloader->targetFile->close(); video_downloader->targetFile->deleteLater(); video_downloader->targetFile = nullptr; video_downloader->deleteLater(); download_video_finished_videos++; } Settings::setValue("DatabaseNeedInstall/video", false); } // ############## PUBLIC SLOTS ############## // void PatchInstaller::init() { emit started(); qDebug() << __FUNCTION__ << "Starting initialisation of LotroDatManager"; qRegisterMetaType(); 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"; if (!FileSystem::fileExists(client_local_filepath) || !FileSystem::fileExists(client_general_filepath)) { qCritical() << __FUNCTION__ << "DatFiles do not exist!" << client_local_filepath << " " << client_general_filepath; emit finished(); return; } // Updating file permissions to be sure, that they're not in read-only mode if (!QFile::setPermissions(client_local_filepath, QFileDevice::Permission(0x6666))) { qDebug() << __FUNCTION__ << "Unable to update permissions on client_local_* file!"; } if (!QFile::setPermissions(client_general_filepath, QFileDevice::Permission(0x6666))) { qDebug() << __FUNCTION__ << "Unable to update permissions on client_general* file!"; } // Initialising client_local_*.dat file and client_general.dat client_local_file_ = new LOTRO_DAT::DatFile(1); client_general_file_ = new LOTRO_DAT::DatFile(2); auto client_local_init_res = client_local_file_->Init(client_local_filepath.toStdString()); auto client_general_init_res = client_general_file_->Init(client_general_filepath.toStdString()); if (!client_local_init_res || !client_general_init_res) { client_local_file_->Deinit(); client_general_file_->Deinit(); qCritical() << __FUNCTION__ << "Finished LotroDatManager initialisation - error: DatFile initialisation error!"; emit finished(); return; } qDebug() << "LotroDatManager initialisation successfull! Dat files: " << QString::fromStdString(client_general_file_->GetFilename()) << QString::fromStdString(client_local_file_->GetFilename()); emit finished(); } void PatchInstaller::startGame(bool freeze_updates) { // if freeze_updates is set to True, original game // launcher will be replaced with special program, // which controls lotro startup and prevents from updates emit started(); QString game_folder = Settings::getValue("Lotro/game_path").toString(); if (game_folder == "none") { qCritical() << __FUNCTION__ << "Starting game FAILED - game folder wasnt set!"; emit finished(); return; } if (!FileSystem::fileExists(QApplication::applicationDirPath() + "/Launcher.exe")) { qCritical() << __FUNCTION__ << "Starting game FAILED - no game launcher in legacy directory found!"; emit finished(); return; } if (freeze_updates) { QFile::remove(game_folder + "/lotro_ru.exe"); if (!QFile::copy(QApplication::applicationDirPath() + "/LotroLauncher.exe", game_folder + "/lotro_ru.exe")) { qCritical() << __FUNCTION__ << "Starting game FAILED - cannot copy LotroLauncher to lotro_ru.exe!!"; emit finished(); return; } QFile::remove(game_folder + "/LotroLauncher.exe"); if (!QFile::copy(QApplication::applicationDirPath() + "/Launcher.exe", game_folder + "/LotroLauncher.exe")) { qCritical() << __FUNCTION__ << "Starting game FAILED - cannot copy GameLauncher to LotroLauncher!!"; emit finished(); return; } QFile file(game_folder + "/legacy_path.txt"); file.open(QIODevice::WriteOnly); QTextStream out(&file); out << QApplication::applicationDirPath() + "/LegacyLauncher.exe"; file.close(); } else { QFile::remove(game_folder + "/LotroLauncher.exe"); if (!QFile::copy(QApplication::applicationDirPath() + "/LotroLauncher.exe", game_folder + "/LotroLauncher.exe")) { qCritical() << __FUNCTION__ << "Starting game FAILED - cannot copy LotroLauncher from working dir to LotroLauncher in lotro dir!!"; emit finished(); return; } } QStringList args; if (freeze_updates) { args << "gamelaunch" << "-disablePatch"; } if (Settings::getValue("Lotro/skip_raw_download").toBool()) { args << "-skiprawdownload"; } if (Settings::getValue("Lotro/no_splash_screen").toBool()) { args << "-nosplashscreen"; } client_general_file_->Deinit(); client_local_file_->Deinit(); QString username = Settings::getValue("Account/username").toString(); QString password = Settings::getValue("Account/password").toString(); if (!username.isEmpty() && !password.isEmpty()) { args << "-username" << username << "-password" << password; } qDebug() << __FUNCTION__ << "Starting game with arguments: " << args; QFile f(Settings::getValue("Lotro/game_path").toString() + "/LotroLauncher.exe"); QProcess process; if (FileSystem::fileExists(f.fileName())) { if (f.fileName().contains(" ")) { f.setFileName("\"" + f.fileName() + "\""); } process.startDetached(f.fileName(), args); process.waitForFinished(-1); QApplication::quit(); } emit finished(); } void PatchInstaller::startPatchInstallationChain() { 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("DatabaseNeedInstall/" + patch).toBool()) { continue; } const QString patch_hashsum = Settings::getValue("PatchDatabases/" + patch + "/hashsum").toString(); const QString patch_filename = Settings::getValue("PatchDatabases/" + patch + "/path").toString(); const QString real_file_hashsum = FileSystem::fileHash(patch_filename); if (!FileSystem::fileExists(patch_filename) || real_file_hashsum != patch_hashsum) { qCritical() << "PatchInstallation: 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() << "PatchInstallation: failed to initialize db " << patch_filename; continue; } patch_databases[patch] = db; current_status.total_parts += db->CountRows(); } emit progressChanged(current_status); for (const QString patch_name: patch_databases.keys()) { qInfo() << "PatchInstaller: Installing patch " << patch_name; installPatch(patch_name, patch_databases[patch_name]); patch_databases[patch_name]->CloseDatabase(); delete patch_databases[patch_name]; } qInfo() << "PatchInstaller: Finished installation chain..."; emit finished(); } // ############## PRIVATE SLOTS ############## // void PatchInstaller::onDownloaderProgressChanged(Downloader*, Downloader::Status progress) { emit videosDownloadProgressChanged(download_video_finished_videos, download_video_total_videos, progress); }