#include "MultiMC.h" #include "BuildConfig.h" #include "MainWindow.h" #include "InstanceWindow.h" #include "pages/BasePageProvider.h" #include "pages/global/MultiMCPage.h" #include "pages/global/MinecraftPage.h" #include "pages/global/JavaPage.h" #include "pages/global/ProxyPage.h" #include "pages/global/ExternalToolsPage.h" #include "pages/global/AccountListPage.h" #include "pages/global/PasteEEPage.h" #include "themes/ITheme.h" #include "themes/SystemTheme.h" #include "themes/DarkTheme.h" #include "themes/BrightTheme.h" #include "themes/CustomTheme.h" #include #include #include #include #include #include #include #include #include #include #include "InstanceList.h" #include "FolderInstanceProvider.h" #include "minecraft/ftb/FTBInstanceProvider.h" #include #include "icons/IconList.h" //FIXME: get rid of this #include "minecraft/legacy/LwjglVersionList.h" #include "minecraft/MinecraftVersionList.h" #include "minecraft/liteloader/LiteLoaderVersionList.h" #include "minecraft/forge/ForgeVersionList.h" #include "net/HttpMetaCache.h" #include "net/URLConstants.h" #include "Env.h" #include "java/JavaUtils.h" #include "updater/UpdateChecker.h" #include "tools/JProfiler.h" #include "tools/JVisualVM.h" #include "tools/MCEditTool.h" #include #include "settings/INISettingsObject.h" #include "settings/Setting.h" #include "trans/TranslationDownloader.h" #include "minecraft/ftb/FTBPlugin.h" #include #include #include #include #if defined Q_OS_WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #endif using namespace Commandline; MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) { #if defined Q_OS_WIN32 // attach the parent console if(AttachConsole(ATTACH_PARENT_PROCESS)) { // if attach succeeds, reopen and sync all the i/o if(freopen("CON", "w", stdout)) { std::cout.sync_with_stdio(); } if(freopen("CON", "w", stderr)) { std::cerr.sync_with_stdio(); } if(freopen("CON", "r", stdin)) { std::cin.sync_with_stdio(); } auto out = GetStdHandle (STD_OUTPUT_HANDLE); DWORD written; const char * endline = "\n"; WriteConsole(out, endline, strlen(endline), &written, NULL); consoleAttached = true; } #endif setOrganizationName("MultiMC"); setApplicationName("MultiMC5"); startTime = QDateTime::currentDateTime(); setAttribute(Qt::AA_UseHighDpiPixmaps); // Don't quit on hiding the last window this->setQuitOnLastWindowClosed(false); // Commandline parsing QHash args; { Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); // --help parser.addSwitch("help"); parser.addShortOpt("help", 'h'); parser.addDocumentation("help", "display this help and exit."); // --version parser.addSwitch("version"); parser.addShortOpt("version", 'V'); parser.addDocumentation("version", "display program version and exit."); // --dir parser.addOption("dir", applicationDirPath()); parser.addShortOpt("dir", 'd'); parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of " "the binary location (use '.' for current)"); // --launch parser.addOption("launch"); parser.addShortOpt("launch", 'l'); parser.addDocumentation("launch", "launch the specified instance (by instance ID)"); // parse the arguments try { args = parser.parse(arguments()); } catch (ParsingError e) { std::cerr << "CommandLineError: " << e.what() << std::endl; std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." << std::endl; m_status = MultiMC::Failed; return; } // display help and exit if (args["help"].toBool()) { std::cout << qPrintable(parser.compileHelp(arguments()[0])); m_status = MultiMC::Succeeded; return; } // display version and exit if (args["version"].toBool()) { std::cout << "Version " << BuildConfig.printableVersionString().toStdString() << std::endl; std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl; m_status = MultiMC::Succeeded; return; } } m_instanceIdToLaunch = args["launch"].toString(); QString origcwdPath = QDir::currentPath(); QString binPath = applicationDirPath(); QString adjustedBy; QString dataPath; // change directory QString dirParam = args["dir"].toString(); if (!dirParam.isEmpty()) { // the dir param. it makes multimc data path point to whatever the user specified // on command line adjustedBy += "Command line " + dirParam; dataPath = dirParam; } else { dataPath = applicationDirPath(); adjustedBy += "Fallback to binary path " + dataPath; } if (!FS::ensureFolderPathExists(dataPath) || !QDir::setCurrent(dataPath)) { // BAD STUFF. WHAT DO? m_status = MultiMC::Failed; return; } auto appID = ApplicationId::fromPathAndVersion(QDir::currentPath(), BuildConfig.printableVersionString()); m_peerInstance = new LocalPeer(this, appID); connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived); if(m_peerInstance->isClient()) { if(m_instanceIdToLaunch.isEmpty()) { m_peerInstance->sendMessage("activate", 2000); } else { m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000); } m_status = MultiMC::Succeeded; return; } #ifdef Q_OS_LINUX QDir foo(FS::PathCombine(binPath, "..")); m_rootPath = foo.absolutePath(); #elif defined(Q_OS_WIN32) m_rootPath = binPath; #elif defined(Q_OS_MAC) QDir foo(FS::PathCombine(binPath, "../..")); m_rootPath = foo.absolutePath(); #endif // init the logger initLogger(); qDebug() << "MultiMC 5, (c) 2013-2015 MultiMC Contributors"; qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; if (adjustedBy.size()) { qDebug() << "Work dir before adjustment : " << origcwdPath; qDebug() << "Work dir after adjustment : " << QDir::currentPath(); qDebug() << "Adjusted by : " << adjustedBy; } else { qDebug() << "Work dir : " << QDir::currentPath(); } qDebug() << "Binary path : " << binPath; qDebug() << "Application root path : " << m_rootPath; if(!m_instanceIdToLaunch.isEmpty()) { qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; } // load settings initGlobalSettings(); // load translations initTranslations(); // initialize the updater if(BuildConfig.UPDATER_ENABLED) { m_updateChecker.reset(new UpdateChecker(BuildConfig.CHANLIST_URL, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); } m_translationChecker.reset(new TranslationDownloader()); initIcons(); initThemes(); // make sure we have at least some minecraft versions before we init instances minecraftlist(); initInstances(); initAccounts(); initNetwork(); m_translationChecker->downloadTranslations(); //FIXME: what to do with these? m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); m_profilers.insert("jvisualvm", std::shared_ptr(new JVisualVMFactory())); for (auto profiler : m_profilers.values()) { profiler->registerSettings(m_settings); } initMCEdit(); connect(this, SIGNAL(aboutToQuit()), SLOT(onExit())); m_status = MultiMC::Initialized; setIconTheme(settings()->get("IconTheme").toString()); setApplicationTheme(settings()->get("ApplicationTheme").toString()); if(!m_instanceIdToLaunch.isEmpty()) { auto inst = instances()->getInstanceById(m_instanceIdToLaunch); if(inst) { // minimized main window showMainWindow(true); launch(inst, true, nullptr); return; } } if(!m_mainWindow) { // normal main window showMainWindow(false); } } MultiMC::~MultiMC() { if (m_mmc_translator) { removeTranslator(m_mmc_translator.get()); } if (m_qt_translator) { removeTranslator(m_qt_translator.get()); } #if defined Q_OS_WIN32 if(consoleAttached) { const char * endline = "\n"; auto out = GetStdHandle (STD_OUTPUT_HANDLE); DWORD written; WriteConsole(out, endline, strlen(endline), &written, NULL); } #endif shutdownLogger(); Env::dispose(); } void MultiMC::messageReceived(const QString& message) { if(message == "activate") { showMainWindow(); } else { auto inst = instances()->getInstanceById(message); if(inst) { launch(inst, true, nullptr); } } } #ifdef Q_OS_MAC #include "CertWorkaround.h" #endif void MultiMC::initNetwork() { // init the http meta cache ENV.initHttpMetaCache(); // init proxy settings { QString proxyTypeStr = settings()->get("ProxyType").toString(); QString addr = settings()->get("ProxyAddr").toString(); int port = settings()->get("ProxyPort").value(); QString user = settings()->get("ProxyUser").toString(); QString pass = settings()->get("ProxyPass").toString(); ENV.updateProxySettings(proxyTypeStr, addr, port, user, pass); } #ifdef Q_OS_MAC Q_INIT_RESOURCE(certs); RebuildQtCertificates(); QFile equifaxFile(":/certs/Equifax_Secure_Certificate_Authority.pem"); equifaxFile.open(QIODevice::ReadOnly); QSslCertificate equifaxCert(equifaxFile.readAll(), QSsl::Pem); QSslSocket::addDefaultCaCertificate(equifaxCert); #endif } void MultiMC::initTranslations() { QLocale locale(m_settings->get("Language").toString()); QLocale::setDefault(locale); qDebug() << "Your language is" << locale.bcp47Name(); m_qt_translator.reset(new QTranslator()); if (m_qt_translator->load("qt_" + locale.bcp47Name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { qDebug() << "Loading Qt Language File for" << locale.bcp47Name().toLocal8Bit().constData() << "..."; if (!installTranslator(m_qt_translator.get())) { qCritical() << "Loading Qt Language File failed."; m_qt_translator.reset(); } } else { m_qt_translator.reset(); } m_mmc_translator.reset(new QTranslator()); if (m_mmc_translator->load("mmc_" + locale.bcp47Name(), FS::PathCombine(QDir::currentPath(), "translations"))) { qDebug() << "Loading MMC Language File for" << locale.bcp47Name().toLocal8Bit().constData() << "..."; if (!installTranslator(m_mmc_translator.get())) { qCritical() << "Loading MMC Language File failed."; m_mmc_translator.reset(); } } else { m_mmc_translator.reset(); } } void MultiMC::initIcons() { auto setting = MMC->settings()->getSetting("IconsDir"); QStringList instFolders = { ":/icons/multimc/32x32/instances/", ":/icons/multimc/50x50/instances/", ":/icons/multimc/128x128/instances/" }; m_icons.reset(new IconList(instFolders, setting->get().toString())); connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value) { m_icons->directoryChanged(value.toString()); }); ENV.registerIconList(m_icons); } void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { const char *levels = "DWCF"; const QString format("%1 %2 %3\n"); qint64 msecstotal = MMC->timeSinceStart(); qint64 seconds = msecstotal / 1000; qint64 msecs = msecstotal % 1000; QString foo; char buf[1025] = {0}; ::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs); QString out = format.arg(buf).arg(levels[type]).arg(msg); MMC->logFile->write(out.toUtf8()); MMC->logFile->flush(); QTextStream(stderr) << out.toLocal8Bit(); fflush(stderr); } static void moveFile(const QString &oldName, const QString &newName) { QFile::remove(newName); QFile::copy(oldName, newName); QFile::remove(oldName); } void MultiMC::initLogger() { static const QString logBase = "MultiMC-%0.log"; moveFile(logBase.arg(3), logBase.arg(4)); moveFile(logBase.arg(2), logBase.arg(3)); moveFile(logBase.arg(1), logBase.arg(2)); moveFile(logBase.arg(0), logBase.arg(1)); qInstallMessageHandler(appDebugOutput); logFile = std::unique_ptr(new QFile(logBase.arg(0))); logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate); } void MultiMC::shutdownLogger() { qInstallMessageHandler(nullptr); } void MultiMC::initInstances() { auto InstDirSetting = m_settings->getSetting("InstanceDir"); // instance path: check for problems with '!' in instance path and warn the user in the log // and rememer that we have to show him a dialog when the gui starts (if it does so) QString instDir = m_settings->get("InstanceDir").toString(); qDebug() << "Instance path : " << instDir; if (FS::checkProblemticPathJava(QDir(instDir))) { qWarning() << "Your instance path contains \'!\' and this is known to cause java problems"; } m_instances.reset(new InstanceList(m_settings, InstDirSetting->get().toString(), this)); m_instanceFolder = new FolderInstanceProvider(m_settings, instDir); connect(InstDirSetting.get(), &Setting::SettingChanged, m_instanceFolder, &FolderInstanceProvider::on_InstFolderChanged); m_instances->addInstanceProvider(m_instanceFolder); m_instances->addInstanceProvider(new FTBInstanceProvider(m_settings)); qDebug() << "Loading Instances..."; m_instances->loadList(true); } void MultiMC::initAccounts() { // and accounts m_accounts.reset(new MojangAccountList(this)); qDebug() << "Loading accounts..."; m_accounts->setListFilePath("accounts.json", true); m_accounts->loadList(); } void MultiMC::initGlobalSettings() { m_settings.reset(new INISettingsObject("multimc.cfg", this)); // Updates m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL); m_settings->registerSetting("AutoUpdate", true); // Theming m_settings->registerSetting("IconTheme", QString("multimc")); m_settings->registerSetting("ApplicationTheme", QString("system")); // Notifications m_settings->registerSetting("ShownNotifications", QString()); // Remembered state m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); QString defaultMonospace; int defaultSize = 11; #ifdef Q_OS_WIN32 defaultMonospace = "Courier"; defaultSize = 10; #elif defined(Q_OS_MAC) defaultMonospace = "Menlo"; #else defaultMonospace = "Monospace"; #endif // resolve the font so the default actually matches QFont consoleFont; consoleFont.setFamily(defaultMonospace); consoleFont.setStyleHint(QFont::Monospace); consoleFont.setFixedPitch(true); QFontInfo consoleFontInfo(consoleFont); QString resolvedDefaultMonospace = consoleFontInfo.family(); QFont resolvedFont(resolvedDefaultMonospace); qDebug() << "Detected default console font:" << resolvedDefaultMonospace << ", substitutions:" << resolvedFont.substitutions().join(','); m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace); m_settings->registerSetting("ConsoleFontSize", defaultSize); m_settings->registerSetting("ConsoleMaxLines", 100000); m_settings->registerSetting("ConsoleOverflowStop", true); FTBPlugin::initialize(m_settings); // Folders m_settings->registerSetting("InstanceDir", "instances"); m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods"); m_settings->registerSetting({"LWJGLDir", "LwjglDir"}, "lwjgl"); m_settings->registerSetting("IconsDir", "icons"); // Editors m_settings->registerSetting("JsonEditor", QString()); // Language m_settings->registerSetting("Language", QLocale(QLocale::system().language()).bcp47Name()); // Console m_settings->registerSetting("ShowConsole", false); m_settings->registerSetting("AutoCloseConsole", false); m_settings->registerSetting("ShowConsoleOnError", true); m_settings->registerSetting("LogPrePostOutput", true); // Console Colors // m_settings->registerSetting("SysMessageColor", QColor(Qt::blue)); // m_settings->registerSetting("StdOutColor", QColor(Qt::black)); // m_settings->registerSetting("StdErrColor", QColor(Qt::red)); // Window Size m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false); m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854); m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480); // Proxy Settings m_settings->registerSetting("ProxyType", "None"); m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1"); m_settings->registerSetting("ProxyPort", 8080); m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, ""); m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, ""); // Memory m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024); m_settings->registerSetting("PermGen", 128); // Java Settings m_settings->registerSetting("JavaPath", ""); m_settings->registerSetting("JavaTimestamp", 0); m_settings->registerSetting("JavaArchitecture", ""); m_settings->registerSetting("JavaVersion", ""); m_settings->registerSetting("LastHostname", ""); m_settings->registerSetting("JavaDetectionHack", ""); m_settings->registerSetting("JvmArgs", ""); // Minecraft launch method m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); // Wrapper command for launch m_settings->registerSetting("WrapperCommand", ""); // Custom Commands m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, ""); m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, ""); // The cat m_settings->registerSetting("TheCat", false); m_settings->registerSetting("InstSortMode", "Name"); m_settings->registerSetting("SelectedInstance", QString()); // Window state and geometry m_settings->registerSetting("MainWindowState", ""); m_settings->registerSetting("MainWindowGeometry", ""); m_settings->registerSetting("ConsoleWindowState", ""); m_settings->registerSetting("ConsoleWindowGeometry", ""); m_settings->registerSetting("SettingsGeometry", ""); m_settings->registerSetting("PagedGeometry", ""); // Jar mod nag dialog in version page m_settings->registerSetting("JarModNagSeen", false); // paste.ee API key m_settings->registerSetting("PasteEEAPIKey", "multimc"); // Init page provider { m_globalSettingsProvider = std::make_shared(tr("Settings")); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); } } void MultiMC::initMCEdit() { m_mcedit.reset(new MCEditTool(m_settings)); } std::shared_ptr MultiMC::lwjgllist() { if (!m_lwjgllist) { m_lwjgllist.reset(new LWJGLVersionList()); ENV.registerVersionList("org.lwjgl.legacy", m_lwjgllist); } return m_lwjgllist; } std::shared_ptr MultiMC::forgelist() { if (!m_forgelist) { m_forgelist.reset(new ForgeVersionList()); ENV.registerVersionList("net.minecraftforge", m_forgelist); } return m_forgelist; } std::shared_ptr MultiMC::liteloaderlist() { if (!m_liteloaderlist) { m_liteloaderlist.reset(new LiteLoaderVersionList()); ENV.registerVersionList("com.mumfrey.liteloader", m_liteloaderlist); } return m_liteloaderlist; } std::shared_ptr MultiMC::minecraftlist() { if (!m_minecraftlist) { m_minecraftlist.reset(new MinecraftVersionList()); ENV.registerVersionList("net.minecraft", m_minecraftlist); } return m_minecraftlist; } std::shared_ptr MultiMC::javalist() { if (!m_javalist) { m_javalist.reset(new JavaInstallList()); ENV.registerVersionList("com.java", m_javalist); } return m_javalist; } // from #ifndef S_IRUSR #define __S_IREAD 0400 /* Read by owner. */ #define __S_IWRITE 0200 /* Write by owner. */ #define __S_IEXEC 0100 /* Execute by owner. */ #define S_IRUSR __S_IREAD /* Read by owner. */ #define S_IWUSR __S_IWRITE /* Write by owner. */ #define S_IXUSR __S_IEXEC /* Execute by owner. */ #define S_IRGRP (S_IRUSR >> 3) /* Read by group. */ #define S_IWGRP (S_IWUSR >> 3) /* Write by group. */ #define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */ #define S_IROTH (S_IRGRP >> 3) /* Read by others. */ #define S_IWOTH (S_IWGRP >> 3) /* Write by others. */ #define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */ #endif static QFile::Permissions unixModeToPermissions(const int mode) { QFile::Permissions perms; if (mode & S_IRUSR) { perms |= QFile::ReadUser; } if (mode & S_IWUSR) { perms |= QFile::WriteUser; } if (mode & S_IXUSR) { perms |= QFile::ExeUser; } if (mode & S_IRGRP) { perms |= QFile::ReadGroup; } if (mode & S_IWGRP) { perms |= QFile::WriteGroup; } if (mode & S_IXGRP) { perms |= QFile::ExeGroup; } if (mode & S_IROTH) { perms |= QFile::ReadOther; } if (mode & S_IWOTH) { perms |= QFile::WriteOther; } if (mode & S_IXOTH) { perms |= QFile::ExeOther; } return perms; } void MultiMC::installUpdates(const QString updateFilesDir, GoUpdate::OperationList operations) { qint64 pid = -1; QStringList args; bool started = false; qDebug() << "Installing updates."; #ifdef Q_OS_WIN QString finishCmd = applicationFilePath(); #elif defined Q_OS_LINUX QString finishCmd = FS::PathCombine(root(), "MultiMC"); #elif defined Q_OS_MAC QString finishCmd = applicationFilePath(); #else #error Unsupported operating system. #endif QString backupPath = FS::PathCombine(root(), "update", "backup"); QDir origin(root()); // clean up the backup folder. it should be empty before we start if(!FS::deletePath(backupPath)) { qWarning() << "couldn't remove previous backup folder" << backupPath; } // and it should exist. if(!FS::ensureFolderPathExists(backupPath)) { qWarning() << "couldn't create folder" << backupPath; return; } struct BackupEntry { QString orig; QString backup; }; enum Failure { Replace, Delete, Start, Nothing } failedOperationType = Nothing; QString failedFile; QList backups; QList trashcan; bool useXPHack = false; QString exePath; QString exeOrigin; QString exeBackup; // perform the update operations for(auto op: operations) { switch(op.type) { // replace = move original out to backup, if it exists, move the new file in its place case GoUpdate::Operation::OP_REPLACE: { #ifdef Q_OS_WIN32 // hack for people renaming the .exe because ... reasons :) if(op.dest == "MultiMC.exe") { op.dest = QFileInfo(applicationFilePath()).fileName(); } #endif QFileInfo replaced (FS::PathCombine(root(), op.dest)); #ifdef Q_OS_WIN32 if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA) { if(replaced.fileName() == "MultiMC.exe") { QDir rootDir(root()); exeOrigin = rootDir.relativeFilePath(op.file); exePath = rootDir.relativeFilePath(op.dest); exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, replaced.fileName())); useXPHack = true; continue; } } #endif if(replaced.exists()) { QString backupName = op.dest; backupName.replace('/', '_'); QString backupFilePath = FS::PathCombine(backupPath, backupName); if(!QFile::rename(replaced.absoluteFilePath(), backupFilePath)) { qWarning() << "Couldn't move:" << replaced.absoluteFilePath() << "to" << backupFilePath; failedOperationType = Replace; failedFile = op.dest; goto FAILED; } BackupEntry be; be.orig = replaced.absoluteFilePath(); be.backup = backupFilePath; backups.append(be); } // make sure the folder we are putting this into exists if(!FS::ensureFilePathExists(replaced.absoluteFilePath())) { qWarning() << "REPLACE: Couldn't create folder:" << replaced.absoluteFilePath(); failedOperationType = Replace; failedFile = op.dest; goto FAILED; } // now move the new file in if(!QFile::rename(op.file, replaced.absoluteFilePath())) { qWarning() << "REPLACE: Couldn't move:" << op.file << "to" << replaced.absoluteFilePath(); failedOperationType = Replace; failedFile = op.dest; goto FAILED; } QFile::setPermissions(replaced.absoluteFilePath(), unixModeToPermissions(op.mode)); } break; // delete = move original to backup case GoUpdate::Operation::OP_DELETE: { QString origFilePath = FS::PathCombine(root(), op.file); if(QFile::exists(origFilePath)) { QString backupName = op.file; backupName.replace('/', '_'); QString trashFilePath = FS::PathCombine(backupPath, backupName); if(!QFile::rename(origFilePath, trashFilePath)) { qWarning() << "DELETE: Couldn't move:" << op.file << "to" << trashFilePath; failedFile = op.file; failedOperationType = Delete; goto FAILED; } BackupEntry be; be.orig = origFilePath; be.backup = trashFilePath; trashcan.append(be); } } break; } } // try to start the new binary args = qApp->arguments(); args.removeFirst(); // on old Windows, do insane things... no error checking here, this is just to have something. if(useXPHack) { QString script; auto nativePath = QDir::toNativeSeparators(exePath); auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin); auto nativeBackupPath = QDir::toNativeSeparators(exeBackup); // so we write this vbscript thing... QTextStream out(&script); out << "WScript.Sleep 1000\n"; out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n"; out << "Set shell=CreateObject(\"WScript.Shell\")\n"; out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n"; out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n"; out << "shell.Run \"" << nativePath << "\"\n"; QString scriptPath = FS::PathCombine(root(), "update", "update.vbs"); // we save it QFile scriptFile(scriptPath); scriptFile.open(QIODevice::WriteOnly); scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n")); scriptFile.close(); // we run it started = QProcess::startDetached("wscript", {scriptPath}, root()); // and we quit. conscious thought. qApp->quit(); return; } started = QProcess::startDetached(finishCmd, args, QDir::currentPath(), &pid); // failed to start... ? if(!started || pid == -1) { qWarning() << "Couldn't start new process properly!"; failedOperationType = Start; goto FAILED; } origin.rmdir(updateFilesDir); qApp->quit(); return; FAILED: qWarning() << "Update failed!"; bool revertOK = true; // if the above failed, roll back changes for(auto backup:backups) { qWarning() << "restoring" << backup.orig << "from" << backup.backup; if(!QFile::remove(backup.orig)) { revertOK = false; qWarning() << "removing new" << backup.orig << "failed!"; continue; } if(!QFile::rename(backup.backup, backup.orig)) { revertOK = false; qWarning() << "restoring" << backup.orig << "failed!"; } } for(auto backup:trashcan) { qWarning() << "restoring" << backup.orig << "from" << backup.backup; if(!QFile::rename(backup.backup, backup.orig)) { revertOK = false; qWarning() << "restoring" << backup.orig << "failed!"; } } QString msg; if(!revertOK) { msg = tr("The update failed and then the update revert failed too.\n" "You will have to repair MultiMC manually.\n" "Please let us know why and how this happened.").arg(failedFile); } else switch (failedOperationType) { case Replace: msg = tr("Couldn't replace file %1. Changes were reverted.\n" "See the MultiMC log file for details.").arg(failedFile); break; case Delete: msg = tr("Couldn't remove file %1. Changes were reverted.\n" "See the MultiMC log file for details.").arg(failedFile); break; case Start: msg = tr("The new version didn't start and the update was rolled back."); break; case Nothing: default: return; } QMessageBox::critical(nullptr, tr("Update failed!"), msg); } std::vector MultiMC::getValidApplicationThemes() { std::vector ret; auto iter = m_themes.cbegin(); while (iter != m_themes.cend()) { ret.push_back((*iter).second.get()); iter++; } return ret; } void MultiMC::initThemes() { auto insertTheme = [this](ITheme * theme) { m_themes.insert(std::make_pair(theme->id(), std::unique_ptr(theme))); }; auto darkTheme = new DarkTheme(); insertTheme(new SystemTheme()); insertTheme(darkTheme); insertTheme(new BrightTheme()); insertTheme(new CustomTheme(darkTheme, "custom")); } void MultiMC::setApplicationTheme(const QString& name) { auto systemPalette = qApp->palette(); auto themeIter = m_themes.find(name); if(themeIter != m_themes.end()) { auto & theme = (*themeIter).second; setStyle(QStyleFactory::create(theme->qtTheme())); setPalette(theme->colorScheme()); QDir::setSearchPaths("theme", theme->searchPaths()); setStyleSheet(theme->appStyleSheet()); } else { qWarning() << "Tried to set invalid theme:" << name; } } void MultiMC::setIconTheme(const QString& name) { XdgIcon::setThemeName(name); } QIcon MultiMC::getThemedIcon(const QString& name) { return XdgIcon::fromTheme(name); } void MultiMC::onExit() { if(m_instances) { // m_instances->saveGroupList(); } if(logFile) { logFile->flush(); logFile->close(); } } bool MultiMC::openJsonEditor(const QString &filename) { const QString file = QDir::current().absoluteFilePath(filename); if (m_settings->get("JsonEditor").toString().isEmpty()) { return DesktopServices::openUrl(QUrl::fromLocalFile(file)); } else { //return DesktopServices::openFile(m_settings->get("JsonEditor").toString(), file); return DesktopServices::run(m_settings->get("JsonEditor").toString(), {file}); } } void MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler) { if(instance->canLaunch()) { auto & extras = m_instanceExtras[instance->id()]; auto & window = extras.window; if(window) { if(!window->saveAll()) { return; } } auto & controller = extras.controller; controller.reset(new LaunchController()); controller->setInstance(instance); controller->setOnline(online); controller->setProfiler(profiler); if(window) { controller->setParentWidget(window); } else if(m_mainWindow) { controller->setParentWidget(m_mainWindow); } connect(controller.get(), &LaunchController::succeeded, this, &MultiMC::controllerSucceeded); connect(controller.get(), &LaunchController::failed, this, &MultiMC::controllerFailed); controller->start(); m_runningInstances ++; } else if (instance->isRunning()) { showInstanceWindow(instance, "console"); } } void MultiMC::controllerSucceeded() { auto controller = qobject_cast(QObject::sender()); if(!controller) return; auto id = controller->id(); auto & extras = m_instanceExtras[id]; // on success, do... if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) { if(extras.window) { extras.window->close(); } } extras.controller.reset(); m_runningInstances --; // quit when there are no more windows. if(m_openWindows == 0 && m_runningInstances == 0) { m_status = Status::Succeeded; quit(); } } void MultiMC::controllerFailed(const QString& error) { Q_UNUSED(error); auto controller = qobject_cast(QObject::sender()); if(!controller) return; auto id = controller->id(); auto & extras = m_instanceExtras[id]; // on failure, do... nothing extras.controller.reset(); m_runningInstances --; // quit when there are no more windows. if(m_openWindows == 0 && m_runningInstances == 0) { m_status = Status::Failed; quit(); } } MainWindow* MultiMC::showMainWindow(bool minimized) { if(m_mainWindow) { m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized); m_mainWindow->raise(); m_mainWindow->activateWindow(); } else { m_mainWindow = new MainWindow(); m_mainWindow->restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray())); m_mainWindow->restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray())); if(minimized) { m_mainWindow->showMinimized(); } else { m_mainWindow->show(); } m_mainWindow->checkSetDefaultJava(); m_mainWindow->checkInstancePathForProblems(); m_openWindows++; } return m_mainWindow; } InstanceWindow *MultiMC::showInstanceWindow(InstancePtr instance, QString page) { if(!instance) return nullptr; auto id = instance->id(); auto & extras = m_instanceExtras[id]; auto & window = extras.window; if(window) { window->raise(); window->activateWindow(); } else { window = new InstanceWindow(instance); m_openWindows ++; connect(window, &InstanceWindow::isClosing, this, &MultiMC::on_windowClose); } if(!page.isEmpty()) { window->selectPage(page); } if(extras.controller) { extras.controller->setParentWidget(window); } return window; } void MultiMC::on_windowClose() { m_openWindows--; auto instWindow = qobject_cast(QObject::sender()); if(instWindow) { auto & extras = m_instanceExtras[instWindow->instanceId()]; extras.window = nullptr; if(extras.controller) { extras.controller->setParentWidget(m_mainWindow); } return; } auto mainWindow = qobject_cast(QObject::sender()); if(mainWindow) { m_mainWindow = nullptr; } // quit when there are no more windows. if(m_openWindows == 0) { quit(); } } #include "MultiMC.moc"