diff options
45 files changed, 1672 insertions, 271 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e7dea67..0045a41b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -460,6 +460,8 @@ logic/lists/ForgeVersionList.h logic/lists/ForgeVersionList.cpp logic/lists/JavaVersionList.h logic/lists/JavaVersionList.cpp +logic/lists/LiteLoaderVersionList.h +logic/lists/LiteLoaderVersionList.cpp # Icons logic/icons/MMCIcon.h @@ -498,6 +500,18 @@ logic/assets/AssetsMigrateTask.h logic/assets/AssetsMigrateTask.cpp logic/assets/AssetsUtils.h logic/assets/AssetsUtils.cpp + +# Tools +logic/tools/BaseExternalTool.h +logic/tools/BaseExternalTool.cpp +logic/tools/MCEditTool.h +logic/tools/MCEditTool.cpp +logic/tools/BaseProfiler.h +logic/tools/BaseProfiler.cpp +logic/tools/JProfiler.h +logic/tools/JProfiler.cpp +logic/tools/JVisualVM.h +logic/tools/JVisualVM.cpp ) diff --git a/MultiMC.cpp b/MultiMC.cpp index 598a3a80..a0745a87 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -17,6 +17,7 @@ #include "logic/lists/LwjglVersionList.h" #include "logic/lists/MinecraftVersionList.h" #include "logic/lists/ForgeVersionList.h" +#include "logic/lists/LiteLoaderVersionList.h" #include "logic/news/NewsChecker.h" @@ -31,6 +32,10 @@ #include "logic/updater/UpdateChecker.h" #include "logic/updater/NotificationChecker.h" +#include "logic/tools/JProfiler.h" +#include "logic/tools/JVisualVM.h" +#include "logic/tools/MCEditTool.h" + #include "pathutils.h" #include "cmdutils.h" #include <inisettingsobject.h> @@ -41,8 +46,9 @@ using namespace Util::Commandline; MultiMC::MultiMC(int &argc, char **argv, bool root_override) - : QApplication(argc, argv), m_version{VERSION_MAJOR, VERSION_MINOR, VERSION_HOTFIX, - VERSION_BUILD, MultiMCVersion::VERSION_TYPE, VERSION_CHANNEL, BUILD_PLATFORM} + : QApplication(argc, argv), + m_version{VERSION_MAJOR, VERSION_MINOR, VERSION_HOTFIX, VERSION_BUILD, + MultiMCVersion::VERSION_TYPE, VERSION_CHANNEL, BUILD_PLATFORM} { setOrganizationName("MultiMC"); setApplicationName("MultiMC5"); @@ -211,6 +217,21 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override) // init proxy settings updateProxySettings(); + m_profilers.insert("jprofiler", + std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory())); + m_profilers.insert("jvisualvm", + std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory())); + for (auto profiler : m_profilers.values()) + { + profiler->registerSettings(m_settings.get()); + } + m_tools.insert("mcedit", + std::shared_ptr<BaseDetachedToolFactory>(new MCEditFactory())); + for (auto tool : m_tools.values()) + { + tool->registerSettings(m_settings.get()); + } + // launch instance, if that's what should be done // WARNING: disabled until further notice /* @@ -536,6 +557,15 @@ std::shared_ptr<ForgeVersionList> MultiMC::forgelist() return m_forgelist; } +std::shared_ptr<LiteLoaderVersionList> MultiMC::liteloaderlist() +{ + if (!m_liteloaderlist) + { + m_liteloaderlist.reset(new LiteLoaderVersionList()); + } + return m_liteloaderlist; +} + std::shared_ptr<MinecraftVersionList> MultiMC::minecraftlist() { if (!m_minecraftlist) @@ -17,11 +17,14 @@ class MojangAccountList; class IconList; class QNetworkAccessManager; class ForgeVersionList; +class LiteLoaderVersionList; class JavaVersionList; class UpdateChecker; class NotificationChecker; class NewsChecker; class StatusChecker; +class BaseProfilerFactory; +class BaseDetachedToolFactory; #if defined(MMC) #undef MMC @@ -123,10 +126,21 @@ public: std::shared_ptr<ForgeVersionList> forgelist(); + std::shared_ptr<LiteLoaderVersionList> liteloaderlist(); + std::shared_ptr<MinecraftVersionList> minecraftlist(); std::shared_ptr<JavaVersionList> javalist(); + QMap<QString, std::shared_ptr<BaseProfilerFactory>> profilers() + { + return m_profilers; + } + QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> tools() + { + return m_tools; + } + void installUpdates(const QString updateFilesDir, UpdateFlags flags = None); /*! @@ -196,8 +210,11 @@ private: std::shared_ptr<HttpMetaCache> m_metacache; std::shared_ptr<LWJGLVersionList> m_lwjgllist; std::shared_ptr<ForgeVersionList> m_forgelist; + std::shared_ptr<LiteLoaderVersionList> m_liteloaderlist; std::shared_ptr<MinecraftVersionList> m_minecraftlist; std::shared_ptr<JavaVersionList> m_javalist; + QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers; + QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> m_tools; QsLogging::DestinationPtr m_fileDestination; QsLogging::DestinationPtr m_debugDestination; diff --git a/depends/launcher/org/multimc/EntryPoint.java b/depends/launcher/org/multimc/EntryPoint.java index e2721ffa..f9fe68d6 100644 --- a/depends/launcher/org/multimc/EntryPoint.java +++ b/depends/launcher/org/multimc/EntryPoint.java @@ -29,7 +29,8 @@ public class EntryPoint private enum Action { Proceed, - Launch + Launch, + Abort } public static void main(String[] args) @@ -61,27 +62,40 @@ public class EntryPoint private Action parseLine(String inData) throws ParseException { String[] pair = inData.split(" ", 2); + + if(pair.length == 1) + { + String command = pair[0]; + if (pair[0].equals("launch")) + return Action.Launch; + + else if (pair[0].equals("abort")) + return Action.Abort; + + else throw new ParseException(); + } + if(pair.length != 2) throw new ParseException(); String command = pair[0]; String param = pair[1]; - if(command.equals("launch")) + if(command.equals("launcher")) { if(param.equals("legacy")) { m_launcher = new LegacyLauncher(); Utils.log("Using legacy launcher."); Utils.log(); - return Action.Launch; + return Action.Proceed; } if(param.equals("onesix")) { m_launcher = new OneSixLauncher(); Utils.log("Using onesix launcher."); Utils.log(); - return Action.Launch; + return Action.Proceed; } else throw new ParseException(); @@ -105,6 +119,7 @@ public class EntryPoint return 1; } boolean isListening = true; + boolean isAborted = false; // Main loop while (isListening) { @@ -115,7 +130,13 @@ public class EntryPoint inData = buffer.readLine(); if (inData != null) { - if(parseLine(inData) == Action.Launch) + Action a = parseLine(inData); + if(a == Action.Abort) + { + isListening = false; + isAborted = true; + } + if(a == Action.Launch) { isListening = false; } @@ -134,6 +155,11 @@ public class EntryPoint return 1; } } + if(isAborted) + { + System.err.println("Launch aborted by MultiMC."); + return 1; + } if(m_launcher != null) { return m_launcher.launch(m_params); diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index a4c04d89..121c2a73 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -33,6 +33,7 @@ #include <QLabel> #include <QToolButton> #include <QWidgetAction> +#include <QProgressDialog> #include "osutils.h" #include "userutils.h" @@ -97,6 +98,8 @@ #include <logic/updater/NotificationChecker.h> #include <logic/tasks/ThreadTask.h> +#include "logic/tools/BaseProfiler.h" + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { MultiMCPlatform::fixWM_CLASS(this); @@ -358,6 +361,51 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) myMenu.exec(view->mapToGlobal(pos)); } +void MainWindow::updateToolsMenu() +{ + if (ui->actionLaunchInstance->menu()) + { + ui->actionLaunchInstance->menu()->deleteLater(); + } + QMenu *launchMenu = new QMenu(this); + QAction *normalLaunch = launchMenu->addAction(tr("Launch")); + connect(normalLaunch, &QAction::triggered, [this](){doLaunch();}); + launchMenu->addSeparator()->setText(tr("Profilers")); + for (auto profiler : MMC->profilers().values()) + { + QAction *profilerAction = launchMenu->addAction(profiler->name()); + QString error; + if (!profiler->check(&error)) + { + profilerAction->setDisabled(true); + profilerAction->setToolTip(tr("Profiler not setup correctly. Go into settings, \"External Tools\".")); + } + else + { + connect(profilerAction, &QAction::triggered, [this, profiler](){doLaunch(true, profiler.get());}); + } + } + launchMenu->addSeparator()->setText(tr("Tools")); + for (auto tool : MMC->tools().values()) + { + QAction *toolAction = launchMenu->addAction(tool->name()); + QString error; + if (!tool->check(&error)) + { + toolAction->setDisabled(true); + toolAction->setToolTip(tr("Tool not setup correctly. Go into settings, \"External Tools\".")); + } + else + { + connect(toolAction, &QAction::triggered, [this, tool]() + { + tool->createDetachedTool(m_selectedInstance, this)->run(); + }); + } + } + ui->actionLaunchInstance->setMenu(launchMenu); +} + void MainWindow::repopulateAccountsMenu() { accountMenu->clear(); @@ -931,6 +979,7 @@ void MainWindow::on_actionSettings_triggered() // FIXME: quick HACK to make this work. improve, optimize. proxymodel->invalidate(); proxymodel->sort(0); + updateToolsMenu(); } void MainWindow::on_actionManageAccounts_triggered() @@ -1079,7 +1128,7 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered() } } -void MainWindow::doLaunch(bool online) +void MainWindow::doLaunch(bool online, BaseProfilerFactory *profiler) { if (!m_selectedInstance) return; @@ -1195,11 +1244,11 @@ void MainWindow::doLaunch(bool online) // update first if the server actually responded if (session->auth_server_online) { - updateInstance(m_selectedInstance, session); + updateInstance(m_selectedInstance, session, profiler); } else { - launchInstance(m_selectedInstance, session); + launchInstance(m_selectedInstance, session, profiler); } tryagain = false; } @@ -1207,22 +1256,22 @@ void MainWindow::doLaunch(bool online) } } -void MainWindow::updateInstance(BaseInstance *instance, AuthSessionPtr session) +void MainWindow::updateInstance(BaseInstance *instance, AuthSessionPtr session, BaseProfilerFactory *profiler) { auto updateTask = instance->doUpdate(); if (!updateTask) { - launchInstance(instance, session); + launchInstance(instance, session, profiler); return; } ProgressDialog tDialog(this); - connect(updateTask.get(), &Task::succeeded, [this, instance, session] - { launchInstance(instance, session); }); + connect(updateTask.get(), &Task::succeeded, [this, instance, session, profiler] + { launchInstance(instance, session, profiler); }); connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); tDialog.exec(updateTask.get()); } -void MainWindow::launchInstance(BaseInstance *instance, AuthSessionPtr session) +void MainWindow::launchInstance(BaseInstance *instance, AuthSessionPtr session, BaseProfilerFactory *profiler) { Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL"); Q_ASSERT_X(session.get() != nullptr, "launchInstance", "session is NULL"); @@ -1237,7 +1286,56 @@ void MainWindow::launchInstance(BaseInstance *instance, AuthSessionPtr session) connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded())); proc->setLogin(session); - proc->launch(); + proc->arm(); + + if (profiler) + { + QString error; + if (!profiler->check(&error)) + { + QMessageBox::critical(this, tr("Error"), tr("Couldn't start profiler: %1").arg(error)); + proc->abort(); + return; + } + BaseProfiler *profilerInstance = profiler->createProfiler(instance, this); + QProgressDialog dialog; + dialog.setMinimum(0); + dialog.setMaximum(0); + dialog.setValue(0); + dialog.setLabelText(tr("Waiting for profiler...")); + connect(&dialog, &QProgressDialog::canceled, profilerInstance, &BaseProfiler::abortProfiling); + dialog.show(); + connect(profilerInstance, &BaseProfiler::readyToLaunch, [&dialog, this](const QString &message) + { + dialog.accept(); + QMessageBox msg; + msg.setText(tr("The launch of Minecraft itself 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")); + msg.setIcon(QMessageBox::Information); + msg.addButton(tr("Launch"), QMessageBox::AcceptRole); + msg.exec(); + proc->launch(); + }); + connect(profilerInstance, &BaseProfiler::abortLaunch, [&dialog, this](const QString &message) + { + dialog.accept(); + QMessageBox msg; + msg.setText(tr("Couldn't start the profiler: %1").arg(message)); + msg.setWindowTitle(tr("Error")); + msg.setIcon(QMessageBox::Critical); + msg.addButton(QMessageBox::Ok); + msg.exec(); + proc->abort(); + }); + profilerInstance->beginProfiling(proc); + dialog.exec(); + } + else + { + proc->launch(); + } } void MainWindow::onGameUpdateError(QString error) @@ -1378,6 +1476,8 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); updateInstanceToolIcon(m_selectedInstance->iconKey()); + updateToolsMenu(); + MMC->settings()->set("SelectedInstance", m_selectedInstance->id()); } else diff --git a/gui/MainWindow.h b/gui/MainWindow.h index 4d9e165d..7a29977d 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -29,6 +29,7 @@ class LabeledToolButton; class QLabel; class MinecraftProcess; class ConsoleWindow; +class BaseProfilerFactory; namespace Ui { @@ -111,18 +112,18 @@ slots: * Launches the currently selected instance with the default account. * If no default account is selected, prompts the user to pick an account. */ - void doLaunch(bool online = true); + void doLaunch(bool online = true, BaseProfilerFactory *profiler = 0); /*! * Launches the given instance with the given account. * This function assumes that the given account has a valid, usable access token. */ - void launchInstance(BaseInstance *instance, AuthSessionPtr session); + void launchInstance(BaseInstance *instance, AuthSessionPtr session, BaseProfilerFactory *profiler = 0); /*! * Prepares the given instance for launch with the given account. */ - void updateInstance(BaseInstance *instance, AuthSessionPtr account); + void updateInstance(BaseInstance *instance, AuthSessionPtr account, BaseProfilerFactory *profiler = 0); void onGameUpdateError(QString error); @@ -140,6 +141,8 @@ slots: void showInstanceContextMenu(const QPoint&); + void updateToolsMenu(); + public slots: void instanceActivated(QModelIndex); diff --git a/gui/MainWindow.ui b/gui/MainWindow.ui index 8cf26d18..90c86075 100644 --- a/gui/MainWindow.ui +++ b/gui/MainWindow.ui @@ -531,9 +531,8 @@ </widget> <layoutdefault spacing="6" margin="11"/> <resources> - <include location="../resources/instances/instances.qrc"/> <include location="../resources/multimc/multimc.qrc"/> - <include location="../resources/backgrounds/backgrounds.qrc"/> + <include location="../resources/instances/instances.qrc"/> </resources> <connections/> </ui> diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp index 9e585de5..fe621a9a 100644 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ b/gui/dialogs/OneSixModEditDialog.cpp @@ -37,6 +37,7 @@ #include "logic/OneSixVersion.h" #include "logic/EnabledItemFilter.h" #include "logic/lists/ForgeVersionList.h" +#include "logic/lists/LiteLoaderVersionList.h" #include "logic/ForgeInstaller.h" #include "logic/LiteLoaderInstaller.h" #include "logic/OneSixVersionBuilder.h" @@ -108,7 +109,7 @@ OneSixModEditDialog::~OneSixModEditDialog() void OneSixModEditDialog::updateVersionControls() { ui->forgeBtn->setEnabled(true); - ui->liteloaderBtn->setEnabled(LiteLoaderInstaller().canApply(m_inst)); + ui->liteloaderBtn->setEnabled(true); ui->mainClassEdit->setText(m_version->mainClass); } @@ -290,24 +291,27 @@ void OneSixModEditDialog::on_liteloaderBtn_clicked() QDir(m_inst->instanceRoot()).remove("custom.json"); m_inst->reloadVersion(this); } - LiteLoaderInstaller liteloader; - if (!liteloader.canApply(m_inst)) - { - QMessageBox::critical( - this, tr("LiteLoader"), - tr("There is no information available on how to install LiteLoader " - "into this version of Minecraft")); - return; - } - if (!liteloader.add(m_inst)) - { - QMessageBox::critical(this, tr("LiteLoader"), - tr("For reasons unknown, the LiteLoader installation failed. " - "Check your MultiMC log files for details.")); - } - else + VersionSelectDialog vselect(MMC->liteloaderlist().get(), tr("Select LiteLoader version"), this); + vselect.setFilter(1, m_inst->currentVersionId()); + vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + + m_inst->currentVersionId()); + if (vselect.exec() && vselect.selectedVersion()) { - m_inst->reloadVersion(this); + LiteLoaderVersionPtr liteloaderVersion = + std::dynamic_pointer_cast<LiteLoaderVersion>(vselect.selectedVersion()); + if (!liteloaderVersion) + return; + LiteLoaderInstaller liteloader(liteloaderVersion); + if (!liteloader.add(m_inst)) + { + QMessageBox::critical(this, tr("LiteLoader"), + tr("For reasons unknown, the LiteLoader installation failed. " + "Check your MultiMC log files for details.")); + } + else + { + m_inst->reloadVersion(this); + } } } diff --git a/gui/dialogs/SettingsDialog.cpp b/gui/dialogs/SettingsDialog.cpp index ef363f02..d79bb558 100644 --- a/gui/dialogs/SettingsDialog.cpp +++ b/gui/dialogs/SettingsDialog.cpp @@ -29,6 +29,8 @@ #include "logic/updater/UpdateChecker.h" +#include "logic/tools/BaseProfiler.h" + #include <settingsobject.h> #include <pathutils.h> #include <QFileDialog> @@ -46,12 +48,14 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Se ui->jsonEditorTextBox->setClearButtonEnabled(true); #endif - restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("SettingsGeometry").toByteArray())); + restoreGeometry( + QByteArray::fromBase64(MMC->settings()->get("SettingsGeometry").toByteArray())); loadSettings(MMC->settings().get()); updateCheckboxStuff(); - QObject::connect(MMC->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &SettingsDialog::refreshUpdateChannelList); + QObject::connect(MMC->updateChecker().get(), &UpdateChecker::channelListLoaded, this, + &SettingsDialog::refreshUpdateChannelList); if (MMC->updateChecker()->hasChannels()) { @@ -62,6 +66,9 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Se MMC->updateChecker()->updateChanList(); } connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int))); + ui->mceditLink->setOpenExternalLinks(true); + ui->jvisualvmLink->setOpenExternalLinks(true); + ui->jprofilerLink->setOpenExternalLinks(true); } SettingsDialog::~SettingsDialog() @@ -84,8 +91,10 @@ void SettingsDialog::updateCheckboxStuff() { ui->windowWidthSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); - ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() && !ui->proxyDefaultBtn->isChecked()); - ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() && !ui->proxyDefaultBtn->isChecked()); + ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() && + !ui->proxyDefaultBtn->isChecked()); + ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() && + !ui->proxyDefaultBtn->isChecked()); } void SettingsDialog::on_ftbLauncherBrowseBtn_clicked() @@ -103,8 +112,8 @@ void SettingsDialog::on_ftbLauncherBrowseBtn_clicked() void SettingsDialog::on_ftbBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("FTB Directory"), - ui->ftbBox->text()); + QString raw_dir = + QFileDialog::getExistingDirectory(this, tr("FTB Directory"), ui->ftbBox->text()); QString cooked_dir = NormalizePath(raw_dir); // do not allow current dir - it's dirty. Do not allow dirs that don't exist @@ -170,11 +179,11 @@ void SettingsDialog::on_jsonEditorBrowseBtn_clicked() QString raw_file = QFileDialog::getOpenFileName( this, tr("JSON Editor"), ui->jsonEditorTextBox->text().isEmpty() - #if defined(Q_OS_LINUX) +#if defined(Q_OS_LINUX) ? QString("/usr/bin") - #else +#else ? QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).first() - #endif +#endif : ui->jsonEditorTextBox->text()); QString cooked_file = NormalizePath(raw_file); @@ -184,14 +193,14 @@ void SettingsDialog::on_jsonEditorBrowseBtn_clicked() } // it has to exist and be an executable - if (QFileInfo(cooked_file).exists() && - QFileInfo(cooked_file).isExecutable()) + if (QFileInfo(cooked_file).exists() && QFileInfo(cooked_file).isExecutable()) { ui->jsonEditorTextBox->setText(cooked_file); } else { - QMessageBox::warning(this, tr("Invalid"), tr("The file chosen does not seem to be an executable")); + QMessageBox::warning(this, tr("Invalid"), + tr("The file chosen does not seem to be an executable")); } } @@ -223,9 +232,11 @@ void SettingsDialog::proxyChanged(int) void SettingsDialog::refreshUpdateChannelList() { - // Stop listening for selection changes. It's going to change a lot while we update it and we don't need to update the + // Stop listening for selection changes. It's going to change a lot while we update it and + // we don't need to update the // description label constantly. - QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateChannelSelectionChanged(int))); + QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, + SLOT(updateChannelSelectionChanged(int))); QList<UpdateChecker::ChannelListEntry> channelList = MMC->updateChecker()->getChannelList(); ui->updateChannelComboBox->clear(); @@ -233,29 +244,34 @@ void SettingsDialog::refreshUpdateChannelList() for (int i = 0; i < channelList.count(); i++) { UpdateChecker::ChannelListEntry entry = channelList.at(i); - - // When it comes to selection, we'll rely on the indexes of a channel entry being the same in the + + // When it comes to selection, we'll rely on the indexes of a channel entry being the + // same in the // combo box as it is in the update checker's channel list. - // This probably isn't very safe, but the channel list doesn't change often enough (or at all) for + // This probably isn't very safe, but the channel list doesn't change often enough (or + // at all) for // this to be a big deal. Hope it doesn't break... ui->updateChannelComboBox->addItem(entry.name); - // If the update channel we just added was the selected one, set the current index in the combo box to it. + // If the update channel we just added was the selected one, set the current index in + // the combo box to it. if (entry.id == m_currentUpdateChannel) { QLOG_DEBUG() << "Selected index" << i << "channel id" << m_currentUpdateChannel; selection = i; } } - + ui->updateChannelComboBox->setCurrentIndex(selection); // Start listening for selection changes again and update the description label. - QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateChannelSelectionChanged(int))); + QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, + SLOT(updateChannelSelectionChanged(int))); refreshUpdateChannelDesc(); // Now that we've updated the channel list, we can enable the combo box. - // It starts off disabled so that if the channel list hasn't been loaded, it will be disabled. + // It starts off disabled so that if the channel list hasn't been loaded, it will be + // disabled. ui->updateChannelComboBox->setEnabled(true); } @@ -269,7 +285,7 @@ void SettingsDialog::refreshUpdateChannelDesc() // Get the channel list. QList<UpdateChecker::ChannelListEntry> channelList = MMC->updateChecker()->getChannelList(); int selectedIndex = ui->updateChannelComboBox->currentIndex(); - if(selectedIndex < 0) + if (selectedIndex < 0) { return; } @@ -289,7 +305,8 @@ void SettingsDialog::refreshUpdateChannelDesc() void SettingsDialog::applySettings(SettingsObject *s) { // Language - s->set("Language", ui->languageBox->itemData(ui->languageBox->currentIndex()).toLocale().bcp47Name()); + s->set("Language", + ui->languageBox->itemData(ui->languageBox->currentIndex()).toLocale().bcp47Name()); // Updates s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); @@ -309,7 +326,8 @@ void SettingsDialog::applySettings(SettingsObject *s) // Editors QString jsonEditor = ui->jsonEditorTextBox->text(); - if (!jsonEditor.isEmpty() && (!QFileInfo(jsonEditor).exists() || !QFileInfo(jsonEditor).isExecutable())) + if (!jsonEditor.isEmpty() && + (!QFileInfo(jsonEditor).exists() || !QFileInfo(jsonEditor).isExecutable())) { QString found = QStandardPaths::findExecutable(jsonEditor); if (!found.isEmpty()) @@ -330,10 +348,14 @@ void SettingsDialog::applySettings(SettingsObject *s) // Proxy QString proxyType = "None"; - if (ui->proxyDefaultBtn->isChecked()) proxyType = "Default"; - else if (ui->proxyNoneBtn->isChecked()) proxyType = "None"; - else if (ui->proxySOCKS5Btn->isChecked()) proxyType = "SOCKS5"; - else if (ui->proxyHTTPBtn->isChecked()) proxyType = "HTTP"; + if (ui->proxyDefaultBtn->isChecked()) + proxyType = "Default"; + else if (ui->proxyNoneBtn->isChecked()) + proxyType = "None"; + else if (ui->proxySOCKS5Btn->isChecked()) + proxyType = "SOCKS5"; + else if (ui->proxyHTTPBtn->isChecked()) + proxyType = "HTTP"; s->set("ProxyType", proxyType); s->set("ProxyAddr", ui->proxyAddrEdit->text()); @@ -368,6 +390,11 @@ void SettingsDialog::applySettings(SettingsObject *s) } s->set("PostExitCommand", ui->postExitCmdTextBox->text()); + + // Profilers + s->set("JProfilerPath", ui->jprofilerPathEdit->text()); + s->set("JVisualVMPath", ui->jvisualvmPathEdit->text()); + s->set("MCEditPath", ui->mceditPathEdit->text()); } void SettingsDialog::loadSettings(SettingsObject *s) @@ -379,11 +406,10 @@ void SettingsDialog::loadSettings(SettingsObject *s) QDir(MMC->root() + "/translations").entryList(QStringList() << "*.qm", QDir::Files)) { QLocale locale(lang.section(QRegExp("[_\.]"), 1)); - ui->languageBox->addItem( - QLocale::languageToString(locale.language()), - locale); + ui->languageBox->addItem(QLocale::languageToString(locale.language()), locale); } - ui->languageBox->setCurrentIndex(ui->languageBox->findData(QLocale(s->get("Language").toString()))); + ui->languageBox->setCurrentIndex( + ui->languageBox->findData(QLocale(s->get("Language").toString()))); // Updates ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); @@ -430,10 +456,14 @@ void SettingsDialog::loadSettings(SettingsObject *s) // Proxy QString proxyType = s->get("ProxyType").toString(); - if (proxyType == "Default") ui->proxyDefaultBtn->setChecked(true); - else if (proxyType == "None") ui->proxyNoneBtn->setChecked(true); - else if (proxyType == "SOCKS5") ui->proxySOCKS5Btn->setChecked(true); - else if (proxyType == "HTTP") ui->proxyHTTPBtn->setChecked(true); + if (proxyType == "Default") + ui->proxyDefaultBtn->setChecked(true); + else if (proxyType == "None") + ui->proxyNoneBtn->setChecked(true); + else if (proxyType == "SOCKS5") + ui->proxySOCKS5Btn->setChecked(true); + else if (proxyType == "HTTP") + ui->proxyHTTPBtn->setChecked(true); ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString()); ui->proxyPortEdit->setValue(s->get("ProxyPort").value<qint16>()); @@ -447,6 +477,11 @@ void SettingsDialog::loadSettings(SettingsObject *s) // Custom Commands ui->preLaunchCmdTextBox->setText(s->get("PreLaunchCommand").toString()); ui->postExitCmdTextBox->setText(s->get("PostExitCommand").toString()); + + // Profilers + ui->jprofilerPathEdit->setText(s->get("JProfilerPath").toString()); + ui->jvisualvmPathEdit->setText(s->get("JVisualVMPath").toString()); + ui->mceditPathEdit->setText(s->get("MCEditPath").toString()); } void SettingsDialog::on_javaDetectBtn_clicked() @@ -503,3 +538,126 @@ void SettingsDialog::checkFinished(JavaCheckResult result) "or set the path to the java executable.")); } } + +void SettingsDialog::on_jprofilerPathBtn_clicked() +{ + QString raw_dir = ui->jprofilerPathEdit->text(); + QString error; + do + { + raw_dir = QFileDialog::getExistingDirectory(this, tr("JProfiler Directory"), raw_dir); + if (raw_dir.isEmpty()) + { + break; + } + QString cooked_dir = NormalizePath(raw_dir); + if (!MMC->profilers()["jprofiler"]->check(cooked_dir, &error)) + { + QMessageBox::critical(this, tr("Error"), + tr("Error while checking JProfiler install:\n%1").arg(error)); + continue; + } + else + { + ui->jprofilerPathEdit->setText(cooked_dir); + break; + } + } while (1); +} +void SettingsDialog::on_jprofilerCheckBtn_clicked() +{ + QString error; + if (!MMC->profilers()["jprofiler"]->check(ui->jprofilerPathEdit->text(), &error)) + { + QMessageBox::critical(this, tr("Error"), + tr("Error while checking JProfiler install:\n%1").arg(error)); + } + else + { + QMessageBox::information(this, tr("OK"), tr("JProfiler setup seems to be OK")); + } +} + +void SettingsDialog::on_jvisualvmPathBtn_clicked() +{ + QString raw_dir = ui->jvisualvmPathEdit->text(); + QString error; + do + { + raw_dir = QFileDialog::getOpenFileName(this, tr("JVisualVM Executable"), raw_dir); + if (raw_dir.isEmpty()) + { + break; + } + QString cooked_dir = NormalizePath(raw_dir); + if (!MMC->profilers()["jvisualvm"]->check(cooked_dir, &error)) + { + QMessageBox::critical(this, tr("Error"), + tr("Error while checking JVisualVM install:\n%1").arg(error)); + continue; + } + else + { + ui->jvisualvmPathEdit->setText(cooked_dir); + break; + } + } while (1); +} +void SettingsDialog::on_jvisualvmCheckBtn_clicked() +{ + QString error; + if (!MMC->profilers()["jvisualvm"]->check(ui->jvisualvmPathEdit->text(), &error)) + { + QMessageBox::critical(this, tr("Error"), + tr("Error while checking JVisualVM install:\n%1").arg(error)); + } + else + { + QMessageBox::information(this, tr("OK"), tr("JVisualVM setup seems to be OK")); + } +} + +void SettingsDialog::on_mceditPathBtn_clicked() +{ + QString raw_dir = ui->mceditPathEdit->text(); + QString error; + do + { +#ifdef Q_OS_OSX +#warning stuff + raw_dir = QFileDialog::getOpenFileName(this, tr("MCEdit Application"), raw_dir); +#else + raw_dir = QFileDialog::getExistingDirectory(this, tr("MCEdit Directory"), raw_dir); +#endif + if (raw_dir.isEmpty()) + { + break; + } + QString cooked_dir = NormalizePath(raw_dir); + if (!MMC->tools()["mcedit"]->check(cooked_dir, &error)) + { + QMessageBox::critical(this, tr("Error"), + tr("Error while checking MCEdit install:\n%1").arg(error)); + continue; + } + else + { + ui->mceditPathEdit->setText(cooked_dir); + break; + } + } while (1); +} + +void SettingsDialog::on_mceditCheckBtn_clicked() +{ + QString error; + if (!MMC->tools()["mcedit"]->check(ui->mceditPathEdit->text(), &error)) + { + QMessageBox::critical(this, tr("Error"), + tr("Error while checking MCEdit install:\n%1").arg(error)); + } + else + { + QMessageBox::information(this, tr("OK"), tr("MCEdit setup seems to be OK")); + } +} diff --git a/gui/dialogs/SettingsDialog.h b/gui/dialogs/SettingsDialog.h index d7bbbeb3..d8495fdd 100644 --- a/gui/dialogs/SettingsDialog.h +++ b/gui/dialogs/SettingsDialog.h @@ -75,6 +75,13 @@ slots: void checkFinished(JavaCheckResult result); + void on_jprofilerPathBtn_clicked(); + void on_jprofilerCheckBtn_clicked(); + void on_jvisualvmPathBtn_clicked(); + void on_jvisualvmCheckBtn_clicked(); + void on_mceditPathBtn_clicked(); + void on_mceditCheckBtn_clicked(); + /*! * Updates the list of update channels in the combo box. */ diff --git a/gui/dialogs/SettingsDialog.ui b/gui/dialogs/SettingsDialog.ui index 54e7db7a..e8da8582 100644 --- a/gui/dialogs/SettingsDialog.ui +++ b/gui/dialogs/SettingsDialog.ui @@ -863,6 +863,137 @@ </item> </layout> </widget> + <widget class="QWidget" name="externalToolsTab"> + <attribute name="title"> + <string>External Tools</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_13"> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>JProfiler</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_10"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLineEdit" name="jprofilerPathEdit"/> + </item> + <item> + <widget class="QPushButton" name="jprofilerPathBtn"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="jprofilerCheckBtn"> + <property name="text"> + <string>Check</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="jprofilerLink"> + <property name="text"> + <string><html><head/><body><p><a href="http://www.ej-technologies.com/products/jprofiler/overview.html"><span style=" text-decoration: underline; color:#0000ff;">http://www.ej-technologies.com/products/jprofiler/overview.html</span></a></p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>JVisualVM</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_11"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QLineEdit" name="jvisualvmPathEdit"/> + </item> + <item> + <widget class="QPushButton" name="jvisualvmPathBtn"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="jvisualvmCheckBtn"> + <property name="text"> + <string>Check</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="jvisualvmLink"> + <property name="text"> + <string><html><head/><body><p><a href="http://visualvm.java.net/"><span style=" text-decoration: underline; color:#0000ff;">http://visualvm.java.net/</span></a></p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>MCEdit</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_12"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QLineEdit" name="mceditPathEdit"/> + </item> + <item> + <widget class="QPushButton" name="mceditPathBtn"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="mceditCheckBtn"> + <property name="text"> + <string>Check</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="mceditLink"> + <property name="text"> + <string><html><head/><body><p><a href="http://www.mcedit.net/"><span style=" text-decoration: underline; color:#0000ff;">http://www.mcedit.net/</span></a></p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> </widget> </item> <item> @@ -938,7 +1069,7 @@ </connection> </connections> <buttongroups> - <buttongroup name="proxyGroup"/> <buttongroup name="sortingModeGroup"/> + <buttongroup name="proxyGroup"/> </buttongroups> </ui> diff --git a/gui/dialogs/VersionSelectDialog.cpp b/gui/dialogs/VersionSelectDialog.cpp index 0f379f56..3277fd2f 100644 --- a/gui/dialogs/VersionSelectDialog.cpp +++ b/gui/dialogs/VersionSelectDialog.cpp @@ -82,6 +82,7 @@ void VersionSelectDialog::loadList() Task *loadTask = m_vlist->getLoadTask(); loadTask->setParent(taskDlg); taskDlg->exec(loadTask); + delete taskDlg; } BaseVersionPtr VersionSelectDialog::selectedVersion() const diff --git a/logic/BaseInstaller.h b/logic/BaseInstaller.h index df7eab89..c572e004 100644 --- a/logic/BaseInstaller.h +++ b/logic/BaseInstaller.h @@ -26,7 +26,6 @@ class BaseInstaller public: BaseInstaller(); - virtual bool canApply(OneSixInstance *instance) const { return true; } bool isApplied(OneSixInstance *on); virtual bool add(OneSixInstance *to); diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 91b83bfc..27f939bc 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -25,6 +25,7 @@ #include "logic/auth/MojangAccount.h" class QDialog; +class QDir; class Task; class MinecraftProcess; class OneSixUpdate; @@ -51,6 +52,9 @@ public: /// virtual destructor to make sure the destruction is COMPLETE virtual ~BaseInstance() {}; + virtual void init() {} + virtual void copy(const QDir &newDir) {} + /// nuke thoroughly - deletes the instance contents, notifies the list/model which is /// responsible of cleaning up the husk void nuke(); diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index 807bccd0..d6e06133 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -75,6 +75,7 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst { return InstanceFactory::UnknownLoadError; } + inst->init(); return NoLoadError; } @@ -156,6 +157,8 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *& return InstanceFactory::NoSuchVersion; } + inst->init(); + // FIXME: really, how do you even know? return InstanceFactory::NoCreateError; } @@ -181,6 +184,8 @@ InstanceFactory::InstCreateError InstanceFactory::copyInstance(BaseInstance *&ne if(inst_type == "LegacyFTB") m_settings->set("InstanceType", "Legacy"); + oldInstance->copy(instDir); + auto error = loadInstance(newInstance, instDir); switch (error) diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index f58f0e76..3b9181e0 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -77,7 +77,7 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account) launchScript += "windowTitle " + windowTitle() + "\n"; launchScript += "windowParams " + windowParams + "\n"; launchScript += "lwjgl " + lwjgl + "\n"; - launchScript += "launch legacy\n"; + launchScript += "launcher legacy\n"; } proc->setLaunchScript(launchScript); diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index c363cad6..126027fb 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -24,23 +24,9 @@ #include "OneSixLibrary.h" #include "OneSixInstance.h" -QMap<QString, QString> LiteLoaderInstaller::m_launcherWrapperVersionMapping; - -LiteLoaderInstaller::LiteLoaderInstaller() - : BaseInstaller() -{ - if (m_launcherWrapperVersionMapping.isEmpty()) - { - m_launcherWrapperVersionMapping["1.6.2"] = "1.3"; - m_launcherWrapperVersionMapping["1.6.4"] = "1.8"; - //m_launcherWrapperVersionMapping["1.7.2"] = "1.8"; - //m_launcherWrapperVersionMapping["1.7.4"] = "1.8"; - } -} - -bool LiteLoaderInstaller::canApply(OneSixInstance *instance) const +LiteLoaderInstaller::LiteLoaderInstaller(LiteLoaderVersionPtr version) + : BaseInstaller(), m_version(version) { - return m_launcherWrapperVersionMapping.contains(instance->intendedVersionId()); } bool LiteLoaderInstaller::add(OneSixInstance *to) @@ -53,24 +39,26 @@ bool LiteLoaderInstaller::add(OneSixInstance *to) QJsonObject obj; obj.insert("mainClass", QString("net.minecraft.launchwrapper.Launch")); - obj.insert("+tweakers", QJsonArray::fromStringList(QStringList() << "com.mumfrey.liteloader.launch.LiteLoaderTweaker")); + obj.insert("+tweakers", QJsonArray::fromStringList(QStringList() << m_version->tweakClass)); obj.insert("order", 10); QJsonArray libraries; - // launchwrapper + for (auto libStr : m_version->libraries) { - OneSixLibrary launchwrapperLib("net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[to->intendedVersionId()]); - launchwrapperLib.finalize(); - QJsonObject lwLibObj = launchwrapperLib.toJson(); - lwLibObj.insert("insert", QString("prepend")); - libraries.append(lwLibObj); + OneSixLibrary lib(libStr); + lib.finalize(); + QJsonObject libObj = lib.toJson(); + libObj.insert("insert", QString("prepend")); + libraries.append(libObj); } // liteloader { - OneSixLibrary liteloaderLib("com.mumfrey:liteloader:" + to->intendedVersionId()); - liteloaderLib.setBaseUrl("http://dl.liteloader.com/versions/"); + OneSixLibrary liteloaderLib("com.mumfrey:liteloader:" + m_version->version); + liteloaderLib.setAbsoluteUrl( + QString("http://dl.liteloader.com/versions/com/mumfrey/liteloader/%1/%2") + .arg(m_version->mcVersion, m_version->file)); liteloaderLib.finalize(); QJsonObject llLibObj = liteloaderLib.toJson(); llLibObj.insert("insert", QString("prepend")); @@ -87,7 +75,8 @@ bool LiteLoaderInstaller::add(OneSixInstance *to) QFile file(filename(to->instanceRoot())); if (!file.open(QFile::WriteOnly)) { - QLOG_ERROR() << "Error opening" << file.fileName() << "for reading:" << file.errorString(); + QLOG_ERROR() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); return false; } file.write(QJsonDocument(obj).toJson()); diff --git a/logic/LiteLoaderInstaller.h b/logic/LiteLoaderInstaller.h index 5e01b16c..2e0de64a 100644 --- a/logic/LiteLoaderInstaller.h +++ b/logic/LiteLoaderInstaller.h @@ -20,16 +20,19 @@ #include <QString> #include <QMap> +#include "logic/lists/LiteLoaderVersionList.h" + class LiteLoaderInstaller : public BaseInstaller { public: - LiteLoaderInstaller(); + LiteLoaderInstaller(LiteLoaderVersionPtr version); - bool canApply(OneSixInstance *instance) const override; bool add(OneSixInstance *to) override; private: - virtual QString id() const override { return "com.mumfrey.liteloader"; } - - static QMap<QString, QString> m_launcherWrapperVersionMapping; + virtual QString id() const override + { + return "com.mumfrey.liteloader"; + } + LiteLoaderVersionPtr m_version; }; diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index 70a9d55f..89cd71ed 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -288,7 +288,7 @@ void MinecraftProcess::killMinecraft() kill(); } -void MinecraftProcess::launch() +void MinecraftProcess::arm() { emit log("MultiMC version: " + MMC->version().toString() + "\n\n"); emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n"); @@ -374,3 +374,17 @@ void MinecraftProcess::launch() 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 index 26214026..56340962 100644 --- a/logic/MinecraftProcess.h +++ b/logic/MinecraftProcess.h @@ -55,10 +55,20 @@ public: MinecraftProcess(BaseInstance *inst); /** - * @brief launch minecraft + * @brief start the launcher part with the provided launch script + */ + void arm(); + + /** + * @brief launch the armed instance! */ void launch(); + /** + * @brief abort launch! + */ + void abort(); + BaseInstance *instance() { return m_instance; diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp index dfd57046..91efce8e 100644 --- a/logic/OneSixFTBInstance.cpp +++ b/logic/OneSixFTBInstance.cpp @@ -5,7 +5,10 @@ #include "tasks/SequentialTask.h" #include "ForgeInstaller.h" #include "lists/ForgeVersionList.h" +#include "OneSixInstance_p.h" +#include "OneSixVersionBuilder.h" #include "MultiMC.h" +#include "pathutils.h" class OneSixFTBInstanceForge : public Task { @@ -80,13 +83,74 @@ private: OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : OneSixInstance(rootDir, settings, parent) { - QFile f(QDir(minecraftRoot()).absoluteFilePath("pack.json")); - if (f.open(QFile::ReadOnly)) +} + +void OneSixFTBInstance::init() +{ + reloadVersion(); +} + +void OneSixFTBInstance::copy(const QDir &newDir) +{ + QStringList libraryNames; + // create patch file { - QString data = QString::fromUtf8(f.readAll()); - QRegularExpressionMatch match = QRegularExpression("net.minecraftforge:minecraftforge:[\\.\\d]*").match(data); - m_forge.reset(new OneSixLibrary(match.captured())); - m_forge->finalize(); + 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"); + 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); + lib->finalize(); + 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(); + } + } } } @@ -95,6 +159,27 @@ 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 +{ + I_D(OneSixInstance); + return QStringList() << versionsPath().absoluteFilePath(intendedVersionId() + "/" + intendedVersionId() + ".json") + << minecraftRoot() + "/pack.json"; +} + +bool OneSixFTBInstance::providesVersionFile() const +{ + return true; +} + QString OneSixFTBInstance::getStatusbarDescription() { if (flags() & VersionBrokenFlag) @@ -110,18 +195,7 @@ bool OneSixFTBInstance::menuActionEnabled(QString action_name) const std::shared_ptr<Task> OneSixFTBInstance::doUpdate() { - std::shared_ptr<SequentialTask> task; - task.reset(new SequentialTask(this)); - if (!MMC->forgelist()->isLoaded()) - { - task->addTask(std::shared_ptr<Task>(MMC->forgelist()->getLoadTask())); - } - task->addTask(OneSixInstance::doUpdate()); - task->addTask(std::shared_ptr<Task>(new OneSixFTBInstanceForge(m_forge->version(), this, this))); - //FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again. - //TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly. - task->addTask(OneSixInstance::doUpdate()); - return task; + return OneSixInstance::doUpdate(); } #include "OneSixFTBInstance.moc" diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h index bc543aeb..c4f845e0 100644 --- a/logic/OneSixFTBInstance.h +++ b/logic/OneSixFTBInstance.h @@ -10,6 +10,10 @@ class OneSixFTBInstance : public OneSixInstance public: explicit OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent = 0); + + void init() override; + void copy(const QDir &newDir) override; + virtual QString getStatusbarDescription(); virtual bool menuActionEnabled(QString action_name) const; @@ -17,6 +21,11 @@ public: virtual QString id() const; + QDir librariesPath() const override; + QDir versionsPath() const override; + QStringList externalPatches() const override; + bool providesVersionFile() const override; + private: std::shared_ptr<OneSixLibrary> m_forge; }; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index b85a593c..10411c56 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -36,6 +36,10 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, d->m_settings->registerSetting("ShouldUpdate", false); d->version.reset(new OneSixVersion(this, this)); d->vanillaVersion.reset(new OneSixVersion(this, this)); +} + +void OneSixInstance::init() +{ if (QDir(instanceRoot()).exists("version.json")) { reloadVersion(); @@ -192,12 +196,10 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session) auto libs = version->getActiveNormalLibs(); for (auto lib : libs) { - QFileInfo fi(QString("libraries/") + lib->storagePath()); - launchScript += "cp " + fi.absoluteFilePath() + "\n"; + launchScript += "cp " + librariesPath().absoluteFilePath(lib->storagePath()) + "\n"; } - QString targetstr = "versions/" + version->id + "/" + version->id + ".jar"; - QFileInfo fi(targetstr); - launchScript += "cp " + fi.absoluteFilePath() + "\n"; + QString targetstr = version->id + "/" + version->id + ".jar"; + launchScript += "cp " + versionsPath().absoluteFilePath(targetstr) + "\n"; } launchScript += "mainClass " + version->mainClass + "\n"; @@ -228,7 +230,7 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session) launchScript += "ext " + finfo.absoluteFilePath() + "\n"; } launchScript += "natives " + natives_dir.absolutePath() + "\n"; - launchScript += "launch onesix\n"; + launchScript += "launcher onesix\n"; // create the process and set its parameters MinecraftProcess *proc = new MinecraftProcess(this); @@ -318,10 +320,10 @@ bool OneSixInstance::reloadVersion(QWidget *widgetParent) { I_D(OneSixInstance); - bool ret = d->version->reload(widgetParent); + bool ret = d->version->reload(widgetParent, false, externalPatches()); if (ret) { - ret = d->vanillaVersion->reload(widgetParent, true); + ret = d->vanillaVersion->reload(widgetParent, true, externalPatches()); } if (ret) { @@ -392,6 +394,25 @@ QString OneSixInstance::getStatusbarDescription() return descr; } +QDir OneSixInstance::librariesPath() const +{ + return QDir::current().absoluteFilePath("libraries"); +} +QDir OneSixInstance::versionsPath() const +{ + return QDir::current().absoluteFilePath("versions"); +} + +QStringList OneSixInstance::externalPatches() const +{ + return QStringList(); +} + +bool OneSixInstance::providesVersionFile() const +{ + return false; +} + QString OneSixInstance::loaderModsDir() const { return PathCombine(minecraftRoot(), "mods"); diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index ae95eab1..06fd9de3 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -27,6 +27,8 @@ public: explicit OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent = 0); + virtual void init() override; + ////// Mod Lists ////// std::shared_ptr<ModList> loaderModList(); std::shared_ptr<ModList> resourcePackList(); @@ -68,6 +70,11 @@ public: virtual bool menuActionEnabled(QString action_name) const override; virtual QString getStatusbarDescription() override; + virtual QDir librariesPath() const; + virtual QDir versionsPath() const; + virtual QStringList externalPatches() const; + virtual bool providesVersionFile() const; + signals: void versionReloaded(); diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index d3ac80c2..f87c65e7 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -35,7 +35,7 @@ #include "pathutils.h" #include <JlCompress.h> -OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent) +OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { } @@ -73,6 +73,11 @@ void OneSixUpdate::executeTask() void OneSixUpdate::versionFileStart() { + if (m_inst->providesVersionFile()) + { + jarlibStart(); + return; + } QLOG_INFO() << m_inst->name() << ": getting version file."; setStatus(tr("Getting the version files from Mojang...")); diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h index 3c18211e..eac882b5 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -23,13 +23,13 @@ #include "logic/tasks/Task.h" class MinecraftVersion; -class BaseInstance; +class OneSixInstance; class OneSixUpdate : public Task { Q_OBJECT public: - explicit OneSixUpdate(BaseInstance *inst, QObject *parent = 0); + explicit OneSixUpdate(OneSixInstance *inst, QObject *parent = 0); virtual void executeTask(); private @@ -55,5 +55,5 @@ private: // target version, determined during this task std::shared_ptr<MinecraftVersion> targetVersion; - BaseInstance *m_inst = nullptr; + OneSixInstance *m_inst = nullptr; }; diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index fb32f3a8..06e748bd 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -26,10 +26,10 @@ OneSixVersion::OneSixVersion(OneSixInstance *instance, QObject *parent) clear(); } -bool OneSixVersion::reload(QWidget *widgetParent, const bool onlyVanilla) +bool OneSixVersion::reload(QWidget *widgetParent, const bool onlyVanilla, const QStringList &external) { beginResetModel(); - bool ret = OneSixVersionBuilder::build(this, m_instance, widgetParent, onlyVanilla); + bool ret = OneSixVersionBuilder::build(this, m_instance, widgetParent, onlyVanilla, external); endResetModel(); return ret; } diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index ba7695d5..fee47fa3 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -37,7 +37,7 @@ public: virtual int columnCount(const QModelIndex &parent) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; - bool reload(QWidget *widgetParent, const bool onlyVanilla = false); + bool reload(QWidget *widgetParent, const bool onlyVanilla = false, const QStringList &external = QStringList()); void clear(); void dump() const; diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp index a4bac80a..f6917697 100644 --- a/logic/OneSixVersionBuilder.cpp +++ b/logic/OneSixVersionBuilder.cpp @@ -32,7 +32,7 @@ #include "modutils.h" #include "logger/QsLog.h" -#define CURRENT_MINIMUM_LAUNCHER_VERSION 13 +#define CURRENT_MINIMUM_LAUNCHER_VERSION 14 struct VersionFile { @@ -201,7 +201,7 @@ struct VersionFile return out; } static VersionFile fromJson(const QJsonDocument &doc, const QString &filename, - const bool requireOrder, bool &isError) + const bool requireOrder, bool &isError, const OneSixVersionBuilder::ParseFlags flags = OneSixVersionBuilder::NoFlags) { VersionFile out; isError = true; @@ -260,7 +260,10 @@ struct VersionFile } }; - readString("id", out.id); + if (!(flags & OneSixVersionBuilder::IsFTBPackJson)) + { + readString("id", out.id); + } readString("mainClass", out.mainClass); readString("processArguments", out.processArguments); readString("minecraftArguments", out.overwriteMinecraftArguments); @@ -352,7 +355,7 @@ struct VersionFile if (root.contains("libraries")) { - out.shouldOverwriteLibs = true; + out.shouldOverwriteLibs = !(flags & OneSixVersionBuilder::IsFTBPackJson); QJsonValue librariesVal = root.value("libraries"); if (!librariesVal.isArray()) { @@ -376,7 +379,16 @@ struct VersionFile QLOG_ERROR() << "Error while reading a library entry in" << filename; return out; } - out.overwriteLibs.append(lib); + if (flags & OneSixVersionBuilder::IsFTBPackJson) + { + lib.hint = "local"; + lib.insertType = Library::Prepend; + out.addLibs.prepend(lib); + } + else + { + out.overwriteLibs.append(lib); + } } } if (root.contains("+libraries")) @@ -795,13 +807,13 @@ OneSixVersionBuilder::OneSixVersionBuilder() } bool OneSixVersionBuilder::build(OneSixVersion *version, OneSixInstance *instance, - QWidget *widgetParent, const bool onlyVanilla) + QWidget *widgetParent, const bool onlyVanilla, const QStringList &external) { OneSixVersionBuilder builder; builder.m_version = version; builder.m_instance = instance; builder.m_widgetParent = widgetParent; - return builder.build(onlyVanilla); + return builder.build(onlyVanilla, external); } bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj) @@ -813,14 +825,34 @@ bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj) return builder.read(obj); } -bool OneSixVersionBuilder::build(const bool onlyVanilla) +bool OneSixVersionBuilder::build(const bool onlyVanilla, const QStringList &external) { m_version->clear(); QDir root(m_instance->instanceRoot()); QDir patches(root.absoluteFilePath("patches/")); - if (QFile::exists(root.absoluteFilePath("custom.json"))) + // if we do external files, do just those. + if(!external.isEmpty()) for (auto fileName : external) + { + QLOG_INFO() << "Reading" << fileName; + VersionFile file; + ParseFlags flags = fileName.endsWith("pack.json") ? IsFTBPackJson : NoFlags; + if (!read(QFileInfo(fileName), false, &file, flags)) + { + return false; + } + file.name = QFileInfo(fileName).fileName(); + file.fileId = "org.multimc.external." + file.name; + file.version = QString(); + file.mcVersion = QString(); + bool isError = false; + auto errorcode = file.applyTo(m_version); + if(errorcode != VersionFile::NoApplyError) + return false; + } + // else, if there's custom json, we just do that. + else if (QFile::exists(root.absoluteFilePath("custom.json"))) { QLOG_INFO() << "Reading custom.json"; VersionFile file; @@ -832,142 +864,79 @@ bool OneSixVersionBuilder::build(const bool onlyVanilla) file.filename = "custom.json"; file.fileId = "org.multimc.custom.json"; file.version = QString(); - VersionFile::ApplyError error = file.applyTo(m_version); - if (error == VersionFile::OtherError) + auto errorcode = file.applyTo(m_version); + if(errorcode != VersionFile::NoApplyError) + return false; + // QObject::tr("The version descriptors of this instance are not compatible with the current version of MultiMC")); + // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more info.") + } + // version.json -> patches/*.json -> user.json + else do + { + // version.json + QLOG_INFO() << "Reading version.json"; + VersionFile file; + if (!read(QFileInfo(root.absoluteFilePath("version.json")), false, &file)) { - QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr( - "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(root.absoluteFilePath("custom.json"))); return false; } - else if (error == VersionFile::LauncherVersionError) + file.name = "version.json"; + file.fileId = "org.multimc.version.json"; + file.version = m_instance->intendedVersionId(); + file.mcVersion = m_instance->intendedVersionId(); + auto error = file.applyTo(m_version); + if (error != VersionFile::NoApplyError) { QMessageBox::critical( m_widgetParent, QObject::tr("Error"), - QObject::tr("The version descriptors of this instance are not compatible with the current version of MultiMC")); + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("version.json"))); return false; } - } - else - { - // version.json -> patches/*.json -> user.json - // version.json + if (onlyVanilla) + break; + + // patches/ + // load all, put into map for ordering, apply in the right order + QMap<QString, int> overrideOrder = readOverrideOrders(m_instance); + + QMap<int, QPair<QString, VersionFile>> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) { - QLOG_INFO() << "Reading version.json"; + QLOG_INFO() << "Reading" << info.fileName(); VersionFile file; - if (!read(QFileInfo(root.absoluteFilePath("version.json")), false, &file)) + if (!read(info, true, &file)) { return false; } - file.name = "version.json"; - file.fileId = "org.multimc.version.json"; - file.version = m_instance->intendedVersionId(); - file.mcVersion = m_instance->intendedVersionId(); - VersionFile::ApplyError error = file.applyTo(m_version); - if (error == VersionFile::OtherError) + if (overrideOrder.contains(file.fileId)) { - QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr( - "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(root.absoluteFilePath("version.json"))); - return false; + file.order = overrideOrder.value(file.fileId); } - else if (error == VersionFile::LauncherVersionError) + if (files.contains(file.order)) { - QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr("The version descriptors of this instance are not compatible with the current version of MultiMC")); + QLOG_ERROR() << file.fileId << "has the same order as" << files[file.order].second.fileId; return false; } + files.insert(file.order, qMakePair(info.fileName(), file)); } - - if (!onlyVanilla) + for (auto order : files.keys()) { - - // patches/ + QLOG_DEBUG() << "Applying file with order" << order; + auto filePair = files[order]; + auto error = filePair.second.applyTo(m_version); + if (error != VersionFile::NoApplyError) { - // load all, put into map for ordering, apply in the right order - QMap<QString, int> overrideOrder = readOverrideOrders(m_instance); - - QMap<int, QPair<QString, VersionFile>> files; - for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - QLOG_INFO() << "Reading" << info.fileName(); - VersionFile file; - if (!read(info, true, &file)) - { - return false; - } - if (overrideOrder.contains(file.fileId)) - { - file.order = overrideOrder.value(file.fileId); - } - if (files.contains(file.order)) - { - QLOG_ERROR() << file.fileId << "has the same order as" - << files[file.order].second.fileId; - return false; - } - files.insert(file.order, qMakePair(info.fileName(), file)); - } - for (auto order : files.keys()) - { - QLOG_DEBUG() << "Applying file with order" << order; - auto filePair = files[order]; - VersionFile::ApplyError error = filePair.second.applyTo(m_version); - if (error == VersionFile::OtherError) - { - QMessageBox::critical( + QMessageBox::critical( m_widgetParent, QObject::tr("Error"), QObject::tr("Error while applying %1. Please check MultiMC-0.log " "for more info.").arg(filePair.first)); - return false; - } - else if (error == VersionFile::LauncherVersionError) - { - QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr("The version descriptors of this instance are not compatible with the current version of MultiMC")); - return false; - } - } - } - -#if 0 - // user.json - { - if (QFile::exists(root.absoluteFilePath("user.json"))) - { - QLOG_INFO() << "Reading user.json"; - VersionFile file; - if (!read(QFileInfo(root.absoluteFilePath("user.json")), false, &file)) - { - return false; - } - file.name = "user.json"; - file.fileId = "org.multimc.user.json"; - file.version = QString(); - file.mcVersion = QString(); - bool isError = false; - file.applyTo(m_version, isError); - if (isError) - { - QMessageBox::critical( - m_widgetParent, QObject::tr("Error"), - QObject::tr( - "Error while applying %1. Please check MultiMC-0.log for more info.") - .arg(root.absoluteFilePath("user.json"))); - return false; - } - } + return false; } -#endif } - } + } while(0); // some final touches { @@ -1032,7 +1001,7 @@ bool OneSixVersionBuilder::read(const QJsonObject &obj) } bool OneSixVersionBuilder::read(const QFileInfo &fileInfo, const bool requireOrder, - VersionFile *out) + VersionFile *out, const ParseFlags flags) { QFile file(fileInfo.absoluteFilePath()); if (!file.open(QFile::ReadOnly)) @@ -1053,7 +1022,7 @@ bool OneSixVersionBuilder::read(const QFileInfo &fileInfo, const bool requireOrd return false; } bool isError = false; - *out = VersionFile::fromJson(doc, file.fileName(), requireOrder, isError); + *out = VersionFile::fromJson(doc, file.fileName(), requireOrder, isError, flags); if (isError) { QMessageBox::critical( @@ -1125,3 +1094,4 @@ bool OneSixVersionBuilder::writeOverrideOrders(const QMap<QString, int> &order, orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); return true; } + diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h index ab0966df..8cf6f32f 100644 --- a/logic/OneSixVersionBuilder.h +++ b/logic/OneSixVersionBuilder.h @@ -29,19 +29,27 @@ class OneSixVersionBuilder { OneSixVersionBuilder(); public: - static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool onlyVanilla); + static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool onlyVanilla, const QStringList &external); static bool read(OneSixVersion *version, const QJsonObject &obj); static QMap<QString, int> readOverrideOrders(OneSixInstance *instance); static bool writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance); + enum ParseFlag + { + NoFlags = 0x0, + IsFTBPackJson = 0x1 + }; + Q_DECLARE_FLAGS(ParseFlags, ParseFlag) + private: OneSixVersion *m_version; OneSixInstance *m_instance; QWidget *m_widgetParent; - bool build(const bool onlyVanilla); + bool build(const bool onlyVanilla, const QStringList &external); bool read(const QJsonObject &obj); - bool read(const QFileInfo &fileInfo, const bool requireOrder, VersionFile *out); - + bool read(const QFileInfo &fileInfo, const bool requireOrder, VersionFile *out, const ParseFlags flags = NoFlags); }; + +Q_DECLARE_OPERATORS_FOR_FLAGS(OneSixVersionBuilder::ParseFlags) diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index cd59e6d6..9a61e2dd 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -336,8 +336,6 @@ QList<FTBRecord> InstanceList::discoverFTBInstances() if (!test.exists()) continue; record.name = attrs.value("name").toString(); - if(record.name.contains("voxel", Qt::CaseInsensitive)) - continue; record.logo = attrs.value("logo").toString(); record.mcVersion = attrs.value("mcVersion").toString(); record.description = attrs.value("description").toString(); diff --git a/logic/lists/LiteLoaderVersionList.cpp b/logic/lists/LiteLoaderVersionList.cpp new file mode 100644 index 00000000..b8cea442 --- /dev/null +++ b/logic/lists/LiteLoaderVersionList.cpp @@ -0,0 +1,190 @@ +/* Copyright 2013 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 "LiteLoaderVersionList.h" +#include "MultiMC.h" +#include "logic/net/URLConstants.h" + +#include <QtXml> + +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonValue> +#include <QJsonParseError> + +#include <QtAlgorithms> + +#include <QtNetwork> + +LiteLoaderVersionList::LiteLoaderVersionList(QObject *parent) : BaseVersionList(parent) +{ +} + +Task *LiteLoaderVersionList::getLoadTask() +{ + return new LLListLoadTask(this); +} + +bool LiteLoaderVersionList::isLoaded() +{ + return m_loaded; +} + +const BaseVersionPtr LiteLoaderVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +int LiteLoaderVersionList::count() const +{ + return m_vlist.count(); +} + +static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) +{ + auto left = std::dynamic_pointer_cast<LiteLoaderVersion>(first); + auto right = std::dynamic_pointer_cast<LiteLoaderVersion>(second); + return left->timestamp > right->timestamp; +} + +void LiteLoaderVersionList::sort() +{ + beginResetModel(); + qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); + endResetModel(); +} + +BaseVersionPtr LiteLoaderVersionList::getLatestStable() const +{ + for (int i = 0; i < m_vlist.length(); i++) + { + auto ver = std::dynamic_pointer_cast<LiteLoaderVersion>(m_vlist.at(i)); + if (ver->isLatest) + { + return m_vlist.at(i); + } + } + return BaseVersionPtr(); +} + +void LiteLoaderVersionList::updateListData(QList<BaseVersionPtr> versions) +{ + beginResetModel(); + m_vlist = versions; + m_loaded = true; + qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); + endResetModel(); +} + +LLListLoadTask::LLListLoadTask(LiteLoaderVersionList *vlist) +{ + m_list = vlist; + vlistReply = nullptr; +} + +LLListLoadTask::~LLListLoadTask() +{ +} + +void LLListLoadTask::executeTask() +{ + setStatus(tr("Loading LiteLoader version list...")); + auto worker = MMC->qnam(); + vlistReply = worker->get(QNetworkRequest(QUrl(URLConstants::LITELOADER_URL))); + connect(vlistReply, SIGNAL(finished()), this, SLOT(listDownloaded())); +} + +void LLListLoadTask::listDownloaded() +{ + if (vlistReply->error() != QNetworkReply::NoError) + { + vlistReply->deleteLater(); + emitFailed("Failed to load LiteLoader version list" + vlistReply->errorString()); + return; + } + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError); + vlistReply->deleteLater(); + + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed("Error parsing version list JSON:" + jsonError.errorString()); + return; + } + + if (!jsonDoc.isObject()) + { + emitFailed("Error parsing version list JSON: jsonDoc is not an object"); + return; + } + + const QJsonObject root = jsonDoc.object(); + + // Now, get the array of versions. + if (!root.value("versions").isObject()) + { + emitFailed("Error parsing version list JSON: missing 'versions' object"); + return; + } + const QJsonObject versions = root.value("versions").toObject(); + + QList<BaseVersionPtr> tempList; + for (auto vIt = versions.begin(); vIt != versions.end(); ++vIt) + { + const QString mcVersion = vIt.key(); + QString latest; + const QJsonObject artefacts = vIt.value() + .toObject() + .value("artefacts") + .toObject() + .value("com.mumfrey:liteloader") + .toObject(); + QList<BaseVersionPtr> perMcVersionList; + for (auto aIt = artefacts.begin(); aIt != artefacts.end(); ++aIt) + { + const QString identifier = aIt.key(); + const QJsonObject artefact = aIt.value().toObject(); + if (identifier == "latest") + { + latest = artefact.value("version").toString(); + continue; + } + LiteLoaderVersionPtr version(new LiteLoaderVersion()); + version->version = artefact.value("version").toString(); + version->file = artefact.value("file").toString(); + version->mcVersion = mcVersion; + version->md5 = artefact.value("md5").toString(); + version->timestamp = artefact.value("timestamp").toDouble(); + version->tweakClass = artefact.value("tweakClass").toString(); + const QJsonArray libs = artefact.value("libraries").toArray(); + for (auto lIt = libs.begin(); lIt != libs.end(); ++lIt) + { + version->libraries.append((*lIt).toObject().value("name").toString()); + } + perMcVersionList.append(version); + } + for (auto version : perMcVersionList) + { + auto v = std::dynamic_pointer_cast<LiteLoaderVersion>(version); + v->isLatest = v->version == latest; + } + tempList.append(perMcVersionList); + } + m_list->updateListData(tempList); + + emitSucceeded(); +} diff --git a/logic/lists/LiteLoaderVersionList.h b/logic/lists/LiteLoaderVersionList.h new file mode 100644 index 00000000..58927b8f --- /dev/null +++ b/logic/lists/LiteLoaderVersionList.h @@ -0,0 +1,101 @@ +/* Copyright 2013 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 <QObject> + +#include "BaseVersionList.h" +#include "logic/tasks/Task.h" +#include "logic/BaseVersion.h" + +class LLListLoadTask; +class QNetworkReply; + +class LiteLoaderVersion : public BaseVersion +{ +public: + QString descriptor() override + { + if (isLatest) + { + return QObject::tr("Latest"); + } + return QString(); + } + QString typeString() const override + { + return mcVersion; + } + QString name() override + { + return version; + } + + QString version; + QString file; + QString mcVersion; + QString md5; + int timestamp; + bool isLatest; + QString tweakClass; + QStringList libraries; +}; +typedef std::shared_ptr<LiteLoaderVersion> LiteLoaderVersionPtr; + +class LiteLoaderVersionList : public BaseVersionList +{ + Q_OBJECT +public: + friend class LLListLoadTask; + + explicit LiteLoaderVersionList(QObject *parent = 0); + + virtual Task *getLoadTask(); + virtual bool isLoaded(); + virtual const BaseVersionPtr at(int i) const; + virtual int count() const; + virtual void sort(); + + virtual BaseVersionPtr getLatestStable() const; + +protected: + QList<BaseVersionPtr> m_vlist; + + bool m_loaded = false; + +protected +slots: + virtual void updateListData(QList<BaseVersionPtr> versions); +}; + +class LLListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit LLListLoadTask(LiteLoaderVersionList *vlist); + ~LLListLoadTask(); + + virtual void executeTask(); + +protected +slots: + void listDownloaded(); + +protected: + QNetworkReply *vlistReply; + LiteLoaderVersionList *m_list; +}; diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp index ece31e3d..b9d60c61 100644 --- a/logic/lists/MinecraftVersionList.cpp +++ b/logic/lists/MinecraftVersionList.cpp @@ -53,7 +53,7 @@ int MinecraftVersionList::count() const return m_vlist.count(); } -bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) +static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) { auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); diff --git a/logic/net/URLConstants.h b/logic/net/URLConstants.h index 8cb1f3fd..07b35c4f 100644 --- a/logic/net/URLConstants.h +++ b/logic/net/URLConstants.h @@ -33,4 +33,5 @@ const QString FORGE_LEGACY_URL("http://files.minecraftforge.net/minecraftforge/j const QString FORGE_GRADLE_URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/json"); const QString MOJANG_STATUS_URL("http://status.mojang.com/check"); const QString MOJANG_STATUS_NEWS_URL("http://status.mojang.com/news"); +const QString LITELOADER_URL("http://dl.liteloader.com/versions/versions.json"); } diff --git a/logic/tools/BaseExternalTool.cpp b/logic/tools/BaseExternalTool.cpp new file mode 100644 index 00000000..69cddd00 --- /dev/null +++ b/logic/tools/BaseExternalTool.cpp @@ -0,0 +1,77 @@ +#include "BaseExternalTool.h" + +#include <QProcess> +#include <QDir> +#include <QInputDialog> + +#ifdef Q_OS_WIN +#include <windows.h> +#endif + +#include "logic/BaseInstance.h" +#include "MultiMC.h" + +BaseExternalTool::BaseExternalTool(BaseInstance *instance, QObject *parent) + : QObject(parent), m_instance(instance) +{ +} + +BaseExternalTool::~BaseExternalTool() +{ +} + +qint64 BaseExternalTool::pid(QProcess *process) +{ +#ifdef Q_OS_WIN + struct _PROCESS_INFORMATION *procinfo = process->pid(); + return procinfo->dwProcessId; +#else + return process->pid(); +#endif +} + +QString BaseExternalTool::getSave() const +{ + QDir saves(m_instance->minecraftRoot() + "/saves"); + QStringList worlds = saves.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + QMutableListIterator<QString> 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(BaseInstance *instance, QObject *parent) + : BaseExternalTool(instance, parent) +{ + +} + +void BaseDetachedTool::run() +{ + runImpl(); +} + + +BaseExternalToolFactory::~BaseExternalToolFactory() +{ +} + +BaseDetachedTool *BaseDetachedToolFactory::createDetachedTool(BaseInstance *instance, QObject *parent) +{ + return qobject_cast<BaseDetachedTool *>(createTool(instance, parent)); +} diff --git a/logic/tools/BaseExternalTool.h b/logic/tools/BaseExternalTool.h new file mode 100644 index 00000000..e8965bfd --- /dev/null +++ b/logic/tools/BaseExternalTool.h @@ -0,0 +1,57 @@ +#pragma once + +#include <QObject> + +class BaseInstance; +class SettingsObject; +class MinecraftProcess; +class QProcess; + +class BaseExternalTool : public QObject +{ + Q_OBJECT +public: + explicit BaseExternalTool(BaseInstance *instance, QObject *parent = 0); + virtual ~BaseExternalTool(); + +protected: + BaseInstance *m_instance; + + qint64 pid(QProcess *process); + QString getSave() const; +}; + +class BaseDetachedTool : public BaseExternalTool +{ + Q_OBJECT +public: + explicit BaseDetachedTool(BaseInstance *instance, QObject *parent = 0); + +public +slots: + void run(); + +protected: + virtual void runImpl() = 0; +}; + +class BaseExternalToolFactory +{ +public: + virtual ~BaseExternalToolFactory(); + + virtual QString name() const = 0; + + virtual void registerSettings(SettingsObject *settings) = 0; + + virtual BaseExternalTool *createTool(BaseInstance *instance, QObject *parent = 0) = 0; + + virtual bool check(QString *error) = 0; + virtual bool check(const QString &path, QString *error) = 0; +}; + +class BaseDetachedToolFactory : public BaseExternalToolFactory +{ +public: + virtual BaseDetachedTool *createDetachedTool(BaseInstance *instance, QObject *parent = 0); +}; diff --git a/logic/tools/BaseProfiler.cpp b/logic/tools/BaseProfiler.cpp new file mode 100644 index 00000000..9aaca793 --- /dev/null +++ b/logic/tools/BaseProfiler.cpp @@ -0,0 +1,35 @@ +#include "BaseProfiler.h" + +#include <QProcess> + +BaseProfiler::BaseProfiler(BaseInstance *instance, QObject *parent) + : BaseExternalTool(instance, parent) +{ +} + +void BaseProfiler::beginProfiling(MinecraftProcess *process) +{ + beginProfilingImpl(process); +} + +void BaseProfiler::abortProfiling() +{ + abortProfilingImpl(); +} + +void BaseProfiler::abortProfilingImpl() +{ + if (!m_profilerProcess) + { + return; + } + m_profilerProcess->terminate(); + m_profilerProcess->deleteLater(); + m_profilerProcess = 0; + emit abortLaunch(tr("Profiler aborted")); +} + +BaseProfiler *BaseProfilerFactory::createProfiler(BaseInstance *instance, QObject *parent) +{ + return qobject_cast<BaseProfiler *>(createTool(instance, parent)); +} diff --git a/logic/tools/BaseProfiler.h b/logic/tools/BaseProfiler.h new file mode 100644 index 00000000..ec57578e --- /dev/null +++ b/logic/tools/BaseProfiler.h @@ -0,0 +1,36 @@ +#pragma once + +#include "BaseExternalTool.h" + +class BaseInstance; +class SettingsObject; +class MinecraftProcess; +class QProcess; + +class BaseProfiler : public BaseExternalTool +{ + Q_OBJECT +public: + explicit BaseProfiler(BaseInstance *instance, QObject *parent = 0); + +public +slots: + void beginProfiling(MinecraftProcess *process); + void abortProfiling(); + +protected: + QProcess *m_profilerProcess; + + virtual void beginProfilingImpl(MinecraftProcess *process) = 0; + virtual void abortProfilingImpl(); + +signals: + void readyToLaunch(const QString &message); + void abortLaunch(const QString &message); +}; + +class BaseProfilerFactory : public BaseExternalToolFactory +{ +public: + virtual BaseProfiler *createProfiler(BaseInstance *instance, QObject *parent = 0); +}; diff --git a/logic/tools/JProfiler.cpp b/logic/tools/JProfiler.cpp new file mode 100644 index 00000000..bb851f0b --- /dev/null +++ b/logic/tools/JProfiler.cpp @@ -0,0 +1,78 @@ +#include "JProfiler.h" + +#include <QDir> +#include <QMessageBox> + +#include "settingsobject.h" +#include "logic/MinecraftProcess.h" +#include "logic/BaseInstance.h" +#include "MultiMC.h" + +JProfiler::JProfiler(BaseInstance *instance, QObject *parent) : BaseProfiler(instance, parent) +{ +} + +void JProfiler::beginProfilingImpl(MinecraftProcess *process) +{ + int port = MMC->settings()->get("JProfilerPort").toInt(); + QProcess *profiler = new QProcess(this); + profiler->setArguments(QStringList() << "-d" << QString::number(pid(process)) << "--gui" + << "-p" << QString::number(port)); + profiler->setProgram(QDir(MMC->settings()->get("JProfilerPath").toString()) + .absoluteFilePath("bin/jpenable")); + connect(profiler, &QProcess::started, [this, port]() + { emit readyToLaunch(tr("Listening on port: %1").arg(port)); }); + connect(profiler, + static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), + [this](int exit, QProcess::ExitStatus status) + { + if (status == QProcess::CrashExit) + { + emit abortLaunch(tr("Profiler aborted")); + } + if (m_profilerProcess) + { + m_profilerProcess->deleteLater(); + m_profilerProcess = 0; + } + }); + profiler->start(); + m_profilerProcess = profiler; +} + +void JProfilerFactory::registerSettings(SettingsObject *settings) +{ + settings->registerSetting("JProfilerPath"); + settings->registerSetting("JProfilerPort", 42042); +} + +BaseExternalTool *JProfilerFactory::createTool(BaseInstance *instance, QObject *parent) +{ + return new JProfiler(instance, parent); +} + +bool JProfilerFactory::check(QString *error) +{ + return check(MMC->settings()->get("JProfilerPath").toString(), error); +} + +bool JProfilerFactory::check(const QString &path, QString *error) +{ + if (path.isEmpty()) + { + *error = QObject::tr("Empty path"); + return false; + } + QDir dir(path); + if (!dir.exists()) + { + *error = QObject::tr("Path does not exist"); + return false; + } + if (!dir.exists("bin") || !dir.exists("bin/jprofiler") || !dir.exists("bin/agent.jar")) + { + *error = QObject::tr("Invalid JProfiler install"); + return false; + } + return true; +} diff --git a/logic/tools/JProfiler.h b/logic/tools/JProfiler.h new file mode 100644 index 00000000..88a02462 --- /dev/null +++ b/logic/tools/JProfiler.h @@ -0,0 +1,23 @@ +#pragma once + +#include "BaseProfiler.h" + +class JProfiler : public BaseProfiler +{ + Q_OBJECT +public: + JProfiler(BaseInstance *instance, QObject *parent = 0); + +protected: + void beginProfilingImpl(MinecraftProcess *process); +}; + +class JProfilerFactory : public BaseProfilerFactory +{ +public: + QString name() const override { return "JProfiler"; } + void registerSettings(SettingsObject *settings) override; + BaseExternalTool *createTool(BaseInstance *instance, QObject *parent = 0) override; + bool check(QString *error) override; + bool check(const QString &path, QString *error) override; +}; diff --git a/logic/tools/JVisualVM.cpp b/logic/tools/JVisualVM.cpp new file mode 100644 index 00000000..02938028 --- /dev/null +++ b/logic/tools/JVisualVM.cpp @@ -0,0 +1,74 @@ +#include "JVisualVM.h" + +#include <QDir> +#include <QStandardPaths> + +#include "settingsobject.h" +#include "logic/MinecraftProcess.h" +#include "logic/BaseInstance.h" +#include "MultiMC.h" + +JVisualVM::JVisualVM(BaseInstance *instance, QObject *parent) : BaseProfiler(instance, parent) +{ +} + +void JVisualVM::beginProfilingImpl(MinecraftProcess *process) +{ + QProcess *profiler = new QProcess(this); + profiler->setArguments(QStringList() << "--openpid" << QString::number(pid(process))); + profiler->setProgram(MMC->settings()->get("JVisualVMPath").toString()); + connect(profiler, &QProcess::started, [this]() + { emit readyToLaunch(tr("JVisualVM started")); }); + connect(profiler, + static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), + [this](int exit, QProcess::ExitStatus status) + { + if (exit != 0 || status == QProcess::CrashExit) + { + emit abortLaunch(tr("Profiler aborted")); + } + if (m_profilerProcess) + { + m_profilerProcess->deleteLater(); + m_profilerProcess = 0; + } + }); + profiler->start(); + m_profilerProcess = profiler; +} + +void JVisualVMFactory::registerSettings(SettingsObject *settings) +{ + QString defaultValue = QStandardPaths::findExecutable("jvisualvm"); + if (defaultValue.isNull()) + { + defaultValue = QStandardPaths::findExecutable("visualvm"); + } + settings->registerSetting("JVisualVMPath", defaultValue); +} + +BaseExternalTool *JVisualVMFactory::createTool(BaseInstance *instance, QObject *parent) +{ + return new JVisualVM(instance, parent); +} + +bool JVisualVMFactory::check(QString *error) +{ + return check(MMC->settings()->get("JVisualVMPath").toString(), error); +} + +bool JVisualVMFactory::check(const QString &path, QString *error) +{ + if (path.isEmpty()) + { + *error = QObject::tr("Empty path"); + return false; + } + QString resolved = QStandardPaths::findExecutable(path); + if (resolved.isEmpty() && !QDir::isAbsolutePath(path)) + { + *error = QObject::tr("Invalid path to JVisualVM"); + return false; + } + return true; +} diff --git a/logic/tools/JVisualVM.h b/logic/tools/JVisualVM.h new file mode 100644 index 00000000..af94fe55 --- /dev/null +++ b/logic/tools/JVisualVM.h @@ -0,0 +1,23 @@ +#pragma once + +#include "BaseProfiler.h" + +class JVisualVM : public BaseProfiler +{ + Q_OBJECT +public: + JVisualVM(BaseInstance *instance, QObject *parent = 0); + +protected: + void beginProfilingImpl(MinecraftProcess *process); +}; + +class JVisualVMFactory : public BaseProfilerFactory +{ +public: + QString name() const override { return "JVisualVM"; } + void registerSettings(SettingsObject *settings) override; + BaseExternalTool *createTool(BaseInstance *instance, QObject *parent = 0) override; + bool check(QString *error) override; + bool check(const QString &path, QString *error) override; +}; diff --git a/logic/tools/MCEditTool.cpp b/logic/tools/MCEditTool.cpp new file mode 100644 index 00000000..e22a5d4a --- /dev/null +++ b/logic/tools/MCEditTool.cpp @@ -0,0 +1,77 @@ +#include "MCEditTool.h" + +#include <QDir> +#include <QProcess> +#include <QDesktopServices> +#include <QUrl> + +#include "settingsobject.h" +#include "logic/BaseInstance.h" +#include "MultiMC.h" + +MCEditTool::MCEditTool(BaseInstance *instance, QObject *parent) + : BaseDetachedTool(instance, parent) +{ +} + +void MCEditTool::runImpl() +{ + const QString mceditPath = MMC->settings()->get("MCEditPath").toString(); + const QString save = getSave(); + if (save.isNull()) + { + return; + } +#ifdef Q_OS_OSX + QProcess *process = new QProcess(); + connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), process, SLOT(deleteLater())); + process->setProgram(mceditPath); + process->setArguments(QStringList() << save); + process->start(); +#else + QDir mceditDir(mceditPath); + QString program; + if (mceditDir.exists("mcedit.py")) + { + program = mceditDir.absoluteFilePath("mcedit.py"); + } + else if (mceditDir.exists("mcedit.exe")) + { + program = mceditDir.absoluteFilePath("mcedit.exe"); + } + QProcess::startDetached(program, QStringList() << save, mceditPath); +#endif +} + +void MCEditFactory::registerSettings(SettingsObject *settings) +{ + settings->registerSetting("MCEditPath"); +} +BaseExternalTool *MCEditFactory::createTool(BaseInstance *instance, QObject *parent) +{ + return new MCEditTool(instance, parent); +} +bool MCEditFactory::check(QString *error) +{ + return check(MMC->settings()->get("MCEditPath").toString(), error); +} +bool MCEditFactory::check(const QString &path, QString *error) +{ + if (path.isEmpty()) + { + *error = QObject::tr("Path is empty"); + return false; + } + const QDir dir(path); + if (!dir.exists()) + { + *error = QObject::tr("Path does not exist"); + return false; + } + if (!dir.exists("mcedit.py") && !dir.exists("mcedit.exe") && !dir.exists("Contents")) + { + *error = QObject::tr("Path does not seem to be a MCEdit path"); + return false; + } + return true; +} diff --git a/logic/tools/MCEditTool.h b/logic/tools/MCEditTool.h new file mode 100644 index 00000000..b0ed1ad4 --- /dev/null +++ b/logic/tools/MCEditTool.h @@ -0,0 +1,23 @@ +#pragma once + +#include "BaseExternalTool.h" + +class MCEditTool : public BaseDetachedTool +{ + Q_OBJECT +public: + explicit MCEditTool(BaseInstance *instance, QObject *parent = 0); + +protected: + void runImpl() override; +}; + +class MCEditFactory : public BaseDetachedToolFactory +{ +public: + QString name() const override { return "MCEdit"; } + void registerSettings(SettingsObject *settings) override; + BaseExternalTool *createTool(BaseInstance *instance, QObject *parent = 0) override; + bool check(QString *error) override; + bool check(const QString &path, QString *error) override; +}; |