summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt14
-rw-r--r--MultiMC.cpp34
-rw-r--r--MultiMC.h17
-rw-r--r--depends/launcher/org/multimc/EntryPoint.java36
-rw-r--r--gui/MainWindow.cpp118
-rw-r--r--gui/MainWindow.h9
-rw-r--r--gui/MainWindow.ui3
-rw-r--r--gui/dialogs/OneSixModEditDialog.cpp40
-rw-r--r--gui/dialogs/SettingsDialog.cpp230
-rw-r--r--gui/dialogs/SettingsDialog.h7
-rw-r--r--gui/dialogs/SettingsDialog.ui133
-rw-r--r--gui/dialogs/VersionSelectDialog.cpp1
-rw-r--r--logic/BaseInstaller.h1
-rw-r--r--logic/BaseInstance.h4
-rw-r--r--logic/InstanceFactory.cpp5
-rw-r--r--logic/LegacyInstance.cpp2
-rw-r--r--logic/LiteLoaderInstaller.cpp41
-rw-r--r--logic/LiteLoaderInstaller.h13
-rw-r--r--logic/MinecraftProcess.cpp16
-rw-r--r--logic/MinecraftProcess.h12
-rw-r--r--logic/OneSixFTBInstance.cpp110
-rw-r--r--logic/OneSixFTBInstance.h9
-rw-r--r--logic/OneSixInstance.cpp37
-rw-r--r--logic/OneSixInstance.h7
-rw-r--r--logic/OneSixUpdate.cpp7
-rw-r--r--logic/OneSixUpdate.h6
-rw-r--r--logic/OneSixVersion.cpp4
-rw-r--r--logic/OneSixVersion.h2
-rw-r--r--logic/OneSixVersionBuilder.cpp210
-rw-r--r--logic/OneSixVersionBuilder.h16
-rw-r--r--logic/lists/InstanceList.cpp2
-rw-r--r--logic/lists/LiteLoaderVersionList.cpp190
-rw-r--r--logic/lists/LiteLoaderVersionList.h101
-rw-r--r--logic/lists/MinecraftVersionList.cpp2
-rw-r--r--logic/net/URLConstants.h1
-rw-r--r--logic/tools/BaseExternalTool.cpp77
-rw-r--r--logic/tools/BaseExternalTool.h57
-rw-r--r--logic/tools/BaseProfiler.cpp35
-rw-r--r--logic/tools/BaseProfiler.h36
-rw-r--r--logic/tools/JProfiler.cpp78
-rw-r--r--logic/tools/JProfiler.h23
-rw-r--r--logic/tools/JVisualVM.cpp74
-rw-r--r--logic/tools/JVisualVM.h23
-rw-r--r--logic/tools/MCEditTool.cpp77
-rw-r--r--logic/tools/MCEditTool.h23
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)
diff --git a/MultiMC.h b/MultiMC.h
index 638a442f..f03ed259 100644
--- a/MultiMC.h
+++ b/MultiMC.h
@@ -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 &current, 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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.ej-technologies.com/products/jprofiler/overview.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://www.ej-technologies.com/products/jprofiler/overview.html&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://visualvm.java.net/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://visualvm.java.net/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.mcedit.net/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://www.mcedit.net/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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;
+};