From 791221e923586bb717396ecf18bd13e57034df99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 27 Jan 2015 22:31:07 +0100 Subject: NOISSUE Refactors and moving of things --- CMakeLists.txt | 38 +- MultiMC.cpp | 21 +- MultiMC.h | 7 - depends/util/include/modutils.h | 68 +++- depends/util/src/modutils.cpp | 23 +- gui/ConsoleWindow.cpp | 18 +- gui/ConsoleWindow.h | 6 +- gui/MainWindow.cpp | 273 +++++++------- gui/MainWindow.h | 8 +- gui/dialogs/AboutDialog.cpp | 2 +- gui/pages/LogPage.cpp | 4 +- gui/pages/LogPage.h | 6 +- gui/pages/ModFolderPage.cpp | 4 +- gui/pages/OtherLogsPage.cpp | 14 +- gui/pages/OtherLogsPage.h | 6 +- gui/pages/ResourcePackPage.h | 2 +- gui/pages/ScreenshotsPage.cpp | 4 +- gui/pages/ScreenshotsPage.h | 2 +- gui/pages/TexturePackPage.h | 2 +- gui/pages/VersionPage.cpp | 70 +--- gui/pages/VersionPage.h | 4 +- logic/BaseInstance.cpp | 24 +- logic/BaseInstance.h | 40 +-- logic/BaseProcess.cpp | 400 +++++++++++++++++++++ logic/BaseProcess.h | 133 +++++++ logic/InstanceFactory.cpp | 43 +-- logic/InstanceFactory.h | 8 +- logic/InstanceList.cpp | 9 +- logic/InstanceList.h | 3 +- logic/JarUtils.cpp | 3 - logic/LegacyFTBInstance.cpp | 20 -- logic/LegacyFTBInstance.h | 13 - logic/LegacyInstance.cpp | 28 +- logic/LegacyInstance.h | 31 +- logic/LegacyUpdate.cpp | 8 +- logic/LegacyUpdate.h | 2 +- logic/MinecraftProcess.cpp | 578 ------------------------------ logic/MinecraftProcess.h | 165 --------- logic/OneSixFTBInstance.cpp | 137 ------- logic/OneSixFTBInstance.h | 30 -- logic/OneSixInstance.cpp | 240 +++++-------- logic/OneSixInstance.h | 47 ++- logic/OneSixUpdate.cpp | 28 +- logic/OneSixUpdate.h | 2 +- logic/VersionFilterData.cpp | 72 ---- logic/VersionFilterData.h | 28 -- logic/assets/AssetsMigrateTask.cpp | 2 - logic/assets/AssetsUtils.cpp | 62 ++++ logic/assets/AssetsUtils.h | 2 + logic/forge/ForgeInstaller.cpp | 21 +- logic/forge/ForgeInstaller.h | 5 +- logic/forge/ForgeVersion.cpp | 2 +- logic/ftb/FTBVersion.h | 32 ++ logic/ftb/LegacyFTBInstance.cpp | 20 ++ logic/ftb/LegacyFTBInstance.h | 13 + logic/ftb/OneSixFTBInstance.cpp | 139 +++++++ logic/ftb/OneSixFTBInstance.h | 29 ++ logic/liteloader/LiteLoaderInstaller.cpp | 4 +- logic/minecraft/InstanceVersion.cpp | 550 ---------------------------- logic/minecraft/InstanceVersion.h | 184 ---------- logic/minecraft/JarMod.cpp | 34 -- logic/minecraft/JarMod.h | 4 - logic/minecraft/MinecraftInstance.cpp | 58 +++ logic/minecraft/MinecraftInstance.h | 30 ++ logic/minecraft/MinecraftProcess.cpp | 216 +++++++++++ logic/minecraft/MinecraftProcess.h | 77 ++++ logic/minecraft/MinecraftProfile.cpp | 434 ++++++++++++++++++++++ logic/minecraft/MinecraftProfile.h | 201 +++++++++++ logic/minecraft/MinecraftVersion.cpp | 18 +- logic/minecraft/MinecraftVersion.h | 20 +- logic/minecraft/MinecraftVersionList.cpp | 65 +++- logic/minecraft/MinecraftVersionList.h | 40 --- logic/minecraft/NullProfileStrategy.h | 24 ++ logic/minecraft/OneSixLibrary.cpp | 4 - logic/minecraft/OneSixProfileStrategy.cpp | 270 ++++++++++++++ logic/minecraft/OneSixProfileStrategy.h | 24 ++ logic/minecraft/ProfilePatch.h | 32 ++ logic/minecraft/ProfileStrategy.h | 30 ++ logic/minecraft/ProfileUtils.cpp | 145 ++++++++ logic/minecraft/ProfileUtils.h | 25 ++ logic/minecraft/VersionBuilder.cpp | 296 +-------------- logic/minecraft/VersionBuilder.h | 27 +- logic/minecraft/VersionFile.cpp | 7 +- logic/minecraft/VersionFile.h | 8 +- logic/minecraft/VersionFilterData.cpp | 72 ++++ logic/minecraft/VersionFilterData.h | 28 ++ logic/minecraft/VersionPatch.h | 32 -- logic/net/PasteUpload.cpp | 2 - logic/tools/BaseExternalTool.cpp | 25 -- logic/tools/BaseExternalTool.h | 2 - logic/tools/BaseProfiler.cpp | 2 +- logic/tools/BaseProfiler.h | 6 +- logic/tools/JProfiler.cpp | 4 +- logic/tools/JProfiler.h | 2 +- logic/tools/JVisualVM.cpp | 4 +- logic/tools/JVisualVM.h | 2 +- logic/tools/MCEditTool.cpp | 33 ++ logic/tools/MCEditTool.h | 1 + main.cpp | 1 - 99 files changed, 3135 insertions(+), 2909 deletions(-) create mode 100644 logic/BaseProcess.cpp create mode 100644 logic/BaseProcess.h delete mode 100644 logic/LegacyFTBInstance.cpp delete mode 100644 logic/LegacyFTBInstance.h delete mode 100644 logic/MinecraftProcess.cpp delete mode 100644 logic/MinecraftProcess.h delete mode 100644 logic/OneSixFTBInstance.cpp delete mode 100644 logic/OneSixFTBInstance.h delete mode 100644 logic/VersionFilterData.cpp delete mode 100644 logic/VersionFilterData.h create mode 100644 logic/ftb/FTBVersion.h create mode 100644 logic/ftb/LegacyFTBInstance.cpp create mode 100644 logic/ftb/LegacyFTBInstance.h create mode 100644 logic/ftb/OneSixFTBInstance.cpp create mode 100644 logic/ftb/OneSixFTBInstance.h delete mode 100644 logic/minecraft/InstanceVersion.cpp delete mode 100644 logic/minecraft/InstanceVersion.h create mode 100644 logic/minecraft/MinecraftInstance.cpp create mode 100644 logic/minecraft/MinecraftInstance.h create mode 100644 logic/minecraft/MinecraftProcess.cpp create mode 100644 logic/minecraft/MinecraftProcess.h create mode 100644 logic/minecraft/MinecraftProfile.cpp create mode 100644 logic/minecraft/MinecraftProfile.h create mode 100644 logic/minecraft/NullProfileStrategy.h create mode 100644 logic/minecraft/OneSixProfileStrategy.cpp create mode 100644 logic/minecraft/OneSixProfileStrategy.h create mode 100644 logic/minecraft/ProfilePatch.h create mode 100644 logic/minecraft/ProfileStrategy.h create mode 100644 logic/minecraft/ProfileUtils.cpp create mode 100644 logic/minecraft/ProfileUtils.h create mode 100644 logic/minecraft/VersionFilterData.cpp create mode 100644 logic/minecraft/VersionFilterData.h delete mode 100644 logic/minecraft/VersionPatch.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 32bf31b5..e4cd6387 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -394,6 +394,8 @@ SET(MULTIMC_SOURCES # LOGIC - Base classes and infrastructure logic/BaseVersion.h + logic/BaseProcess.h + logic/BaseProcess.cpp logic/InstanceFactory.h logic/InstanceFactory.cpp logic/BaseInstance.h @@ -404,12 +406,8 @@ SET(MULTIMC_SOURCES logic/ModList.cpp # sets and maps for deciding based on versions - logic/VersionFilterData.h - logic/VersionFilterData.cpp - - # Instance launch - logic/MinecraftProcess.h - logic/MinecraftProcess.cpp + logic/minecraft/VersionFilterData.h + logic/minecraft/VersionFilterData.cpp # Annoying nag screen logic logic/NagUtils.h @@ -503,24 +501,34 @@ SET(MULTIMC_SOURCES logic/JarUtils.h logic/JarUtils.cpp - # OneSix version json infrastructure + # Minecraft support logic/minecraft/GradleSpecifier.h - logic/minecraft/InstanceVersion.cpp - logic/minecraft/InstanceVersion.h + logic/minecraft/MinecraftProfile.cpp + logic/minecraft/MinecraftProfile.h logic/minecraft/JarMod.cpp logic/minecraft/JarMod.h + logic/minecraft/MinecraftInstance.cpp + logic/minecraft/MinecraftInstance.h + logic/minecraft/MinecraftProcess.cpp + logic/minecraft/MinecraftProcess.h logic/minecraft/MinecraftVersion.cpp logic/minecraft/MinecraftVersion.h logic/minecraft/MinecraftVersionList.cpp logic/minecraft/MinecraftVersionList.h + logic/minecraft/NullProfileStrategy.h logic/minecraft/OneSixLibrary.cpp logic/minecraft/OneSixLibrary.h + logic/minecraft/OneSixProfileStrategy.cpp + logic/minecraft/OneSixProfileStrategy.h logic/minecraft/OneSixRule.cpp logic/minecraft/OneSixRule.h logic/minecraft/OpSys.cpp logic/minecraft/OpSys.h logic/minecraft/ParseUtils.cpp logic/minecraft/ParseUtils.h + logic/minecraft/ProfileUtils.cpp + logic/minecraft/ProfileUtils.h + logic/minecraft/ProfileStrategy.h logic/minecraft/RawLibrary.cpp logic/minecraft/RawLibrary.h logic/minecraft/VersionBuilder.cpp @@ -528,7 +536,7 @@ SET(MULTIMC_SOURCES logic/minecraft/VersionBuildError.h logic/minecraft/VersionFile.cpp logic/minecraft/VersionFile.h - logic/minecraft/VersionPatch.h + logic/minecraft/ProfilePatch.h logic/minecraft/VersionSource.h # A Recursive file system watcher @@ -547,10 +555,10 @@ SET(MULTIMC_SOURCES logic/LwjglVersionList.cpp # FTB - logic/OneSixFTBInstance.h - logic/OneSixFTBInstance.cpp - logic/LegacyFTBInstance.h - logic/LegacyFTBInstance.cpp + logic/ftb/OneSixFTBInstance.h + logic/ftb/OneSixFTBInstance.cpp + logic/ftb/LegacyFTBInstance.h + logic/ftb/LegacyFTBInstance.cpp # the screenshots feature logic/screenshots/Screenshot.h @@ -597,8 +605,6 @@ SET(MULTIMC_SOURCES logic/java/JavaCheckerJob.cpp # Assets - logic/assets/AssetsMigrateTask.h - logic/assets/AssetsMigrateTask.cpp logic/assets/AssetsUtils.h logic/assets/AssetsUtils.cpp diff --git a/MultiMC.cpp b/MultiMC.cpp index abb30225..ae6070a3 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -21,8 +21,6 @@ #include "logic/forge/ForgeVersionList.h" -#include "logic/news/NewsChecker.h" - #include "logic/status/StatusChecker.h" #include "logic/net/HttpMetaCache.h" @@ -201,9 +199,6 @@ MultiMC::MultiMC(int &argc, char **argv, bool test_mode) : QApplication(argc, ar // initialize the notification checker m_notificationChecker.reset(new NotificationChecker()); - // initialize the news checker - m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL)); - // initialize the status checker m_statusChecker.reset(new StatusChecker()); @@ -213,7 +208,7 @@ MultiMC::MultiMC(int &argc, char **argv, bool test_mode) : QApplication(argc, ar 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 = MMC->settings()->get("InstanceDir").toString(); + QString instDir = m_settings->get("InstanceDir").toString(); QLOG_INFO() << "Instance path : " << instDir; if (checkProblemticPathJava(QDir(instDir))) { @@ -243,6 +238,7 @@ MultiMC::MultiMC(int &argc, char **argv, bool test_mode) : QApplication(argc, ar // init proxy settings updateProxySettings(); + //FIXME: what to do with these? m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); m_profilers.insert("jvisualvm", @@ -251,6 +247,8 @@ MultiMC::MultiMC(int &argc, char **argv, bool test_mode) : QApplication(argc, ar { profiler->registerSettings(m_settings); } + + //FIXME: what to do with these? m_tools.insert("mcedit", std::shared_ptr(new MCEditFactory())); for (auto tool : m_tools.values()) { @@ -296,8 +294,7 @@ void MultiMC::initTranslations() } m_mmc_translator.reset(new QTranslator()); - if (m_mmc_translator->load("mmc_" + locale.bcp47Name(), - MMC->staticData() + "/translations")) + if (m_mmc_translator->load("mmc_" + locale.bcp47Name(), staticData() + "/translations")) { QLOG_DEBUG() << "Loading MMC Language File for" << locale.bcp47Name().toLocal8Bit().constData() << "..."; @@ -701,13 +698,13 @@ void MultiMC::installUpdates(const QString updateFilesDir, UpdateFlags flags) } QLOG_INFO() << "Installing updates."; #ifdef WINDOWS - QString finishCmd = MMC->applicationFilePath(); + QString finishCmd = applicationFilePath(); QString updaterBinary = PathCombine(bin(), "updater.exe"); #elif LINUX QString finishCmd = PathCombine(root(), "MultiMC"); QString updaterBinary = PathCombine(bin(), "updater"); #elif OSX - QString finishCmd = MMC->applicationFilePath(); + QString finishCmd = applicationFilePath(); QString updaterBinary = PathCombine(bin(), "updater"); #else #error Unsupported operating system. @@ -719,7 +716,7 @@ void MultiMC::installUpdates(const QString updateFilesDir, UpdateFlags flags) args << "--install-dir" << root(); args << "--package-dir" << updateFilesDir; args << "--script" << PathCombine(updateFilesDir, "file_list.xml"); - args << "--wait" << QString::number(MMC->applicationPid()); + args << "--wait" << QString::number(applicationPid()); if (flags & DryRun) args << "--dry-run"; if (flags & RestartOnFinish) @@ -737,7 +734,7 @@ void MultiMC::installUpdates(const QString updateFilesDir, UpdateFlags flags) } // Now that we've started the updater, quit MultiMC. - MMC->quit(); + quit(); } void MultiMC::setIconTheme(const QString& name) diff --git a/MultiMC.h b/MultiMC.h index bce8a53b..02daed17 100644 --- a/MultiMC.h +++ b/MultiMC.h @@ -20,7 +20,6 @@ class LiteLoaderVersionList; class JavaVersionList; class UpdateChecker; class NotificationChecker; -class NewsChecker; class StatusChecker; class BaseProfilerFactory; class BaseDetachedToolFactory; @@ -102,11 +101,6 @@ public: return m_notificationChecker; } - std::shared_ptr newsChecker() - { - return m_newsChecker; - } - std::shared_ptr statusChecker() { return m_statusChecker; @@ -197,7 +191,6 @@ private: std::shared_ptr m_instances; std::shared_ptr m_updateChecker; std::shared_ptr m_notificationChecker; - std::shared_ptr m_newsChecker; std::shared_ptr m_statusChecker; std::shared_ptr m_accounts; std::shared_ptr m_icons; diff --git a/depends/util/include/modutils.h b/depends/util/include/modutils.h index 1fecd4d5..bc608f96 100644 --- a/depends/util/include/modutils.h +++ b/depends/util/include/modutils.h @@ -30,24 +30,76 @@ private: QString m_string; struct Section { - explicit Section(const QString &str, const int num) : numValid(true), number(num), string(str) {} - explicit Section(const QString &str) : numValid(false), string(str) {} + explicit Section(const QString &fullString) + { + m_fullString = fullString; + int cutoff = m_fullString.size(); + for(int i = 0; i < m_fullString.size(); i++) + { + if(!m_fullString[i].isDigit()) + { + cutoff = i; + break; + } + } + auto numPart = m_fullString.leftRef(cutoff); + if(numPart.size()) + { + numValid = true; + m_numPart = numPart.toInt(); + } + auto stringPart = m_fullString.midRef(cutoff); + if(stringPart.size()) + { + m_stringPart = stringPart.toString(); + } + } explicit Section() {} - bool numValid; - int number; - QString string; + bool numValid = false; + int m_numPart = 0; + QString m_stringPart; + QString m_fullString; inline bool operator!=(const Section &other) const { - return (numValid && other.numValid) ? (number != other.number) : (string != other.string); + if(numValid && other.numValid) + { + return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart; + } + else + { + return m_fullString != other.m_fullString; + } } inline bool operator<(const Section &other) const { - return (numValid && other.numValid) ? (number < other.number) : (string < other.string); + if(numValid && other.numValid) + { + if(m_numPart < other.m_numPart) + return true; + if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) + return true; + return false; + } + else + { + return m_fullString < other.m_fullString; + } } inline bool operator>(const Section &other) const { - return (numValid && other.numValid) ? (number > other.number) : (string > other.string); + if(numValid && other.numValid) + { + if(m_numPart > other.m_numPart) + return true; + if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart) + return true; + return false; + } + else + { + return m_fullString > other.m_fullString; + } } }; QList
m_sections; diff --git a/depends/util/src/modutils.cpp b/depends/util/src/modutils.cpp index 67c09dec..5f89eacd 100644 --- a/depends/util/src/modutils.cpp +++ b/depends/util/src/modutils.cpp @@ -15,9 +15,9 @@ bool Util::Version::operator<(const Version &other) const const int size = qMax(m_sections.size(), other.m_sections.size()); for (int i = 0; i < size; ++i) { - const Section sec1 = (i >= m_sections.size()) ? Section("0", 0) : m_sections.at(i); + const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); const Section sec2 = - (i >= other.m_sections.size()) ? Section("0", 0) : other.m_sections.at(i); + (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); if (sec1 != sec2) { return sec1 < sec2; @@ -35,9 +35,9 @@ bool Util::Version::operator>(const Version &other) const const int size = qMax(m_sections.size(), other.m_sections.size()); for (int i = 0; i < size; ++i) { - const Section sec1 = (i >= m_sections.size()) ? Section("0", 0) : m_sections.at(i); + const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); const Section sec2 = - (i >= other.m_sections.size()) ? Section("0", 0) : other.m_sections.at(i); + (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); if (sec1 != sec2) { return sec1 > sec2; @@ -55,9 +55,9 @@ bool Util::Version::operator==(const Version &other) const const int size = qMax(m_sections.size(), other.m_sections.size()); for (int i = 0; i < size; ++i) { - const Section sec1 = (i >= m_sections.size()) ? Section("0", 0) : m_sections.at(i); + const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); const Section sec2 = - (i >= other.m_sections.size()) ? Section("0", 0) : other.m_sections.at(i); + (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); if (sec1 != sec2) { return false; @@ -79,16 +79,7 @@ void Util::Version::parse() for (const auto part : parts) { - bool ok = false; - int num = part.toInt(&ok); - if (ok) - { - m_sections.append(Section(part, num)); - } - else - { - m_sections.append(Section(part)); - } + m_sections.append(Section(part)); } } diff --git a/gui/ConsoleWindow.cpp b/gui/ConsoleWindow.cpp index 4ee0d487..ad3c2636 100644 --- a/gui/ConsoleWindow.cpp +++ b/gui/ConsoleWindow.cpp @@ -52,8 +52,8 @@ private: BasePage * m_log_page; }; -ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent) - : QMainWindow(parent), m_proc(mcproc) +ConsoleWindow::ConsoleWindow(BaseProcess *process, QWidget *parent) + : QMainWindow(parent), m_proc(process) { MultiMCPlatform::fixWM_CLASS(this); setAttribute(Qt::WA_DeleteOnClose); @@ -120,23 +120,23 @@ ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent) { m_trayIcon = new QSystemTrayIcon(icon, this); m_trayIcon->setToolTip(windowTitle); - + connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); m_trayIcon->show(); } // Set up signal connections - connect(mcproc, SIGNAL(ended(InstancePtr, int, QProcess::ExitStatus)), this, + connect(m_proc, SIGNAL(ended(InstancePtr, int, QProcess::ExitStatus)), this, SLOT(onEnded(InstancePtr, int, QProcess::ExitStatus))); - connect(mcproc, SIGNAL(prelaunch_failed(InstancePtr, int, QProcess::ExitStatus)), this, + connect(m_proc, SIGNAL(prelaunch_failed(InstancePtr, int, QProcess::ExitStatus)), this, SLOT(onEnded(InstancePtr, int, QProcess::ExitStatus))); - connect(mcproc, SIGNAL(launch_failed(InstancePtr)), this, + connect(m_proc, SIGNAL(launch_failed(InstancePtr)), this, SLOT(onLaunchFailed(InstancePtr))); setMayClose(false); - if (mcproc->instance()->settings().get("ShowConsole").toBool()) + if (m_proc->instance()->settings().get("ShowConsole").toBool()) { show(); } @@ -213,7 +213,7 @@ void ConsoleWindow::on_btnKillMinecraft_clicked() "is frozen for some reason"), QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec(); if (response == QMessageBox::Yes) - m_proc->killMinecraft(); + m_proc->killProcess(); else m_killButton->setEnabled(true); } @@ -254,5 +254,5 @@ void ConsoleWindow::onLaunchFailed(InstancePtr instance) } ConsoleWindow::~ConsoleWindow() { - + } diff --git a/gui/ConsoleWindow.h b/gui/ConsoleWindow.h index 1ad0f760..a14cd2fc 100644 --- a/gui/ConsoleWindow.h +++ b/gui/ConsoleWindow.h @@ -17,7 +17,7 @@ #include #include -#include "logic/MinecraftProcess.h" +#include "logic/BaseProcess.h" class QPushButton; class PageContainer; @@ -26,7 +26,7 @@ class ConsoleWindow : public QMainWindow Q_OBJECT public: - explicit ConsoleWindow(MinecraftProcess *proc, QWidget *parent = 0); + explicit ConsoleWindow(BaseProcess *proc, QWidget *parent = 0); virtual ~ConsoleWindow(); /** @@ -56,7 +56,7 @@ protected: void closeEvent(QCloseEvent *); private: - MinecraftProcess *m_proc = nullptr; + BaseProcess *m_proc = nullptr; bool m_mayclose = true; QSystemTrayIcon *m_trayIcon = nullptr; PageContainer *m_container = nullptr; diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index b28d2d9b..5ef4c09a 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -381,7 +381,7 @@ namespace Ui { #include "logic/BaseInstance.h" #include "logic/OneSixInstance.h" #include "logic/InstanceFactory.h" -#include "logic/MinecraftProcess.h" +#include "logic/BaseProcess.h" #include "logic/OneSixUpdate.h" #include "logic/java/JavaUtils.h" #include "logic/NagUtils.h" @@ -403,6 +403,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); + // initialize the news checker + m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL)); + QString winTitle = QString("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString()); if (!BuildConfig.BUILD_PLATFORM.isEmpty()) @@ -443,7 +446,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); - QObject::connect(MMC->newsChecker().get(), &NewsChecker::newsLoaded, this, + QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); updateNewsLabel(); } @@ -606,7 +609,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi MMC->lwjgllist()->loadList(); } - MMC->newsChecker()->reloadNews(); + m_newsChecker->reloadNews(); updateNewsLabel(); // set up the updater object. @@ -888,15 +891,14 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev) void MainWindow::updateNewsLabel() { - auto newsChecker = MMC->newsChecker(); - if (newsChecker->isLoadingNews()) + if (m_newsChecker->isLoadingNews()) { newsLabel->setText(tr("Loading news...")); newsLabel->setEnabled(false); } else { - QList entries = newsChecker->getNewsEntries(); + QList entries = m_newsChecker->getNewsEntries(); if (entries.length() > 0) { newsLabel->setText(entries[0]->title); @@ -1041,7 +1043,9 @@ static QFileInfo findRecursive(const QString &dir, const QString &name) } return QFileInfo(); } -void MainWindow::on_actionAddInstance_triggered() + +// FIXME: eliminate, should not be needed +void MainWindow::waitForMinecraftVersions() { if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask && m_versionLoadTask->isRunning()) @@ -1051,121 +1055,130 @@ void MainWindow::on_actionAddInstance_triggered() waitLoop.connect(m_versionLoadTask, SIGNAL(succeeded()), SLOT(quit())); waitLoop.exec(); } +} - NewInstanceDialog newInstDlg(this); - if (!newInstDlg.exec()) - return; - - MMC->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup()); - +void MainWindow::instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url) +{ InstancePtr newInstance; QString instancesDir = MMC->settings()->get("InstanceDir").toString(); - QString instDirName = DirNameFromString(newInstDlg.instName(), instancesDir); + QString instDirName = DirNameFromString(instName, instancesDir); QString instDir = PathCombine(instancesDir, instDirName); auto &loader = InstanceFactory::get(); - - const QUrl modpackUrl = newInstDlg.modpackUrl(); - if (modpackUrl.isValid()) + QString archivePath; + if (url.isLocalFile()) { - QString archivePath; - if (modpackUrl.isLocalFile()) - { - archivePath = modpackUrl.toLocalFile(); - } - else - { - const QString path = modpackUrl.host() + '/' + modpackUrl.path(); - auto entry = MMC->metacache()->resolveEntry("general", path); - CacheDownloadPtr dl = CacheDownload::make(modpackUrl, entry); - NetJob job(tr("Modpack download")); - job.addNetAction(dl); - - // FIXME: possibly causes endless loop problems - ProgressDialog dlDialog(this); - if (dlDialog.exec(&job) != QDialog::Accepted) - { - return; - } - - archivePath = entry->getFullPath(); - } - + archivePath = url.toLocalFile(); + } + else + { + const QString path = url.host() + '/' + url.path(); + auto entry = MMC->metacache()->resolveEntry("general", path); + CacheDownloadPtr dl = CacheDownload::make(url, entry); + NetJob job(tr("Modpack download")); + job.addNetAction(dl); - QTemporaryDir extractTmpDir; - QDir extractDir(extractTmpDir.path()); - QLOG_INFO() << "Attempting to create instance from" << archivePath; - if (JlCompress::extractDir(archivePath, extractDir.absolutePath()).isEmpty()) - { - CustomMessageBox::selectable(this, tr("Error"), - tr("Failed to extract modpack"), QMessageBox::Warning)->show(); - return; - } - const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg"); - if (!instanceCfgFile.isFile() || !instanceCfgFile.exists()) + // FIXME: possibly causes endless loop problems + ProgressDialog dlDialog(this); + if (dlDialog.exec(&job) != QDialog::Accepted) { - CustomMessageBox::selectable(this, tr("Error"), tr("Archive does not contain instance.cfg"))->show(); - return; - } - if (!copyPath(instanceCfgFile.absoluteDir().absolutePath(), instDir)) - { - CustomMessageBox::selectable(this, tr("Error"), tr("Unable to copy instance"))->show(); return; } + archivePath = entry->getFullPath(); + } - auto error = loader.loadInstance(newInstance, instDir); - QString errorMsg = tr("Failed to load instance %1: ").arg(instDirName); - switch (error) - { - case InstanceFactory::UnknownLoadError: - errorMsg += tr("Unkown error"); - CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); - return; - case InstanceFactory::NotAnInstance: - errorMsg += tr("Not an instance"); - CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); - return; - } + QTemporaryDir extractTmpDir; + QDir extractDir(extractTmpDir.path()); + QLOG_INFO() << "Attempting to create instance from" << archivePath; + if (JlCompress::extractDir(archivePath, extractDir.absolutePath()).isEmpty()) + { + CustomMessageBox::selectable(this, tr("Error"), + tr("Failed to extract modpack"), QMessageBox::Warning)->show(); + return; } - else + const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg"); + if (!instanceCfgFile.isFile() || !instanceCfgFile.exists()) { - auto error = loader.createInstance(newInstance, newInstDlg.selectedVersion(), instDir); - QString errorMsg = tr("Failed to create instance %1: ").arg(instDirName); - switch (error) - { - case InstanceFactory::NoCreateError: break; - case InstanceFactory::InstExists: - { - errorMsg += tr("An instance with the given directory name already exists."); - CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); - return; - } + CustomMessageBox::selectable(this, tr("Error"), tr("Archive does not contain instance.cfg"))->show(); + return; + } + if (!copyPath(instanceCfgFile.absoluteDir().absolutePath(), instDir)) + { + CustomMessageBox::selectable(this, tr("Error"), tr("Unable to copy instance"))->show(); + return; + } - case InstanceFactory::CantCreateDir: - { - errorMsg += tr("Failed to create the instance directory."); - CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); - return; - } + auto error = loader.loadInstance(newInstance, instDir); + QString errorMsg = tr("Failed to load instance %1: ").arg(instDirName); + switch (error) + { + case InstanceFactory::UnknownLoadError: + errorMsg += tr("Unkown error"); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; + case InstanceFactory::NotAnInstance: + errorMsg += tr("Not an instance"); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; + } - default: - { - errorMsg += tr("Unknown instance loader error %1").arg(error); - CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); - return; - } - } + newInstance->setName(instName); + newInstance->setIconKey(instIcon); + newInstance->setGroupInitial(instGroup); + MMC->instances()->add(InstancePtr(newInstance)); + MMC->instances()->saveGroupList(); + + finalizeInstance(newInstance); +} + +void MainWindow::instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version) +{ + InstancePtr newInstance; + + QString instancesDir = MMC->settings()->get("InstanceDir").toString(); + QString instDirName = DirNameFromString(instName, instancesDir); + QString instDir = PathCombine(instancesDir, instDirName); + auto &loader = InstanceFactory::get(); + auto error = loader.createInstance(newInstance, version, instDir); + QString errorMsg = tr("Failed to create instance %1: ").arg(instDirName); + switch (error) + { + case InstanceFactory::NoCreateError: break; + case InstanceFactory::InstExists: + { + errorMsg += tr("An instance with the given directory name already exists."); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; + } + + case InstanceFactory::CantCreateDir: + { + errorMsg += tr("Failed to create the instance directory."); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; } - newInstance->setName(newInstDlg.instName()); - newInstance->setIconKey(newInstDlg.iconKey()); - newInstance->setGroupInitial(newInstDlg.instGroup()); + default: + { + errorMsg += tr("Unknown instance loader error %1").arg(error); + CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show(); + return; + } + } + newInstance->setName(instName); + newInstance->setIconKey(instIcon); + newInstance->setGroupInitial(instGroup); MMC->instances()->add(InstancePtr(newInstance)); + MMC->instances()->saveGroupList(); + finalizeInstance(newInstance); +} +void MainWindow::finalizeInstance(InstancePtr inst) +{ if (MMC->accounts()->anyAccountIsValid()) { ProgressDialog loadDialog(this); - auto update = newInstance->doUpdate(); + auto update = inst->doUpdate(); connect(update.get(), &Task::failed, [this](QString reason) { QString error = QString("Instance load failed: %1").arg(reason); @@ -1184,6 +1197,30 @@ void MainWindow::on_actionAddInstance_triggered() } } + +void MainWindow::on_actionAddInstance_triggered() +{ + waitForMinecraftVersions(); + + NewInstanceDialog newInstDlg(this); + if (!newInstDlg.exec()) + return; + + MMC->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup()); + + const QUrl modpackUrl = newInstDlg.modpackUrl(); + + + if (modpackUrl.isValid()) + { + instanceFromZipPack(newInstDlg.instName(), newInstDlg.instGroup(), newInstDlg.iconKey(), modpackUrl); + } + else + { + instanceFromVersion(newInstDlg.instName(), newInstDlg.instGroup(), newInstDlg.iconKey(), newInstDlg.selectedVersion()); + } +} + void MainWindow::on_actionCopyInstance_triggered() { if (!m_selectedInstance) @@ -1389,7 +1426,7 @@ void MainWindow::on_actionMoreNews_triggered() void MainWindow::newsButtonClicked() { - QList entries = MMC->newsChecker()->getNewsEntries(); + QList entries = m_newsChecker->getNewsEntries(); if (entries.count() > 0) openWebPage(QUrl(entries[0]->link)); else @@ -1686,19 +1723,15 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, QString launchScript; - if (!instance->prepareForLaunch(session, launchScript)) + BaseProcess *proc = instance->prepareForLaunch(session); + if (!proc) return; - MinecraftProcess *proc = new MinecraftProcess(instance); - proc->setLaunchScript(launchScript); - proc->setWorkdir(instance->minecraftRoot()); - this->hide(); console = new ConsoleWindow(proc); connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded())); - proc->setLogin(session); proc->arm(); if (profiler) @@ -1725,7 +1758,7 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, { dialog.accept(); QMessageBox msg; - msg.setText(tr("The launch of Minecraft itself is delayed until you press the " + msg.setText(tr("The game launch is delayed until you press the " "button. This is the right time to setup the profiler, as the " "profiler server is running now.\n\n%1").arg(message)); msg.setWindowTitle(tr("Waiting")); @@ -1837,32 +1870,6 @@ void MainWindow::instanceEnded() this->show(); } -void MainWindow::checkMigrateLegacyAssets() -{ - int legacyAssets = AssetsUtils::findLegacyAssets(); - if (legacyAssets > 0) - { - ProgressDialog migrateDlg(this); - AssetsMigrateTask migrateTask(legacyAssets, &migrateDlg); - { - ThreadTask threadTask(&migrateTask); - - if (migrateDlg.exec(&threadTask)) - { - QLOG_INFO() << "Assets migration task completed successfully"; - } - else - { - QLOG_INFO() << "Assets migration task reported failure"; - } - } - } - else - { - QLOG_INFO() << "Didn't find any legacy assets to migrate"; - } -} - void MainWindow::checkSetDefaultJava() { const QString javaHack = "IntelHack"; diff --git a/gui/MainWindow.h b/gui/MainWindow.h index 4d417f15..4e8e8f35 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -24,6 +24,7 @@ #include "logic/auth/MojangAccount.h" #include "logic/net/NetJob.h" +class NewsChecker; class QToolButton; class LabeledToolButton; class QLabel; @@ -51,7 +52,6 @@ public: void openWebPage(QUrl url); void checkSetDefaultJava(); - void checkMigrateLegacyAssets(); void checkInstancePathForProblems(); private @@ -182,6 +182,11 @@ protected: void setSelectedInstanceById(const QString &id); + void waitForMinecraftVersions(); + void instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version); + void instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url); + void finalizeInstance(InstancePtr inst); + private: Ui::MainWindow *ui; class GroupView *view; @@ -194,6 +199,7 @@ private: QToolButton *newsLabel; std::shared_ptr m_globalSettingsProvider; + std::shared_ptr m_newsChecker; InstancePtr m_selectedInstance; QString m_currentInstIcon; diff --git a/gui/dialogs/AboutDialog.cpp b/gui/dialogs/AboutDialog.cpp index e15da142..ee08beea 100644 --- a/gui/dialogs/AboutDialog.cpp +++ b/gui/dialogs/AboutDialog.cpp @@ -99,7 +99,7 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); - MMC->connect(ui->aboutQt, SIGNAL(clicked()), SLOT(aboutQt())); + connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt); loadPatronList(); } diff --git a/gui/pages/LogPage.cpp b/gui/pages/LogPage.cpp index 957c2f72..4e9e4f52 100644 --- a/gui/pages/LogPage.cpp +++ b/gui/pages/LogPage.cpp @@ -7,10 +7,10 @@ #include #include -#include "logic/MinecraftProcess.h" +#include "logic/BaseProcess.h" #include "gui/GuiUtil.h" -LogPage::LogPage(MinecraftProcess *proc, QWidget *parent) +LogPage::LogPage(BaseProcess *proc, QWidget *parent) : QWidget(parent), ui(new Ui::LogPage), m_process(proc) { ui->setupUi(this); diff --git a/gui/pages/LogPage.h b/gui/pages/LogPage.h index 7d073a53..3d5580dc 100644 --- a/gui/pages/LogPage.h +++ b/gui/pages/LogPage.h @@ -19,7 +19,7 @@ #include "logic/BaseInstance.h" #include "logic/net/NetJob.h" -#include "logic/MinecraftProcess.h" +#include "logic/BaseProcess.h" #include "BasePage.h" #include @@ -36,7 +36,7 @@ class LogPage : public QWidget, public BasePage Q_OBJECT public: - explicit LogPage(MinecraftProcess *proc, QWidget *parent = 0); + explicit LogPage(BaseProcess *proc, QWidget *parent = 0); virtual ~LogPage(); virtual QString displayName() const override { @@ -78,7 +78,7 @@ private slots: private: Ui::LogPage *ui; - MinecraftProcess *m_process; + BaseProcess *m_process; int m_last_scroll_value = 0; bool m_scroll_active = true; int m_saved_offset = 0; diff --git a/gui/pages/ModFolderPage.cpp b/gui/pages/ModFolderPage.cpp index 5172aa1e..938ec421 100644 --- a/gui/pages/ModFolderPage.cpp +++ b/gui/pages/ModFolderPage.cpp @@ -30,7 +30,7 @@ #include "gui/dialogs/ModEditDialogCommon.h" #include "logic/ModList.h" #include "logic/Mod.h" -#include "logic/VersionFilterData.h" +#include "logic/minecraft/VersionFilterData.h" ModFolderPage::ModFolderPage(BaseInstance *inst, std::shared_ptr mods, QString id, QString iconName, QString displayName, QString helpPage, @@ -80,7 +80,7 @@ bool CoreModFolderPage::shouldDisplay() const auto inst = dynamic_cast(m_inst); if (!inst) return true; - auto version = inst->getFullVersion(); + auto version = inst->getMinecraftProfile(); if (!version) return true; if (version->m_releaseTime < g_VersionFilterData.legacyCutoffDate) diff --git a/gui/pages/OtherLogsPage.cpp b/gui/pages/OtherLogsPage.cpp index f12ff8a3..c9b0aa51 100644 --- a/gui/pages/OtherLogsPage.cpp +++ b/gui/pages/OtherLogsPage.cpp @@ -21,17 +21,17 @@ #include "gui/GuiUtil.h" #include "logic/RecursiveFileSystemWatcher.h" -#include "logic/BaseInstance.h" +#include -OtherLogsPage::OtherLogsPage(BaseInstance *instance, QWidget *parent) - : QWidget(parent), ui(new Ui::OtherLogsPage), m_instance(instance), +OtherLogsPage::OtherLogsPage(QString path, QWidget *parent) + : QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_watcher(new RecursiveFileSystemWatcher(this)) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); m_watcher->setFileExpression("(.*\\.log(\\.[0-9]*)?$)|(crash-.*\\.txt)"); - m_watcher->setRootDir(QDir::current().absoluteFilePath(m_instance->minecraftRoot())); + m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); connect(m_watcher, &RecursiveFileSystemWatcher::filesChanged, this, &OtherLogsPage::populateSelectLogBox); @@ -76,7 +76,7 @@ void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) file = ui->selectLogBox->itemText(index); } - if (file.isEmpty() || !QFile::exists(m_instance->minecraftRoot() + "/" + file)) + if (file.isEmpty() || !QFile::exists(PathCombine(m_path, file))) { m_currentFile = QString(); ui->text->clear(); @@ -92,7 +92,7 @@ void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) void OtherLogsPage::on_btnReload_clicked() { - QFile file(m_instance->minecraftRoot() + "/" + m_currentFile); + QFile file(PathCombine(m_path, m_currentFile)); if (!file.open(QFile::ReadOnly)) { setControlsEnabled(false); @@ -132,7 +132,7 @@ void OtherLogsPage::on_btnDelete_clicked() { return; } - QFile file(m_instance->minecraftRoot() + "/" + m_currentFile); + QFile file(PathCombine(m_path, m_currentFile)); if (!file.remove()) { QMessageBox::critical(this, tr("Error"), tr("Unable to delete %1: %2") diff --git a/gui/pages/OtherLogsPage.h b/gui/pages/OtherLogsPage.h index 5777cb83..d6e4ec9f 100644 --- a/gui/pages/OtherLogsPage.h +++ b/gui/pages/OtherLogsPage.h @@ -27,14 +27,12 @@ class OtherLogsPage; class RecursiveFileSystemWatcher; -class BaseInstance; - class OtherLogsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit OtherLogsPage(BaseInstance *instance, QWidget *parent = 0); + explicit OtherLogsPage(QString path, QWidget *parent = 0); ~OtherLogsPage(); QString id() const override @@ -66,7 +64,7 @@ private slots: private: Ui::OtherLogsPage *ui; - BaseInstance *m_instance; + QString m_path; RecursiveFileSystemWatcher *m_watcher; QString m_currentFile; diff --git a/gui/pages/ResourcePackPage.h b/gui/pages/ResourcePackPage.h index 1a9039ff..d79590df 100644 --- a/gui/pages/ResourcePackPage.h +++ b/gui/pages/ResourcePackPage.h @@ -4,7 +4,7 @@ class ResourcePackPage : public ModFolderPage { public: - explicit ResourcePackPage(BaseInstance *instance, QWidget *parent = 0) + explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0) : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks", "resourcepacks", tr("Resource packs"), "Resource-packs", parent) { diff --git a/gui/pages/ScreenshotsPage.cpp b/gui/pages/ScreenshotsPage.cpp index 1771a118..219a47ae 100644 --- a/gui/pages/ScreenshotsPage.cpp +++ b/gui/pages/ScreenshotsPage.cpp @@ -209,7 +209,7 @@ public: } }; -ScreenshotsPage::ScreenshotsPage(BaseInstance *instance, QWidget *parent) +ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent) : QWidget(parent), ui(new Ui::ScreenshotsPage) { m_model.reset(new QFileSystemModel()); @@ -219,7 +219,7 @@ ScreenshotsPage::ScreenshotsPage(BaseInstance *instance, QWidget *parent) m_model->setReadOnly(false); m_model->setNameFilters({"*.png"}); m_model->setNameFilterDisables(false); - m_folder = PathCombine(instance->minecraftRoot(), "screenshots"); + m_folder = path; m_valid = ensureFolderPathExists(m_folder); ui->setupUi(this); diff --git a/gui/pages/ScreenshotsPage.h b/gui/pages/ScreenshotsPage.h index 6be2a26d..08ffbf26 100644 --- a/gui/pages/ScreenshotsPage.h +++ b/gui/pages/ScreenshotsPage.h @@ -37,7 +37,7 @@ class ScreenshotsPage : public QWidget, public BasePage Q_OBJECT public: - explicit ScreenshotsPage(BaseInstance *instance, QWidget *parent = 0); + explicit ScreenshotsPage(QString path, QWidget *parent = 0); virtual ~ScreenshotsPage(); virtual void opened() override; diff --git a/gui/pages/TexturePackPage.h b/gui/pages/TexturePackPage.h index 7e663789..3c5c27d7 100644 --- a/gui/pages/TexturePackPage.h +++ b/gui/pages/TexturePackPage.h @@ -4,7 +4,7 @@ class TexturePackPage : public ModFolderPage { public: - explicit TexturePackPage(BaseInstance *instance, QWidget *parent = 0) + explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0) : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks", tr("Texture packs"), "Texture-packs", parent) { diff --git a/gui/pages/VersionPage.cpp b/gui/pages/VersionPage.cpp index 554bf8a4..17869589 100644 --- a/gui/pages/VersionPage.cpp +++ b/gui/pages/VersionPage.cpp @@ -38,7 +38,7 @@ #include #include "logic/ModList.h" -#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/MinecraftProfile.h" #include "logic/EnabledItemFilter.h" #include "logic/forge/ForgeVersionList.h" #include "logic/forge/ForgeInstaller.h" @@ -65,7 +65,7 @@ VersionPage::VersionPage(OneSixInstance *inst, QWidget *parent) ui->setupUi(this); ui->tabWidget->tabBar()->hide(); - m_version = m_inst->getFullVersion(); + m_version = m_inst->getMinecraftProfile(); if (m_version) { main_model = new EnabledItemFilter(this); @@ -109,11 +109,11 @@ void VersionPage::disableVersionControls() ui->removeLibraryBtn->setEnabled(false); } -bool VersionPage::reloadInstanceVersion() +bool VersionPage::reloadMinecraftProfile() { try { - m_inst->reloadVersion(); + m_inst->reloadProfile(); return true; } catch (MMCError &e) @@ -132,7 +132,7 @@ bool VersionPage::reloadInstanceVersion() void VersionPage::on_reloadLibrariesBtn_clicked() { - reloadInstanceVersion(); + reloadMinecraftProfile(); } void VersionPage::on_removeLibraryBtn_clicked() @@ -202,7 +202,7 @@ void VersionPage::on_moveLibraryUpBtn_clicked() try { const int row = ui->libraryTreeView->selectionModel()->selectedRows().first().row(); - m_version->move(row, InstanceVersion::MoveUp); + m_version->move(row, MinecraftProfile::MoveUp); } catch (MMCError &e) { @@ -219,7 +219,7 @@ void VersionPage::on_moveLibraryDownBtn_clicked() try { const int row = ui->libraryTreeView->selectionModel()->selectedRows().first().row(); - m_version->move(row, InstanceVersion::MoveDown); + m_version->move(row, MinecraftProfile::MoveDown); } catch (MMCError &e) { @@ -244,7 +244,7 @@ void VersionPage::on_changeMCVersionBtn_clicked() return; } - if (m_inst->versionIsCustom()) + if (!m_version->isVanilla()) { auto result = CustomMessageBox::selectable( this, tr("Are you sure?"), @@ -256,7 +256,7 @@ void VersionPage::on_changeMCVersionBtn_clicked() if (result != QMessageBox::Ok) return; m_version->revertToVanilla(); - reloadInstanceVersion(); + reloadMinecraftProfile(); } m_inst->setIntendedVersionId(vselect.selectedVersion()->descriptor()); @@ -272,31 +272,6 @@ void VersionPage::on_changeMCVersionBtn_clicked() void VersionPage::on_forgeBtn_clicked() { - // FIXME: use actual model, not reloading. Move logic to model. - if (m_version->hasFtbPack()) - { - if (QMessageBox::question( - this, tr("Revert?"), - tr("This action will remove the FTB pack version patch. Continue?")) != - QMessageBox::Yes) - { - return; - } - m_version->removeFtbPack(); - reloadInstanceVersion(); - } - if (m_version->hasDeprecatedVersionFiles()) - { - if (QMessageBox::question(this, tr("Revert?"), - tr("This action will remove deprecated version files " - "(custom.json and version.json). Continue?")) != - QMessageBox::Yes) - { - return; - } - m_version->removeDeprecatedVersionFiles(); - reloadInstanceVersion(); - } VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); vselect.setExactFilter(1, m_inst->currentVersionId()); vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + @@ -311,30 +286,6 @@ void VersionPage::on_forgeBtn_clicked() void VersionPage::on_liteloaderBtn_clicked() { - if (m_version->hasFtbPack()) - { - if (QMessageBox::question( - this, tr("Revert?"), - tr("This action will remove the FTB pack version patch. Continue?")) != - QMessageBox::Yes) - { - return; - } - m_version->removeFtbPack(); - reloadInstanceVersion(); - } - if (m_version->hasDeprecatedVersionFiles()) - { - if (QMessageBox::question(this, tr("Revert?"), - tr("This action will remove deprecated version files " - "(custom.json and version.json). Continue?")) != - QMessageBox::Yes) - { - return; - } - m_version->removeDeprecatedVersionFiles(); - reloadInstanceVersion(); - } VersionSelectDialog vselect(MMC->liteloaderlist().get(), tr("Select LiteLoader version"), this); vselect.setExactFilter(1, m_inst->currentVersionId()); @@ -364,8 +315,7 @@ void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex & ui->moveLibraryUpBtn->setEnabled(enabled); } QString selectedId = m_version->versionFileId(current.row()); - if (selectedId == "net.minecraft" || selectedId == "org.multimc.custom.json" || - selectedId == "org.multimc.version.json") + if (selectedId == "net.minecraft") { ui->changeMCVersionBtn->setEnabled(true); } diff --git a/gui/pages/VersionPage.h b/gui/pages/VersionPage.h index 1249f47e..f1b98dbe 100644 --- a/gui/pages/VersionPage.h +++ b/gui/pages/VersionPage.h @@ -67,11 +67,11 @@ slots: protected: /// FIXME: this shouldn't be necessary! - bool reloadInstanceVersion(); + bool reloadMinecraftProfile(); private: Ui::VersionPage *ui; - std::shared_ptr m_version; + std::shared_ptr m_version; EnabledItemFilter *main_model; OneSixInstance *m_inst; NetJobPtr forgeJob; diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index 8048beea..a073a921 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -28,7 +28,6 @@ #include #include "logic/minecraft/MinecraftVersionList.h" #include "logic/icons/IconList.h" -#include "logic/InstanceList.h" BaseInstance::BaseInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : QObject(parent) @@ -115,30 +114,9 @@ QString BaseInstance::instanceRoot() const return m_rootDir; } -QString BaseInstance::minecraftRoot() const -{ - QFileInfo mcDir(PathCombine(instanceRoot(), "minecraft")); - QFileInfo dotMCDir(PathCombine(instanceRoot(), ".minecraft")); - - if (dotMCDir.exists() && !mcDir.exists()) - return dotMCDir.filePath(); - else - return mcDir.filePath(); -} - -InstanceList *BaseInstance::instList() const -{ - return qobject_cast(parent()); -} - InstancePtr BaseInstance::getSharedPtr() { - return instList()->getInstanceById(id()); -} - -std::shared_ptr BaseInstance::versionList() const -{ - return MMC->minecraftlist(); + return shared_from_this(); } SettingsObject &BaseInstance::settings() const diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index bcf28031..cbac701c 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -24,14 +24,14 @@ #include "logic/settings/INIFile.h" #include "logic/BaseVersionList.h" #include "logic/auth/MojangAccount.h" +#include "Mod.h" class ModList; class QDialog; class QDir; class Task; -class MinecraftProcess; +class BaseProcess; class OneSixUpdate; -class InstanceList; class BaseInstancePrivate; // pointer for lazy people @@ -46,7 +46,7 @@ typedef std::shared_ptr InstancePtr; * To create a new instance type, create a new class inheriting from this class * and implement the pure virtual functions. */ -class BaseInstance : public QObject +class BaseInstance : public QObject, public std::enable_shared_from_this { Q_OBJECT protected: @@ -57,9 +57,10 @@ public: /// virtual destructor to make sure the destruction is COMPLETE virtual ~BaseInstance() {}; - virtual void init() {} virtual void copy(const QDir &newDir) {} + virtual void init() = 0; + /// nuke thoroughly - deletes the instance contents, notifies the list/model which is /// responsible of cleaning up the husk void nuke(); @@ -77,9 +78,6 @@ public: /// Path to the instance's root directory. QString instanceRoot() const; - /// Path to the instance's minecraft directory. - QString minecraftRoot() const; - QString name() const; void setName(QString val); @@ -101,8 +99,6 @@ public: virtual QString intendedVersionId() const = 0; virtual bool setIntendedVersionId(QString version) = 0; - virtual bool versionIsCustom() = 0; - /*! * The instance's current version. * This value represents the instance's current version. If this value is @@ -112,21 +108,11 @@ public: virtual QString currentVersionId() const = 0; /*! - * Whether or not Minecraft should be downloaded when the instance is launched. + * Whether or not 'the game' should be downloaded when the instance is launched. */ virtual bool shouldUpdate() const = 0; virtual void setShouldUpdate(bool val) = 0; - ////// Mod Lists ////// - virtual std::shared_ptr resourcePackList() - { - return nullptr; - } - virtual std::shared_ptr texturePackList() - { - return nullptr; - } - /// Traits. Normally inside the version, depends on instance implementation. virtual QSet traits() = 0; @@ -138,21 +124,13 @@ public: /// Sets the last launched time to 'val' milliseconds since epoch void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch()); - /*! - * \brief Gets the instance list that this instance is a part of. - * Returns NULL if this instance is not in a list - * (the parent is not an InstanceList). - * \return A pointer to the InstanceList containing this instance. - */ - InstanceList *instList() const; - InstancePtr getSharedPtr(); /*! * \brief Gets a pointer to this instance's version list. * \return A pointer to the available version list for this instance. */ - virtual std::shared_ptr versionList() const; + virtual std::shared_ptr versionList() const = 0; /*! * \brief Gets this instance's settings object. @@ -164,8 +142,8 @@ public: /// returns a valid update task virtual std::shared_ptr doUpdate() = 0; - /// returns a valid minecraft process, ready for launch with the given account. - virtual bool prepareForLaunch(AuthSessionPtr account, QString & launchScript) = 0; + /// returns a valid process, ready for launch with the given account. + virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) = 0; /// do any necessary cleanups after the instance finishes. also runs before /// 'prepareForLaunch' diff --git a/logic/BaseProcess.cpp b/logic/BaseProcess.cpp new file mode 100644 index 00000000..d65e76d9 --- /dev/null +++ b/logic/BaseProcess.cpp @@ -0,0 +1,400 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Authors: Orochimarufan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "logic/BaseProcess.h" +#include "logger/QsLog.h" +#include +#include + +MessageLevel::Enum MessageLevel::getLevel(const QString& levelName) +{ + if (levelName == "MultiMC") + return MessageLevel::MultiMC; + else if (levelName == "Debug") + return MessageLevel::Debug; + else if (levelName == "Info") + return MessageLevel::Info; + else if (levelName == "Message") + return MessageLevel::Message; + else if (levelName == "Warning") + return MessageLevel::Warning; + else if (levelName == "Error") + return MessageLevel::Error; + else if (levelName == "Fatal") + return MessageLevel::Fatal; + // Skip PrePost, it's not exposed to !![]! + else + return MessageLevel::Message; +} + +BaseProcess::BaseProcess(InstancePtr instance): QProcess(), m_instance(instance) +{ +} + +void BaseProcess::init() +{ + connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(finish(int, QProcess::ExitStatus))); + + // prepare the process environment + QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); + + QProcessEnvironment env; + + QStringList ignored = + { + "JAVA_ARGS", + "CLASSPATH", + "CONFIGPATH", + "JAVA_HOME", + "JRE_HOME", + "_JAVA_OPTIONS", + "JAVA_OPTIONS", + "JAVA_TOOL_OPTIONS" + }; + for(auto key: rawenv.keys()) + { + auto value = rawenv.value(key); + // filter out dangerous java crap + if(ignored.contains(key)) + { + QLOG_INFO() << "Env: ignoring" << key << value; + continue; + } + // filter MultiMC-related things + if(key.startsWith("QT_")) + { + QLOG_INFO() << "Env: ignoring" << key << value; + continue; + } +#ifdef LINUX + // Do not pass LD_* variables to java. They were intended for MultiMC + if(key.startsWith("LD_")) + { + QLOG_INFO() << "Env: ignoring" << key << value; + continue; + } + // Strip IBus + // IBus is a Linux IME framework. For some reason, it breaks MC? + if (key == "XMODIFIERS" && value.contains(IBUS)) + { + QString save = value; + value.replace(IBUS, ""); + QLOG_INFO() << "Env: stripped" << IBUS << "from" << save << ":" << value; + } +#endif + QLOG_INFO() << "Env: " << key << value; + env.insert(key, value); + } +#ifdef LINUX + // HACK: Workaround for QTBUG-42500 + env.insert("LD_LIBRARY_PATH", ""); +#endif + + // export some infos + auto variables = getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + env.insert(it.key(), it.value()); + } + + this->setProcessEnvironment(env); + m_prepostlaunchprocess.setProcessEnvironment(env); + + // std channels + connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr())); + connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut())); + + // Log prepost launch command output (can be disabled.) + if (m_instance->settings().get("LogPrePostOutput").toBool()) + { + connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardError, this, + &BaseProcess::on_prepost_stdErr); + connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput, this, + &BaseProcess::on_prepost_stdOut); + } + + // a process has been constructed for the instance. It is running from MultiMC POV + m_instance->setRunning(true); +} + + +void BaseProcess::setWorkdir(QString path) +{ + QDir mcDir(path); + this->setWorkingDirectory(mcDir.absolutePath()); + m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); +} + +void BaseProcess::logOutput(const QStringList &lines, MessageLevel::Enum defaultLevel, + bool guessLevel, bool censor) +{ + for (int i = 0; i < lines.size(); ++i) + logOutput(lines[i], defaultLevel, guessLevel, censor); +} + +void BaseProcess::logOutput(QString line, MessageLevel::Enum defaultLevel, bool guessLevel, + bool censor) +{ + MessageLevel::Enum level = defaultLevel; + + //FIXME: make more flexible in the future + if(line.contains("ignoring option PermSize")) + { + return; + } + + // Level prefix + int endmark = line.indexOf("]!"); + if (line.startsWith("!![") && endmark != -1) + { + level = MessageLevel::getLevel(line.left(endmark).mid(3)); + line = line.mid(endmark + 2); + } + // Guess level + else if (guessLevel) + level = this->guessLevel(line, defaultLevel); + + if (censor) + line = censorPrivateInfo(line); + + emit log(line, level); +} + +void BaseProcess::on_stdErr() +{ + QByteArray data = readAllStandardError(); + QString str = m_err_leftover + QString::fromLocal8Bit(data); + + str.remove('\r'); + QStringList lines = str.split("\n"); + m_err_leftover = lines.takeLast(); + + logOutput(lines, MessageLevel::Error); +} + +void BaseProcess::on_stdOut() +{ + QByteArray data = readAllStandardOutput(); + QString str = m_out_leftover + QString::fromLocal8Bit(data); + + str.remove('\r'); + QStringList lines = str.split("\n"); + m_out_leftover = lines.takeLast(); + + logOutput(lines); +} + +void BaseProcess::on_prepost_stdErr() +{ + QByteArray data = m_prepostlaunchprocess.readAllStandardError(); + QString str = m_err_leftover + QString::fromLocal8Bit(data); + + str.remove('\r'); + QStringList lines = str.split("\n"); + m_err_leftover = lines.takeLast(); + + logOutput(lines, MessageLevel::PrePost, false, false); +} + +void BaseProcess::on_prepost_stdOut() +{ + QByteArray data = m_prepostlaunchprocess.readAllStandardOutput(); + QString str = m_out_leftover + QString::fromLocal8Bit(data); + + str.remove('\r'); + QStringList lines = str.split("\n"); + m_out_leftover = lines.takeLast(); + + logOutput(lines, MessageLevel::PrePost, false, false); +} + +// exit handler +void BaseProcess::finish(int code, ExitStatus status) +{ + // Flush console window + if (!m_err_leftover.isEmpty()) + { + logOutput(m_err_leftover, MessageLevel::Error); + m_err_leftover.clear(); + } + if (!m_out_leftover.isEmpty()) + { + logOutput(m_out_leftover); + m_out_leftover.clear(); + } + + if (!killed) + { + if (status == NormalExit) + { + //: Message displayed on instance exit + emit log(tr("Game exited with exitcode %1.").arg(code)); + } + else + { + //: Message displayed on instance crashed + emit log(tr("Game crashed with exitcode %1.").arg(code)); + } + } + else + { + //: Message displayed after the instance exits due to kill request + emit log(tr("Game was killed by user."), MessageLevel::Error); + } + + m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); + + // run post-exit + postLaunch(); + m_instance->cleanupAfterRun(); + // no longer running... + m_instance->setRunning(false); + emit ended(m_instance, code, status); +} + +void BaseProcess::killProcess() +{ + killed = true; + if (m_prepostlaunchprocess.state() == QProcess::Running) + { + m_prepostlaunchprocess.kill(); + } + else + { + kill(); + } +} + +bool BaseProcess::preLaunch() +{ + QString prelaunch_cmd = m_instance->settings().get("PreLaunchCommand").toString(); + if (!prelaunch_cmd.isEmpty()) + { + prelaunch_cmd = substituteVariables(prelaunch_cmd); + // Launch + emit log(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd)); + m_prepostlaunchprocess.start(prelaunch_cmd); + if (!waitForPrePost()) + { + emit log(tr("The command failed to start"), MessageLevel::Fatal); + return false; + } + // Flush console window + if (!m_err_leftover.isEmpty()) + { + logOutput(m_err_leftover, MessageLevel::PrePost); + m_err_leftover.clear(); + } + if (!m_out_leftover.isEmpty()) + { + logOutput(m_out_leftover, MessageLevel::PrePost); + m_out_leftover.clear(); + } + // Process return values + if (m_prepostlaunchprocess.exitStatus() != NormalExit) + { + emit log(tr("Pre-Launch command failed with code %1.\n\n") + .arg(m_prepostlaunchprocess.exitCode()), + MessageLevel::Fatal); + m_instance->cleanupAfterRun(); + emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), + m_prepostlaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); + return false; + } + else + emit log(tr("Pre-Launch command ran successfully.\n\n")); + + return m_instance->reload(); + } + return true; +} +bool BaseProcess::postLaunch() +{ + QString postlaunch_cmd = m_instance->settings().get("PostExitCommand").toString(); + if (!postlaunch_cmd.isEmpty()) + { + postlaunch_cmd = substituteVariables(postlaunch_cmd); + emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd)); + m_prepostlaunchprocess.start(postlaunch_cmd); + if (!waitForPrePost()) + { + return false; + } + // Flush console window + if (!m_err_leftover.isEmpty()) + { + logOutput(m_err_leftover, MessageLevel::PrePost); + m_err_leftover.clear(); + } + if (!m_out_leftover.isEmpty()) + { + logOutput(m_out_leftover, MessageLevel::PrePost); + m_out_leftover.clear(); + } + if (m_prepostlaunchprocess.exitStatus() != NormalExit) + { + emit log(tr("Post-Launch command failed with code %1.\n\n") + .arg(m_prepostlaunchprocess.exitCode()), + MessageLevel::Error); + emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), + m_prepostlaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); + } + else + emit log(tr("Post-Launch command ran successfully.\n\n")); + + return m_instance->reload(); + } + return true; +} + +bool BaseProcess::waitForPrePost() +{ + if (!m_prepostlaunchprocess.waitForStarted()) + return false; + QEventLoop eventLoop; + auto finisher = [this, &eventLoop](QProcess::ProcessState state) + { + if (state == QProcess::NotRunning) + { + eventLoop.quit(); + } + }; + auto connection = connect(&m_prepostlaunchprocess, &QProcess::stateChanged, finisher); + int ret = eventLoop.exec(); + disconnect(connection); + return ret == 0; +} + +QString BaseProcess::substituteVariables(const QString &cmd) const +{ + QString out = cmd; + auto variables = getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + out.replace("$" + it.key(), it.value()); + } + auto env = QProcessEnvironment::systemEnvironment(); + for (auto var : env.keys()) + { + out.replace("$" + var, env.value(var)); + } + return out; +} diff --git a/logic/BaseProcess.h b/logic/BaseProcess.h new file mode 100644 index 00000000..ff8b9ed3 --- /dev/null +++ b/logic/BaseProcess.h @@ -0,0 +1,133 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Authors: Orochimarufan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include "BaseInstance.h" + +/** + * @brief the MessageLevel Enum + * defines what level a message is + */ +namespace MessageLevel +{ +enum Enum +{ + MultiMC, /**< MultiMC Messages */ + Debug, /**< Debug Messages */ + Info, /**< Info Messages */ + Message, /**< Standard Messages */ + Warning, /**< Warnings */ + Error, /**< Errors */ + Fatal, /**< Fatal Errors */ + PrePost, /**< Pre/Post Launch command output */ +}; +MessageLevel::Enum getLevel(const QString &levelName); +} + +class BaseProcess: public QProcess +{ + Q_OBJECT +protected: + explicit BaseProcess(InstancePtr instance); + void init(); + +public: /* methods */ + virtual ~BaseProcess() {}; + + InstancePtr instance() + { + return m_instance; + } + + void setWorkdir(QString path); + + void killProcess(); + + /** + * @brief prepare the process for launch (for multi-stage launch) + */ + virtual void arm() = 0; + + /** + * @brief launch the armed instance + */ + virtual void launch() = 0; + + /** + * @brief abort launch + */ + virtual void abort() = 0; + +protected: /* methods */ + bool preLaunch(); + bool postLaunch(); + bool waitForPrePost(); + QString substituteVariables(const QString &cmd) const; + + virtual QMap getVariables() const = 0; + virtual QString censorPrivateInfo(QString in) = 0; + virtual MessageLevel::Enum guessLevel(const QString &message, MessageLevel::Enum defaultLevel) = 0; + +signals: + /** + * @brief emitted when the Process immediately fails to run + */ + void launch_failed(InstancePtr); + + /** + * @brief emitted when the PreLaunchCommand fails + */ + void prelaunch_failed(InstancePtr, int code, QProcess::ExitStatus status); + + /** + * @brief emitted when the PostLaunchCommand fails + */ + void postlaunch_failed(InstancePtr, int code, QProcess::ExitStatus status); + + /** + * @brief emitted when the process has finished and the PostLaunchCommand was run + */ + void ended(InstancePtr, int code, QProcess::ExitStatus status); + + /** + * @brief emitted when we want to log something + * @param text the text to log + * @param level the level to log at + */ + void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC); + +protected slots: + void finish(int, QProcess::ExitStatus status); + void on_stdErr(); + void on_stdOut(); + void on_prepost_stdOut(); + void on_prepost_stdErr(); + void logOutput(const QStringList &lines, + MessageLevel::Enum defaultLevel = MessageLevel::Message, + bool guessLevel = true, bool censor = true); + void logOutput(QString line, + MessageLevel::Enum defaultLevel = MessageLevel::Message, + bool guessLevel = true, bool censor = true); + +protected: + InstancePtr m_instance; + QString m_err_leftover; + QString m_out_leftover; + QProcess m_prepostlaunchprocess; + bool killed = false; +}; diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index 0462aec0..857d3a09 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -27,12 +27,13 @@ #include "logic/BaseInstance.h" #include "logic/LegacyInstance.h" -#include "logic/LegacyFTBInstance.h" #include "logic/OneSixInstance.h" -#include "logic/OneSixFTBInstance.h" #include "logic/OneSixInstance.h" #include "logic/BaseVersion.h" #include "logic/minecraft/MinecraftVersion.h" +#include "logic/ftb/LegacyFTBInstance.h" +#include "logic/ftb/OneSixFTBInstance.h" +#include "logic/ftb/FTBVersion.h" InstanceFactory InstanceFactory::loader; @@ -74,8 +75,8 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(InstancePtr &inst, return NoLoadError; } -InstanceFactory::InstCreateError InstanceFactory::createInstance(InstancePtr &inst, BaseVersionPtr version, - const QString &instDir, const InstanceFactory::InstType type) +InstanceFactory::InstCreateError +InstanceFactory::createInstance(InstancePtr &inst, BaseVersionPtr version, const QString &instDir) { QDir rootDir(instDir); @@ -85,8 +86,8 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(InstancePtr &in QLOG_ERROR() << "Can't create instance folder" << instDir; return InstanceFactory::CantCreateDir; } - auto mcVer = std::dynamic_pointer_cast(version); - if (!mcVer) + + if (!version) { QLOG_ERROR() << "Can't create instance for non-existing MC version"; return InstanceFactory::NoSuchVersion; @@ -95,37 +96,37 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(InstancePtr &in auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg")); m_settings->registerSetting("InstanceType", "Legacy"); - if (type == NormalInst) + auto minecraftVersion = std::dynamic_pointer_cast(version); + if(minecraftVersion) { + auto mcVer = std::dynamic_pointer_cast(version); m_settings->set("InstanceType", "OneSix"); inst.reset(new OneSixInstance(instDir, m_settings)); inst->setIntendedVersionId(version->descriptor()); + inst->init(); + return InstanceFactory::NoCreateError; } - else if (type == FTBInstance) + auto ftbVersion = std::dynamic_pointer_cast(version); + if(ftbVersion) { - if(mcVer->usesLegacyLauncher()) + auto mcversion = ftbVersion->getMinecraftVersion(); + if (mcversion->usesLegacyLauncher()) { m_settings->set("InstanceType", "LegacyFTB"); inst.reset(new LegacyFTBInstance(instDir, m_settings)); - inst->setIntendedVersionId(version->descriptor()); + inst->setIntendedVersionId(mcversion->descriptor()); } else { m_settings->set("InstanceType", "OneSixFTB"); inst.reset(new OneSixFTBInstance(instDir, m_settings)); - inst->setIntendedVersionId(version->descriptor()); + inst->setIntendedVersionId(mcversion->descriptor()); + inst->init(); } + return InstanceFactory::NoCreateError; } - else - { - delete m_settings; - return InstanceFactory::NoSuchVersion; - } - - inst->init(); - - // FIXME: really, how do you even know? - return InstanceFactory::NoCreateError; + delete m_settings; + return InstanceFactory::NoSuchVersion; } InstanceFactory::InstCreateError InstanceFactory::copyInstance(InstancePtr &newInstance, diff --git a/logic/InstanceFactory.h b/logic/InstanceFactory.h index 5ddb1d81..17ef8f38 100644 --- a/logic/InstanceFactory.h +++ b/logic/InstanceFactory.h @@ -56,12 +56,6 @@ public: CantCreateDir }; - enum InstType - { - NormalInst, - FTBInstance - }; - /*! * \brief Creates a stub instance * @@ -74,7 +68,7 @@ public: * - CantCreateDir if the given instance directory cannot be created. */ InstCreateError createInstance(InstancePtr &inst, BaseVersionPtr version, - const QString &instDir, const InstType type = NormalInst); + const QString &instDir); /*! * \brief Creates a copy of an existing instance with a new name diff --git a/logic/InstanceList.cpp b/logic/InstanceList.cpp index 29908e01..ec7dbdbf 100644 --- a/logic/InstanceList.cpp +++ b/logic/InstanceList.cpp @@ -32,6 +32,7 @@ #include "logic/minecraft/MinecraftVersionList.h" #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" +#include "ftb/FTBVersion.h" #include "logger/QsLog.h" #include "gui/groupview/GroupView.h" @@ -403,16 +404,16 @@ void InstanceList::loadFTBInstances(QMap &groupMap, QLOG_INFO() << "Converting " << record.name << " as new."; InstancePtr instPtr; auto &factory = InstanceFactory::get(); - auto version = MMC->minecraftlist()->findVersion(record.mcVersion); - if (!version) + auto mcVersion = std::dynamic_pointer_cast(MMC->minecraftlist()->findVersion(record.mcVersion)); + if (!mcVersion) { QLOG_ERROR() << "Can't load instance " << record.instanceDir << " because minecraft version " << record.mcVersion << " can't be resolved."; continue; } - auto error = factory.createInstance(instPtr, version, record.instanceDir, - InstanceFactory::FTBInstance); + auto ftbVersion = std::make_shared(mcVersion); + auto error = factory.createInstance(instPtr, ftbVersion, record.instanceDir); if (!instPtr || error != InstanceFactory::NoCreateError) continue; diff --git a/logic/InstanceList.h b/logic/InstanceList.h index 4cb8ab6f..c251be5a 100644 --- a/logic/InstanceList.h +++ b/logic/InstanceList.h @@ -19,7 +19,6 @@ #include #include #include -#include #include "logic/BaseInstance.h" @@ -55,7 +54,7 @@ private: QSet discoverFTBInstances(); void loadFTBInstances(QMap &groupMap, QList & tempList); -private +public slots: void saveGroupList(); diff --git a/logic/JarUtils.cpp b/logic/JarUtils.cpp index 3b4f780a..d4f1ed56 100644 --- a/logic/JarUtils.cpp +++ b/logic/JarUtils.cpp @@ -30,7 +30,6 @@ bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, continue; } contained.insert(filename); - QLOG_INFO() << "Adding file " << filename << " from " << from.fileName(); if (!fileInsideMod.open(QIODevice::ReadOnly)) { @@ -103,8 +102,6 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) - : BaseInstance(rootDir, settings, parent) + : MinecraftInstance(rootDir, settings, parent) { settings->registerSetting("NeedsRebuild", true); settings->registerSetting("ShouldUpdate", false); @@ -66,7 +66,7 @@ QList LegacyInstance::getPages() "Loader-mods")); values.append(new TexturePackPage(this)); values.append(new NotesPage(this)); - values.append(new ScreenshotsPage(this)); + values.append(new ScreenshotsPage(PathCombine(minecraftRoot(), "screenshots"))); values.append(new InstanceSettingsPage(this)); return values; } @@ -124,8 +124,9 @@ std::shared_ptr LegacyInstance::doUpdate() return std::shared_ptr(new LegacyUpdate(this, this)); } -bool LegacyInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScript) +BaseProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account) { + QString launchScript; QIcon icon = MMC->icons()->getIcon(iconKey()); auto pixmap = icon.pixmap(128, 128); pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); @@ -150,7 +151,11 @@ bool LegacyInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScr launchScript += "lwjgl " + lwjgl + "\n"; launchScript += "launcher legacy\n"; } - return true; + auto process = MinecraftProcess::create(std::dynamic_pointer_cast(getSharedPtr())); + process->setLaunchScript(launchScript); + process->setWorkdir(minecraftRoot()); + process->setLogin(account); + return process; } void LegacyInstance::cleanupAfterRun() @@ -158,7 +163,7 @@ void LegacyInstance::cleanupAfterRun() // FIXME: delete the launcher and icons and whatnot. } -std::shared_ptr LegacyInstance::coreModList() +std::shared_ptr LegacyInstance::coreModList() const { if (!core_mod_list) { @@ -168,7 +173,7 @@ std::shared_ptr LegacyInstance::coreModList() return core_mod_list; } -std::shared_ptr LegacyInstance::jarModList() +std::shared_ptr LegacyInstance::jarModList() const { if (!jar_mod_list) { @@ -180,13 +185,18 @@ std::shared_ptr LegacyInstance::jarModList() return jar_mod_list; } +QList LegacyInstance::getJarMods() const +{ + return jarModList()->allMods(); +} + void LegacyInstance::jarModsChanged() { QLOG_INFO() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt."; setShouldRebuild(true); } -std::shared_ptr LegacyInstance::loaderModList() +std::shared_ptr LegacyInstance::loaderModList() const { if (!loader_mod_list) { @@ -196,7 +206,7 @@ std::shared_ptr LegacyInstance::loaderModList() return loader_mod_list; } -std::shared_ptr LegacyInstance::texturePackList() +std::shared_ptr LegacyInstance::texturePackList() const { if (!texture_pack_list) { diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index 0ced2b77..de7b2bd2 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -15,13 +15,13 @@ #pragma once -#include "BaseInstance.h" +#include "logic/minecraft/MinecraftInstance.h" #include "gui/pages/BasePageProvider.h" class ModList; class Task; -class LegacyInstance : public BaseInstance, public BasePageProvider +class LegacyInstance : public MinecraftInstance, public BasePageProvider { Q_OBJECT public: @@ -29,6 +29,8 @@ public: explicit LegacyInstance(const QString &rootDir, SettingsObject *settings, QObject *parent = 0); + virtual void init() {}; + /// Path to the instance's minecraft.jar QString runnableJar() const; @@ -40,10 +42,11 @@ public: virtual QString dialogTitle(); ////// Mod Lists ////// - std::shared_ptr jarModList(); - std::shared_ptr coreModList(); - std::shared_ptr loaderModList(); - std::shared_ptr texturePackList(); + std::shared_ptr jarModList() const ; + virtual QList< Mod > getJarMods() const override; + std::shared_ptr coreModList() const; + std::shared_ptr loaderModList() const; + std::shared_ptr texturePackList() const override; ////// Directories ////// QString libDir() const; @@ -94,12 +97,6 @@ public: virtual QString intendedVersionId() const override; virtual bool setIntendedVersionId(QString version) override; - // the `version' of Legacy instances is defined by the launcher code. - // in contrast with OneSix, where `version' is described in a json file - virtual bool versionIsCustom() override - { - return false; - } virtual QSet traits() { @@ -110,16 +107,16 @@ public: virtual void setShouldUpdate(bool val) override; virtual std::shared_ptr doUpdate() override; - virtual bool prepareForLaunch(AuthSessionPtr account, QString & launchScript) override; + virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override; virtual void cleanupAfterRun() override; virtual QString getStatusbarDescription() override; protected: - std::shared_ptr jar_mod_list; - std::shared_ptr core_mod_list; - std::shared_ptr loader_mod_list; - std::shared_ptr texture_pack_list; + mutable std::shared_ptr jar_mod_list; + mutable std::shared_ptr core_mod_list; + mutable std::shared_ptr loader_mod_list; + mutable std::shared_ptr texture_pack_list; protected slots: diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index a6dae1b4..30f781ab 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -409,7 +409,7 @@ void LegacyUpdate::ModTheJar() } // Get the mod list - auto modList = inst->jarModList(); + auto modList = inst->getJarMods(); QFileInfo runnableJar(inst->runnableJar()); QFileInfo baseJar(inst->baseJar()); @@ -421,7 +421,7 @@ void LegacyUpdate::ModTheJar() // yes, this can happen if the instance only has the runnable jar and not the base jar // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe // because that's not something mmc4 guarantees - if (runnableJar.isFile() && !baseJar.exists() && modList->empty()) + if (runnableJar.isFile() && !baseJar.exists() && modList.empty()) { inst->setShouldRebuild(false); emitSucceeded(); @@ -452,14 +452,12 @@ void LegacyUpdate::ModTheJar() return; } - // TaskStep(); // STEP 1 setStatus(tr("Installing mods: Opening minecraft.jar ...")); - const auto & mods = modList->allMods(); QString outputJarPath = runnableJar.filePath(); QString inputJarPath = baseJar.filePath(); - if(!JarUtils::createModdedJar(inputJarPath, outputJarPath, mods)) + if(!JarUtils::createModdedJar(inputJarPath, outputJarPath, modList)) { emitFailed(tr("Failed to create the custom Minecraft jar file.")); return; diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h index f840e76c..78e456a1 100644 --- a/logic/LegacyUpdate.h +++ b/logic/LegacyUpdate.h @@ -21,7 +21,7 @@ #include "logic/net/NetJob.h" #include "logic/tasks/Task.h" -#include "logic/VersionFilterData.h" +#include "logic/minecraft/VersionFilterData.h" class MinecraftVersion; class BaseInstance; diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp deleted file mode 100644 index 92aae69f..00000000 --- a/logic/MinecraftProcess.cpp +++ /dev/null @@ -1,578 +0,0 @@ -/* Copyright 2013-2015 MultiMC Contributors - * - * Authors: Orochimarufan - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "MultiMC.h" -#include "BuildConfig.h" - -#include "MinecraftProcess.h" - -#include -#include -#include -#include -#include -#include - -#include "BaseInstance.h" - -#include "osutils.h" -#include "pathutils.h" -#include "cmdutils.h" - -#define IBUS "@im=ibus" - -// constructor -MinecraftProcess::MinecraftProcess(InstancePtr inst) : m_instance(inst) -{ - connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), - SLOT(finish(int, QProcess::ExitStatus))); - - // prepare the process environment - QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); - - QProcessEnvironment env; - - QStringList ignored = - { - "JAVA_ARGS", - "CLASSPATH", - "CONFIGPATH", - "JAVA_HOME", - "JRE_HOME", - "_JAVA_OPTIONS", - "JAVA_OPTIONS", - "JAVA_TOOL_OPTIONS" - }; - for(auto key: rawenv.keys()) - { - auto value = rawenv.value(key); - // filter out dangerous java crap - if(ignored.contains(key)) - { - QLOG_INFO() << "Env: ignoring" << key << value; - continue; - } - // filter MultiMC-related things - if(key.startsWith("QT_")) - { - QLOG_INFO() << "Env: ignoring" << key << value; - continue; - } -#ifdef LINUX - // Do not pass LD_* variables to java. They were intended for MultiMC - if(key.startsWith("LD_")) - { - QLOG_INFO() << "Env: ignoring" << key << value; - continue; - } - // Strip IBus - // IBus is a Linux IME framework. For some reason, it breaks MC? - if (key == "XMODIFIERS" && value.contains(IBUS)) - { - QString save = value; - value.replace(IBUS, ""); - QLOG_INFO() << "Env: stripped" << IBUS << "from" << save << ":" << value; - } -#endif - QLOG_INFO() << "Env: " << key << value; - env.insert(key, value); - } -#ifdef LINUX - // HACK: Workaround for QTBUG-42500 - env.insert("LD_LIBRARY_PATH", ""); -#endif - - // export some infos - auto variables = getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - env.insert(it.key(), it.value()); - } - - this->setProcessEnvironment(env); - m_prepostlaunchprocess.setProcessEnvironment(env); - - // std channels - connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr())); - connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut())); - - // Log prepost launch command output (can be disabled.) - if (m_instance->settings().get("LogPrePostOutput").toBool()) - { - connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardError, this, - &MinecraftProcess::on_prepost_stdErr); - connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput, this, - &MinecraftProcess::on_prepost_stdOut); - } - - // a process has been constructed for the instance. It is running from MultiMC POV - m_instance->setRunning(true); -} - -void MinecraftProcess::setWorkdir(QString path) -{ - QDir mcDir(path); - this->setWorkingDirectory(mcDir.absolutePath()); - m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); -} - -QString MinecraftProcess::censorPrivateInfo(QString in) -{ - if (!m_session) - return in; - - if (m_session->session != "-") - in.replace(m_session->session, ""); - in.replace(m_session->access_token, ""); - in.replace(m_session->client_token, ""); - in.replace(m_session->uuid, ""); - in.replace(m_session->player_name, ""); - - auto i = m_session->u.properties.begin(); - while (i != m_session->u.properties.end()) - { - in.replace(i.value(), "<" + i.key().toUpper() + ">"); - ++i; - } - - return in; -} - -// console window -MessageLevel::Enum MinecraftProcess::guessLevel(const QString &line, MessageLevel::Enum level) -{ - QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); - auto match = re.match(line); - if(match.hasMatch()) - { - // New style logs from log4j - QString timestamp = match.captured("timestamp"); - QString levelStr = match.captured("level"); - if(levelStr == "INFO") - level = MessageLevel::Message; - if(levelStr == "WARN") - level = MessageLevel::Warning; - if(levelStr == "ERROR") - level = MessageLevel::Error; - if(levelStr == "FATAL") - level = MessageLevel::Fatal; - if(levelStr == "TRACE" || levelStr == "DEBUG") - level = MessageLevel::Debug; - } - else - { - // Old style forge logs - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || - line.contains("[FINER]") || line.contains("[FINEST]")) - level = MessageLevel::Message; - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - if (line.contains("[WARNING]")) - level = MessageLevel::Warning; - if (line.contains("[DEBUG]")) - level = MessageLevel::Debug; - } - if (line.contains("overwriting existing")) - return MessageLevel::Fatal; - if (line.contains("Exception in thread") || line.contains(QRegularExpression("\\s+at "))) - return MessageLevel::Error; - return level; -} - -MessageLevel::Enum MinecraftProcess::getLevel(const QString &levelName) -{ - if (levelName == "MultiMC") - return MessageLevel::MultiMC; - else if (levelName == "Debug") - return MessageLevel::Debug; - else if (levelName == "Info") - return MessageLevel::Info; - else if (levelName == "Message") - return MessageLevel::Message; - else if (levelName == "Warning") - return MessageLevel::Warning; - else if (levelName == "Error") - return MessageLevel::Error; - else if (levelName == "Fatal") - return MessageLevel::Fatal; - // Skip PrePost, it's not exposed to !![]! - else - return MessageLevel::Message; -} - -void MinecraftProcess::logOutput(const QStringList &lines, MessageLevel::Enum defaultLevel, - bool guessLevel, bool censor) -{ - for (int i = 0; i < lines.size(); ++i) - logOutput(lines[i], defaultLevel, guessLevel, censor); -} - -void MinecraftProcess::logOutput(QString line, MessageLevel::Enum defaultLevel, bool guessLevel, - bool censor) -{ - MessageLevel::Enum level = defaultLevel; - - //FIXME: make more flexible in the future - if(line.contains("ignoring option PermSize")) - { - return; - } - - // Level prefix - int endmark = line.indexOf("]!"); - if (line.startsWith("!![") && endmark != -1) - { - level = getLevel(line.left(endmark).mid(3)); - line = line.mid(endmark + 2); - } - // Guess level - else if (guessLevel) - level = this->guessLevel(line, defaultLevel); - - if (censor) - line = censorPrivateInfo(line); - - emit log(line, level); -} - -void MinecraftProcess::on_stdErr() -{ - QByteArray data = readAllStandardError(); - QString str = m_err_leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - m_err_leftover = lines.takeLast(); - - logOutput(lines, MessageLevel::Error); -} - -void MinecraftProcess::on_stdOut() -{ - QByteArray data = readAllStandardOutput(); - QString str = m_out_leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - m_out_leftover = lines.takeLast(); - - logOutput(lines); -} - -void MinecraftProcess::on_prepost_stdErr() -{ - QByteArray data = m_prepostlaunchprocess.readAllStandardError(); - QString str = m_err_leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - m_err_leftover = lines.takeLast(); - - logOutput(lines, MessageLevel::PrePost, false, false); -} - -void MinecraftProcess::on_prepost_stdOut() -{ - QByteArray data = m_prepostlaunchprocess.readAllStandardOutput(); - QString str = m_out_leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - m_out_leftover = lines.takeLast(); - - logOutput(lines, MessageLevel::PrePost, false, false); -} - -// exit handler -void MinecraftProcess::finish(int code, ExitStatus status) -{ - // Flush console window - if (!m_err_leftover.isEmpty()) - { - logOutput(m_err_leftover, MessageLevel::Error); - m_err_leftover.clear(); - } - if (!m_out_leftover.isEmpty()) - { - logOutput(m_out_leftover); - m_out_leftover.clear(); - } - - if (!killed) - { - if (status == NormalExit) - { - //: Message displayed on instance exit - emit log(tr("Minecraft exited with exitcode %1.").arg(code)); - } - else - { - //: Message displayed on instance crashed - emit log(tr("Minecraft crashed with exitcode %1.").arg(code)); - } - } - else - { - //: Message displayed after the instance exits due to kill request - emit log(tr("Minecraft was killed by user."), MessageLevel::Error); - } - - m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); - - // run post-exit - postLaunch(); - m_instance->cleanupAfterRun(); - // no longer running... - m_instance->setRunning(false); - emit ended(m_instance, code, status); -} - -void MinecraftProcess::killMinecraft() -{ - killed = true; - if (m_prepostlaunchprocess.state() == QProcess::Running) - { - m_prepostlaunchprocess.kill(); - } - else - { - kill(); - } -} - -bool MinecraftProcess::preLaunch() -{ - QString prelaunch_cmd = m_instance->settings().get("PreLaunchCommand").toString(); - if (!prelaunch_cmd.isEmpty()) - { - prelaunch_cmd = substituteVariables(prelaunch_cmd); - // Launch - emit log(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd)); - m_prepostlaunchprocess.start(prelaunch_cmd); - if (!waitForPrePost()) - { - emit log(tr("The command failed to start"), MessageLevel::Fatal); - return false; - } - // Flush console window - if (!m_err_leftover.isEmpty()) - { - logOutput(m_err_leftover, MessageLevel::PrePost); - m_err_leftover.clear(); - } - if (!m_out_leftover.isEmpty()) - { - logOutput(m_out_leftover, MessageLevel::PrePost); - m_out_leftover.clear(); - } - // Process return values - if (m_prepostlaunchprocess.exitStatus() != NormalExit) - { - emit log(tr("Pre-Launch command failed with code %1.\n\n") - .arg(m_prepostlaunchprocess.exitCode()), - MessageLevel::Fatal); - m_instance->cleanupAfterRun(); - emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), - m_prepostlaunchprocess.exitStatus()); - // not running, failed - m_instance->setRunning(false); - return false; - } - else - emit log(tr("Pre-Launch command ran successfully.\n\n")); - - return m_instance->reload(); - } - return true; -} -bool MinecraftProcess::postLaunch() -{ - QString postlaunch_cmd = m_instance->settings().get("PostExitCommand").toString(); - if (!postlaunch_cmd.isEmpty()) - { - postlaunch_cmd = substituteVariables(postlaunch_cmd); - emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd)); - m_prepostlaunchprocess.start(postlaunch_cmd); - if (!waitForPrePost()) - { - return false; - } - // Flush console window - if (!m_err_leftover.isEmpty()) - { - logOutput(m_err_leftover, MessageLevel::PrePost); - m_err_leftover.clear(); - } - if (!m_out_leftover.isEmpty()) - { - logOutput(m_out_leftover, MessageLevel::PrePost); - m_out_leftover.clear(); - } - if (m_prepostlaunchprocess.exitStatus() != NormalExit) - { - emit log(tr("Post-Launch command failed with code %1.\n\n") - .arg(m_prepostlaunchprocess.exitCode()), - MessageLevel::Error); - emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), - m_prepostlaunchprocess.exitStatus()); - // not running, failed - m_instance->setRunning(false); - } - else - emit log(tr("Post-Launch command ran successfully.\n\n")); - - return m_instance->reload(); - } - return true; -} - -bool MinecraftProcess::waitForPrePost() -{ - if (!m_prepostlaunchprocess.waitForStarted()) - return false; - QEventLoop eventLoop; - auto finisher = [this, &eventLoop](QProcess::ProcessState state) - { - if (state == QProcess::NotRunning) - { - eventLoop.quit(); - } - }; - auto connection = connect(&m_prepostlaunchprocess, &QProcess::stateChanged, finisher); - int ret = eventLoop.exec(); - disconnect(connection); - return ret == 0; -} - -QMap MinecraftProcess::getVariables() const -{ - QMap out; - out.insert("INST_NAME", m_instance->name()); - out.insert("INST_ID", m_instance->id()); - out.insert("INST_DIR", QDir(m_instance->instanceRoot()).absolutePath()); - out.insert("INST_MC_DIR", QDir(m_instance->minecraftRoot()).absolutePath()); - out.insert("INST_JAVA", m_instance->settings().get("JavaPath").toString()); - out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); - return out; -} -QString MinecraftProcess::substituteVariables(const QString &cmd) const -{ - QString out = cmd; - auto variables = getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - out.replace("$" + it.key(), it.value()); - } - auto env = QProcessEnvironment::systemEnvironment(); - for (auto var : env.keys()) - { - out.replace("$" + var, env.value(var)); - } - return out; -} - -QStringList MinecraftProcess::javaArguments() const -{ - QStringList args; - - // custom args go first. we want to override them if we have our own here. - args.append(m_instance->extraArguments()); - -// OSX dock icon and name -#ifdef OSX - args << "-Xdock:icon=icon.png"; - args << QString("-Xdock:name=\"%1\"").arg(m_instance->windowTitle()); -#endif - -// HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 -#ifdef Q_OS_WIN32 - args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" - "minecraft.exe.heapdump"); -#endif - - args << QString("-Xms%1m").arg(m_instance->settings().get("MinMemAlloc").toInt()); - args << QString("-Xmx%1m").arg(m_instance->settings().get("MaxMemAlloc").toInt()); - auto permgen = m_instance->settings().get("PermGen").toInt(); - if (permgen != 64) - { - args << QString("-XX:PermSize=%1m").arg(permgen); - } - args << "-Duser.language=en"; - if (!m_nativeFolder.isEmpty()) - args << QString("-Djava.library.path=%1").arg(m_nativeFolder); - args << "-jar" << PathCombine(MMC->bin(), "jars", "NewLaunch.jar"); - - return args; -} - -void MinecraftProcess::arm() -{ - emit log("MultiMC version: " + BuildConfig.printableVersionString() + "\n\n"); - emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n"); - - if (!preLaunch()) - { - emit ended(m_instance, 1, QProcess::CrashExit); - return; - } - - m_instance->setLastLaunch(); - - QStringList args = javaArguments(); - - QString JavaPath = m_instance->settings().get("JavaPath").toString(); - emit log("Java path is:\n" + JavaPath + "\n\n"); - QString allArgs = args.join(", "); - emit log("Java Arguments:\n[" + censorPrivateInfo(allArgs) + "]\n\n"); - - auto realJavaPath = QStandardPaths::findExecutable(JavaPath); - if (realJavaPath.isEmpty()) - { - emit log(tr("The java binary \"%1\" couldn't be found. You may have to set up java " - "if Minecraft fails to launch.").arg(JavaPath), - MessageLevel::Warning); - } - - // instantiate the launcher part - start(JavaPath, args); - if (!waitForStarted()) - { - //: Error message displayed if instace can't start - emit log(tr("Could not launch minecraft!"), MessageLevel::Error); - m_instance->cleanupAfterRun(); - emit launch_failed(m_instance); - // not running, failed - m_instance->setRunning(false); - return; - } - // send the launch script to the launcher part - QByteArray bytes = launchScript.toUtf8(); - writeData(bytes.constData(), bytes.length()); -} - -void MinecraftProcess::launch() -{ - QString launchString("launch\n"); - QByteArray bytes = launchString.toUtf8(); - writeData(bytes.constData(), bytes.length()); -} - -void MinecraftProcess::abort() -{ - QString launchString("abort\n"); - QByteArray bytes = launchString.toUtf8(); - writeData(bytes.constData(), bytes.length()); -} diff --git a/logic/MinecraftProcess.h b/logic/MinecraftProcess.h deleted file mode 100644 index d74363f6..00000000 --- a/logic/MinecraftProcess.h +++ /dev/null @@ -1,165 +0,0 @@ -/* Copyright 2013-2015 MultiMC Contributors - * - * Authors: Orochimarufan - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "BaseInstance.h" - -/** - * @brief the MessageLevel Enum - * defines what level a message is - */ -namespace MessageLevel -{ -enum Enum -{ - MultiMC, /**< MultiMC Messages */ - Debug, /**< Debug Messages */ - Info, /**< Info Messages */ - Message, /**< Standard Messages */ - Warning, /**< Warnings */ - Error, /**< Errors */ - Fatal, /**< Fatal Errors */ - PrePost, /**< Pre/Post Launch command output */ -}; -} - -/** - * @file data/minecraftprocess.h - * @brief The MinecraftProcess class - */ -class MinecraftProcess : public QProcess -{ - Q_OBJECT -public: - /** - * @brief MinecraftProcess constructor - * @param inst the Instance pointer to launch - */ - MinecraftProcess(InstancePtr inst); - - virtual ~MinecraftProcess() - { - - }; - - /** - * @brief start the launcher part with the provided launch script - */ - void arm(); - - /** - * @brief launch the armed instance! - */ - void launch(); - - /** - * @brief abort launch! - */ - void abort(); - - InstancePtr instance() - { - return m_instance; - } - - void setWorkdir(QString path); - - void setLaunchScript(QString script) - { - launchScript = script; - } - - void setNativeFolder(QString natives) - { - m_nativeFolder = natives; - } - - void killMinecraft(); - - inline void setLogin(AuthSessionPtr session) - { - m_session = session; - } - -signals: - /** - * @brief emitted when Minecraft immediately fails to run - */ - void launch_failed(InstancePtr); - - /** - * @brief emitted when the PreLaunchCommand fails - */ - void prelaunch_failed(InstancePtr, int code, QProcess::ExitStatus status); - - /** - * @brief emitted when the PostLaunchCommand fails - */ - void postlaunch_failed(InstancePtr, int code, QProcess::ExitStatus status); - - /** - * @brief emitted when mc has finished and the PostLaunchCommand was run - */ - void ended(InstancePtr, int code, QProcess::ExitStatus status); - - /** - * @brief emitted when we want to log something - * @param text the text to log - * @param level the level to log at - */ - void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC); - -protected: - InstancePtr m_instance; - QString m_err_leftover; - QString m_out_leftover; - QProcess m_prepostlaunchprocess; - bool killed = false; - AuthSessionPtr m_session; - QString launchScript; - QString m_nativeFolder; - - bool preLaunch(); - bool postLaunch(); - bool waitForPrePost(); - QMap getVariables() const; - QString substituteVariables(const QString &cmd) const; - - QStringList javaArguments() const; - -protected -slots: - void finish(int, QProcess::ExitStatus status); - void on_stdErr(); - void on_stdOut(); - void on_prepost_stdOut(); - void on_prepost_stdErr(); - void logOutput(const QStringList &lines, - MessageLevel::Enum defaultLevel = MessageLevel::Message, - bool guessLevel = true, bool censor = true); - void logOutput(QString line, - MessageLevel::Enum defaultLevel = MessageLevel::Message, - bool guessLevel = true, bool censor = true); - -private: - QString censorPrivateInfo(QString in); - MessageLevel::Enum guessLevel(const QString &message, MessageLevel::Enum defaultLevel); - MessageLevel::Enum getLevel(const QString &levelName); -}; diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp deleted file mode 100644 index bf1361dd..00000000 --- a/logic/OneSixFTBInstance.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "OneSixFTBInstance.h" - -#include "logic/minecraft/InstanceVersion.h" -#include "logic/minecraft/OneSixLibrary.h" -#include "logic/minecraft/VersionBuilder.h" -#include "tasks/SequentialTask.h" -#include "forge/ForgeInstaller.h" -#include "forge/ForgeVersionList.h" -#include "MultiMC.h" -#include "pathutils.h" - -OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : - OneSixInstance(rootDir, settings, parent) -{ -} - -void OneSixFTBInstance::copy(const QDir &newDir) -{ - QStringList libraryNames; - // create patch file - { - QLOG_DEBUG() << "Creating patch file for FTB instance..."; - QFile f(minecraftRoot() + "/pack.json"); - if (!f.open(QFile::ReadOnly)) - { - QLOG_ERROR() << "Couldn't open" << f.fileName() << ":" << f.errorString(); - return; - } - QJsonObject root = QJsonDocument::fromJson(f.readAll()).object(); - QJsonArray libs = root.value("libraries").toArray(); - QJsonArray outLibs; - for (auto lib : libs) - { - QJsonObject libObj = lib.toObject(); - libObj.insert("MMC-hint", QString("local")); - libObj.insert("insert", QString("prepend")); - libraryNames.append(libObj.value("name").toString()); - outLibs.append(libObj); - } - root.remove("libraries"); - root.remove("id"); - - // HACK HACK HACK HACK - // A workaround for a problem in MultiMC, triggered by a historical problem in FTB, - // triggered by Mojang getting their library versions wrong in 1.7.10 - if(intendedVersionId() == "1.7.10") - { - auto insert = [&outLibs, &libraryNames](QString name) - { - QJsonObject libObj; - libObj.insert("insert", QString("replace")); - libObj.insert("name", name); - libraryNames.push_back(name); - outLibs.prepend(libObj); - }; - insert("com.google.guava:guava:16.0"); - insert("org.apache.commons:commons-lang3:3.2.1"); - } - root.insert("+libraries", outLibs); - root.insert("order", 1); - root.insert("fileId", QString("org.multimc.ftb.pack.json")); - root.insert("name", name()); - root.insert("mcVersion", intendedVersionId()); - root.insert("version", intendedVersionId()); - ensureFilePathExists(newDir.absoluteFilePath("patches/ftb.json")); - QFile out(newDir.absoluteFilePath("patches/ftb.json")); - if (!out.open(QFile::WriteOnly | QFile::Truncate)) - { - QLOG_ERROR() << "Couldn't open" << out.fileName() << ":" << out.errorString(); - return; - } - out.write(QJsonDocument(root).toJson()); - } - // copy libraries - { - QLOG_DEBUG() << "Copying FTB libraries"; - for (auto library : libraryNames) - { - OneSixLibrary *lib = new OneSixLibrary(library); - const QString out = QDir::current().absoluteFilePath("libraries/" + lib->storagePath()); - if (QFile::exists(out)) - { - continue; - } - if (!ensureFilePathExists(out)) - { - QLOG_ERROR() << "Couldn't create folder structure for" << out; - } - if (!QFile::copy(librariesPath().absoluteFilePath(lib->storagePath()), out)) - { - QLOG_ERROR() << "Couldn't copy" << lib->rawName(); - } - } - } -} - -QString OneSixFTBInstance::id() const -{ - return "FTB/" + BaseInstance::id(); -} - -QDir OneSixFTBInstance::librariesPath() const -{ - return QDir(MMC->settings()->get("FTBRoot").toString() + "/libraries"); -} - -QDir OneSixFTBInstance::versionsPath() const -{ - return QDir(MMC->settings()->get("FTBRoot").toString() + "/versions"); -} - -QStringList OneSixFTBInstance::externalPatches() const -{ - return QStringList() << versionsPath().absoluteFilePath(intendedVersionId() + "/" + intendedVersionId() + ".json") - << minecraftRoot() + "/pack.json"; -} - -bool OneSixFTBInstance::providesVersionFile() const -{ - return true; -} - -QString OneSixFTBInstance::getStatusbarDescription() -{ - if (flags() & VersionBrokenFlag) - { - return "OneSix FTB: " + intendedVersionId() + " (broken)"; - } - return "OneSix FTB: " + intendedVersionId(); -} - -std::shared_ptr OneSixFTBInstance::doUpdate() -{ - return OneSixInstance::doUpdate(); -} - -#include "OneSixFTBInstance.moc" diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h deleted file mode 100644 index 1f9df8ab..00000000 --- a/logic/OneSixFTBInstance.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "OneSixInstance.h" - -class OneSixLibrary; - -class OneSixFTBInstance : public OneSixInstance -{ - Q_OBJECT -public: - explicit OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent = 0); - virtual ~OneSixFTBInstance(){}; - - void copy(const QDir &newDir) override; - - virtual QString getStatusbarDescription(); - - virtual std::shared_ptr doUpdate() override; - - virtual QString id() const; - - QDir librariesPath() const override; - QDir versionsPath() const override; - QStringList externalPatches() const override; - bool providesVersionFile() const override; - -private: - std::shared_ptr m_forge; -}; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 58b1359b..15dd0585 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -22,12 +22,13 @@ #include "logic/OneSixInstance.h" #include "logic/OneSixUpdate.h" -#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/MinecraftProfile.h" #include "minecraft/VersionBuildError.h" +#include "logic/minecraft/MinecraftProcess.h" +#include "minecraft/OneSixProfileStrategy.h" #include "logic/assets/AssetsUtils.h" -#include "icons/IconList.h" -#include "logic/MinecraftProcess.h" +#include "logic/icons/IconList.h" #include "gui/pagedialog/PageDialog.h" #include "gui/pages/VersionPage.h" #include "gui/pages/ModFolderPage.h" @@ -39,24 +40,22 @@ #include "gui/pages/OtherLogsPage.h" OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) - : BaseInstance(rootDir, settings, parent) + : MinecraftInstance(rootDir, settings, parent) { m_settings->registerSetting("IntendedVersion", ""); - version.reset(new InstanceVersion(this, this)); } void OneSixInstance::init() { - try - { - reloadVersion(); - } - catch (MMCError &e) - { - QLOG_ERROR() << "Caught exception on instance init: " << e.cause(); - } + createProfile(); +} + +void OneSixInstance::createProfile() +{ + m_version.reset(new MinecraftProfile(new OneSixProfileStrategy(this))); } + QList OneSixInstance::getPages() { QList values; @@ -68,9 +67,9 @@ QList OneSixInstance::getPages() values.append(new ResourcePackPage(this)); values.append(new TexturePackPage(this)); values.append(new NotesPage(this)); - values.append(new ScreenshotsPage(this)); + values.append(new ScreenshotsPage(PathCombine(minecraftRoot(), "screenshots"))); values.append(new InstanceSettingsPage(this)); - values.append(new OtherLogsPage(this)); + values.append(new OtherLogsPage(minecraftRoot())); return values; } @@ -81,7 +80,7 @@ QString OneSixInstance::dialogTitle() QSet OneSixInstance::traits() { - auto version = getFullVersion(); + auto version = getMinecraftProfile(); if (!version) { return {"version-incomplete"}; @@ -119,70 +118,10 @@ QString replaceTokensIn(QString text, QMap with) return result; } -QDir OneSixInstance::reconstructAssets(std::shared_ptr version) -{ - QDir assetsDir = QDir("assets/"); - QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes")); - QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects")); - QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual")); - - QString indexPath = PathCombine(indexDir.path(), version->assets + ".json"); - QFile indexFile(indexPath); - QDir virtualRoot(PathCombine(virtualDir.path(), version->assets)); - - if (!indexFile.exists()) - { - QLOG_ERROR() << "No assets index file" << indexPath << "; can't reconstruct assets"; - return virtualRoot; - } - - QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() - << objectDir.path() << virtualDir.path() << virtualRoot.path(); - - AssetsIndex index; - bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index); - - if (loadAssetsIndex && index.isVirtual) - { - QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path(); - - for (QString map : index.objects.keys()) - { - AssetObject asset_object = index.objects.value(map); - QString target_path = PathCombine(virtualRoot.path(), map); - QFile target(target_path); - - QString tlk = asset_object.hash.left(2); - - QString original_path = - PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); - QFile original(original_path); - if (!original.exists()) - continue; - if (!target.exists()) - { - QFileInfo info(target_path); - QDir target_dir = info.dir(); - // QLOG_DEBUG() << target_dir; - if (!target_dir.exists()) - QDir("").mkpath(target_dir.path()); - - bool couldCopy = original.copy(target_path); - QLOG_DEBUG() << " Copying" << original_path << "to" << target_path - << QString::number(couldCopy); // << original.errorString(); - } - } - - // TODO: Write last used time to virtualRoot/.lastused - } - - return virtualRoot; -} - QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) { - QString args_pattern = version->minecraftArguments; - for (auto tweaker : version->tweakers) + QString args_pattern = m_version->minecraftArguments; + for (auto tweaker : m_version->tweakers) { args_pattern += " --tweakClass " + tweaker; } @@ -197,18 +136,18 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) // these do nothing and are stupid. token_mapping["profile_name"] = name(); - token_mapping["version_name"] = version->id; + token_mapping["version_name"] = m_version->id; QString absRootDir = QDir(minecraftRoot()).absolutePath(); token_mapping["game_directory"] = absRootDir; QString absAssetsDir = QDir("assets/").absolutePath(); - token_mapping["game_assets"] = reconstructAssets(version).absolutePath(); + token_mapping["game_assets"] = AssetsUtils::reconstructAssets(m_version->assets).absolutePath(); token_mapping["user_properties"] = session->serializeUserProperties(); token_mapping["user_type"] = session->user_type; // 1.7.3+ assets tokens token_mapping["assets_root"] = absAssetsDir; - token_mapping["assets_index_name"] = version->assets; + token_mapping["assets_index_name"] = m_version->assets; QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); for (int i = 0; i < parts.length(); i++) @@ -218,40 +157,41 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) return parts; } -bool OneSixInstance::prepareForLaunch(AuthSessionPtr session, QString &launchScript) +BaseProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session) { - + QString launchScript; QIcon icon = MMC->icons()->getIcon(iconKey()); auto pixmap = icon.pixmap(128, 128); pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); - if (!version) + if (!m_version) return nullptr; // libraries and class path. { - auto libs = version->getActiveNormalLibs(); + auto libs = m_version->getActiveNormalLibs(); for (auto lib : libs) { launchScript += "cp " + librariesPath().absoluteFilePath(lib->storagePath()) + "\n"; } - if (version->hasJarMods()) + auto jarMods = getJarMods(); + if (!jarMods.isEmpty()) { launchScript += "cp " + QDir(instanceRoot()).absoluteFilePath("temp.jar") + "\n"; } else { - QString relpath = version->id + "/" + version->id + ".jar"; + QString relpath = m_version->id + "/" + m_version->id + ".jar"; launchScript += "cp " + versionsPath().absoluteFilePath(relpath) + "\n"; } } - if (!version->mainClass.isEmpty()) + if (!m_version->mainClass.isEmpty()) { - launchScript += "mainClass " + version->mainClass + "\n"; + launchScript += "mainClass " + m_version->mainClass + "\n"; } - if (!version->appletClass.isEmpty()) + if (!m_version->appletClass.isEmpty()) { - launchScript += "appletClass " + version->appletClass + "\n"; + launchScript += "appletClass " + m_version->appletClass + "\n"; } // generic minecraft params @@ -282,7 +222,7 @@ bool OneSixInstance::prepareForLaunch(AuthSessionPtr session, QString &launchScr // native libraries (mostly LWJGL) { QDir natives_dir(PathCombine(instanceRoot(), "natives/")); - for (auto native : version->getActiveNativeLibs()) + for (auto native : m_version->getActiveNativeLibs()) { QFileInfo finfo(PathCombine("libraries", native->storagePath())); launchScript += "ext " + finfo.absoluteFilePath() + "\n"; @@ -291,12 +231,17 @@ bool OneSixInstance::prepareForLaunch(AuthSessionPtr session, QString &launchScr } // traits. including legacyLaunch and others ;) - for (auto trait : version->traits) + for (auto trait : m_version->traits) { launchScript += "traits " + trait + "\n"; } launchScript += "launcher onesix\n"; - return true; + + auto process = MinecraftProcess::create(std::dynamic_pointer_cast(getSharedPtr())); + process->setLaunchScript(launchScript); + process->setWorkdir(minecraftRoot()); + process->setLogin(session); + return process; } void OneSixInstance::cleanupAfterRun() @@ -306,54 +251,68 @@ void OneSixInstance::cleanupAfterRun() dir.removeRecursively(); } -std::shared_ptr OneSixInstance::loaderModList() +std::shared_ptr OneSixInstance::loaderModList() const { - if (!loader_mod_list) + if (!m_loader_mod_list) { - loader_mod_list.reset(new ModList(loaderModsDir())); + m_loader_mod_list.reset(new ModList(loaderModsDir())); } - loader_mod_list->update(); - return loader_mod_list; + m_loader_mod_list->update(); + return m_loader_mod_list; } -std::shared_ptr OneSixInstance::coreModList() +std::shared_ptr OneSixInstance::coreModList() const { - if (!core_mod_list) + if (!m_core_mod_list) { - core_mod_list.reset(new ModList(coreModsDir())); + m_core_mod_list.reset(new ModList(coreModsDir())); } - core_mod_list->update(); - return core_mod_list; + m_core_mod_list->update(); + return m_core_mod_list; } -std::shared_ptr OneSixInstance::resourcePackList() +std::shared_ptr OneSixInstance::resourcePackList() const { - if (!resource_pack_list) + if (!m_resource_pack_list) { - resource_pack_list.reset(new ModList(resourcePacksDir())); + m_resource_pack_list.reset(new ModList(resourcePacksDir())); } - resource_pack_list->update(); - return resource_pack_list; + m_resource_pack_list->update(); + return m_resource_pack_list; } -std::shared_ptr OneSixInstance::texturePackList() +std::shared_ptr OneSixInstance::texturePackList() const { - if (!texture_pack_list) + if (!m_texture_pack_list) { - texture_pack_list.reset(new ModList(texturePacksDir())); + m_texture_pack_list.reset(new ModList(texturePacksDir())); } - texture_pack_list->update(); - return texture_pack_list; + m_texture_pack_list->update(); + return m_texture_pack_list; } bool OneSixInstance::setIntendedVersionId(QString version) { settings().set("IntendedVersion", version); - QFile::remove(PathCombine(instanceRoot(), "version.json")); - clearVersion(); + if(getMinecraftProfile()) + { + clearProfile(); + } return true; } +QList< Mod > OneSixInstance::getJarMods() const +{ + QList mods; + for (auto jarmod : m_version->jarMods) + { + QString filePath = jarmodsPath().absoluteFilePath(jarmod->name); + mods.push_back(Mod(QFileInfo(filePath))); + } + return mods; +} + + QString OneSixInstance::intendedVersionId() const { return settings().get("IntendedVersion").toString(); @@ -368,35 +327,16 @@ bool OneSixInstance::shouldUpdate() const return true; } -bool OneSixInstance::versionIsCustom() -{ - if (version) - { - return !version->isVanilla(); - } - return false; -} - -bool OneSixInstance::versionIsFTBPack() -{ - if (version) - { - return version->hasFtbPack(); - } - return false; -} - QString OneSixInstance::currentVersionId() const { return intendedVersionId(); } -void OneSixInstance::reloadVersion() +void OneSixInstance::reloadProfile() { - try { - version->reload(externalPatches()); + m_version->reload(); unsetFlag(VersionBrokenFlag); emit versionReloaded(); } @@ -405,7 +345,7 @@ void OneSixInstance::reloadVersion() } catch (MMCError &error) { - version->clear(); + m_version->clear(); setFlag(VersionBrokenFlag); // TODO: rethrow to show some error message(s)? emit versionReloaded(); @@ -413,24 +353,20 @@ void OneSixInstance::reloadVersion() } } -void OneSixInstance::clearVersion() +void OneSixInstance::clearProfile() { - version->clear(); + m_version->clear(); emit versionReloaded(); } -std::shared_ptr OneSixInstance::getFullVersion() const +std::shared_ptr OneSixInstance::getMinecraftProfile() const { - return version; + return m_version; } QString OneSixInstance::getStatusbarDescription() { QStringList traits; - if (versionIsCustom()) - { - traits.append(tr("custom")); - } if (flags() & VersionBrokenFlag) { traits.append(tr("broken")); @@ -461,11 +397,6 @@ QDir OneSixInstance::versionsPath() const return QDir::current().absoluteFilePath("versions"); } -QStringList OneSixInstance::externalPatches() const -{ - return QStringList(); -} - bool OneSixInstance::providesVersionFile() const { return false; @@ -477,7 +408,7 @@ bool OneSixInstance::reload() { try { - reloadVersion(); + reloadProfile(); return true; } catch (...) @@ -526,10 +457,11 @@ QString OneSixInstance::libDir() const QStringList OneSixInstance::extraArguments() const { auto list = BaseInstance::extraArguments(); - auto version = getFullVersion(); + auto version = getMinecraftProfile(); if (!version) return list; - if (version->hasJarMods()) + auto jarMods = getJarMods(); + if (!jarMods.isEmpty()) { list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", "-Dfml.ignorePatchDiscrepancies=true"}); diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index ddb296ac..d6593d1b 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -15,13 +15,13 @@ #pragma once -#include "BaseInstance.h" +#include "logic/minecraft/MinecraftInstance.h" -#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/MinecraftProfile.h" #include "logic/ModList.h" #include "gui/pages/BasePageProvider.h" -class OneSixInstance : public BaseInstance, public BasePageProvider +class OneSixInstance : public MinecraftInstance, public BasePageProvider { Q_OBJECT public: @@ -29,17 +29,19 @@ public: QObject *parent = 0); virtual ~OneSixInstance(){}; - virtual void init() override; + virtual void init(); ////// Edit Instance Dialog stuff ////// virtual QList getPages(); virtual QString dialogTitle(); ////// Mod Lists ////// - std::shared_ptr loaderModList(); - std::shared_ptr coreModList(); - std::shared_ptr resourcePackList() override; - std::shared_ptr texturePackList() override; + std::shared_ptr loaderModList() const; + std::shared_ptr coreModList() const; + std::shared_ptr resourcePackList() const override; + std::shared_ptr texturePackList() const override; + virtual QList getJarMods() const override; + virtual void createProfile(); virtual QSet traits(); @@ -53,7 +55,7 @@ public: virtual QString instanceConfigFolder() const override; virtual std::shared_ptr doUpdate() override; - virtual bool prepareForLaunch(AuthSessionPtr account, QString & launchScript) override; + virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override; virtual void cleanupAfterRun() override; @@ -66,30 +68,23 @@ public: virtual void setShouldUpdate(bool val) override; /** - * reload the full version json files. + * reload the profile, including version json files. * * throws various exceptions :3 */ - void reloadVersion(); + void reloadProfile(); /// clears all version information in preparation for an update - void clearVersion(); + void clearProfile(); /// get the current full version info - std::shared_ptr getFullVersion() const; - - /// is the current version original, or custom? - virtual bool versionIsCustom() override; - - /// does this instance have an FTB pack patch inside? - bool versionIsFTBPack(); + std::shared_ptr getMinecraftProfile() const; virtual QString getStatusbarDescription() override; virtual QDir jarmodsPath() const; virtual QDir librariesPath() const; virtual QDir versionsPath() const; - virtual QStringList externalPatches() const; virtual bool providesVersionFile() const; bool reload() override; @@ -103,15 +98,13 @@ signals: private: QStringList processMinecraftArgs(AuthSessionPtr account); - QDir reconstructAssets(std::shared_ptr version); protected: - std::shared_ptr version; - std::shared_ptr jar_mod_list; - std::shared_ptr loader_mod_list; - std::shared_ptr core_mod_list; - std::shared_ptr resource_pack_list; - std::shared_ptr texture_pack_list; + std::shared_ptr m_version; + mutable std::shared_ptr m_loader_mod_list; + mutable std::shared_ptr m_core_mod_list; + mutable std::shared_ptr m_resource_pack_list; + mutable std::shared_ptr m_texture_pack_list; }; Q_DECLARE_METATYPE(std::shared_ptr) diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index 5663484a..692e4ce3 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -27,7 +27,7 @@ #include "logic/BaseInstance.h" #include "logic/minecraft/MinecraftVersionList.h" -#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/MinecraftProfile.h" #include "logic/minecraft/OneSixLibrary.h" #include "logic/OneSixInstance.h" #include "logic/forge/ForgeMirrors.h" @@ -88,7 +88,7 @@ void OneSixUpdate::assetIndexStart() { setStatus(tr("Updating assets index...")); OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr version = inst->getFullVersion(); + std::shared_ptr version = inst->getMinecraftProfile(); QString assetName = version->assets; QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json"; QString localPath = assetName + ".json"; @@ -112,7 +112,7 @@ void OneSixUpdate::assetIndexFinished() AssetsIndex index; OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr version = inst->getFullVersion(); + std::shared_ptr version = inst->getMinecraftProfile(); QString assetName = version->assets; QString asset_fname = "assets/indexes/" + assetName + ".json"; @@ -177,7 +177,7 @@ void OneSixUpdate::jarlibStart() OneSixInstance *inst = (OneSixInstance *)m_inst; try { - inst->reloadVersion(); + inst->reloadProfile(); } catch (MMCError &e) { @@ -191,7 +191,7 @@ void OneSixUpdate::jarlibStart() } // Build a list of URLs that will need to be downloaded. - std::shared_ptr version = inst->getFullVersion(); + std::shared_ptr version = inst->getMinecraftProfile(); // minecraft.jar for this version { QString version_id = version->id; @@ -290,7 +290,7 @@ void OneSixUpdate::jarlibStart() void OneSixUpdate::jarlibFinished() { OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr version = inst->getFullVersion(); + std::shared_ptr version = inst->getMinecraftProfile(); // nuke obsolete stripped jar(s) if needed QString version_id = version->id; @@ -311,22 +311,16 @@ void OneSixUpdate::jarlibFinished() } } - // create stripped jar, if needed - if (version->hasJarMods()) + // create temporary modded jar, if needed + auto jarMods = inst->getJarMods(); + if(jarMods.size()) { auto sourceJarPath = m_inst->versionsPath().absoluteFilePath(version->id + "/" + version->id + ".jar"); QString localPath = version_id + "/" + version_id + ".jar"; auto metacache = MMC->metacache(); auto entry = metacache->resolveEntry("versions", localPath); QString fullJarPath = entry->getFullPath(); - //FIXME: remove need to convert to different objects here - QList mods; - for (auto jarmod : version->jarMods) - { - QString filePath = m_inst->jarmodsPath().absoluteFilePath(jarmod->name); - mods.push_back(Mod(QFileInfo(filePath))); - } - if(!JarUtils::createModdedJar(sourceJarPath, finalJarPath, mods)) + if(!JarUtils::createModdedJar(sourceJarPath, finalJarPath, jarMods)) { emitFailed(tr("Failed to create the custom Minecraft jar file.")); return; @@ -354,7 +348,7 @@ void OneSixUpdate::fmllibsStart() { // Get the mod list OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr fullversion = inst->getFullVersion(); + std::shared_ptr fullversion = inst->getMinecraftProfile(); bool forge_present = false; QString version = inst->intendedVersionId(); diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h index 79215f56..e3571e5a 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -21,7 +21,7 @@ #include "logic/net/NetJob.h" #include "logic/tasks/Task.h" -#include "logic/VersionFilterData.h" +#include "logic/minecraft/VersionFilterData.h" #include class MinecraftVersion; diff --git a/logic/VersionFilterData.cpp b/logic/VersionFilterData.cpp deleted file mode 100644 index 866d4947..00000000 --- a/logic/VersionFilterData.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "VersionFilterData.h" -#include "minecraft/ParseUtils.h" - -VersionFilterData g_VersionFilterData = VersionFilterData(); - -VersionFilterData::VersionFilterData() -{ - // 1.3.* - auto libs13 = - QList{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}}; - - fmlLibsMapping["1.3.2"] = libs13; - - // 1.4.* - auto libs14 = QList{ - {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}, - {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}}; - - fmlLibsMapping["1.4"] = libs14; - fmlLibsMapping["1.4.1"] = libs14; - fmlLibsMapping["1.4.2"] = libs14; - fmlLibsMapping["1.4.3"] = libs14; - fmlLibsMapping["1.4.4"] = libs14; - fmlLibsMapping["1.4.5"] = libs14; - fmlLibsMapping["1.4.6"] = libs14; - fmlLibsMapping["1.4.7"] = libs14; - - // 1.5 - fmlLibsMapping["1.5"] = QList{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - - // 1.5.1 - fmlLibsMapping["1.5.1"] = QList{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - - // 1.5.2 - fmlLibsMapping["1.5.2"] = QList{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - - // don't use installers for those. - forgeInstallerBlacklist = QSet({"1.5.2"}); - // these won't show up in version lists because they are extremely bad and dangerous - legacyBlacklist = QSet({"rd-160052"}); - /* - * nothing older than this will be accepted from Mojang servers - * (these versions need to be tested by us first) - */ - legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); - lwjglWhitelist = - QSet{"net.java.jinput:jinput", "net.java.jinput:jinput-platform", - "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", - "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; -} diff --git a/logic/VersionFilterData.h b/logic/VersionFilterData.h deleted file mode 100644 index 98ecb36c..00000000 --- a/logic/VersionFilterData.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include -#include -#include -#include - -struct FMLlib -{ - QString filename; - QString checksum; - bool ours; -}; - -struct VersionFilterData -{ - VersionFilterData(); - // mapping between minecraft versions and FML libraries required - QMap> fmlLibsMapping; - // set of minecraft versions for which using forge installers is blacklisted - QSet forgeInstallerBlacklist; - // set of 'legacy' versions that will not show up in the version lists. - QSet legacyBlacklist; - // no new versions below this date will be accepted from Mojang servers - QDateTime legacyCutoffDate; - // Libraries that belong to LWJGL - QSet lwjglWhitelist; -}; -extern VersionFilterData g_VersionFilterData; diff --git a/logic/assets/AssetsMigrateTask.cpp b/logic/assets/AssetsMigrateTask.cpp index 7c1f5204..9ded3cc2 100644 --- a/logic/assets/AssetsMigrateTask.cpp +++ b/logic/assets/AssetsMigrateTask.cpp @@ -5,8 +5,6 @@ #include #include #include -#include "gui/dialogs/CustomMessageBox.h" -#include AssetsMigrateTask::AssetsMigrateTask(int expected, QObject *parent) : Task(parent) diff --git a/logic/assets/AssetsUtils.cpp b/logic/assets/AssetsUtils.cpp index e418d14a..472b9589 100644 --- a/logic/assets/AssetsUtils.cpp +++ b/logic/assets/AssetsUtils.cpp @@ -22,6 +22,7 @@ #include "AssetsUtils.h" #include "MultiMC.h" +#include namespace AssetsUtils { @@ -151,4 +152,65 @@ bool loadAssetsIndexJson(QString path, AssetsIndex *index) return true; } + +QDir reconstructAssets(QString assetsId) +{ + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes")); + QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects")); + QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual")); + + QString indexPath = PathCombine(indexDir.path(), assetsId + ".json"); + QFile indexFile(indexPath); + QDir virtualRoot(PathCombine(virtualDir.path(), assetsId)); + + if (!indexFile.exists()) + { + QLOG_ERROR() << "No assets index file" << indexPath << "; can't reconstruct assets"; + return virtualRoot; + } + + QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() + << objectDir.path() << virtualDir.path() << virtualRoot.path(); + + AssetsIndex index; + bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index); + + if (loadAssetsIndex && index.isVirtual) + { + QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path(); + + for (QString map : index.objects.keys()) + { + AssetObject asset_object = index.objects.value(map); + QString target_path = PathCombine(virtualRoot.path(), map); + QFile target(target_path); + + QString tlk = asset_object.hash.left(2); + + QString original_path = + PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); + QFile original(original_path); + if (!original.exists()) + continue; + if (!target.exists()) + { + QFileInfo info(target_path); + QDir target_dir = info.dir(); + // QLOG_DEBUG() << target_dir; + if (!target_dir.exists()) + QDir("").mkpath(target_dir.path()); + + bool couldCopy = original.copy(target_path); + QLOG_DEBUG() << " Copying" << original_path << "to" << target_path + << QString::number(couldCopy); // << original.errorString(); + } + } + + // TODO: Write last used time to virtualRoot/.lastused + } + + return virtualRoot; +} + } diff --git a/logic/assets/AssetsUtils.h b/logic/assets/AssetsUtils.h index 88fa89c4..ea12136d 100644 --- a/logic/assets/AssetsUtils.h +++ b/logic/assets/AssetsUtils.h @@ -34,4 +34,6 @@ namespace AssetsUtils { bool loadAssetsIndexJson(QString file, AssetsIndex* index); int findLegacyAssets(); +/// Reconstruct a virtual assets folder for the given assets ID and return the folder +QDir reconstructAssets(QString assetsId); } diff --git a/logic/forge/ForgeInstaller.cpp b/logic/forge/ForgeInstaller.cpp index 13027ae7..01671306 100644 --- a/logic/forge/ForgeInstaller.cpp +++ b/logic/forge/ForgeInstaller.cpp @@ -14,14 +14,13 @@ */ #include "ForgeInstaller.h" -#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/MinecraftProfile.h" #include "logic/minecraft/OneSixLibrary.h" #include "logic/net/HttpMetaCache.h" #include "logic/tasks/Task.h" #include "logic/OneSixInstance.h" #include "logic/forge/ForgeVersionList.h" -#include "logic/VersionFilterData.h" -#include "gui/dialogs/ProgressDialog.h" +#include "logic/minecraft/VersionFilterData.h" #include #include @@ -41,7 +40,7 @@ ForgeInstaller::ForgeInstaller() : BaseInstaller() void ForgeInstaller::prepare(const QString &filename, const QString &universalUrl) { - std::shared_ptr newVersion; + std::shared_ptr newVersion; m_universal_url = universalUrl; QuaZip zip(filename); @@ -74,7 +73,7 @@ void ForgeInstaller::prepare(const QString &filename, const QString &universalUr // read the forge version info { - newVersion = InstanceVersion::fromJson(versionInfoVal.toObject()); + newVersion = MinecraftProfile::fromJson(versionInfoVal.toObject()); if (!newVersion) return; } @@ -116,7 +115,7 @@ void ForgeInstaller::prepare(const QString &filename, const QString &universalUr file.close(); m_forge_json = newVersion; - realVersionId = m_forge_json->id = installObj.value("minecraft").toString(); + m_forge_json->id = installObj.value("minecraft").toString(); } bool ForgeInstaller::add(OneSixInstance *to) @@ -194,7 +193,7 @@ bool ForgeInstaller::add(OneSixInstance *to) bool found = false; bool equals = false; // find an entry that matches this one - for (auto tolib : to->getFullVersion()->vanillaLibraries) + for (auto tolib : to->getMinecraftProfile()->vanillaLibraries) { if (tolib->artifactId() != libName) continue; @@ -237,7 +236,7 @@ bool ForgeInstaller::add(OneSixInstance *to) match = expression.match(args); } } - if (!args.isEmpty() && args != to->getFullVersion()->vanillaMinecraftArguments) + if (!args.isEmpty() && args != to->getMinecraftProfile()->vanillaMinecraftArguments) { obj.insert("minecraftArguments", args); } @@ -246,7 +245,7 @@ bool ForgeInstaller::add(OneSixInstance *to) obj.insert("+tweakers", QJsonArray::fromStringList(tweakers)); } if (!m_forge_json->processArguments.isEmpty() && - m_forge_json->processArguments != to->getFullVersion()->vanillaProcessArguments) + m_forge_json->processArguments != to->getMinecraftProfile()->vanillaProcessArguments) { obj.insert("processArguments", m_forge_json->processArguments); } @@ -308,7 +307,7 @@ bool ForgeInstaller::addLegacy(OneSixInstance *to) traitsPlus.append(QString("legacyFML")); obj.insert("+traits", traitsPlus); } - auto fullversion = to->getFullVersion(); + auto fullversion = to->getMinecraftProfile(); fullversion->remove("net.minecraftforge"); QFile file(filename(to->instanceRoot())); @@ -409,7 +408,7 @@ protected: { try { - m_instance->reloadVersion(); + m_instance->reloadProfile(); emitSucceeded(); } catch (MMCError &e) diff --git a/logic/forge/ForgeInstaller.h b/logic/forge/ForgeInstaller.h index f2e5e393..e85582fd 100644 --- a/logic/forge/ForgeInstaller.h +++ b/logic/forge/ForgeInstaller.h @@ -20,7 +20,7 @@ #include #include -class InstanceVersion; +class MinecraftProfile; class ForgeInstallTask; struct ForgeVersion; @@ -40,12 +40,11 @@ protected: private: // the parsed version json, read from the installer - std::shared_ptr m_forge_json; + std::shared_ptr m_forge_json; // the actual forge version std::shared_ptr m_forge_version; QString internalPath; QString finalPath; - QString realVersionId; QString m_forgeVersionString; QString m_universal_url; }; diff --git a/logic/forge/ForgeVersion.cpp b/logic/forge/ForgeVersion.cpp index 3131ec39..f3e64204 100644 --- a/logic/forge/ForgeVersion.cpp +++ b/logic/forge/ForgeVersion.cpp @@ -1,5 +1,5 @@ #include "ForgeVersion.h" -#include "logic/VersionFilterData.h" +#include "logic/minecraft/VersionFilterData.h" #include QString ForgeVersion::name() diff --git a/logic/ftb/FTBVersion.h b/logic/ftb/FTBVersion.h new file mode 100644 index 00000000..dbd9c29e --- /dev/null +++ b/logic/ftb/FTBVersion.h @@ -0,0 +1,32 @@ +#pragma once +#include + +class FTBVersion : public BaseVersion +{ +public: + FTBVersion(MinecraftVersionPtr parent) : m_version(parent){}; + +public: + virtual QString descriptor() override + { + return m_version->descriptor(); + } + + virtual QString name() override + { + return m_version->name(); + } + + virtual QString typeString() const override + { + return m_version->typeString(); + } + + MinecraftVersionPtr getMinecraftVersion() + { + return m_version; + } + +private: + MinecraftVersionPtr m_version; +}; diff --git a/logic/ftb/LegacyFTBInstance.cpp b/logic/ftb/LegacyFTBInstance.cpp new file mode 100644 index 00000000..06bef948 --- /dev/null +++ b/logic/ftb/LegacyFTBInstance.cpp @@ -0,0 +1,20 @@ +#include "LegacyFTBInstance.h" + +LegacyFTBInstance::LegacyFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : + LegacyInstance(rootDir, settings, parent) +{ +} + +QString LegacyFTBInstance::getStatusbarDescription() +{ + if (flags() & VersionBrokenFlag) + { + return "Legacy FTB: " + intendedVersionId() + " (broken)"; + } + return "Legacy FTB: " + intendedVersionId(); +} + +QString LegacyFTBInstance::id() const +{ + return "FTB/" + BaseInstance::id(); +} diff --git a/logic/ftb/LegacyFTBInstance.h b/logic/ftb/LegacyFTBInstance.h new file mode 100644 index 00000000..7ae7a97c --- /dev/null +++ b/logic/ftb/LegacyFTBInstance.h @@ -0,0 +1,13 @@ +#pragma once + +#include "logic/LegacyInstance.h" + +class LegacyFTBInstance : public LegacyInstance +{ + Q_OBJECT +public: + explicit LegacyFTBInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + virtual QString getStatusbarDescription(); + virtual QString id() const; +}; diff --git a/logic/ftb/OneSixFTBInstance.cpp b/logic/ftb/OneSixFTBInstance.cpp new file mode 100644 index 00000000..d55b1ea9 --- /dev/null +++ b/logic/ftb/OneSixFTBInstance.cpp @@ -0,0 +1,139 @@ +#include "OneSixFTBInstance.h" + +#include "logic/minecraft/MinecraftProfile.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/minecraft/VersionBuilder.h" +#include "logic/tasks/SequentialTask.h" +#include "logic/forge/ForgeInstaller.h" +#include "logic/forge/ForgeVersionList.h" +#include "MultiMC.h" +#include "pathutils.h" + +OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : + OneSixInstance(rootDir, settings, parent) +{ +} + +void OneSixFTBInstance::copy(const QDir &newDir) +{ + QStringList libraryNames; + // create patch file + { + QLOG_DEBUG() << "Creating patch file for FTB instance..."; + QFile f(minecraftRoot() + "/pack.json"); + if (!f.open(QFile::ReadOnly)) + { + QLOG_ERROR() << "Couldn't open" << f.fileName() << ":" << f.errorString(); + return; + } + QJsonObject root = QJsonDocument::fromJson(f.readAll()).object(); + QJsonArray libs = root.value("libraries").toArray(); + QJsonArray outLibs; + for (auto lib : libs) + { + QJsonObject libObj = lib.toObject(); + libObj.insert("MMC-hint", QString("local")); + libObj.insert("insert", QString("prepend")); + libraryNames.append(libObj.value("name").toString()); + outLibs.append(libObj); + } + root.remove("libraries"); + root.remove("id"); + + // HACK HACK HACK HACK + // A workaround for a problem in MultiMC, triggered by a historical problem in FTB, + // triggered by Mojang getting their library versions wrong in 1.7.10 + if(intendedVersionId() == "1.7.10") + { + auto insert = [&outLibs, &libraryNames](QString name) + { + QJsonObject libObj; + libObj.insert("insert", QString("replace")); + libObj.insert("name", name); + libraryNames.push_back(name); + outLibs.prepend(libObj); + }; + insert("com.google.guava:guava:16.0"); + insert("org.apache.commons:commons-lang3:3.2.1"); + } + root.insert("+libraries", outLibs); + root.insert("order", 1); + root.insert("fileId", QString("org.multimc.ftb.pack.json")); + root.insert("name", name()); + root.insert("mcVersion", intendedVersionId()); + root.insert("version", intendedVersionId()); + ensureFilePathExists(newDir.absoluteFilePath("patches/ftb.json")); + QFile out(newDir.absoluteFilePath("patches/ftb.json")); + if (!out.open(QFile::WriteOnly | QFile::Truncate)) + { + QLOG_ERROR() << "Couldn't open" << out.fileName() << ":" << out.errorString(); + return; + } + out.write(QJsonDocument(root).toJson()); + } + // copy libraries + { + QLOG_DEBUG() << "Copying FTB libraries"; + for (auto library : libraryNames) + { + OneSixLibrary *lib = new OneSixLibrary(library); + const QString out = QDir::current().absoluteFilePath("libraries/" + lib->storagePath()); + if (QFile::exists(out)) + { + continue; + } + if (!ensureFilePathExists(out)) + { + QLOG_ERROR() << "Couldn't create folder structure for" << out; + } + if (!QFile::copy(librariesPath().absoluteFilePath(lib->storagePath()), out)) + { + QLOG_ERROR() << "Couldn't copy" << lib->rawName(); + } + } + } +} + +QString OneSixFTBInstance::id() const +{ + return "FTB/" + BaseInstance::id(); +} + +QDir OneSixFTBInstance::librariesPath() const +{ + return QDir(MMC->settings()->get("FTBRoot").toString() + "/libraries"); +} + +QDir OneSixFTBInstance::versionsPath() const +{ + return QDir(MMC->settings()->get("FTBRoot").toString() + "/versions"); +} + +/* +QStringList OneSixFTBInstance::externalPatches() const +{ + return QStringList() << versionsPath().absoluteFilePath(intendedVersionId() + "/" + intendedVersionId() + ".json") + << minecraftRoot() + "/pack.json"; +} +*/ + +bool OneSixFTBInstance::providesVersionFile() const +{ + return true; +} + +QString OneSixFTBInstance::getStatusbarDescription() +{ + if (flags() & VersionBrokenFlag) + { + return "OneSix FTB: " + intendedVersionId() + " (broken)"; + } + return "OneSix FTB: " + intendedVersionId(); +} + +std::shared_ptr OneSixFTBInstance::doUpdate() +{ + return OneSixInstance::doUpdate(); +} + +#include "OneSixFTBInstance.moc" diff --git a/logic/ftb/OneSixFTBInstance.h b/logic/ftb/OneSixFTBInstance.h new file mode 100644 index 00000000..3419e94c --- /dev/null +++ b/logic/ftb/OneSixFTBInstance.h @@ -0,0 +1,29 @@ +#pragma once + +#include "logic/OneSixInstance.h" + +class OneSixLibrary; + +class OneSixFTBInstance : public OneSixInstance +{ + Q_OBJECT +public: + explicit OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + virtual ~OneSixFTBInstance(){}; + + void copy(const QDir &newDir) override; + + virtual QString getStatusbarDescription(); + + virtual std::shared_ptr doUpdate() override; + + virtual QString id() const; + + QDir librariesPath() const override; + QDir versionsPath() const override; + bool providesVersionFile() const override; + +private: + std::shared_ptr m_forge; +}; diff --git a/logic/liteloader/LiteLoaderInstaller.cpp b/logic/liteloader/LiteLoaderInstaller.cpp index 82c3b063..be1791ee 100644 --- a/logic/liteloader/LiteLoaderInstaller.cpp +++ b/logic/liteloader/LiteLoaderInstaller.cpp @@ -20,7 +20,7 @@ #include "logger/QsLog.h" -#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/MinecraftProfile.h" #include "logic/minecraft/OneSixLibrary.h" #include "logic/OneSixInstance.h" #include "MultiMC.h" @@ -116,7 +116,7 @@ protected: { try { - m_instance->reloadVersion(); + m_instance->reloadProfile(); emitSucceeded(); } catch (MMCError &e) diff --git a/logic/minecraft/InstanceVersion.cpp b/logic/minecraft/InstanceVersion.cpp deleted file mode 100644 index cc077767..00000000 --- a/logic/minecraft/InstanceVersion.cpp +++ /dev/null @@ -1,550 +0,0 @@ -/* Copyright 2013-2015 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include - -#include "logic/minecraft/InstanceVersion.h" -#include "logic/minecraft/VersionBuilder.h" -#include "logic/OneSixInstance.h" - -InstanceVersion::InstanceVersion(OneSixInstance *instance, QObject *parent) - : QAbstractListModel(parent), m_instance(instance) -{ - clear(); -} - -void InstanceVersion::reload(const QStringList &external) -{ - m_externalPatches = external; - beginResetModel(); - VersionBuilder::build(this, m_instance, m_externalPatches); - reapply(true); - endResetModel(); -} - -void InstanceVersion::clear() -{ - id.clear(); - m_updateTimeString.clear(); - m_updateTime = QDateTime(); - m_releaseTimeString.clear(); - m_releaseTime = QDateTime(); - type.clear(); - assets.clear(); - processArguments.clear(); - minecraftArguments.clear(); - minimumLauncherVersion = 0xDEADBEAF; - mainClass.clear(); - appletClass.clear(); - libraries.clear(); - tweakers.clear(); - jarMods.clear(); - traits.clear(); -} - -bool InstanceVersion::canRemove(const int index) const -{ - return VersionPatches.at(index)->isMoveable(); -} - -bool InstanceVersion::preremove(VersionPatchPtr patch) -{ - bool ok = true; - for(auto & jarmod: patch->getJarMods()) - { - QString fullpath =PathCombine(m_instance->jarModsDir(), jarmod->name); - QFileInfo finfo (fullpath); - if(finfo.exists()) - ok &= QFile::remove(fullpath); - } - return ok; -} - -bool InstanceVersion::remove(const int index) -{ - if (!canRemove(index)) - return false; - if(!preremove(VersionPatches[index])) - { - return false; - } - auto toDelete = VersionPatches.at(index)->getPatchFilename(); - if(!QFile::remove(toDelete)) - return false; - beginRemoveRows(QModelIndex(), index, index); - VersionPatches.removeAt(index); - endRemoveRows(); - reapply(true); - saveCurrentOrder(); - return true; -} - -bool InstanceVersion::remove(const QString id) -{ - int i = 0; - for (auto patch : VersionPatches) - { - if (patch->getPatchID() == id) - { - return remove(i); - } - i++; - } - return false; -} - -QString InstanceVersion::versionFileId(const int index) const -{ - if (index < 0 || index >= VersionPatches.size()) - { - return QString(); - } - return VersionPatches.at(index)->getPatchID(); -} - -VersionPatchPtr InstanceVersion::versionPatch(const QString &id) -{ - for (auto file : VersionPatches) - { - if (file->getPatchID() == id) - { - return file; - } - } - return 0; -} - -VersionPatchPtr InstanceVersion::versionPatch(int index) -{ - if(index < 0 || index >= VersionPatches.size()) - return 0; - return VersionPatches[index]; -} - - -bool InstanceVersion::hasJarMods() -{ - return !jarMods.isEmpty(); -} - -bool InstanceVersion::hasFtbPack() -{ - return versionPatch("org.multimc.ftb.pack.json") != nullptr; -} - -bool InstanceVersion::removeFtbPack() -{ - return remove("org.multimc.ftb.pack.json"); -} - -bool InstanceVersion::isVanilla() -{ - QDir patches(PathCombine(m_instance->instanceRoot(), "patches/")); - for(auto patchptr: VersionPatches) - { - if(patchptr->isCustom()) - return false; - } - if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) - return false; - if(QFile::exists(PathCombine(m_instance->instanceRoot(), "version.json"))) - return false; - return true; -} - -bool InstanceVersion::revertToVanilla() -{ - beginResetModel(); - // remove custom.json, if present - QString customPath = PathCombine(m_instance->instanceRoot(), "custom.json"); - if(QFile::exists(customPath)) - { - if(!QFile::remove(customPath)) - { - endResetModel(); - return false; - } - } - // remove version.json, if present - QString versionPath = PathCombine(m_instance->instanceRoot(), "version.json"); - if(QFile::exists(versionPath)) - { - if(!QFile::remove(versionPath)) - { - endResetModel(); - return false; - } - } - // remove patches, if present - auto it = VersionPatches.begin(); - while (it != VersionPatches.end()) - { - if ((*it)->isMoveable()) - { - if(!preremove(*it)) - { - endResetModel(); - saveCurrentOrder(); - return false; - } - if(!QFile::remove((*it)->getPatchFilename())) - { - endResetModel(); - saveCurrentOrder(); - return false; - } - it = VersionPatches.erase(it); - } - else - it++; - } - reapply(true); - endResetModel(); - saveCurrentOrder(); - return true; -} - -bool InstanceVersion::hasDeprecatedVersionFiles() -{ - if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) - return true; - if(QFile::exists(PathCombine(m_instance->instanceRoot(), "version.json"))) - return true; - return false; -} - -bool InstanceVersion::removeDeprecatedVersionFiles() -{ - beginResetModel(); - // remove custom.json, if present - QString customPath = PathCombine(m_instance->instanceRoot(), "custom.json"); - if(QFile::exists(customPath)) - { - if(!QFile::remove(customPath)) - { - endResetModel(); - return false; - } - } - // remove version.json, if present - QString versionPath = PathCombine(m_instance->instanceRoot(), "version.json"); - if(QFile::exists(versionPath)) - { - if(!QFile::remove(versionPath)) - { - endResetModel(); - return false; - } - } - endResetModel(); - return true; -} - -QList > InstanceVersion::getActiveNormalLibs() -{ - QList > output; - for (auto lib : libraries) - { - if (lib->isActive() && !lib->isNative()) - { - for (auto other : output) - { - if (other->rawName() == lib->rawName()) - { - QLOG_WARN() << "Multiple libraries with name" << lib->rawName() << "in library list!"; - continue; - } - } - output.append(lib); - } - } - return output; -} -QList > InstanceVersion::getActiveNativeLibs() -{ - QList > output; - for (auto lib : libraries) - { - if (lib->isActive() && lib->isNative()) - { - output.append(lib); - } - } - return output; -} - -std::shared_ptr InstanceVersion::fromJson(const QJsonObject &obj) -{ - std::shared_ptr version(new InstanceVersion(0)); - try - { - VersionBuilder::readJsonAndApplyToVersion(version.get(), obj); - } - catch(MMCError & err) - { - return 0; - } - return version; -} - -QVariant InstanceVersion::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= VersionPatches.size()) - return QVariant(); - - if (role == Qt::DisplayRole) - { - switch (column) - { - case 0: - return VersionPatches.at(row)->getPatchName(); - case 1: - return VersionPatches.at(row)->getPatchVersion(); - default: - return QVariant(); - } - } - return QVariant(); -} -QVariant InstanceVersion::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) - { - if (role == Qt::DisplayRole) - { - switch (section) - { - case 0: - return tr("Name"); - case 1: - return tr("Version"); - default: - return QVariant(); - } - } - } - return QVariant(); -} -Qt::ItemFlags InstanceVersion::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; -} - -int InstanceVersion::rowCount(const QModelIndex &parent) const -{ - return VersionPatches.size(); -} - -int InstanceVersion::columnCount(const QModelIndex &parent) const -{ - return 2; -} - -void InstanceVersion::saveCurrentOrder() const -{ - PatchOrder order; - for(auto item: VersionPatches) - { - if(!item->isMoveable()) - continue; - order.append(item->getPatchID()); - } - VersionBuilder::writeOverrideOrders(m_instance, order); -} - -void InstanceVersion::move(const int index, const MoveDirection direction) -{ - int theirIndex; - if (direction == MoveUp) - { - theirIndex = index - 1; - } - else - { - theirIndex = index + 1; - } - - if (index < 0 || index >= VersionPatches.size()) - return; - if (theirIndex >= rowCount()) - theirIndex = rowCount() - 1; - if (theirIndex == -1) - theirIndex = rowCount() - 1; - if (index == theirIndex) - return; - int togap = theirIndex > index ? theirIndex + 1 : theirIndex; - - auto from = versionPatch(index); - auto to = versionPatch(theirIndex); - - if (!from || !to || !to->isMoveable() || !from->isMoveable()) - { - return; - } - beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); - VersionPatches.swap(index, theirIndex); - endMoveRows(); - saveCurrentOrder(); - reapply(); -} -void InstanceVersion::resetOrder() -{ - QDir(m_instance->instanceRoot()).remove("order.json"); - reload(m_externalPatches); -} - -void InstanceVersion::reapply(const bool alreadyReseting) -{ - clear(); - for(auto file: VersionPatches) - { - file->applyTo(this); - } - finalize(); -} - -void InstanceVersion::finalize() -{ - // HACK: deny april fools. my head hurts enough already. - QDate now = QDate::currentDate(); - bool isAprilFools = now.month() == 4 && now.day() == 1; - if (assets.endsWith("_af") && !isAprilFools) - { - assets = assets.left(assets.length() - 3); - } - if (assets.isEmpty()) - { - assets = "legacy"; - } - auto finalizeArguments = [&]( QString & minecraftArguments, const QString & processArguments ) -> void - { - if (!minecraftArguments.isEmpty()) - return; - QString toCompare = processArguments.toLower(); - if (toCompare == "legacy") - { - minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - minecraftArguments = "--username ${auth_player_name} " - "--session ${auth_session} " - "--version ${profile_name}"; - } - }; - finalizeArguments(vanillaMinecraftArguments, vanillaProcessArguments); - finalizeArguments(minecraftArguments, processArguments); -} - -void InstanceVersion::installJarMods(QStringList selectedFiles) -{ - for(auto filename: selectedFiles) - { - installJarModByFilename(filename); - } -} - -void InstanceVersion::installJarModByFilename(QString filepath) -{ - QString patchDir = PathCombine(m_instance->instanceRoot(), "patches"); - if(!ensureFolderPathExists(patchDir)) - { - // THROW... - return; - } - - if (!ensureFolderPathExists(m_instance->jarModsDir())) - { - // THROW... - return; - } - - QFileInfo sourceInfo(filepath); - auto uuid = QUuid::createUuid(); - QString id = uuid.toString().remove('{').remove('}'); - QString target_filename = id + ".jar"; - QString target_id = "org.multimc.jarmod." + id; - QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; - QString finalPath = PathCombine(m_instance->jarModsDir(), target_filename); - - QFileInfo targetInfo(finalPath); - if(targetInfo.exists()) - { - // THROW - return; - } - - if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) - { - // THROW - return; - } - - auto f = std::make_shared(); - auto jarMod = std::make_shared(); - jarMod->name = target_filename; - f->jarMods.append(jarMod); - f->name = target_name; - f->fileId = target_id; - f->order = getFreeOrderNumber(); - QString patchFileName = PathCombine(patchDir, target_id + ".json"); - f->filename = patchFileName; - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - QLOG_ERROR() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return; - // THROW - } - file.write(f->toJson(true).toJson()); - file.close(); - int index = VersionPatches.size(); - beginInsertRows(QModelIndex(), index, index); - VersionPatches.append(f); - endInsertRows(); - saveCurrentOrder(); -} - -int InstanceVersion::getFreeOrderNumber() -{ - int largest = 100; - // yes, I do realize this is dumb. The order thing itself is dumb. and to be removed next. - for(auto thing: VersionPatches) - { - int order = thing->getOrder(); - if(order > largest) - largest = order; - } - return largest + 1; -} diff --git a/logic/minecraft/InstanceVersion.h b/logic/minecraft/InstanceVersion.h deleted file mode 100644 index a4849d6a..00000000 --- a/logic/minecraft/InstanceVersion.h +++ /dev/null @@ -1,184 +0,0 @@ -/* Copyright 2013-2015 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include -#include -#include - -#include "OneSixLibrary.h" -#include "VersionFile.h" -#include "JarMod.h" - -class OneSixInstance; - -class InstanceVersion : public QAbstractListModel -{ - Q_OBJECT -public: - explicit InstanceVersion(OneSixInstance *instance, QObject *parent = 0); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - - void reload(const QStringList &external = QStringList()); - void clear(); - - bool canRemove(const int index) const; - - QString versionFileId(const int index) const; - - // is this version unmodded vanilla minecraft? - bool isVanilla(); - // remove any customizations on top of vanilla - bool revertToVanilla(); - - // does this version consist of obsolete files? - bool hasDeprecatedVersionFiles(); - // remove obsolete files - bool removeDeprecatedVersionFiles(); - - // does this version have an FTB pack patch file? - bool hasFtbPack(); - // remove FTB pack - bool removeFtbPack(); - - // does this version have any jar mods? - bool hasJarMods(); - void installJarMods(QStringList selectedFiles); - void installJarModByFilename(QString filepath); - - enum MoveDirection { MoveUp, MoveDown }; - void move(const int index, const MoveDirection direction); - void resetOrder(); - - // clears and reapplies all version files - void reapply(const bool alreadyReseting = false); - void finalize(); - -public -slots: - bool remove(const int index); - bool remove(const QString id); - -public: - QList> getActiveNormalLibs(); - QList> getActiveNativeLibs(); - - static std::shared_ptr fromJson(const QJsonObject &obj); - -private: - bool preremove(VersionPatchPtr patch); - - // data members -public: - /// the ID - determines which jar to use! ACTUALLY IMPORTANT! - QString id; - - /// the time this version was actually released by Mojang, as string and as QDateTime - QString m_releaseTimeString; - QDateTime m_releaseTime; - - /// the time this version was last updated by Mojang, as string and as QDateTime - QString m_updateTimeString; - QDateTime m_updateTime; - - /// Release type - "release" or "snapshot" - QString type; - /// Assets type - "legacy" or a version ID - QString assets; - /** - * DEPRECATED: Old versions of the new vanilla launcher used this - * ex: "username_session_version" - */ - QString processArguments; - /// Same as above, but only for vanilla - QString vanillaProcessArguments; - /** - * arguments that should be used for launching minecraft - * - * ex: "--username ${auth_player_name} --session ${auth_session} - * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" - */ - QString minecraftArguments; - /// Same as above, but only for vanilla - QString vanillaMinecraftArguments; - /** - * the minimum launcher version required by this version ... current is 4 (at point of - * writing) - */ - int minimumLauncherVersion = 0xDEADBEEF; - /** - * A list of all tweaker classes - */ - QStringList tweakers; - /** - * The main class to load first - */ - QString mainClass; - /** - * The applet class, for some very old minecraft releases - */ - QString appletClass; - - /// the list of libs - both active and inactive, native and java - QList libraries; - - /// same, but only vanilla. - QList vanillaLibraries; - - /// traits, collected from all the version files (version files can only add) - QSet traits; - - /// A list of jar mods. version files can add those. - QList jarMods; - - /* - FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. - - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx", - "version": "^10\\.5\\.\\d$" - } - } - ], - "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX - 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" - } - */ - // QList rules; - - QList VersionPatches; - VersionPatchPtr versionPatch(const QString &id); - VersionPatchPtr versionPatch(int index); - -private: - QStringList m_externalPatches; - OneSixInstance *m_instance; - void saveCurrentOrder() const; - int getFreeOrderNumber(); -}; diff --git a/logic/minecraft/JarMod.cpp b/logic/minecraft/JarMod.cpp index 18a9411c..fce8a492 100644 --- a/logic/minecraft/JarMod.cpp +++ b/logic/minecraft/JarMod.cpp @@ -11,30 +11,6 @@ JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename) "contains a jarmod that doesn't have a 'name' field"); } out->name = libObj.value("name").toString(); - - auto readString = [libObj, filename](const QString & key, QString & variable) - { - if (libObj.contains(key)) - { - QJsonValue val = libObj.value(key); - if (!val.isString()) - { - QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; - } - else - { - variable = val.toString(); - } - } - }; - - readString("url", out->baseurl); - readString("MMC-hint", out->hint); - readString("MMC-absoluteUrl", out->absoluteUrl); - if(!out->baseurl.isEmpty() && out->absoluteUrl.isEmpty()) - { - out->absoluteUrl = out->baseurl + out->name; - } return out; } @@ -42,15 +18,5 @@ QJsonObject Jarmod::toJson() { QJsonObject out; writeString(out, "name", name); - writeString(out, "url", baseurl); - writeString(out, "MMC-absoluteUrl", absoluteUrl); - writeString(out, "MMC-hint", hint); return out; } - -QString Jarmod::url() -{ - if(!absoluteUrl.isEmpty()) - return absoluteUrl; - else return baseurl + name; -} diff --git a/logic/minecraft/JarMod.h b/logic/minecraft/JarMod.h index c438dbcd..7d9fa038 100644 --- a/logic/minecraft/JarMod.h +++ b/logic/minecraft/JarMod.h @@ -9,10 +9,6 @@ class Jarmod public: /* methods */ static JarmodPtr fromJson(const QJsonObject &libObj, const QString &filename); QJsonObject toJson(); - QString url(); public: /* data */ QString name; - QString baseurl; - QString hint; - QString absoluteUrl; }; diff --git a/logic/minecraft/MinecraftInstance.cpp b/logic/minecraft/MinecraftInstance.cpp new file mode 100644 index 00000000..090c9389 --- /dev/null +++ b/logic/minecraft/MinecraftInstance.cpp @@ -0,0 +1,58 @@ +#include "MinecraftInstance.h" +#include "MultiMC.h" +#include "logic/settings/SettingsObject.h" +#include +#include "logic/minecraft/MinecraftVersionList.h" + +MinecraftInstance::MinecraftInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) + : BaseInstance(rootDir, settings, parent) +{ + auto globalSettings = MMC->settings(); + + // Java Settings + m_settings->registerSetting("OverrideJava", false); + m_settings->registerSetting("OverrideJavaLocation", false); + m_settings->registerSetting("OverrideJavaArgs", false); + m_settings->registerOverride(globalSettings->getSetting("JavaPath")); + m_settings->registerOverride(globalSettings->getSetting("JvmArgs")); + + // Custom Commands + m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); + m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand")); + m_settings->registerOverride(globalSettings->getSetting("PostExitCommand")); + + // Window Size + m_settings->registerSetting("OverrideWindow", false); + m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized")); + m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth")); + m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight")); + + // Memory + m_settings->registerSetting("OverrideMemory", false); + m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc")); + m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc")); + m_settings->registerOverride(globalSettings->getSetting("PermGen")); + + // Console + m_settings->registerSetting("OverrideConsole", false); + m_settings->registerOverride(globalSettings->getSetting("ShowConsole")); + m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole")); + m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput")); +} + +QString MinecraftInstance::minecraftRoot() const +{ + QFileInfo mcDir(PathCombine(instanceRoot(), "minecraft")); + QFileInfo dotMCDir(PathCombine(instanceRoot(), ".minecraft")); + + if (dotMCDir.exists() && !mcDir.exists()) + return dotMCDir.filePath(); + else + return mcDir.filePath(); +} + +std::shared_ptr< BaseVersionList > MinecraftInstance::versionList() const +{ + return std::dynamic_pointer_cast(MMC->minecraftlist()); +} + diff --git a/logic/minecraft/MinecraftInstance.h b/logic/minecraft/MinecraftInstance.h new file mode 100644 index 00000000..9097a2da --- /dev/null +++ b/logic/minecraft/MinecraftInstance.h @@ -0,0 +1,30 @@ +#pragma once +#include "logic/BaseInstance.h" + +class MinecraftInstance: public BaseInstance +{ +public: + MinecraftInstance(const QString& rootDir, SettingsObject* settings, QObject* parent = 0); + virtual ~MinecraftInstance() {}; + + /// Path to the instance's minecraft directory. + QString minecraftRoot() const; + + ////// Mod Lists ////// + virtual std::shared_ptr resourcePackList() const + { + return nullptr; + } + virtual std::shared_ptr texturePackList() const + { + return nullptr; + } + /// get all jar mods applicable to this instance's jar + virtual QList getJarMods() const + { + return QList(); + } + virtual std::shared_ptr< BaseVersionList > versionList() const; +}; + +typedef std::shared_ptr MinecraftInstancePtr; diff --git a/logic/minecraft/MinecraftProcess.cpp b/logic/minecraft/MinecraftProcess.cpp new file mode 100644 index 00000000..0fc3f067 --- /dev/null +++ b/logic/minecraft/MinecraftProcess.cpp @@ -0,0 +1,216 @@ +/* Copyright 2013-2014 MultiMC Contributors + * + * Authors: Orochimarufan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "MultiMC.h" +#include "BuildConfig.h" + +#include "logic/minecraft/MinecraftProcess.h" +#include "logic/BaseInstance.h" + +#include +#include +#include +#include +#include +#include + +#include "osutils.h" +#include "pathutils.h" +#include "cmdutils.h" + +#define IBUS "@im=ibus" + +// constructor +MinecraftProcess::MinecraftProcess(MinecraftInstancePtr inst) : BaseProcess(inst) +{ +} + +MinecraftProcess* MinecraftProcess::create(MinecraftInstancePtr inst) +{ + auto proc = new MinecraftProcess(inst); + proc->init(); + return proc; +} + + +QString MinecraftProcess::censorPrivateInfo(QString in) +{ + if (!m_session) + return in; + + if (m_session->session != "-") + in.replace(m_session->session, ""); + in.replace(m_session->access_token, ""); + in.replace(m_session->client_token, ""); + in.replace(m_session->uuid, ""); + in.replace(m_session->player_name, ""); + + auto i = m_session->u.properties.begin(); + while (i != m_session->u.properties.end()) + { + in.replace(i.value(), "<" + i.key().toUpper() + ">"); + ++i; + } + + return in; +} + +// console window +MessageLevel::Enum MinecraftProcess::guessLevel(const QString &line, MessageLevel::Enum level) +{ + QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); + auto match = re.match(line); + if(match.hasMatch()) + { + // New style logs from log4j + QString timestamp = match.captured("timestamp"); + QString levelStr = match.captured("level"); + if(levelStr == "INFO") + level = MessageLevel::Message; + if(levelStr == "WARN") + level = MessageLevel::Warning; + if(levelStr == "ERROR") + level = MessageLevel::Error; + if(levelStr == "FATAL") + level = MessageLevel::Fatal; + if(levelStr == "TRACE" || levelStr == "DEBUG") + level = MessageLevel::Debug; + } + else + { + // Old style forge logs + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || + line.contains("[FINER]") || line.contains("[FINEST]")) + level = MessageLevel::Message; + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + level = MessageLevel::Error; + if (line.contains("[WARNING]")) + level = MessageLevel::Warning; + if (line.contains("[DEBUG]")) + level = MessageLevel::Debug; + } + if (line.contains("overwriting existing")) + return MessageLevel::Fatal; + if (line.contains("Exception in thread") || line.contains(QRegularExpression("\\s+at "))) + return MessageLevel::Error; + return level; +} + +QMap MinecraftProcess::getVariables() const +{ + auto mcInstance = std::dynamic_pointer_cast(m_instance); + QMap out; + out.insert("INST_NAME", mcInstance->name()); + out.insert("INST_ID", mcInstance->id()); + out.insert("INST_DIR", QDir(mcInstance->instanceRoot()).absolutePath()); + out.insert("INST_MC_DIR", QDir(mcInstance->minecraftRoot()).absolutePath()); + out.insert("INST_JAVA", mcInstance->settings().get("JavaPath").toString()); + out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); + return out; +} + +QStringList MinecraftProcess::javaArguments() const +{ + QStringList args; + + // custom args go first. we want to override them if we have our own here. + args.append(m_instance->extraArguments()); + + // OSX dock icon and name +#ifdef OSX + args << "-Xdock:icon=icon.png"; + args << QString("-Xdock:name=\"%1\"").arg(m_instance->windowTitle()); +#endif + + // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 +#ifdef Q_OS_WIN32 + args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" + "minecraft.exe.heapdump"); +#endif + + args << QString("-Xms%1m").arg(m_instance->settings().get("MinMemAlloc").toInt()); + args << QString("-Xmx%1m").arg(m_instance->settings().get("MaxMemAlloc").toInt()); + auto permgen = m_instance->settings().get("PermGen").toInt(); + if (permgen != 64) + { + args << QString("-XX:PermSize=%1m").arg(permgen); + } + args << "-Duser.language=en"; + if (!m_nativeFolder.isEmpty()) + args << QString("-Djava.library.path=%1").arg(m_nativeFolder); + args << "-jar" << PathCombine(MMC->bin(), "jars", "NewLaunch.jar"); + + return args; +} + +void MinecraftProcess::arm() +{ + emit log("MultiMC version: " + BuildConfig.printableVersionString() + "\n\n"); + emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n"); + + if (!preLaunch()) + { + emit ended(m_instance, 1, QProcess::CrashExit); + return; + } + + m_instance->setLastLaunch(); + + QStringList args = javaArguments(); + + QString JavaPath = m_instance->settings().get("JavaPath").toString(); + emit log("Java path is:\n" + JavaPath + "\n\n"); + QString allArgs = args.join(", "); + emit log("Java Arguments:\n[" + censorPrivateInfo(allArgs) + "]\n\n"); + + auto realJavaPath = QStandardPaths::findExecutable(JavaPath); + if (realJavaPath.isEmpty()) + { + emit log(tr("The java binary \"%1\" couldn't be found. You may have to set up java " + "if Minecraft fails to launch.").arg(JavaPath), + MessageLevel::Warning); + } + + // instantiate the launcher part + start(JavaPath, args); + if (!waitForStarted()) + { + //: Error message displayed if instace can't start + emit log(tr("Could not launch minecraft!"), MessageLevel::Error); + m_instance->cleanupAfterRun(); + emit launch_failed(m_instance); + // not running, failed + m_instance->setRunning(false); + return; + } + // send the launch script to the launcher part + QByteArray bytes = launchScript.toUtf8(); + writeData(bytes.constData(), bytes.length()); +} + +void MinecraftProcess::launch() +{ + QString launchString("launch\n"); + QByteArray bytes = launchString.toUtf8(); + writeData(bytes.constData(), bytes.length()); +} + +void MinecraftProcess::abort() +{ + QString launchString("abort\n"); + QByteArray bytes = launchString.toUtf8(); + writeData(bytes.constData(), bytes.length()); +} diff --git a/logic/minecraft/MinecraftProcess.h b/logic/minecraft/MinecraftProcess.h new file mode 100644 index 00000000..30a59e91 --- /dev/null +++ b/logic/minecraft/MinecraftProcess.h @@ -0,0 +1,77 @@ +/* Copyright 2013-2014 MultiMC Contributors + * + * Authors: Orochimarufan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "logic/minecraft/MinecraftInstance.h" +#include "logic/BaseProcess.h" + +/** + * The MinecraftProcess class + */ +class MinecraftProcess : public BaseProcess +{ + Q_OBJECT +protected: + MinecraftProcess(MinecraftInstancePtr inst); +public: + static MinecraftProcess *create(MinecraftInstancePtr inst); + + virtual ~MinecraftProcess(){}; + + /** + * @brief start the launcher part with the provided launch script + */ + void arm() override; + + /** + * @brief launch the armed instance! + */ + void launch() override; + + /** + * @brief abort launch! + */ + void abort() override; + + void setLaunchScript(QString script) + { + launchScript = script; + } + + void setNativeFolder(QString natives) + { + m_nativeFolder = natives; + } + + inline void setLogin(AuthSessionPtr session) + { + m_session = session; + } + +protected: + AuthSessionPtr m_session; + QString launchScript; + QString m_nativeFolder; + + virtual QMap getVariables() const override; + + QStringList javaArguments() const; + virtual QString censorPrivateInfo(QString in) override; + virtual MessageLevel::Enum guessLevel(const QString &message, MessageLevel::Enum defaultLevel) override; +}; diff --git a/logic/minecraft/MinecraftProfile.cpp b/logic/minecraft/MinecraftProfile.cpp new file mode 100644 index 00000000..3fbfd105 --- /dev/null +++ b/logic/minecraft/MinecraftProfile.cpp @@ -0,0 +1,434 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "logic/minecraft/MinecraftProfile.h" +#include "logic/minecraft/VersionBuilder.h" +#include "ProfileUtils.h" +#include "NullProfileStrategy.h" +#include "logic/OneSixInstance.h" + +MinecraftProfile::MinecraftProfile(ProfileStrategy *strategy) + : QAbstractListModel() +{ + setStrategy(strategy); + clear(); +} + +void MinecraftProfile::setStrategy(ProfileStrategy* strategy) +{ + Q_ASSERT(strategy != nullptr); + + if(m_strategy != nullptr) + { + delete m_strategy; + m_strategy = nullptr; + } + m_strategy = strategy; + m_strategy->profile = this; +} + +ProfileStrategy* MinecraftProfile::strategy() +{ + return m_strategy; +} + +void MinecraftProfile::reload() +{ + beginResetModel(); + m_strategy->load(); + reapply(); + endResetModel(); +} + +void MinecraftProfile::clear() +{ + id.clear(); + m_updateTimeString.clear(); + m_updateTime = QDateTime(); + m_releaseTimeString.clear(); + m_releaseTime = QDateTime(); + type.clear(); + assets.clear(); + processArguments.clear(); + minecraftArguments.clear(); + minimumLauncherVersion = 0xDEADBEAF; + mainClass.clear(); + appletClass.clear(); + libraries.clear(); + tweakers.clear(); + jarMods.clear(); + traits.clear(); +} + +void MinecraftProfile::clearPatches() +{ + beginResetModel(); + VersionPatches.clear(); + endResetModel(); +} + +void MinecraftProfile::appendPatch(ProfilePatchPtr patch) +{ + int index = VersionPatches.size(); + beginInsertRows(QModelIndex(), index, index); + VersionPatches.append(patch); + endInsertRows(); +} + +bool MinecraftProfile::canRemove(const int index) const +{ + return VersionPatches.at(index)->isMoveable(); +} + +bool MinecraftProfile::remove(const int index) +{ + if (!canRemove(index)) + return false; + + if(!m_strategy->removePatch(VersionPatches.at(index))) + { + return false; + } + + beginRemoveRows(QModelIndex(), index, index); + VersionPatches.removeAt(index); + endRemoveRows(); + reapply(); + saveCurrentOrder(); + return true; +} + +bool MinecraftProfile::remove(const QString id) +{ + int i = 0; + for (auto patch : VersionPatches) + { + if (patch->getPatchID() == id) + { + return remove(i); + } + i++; + } + return false; +} + +QString MinecraftProfile::versionFileId(const int index) const +{ + if (index < 0 || index >= VersionPatches.size()) + { + return QString(); + } + return VersionPatches.at(index)->getPatchID(); +} + +ProfilePatchPtr MinecraftProfile::versionPatch(const QString &id) +{ + for (auto file : VersionPatches) + { + if (file->getPatchID() == id) + { + return file; + } + } + return 0; +} + +ProfilePatchPtr MinecraftProfile::versionPatch(int index) +{ + if(index < 0 || index >= VersionPatches.size()) + return 0; + return VersionPatches[index]; +} + +bool MinecraftProfile::isVanilla() +{ + for(auto patchptr: VersionPatches) + { + if(patchptr->isCustom()) + return false; + } + return true; +} + +bool MinecraftProfile::revertToVanilla() +{ + /* + beginResetModel(); + // remove patches, if present + auto it = VersionPatches.begin(); + while (it != VersionPatches.end()) + { + if ((*it)->isMoveable()) + { + if(!preremove(*it)) + { + endResetModel(); + saveCurrentOrder(); + return false; + } + if(!QFile::remove((*it)->getPatchFilename())) + { + endResetModel(); + saveCurrentOrder(); + return false; + } + it = VersionPatches.erase(it); + } + else + it++; + } + reapply(); + endResetModel(); + saveCurrentOrder(); + return true; + */ + return false; +} + +QList > MinecraftProfile::getActiveNormalLibs() +{ + QList > output; + for (auto lib : libraries) + { + if (lib->isActive() && !lib->isNative()) + { + for (auto other : output) + { + if (other->rawName() == lib->rawName()) + { + QLOG_WARN() << "Multiple libraries with name" << lib->rawName() << "in library list!"; + continue; + } + } + output.append(lib); + } + } + return output; +} + +QList > MinecraftProfile::getActiveNativeLibs() +{ + QList > output; + for (auto lib : libraries) + { + if (lib->isActive() && lib->isNative()) + { + output.append(lib); + } + } + return output; +} + +std::shared_ptr MinecraftProfile::fromJson(const QJsonObject &obj) +{ + std::shared_ptr version(new MinecraftProfile(new NullProfileStrategy())); + try + { + VersionBuilder::readJsonAndApplyToVersion(version.get(), obj); + } + catch(MMCError & err) + { + return 0; + } + return version; +} + +QVariant MinecraftProfile::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= VersionPatches.size()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + switch (column) + { + case 0: + return VersionPatches.at(row)->getPatchName(); + case 1: + return VersionPatches.at(row)->getPatchVersion(); + default: + return QVariant(); + } + } + return QVariant(); +} +QVariant MinecraftProfile::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) + { + switch (section) + { + case 0: + return tr("Name"); + case 1: + return tr("Version"); + default: + return QVariant(); + } + } + } + return QVariant(); +} +Qt::ItemFlags MinecraftProfile::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +int MinecraftProfile::rowCount(const QModelIndex &parent) const +{ + return VersionPatches.size(); +} + +int MinecraftProfile::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +void MinecraftProfile::saveCurrentOrder() const +{ + ProfileUtils::PatchOrder order; + for(auto item: VersionPatches) + { + if(!item->isMoveable()) + continue; + order.append(item->getPatchID()); + } + m_strategy->saveOrder(order); +} + +void MinecraftProfile::move(const int index, const MoveDirection direction) +{ + int theirIndex; + if (direction == MoveUp) + { + theirIndex = index - 1; + } + else + { + theirIndex = index + 1; + } + + if (index < 0 || index >= VersionPatches.size()) + return; + if (theirIndex >= rowCount()) + theirIndex = rowCount() - 1; + if (theirIndex == -1) + theirIndex = rowCount() - 1; + if (index == theirIndex) + return; + int togap = theirIndex > index ? theirIndex + 1 : theirIndex; + + auto from = versionPatch(index); + auto to = versionPatch(theirIndex); + + if (!from || !to || !to->isMoveable() || !from->isMoveable()) + { + return; + } + beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); + VersionPatches.swap(index, theirIndex); + endMoveRows(); + saveCurrentOrder(); + reapply(); +} +void MinecraftProfile::resetOrder() +{ + m_strategy->resetOrder(); + reload(); +} + +void MinecraftProfile::reapply() +{ + clear(); + for(auto file: VersionPatches) + { + file->applyTo(this); + } + finalize(); +} + +void MinecraftProfile::finalize() +{ + // HACK: deny april fools. my head hurts enough already. + QDate now = QDate::currentDate(); + bool isAprilFools = now.month() == 4 && now.day() == 1; + if (assets.endsWith("_af") && !isAprilFools) + { + assets = assets.left(assets.length() - 3); + } + if (assets.isEmpty()) + { + assets = "legacy"; + } + auto finalizeArguments = [&]( QString & minecraftArguments, const QString & processArguments ) -> void + { + if (!minecraftArguments.isEmpty()) + return; + QString toCompare = processArguments.toLower(); + if (toCompare == "legacy") + { + minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + minecraftArguments = "--username ${auth_player_name} " + "--session ${auth_session} " + "--version ${profile_name}"; + } + }; + finalizeArguments(vanillaMinecraftArguments, vanillaProcessArguments); + finalizeArguments(minecraftArguments, processArguments); +} + +void MinecraftProfile::installJarMods(QStringList selectedFiles) +{ + m_strategy->installJarMods(selectedFiles); +} + +/* + * TODO: get rid of this. Get rid of all order numbers. + */ +int MinecraftProfile::getFreeOrderNumber() +{ + int largest = 100; + // yes, I do realize this is dumb. The order thing itself is dumb. and to be removed next. + for(auto thing: VersionPatches) + { + int order = thing->getOrder(); + if(order > largest) + largest = order; + } + return largest + 1; +} diff --git a/logic/minecraft/MinecraftProfile.h b/logic/minecraft/MinecraftProfile.h new file mode 100644 index 00000000..ef7e1369 --- /dev/null +++ b/logic/minecraft/MinecraftProfile.h @@ -0,0 +1,201 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +#include "OneSixLibrary.h" +#include "VersionFile.h" +#include "JarMod.h" + +class ProfileStrategy; +class OneSixInstance; + +class MinecraftProfile : public QAbstractListModel +{ + Q_OBJECT + friend class ProfileStrategy; + +public: + explicit MinecraftProfile(ProfileStrategy *strategy); + + /// construct a MinecraftProfile from a single file + static std::shared_ptr fromJson(const QJsonObject &obj); + + void setStrategy(ProfileStrategy * strategy); + ProfileStrategy *strategy(); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + + /// is this version unchanged by the user? + bool isVanilla(); + + /// remove any customizations on top of whatever 'vanilla' means + bool revertToVanilla(); + + /// install more jar mods + void installJarMods(QStringList selectedFiles); + + /// DEPRECATED, remove ASAP + int getFreeOrderNumber(); + + /// Can patch file # be removed? + bool canRemove(const int index) const; + + enum MoveDirection { MoveUp, MoveDown }; + /// move patch file # up or down the list + void move(const int index, const MoveDirection direction); + + /// remove patch file # - including files/records + bool remove(const int index); + + /// remove patch file by id - including files/records + bool remove(const QString id); + + void resetOrder(); + + /// reload all profile patches from storage, clear the profile and apply the patches + void reload(); + + /// clear the profile + void clear(); + + /// apply the patches + void reapply(); + + /// do a finalization step (should always be done after applying all patches to profile) + void finalize(); + +public: + /// get all java libraries that belong to the classpath + QList> getActiveNormalLibs(); + + /// get all native libraries that need to be available to the process + QList> getActiveNativeLibs(); + + /// get file ID of the patch file at # + QString versionFileId(const int index) const; + + /// get the profile patch by id + ProfilePatchPtr versionPatch(const QString &id); + + /// get the profile patch by index + ProfilePatchPtr versionPatch(int index); + + /// save the current patch order + void saveCurrentOrder() const; + +public: /* only use in ProfileStrategy */ + /// Remove all the patches + void clearPatches(); + + /// Add the patch object to the internal list of patches + void appendPatch(ProfilePatchPtr patch); + +public: /* data */ + /// the ID - determines which jar to use! ACTUALLY IMPORTANT! + QString id; + + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + + /// Release type - "release" or "snapshot" + QString type; + /// Assets type - "legacy" or a version ID + QString assets; + /** + * DEPRECATED: Old versions of the new vanilla launcher used this + * ex: "username_session_version" + */ + QString processArguments; + /// Same as above, but only for vanilla + QString vanillaProcessArguments; + /** + * arguments that should be used for launching minecraft + * + * ex: "--username ${auth_player_name} --session ${auth_session} + * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" + */ + QString minecraftArguments; + /// Same as above, but only for vanilla + QString vanillaMinecraftArguments; + /** + * the minimum launcher version required by this version ... current is 4 (at point of + * writing) + */ + int minimumLauncherVersion = 0xDEADBEEF; + /** + * A list of all tweaker classes + */ + QStringList tweakers; + /** + * The main class to load first + */ + QString mainClass; + /** + * The applet class, for some very old minecraft releases + */ + QString appletClass; + + /// the list of libs - both active and inactive, native and java + QList libraries; + + /// same, but only vanilla. + QList vanillaLibraries; + + /// traits, collected from all the version files (version files can only add) + QSet traits; + + /// A list of jar mods. version files can add those. + QList jarMods; + + /* + FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. + + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx", + "version": "^10\\.5\\.\\d$" + } + } + ], + "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX + 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" + } + */ + // QList rules; +private: + QList VersionPatches; + ProfileStrategy *m_strategy = nullptr; +}; diff --git a/logic/minecraft/MinecraftVersion.cpp b/logic/minecraft/MinecraftVersion.cpp index bd53997b..32682bfb 100644 --- a/logic/minecraft/MinecraftVersion.cpp +++ b/logic/minecraft/MinecraftVersion.cpp @@ -1,7 +1,8 @@ #include "MinecraftVersion.h" -#include "InstanceVersion.h" +#include "MinecraftProfile.h" #include "VersionBuildError.h" #include "VersionBuilder.h" +#include "ProfileUtils.h" #include "MultiMC.h" #include "logic/settings/SettingsObject.h" @@ -56,15 +57,20 @@ bool MinecraftVersion::isMinecraftVersion() // 1. assume the local file is good. load, check. If it's good, apply. // 2. if discrepancies are found, fall out and fail (impossible to apply incomplete version). -void MinecraftVersion::applyFileTo(InstanceVersion *version) +void MinecraftVersion::applyFileTo(MinecraftProfile *version) +{ + getVersionFile()->applyTo(version); +} + +VersionFilePtr MinecraftVersion::getVersionFile() { QFileInfo versionFile(QString("versions/%1/%1.dat").arg(m_descriptor)); - - auto versionObj = VersionBuilder::parseBinaryJsonFile(versionFile); - versionObj->applyTo(version); + + return ProfileUtils::parseBinaryJsonFile(versionFile); } -void MinecraftVersion::applyTo(InstanceVersion *version) + +void MinecraftVersion::applyTo(MinecraftProfile *version) { // do we have this one cached? if (m_versionSource == Local) diff --git a/logic/minecraft/MinecraftVersion.h b/logic/minecraft/MinecraftVersion.h index 84d0773e..fbb209f2 100644 --- a/logic/minecraft/MinecraftVersion.h +++ b/logic/minecraft/MinecraftVersion.h @@ -20,15 +20,15 @@ #include #include "logic/BaseVersion.h" -#include "VersionPatch.h" +#include "ProfilePatch.h" #include "VersionFile.h" #include "VersionSource.h" -class InstanceVersion; +class MinecraftProfile; class MinecraftVersion; typedef std::shared_ptr MinecraftVersionPtr; -class MinecraftVersion : public BaseVersion, public VersionPatch +class MinecraftVersion : public BaseVersion, public ProfilePatch { public: /* methods */ bool usesLegacyLauncher(); @@ -37,9 +37,9 @@ public: /* methods */ virtual QString typeString() const override; virtual bool hasJarMods() override; virtual bool isMinecraftVersion() override; - virtual void applyTo(InstanceVersion *version) override; - virtual int getOrder(); - virtual void setOrder(int order); + virtual void applyTo(MinecraftProfile *version) override; + virtual int getOrder() override; + virtual void setOrder(int order) override; virtual QList getJarMods() override; virtual QString getPatchID() override; virtual QString getPatchVersion() override; @@ -47,10 +47,12 @@ public: /* methods */ virtual QString getPatchFilename() override; bool needsUpdate(); bool hasUpdate(); - virtual bool isCustom(); + virtual bool isCustom() override; + + VersionFilePtr getVersionFile(); private: /* methods */ - void applyFileTo(InstanceVersion *version); + void applyFileTo(MinecraftProfile *version); public: /* data */ /// The URL that this version will be downloaded from. maybe. @@ -92,7 +94,7 @@ public: /* data */ /// order of this file... default = -2 int order = -2; - + /// an update available from Mojang MinecraftVersionPtr upstreamUpdate; }; diff --git a/logic/minecraft/MinecraftVersionList.cpp b/logic/minecraft/MinecraftVersionList.cpp index a93ea301..68e93d54 100644 --- a/logic/minecraft/MinecraftVersionList.cpp +++ b/logic/minecraft/MinecraftVersionList.cpp @@ -25,12 +25,53 @@ #include "logic/net/URLConstants.h" #include "ParseUtils.h" +#include "ProfileUtils.h" #include "VersionBuilder.h" -#include +#include "VersionFilterData.h" + #include static const char * localVersionCache = "versions/versions.dat"; +class MCVListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit MCVListLoadTask(MinecraftVersionList *vlist); + virtual ~MCVListLoadTask() override{}; + + virtual void executeTask() override; + +protected +slots: + void list_downloaded(); + +protected: + QNetworkReply *vlistReply; + MinecraftVersionList *m_list; + MinecraftVersion *m_currentStable; +}; + +class MCVListVersionUpdateTask : public Task +{ + Q_OBJECT + +public: + explicit MCVListVersionUpdateTask(MinecraftVersionList *vlist, QString updatedVersion); + virtual ~MCVListVersionUpdateTask() override{}; + virtual void executeTask() override; + +protected +slots: + void json_downloaded(); + +protected: + NetJobPtr specificVersionDownloadJob; + QString versionToUpdate; + MinecraftVersionList *m_list; +}; + class ListLoadError : public MMCError { public: @@ -442,21 +483,9 @@ void MCVListVersionUpdateTask::json_downloaded() emitFailed(tr("Couldn't process version file: %1").arg(e.cause())); return; } - QList filteredLibs; - QList lwjglLibs; - for (auto lib : file->overwriteLibs) - { - if (g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix())) - { - lwjglLibs.append(lib); - } - else - { - filteredLibs.append(lib); - } - } - file->overwriteLibs = filteredLibs; + // Strip LWJGL from the version file. We use our own. + ProfileUtils::removeLwjglFromPatch(file); // TODO: recognize and add LWJGL versions here. @@ -523,7 +552,7 @@ void MinecraftVersionList::saveCachedList() entriesArr.append(entryObj); } toplevel.insert("versions", entriesArr); - + { bool someLatest = false; QJsonObject latestObj; @@ -542,7 +571,7 @@ void MinecraftVersionList::saveCachedList() toplevel.insert("latest", latestObj); } } - + QJsonDocument doc(toplevel); QByteArray jsonData = doc.toBinaryData(); qint64 result = tfile.write(jsonData); @@ -593,3 +622,5 @@ void MinecraftVersionList::finalizeUpdate(QString version) saveCachedList(); } + +#include "MinecraftVersionList.moc" \ No newline at end of file diff --git a/logic/minecraft/MinecraftVersionList.h b/logic/minecraft/MinecraftVersionList.h index 17708444..be9f8581 100644 --- a/logic/minecraft/MinecraftVersionList.h +++ b/logic/minecraft/MinecraftVersionList.h @@ -26,7 +26,6 @@ class MCVListLoadTask; class MCVListVersionUpdateTask; -class QNetworkReply; class MinecraftVersionList : public BaseVersionList { @@ -67,42 +66,3 @@ protected slots: virtual void updateListData(QList versions); }; - -class MCVListLoadTask : public Task -{ - Q_OBJECT - -public: - explicit MCVListLoadTask(MinecraftVersionList *vlist); - virtual ~MCVListLoadTask() override{}; - - virtual void executeTask() override; - -protected -slots: - void list_downloaded(); - -protected: - QNetworkReply *vlistReply; - MinecraftVersionList *m_list; - MinecraftVersion *m_currentStable; -}; - -class MCVListVersionUpdateTask : public Task -{ - Q_OBJECT - -public: - explicit MCVListVersionUpdateTask(MinecraftVersionList *vlist, QString updatedVersion); - virtual ~MCVListVersionUpdateTask() override{}; - virtual void executeTask() override; - -protected -slots: - void json_downloaded(); - -protected: - NetJobPtr specificVersionDownloadJob; - QString versionToUpdate; - MinecraftVersionList *m_list; -}; diff --git a/logic/minecraft/NullProfileStrategy.h b/logic/minecraft/NullProfileStrategy.h new file mode 100644 index 00000000..eaabd3c7 --- /dev/null +++ b/logic/minecraft/NullProfileStrategy.h @@ -0,0 +1,24 @@ +#pragma once + +#include "ProfileStrategy.h" + +class NullProfileStrategy: public ProfileStrategy +{ + virtual bool installJarMods(QStringList filepaths) + { + return false; + } + virtual void load() {}; + virtual bool removePatch(ProfilePatchPtr jarMod) + { + return false; + } + virtual bool resetOrder() + { + return false; + } + virtual bool saveOrder(ProfileUtils::PatchOrder order) + { + return false; + } +}; \ No newline at end of file diff --git a/logic/minecraft/OneSixLibrary.cpp b/logic/minecraft/OneSixLibrary.cpp index 67257a55..43d381fb 100644 --- a/logic/minecraft/OneSixLibrary.cpp +++ b/logic/minecraft/OneSixLibrary.cpp @@ -18,10 +18,6 @@ #include "OneSixLibrary.h" #include "OneSixRule.h" #include "OpSys.h" -#include "logic/net/URLConstants.h" -#include -#include -#include "logger/QsLog.h" OneSixLibrary::OneSixLibrary(RawLibraryPtr base) { diff --git a/logic/minecraft/OneSixProfileStrategy.cpp b/logic/minecraft/OneSixProfileStrategy.cpp new file mode 100644 index 00000000..bdb2f8ee --- /dev/null +++ b/logic/minecraft/OneSixProfileStrategy.cpp @@ -0,0 +1,270 @@ +#include "logic/minecraft/OneSixProfileStrategy.h" +#include "logic/minecraft/VersionBuildError.h" +#include "logic/OneSixInstance.h" +#include "logic/minecraft/MinecraftVersionList.h" + +#include "MultiMC.h" + +#include +#include +#include +#include +#include + +OneSixProfileStrategy::OneSixProfileStrategy(OneSixInstance* instance) +{ + m_instance = instance; +} + +void OneSixProfileStrategy::upgradeDeprecatedFiles() +{ + auto versionJsonPath = PathCombine(m_instance->instanceRoot(), "version.json"); + auto customJsonPath = PathCombine(m_instance->instanceRoot(), "custom.json"); + auto mcJson = PathCombine(m_instance->instanceRoot(), "patches" , "net.minecraft.json"); + + // convert old crap. + if(QFile::exists(customJsonPath)) + { + if(!ensureFilePathExists(mcJson)) + { + // WHAT DO??? + } + if(!QFile::rename(customJsonPath, mcJson)) + { + // WHAT DO??? + } + if(QFile::exists(versionJsonPath)) + { + if(!QFile::remove(versionJsonPath)) + { + // WHAT DO??? + } + } + } + else if(QFile::exists(versionJsonPath)) + { + if(!ensureFilePathExists(mcJson)) + { + // WHAT DO??? + } + if(!QFile::rename(versionJsonPath, mcJson)) + { + // WHAT DO??? + } + } +} + + +void OneSixProfileStrategy::loadDefaultBuiltinPatches() +{ + auto mcJson = PathCombine(m_instance->instanceRoot(), "patches" , "net.minecraft.json"); + // load up the base minecraft patch + ProfilePatchPtr minecraftPatch; + if(QFile::exists(mcJson)) + { + auto file = ProfileUtils::parseJsonFile(QFileInfo(mcJson), false); + file->fileId = "net.minecraft"; + file->name = "Minecraft"; + if(file->version.isEmpty()) + { + file->version = m_instance->intendedVersionId(); + } + minecraftPatch = std::dynamic_pointer_cast(file); + } + else + { + auto minecraftList = MMC->minecraftlist(); + auto mcversion = minecraftList->findVersion(m_instance->intendedVersionId()); + minecraftPatch = std::dynamic_pointer_cast(mcversion); + } + if (!minecraftPatch) + { + throw VersionIncomplete("net.minecraft"); + } + minecraftPatch->setOrder(-2); + profile->appendPatch(minecraftPatch); + + + // TODO: this is obviously fake. + QResource LWJGL(":/versions/LWJGL/2.9.1.json"); + auto lwjgl = ProfileUtils::parseJsonFile(LWJGL.absoluteFilePath(), false, false); + auto lwjglPatch = std::dynamic_pointer_cast(lwjgl); + if (!lwjglPatch) + { + throw VersionIncomplete("org.lwjgl"); + } + lwjglPatch->setOrder(-1); + lwjgl->setVanilla(true); + profile->appendPatch(lwjglPatch); +} + +void OneSixProfileStrategy::loadUserPatches() +{ + // load all patches, put into map for ordering, apply in the right order + ProfileUtils::PatchOrder userOrder; + ProfileUtils::readOverrideOrders(PathCombine(m_instance->instanceRoot(), "order.json"), userOrder); + QDir patches(PathCombine(m_instance->instanceRoot(),"patches")); + + // first, load things by sort order. + for (auto id : userOrder) + { + // ignore builtins + if (id == "net.minecraft") + continue; + if (id == "org.lwjgl") + continue; + // parse the file + QString filename = patches.absoluteFilePath(id + ".json"); + QFileInfo finfo(filename); + if(!finfo.exists()) + { + QLOG_INFO() << "Patch file " << filename << " was deleted by external means..."; + continue; + } + QLOG_INFO() << "Reading" << filename << "by user order"; + auto file = ProfileUtils::parseJsonFile(finfo, false); + // sanity check. prevent tampering with files. + if (file->fileId != id) + { + throw VersionBuildError( + QObject::tr("load id %1 does not match internal id %2").arg(id, file->fileId)); + } + profile->appendPatch(file); + } + // now load the rest by internal preference. + QMap> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + // parse the file + QLOG_INFO() << "Reading" << info.fileName(); + auto file = ProfileUtils::parseJsonFile(info, true); + // ignore builtins + if (file->fileId == "net.minecraft") + continue; + if (file->fileId == "org.lwjgl") + continue; + // do not load what we already loaded in the first pass + if (userOrder.contains(file->fileId)) + continue; + if (files.contains(file->order)) + { + // FIXME: do not throw? + throw VersionBuildError(QObject::tr("%1 has the same order as %2") + .arg(file->fileId, files[file->order].second->fileId)); + } + files.insert(file->order, qMakePair(info.fileName(), file)); + } + for (auto order : files.keys()) + { + auto &filePair = files[order]; + profile->appendPatch(filePair.second); + } +} + + +void OneSixProfileStrategy::load() +{ + profile->clearPatches(); + + upgradeDeprecatedFiles(); + loadDefaultBuiltinPatches(); + loadUserPatches(); + + profile->finalize(); +} + +bool OneSixProfileStrategy::saveOrder(ProfileUtils::PatchOrder order) +{ + return ProfileUtils::writeOverrideOrders(PathCombine(m_instance->instanceRoot(), "order.json"), order); +} + +bool OneSixProfileStrategy::resetOrder() +{ + return QDir(m_instance->instanceRoot()).remove("order.json"); +} + +bool OneSixProfileStrategy::removePatch(ProfilePatchPtr patch) +{ + bool ok = true; + // first, remove the patch file. this ensures it's not used anymore + auto fileName = patch->getPatchFilename(); + + + auto preRemoveJarMod = [&](JarmodPtr jarMod) -> bool + { + QString fullpath = PathCombine(m_instance->jarModsDir(), jarMod->name); + QFileInfo finfo (fullpath); + if(finfo.exists()) + { + return QFile::remove(fullpath); + } + return true; + }; + + for(auto &jarmod: patch->getJarMods()) + { + ok &= preRemoveJarMod(jarmod); + } + return ok; +} + +bool OneSixProfileStrategy::installJarMods(QStringList filepaths) +{ + QString patchDir = PathCombine(m_instance->instanceRoot(), "patches"); + if(!ensureFolderPathExists(patchDir)) + { + return false; + } + + if (!ensureFolderPathExists(m_instance->jarModsDir())) + { + return false; + } + + for(auto filepath:filepaths) + { + QFileInfo sourceInfo(filepath); + auto uuid = QUuid::createUuid(); + QString id = uuid.toString().remove('{').remove('}'); + QString target_filename = id + ".jar"; + QString target_id = "org.multimc.jarmod." + id; + QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; + QString finalPath = PathCombine(m_instance->jarModsDir(), target_filename); + + QFileInfo targetInfo(finalPath); + if(targetInfo.exists()) + { + return false; + } + + if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) + { + return false; + } + + auto f = std::make_shared(); + auto jarMod = std::make_shared(); + jarMod->name = target_filename; + f->jarMods.append(jarMod); + f->name = target_name; + f->fileId = target_id; + f->order = profile->getFreeOrderNumber(); + QString patchFileName = PathCombine(patchDir, target_id + ".json"); + f->filename = patchFileName; + + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(f->toJson(true).toJson()); + file.close(); + profile->appendPatch(f); + } + profile->saveCurrentOrder(); + profile->reapply(); + return true; +} + diff --git a/logic/minecraft/OneSixProfileStrategy.h b/logic/minecraft/OneSixProfileStrategy.h new file mode 100644 index 00000000..b554b1ea --- /dev/null +++ b/logic/minecraft/OneSixProfileStrategy.h @@ -0,0 +1,24 @@ +#pragma once +#include "ProfileStrategy.h" + +class OneSixInstance; + +class OneSixProfileStrategy : public ProfileStrategy +{ +public: + OneSixProfileStrategy(OneSixInstance * instance); + virtual ~OneSixProfileStrategy() {}; + virtual void load() override; + virtual bool resetOrder() override; + virtual bool saveOrder(ProfileUtils::PatchOrder order) override; + virtual bool installJarMods(QStringList filepaths) override; + virtual bool removePatch(ProfilePatchPtr patch) override; + +protected: + void loadDefaultBuiltinPatches(); + void loadUserPatches(); + void upgradeDeprecatedFiles(); + +protected: + OneSixInstance *m_instance; +}; \ No newline at end of file diff --git a/logic/minecraft/ProfilePatch.h b/logic/minecraft/ProfilePatch.h new file mode 100644 index 00000000..2e97677e --- /dev/null +++ b/logic/minecraft/ProfilePatch.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include "JarMod.h" + +class MinecraftProfile; +class ProfilePatch +{ +public: + virtual ~ProfilePatch(){}; + virtual void applyTo(MinecraftProfile *version) = 0; + + virtual bool isMinecraftVersion() = 0; + virtual bool hasJarMods() = 0; + virtual QList getJarMods() = 0; + + virtual bool isMoveable() + { + return getOrder() >= 0; + } + virtual void setOrder(int order) = 0; + virtual int getOrder() = 0; + + virtual QString getPatchID() = 0; + virtual QString getPatchName() = 0; + virtual QString getPatchVersion() = 0; + virtual QString getPatchFilename() = 0; + virtual bool isCustom() = 0; +}; + +typedef std::shared_ptr ProfilePatchPtr; diff --git a/logic/minecraft/ProfileStrategy.h b/logic/minecraft/ProfileStrategy.h new file mode 100644 index 00000000..364458f5 --- /dev/null +++ b/logic/minecraft/ProfileStrategy.h @@ -0,0 +1,30 @@ +#pragma once + +#include "ProfileUtils.h" + +class MinecraftProfile; + +class ProfileStrategy +{ + friend class MinecraftProfile; +public: + virtual ~ProfileStrategy(){}; + + /// load the patch files into the profile + virtual void load() = 0; + + /// reset the order of patches + virtual bool resetOrder() = 0; + + /// save the order of patches, given the order + virtual bool saveOrder(ProfileUtils::PatchOrder order) = 0; + + /// install a list of jar mods into the instance + virtual bool installJarMods(QStringList filepaths) = 0; + + /// remove any files or records that constitute the version patch + virtual bool removePatch(ProfilePatchPtr jarMod) = 0; + +protected: + MinecraftProfile *profile; +}; diff --git a/logic/minecraft/ProfileUtils.cpp b/logic/minecraft/ProfileUtils.cpp new file mode 100644 index 00000000..48e73472 --- /dev/null +++ b/logic/minecraft/ProfileUtils.cpp @@ -0,0 +1,145 @@ +#include "ProfileUtils.h" +#include "logic/minecraft/VersionFilterData.h" +#include "logic/MMCJson.h" +#include "logger/QsLog.h" + +#include +#include +#include + +namespace ProfileUtils +{ + +static const int currentOrderFileVersion = 1; + +bool writeOverrideOrders(QString path, const PatchOrder &order) +{ + QJsonObject obj; + obj.insert("version", currentOrderFileVersion); + QJsonArray orderArray; + for(auto str: order) + { + orderArray.append(str); + } + obj.insert("order", orderArray); + QFile orderFile(path); + if (!orderFile.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << "for writing:" << orderFile.errorString(); + return false; + } + orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); + return true; +} + +bool readOverrideOrders(QString path, PatchOrder &order) +{ + QFile orderFile(path); + if (!orderFile.exists()) + { + QLOG_WARN() << "Order file doesn't exist. Ignoring."; + return false; + } + if (!orderFile.open(QFile::ReadOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << " for reading:" << orderFile.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return false; + } + + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return false; + } + + // and then read it and process it if all above is true. + try + { + auto obj = MMCJson::ensureObject(doc); + // check order file version. + auto version = MMCJson::ensureInteger(obj.value("version"), "version"); + if (version != currentOrderFileVersion) + { + throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") + .arg(currentOrderFileVersion)); + } + auto orderArray = MMCJson::ensureArray(obj.value("order")); + for(auto item: orderArray) + { + order.append(MMCJson::ensureString(item)); + } + } + catch (JSONValidationError &err) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; + QLOG_WARN() << "Ignoring overriden order"; + order.clear(); + return false; + } + return true; +} + +VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, bool isFTB) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") + .arg(fileInfo.fileName(), file.errorString())); + } + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + throw JSONValidationError( + QObject::tr("Unable to process the version file %1: %2 at %3.") + .arg(fileInfo.fileName(), error.errorString()) + .arg(error.offset)); + } + return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); +} + +VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") + .arg(fileInfo.fileName(), file.errorString())); + } + QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); + file.close(); + if (doc.isNull()) + { + file.remove(); + throw JSONValidationError( + QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); + } + return VersionFile::fromJson(doc, file.fileName(), false, false); +} + +void removeLwjglFromPatch(VersionFilePtr patch) +{ + auto filter = [](QList& libs) + { + QList filteredLibs; + for (auto lib : libs) + { + if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix())) + { + filteredLibs.append(lib); + } + } + libs = filteredLibs; + }; + filter(patch->addLibs); + filter(patch->overwriteLibs); +} +} diff --git a/logic/minecraft/ProfileUtils.h b/logic/minecraft/ProfileUtils.h new file mode 100644 index 00000000..88027374 --- /dev/null +++ b/logic/minecraft/ProfileUtils.h @@ -0,0 +1,25 @@ +#pragma once +#include "RawLibrary.h" +#include "VersionFile.h" + +namespace ProfileUtils +{ +typedef QStringList PatchOrder; + +/// Read and parse a OneSix format order file +bool readOverrideOrders(QString path, PatchOrder &order); + +/// Write a OneSix format order file +bool writeOverrideOrders(QString path, const PatchOrder &order); + + +/// Parse a version file in JSON format +VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, bool isFTB = false); + +/// Parse a version file in binary JSON format +VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo); + +/// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files. +void removeLwjglFromPatch(VersionFilePtr patch); + +} diff --git a/logic/minecraft/VersionBuilder.cpp b/logic/minecraft/VersionBuilder.cpp index b1622b58..7f77b493 100644 --- a/logic/minecraft/VersionBuilder.cpp +++ b/logic/minecraft/VersionBuilder.cpp @@ -24,15 +24,17 @@ #include #include #include +#include #include "MultiMC.h" #include "logic/minecraft/VersionBuilder.h" -#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/MinecraftProfile.h" #include "logic/minecraft/OneSixRule.h" -#include "logic/minecraft/VersionPatch.h" +#include "logic/minecraft/ProfilePatch.h" #include "logic/minecraft/VersionFile.h" #include "VersionBuildError.h" #include "MinecraftVersionList.h" +#include "ProfileUtils.h" #include "logic/OneSixInstance.h" #include "logic/MMCJson.h" @@ -43,17 +45,15 @@ VersionBuilder::VersionBuilder() { } -void VersionBuilder::build(InstanceVersion *version, OneSixInstance *instance, - const QStringList &external) +void VersionBuilder::build(MinecraftProfile *version, OneSixInstance *instance) { VersionBuilder builder; builder.m_version = version; builder.m_instance = instance; - builder.external_patches = external; builder.buildInternal(); } -void VersionBuilder::readJsonAndApplyToVersion(InstanceVersion *version, const QJsonObject &obj) +void VersionBuilder::readJsonAndApplyToVersion(MinecraftProfile *version, const QJsonObject &obj) { VersionBuilder builder; builder.m_version = version; @@ -61,178 +61,6 @@ void VersionBuilder::readJsonAndApplyToVersion(InstanceVersion *version, const Q builder.readJsonAndApply(obj); } -void VersionBuilder::buildFromCustomJson() -{ - QLOG_INFO() << "Building version from custom.json within the instance."; - QLOG_INFO() << "Reading custom.json"; - auto file = parseJsonFile(QFileInfo(instance_root.absoluteFilePath("custom.json")), false); - file->name = "custom.json"; - file->filename = "custom.json"; - file->fileId = "org.multimc.custom.json"; - file->order = -1; - file->version = QString(); - m_version->VersionPatches.append(file); - m_version->finalize(); - return; -} - -void VersionBuilder::buildFromVersionJson() -{ - QLOG_INFO() << "Building version from version.json and patches within the instance."; - QLOG_INFO() << "Reading version.json"; - auto file = parseJsonFile(QFileInfo(instance_root.absoluteFilePath("version.json")), false); - file->name = "Minecraft"; - file->fileId = "org.multimc.version.json"; - file->order = -1; - file->version = m_instance->intendedVersionId(); - file->mcVersion = m_instance->intendedVersionId(); - m_version->VersionPatches.append(file); - - // load all patches, put into map for ordering, apply in the right order - readInstancePatches(); - - // some final touches - m_version->finalize(); -} - -void VersionBuilder::readInstancePatches() -{ - PatchOrder userOrder; - readOverrideOrders(m_instance, userOrder); - QDir patches(instance_root.absoluteFilePath("patches/")); - - // first, load things by sort order. - for (auto id : userOrder) - { - // ignore builtins - if (id == "net.minecraft") - continue; - if (id == "org.lwjgl") - continue; - // parse the file - QString filename = patches.absoluteFilePath(id + ".json"); - QFileInfo finfo(filename); - if(!finfo.exists()) - { - QLOG_INFO() << "Patch file " << filename << " was deleted by external means..."; - continue; - } - QLOG_INFO() << "Reading" << filename << "by user order"; - auto file = parseJsonFile(finfo, false); - // sanity check. prevent tampering with files. - if (file->fileId != id) - { - throw VersionBuildError( - QObject::tr("load id %1 does not match internal id %2").arg(id, file->fileId)); - } - m_version->VersionPatches.append(file); - } - // now load the rest by internal preference. - QMap> files; - for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - // parse the file - QLOG_INFO() << "Reading" << info.fileName(); - auto file = parseJsonFile(info, true); - // ignore builtins - if (file->fileId == "net.minecraft") - continue; - if (file->fileId == "org.lwjgl") - continue; - // do not load what we already loaded in the first pass - if (userOrder.contains(file->fileId)) - continue; - if (files.contains(file->order)) - { - // FIXME: do not throw? - throw VersionBuildError(QObject::tr("%1 has the same order as %2") - .arg(file->fileId, files[file->order].second->fileId)); - } - files.insert(file->order, qMakePair(info.fileName(), file)); - } - for (auto order : files.keys()) - { - auto &filePair = files[order]; - m_version->VersionPatches.append(filePair.second); - } -} - -void VersionBuilder::buildFromExternalPatches() -{ - QLOG_INFO() << "Building version from external files."; - int externalOrder = -1; - for (auto fileName : external_patches) - { - QLOG_INFO() << "Reading" << fileName; - auto file = parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); - file->name = QFileInfo(fileName).fileName(); - file->fileId = "org.multimc.external." + file->name; - file->order = (externalOrder += 1); - file->version = QString(); - file->mcVersion = QString(); - m_version->VersionPatches.append(file); - } - // some final touches - m_version->finalize(); -} - -void VersionBuilder::buildFromMultilayer() -{ - QLOG_INFO() << "Building version from multilayered sources."; - // just the builtin stuff for now - auto minecraftList = MMC->minecraftlist(); - auto mcversion = minecraftList->findVersion(m_instance->intendedVersionId()); - auto minecraftPatch = std::dynamic_pointer_cast(mcversion); - if (!minecraftPatch) - { - throw VersionIncomplete("net.minecraft"); - } - minecraftPatch->setOrder(-2); - m_version->VersionPatches.append(minecraftPatch); - - // TODO: this is obviously fake. - QResource LWJGL(":/versions/LWJGL/2.9.1.json"); - auto lwjgl = parseJsonFile(LWJGL.absoluteFilePath(), false, false); - auto lwjglPatch = std::dynamic_pointer_cast(lwjgl); - if (!lwjglPatch) - { - throw VersionIncomplete("org.lwjgl"); - } - lwjglPatch->setOrder(-1); - lwjgl->setVanilla(true); - m_version->VersionPatches.append(lwjglPatch); - - // load all patches, put into map for ordering, apply in the right order - readInstancePatches(); - - m_version->finalize(); -} - -void VersionBuilder::buildInternal() -{ - m_version->VersionPatches.clear(); - instance_root = QDir(m_instance->instanceRoot()); - // if we do external files, do just those. - if (!external_patches.isEmpty()) - { - buildFromExternalPatches(); - } - // else, if there's custom json, we just do that. - else if (QFile::exists(instance_root.absoluteFilePath("custom.json"))) - { - buildFromCustomJson(); - } - // version.json -> patches/*.json - else if (QFile::exists(instance_root.absoluteFilePath("version.json"))) - { - buildFromVersionJson(); - } - else - { - buildFromMultilayer(); - } -} - void VersionBuilder::readJsonAndApply(const QJsonObject &obj) { m_version->clear(); @@ -240,121 +68,17 @@ void VersionBuilder::readJsonAndApply(const QJsonObject &obj) auto file = VersionFile::fromJson(QJsonDocument(obj), QString(), false); file->applyTo(m_version); - m_version->VersionPatches.append(file); + m_version->appendPatch(file); } -VersionFilePtr VersionBuilder::parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, - bool isFTB) -{ - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") - .arg(fileInfo.fileName(), file.errorString())); - } - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - throw JSONValidationError( - QObject::tr("Unable to process the version file %1: %2 at %3.") - .arg(fileInfo.fileName(), error.errorString()) - .arg(error.offset)); - } - return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); -} -VersionFilePtr VersionBuilder::parseBinaryJsonFile(const QFileInfo &fileInfo) +void VersionBuilder::readInstancePatches() { - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") - .arg(fileInfo.fileName(), file.errorString())); - } - QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); - file.close(); - if (doc.isNull()) - { - file.remove(); - throw JSONValidationError( - QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); - } - return VersionFile::fromJson(doc, file.fileName(), false, false); -} -static const int currentOrderFileVersion = 1; +} -bool VersionBuilder::readOverrideOrders(OneSixInstance *instance, PatchOrder &order) +void VersionBuilder::buildInternal() { - QFile orderFile(instance->instanceRoot() + "/order.json"); - if (!orderFile.exists()) - { - QLOG_WARN() << "Order file doesn't exist. Ignoring."; - return false; - } - if (!orderFile.open(QFile::ReadOnly)) - { - QLOG_ERROR() << "Couldn't open" << orderFile.fileName() - << " for reading:" << orderFile.errorString(); - QLOG_WARN() << "Ignoring overriden order"; - return false; - } - - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); - QLOG_WARN() << "Ignoring overriden order"; - return false; - } - // and then read it and process it if all above is true. - try - { - auto obj = MMCJson::ensureObject(doc); - // check order file version. - auto version = MMCJson::ensureInteger(obj.value("version"), "version"); - if (version != currentOrderFileVersion) - { - throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") - .arg(currentOrderFileVersion)); - } - auto orderArray = MMCJson::ensureArray(obj.value("order")); - for(auto item: orderArray) - { - order.append(MMCJson::ensureString(item)); - } - } - catch (JSONValidationError &err) - { - QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; - QLOG_WARN() << "Ignoring overriden order"; - order.clear(); - return false; - } - return true; } -bool VersionBuilder::writeOverrideOrders(OneSixInstance *instance, const PatchOrder &order) -{ - QJsonObject obj; - obj.insert("version", currentOrderFileVersion); - QJsonArray orderArray; - for(auto str: order) - { - orderArray.append(str); - } - obj.insert("order", orderArray); - QFile orderFile(instance->instanceRoot() + "/order.json"); - if (!orderFile.open(QFile::WriteOnly)) - { - QLOG_ERROR() << "Couldn't open" << orderFile.fileName() - << "for writing:" << orderFile.errorString(); - return false; - } - orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); - return true; -} diff --git a/logic/minecraft/VersionBuilder.h b/logic/minecraft/VersionBuilder.h index 2f7e6b6b..b2d23da5 100644 --- a/logic/minecraft/VersionBuilder.h +++ b/logic/minecraft/VersionBuilder.h @@ -19,38 +19,25 @@ #include #include "VersionFile.h" -class InstanceVersion; +class MinecraftProfile; class OneSixInstance; class QJsonObject; class QFileInfo; -typedef QStringList PatchOrder; - class VersionBuilder { VersionBuilder(); public: - static void build(InstanceVersion *version, OneSixInstance *instance, const QStringList &external); - static void readJsonAndApplyToVersion(InstanceVersion *version, const QJsonObject &obj); - static VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, bool isFTB = false); - static VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo); - - bool readOverrideOrders(OneSixInstance *instance, PatchOrder &order); - static bool writeOverrideOrders(OneSixInstance *instance, const PatchOrder &order); + static void build(MinecraftProfile *version, OneSixInstance *instance); + static void readJsonAndApplyToVersion(MinecraftProfile *version, const QJsonObject &obj); private: - InstanceVersion *m_version; + MinecraftProfile *m_version; OneSixInstance *m_instance; - QStringList external_patches; - QDir instance_root; - + void buildInternal(); - void buildFromExternalPatches(); - void buildFromCustomJson(); - void buildFromVersionJson(); - void buildFromMultilayer(); - + void readInstancePatches(); - + void readJsonAndApply(const QJsonObject &obj); }; diff --git a/logic/minecraft/VersionFile.cpp b/logic/minecraft/VersionFile.cpp index 3e0648cc..97ce6535 100644 --- a/logic/minecraft/VersionFile.cpp +++ b/logic/minecraft/VersionFile.cpp @@ -6,7 +6,7 @@ #include "logic/minecraft/VersionFile.h" #include "logic/minecraft/OneSixLibrary.h" -#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/MinecraftProfile.h" #include "logic/minecraft/JarMod.h" #include "ParseUtils.h" @@ -256,8 +256,7 @@ QJsonDocument VersionFile::toJson(bool saveOrder) bool VersionFile::isMinecraftVersion() { - return (fileId == "org.multimc.version.json") || (fileId == "net.minecraft") || - (fileId == "org.multimc.custom.json"); + return fileId == "net.minecraft"; } bool VersionFile::hasJarMods() @@ -265,7 +264,7 @@ bool VersionFile::hasJarMods() return !jarMods.isEmpty(); } -void VersionFile::applyTo(InstanceVersion *version) +void VersionFile::applyTo(MinecraftProfile *version) { if (minimumLauncherVersion != -1) { diff --git a/logic/minecraft/VersionFile.h b/logic/minecraft/VersionFile.h index 96856891..ddc3f849 100644 --- a/logic/minecraft/VersionFile.h +++ b/logic/minecraft/VersionFile.h @@ -6,23 +6,23 @@ #include #include "logic/minecraft/OpSys.h" #include "logic/minecraft/OneSixRule.h" -#include "VersionPatch.h" +#include "ProfilePatch.h" #include "MMCError.h" #include "OneSixLibrary.h" #include "JarMod.h" -class InstanceVersion; +class MinecraftProfile; class VersionFile; typedef std::shared_ptr VersionFilePtr; -class VersionFile : public VersionPatch +class VersionFile : public ProfilePatch { public: /* methods */ static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder, const bool isFTB = false); QJsonDocument toJson(bool saveOrder); - virtual void applyTo(InstanceVersion *version) override; + virtual void applyTo(MinecraftProfile *version) override; virtual bool isMinecraftVersion() override; virtual bool hasJarMods() override; virtual int getOrder() override diff --git a/logic/minecraft/VersionFilterData.cpp b/logic/minecraft/VersionFilterData.cpp new file mode 100644 index 00000000..ef8b049e --- /dev/null +++ b/logic/minecraft/VersionFilterData.cpp @@ -0,0 +1,72 @@ +#include "VersionFilterData.h" +#include "ParseUtils.h" + +VersionFilterData g_VersionFilterData = VersionFilterData(); + +VersionFilterData::VersionFilterData() +{ + // 1.3.* + auto libs13 = + QList{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}}; + + fmlLibsMapping["1.3.2"] = libs13; + + // 1.4.* + auto libs14 = QList{ + {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}, + {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}}; + + fmlLibsMapping["1.4"] = libs14; + fmlLibsMapping["1.4.1"] = libs14; + fmlLibsMapping["1.4.2"] = libs14; + fmlLibsMapping["1.4.3"] = libs14; + fmlLibsMapping["1.4.4"] = libs14; + fmlLibsMapping["1.4.5"] = libs14; + fmlLibsMapping["1.4.6"] = libs14; + fmlLibsMapping["1.4.7"] = libs14; + + // 1.5 + fmlLibsMapping["1.5"] = QList{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + + // 1.5.1 + fmlLibsMapping["1.5.1"] = QList{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + + // 1.5.2 + fmlLibsMapping["1.5.2"] = QList{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + + // don't use installers for those. + forgeInstallerBlacklist = QSet({"1.5.2"}); + // these won't show up in version lists because they are extremely bad and dangerous + legacyBlacklist = QSet({"rd-160052"}); + /* + * nothing older than this will be accepted from Mojang servers + * (these versions need to be tested by us first) + */ + legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); + lwjglWhitelist = + QSet{"net.java.jinput:jinput", "net.java.jinput:jinput-platform", + "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", + "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; +} diff --git a/logic/minecraft/VersionFilterData.h b/logic/minecraft/VersionFilterData.h new file mode 100644 index 00000000..98ecb36c --- /dev/null +++ b/logic/minecraft/VersionFilterData.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include +#include + +struct FMLlib +{ + QString filename; + QString checksum; + bool ours; +}; + +struct VersionFilterData +{ + VersionFilterData(); + // mapping between minecraft versions and FML libraries required + QMap> fmlLibsMapping; + // set of minecraft versions for which using forge installers is blacklisted + QSet forgeInstallerBlacklist; + // set of 'legacy' versions that will not show up in the version lists. + QSet legacyBlacklist; + // no new versions below this date will be accepted from Mojang servers + QDateTime legacyCutoffDate; + // Libraries that belong to LWJGL + QSet lwjglWhitelist; +}; +extern VersionFilterData g_VersionFilterData; diff --git a/logic/minecraft/VersionPatch.h b/logic/minecraft/VersionPatch.h deleted file mode 100644 index 6c7bd7cf..00000000 --- a/logic/minecraft/VersionPatch.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include "JarMod.h" - -class InstanceVersion; -class VersionPatch -{ -public: - virtual ~VersionPatch(){}; - virtual void applyTo(InstanceVersion *version) = 0; - - virtual bool isMinecraftVersion() = 0; - virtual bool hasJarMods() = 0; - virtual QList getJarMods() = 0; - - virtual bool isMoveable() - { - return getOrder() >= 0; - } - virtual void setOrder(int order) = 0; - virtual int getOrder() = 0; - - virtual QString getPatchID() = 0; - virtual QString getPatchName() = 0; - virtual QString getPatchVersion() = 0; - virtual QString getPatchFilename() = 0; - virtual bool isCustom() = 0; -}; - -typedef std::shared_ptr VersionPatchPtr; diff --git a/logic/net/PasteUpload.cpp b/logic/net/PasteUpload.cpp index 5fd8d679..c7bde06e 100644 --- a/logic/net/PasteUpload.cpp +++ b/logic/net/PasteUpload.cpp @@ -3,8 +3,6 @@ #include "logger/QsLog.h" #include #include -#include "gui/dialogs/CustomMessageBox.h" -#include PasteUpload::PasteUpload(QWidget *window, QString text) : m_window(window) { diff --git a/logic/tools/BaseExternalTool.cpp b/logic/tools/BaseExternalTool.cpp index 23f154e2..04edece8 100644 --- a/logic/tools/BaseExternalTool.cpp +++ b/logic/tools/BaseExternalTool.cpp @@ -30,31 +30,6 @@ qint64 BaseExternalTool::pid(QProcess *process) #endif } -QString BaseExternalTool::getSave() const -{ - QDir saves(m_instance->minecraftRoot() + "/saves"); - QStringList worlds = saves.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - QMutableListIterator it(worlds); - while (it.hasNext()) - { - it.next(); - if (!QDir(saves.absoluteFilePath(it.value())).exists("level.dat")) - { - it.remove(); - } - } - bool ok = true; - const QString save = QInputDialog::getItem( - MMC->activeWindow(), tr("MCEdit"), tr("Choose which world to open:"), - worlds, 0, false, &ok); - if (ok) - { - return saves.absoluteFilePath(save); - } - return QString(); -} - - BaseDetachedTool::BaseDetachedTool(InstancePtr instance, QObject *parent) : BaseExternalTool(instance, parent) { diff --git a/logic/tools/BaseExternalTool.h b/logic/tools/BaseExternalTool.h index 5f112970..8b8b2790 100644 --- a/logic/tools/BaseExternalTool.h +++ b/logic/tools/BaseExternalTool.h @@ -5,7 +5,6 @@ class BaseInstance; class SettingsObject; -class MinecraftProcess; class QProcess; class BaseExternalTool : public QObject @@ -19,7 +18,6 @@ protected: InstancePtr m_instance; qint64 pid(QProcess *process); - QString getSave() const; }; class BaseDetachedTool : public BaseExternalTool diff --git a/logic/tools/BaseProfiler.cpp b/logic/tools/BaseProfiler.cpp index 7cd1c5c7..d3c8d41e 100644 --- a/logic/tools/BaseProfiler.cpp +++ b/logic/tools/BaseProfiler.cpp @@ -7,7 +7,7 @@ BaseProfiler::BaseProfiler(InstancePtr instance, QObject *parent) { } -void BaseProfiler::beginProfiling(MinecraftProcess *process) +void BaseProfiler::beginProfiling(BaseProcess *process) { beginProfilingImpl(process); } diff --git a/logic/tools/BaseProfiler.h b/logic/tools/BaseProfiler.h index dd841da4..decaa5b8 100644 --- a/logic/tools/BaseProfiler.h +++ b/logic/tools/BaseProfiler.h @@ -4,7 +4,7 @@ class BaseInstance; class SettingsObject; -class MinecraftProcess; +class BaseProcess; class QProcess; class BaseProfiler : public BaseExternalTool @@ -15,13 +15,13 @@ public: public slots: - void beginProfiling(MinecraftProcess *process); + void beginProfiling(BaseProcess *process); void abortProfiling(); protected: QProcess *m_profilerProcess; - virtual void beginProfilingImpl(MinecraftProcess *process) = 0; + virtual void beginProfilingImpl(BaseProcess *process) = 0; virtual void abortProfilingImpl(); signals: diff --git a/logic/tools/JProfiler.cpp b/logic/tools/JProfiler.cpp index 659d80b6..7cdf0268 100644 --- a/logic/tools/JProfiler.cpp +++ b/logic/tools/JProfiler.cpp @@ -4,7 +4,7 @@ #include #include "logic/settings/SettingsObject.h" -#include "logic/MinecraftProcess.h" +#include "logic/BaseProcess.h" #include "logic/BaseInstance.h" #include "MultiMC.h" @@ -12,7 +12,7 @@ JProfiler::JProfiler(InstancePtr instance, QObject *parent) : BaseProfiler(insta { } -void JProfiler::beginProfilingImpl(MinecraftProcess *process) +void JProfiler::beginProfilingImpl(BaseProcess *process) { int port = MMC->settings()->get("JProfilerPort").toInt(); QProcess *profiler = new QProcess(this); diff --git a/logic/tools/JProfiler.h b/logic/tools/JProfiler.h index 3085511b..0763a799 100644 --- a/logic/tools/JProfiler.h +++ b/logic/tools/JProfiler.h @@ -9,7 +9,7 @@ public: JProfiler(InstancePtr instance, QObject *parent = 0); protected: - void beginProfilingImpl(MinecraftProcess *process); + void beginProfilingImpl(BaseProcess *process); }; class JProfilerFactory : public BaseProfilerFactory diff --git a/logic/tools/JVisualVM.cpp b/logic/tools/JVisualVM.cpp index f47294d7..312be863 100644 --- a/logic/tools/JVisualVM.cpp +++ b/logic/tools/JVisualVM.cpp @@ -4,7 +4,7 @@ #include #include "logic/settings/SettingsObject.h" -#include "logic/MinecraftProcess.h" +#include "logic/BaseProcess.h" #include "logic/BaseInstance.h" #include "MultiMC.h" @@ -12,7 +12,7 @@ JVisualVM::JVisualVM(InstancePtr instance, QObject *parent) : BaseProfiler(insta { } -void JVisualVM::beginProfilingImpl(MinecraftProcess *process) +void JVisualVM::beginProfilingImpl(BaseProcess *process) { QProcess *profiler = new QProcess(this); profiler->setArguments(QStringList() << "--openpid" << QString::number(pid(process))); diff --git a/logic/tools/JVisualVM.h b/logic/tools/JVisualVM.h index a646c681..2a273568 100644 --- a/logic/tools/JVisualVM.h +++ b/logic/tools/JVisualVM.h @@ -9,7 +9,7 @@ public: JVisualVM(InstancePtr instance, QObject *parent = 0); protected: - void beginProfilingImpl(MinecraftProcess *process); + void beginProfilingImpl(BaseProcess *process); }; class JVisualVMFactory : public BaseProfilerFactory diff --git a/logic/tools/MCEditTool.cpp b/logic/tools/MCEditTool.cpp index e44ce784..c918d5d0 100644 --- a/logic/tools/MCEditTool.cpp +++ b/logic/tools/MCEditTool.cpp @@ -4,9 +4,12 @@ #include #include #include +// FIXME: mixing logic and UI!!!! +#include #include "logic/settings/SettingsObject.h" #include "logic/BaseInstance.h" +#include "logic/minecraft/MinecraftInstance.h" #include "MultiMC.h" MCEditTool::MCEditTool(InstancePtr instance, QObject *parent) @@ -14,6 +17,36 @@ MCEditTool::MCEditTool(InstancePtr instance, QObject *parent) { } +QString MCEditTool::getSave() const +{ + // FIXME: mixing logic and UI!!!! + auto mcInstance = std::dynamic_pointer_cast(m_instance); + if(!mcInstance) + { + return QString(); + } + QDir saves(mcInstance->minecraftRoot() + "/saves"); + QStringList worlds = saves.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + QMutableListIterator it(worlds); + while (it.hasNext()) + { + it.next(); + if (!QDir(saves.absoluteFilePath(it.value())).exists("level.dat")) + { + it.remove(); + } + } + bool ok = true; + const QString save = QInputDialog::getItem( + MMC->activeWindow(), tr("MCEdit"), tr("Choose which world to open:"), + worlds, 0, false, &ok); + if (ok) + { + return saves.absoluteFilePath(save); + } + return QString(); +} + void MCEditTool::runImpl() { const QString mceditPath = MMC->settings()->get("MCEditPath").toString(); diff --git a/logic/tools/MCEditTool.h b/logic/tools/MCEditTool.h index 810caf25..b5cfc64e 100644 --- a/logic/tools/MCEditTool.h +++ b/logic/tools/MCEditTool.h @@ -9,6 +9,7 @@ public: explicit MCEditTool(InstancePtr instance, QObject *parent = 0); protected: + QString getSave() const; void runImpl() override; }; diff --git a/main.cpp b/main.cpp index ed35a9e8..56ffa8a1 100644 --- a/main.cpp +++ b/main.cpp @@ -9,7 +9,6 @@ int main_gui(MultiMC &app) mainWin.restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray())); mainWin.restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray())); mainWin.show(); - mainWin.checkMigrateLegacyAssets(); mainWin.checkSetDefaultJava(); mainWin.checkInstancePathForProblems(); return app.exec(); -- cgit v1.2.3