summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt61
-rw-r--r--MMCError.h25
-rw-r--r--MultiMC.cpp35
-rw-r--r--MultiMC.h17
-rw-r--r--depends/launcher/org/multimc/EntryPoint.java36
-rw-r--r--depends/settings/inisettingsobject.cpp5
-rw-r--r--depends/settings/inisettingsobject.h2
-rw-r--r--depends/settings/settingsobject.cpp9
-rw-r--r--depends/settings/settingsobject.h6
-rw-r--r--gui/MainWindow.cpp121
-rw-r--r--gui/MainWindow.h9
-rw-r--r--gui/dialogs/OneSixModEditDialog.cpp206
-rw-r--r--gui/dialogs/OneSixModEditDialog.h6
-rw-r--r--gui/dialogs/OneSixModEditDialog.ui50
-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--gui/groupview/GroupView.cpp14
-rw-r--r--gui/groupview/GroupView.h3
-rw-r--r--logic/BaseInstaller.cpp2
-rw-r--r--logic/BaseInstaller.h1
-rw-r--r--logic/BaseInstance.cpp28
-rw-r--r--logic/BaseInstance.h21
-rw-r--r--logic/BaseInstance_p.h3
-rw-r--r--logic/ForgeInstaller.cpp6
-rw-r--r--logic/ForgeInstaller.h4
-rw-r--r--logic/InstanceFactory.cpp5
-rw-r--r--logic/LegacyFTBInstance.cpp4
-rw-r--r--logic/LegacyInstance.cpp12
-rw-r--r--logic/LiteLoaderInstaller.cpp45
-rw-r--r--logic/LiteLoaderInstaller.h13
-rw-r--r--logic/MMCJson.cpp60
-rw-r--r--logic/MMCJson.h46
-rw-r--r--logic/MinecraftProcess.cpp213
-rw-r--r--logic/MinecraftProcess.h19
-rw-r--r--logic/NostalgiaInstance.cpp4
-rw-r--r--logic/OneSixFTBInstance.cpp167
-rw-r--r--logic/OneSixFTBInstance.h9
-rw-r--r--logic/OneSixInstance.cpp106
-rw-r--r--logic/OneSixInstance.h25
-rw-r--r--logic/OneSixInstance_p.h6
-rw-r--r--logic/OneSixLibrary.h3
-rw-r--r--logic/OneSixUpdate.cpp55
-rw-r--r--logic/OneSixUpdate.h6
-rw-r--r--logic/OneSixVersion.cpp221
-rw-r--r--logic/OneSixVersionBuilder.cpp1101
-rw-r--r--logic/OneSixVersionBuilder.h23
-rw-r--r--logic/VersionFile.cpp535
-rw-r--r--logic/VersionFile.h127
-rw-r--r--logic/VersionFinal.cpp183
-rw-r--r--logic/VersionFinal.h (renamed from logic/OneSixVersion.h)23
-rw-r--r--logic/lists/InstanceList.cpp2
-rw-r--r--logic/lists/LiteLoaderVersionList.cpp223
-rw-r--r--logic/lists/LiteLoaderVersionList.h112
-rw-r--r--logic/lists/MinecraftVersionList.cpp2
-rw-r--r--logic/net/URLConstants.cpp19
-rw-r--r--logic/net/URLConstants.h30
-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
-rw-r--r--translations/CMakeLists.txt20
-rw-r--r--translations/mmc_de.ts1205
70 files changed, 4324 insertions, 1844 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ee836cd2..d9279bcb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -257,6 +257,7 @@ SET(MULTIMC_SOURCES
MultiMC.h
MultiMC.cpp
MultiMCVersion.h
+MMCError.h
# Logging
logger/QsDebugOutput.cpp
@@ -353,6 +354,10 @@ logic/ModList.cpp
logic/InstanceLauncher.h
logic/InstanceLauncher.cpp
+# JSON parsing helpers
+logic/MMCJson.h
+logic/MMCJson.cpp
+
# network stuffs
logic/net/NetAction.h
logic/net/MD5EtagDownload.h
@@ -372,6 +377,7 @@ logic/net/HttpMetaCache.cpp
logic/net/PasteUpload.h
logic/net/PasteUpload.cpp
logic/net/URLConstants.h
+logic/net/URLConstants.cpp
# Yggdrasil login stuff
logic/auth/AuthSession.h
@@ -413,31 +419,38 @@ logic/LegacyInstance.cpp
logic/LegacyInstance_p.h
logic/LegacyUpdate.h
logic/LegacyUpdate.cpp
+
logic/LegacyForge.h
logic/LegacyForge.cpp
# OneSix instances
logic/OneSixUpdate.h
logic/OneSixUpdate.cpp
-logic/OneSixVersion.h
-logic/OneSixVersion.cpp
+logic/OneSixInstance.h
+logic/OneSixInstance.cpp
+logic/OneSixInstance_p.h
+
+# OneSix version json infrastructure
+logic/OneSixVersionBuilder.h
+logic/OneSixVersionBuilder.cpp
+logic/VersionFile.h
+logic/VersionFile.cpp
+logic/VersionFinal.h
+logic/VersionFinal.cpp
logic/OneSixLibrary.h
logic/OneSixLibrary.cpp
logic/OneSixRule.h
logic/OneSixRule.cpp
logic/OpSys.h
logic/OpSys.cpp
+
+# Mod installers
logic/BaseInstaller.h
logic/BaseInstaller.cpp
logic/ForgeInstaller.h
logic/ForgeInstaller.cpp
logic/LiteLoaderInstaller.h
logic/LiteLoaderInstaller.cpp
-logic/OneSixInstance.h
-logic/OneSixInstance.cpp
-logic/OneSixInstance_p.h
-logic/OneSixVersionBuilder.h
-logic/OneSixVersionBuilder.cpp
# Nostalgia
logic/NostalgiaInstance.h
@@ -462,6 +475,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
# the screenshots feature
logic/screenshots/Screenshot.h
@@ -510,6 +525,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
)
@@ -743,24 +770,8 @@ INCLUDE(CPack)
include_directories(${PROJECT_BINARY_DIR}/include)
-### translation stuff
-
-file (GLOB TRANSLATIONS_FILES translations/*.ts)
-
-option (UPDATE_TRANSLATIONS "Update source translation translations/*.ts files (WARNING: make clean will delete the source .ts files! Danger!)")
-IF(UPDATE_TRANSLATIONS)
- qt5_create_translation(QM_FILES ${FILES_TO_TRANSLATE} ${TRANSLATIONS_FILES})
-ELSE()
- qt5_add_translation(QM_FILES ${TRANSLATIONS_FILES})
-ENDIF()
-
-add_custom_target (translations DEPENDS ${QM_FILES})
-IF(APPLE AND UNIX) ## OSX
- install(FILES ${QM_FILES} DESTINATION MultiMC.app/Contents/MacOS/translations)
-ELSE()
- install(FILES ${QM_FILES} DESTINATION translations)
-ENDIF()
-
+# Translations
+add_subdirectory(translations)
# Tests
add_subdirectory(tests)
diff --git a/MMCError.h b/MMCError.h
new file mode 100644
index 00000000..7b2bd0c4
--- /dev/null
+++ b/MMCError.h
@@ -0,0 +1,25 @@
+#pragma once
+#include <exception>
+#include <QString>
+#include <logger/QsLog.h>
+
+class MMCError : public std::exception
+{
+public:
+ MMCError(QString cause)
+ {
+ exceptionCause = cause;
+ QLOG_ERROR() << "Exception: " + cause;
+ };
+ virtual ~MMCError(){};
+ virtual const char *what() const noexcept
+ {
+ return exceptionCause.toLocal8Bit();
+ };
+ virtual QString cause() const
+ {
+ return exceptionCause;
+ }
+private:
+ QString exceptionCause;
+};
diff --git a/MultiMC.cpp b/MultiMC.cpp
index 6c5c4db6..f6e4e995 100644
--- a/MultiMC.cpp
+++ b/MultiMC.cpp
@@ -16,6 +16,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"
@@ -30,6 +31,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>
@@ -45,8 +50,9 @@ static const int APPDATA_BUFFER_SIZE = 1024;
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");
@@ -215,6 +221,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
/*
@@ -449,6 +470,7 @@ void MultiMC::initHttpMetaCache()
m_metacache->addBase("versions", QDir("versions").absolutePath());
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
+ m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir(root()).absolutePath());
m_metacache->Load();
@@ -549,6 +571,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/depends/settings/inisettingsobject.cpp b/depends/settings/inisettingsobject.cpp
index 5e52a56f..2cee8e3c 100644
--- a/depends/settings/inisettingsobject.cpp
+++ b/depends/settings/inisettingsobject.cpp
@@ -28,6 +28,11 @@ void INISettingsObject::setFilePath(const QString &filePath)
m_filePath = filePath;
}
+bool INISettingsObject::reload()
+{
+ return m_ini.loadFile(m_filePath) && SettingsObject::reload();
+}
+
void INISettingsObject::changeSetting(const Setting &setting, QVariant value)
{
if (contains(setting.id()))
diff --git a/depends/settings/inisettingsobject.h b/depends/settings/inisettingsobject.h
index 8badc0c6..12370896 100644
--- a/depends/settings/inisettingsobject.h
+++ b/depends/settings/inisettingsobject.h
@@ -47,6 +47,8 @@ public:
*/
virtual void setFilePath(const QString &filePath);
+ bool reload() override;
+
protected
slots:
virtual void changeSetting(const Setting &setting, QVariant value);
diff --git a/depends/settings/settingsobject.cpp b/depends/settings/settingsobject.cpp
index 43fc989a..0e3030df 100644
--- a/depends/settings/settingsobject.cpp
+++ b/depends/settings/settingsobject.cpp
@@ -126,6 +126,15 @@ bool SettingsObject::contains(const QString &id)
return m_settings.contains(id);
}
+bool SettingsObject::reload()
+{
+ for (auto setting : m_settings.values())
+ {
+ setting->set(setting->get());
+ }
+ return true;
+}
+
void SettingsObject::connectSignals(const Setting &setting)
{
connect(&setting, SIGNAL(settingChanged(const Setting &, QVariant)),
diff --git a/depends/settings/settingsobject.h b/depends/settings/settingsobject.h
index 27746f2d..b1b26b09 100644
--- a/depends/settings/settingsobject.h
+++ b/depends/settings/settingsobject.h
@@ -113,6 +113,12 @@ public:
*/
bool contains(const QString &id);
+ /*!
+ * \brief Reloads the settings and emit signals for changed settings
+ * \return True if reloading was successful
+ */
+ virtual bool reload();
+
signals:
/*!
* \brief Signal emitted when one of this SettingsObject object's settings changes.
diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp
index 559c2f48..17d4630b 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"
@@ -98,6 +99,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);
@@ -354,9 +357,55 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
QMenu myMenu;
myMenu.addActions(actions);
+ myMenu.setEnabled(m_selectedInstance->canLaunch());
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();
@@ -930,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()
@@ -1078,7 +1128,7 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered()
}
}
-void MainWindow::doLaunch(bool online)
+void MainWindow::doLaunch(bool online, BaseProfilerFactory *profiler)
{
if (!m_selectedInstance)
return;
@@ -1194,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;
}
@@ -1206,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 +1287,56 @@ void MainWindow::launchInstance(BaseInstance *instance, AuthSessionPtr session)
connect(console, &ConsoleWindow::uploadScreenshots, this, &MainWindow::on_actionScreenshots_triggered);
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)
@@ -1367,7 +1466,7 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
(BaseInstance *)current.data(InstanceList::InstancePointerRole)
.value<void *>()))
{
- ui->instanceToolBar->setEnabled(true);
+ ui->instanceToolBar->setEnabled(m_selectedInstance->canLaunch());
renameButton->setText(m_selectedInstance->name());
ui->actionChangeInstLWJGLVersion->setEnabled(
m_selectedInstance->menuActionEnabled("actionChangeInstLWJGLVersion"));
@@ -1378,6 +1477,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 5911d175..3a2843f8 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);
@@ -142,6 +143,8 @@ slots:
void on_actionScreenshots_triggered();
+ void updateToolsMenu();
+
public
slots:
void instanceActivated(QModelIndex);
diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp
index 9e585de5..78585a05 100644
--- a/gui/dialogs/OneSixModEditDialog.cpp
+++ b/gui/dialogs/OneSixModEditDialog.cpp
@@ -34,15 +34,15 @@
#include "gui/dialogs/ProgressDialog.h"
#include "logic/ModList.h"
-#include "logic/OneSixVersion.h"
+#include "logic/VersionFinal.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"
-template<typename A, typename B>
-QMap<A, B> invert(const QMap<B, A> &in)
+template <typename A, typename B> QMap<A, B> invert(const QMap<B, A> &in)
{
QMap<A, B> out;
for (auto it = in.begin(); it != in.end(); ++it)
@@ -95,7 +95,8 @@ OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent)
m_resourcepacks->startWatching();
}
- connect(m_inst, &OneSixInstance::versionReloaded, this, &OneSixModEditDialog::updateVersionControls);
+ connect(m_inst, &OneSixInstance::versionReloaded, this,
+ &OneSixModEditDialog::updateVersionControls);
}
OneSixModEditDialog::~OneSixModEditDialog()
@@ -108,8 +109,7 @@ OneSixModEditDialog::~OneSixModEditDialog()
void OneSixModEditDialog::updateVersionControls()
{
ui->forgeBtn->setEnabled(true);
- ui->liteloaderBtn->setEnabled(LiteLoaderInstaller().canApply(m_inst));
- ui->mainClassEdit->setText(m_version->mainClass);
+ ui->liteloaderBtn->setEnabled(true);
}
void OneSixModEditDialog::disableVersionControls()
@@ -118,125 +118,86 @@ void OneSixModEditDialog::disableVersionControls()
ui->liteloaderBtn->setEnabled(false);
ui->reloadLibrariesBtn->setEnabled(false);
ui->removeLibraryBtn->setEnabled(false);
- ui->mainClassEdit->setText("");
+}
+
+bool OneSixModEditDialog::reloadInstanceVersion()
+{
+ try
+ {
+ m_inst->reloadVersion();
+ return true;
+ }
+ catch (MMCError &e)
+ {
+ QMessageBox::critical(this, tr("Error"), e.cause());
+ return false;
+ }
+ catch (...)
+ {
+ QMessageBox::critical(
+ this, tr("Error"),
+ tr("Failed to load the version description file for reasons unknown."));
+ return false;
+ }
}
void OneSixModEditDialog::on_reloadLibrariesBtn_clicked()
{
- m_inst->reloadVersion(this);
+ reloadInstanceVersion();
}
void OneSixModEditDialog::on_removeLibraryBtn_clicked()
{
if (ui->libraryTreeView->currentIndex().isValid())
{
+ // FIXME: use actual model, not reloading.
if (!m_version->remove(ui->libraryTreeView->currentIndex().row()))
{
QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file"));
}
else
{
- m_inst->reloadVersion(this);
+ reloadInstanceVersion();
}
}
}
void OneSixModEditDialog::on_resetLibraryOrderBtn_clicked()
{
- QDir(m_inst->instanceRoot()).remove("order.json");
- m_inst->reloadVersion(this);
+ // FIXME: IMPLEMENT LOGIC IN MODEL. SEE LEGACY DIALOG FOR EXAMPLE(S).
}
+
void OneSixModEditDialog::on_moveLibraryUpBtn_clicked()
{
-
- QMap<QString, int> order = getExistingOrder();
- if (order.size() < 2 || ui->libraryTreeView->selectionModel()->selectedIndexes().isEmpty())
- {
- return;
- }
- const int ourRow = ui->libraryTreeView->selectionModel()->selectedIndexes().first().row();
- const QString ourId = m_version->versionFileId(ourRow);
- const int ourOrder = order[ourId];
- if (ourId.isNull() || ourId.startsWith("org.multimc."))
- {
- return;
- }
-
- QMap<int, QString> sortedOrder = invert(order);
-
- QList<int> sortedOrders = sortedOrder.keys();
- const int ourIndex = sortedOrders.indexOf(ourOrder);
- if (ourIndex <= 0)
- {
- return;
- }
- const int ourNewOrder = sortedOrders.at(ourIndex - 1);
- order[ourId] = ourNewOrder;
- order[sortedOrder[sortedOrders[ourIndex - 1]]] = ourOrder;
-
- if (!OneSixVersionBuilder::writeOverrideOrders(order, m_inst))
- {
- QMessageBox::critical(this, tr("Error"), tr("Couldn't save the new order"));
- }
- else
- {
- m_inst->reloadVersion(this);
- ui->libraryTreeView->selectionModel()->select(m_version->index(ourRow - 1), QItemSelectionModel::SelectCurrent);
- }
+ // FIXME: IMPLEMENT LOGIC IN MODEL. SEE LEGACY DIALOG FOR EXAMPLE(S).
}
+
void OneSixModEditDialog::on_moveLibraryDownBtn_clicked()
{
- QMap<QString, int> order = getExistingOrder();
- if (order.size() < 2 || ui->libraryTreeView->selectionModel()->selectedIndexes().isEmpty())
- {
- return;
- }
- const int ourRow = ui->libraryTreeView->selectionModel()->selectedIndexes().first().row();
- const QString ourId = m_version->versionFileId(ourRow);
- const int ourOrder = order[ourId];
- if (ourId.isNull() || ourId.startsWith("org.multimc."))
- {
- return;
- }
-
- QMap<int, QString> sortedOrder = invert(order);
-
- QList<int> sortedOrders = sortedOrder.keys();
- const int ourIndex = sortedOrders.indexOf(ourOrder);
- if ((ourIndex + 1) >= sortedOrders.size())
- {
- return;
- }
- const int ourNewOrder = sortedOrders.at(ourIndex + 1);
- order[ourId] = ourNewOrder;
- order[sortedOrder[sortedOrders[ourIndex + 1]]] = ourOrder;
-
- if (!OneSixVersionBuilder::writeOverrideOrders(order, m_inst))
- {
- QMessageBox::critical(this, tr("Error"), tr("Couldn't save the new order"));
- }
- else
- {
- m_inst->reloadVersion(this);
- ui->libraryTreeView->selectionModel()->select(m_version->index(ourRow + 1), QItemSelectionModel::SelectCurrent);
- }
+ // FIXME: IMPLEMENT LOGIC IN MODEL. SEE LEGACY DIALOG FOR EXAMPLE(S).
}
void OneSixModEditDialog::on_forgeBtn_clicked()
{
+ // FIXME: use actual model, not reloading. Move logic to model.
+
+ // FIXME: model::isCustom();
if (QDir(m_inst->instanceRoot()).exists("custom.json"))
{
- if (QMessageBox::question(this, tr("Revert?"), tr("This action will remove your custom.json. Continue?")) != QMessageBox::Yes)
+ if (QMessageBox::question(this, tr("Revert?"),
+ tr("This action will remove your custom.json. Continue?")) !=
+ QMessageBox::Yes)
{
return;
}
+ // FIXME: model::revertToBase();
QDir(m_inst->instanceRoot()).remove("custom.json");
- m_inst->reloadVersion(this);
+ reloadInstanceVersion();
}
VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this);
vselect.setFilter(1, m_inst->currentVersionId());
vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") +
- m_inst->currentVersionId());
+ m_inst->currentVersionId());
if (vselect.exec() && vselect.selectedVersion())
{
ForgeVersionPtr forgeVersion =
@@ -276,38 +237,45 @@ void OneSixModEditDialog::on_forgeBtn_clicked()
}
}
}
- m_inst->reloadVersion(this);
+ reloadInstanceVersion();
}
void OneSixModEditDialog::on_liteloaderBtn_clicked()
{
+ // FIXME: model...
if (QDir(m_inst->instanceRoot()).exists("custom.json"))
{
- if (QMessageBox::question(this, tr("Revert?"), tr("This action will remove your custom.json. Continue?")) != QMessageBox::Yes)
+ if (QMessageBox::question(this, tr("Revert?"),
+ tr("This action will remove your custom.json. Continue?")) !=
+ QMessageBox::Yes)
{
return;
}
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."));
+ reloadInstanceVersion();
}
- 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
+ {
+ reloadInstanceVersion();
+ }
}
}
@@ -343,35 +311,6 @@ bool OneSixModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent)
return QDialog::eventFilter(ui->resPackTreeView, keyEvent);
}
-QMap<QString, int> OneSixModEditDialog::getExistingOrder() const
-{
-
- QMap<QString, int> order;
- // default
- {
- for (OneSixVersion::VersionFile file : m_version->versionFiles)
- {
- if (file.id.startsWith("org.multimc."))
- {
- continue;
- }
- order.insert(file.id, file.order);
- }
- }
- // overriden
- {
- QMap<QString, int> overridenOrder = OneSixVersionBuilder::readOverrideOrders(m_inst);
- for (auto id : order.keys())
- {
- if (overridenOrder.contains(id))
- {
- order[id] = overridenOrder[id];
- }
- }
- }
- return order;
-}
-
bool OneSixModEditDialog::eventFilter(QObject *obj, QEvent *ev)
{
if (ev->type() != QEvent::KeyPress)
@@ -457,7 +396,8 @@ void OneSixModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previou
ui->frame->updateWithMod(m);
}
-void OneSixModEditDialog::versionCurrent(const QModelIndex &current, const QModelIndex &previous)
+void OneSixModEditDialog::versionCurrent(const QModelIndex &current,
+ const QModelIndex &previous)
{
if (!current.isValid())
{
diff --git a/gui/dialogs/OneSixModEditDialog.h b/gui/dialogs/OneSixModEditDialog.h
index f44b336b..e106c6fe 100644
--- a/gui/dialogs/OneSixModEditDialog.h
+++ b/gui/dialogs/OneSixModEditDialog.h
@@ -57,17 +57,17 @@ protected:
bool eventFilter(QObject *obj, QEvent *ev);
bool loaderListFilter(QKeyEvent *ev);
bool resourcePackListFilter(QKeyEvent *ev);
+ /// FIXME: this shouldn't be necessary!
+ bool reloadInstanceVersion();
private:
Ui::OneSixModEditDialog *ui;
- std::shared_ptr<OneSixVersion> m_version;
+ std::shared_ptr<VersionFinal> m_version;
std::shared_ptr<ModList> m_mods;
std::shared_ptr<ModList> m_resourcepacks;
EnabledItemFilter *main_model;
OneSixInstance *m_inst;
- QMap<QString, int> getExistingOrder() const;
-
public
slots:
void loaderCurrent(QModelIndex current, QModelIndex previous);
diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui
index eaf8f7fd..b606dcd2 100644
--- a/gui/dialogs/OneSixModEditDialog.ui
+++ b/gui/dialogs/OneSixModEditDialog.ui
@@ -48,24 +48,6 @@
</attribute>
</widget>
</item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <item>
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Main Class:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="mainClassEdit">
- <property name="enabled">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
</item>
<item>
@@ -109,13 +91,6 @@
</widget>
</item>
<item>
- <widget class="QPushButton" name="resetLibraryOrderBtn">
- <property name="text">
- <string>Reset order</string>
- </property>
- </widget>
- </item>
- <item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -124,6 +99,12 @@
</item>
<item>
<widget class="QPushButton" name="moveLibraryUpBtn">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>This isn't implemented yet.</string>
+ </property>
<property name="text">
<string>Move up</string>
</property>
@@ -131,12 +112,31 @@
</item>
<item>
<widget class="QPushButton" name="moveLibraryDownBtn">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>This isn't implemented yet.</string>
+ </property>
<property name="text">
<string>Move down</string>
</property>
</widget>
</item>
<item>
+ <widget class="QPushButton" name="resetLibraryOrderBtn">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>This isn't implemented yet.</string>
+ </property>
+ <property name="text">
+ <string>Reset order</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
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/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp
index 25042d02..b650efee 100644
--- a/gui/groupview/GroupView.cpp
+++ b/gui/groupview/GroupView.cpp
@@ -45,6 +45,12 @@ GroupView::~GroupView()
m_groups.clear();
}
+void GroupView::setModel(QAbstractItemModel *model)
+{
+ QAbstractItemView::setModel(model);
+ connect(model, &QAbstractItemModel::modelReset, this, &GroupView::modelReset);
+}
+
void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector<int> &roles)
{
@@ -133,6 +139,12 @@ void GroupView::updateGeometries()
viewport()->update();
}
+void GroupView::modelReset()
+{
+ scheduleDelayedItemsLayout();
+ executeDelayedItemsLayout();
+}
+
bool GroupView::isIndexHidden(const QModelIndex &index) const
{
Group *cat = category(index);
@@ -429,6 +441,8 @@ void GroupView::mouseDoubleClickEvent(QMouseEvent *event)
void GroupView::paintEvent(QPaintEvent *event)
{
+ executeDelayedItemsLayout();
+
QPainter painter(this->viewport());
QStyleOptionViewItemV4 option(viewOptions());
diff --git a/gui/groupview/GroupView.h b/gui/groupview/GroupView.h
index e8f9107c..b3ab5357 100644
--- a/gui/groupview/GroupView.h
+++ b/gui/groupview/GroupView.h
@@ -24,6 +24,8 @@ public:
GroupView(QWidget *parent = 0);
~GroupView();
+ void setModel(QAbstractItemModel *model) override;
+
/// return geometry rectangle occupied by the specified model item
QRect geometryRect(const QModelIndex &index) const;
/// return visual rectangle occupied by the specified model item
@@ -69,6 +71,7 @@ slots:
virtual void rowsInserted(const QModelIndex &parent, int start, int end) override;
virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;
virtual void updateGeometries() override;
+ void modelReset();
protected:
virtual bool isIndexHidden(const QModelIndex &index) const override;
diff --git a/logic/BaseInstaller.cpp b/logic/BaseInstaller.cpp
index 92aa0c92..669fd0ac 100644
--- a/logic/BaseInstaller.cpp
+++ b/logic/BaseInstaller.cpp
@@ -17,7 +17,7 @@
#include <QFile>
-#include "OneSixVersion.h"
+#include "VersionFinal.h"
#include "OneSixLibrary.h"
#include "OneSixInstance.h"
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.cpp b/logic/BaseInstance.cpp
index 222004a3..c565ab59 100644
--- a/logic/BaseInstance.cpp
+++ b/logic/BaseInstance.cpp
@@ -37,6 +37,7 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
I_D(BaseInstance);
d->m_settings = settings_obj;
d->m_rootDir = rootDir;
+ d->m_flags = 0;
settings().registerSetting("name", "Unnamed Instance");
settings().registerSetting("iconKey", "default");
@@ -146,6 +147,33 @@ SettingsObject &BaseInstance::settings() const
return *d->m_settings;
}
+BaseInstance::InstanceFlags BaseInstance::flags() const
+{
+ I_D(const BaseInstance);
+ return InstanceFlags(d->m_flags);
+}
+
+void BaseInstance::setFlags(const BaseInstance::InstanceFlags flags)
+{
+ I_D(BaseInstance);
+ if (flags != d->m_flags)
+ {
+ d->m_flags = flags;
+ emit flagsChanged();
+ emit propertiesChanged(this);
+ }
+}
+
+bool BaseInstance::canLaunch() const
+{
+ return !(flags() & VersionBrokenFlag);
+}
+
+bool BaseInstance::reload()
+{
+ return settings().reload();
+}
+
QString BaseInstance::baseJar() const
{
I_D(BaseInstance);
diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h
index cd49f99b..195fd339 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();
@@ -175,6 +179,19 @@ public:
/// FIXME: this really should be elsewhere...
virtual QString instanceConfigFolder() const = 0;
+ enum InstanceFlag
+ {
+ NoFlags = 0x00,
+ VersionBrokenFlag = 0x01
+ };
+ Q_DECLARE_FLAGS(InstanceFlags, InstanceFlag)
+ InstanceFlags flags() const;
+ void setFlags(const BaseInstance::InstanceFlags flags);
+
+ bool canLaunch() const;
+
+ virtual bool reload();
+
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
@@ -189,6 +206,8 @@ signals:
*/
void nuked(BaseInstance *inst);
+ void flagsChanged();
+
protected slots:
void iconUpdated(QString key);
@@ -198,3 +217,5 @@ protected:
// pointer for lazy people
typedef std::shared_ptr<BaseInstance> InstancePtr;
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
diff --git a/logic/BaseInstance_p.h b/logic/BaseInstance_p.h
index 06581a34..73eebec7 100644
--- a/logic/BaseInstance_p.h
+++ b/logic/BaseInstance_p.h
@@ -26,4 +26,5 @@ struct BaseInstancePrivate
QString m_rootDir;
QString m_group;
SettingsObject *m_settings;
-}; \ No newline at end of file
+ int m_flags;
+};
diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp
index 3e18d17f..6f238c21 100644
--- a/logic/ForgeInstaller.cpp
+++ b/logic/ForgeInstaller.cpp
@@ -14,7 +14,7 @@
*/
#include "ForgeInstaller.h"
-#include "OneSixVersion.h"
+#include "VersionFinal.h"
#include "OneSixLibrary.h"
#include "net/HttpMetaCache.h"
#include <quazip.h>
@@ -33,7 +33,7 @@
ForgeInstaller::ForgeInstaller(QString filename, QString universal_url)
{
- std::shared_ptr<OneSixVersion> newVersion;
+ std::shared_ptr<VersionFinal> newVersion;
m_universal_url = universal_url;
QuaZip zip(filename);
@@ -66,7 +66,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url)
// read the forge version info
{
- newVersion = OneSixVersion::fromJson(versionInfoVal.toObject());
+ newVersion = VersionFinal::fromJson(versionInfoVal.toObject());
if (!newVersion)
return;
}
diff --git a/logic/ForgeInstaller.h b/logic/ForgeInstaller.h
index c5052092..df029f38 100644
--- a/logic/ForgeInstaller.h
+++ b/logic/ForgeInstaller.h
@@ -20,7 +20,7 @@
#include <QString>
#include <memory>
-class OneSixVersion;
+class VersionFinal;
class ForgeInstaller : public BaseInstaller
{
@@ -33,7 +33,7 @@ public:
private:
// the version, read from the installer
- std::shared_ptr<OneSixVersion> m_forge_version;
+ std::shared_ptr<VersionFinal> m_forge_version;
QString internalPath;
QString finalPath;
QString realVersionId;
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/LegacyFTBInstance.cpp b/logic/LegacyFTBInstance.cpp
index 6c6bd10b..23cb259d 100644
--- a/logic/LegacyFTBInstance.cpp
+++ b/logic/LegacyFTBInstance.cpp
@@ -7,6 +7,10 @@ LegacyFTBInstance::LegacyFTBInstance(const QString &rootDir, SettingsObject *set
QString LegacyFTBInstance::getStatusbarDescription()
{
+ if (flags() & VersionBrokenFlag)
+ {
+ return "Legacy FTB: " + intendedVersionId() + " (broken)";
+ }
return "Legacy FTB: " + intendedVersionId();
}
diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp
index a9f0d112..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);
@@ -268,13 +268,23 @@ QString LegacyInstance::defaultCustomBaseJar() const
bool LegacyInstance::menuActionEnabled(QString action_name) const
{
+ if (flags() & VersionBrokenFlag)
+ {
+ return false;
+ }
if (action_name == "actionChangeInstMCVersion")
+ {
return false;
+ }
return true;
}
QString LegacyInstance::getStatusbarDescription()
{
+ if (flags() & VersionBrokenFlag)
+ {
+ return "Legacy : " + intendedVersionId() + " (broken)";
+ }
if (shouldUpdate())
return "Legacy : " + currentVersionId() + " -> " + intendedVersionId();
else
diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp
index c363cad6..bb4b07ca 100644
--- a/logic/LiteLoaderInstaller.cpp
+++ b/logic/LiteLoaderInstaller.cpp
@@ -20,27 +20,13 @@
#include "logger/QsLog.h"
-#include "OneSixVersion.h"
+#include "VersionFinal.h"
#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"));
@@ -81,13 +69,14 @@ bool LiteLoaderInstaller::add(OneSixInstance *to)
obj.insert("+libraries", libraries);
obj.insert("name", QString("LiteLoader"));
obj.insert("fileId", id());
- obj.insert("version", to->intendedVersionId());
+ obj.insert("version", m_version->version);
obj.insert("mcVersion", to->intendedVersionId());
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/MMCJson.cpp b/logic/MMCJson.cpp
new file mode 100644
index 00000000..80d36204
--- /dev/null
+++ b/logic/MMCJson.cpp
@@ -0,0 +1,60 @@
+#include "MMCJson.h"
+#include <QString>
+
+bool MMCJson::ensureBoolean(const QJsonValue val, const QString what)
+{
+ if (!val.isBool())
+ throw JSONValidationError(what + " is not boolean");
+ return val.isBool();
+}
+
+QJsonValue MMCJson::ensureExists(QJsonValue val, const QString what)
+{
+ if(val.isNull())
+ throw JSONValidationError(what + " does not exist");
+ return val;
+}
+
+QJsonArray MMCJson::ensureArray(const QJsonValue val, const QString what)
+{
+ if (!val.isArray())
+ throw JSONValidationError(what + " is not an array");
+ return val.toArray();
+}
+
+double MMCJson::ensureDouble(const QJsonValue val, const QString what)
+{
+ if (!val.isDouble())
+ throw JSONValidationError(what + " is not a number");
+ double ret = val.toDouble();
+}
+
+int MMCJson::ensureInteger(const QJsonValue val, const QString what)
+{
+ double ret = ensureDouble(val, what);
+ if (fmod(ret, 1) != 0)
+ throw JSONValidationError(what + " is not an integer");
+ return ret;
+}
+
+QJsonObject MMCJson::ensureObject(const QJsonValue val, const QString what)
+{
+ if (!val.isObject())
+ throw JSONValidationError(what + " is not an object");
+ return val.toObject();
+}
+
+QJsonObject MMCJson::ensureObject(const QJsonDocument val, const QString what)
+{
+ if (!val.isObject())
+ throw JSONValidationError(what + " is not an object");
+ return val.object();
+}
+
+QString MMCJson::ensureString(const QJsonValue val, const QString what)
+{
+ if (!val.isString())
+ throw JSONValidationError(what + " is not a string");
+ return val.toString();
+}
+
diff --git a/logic/MMCJson.h b/logic/MMCJson.h
new file mode 100644
index 00000000..f2cc4b31
--- /dev/null
+++ b/logic/MMCJson.h
@@ -0,0 +1,46 @@
+/**
+ * Some de-bullshitting for Qt JSON failures.
+ *
+ * Simple exception-throwing
+ */
+
+#pragma once
+#include <QJsonValue>
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include "MMCError.h"
+
+class JSONValidationError : public MMCError
+{
+public:
+ JSONValidationError(QString cause) : MMCError(cause) {};
+ virtual ~JSONValidationError() {};
+};
+
+namespace MMCJson
+{
+/// make sure the value exists. throw otherwise.
+QJsonValue ensureExists(QJsonValue val, const QString what = "value");
+
+/// make sure the value is converted into an object. throw otherwise.
+QJsonObject ensureObject(const QJsonValue val, const QString what = "value");
+
+/// make sure the document is converted into an object. throw otherwise.
+QJsonObject ensureObject(const QJsonDocument val, const QString what = "value");
+
+/// make sure the value is converted into an array. throw otherwise.
+QJsonArray ensureArray(const QJsonValue val, QString what = "value");
+
+/// make sure the value is converted into a string. throw otherwise.
+QString ensureString(const QJsonValue val, QString what = "value");
+
+/// make sure the value is converted into a boolean. throw otherwise.
+bool ensureBoolean(const QJsonValue val, QString what = "value");
+
+/// make sure the value is converted into an integer. throw otherwise.
+int ensureInteger(const QJsonValue val, QString what = "value");
+
+/// make sure the value is converted into a double precision floating number. throw otherwise.
+double ensureDouble(const QJsonValue val, QString what = "value");
+}
diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp
index 70a9d55f..effa1095 100644
--- a/logic/MinecraftProcess.cpp
+++ b/logic/MinecraftProcess.cpp
@@ -49,9 +49,11 @@ MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst)
#endif
// export some infos
- env.insert("INST_NAME", inst->name());
- env.insert("INST_ID", inst->id());
- env.insert("INST_DIR", QDir(inst->instanceRoot()).absolutePath());
+ auto variables = getVariables();
+ for (auto it = variables.begin(); it != variables.end(); ++it)
+ {
+ env.insert(it.key(), it.value());
+ }
this->setProcessEnvironment(env);
m_prepostlaunchprocess.setProcessEnvironment(env);
@@ -63,10 +65,10 @@ MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst)
// Log prepost launch command output (can be disabled.)
if (m_instance->settings().get("LogPrePostOutput").toBool())
{
- connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardError,
- this, &MinecraftProcess::on_prepost_stdErr);
- connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput,
- this, &MinecraftProcess::on_prepost_stdOut);
+ connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardError, this,
+ &MinecraftProcess::on_prepost_stdErr);
+ connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput, this,
+ &MinecraftProcess::on_prepost_stdOut);
}
}
@@ -79,10 +81,10 @@ void MinecraftProcess::setWorkdir(QString path)
QString MinecraftProcess::censorPrivateInfo(QString in)
{
- if(!m_session)
+ if (!m_session)
return in;
- if(m_session->session != "-")
+ if (m_session->session != "-")
in.replace(m_session->session, "<SESSION ID>");
in.replace(m_session->access_token, "<ACCESS TOKEN>");
in.replace(m_session->client_token, "<CLIENT TOKEN>");
@@ -113,7 +115,7 @@ MessageLevel::Enum MinecraftProcess::guessLevel(const QString &line, MessageLeve
level = MessageLevel::Fatal;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
- if(line.contains("overwriting existing"))
+ if (line.contains("overwriting existing"))
level = MessageLevel::Fatal;
return level;
}
@@ -139,17 +141,15 @@ MessageLevel::Enum MinecraftProcess::getLevel(const QString &levelName)
return MessageLevel::Message;
}
-void MinecraftProcess::logOutput(const QStringList &lines,
- MessageLevel::Enum defaultLevel,
+void MinecraftProcess::logOutput(const QStringList &lines, MessageLevel::Enum defaultLevel,
bool guessLevel, bool censor)
{
for (int i = 0; i < lines.size(); ++i)
logOutput(lines[i], defaultLevel, guessLevel, censor);
}
-void MinecraftProcess::logOutput(QString line,
- MessageLevel::Enum defaultLevel,
- bool guessLevel, bool censor)
+void MinecraftProcess::logOutput(QString line, MessageLevel::Enum defaultLevel, bool guessLevel,
+ bool censor)
{
MessageLevel::Enum level = defaultLevel;
@@ -251,33 +251,7 @@ void MinecraftProcess::finish(int code, ExitStatus status)
m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code));
// run post-exit
- QString postlaunch_cmd = m_instance->settings().get("PostExitCommand").toString();
- if (!postlaunch_cmd.isEmpty())
- {
- emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd));
- m_prepostlaunchprocess.start(postlaunch_cmd);
- m_prepostlaunchprocess.waitForFinished();
- // Flush console window
- if (!m_err_leftover.isEmpty())
- {
- logOutput(m_err_leftover, MessageLevel::PrePost);
- m_err_leftover.clear();
- }
- if (!m_out_leftover.isEmpty())
- {
- logOutput(m_out_leftover, MessageLevel::PrePost);
- m_out_leftover.clear();
- }
- if (m_prepostlaunchprocess.exitStatus() != NormalExit)
- {
- emit log(tr("Post-Launch command failed with code %1.\n\n").arg(m_prepostlaunchprocess.exitCode()),
- MessageLevel::Error);
- emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(),
- m_prepostlaunchprocess.exitStatus());
- }
- else
- emit log(tr("Post-Launch command ran successfully.\n\n"));
- }
+ postLaunch();
m_instance->cleanupAfterRun();
emit ended(m_instance, code, status);
}
@@ -288,14 +262,12 @@ void MinecraftProcess::killMinecraft()
kill();
}
-void MinecraftProcess::launch()
+bool MinecraftProcess::preLaunch()
{
- emit log("MultiMC version: " + MMC->version().toString() + "\n\n");
- emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n");
-
QString prelaunch_cmd = m_instance->settings().get("PreLaunchCommand").toString();
if (!prelaunch_cmd.isEmpty())
{
+ prelaunch_cmd = substituteVariables(prelaunch_cmd);
// Launch
emit log(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd));
m_prepostlaunchprocess.start(prelaunch_cmd);
@@ -315,46 +287,129 @@ void MinecraftProcess::launch()
// Process return values
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
- emit log(tr("Pre-Launch command failed with code %1.\n\n").arg(m_prepostlaunchprocess.exitCode()),
+ emit log(tr("Pre-Launch command failed with code %1.\n\n")
+ .arg(m_prepostlaunchprocess.exitCode()),
MessageLevel::Fatal);
m_instance->cleanupAfterRun();
emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(),
m_prepostlaunchprocess.exitStatus());
- return;
+ return false;
}
else
emit log(tr("Pre-Launch command ran successfully.\n\n"));
+
+ return m_instance->reload();
+ }
+ return true;
+}
+bool MinecraftProcess::postLaunch()
+{
+ QString postlaunch_cmd = m_instance->settings().get("PostExitCommand").toString();
+ if (!postlaunch_cmd.isEmpty())
+ {
+ postlaunch_cmd = substituteVariables(postlaunch_cmd);
+ emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd));
+ m_prepostlaunchprocess.start(postlaunch_cmd);
+ m_prepostlaunchprocess.waitForFinished();
+ // Flush console window
+ if (!m_err_leftover.isEmpty())
+ {
+ logOutput(m_err_leftover, MessageLevel::PrePost);
+ m_err_leftover.clear();
+ }
+ if (!m_out_leftover.isEmpty())
+ {
+ logOutput(m_out_leftover, MessageLevel::PrePost);
+ m_out_leftover.clear();
+ }
+ if (m_prepostlaunchprocess.exitStatus() != NormalExit)
+ {
+ emit log(tr("Post-Launch command failed with code %1.\n\n")
+ .arg(m_prepostlaunchprocess.exitCode()),
+ MessageLevel::Error);
+ emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(),
+ m_prepostlaunchprocess.exitStatus());
+ }
+ else
+ emit log(tr("Post-Launch command ran successfully.\n\n"));
+
+ return m_instance->reload();
}
+ return true;
+}
- m_instance->setLastLaunch();
- auto &settings = m_instance->settings();
+QMap<QString, QString> MinecraftProcess::getVariables() const
+{
+ QMap<QString, QString> out;
+ out.insert("INST_NAME", m_instance->name());
+ out.insert("INST_ID", m_instance->id());
+ out.insert("INST_DIR", QDir(m_instance->instanceRoot()).absolutePath());
+ out.insert("INST_MC_DIR", QDir(m_instance->minecraftRoot()).absolutePath());
+ out.insert("INST_JAVA", m_instance->settings().get("JavaPath").toString());
+ out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
+ return out;
+}
+QString MinecraftProcess::substituteVariables(const QString &cmd) const
+{
+ QString out = cmd;
+ auto variables = getVariables();
+ for (auto it = variables.begin(); it != variables.end(); ++it)
+ {
+ out.replace("$" + it.key(), it.value());
+ }
+ auto env = QProcessEnvironment::systemEnvironment();
+ for (auto var : env.keys())
+ {
+ out.replace("$" + var, env.value(var));
+ }
+ return out;
+}
- //////////// java arguments ////////////
+QStringList MinecraftProcess::javaArguments() const
+{
QStringList args;
+
+ // custom args go first. we want to override them if we have our own here.
+ args.append(m_instance->extraArguments());
+
+// OSX dock icon and name
+#ifdef OSX
+ args << "-Xdock:icon=icon.png";
+ args << QString("-Xdock:name=\"%1\"").arg(m_instance->windowTitle());
+#endif
+
+// HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767
+#ifdef Q_OS_WIN32
+ args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
+ "minecraft.exe.heapdump");
+#endif
+
+ args << QString("-Xms%1m").arg(m_instance->settings().get("MinMemAlloc").toInt());
+ args << QString("-Xmx%1m").arg(m_instance->settings().get("MaxMemAlloc").toInt());
+ args << QString("-XX:PermSize=%1m").arg(m_instance->settings().get("PermGen").toInt());
+ args << "-Duser.language=en";
+ if (!m_nativeFolder.isEmpty())
+ args << QString("-Djava.library.path=%1").arg(m_nativeFolder);
+ args << "-jar" << PathCombine(MMC->bin(), "jars", "NewLaunch.jar");
+
+ return args;
+}
+
+void MinecraftProcess::arm()
+{
+ emit log("MultiMC version: " + MMC->version().toString() + "\n\n");
+ emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n");
+
+ if (!preLaunch())
{
- // custom args go first. we want to override them if we have our own here.
- args.append(m_instance->extraArguments());
-
- // OSX dock icon and name
- #ifdef OSX
- args << "-Xdock:icon=icon.png";
- args << QString("-Xdock:name=\"%1\"").arg(m_instance->windowTitle());
- #endif
-
- // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767
- #ifdef Q_OS_WIN32
- args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
- "minecraft.exe.heapdump");
- #endif
-
- args << QString("-Xms%1m").arg(settings.get("MinMemAlloc").toInt());
- args << QString("-Xmx%1m").arg(settings.get("MaxMemAlloc").toInt());
- args << QString("-XX:PermSize=%1m").arg(settings.get("PermGen").toInt());
- if(!m_nativeFolder.isEmpty())
- args << QString("-Djava.library.path=%1").arg(m_nativeFolder);
- args << "-jar" << PathCombine(MMC->bin(), "jars", "NewLaunch.jar");
+ return;
}
+ m_instance->setLastLaunch();
+ auto &settings = m_instance->settings();
+
+ QStringList args = javaArguments();
+
QString JavaPath = m_instance->settings().get("JavaPath").toString();
emit log("Java path is:\n" + JavaPath + "\n\n");
QString allArgs = args.join(", ");
@@ -374,3 +429,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..d91dad56 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;
@@ -121,6 +131,13 @@ protected:
QString launchScript;
QString m_nativeFolder;
+ bool preLaunch();
+ bool postLaunch();
+ QMap<QString, QString> getVariables() const;
+ QString substituteVariables(const QString &cmd) const;
+
+ QStringList javaArguments() const;
+
protected
slots:
void finish(int, QProcess::ExitStatus status);
diff --git a/logic/NostalgiaInstance.cpp b/logic/NostalgiaInstance.cpp
index 2e23ee71..96ce4cc7 100644
--- a/logic/NostalgiaInstance.cpp
+++ b/logic/NostalgiaInstance.cpp
@@ -23,6 +23,10 @@ NostalgiaInstance::NostalgiaInstance(const QString &rootDir, SettingsObject *set
QString NostalgiaInstance::getStatusbarDescription()
{
+ if (flags() & VersionBrokenFlag)
+ {
+ return "Nostalgia : " + intendedVersionId() + " (broken)";
+ }
return "Nostalgia : " + intendedVersionId();
}
diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp
index ca88142a..8f70ed08 100644
--- a/logic/OneSixFTBInstance.cpp
+++ b/logic/OneSixFTBInstance.cpp
@@ -1,102 +1,128 @@
#include "OneSixFTBInstance.h"
-#include "OneSixVersion.h"
+#include "VersionFinal.h"
#include "OneSixLibrary.h"
#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
+OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) :
+ OneSixInstance(rootDir, settings, parent)
+{
+}
+
+void OneSixFTBInstance::init()
{
- Q_OBJECT
-public:
- explicit OneSixFTBInstanceForge(const QString &version, OneSixFTBInstance *inst, QObject *parent = 0) :
- Task(parent), instance(inst), version("Forge " + version)
+ try
+ {
+ reloadVersion();
+ }
+ catch(MMCError & e)
{
+ // QLOG_ERROR() << "Caught exception on instance init: " << e.cause();
}
+}
- void executeTask()
+void OneSixFTBInstance::copy(const QDir &newDir)
+{
+ QStringList libraryNames;
+ // create patch file
{
- for (int i = 0; i < MMC->forgelist()->count(); ++i)
+ QLOG_DEBUG() << "Creating patch file for FTB instance...";
+ QFile f(minecraftRoot() + "/pack.json");
+ if (!f.open(QFile::ReadOnly))
{
- if (MMC->forgelist()->at(i)->name() == version)
- {
- forgeVersion = std::dynamic_pointer_cast<ForgeVersion>(MMC->forgelist()->at(i));
- break;
- }
- }
- if (!forgeVersion)
- {
- emitFailed(QString("Couldn't find forge version ") + version );
+ QLOG_ERROR() << "Couldn't open" << f.fileName() << ":" << f.errorString();
return;
}
- entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename);
- if (entry->stale)
+ QJsonObject root = QJsonDocument::fromJson(f.readAll()).object();
+ QJsonArray libs = root.value("libraries").toArray();
+ QJsonArray outLibs;
+ for (auto lib : libs)
{
- setStatus(tr("Downloading Forge..."));
- fjob = new NetJob("Forge download");
- fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry));
- connect(fjob, &NetJob::failed, [this](){emitFailed(m_failReason);});
- connect(fjob, &NetJob::succeeded, this, &OneSixFTBInstanceForge::installForge);
- connect(fjob, &NetJob::progress, [this](qint64 c, qint64 total){ setProgress(100 * c / total); });
- fjob->start();
+ QJsonObject libObj = lib.toObject();
+ libObj.insert("MMC-hint", QString("local"));
+ libObj.insert("insert", QString("prepend"));
+ libraryNames.append(libObj.value("name").toString());
+ outLibs.append(libObj);
}
- else
+ 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))
{
- installForge();
+ QLOG_ERROR() << "Couldn't open" << out.fileName() << ":" << out.errorString();
+ return;
}
+ out.write(QJsonDocument(root).toJson());
}
-
-private
-slots:
- void installForge()
+ // copy libraries
{
- setStatus(tr("Installing Forge..."));
- QString forgePath = entry->getFullPath();
- ForgeInstaller forge(forgePath, forgeVersion->universal_url);
- if (!instance->reloadVersion())
- {
- emitFailed(tr("Couldn't load the version config"));
- return;
- }
- auto version = instance->getFullVersion();
- if (!forge.add(instance))
+ QLOG_DEBUG() << "Copying FTB libraries";
+ for (auto library : libraryNames)
{
- emitFailed(tr("Couldn't install Forge"));
- return;
+ 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();
+ }
}
- emitSucceeded();
}
+}
-private:
- OneSixFTBInstance *instance;
- QString version;
- ForgeVersionPtr forgeVersion;
- MetaEntryPtr entry;
- NetJob *fjob;
-};
+QString OneSixFTBInstance::id() const
+{
+ return "FTB/" + BaseInstance::id();
+}
-OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) :
- OneSixInstance(rootDir, settings, parent)
+QDir OneSixFTBInstance::librariesPath() const
{
- QFile f(QDir(minecraftRoot()).absoluteFilePath("pack.json"));
- if (f.open(QFile::ReadOnly))
- {
- 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();
- }
+ return QDir(MMC->settings()->get("FTBRoot").toString() + "/libraries");
+}
+QDir OneSixFTBInstance::versionsPath() const
+{
+ return QDir(MMC->settings()->get("FTBRoot").toString() + "/versions");
}
-QString OneSixFTBInstance::id() const
+QStringList OneSixFTBInstance::externalPatches() const
{
- return "FTB/" + BaseInstance::id();
+ 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)
+ {
+ return "OneSix FTB: " + intendedVersionId() + " (broken)";
+ }
return "OneSix FTB: " + intendedVersionId();
}
bool OneSixFTBInstance::menuActionEnabled(QString action_name) const
@@ -106,18 +132,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 ae172f21..ed7ad993 100644
--- a/logic/OneSixInstance.cpp
+++ b/logic/OneSixInstance.cpp
@@ -19,7 +19,7 @@
#include "OneSixInstance_p.h"
#include "OneSixUpdate.h"
-#include "OneSixVersion.h"
+#include "VersionFinal.h"
#include "pathutils.h"
#include "logger/QsLog.h"
#include "assets/AssetsUtils.h"
@@ -27,6 +27,7 @@
#include "icons/IconList.h"
#include "MinecraftProcess.h"
#include "gui/dialogs/OneSixModEditDialog.h"
+#include <MMCError.h>
OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent)
: BaseInstance(new OneSixInstancePrivate(), rootDir, settings, parent)
@@ -34,11 +35,23 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings,
I_D(OneSixInstance);
d->m_settings->registerSetting("IntendedVersion", "");
d->m_settings->registerSetting("ShouldUpdate", false);
- d->version.reset(new OneSixVersion(this, this));
- d->vanillaVersion.reset(new OneSixVersion(this, this));
+ d->version.reset(new VersionFinal(this, this));
+ d->vanillaVersion.reset(new VersionFinal(this, this));
+}
+
+void OneSixInstance::init()
+{
+ // FIXME: why is this decided here? what does this even mean?
if (QDir(instanceRoot()).exists("version.json"))
{
- reloadVersion();
+ try
+ {
+ reloadVersion();
+ }
+ catch(MMCError & e)
+ {
+ // QLOG_ERROR() << "Caught exception on instance init: " << e.cause();
+ }
}
else
{
@@ -75,7 +88,7 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with)
return result;
}
-QDir OneSixInstance::reconstructAssets(std::shared_ptr<OneSixVersion> version)
+QDir OneSixInstance::reconstructAssets(std::shared_ptr<VersionFinal> version)
{
QDir assetsDir = QDir("assets/");
QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes"));
@@ -192,12 +205,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 +239,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);
@@ -314,17 +325,26 @@ QString OneSixInstance::currentVersionId() const
return intendedVersionId();
}
-bool OneSixInstance::reloadVersion(QWidget *widgetParent)
+void OneSixInstance::reloadVersion()
{
I_D(OneSixInstance);
- bool ret = d->version->reload(widgetParent);
- if (ret)
+ try
{
- ret = d->vanillaVersion->reload(widgetParent, true);
+ d->version->reload(false, externalPatches());
+ d->vanillaVersion->reload(true, externalPatches());
+ setFlags(flags() & ~VersionBrokenFlag);
+ emit versionReloaded();
+ }
+ catch(MMCError & error)
+ {
+ d->version->clear();
+ d->vanillaVersion->clear();
+ setFlags(flags() | VersionBrokenFlag);
+ //TODO: rethrow to show some error message(s)?
+ emit versionReloaded();
+ throw;
}
- emit versionReloaded();
- return ret;
}
void OneSixInstance::clearVersion()
@@ -335,13 +355,13 @@ void OneSixInstance::clearVersion()
emit versionReloaded();
}
-std::shared_ptr<OneSixVersion> OneSixInstance::getFullVersion() const
+std::shared_ptr<VersionFinal> OneSixInstance::getFullVersion() const
{
I_D(const OneSixInstance);
return d->version;
}
-std::shared_ptr<OneSixVersion> OneSixInstance::getVanillaVersion() const
+std::shared_ptr<VersionFinal> OneSixInstance::getVanillaVersion() const
{
I_D(const OneSixInstance);
return d->vanillaVersion;
@@ -359,8 +379,14 @@ QString OneSixInstance::defaultCustomBaseJar() const
bool OneSixInstance::menuActionEnabled(QString action_name) const
{
+ if (flags() & VersionBrokenFlag)
+ {
+ return false;
+ }
if (action_name == "actionChangeInstLWJGLVersion")
+ {
return false;
+ }
return true;
}
@@ -369,11 +395,51 @@ QString OneSixInstance::getStatusbarDescription()
QString descr = "OneSix : " + intendedVersionId();
if (versionIsCustom())
{
- descr + " (custom)";
+ descr += " (custom)";
+ }
+ if (flags() & VersionBrokenFlag)
+ {
+ descr += " (broken)";
}
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;
+}
+
+bool OneSixInstance::reload()
+{
+ if(BaseInstance::reload())
+ {
+ try
+ {
+ reloadVersion();
+ return true;
+ }
+ catch (...)
+ {
+ return false;
+ }
+ }
+ return false;
+}
+
QString OneSixInstance::loaderModsDir() const
{
return PathCombine(minecraftRoot(), "mods");
diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h
index ae95eab1..7a049922 100644
--- a/logic/OneSixInstance.h
+++ b/logic/OneSixInstance.h
@@ -17,7 +17,7 @@
#include "BaseInstance.h"
-#include "OneSixVersion.h"
+#include "VersionFinal.h"
#include "ModList.h"
class OneSixInstance : public BaseInstance
@@ -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();
@@ -51,14 +53,18 @@ public:
virtual QDialog *createModEditDialog(QWidget *parent) override;
- /// reload the full version json files. return true on success!
- bool reloadVersion(QWidget *widgetParent = 0);
+ /**
+ * reload the full version json files. return true on success!
+ *
+ * throws various exceptions :3
+ */
+ void reloadVersion();
/// clears all version information in preparation for an update
void clearVersion();
/// get the current full version info
- std::shared_ptr<OneSixVersion> getFullVersion() const;
+ std::shared_ptr<VersionFinal> getFullVersion() const;
/// gets the current version info, but only for version.json
- std::shared_ptr<OneSixVersion> getVanillaVersion() const;
+ std::shared_ptr<VersionFinal> getVanillaVersion() const;
/// is the current version original, or custom?
virtual bool versionIsCustom() override;
@@ -68,10 +74,17 @@ 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;
+
+ bool reload() override;
+
signals:
void versionReloaded();
private:
QStringList processMinecraftArgs(AuthSessionPtr account);
- QDir reconstructAssets(std::shared_ptr<OneSixVersion> version);
+ QDir reconstructAssets(std::shared_ptr<VersionFinal> version);
};
diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h
index 0cc46f33..2dffa62c 100644
--- a/logic/OneSixInstance_p.h
+++ b/logic/OneSixInstance_p.h
@@ -16,13 +16,13 @@
#pragma once
#include "BaseInstance_p.h"
-#include "OneSixVersion.h"
+#include "VersionFinal.h"
#include "ModList.h"
struct OneSixInstancePrivate : public BaseInstancePrivate
{
- std::shared_ptr<OneSixVersion> version;
- std::shared_ptr<OneSixVersion> vanillaVersion;
+ std::shared_ptr<VersionFinal> version;
+ std::shared_ptr<VersionFinal> vanillaVersion;
std::shared_ptr<ModList> loader_mod_list;
std::shared_ptr<ModList> resource_pack_list;
};
diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h
index 371ca6f4..3bd21c51 100644
--- a/logic/OneSixLibrary.h
+++ b/logic/OneSixLibrary.h
@@ -26,6 +26,9 @@
class Rule;
+class OneSixLibrary;
+typedef std::shared_ptr<OneSixLibrary> OneSixLibraryPtr;
+
class OneSixLibrary
{
private:
diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp
index d3ac80c2..65f30cda 100644
--- a/logic/OneSixUpdate.cpp
+++ b/logic/OneSixUpdate.cpp
@@ -25,7 +25,7 @@
#include "BaseInstance.h"
#include "lists/MinecraftVersionList.h"
-#include "OneSixVersion.h"
+#include "VersionFinal.h"
#include "OneSixLibrary.h"
#include "OneSixInstance.h"
#include "net/ForgeMirrors.h"
@@ -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)
{
}
@@ -48,7 +48,7 @@ void OneSixUpdate::executeTask()
QDir mcDir(m_inst->minecraftRoot());
if (!mcDir.exists() && !mcDir.mkpath("."))
{
- emitFailed("Failed to create bin folder.");
+ emitFailed(tr("Failed to create folder for minecraft binaries."));
return;
}
@@ -60,7 +60,7 @@ void OneSixUpdate::executeTask()
if (targetVersion == nullptr)
{
// don't do anything if it was invalid
- emitFailed("The specified Minecraft version is invalid. Choose a different one.");
+ emitFailed(tr("The specified Minecraft version is invalid. Choose a different one."));
return;
}
versionFileStart();
@@ -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..."));
@@ -103,20 +108,19 @@ void OneSixUpdate::versionFileFinished()
QSaveFile vfile1(version1);
if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly))
{
- emitFailed("Can't open " + version1 + " for writing.");
+ emitFailed(tr("Can't open %1 for writing.").arg(version1));
return;
}
auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data;
qint64 actual = 0;
if ((actual = vfile1.write(data)) != data.size())
{
- emitFailed("Failed to write into " + version1 + ". Written " + actual + " out of " +
- data.size() + '.');
+ emitFailed(tr("Failed to write into %1. Written %2 out of %3.").arg(version1).arg(actual).arg(data.size()));
return;
}
if (!vfile1.commit())
{
- emitFailed("Can't commit changes to " + version1);
+ emitFailed(tr("Can't commit changes to %1").arg(version1));
return;
}
}
@@ -131,21 +135,20 @@ void OneSixUpdate::versionFileFinished()
{
finfo.remove();
}
- inst->reloadVersion();
-
+ // NOTE: Version is reloaded in jarlibStart
jarlibStart();
}
void OneSixUpdate::versionFileFailed()
{
- emitFailed("Failed to download the version description. Try again.");
+ emitFailed(tr("Failed to download the version description. Try again."));
}
void OneSixUpdate::assetIndexStart()
{
setStatus(tr("Updating assets index..."));
OneSixInstance *inst = (OneSixInstance *)m_inst;
- std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
+ std::shared_ptr<VersionFinal> version = inst->getFullVersion();
QString assetName = version->assets;
QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json";
QString localPath = assetName + ".json";
@@ -169,13 +172,13 @@ void OneSixUpdate::assetIndexFinished()
AssetsIndex index;
OneSixInstance *inst = (OneSixInstance *)m_inst;
- std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
+ std::shared_ptr<VersionFinal> version = inst->getFullVersion();
QString assetName = version->assets;
QString asset_fname = "assets/indexes/" + assetName + ".json";
if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index))
{
- emitFailed("Failed to read the assets index!");
+ emitFailed(tr("Failed to read the assets index!"));
}
QList<Md5EtagDownloadPtr> dls;
@@ -211,7 +214,7 @@ void OneSixUpdate::assetIndexFinished()
void OneSixUpdate::assetIndexFailed()
{
- emitFailed("Failed to download the assets index!");
+ emitFailed(tr("Failed to download the assets index!"));
}
void OneSixUpdate::assetsFinished()
@@ -221,7 +224,7 @@ void OneSixUpdate::assetsFinished()
void OneSixUpdate::assetsFailed()
{
- emitFailed("Failed to download assets!");
+ emitFailed(tr("Failed to download assets!"));
}
void OneSixUpdate::jarlibStart()
@@ -229,16 +232,23 @@ void OneSixUpdate::jarlibStart()
setStatus(tr("Getting the library files from Mojang..."));
QLOG_INFO() << m_inst->name() << ": downloading libraries";
OneSixInstance *inst = (OneSixInstance *)m_inst;
- bool successful = inst->reloadVersion();
- if (!successful)
+ try
+ {
+ inst->reloadVersion();
+ }
+ catch(MMCError & e)
+ {
+ emitFailed(e.cause());
+ return;
+ }
+ catch(...)
{
- emitFailed("Failed to load the version description file. It might be "
- "corrupted, missing or simply too new.");
+ emitFailed(tr("Failed to load the version description file for reasons unknown."));
return;
}
// Build a list of URLs that will need to be downloaded.
- std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
+ std::shared_ptr<VersionFinal> version = inst->getFullVersion();
// minecraft.jar for this version
{
QString version_id = version->id;
@@ -321,6 +331,5 @@ void OneSixUpdate::jarlibFailed()
{
QStringList failed = jarlibDownloadJob->getFailedFiles();
QString failed_all = failed.join("\n");
- emitFailed("Failed to download the following files:\n" + failed_all +
- "\n\nPlease try again.");
+ emitFailed(tr("Failed to download the following files:\n%1\n\nPlease try again.").arg(failed_all));
}
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
deleted file mode 100644
index fb32f3a8..00000000
--- a/logic/OneSixVersion.cpp
+++ /dev/null
@@ -1,221 +0,0 @@
-/* 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 "OneSixVersion.h"
-
-#include <QDebug>
-#include <QFile>
-
-#include "OneSixVersionBuilder.h"
-
-OneSixVersion::OneSixVersion(OneSixInstance *instance, QObject *parent)
- : QAbstractListModel(parent), m_instance(instance)
-{
- clear();
-}
-
-bool OneSixVersion::reload(QWidget *widgetParent, const bool onlyVanilla)
-{
- beginResetModel();
- bool ret = OneSixVersionBuilder::build(this, m_instance, widgetParent, onlyVanilla);
- endResetModel();
- return ret;
-}
-
-void OneSixVersion::clear()
-{
- beginResetModel();
- id.clear();
- time.clear();
- releaseTime.clear();
- type.clear();
- assets.clear();
- processArguments.clear();
- minecraftArguments.clear();
- minimumLauncherVersion = 0xDEADBEAF;
- mainClass.clear();
- libraries.clear();
- tweakers.clear();
- versionFiles.clear();
- endResetModel();
-}
-
-void OneSixVersion::dump() const
-{
- qDebug().nospace() << "OneSixVersion("
- << "\n\tid=" << id
- << "\n\ttime=" << time
- << "\n\treleaseTime=" << releaseTime
- << "\n\ttype=" << type
- << "\n\tassets=" << assets
- << "\n\tprocessArguments=" << processArguments
- << "\n\tminecraftArguments=" << minecraftArguments
- << "\n\tminimumLauncherVersion=" << minimumLauncherVersion
- << "\n\tmainClass=" << mainClass
- << "\n\tlibraries=";
- for (auto lib : libraries)
- {
- qDebug().nospace() << "\n\t\t" << lib.get();
- }
- qDebug().nospace() << "\n)";
-}
-
-bool OneSixVersion::canRemove(const int index) const
-{
- if (index < versionFiles.size())
- {
- return versionFiles.at(index).id != "org.multimc.version.json";
- }
- return false;
-}
-
-QString OneSixVersion::versionFileId(const int index) const
-{
- if (index < 0 || index >= versionFiles.size())
- {
- return QString();
- }
- return versionFiles.at(index).id;
-}
-
-bool OneSixVersion::remove(const int index)
-{
- if (canRemove(index))
- {
- return QFile::remove(versionFiles.at(index).filename);
- }
- return false;
-}
-
-QList<std::shared_ptr<OneSixLibrary> > OneSixVersion::getActiveNormalLibs()
-{
- QList<std::shared_ptr<OneSixLibrary> > output;
- for (auto lib : libraries)
- {
- if (lib->isActive() && !lib->isNative())
- {
- output.append(lib);
- }
- }
- return output;
-}
-
-QList<std::shared_ptr<OneSixLibrary> > OneSixVersion::getActiveNativeLibs()
-{
- QList<std::shared_ptr<OneSixLibrary> > output;
- for (auto lib : libraries)
- {
- if (lib->isActive() && lib->isNative())
- {
- output.append(lib);
- }
- }
- return output;
-}
-
-std::shared_ptr<OneSixVersion> OneSixVersion::fromJson(const QJsonObject &obj)
-{
- std::shared_ptr<OneSixVersion> version(new OneSixVersion(0));
- if (OneSixVersionBuilder::read(version.get(), obj))
- {
- return version;
- }
- return 0;
-}
-
-QVariant OneSixVersion::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- int row = index.row();
- int column = index.column();
-
- if (row < 0 || row >= versionFiles.size())
- return QVariant();
-
- if (role == Qt::DisplayRole)
- {
- switch (column)
- {
- case 0:
- return versionFiles.at(row).name;
- case 1:
- return versionFiles.at(row).version;
- default:
- return QVariant();
- }
- }
- return QVariant();
-}
-
-QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const
-{
- if (orientation == Qt::Horizontal)
- {
- if (role == Qt::DisplayRole)
- {
- switch (section)
- {
- case 0:
- return tr("Name");
- case 1:
- return tr("Version");
- default:
- return QVariant();
- }
- }
- }
- return QVariant();
-}
-
-Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const
-{
- if (!index.isValid())
- return Qt::NoItemFlags;
- return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
-}
-
-int OneSixVersion::rowCount(const QModelIndex &parent) const
-{
- return versionFiles.size();
-}
-
-int OneSixVersion::columnCount(const QModelIndex &parent) const
-{
- return 2;
-}
-
-QDebug operator<<(QDebug &dbg, const OneSixVersion *version)
-{
- version->dump();
- return dbg.maybeSpace();
-}
-QDebug operator<<(QDebug &dbg, const OneSixLibrary *library)
-{
- dbg.nospace() << "OneSixLibrary("
- << "\n\t\t\trawName=" << library->rawName()
- << "\n\t\t\tname=" << library->name()
- << "\n\t\t\tversion=" << library->version()
- << "\n\t\t\ttype=" << library->type()
- << "\n\t\t\tisActive=" << library->isActive()
- << "\n\t\t\tisNative=" << library->isNative()
- << "\n\t\t\tdownloadUrl=" << library->downloadUrl()
- << "\n\t\t\tstoragePath=" << library->storagePath()
- << "\n\t\t\tabsolutePath=" << library->absoluteUrl()
- << "\n\t\t\thint=" << library->hint();
- dbg.nospace() << "\n\t\t)";
- return dbg.maybeSpace();
-}
diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp
index bbd33ddc..8eacbce4 100644
--- a/logic/OneSixVersionBuilder.cpp
+++ b/logic/OneSixVersionBuilder.cpp
@@ -26,1036 +26,234 @@
#include <QDir>
#include <QDebug>
-#include "OneSixVersion.h"
+#include "VersionFinal.h"
#include "OneSixInstance.h"
#include "OneSixRule.h"
+#include "VersionFile.h"
+#include "MMCJson.h"
#include "modutils.h"
#include "logger/QsLog.h"
-struct VersionFile
-{
- int order;
- QString name;
- QString fileId;
- QString version;
- // TODO use the mcVersion to determine if a version file should be removed on update
- QString mcVersion;
- QString filename;
- // TODO requirements
- // QMap<QString, QString> requirements;
- QString id;
- QString mainClass;
- QString overwriteMinecraftArguments;
- QString addMinecraftArguments;
- QString removeMinecraftArguments;
- QString processArguments;
- QString type;
- QString releaseTime;
- QString time;
- QString assets;
- int minimumLauncherVersion = -1;
-
- bool shouldOverwriteTweakers = false;
- QStringList overwriteTweakers;
- QStringList addTweakers;
- QStringList removeTweakers;
-
- struct Library
- {
- QString name;
- QString url;
- QString hint;
- QString absoluteUrl;
- bool applyExcludes = false;
- QStringList excludes;
- bool applyNatives = false;
- QList<QPair<OpSys, QString>> natives;
- bool applyRules = false;
- QList<std::shared_ptr<Rule>> rules;
-
- // user for '+' libraries
- enum InsertType
- {
- Apply,
- Append,
- Prepend,
- Replace
- };
- InsertType insertType = Append;
- QString insertData;
- enum DependType
- {
- Soft,
- Hard
- };
- DependType dependType = Soft;
- };
- bool shouldOverwriteLibs = false;
- QList<Library> overwriteLibs;
- QList<Library> addLibs;
- QList<QString> removeLibs;
-
- static Library fromLibraryJson(const QJsonObject &libObj, const QString &filename,
- bool &isError)
- {
- isError = true;
- Library out;
- if (!libObj.contains("name"))
- {
- QLOG_ERROR() << filename << "contains a library that doesn't have a 'name' field";
- return out;
- }
- out.name = libObj.value("name").toString();
-
- auto readString = [libObj, filename](const QString &key, QString &variable)
- {
- if (libObj.contains(key))
- {
- QJsonValue val = libObj.value(key);
- if (!val.isString())
- {
- QLOG_WARN() << key << "is not a string in" << filename << "(skipping)";
- }
- else
- {
- variable = val.toString();
- }
- }
- };
-
- readString("url", out.url);
- readString("MMC-hint", out.hint);
- readString("MMC-absulute_url", out.absoluteUrl);
- readString("MMC-absoluteUrl", out.absoluteUrl);
- if (libObj.contains("extract"))
- {
- if (!libObj.value("extract").isObject())
- {
- QLOG_ERROR()
- << filename
- << "contains a library with an 'extract' field that's not an object";
- return out;
- }
- QJsonObject extractObj = libObj.value("extract").toObject();
- if (!extractObj.contains("exclude") || !extractObj.value("exclude").isArray())
- {
- QLOG_ERROR() << filename
- << "contains a library with an invalid 'extract' field";
- return out;
- }
- out.applyExcludes = true;
- QJsonArray excludeArray = extractObj.value("exclude").toArray();
- for (auto excludeVal : excludeArray)
- {
- if (!excludeVal.isString())
- {
- QLOG_WARN() << filename << "contains a library that contains an 'extract' "
- "field that contains an invalid 'exclude' entry "
- "(skipping)";
- }
- else
- {
- out.excludes.append(excludeVal.toString());
- }
- }
- }
- if (libObj.contains("natives"))
- {
- if (!libObj.value("natives").isObject())
- {
- QLOG_ERROR()
- << filename
- << "contains a library with a 'natives' field that's not an object";
- return out;
- }
- out.applyNatives = true;
- QJsonObject nativesObj = libObj.value("natives").toObject();
- for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it)
- {
- if (!it.value().isString())
- {
- QLOG_WARN() << filename << "contains an invalid native (skipping)";
- }
- OpSys opSys = OpSys_fromString(it.key());
- if (opSys != Os_Other)
- {
- out.natives.append(qMakePair(opSys, it.value().toString()));
- }
- }
- }
- if (libObj.contains("rules"))
- {
- out.applyRules = true;
- out.rules = rulesFromJsonV4(libObj);
- }
- isError = false;
- return out;
- }
- static VersionFile fromJson(const QJsonDocument &doc, const QString &filename,
- const bool requireOrder, bool &isError)
- {
- VersionFile out;
- isError = true;
- if (doc.isEmpty() || doc.isNull())
- {
- QLOG_ERROR() << filename << "is empty or null";
- return out;
- }
- if (!doc.isObject())
- {
- QLOG_ERROR() << "The root of" << filename << "is not an object";
- return out;
- }
-
- QJsonObject root = doc.object();
-
- if (requireOrder)
- {
- if (root.contains("order"))
- {
- if (root.value("order").isDouble())
- {
- out.order = root.value("order").toDouble();
- }
- else
- {
- QLOG_ERROR() << "'order' field contains an invalid value in" << filename;
- return out;
- }
- }
- else
- {
- QLOG_ERROR() << filename << "doesn't contain an order field";
- }
- }
-
- out.name = root.value("name").toString();
- out.fileId = root.value("fileId").toString();
- out.version = root.value("version").toString();
- out.mcVersion = root.value("mcVersion").toString();
- out.filename = filename;
-
- auto readString = [root, filename](const QString &key, QString &variable)
- {
- if (root.contains(key))
- {
- QJsonValue val = root.value(key);
- if (!val.isString())
- {
- QLOG_WARN() << key << "is not a string in" << filename << "(skipping)";
- }
- else
- {
- variable = val.toString();
- }
- }
- };
-
- readString("id", out.id);
- readString("mainClass", out.mainClass);
- readString("processArguments", out.processArguments);
- readString("minecraftArguments", out.overwriteMinecraftArguments);
- readString("+minecraftArguments", out.addMinecraftArguments);
- readString("-minecraftArguments", out.removeMinecraftArguments);
- readString("type", out.type);
- readString("releaseTime", out.releaseTime);
- readString("time", out.time);
- readString("assets", out.assets);
- if (root.contains("minimumLauncherVersion"))
- {
- QJsonValue val = root.value("minimumLauncherVersion");
- if (!val.isDouble())
- {
- QLOG_WARN() << "minimumLauncherVersion is not an int in" << filename
- << "(skipping)";
- }
- else
- {
- out.minimumLauncherVersion = val.toDouble();
- }
- }
-
- if (root.contains("tweakers"))
- {
- QJsonValue tweakersVal = root.value("tweakers");
- if (!tweakersVal.isArray())
- {
- QLOG_ERROR() << filename
- << "contains a 'tweakers' field, but it's not an array";
- return out;
- }
- out.shouldOverwriteTweakers = true;
- QJsonArray tweakers = root.value("tweakers").toArray();
- for (auto tweakerVal : tweakers)
- {
- if (!tweakerVal.isString())
- {
- QLOG_ERROR() << filename
- << "contains a 'tweakers' field entry that's not a string";
- return out;
- }
- out.overwriteTweakers.append(tweakerVal.toString());
- }
- }
- if (root.contains("+tweakers"))
- {
- QJsonValue tweakersVal = root.value("+tweakers");
- if (!tweakersVal.isArray())
- {
- QLOG_ERROR() << filename
- << "contains a '+tweakers' field, but it's not an array";
- return out;
- }
- QJsonArray tweakers = root.value("+tweakers").toArray();
- for (auto tweakerVal : tweakers)
- {
- if (!tweakerVal.isString())
- {
- QLOG_ERROR() << filename
- << "contains a '+tweakers' field entry that's not a string";
- return out;
- }
- out.addTweakers.append(tweakerVal.toString());
- }
- }
- if (root.contains("-tweakers"))
- {
- QJsonValue tweakersVal = root.value("-tweakers");
- if (!tweakersVal.isArray())
- {
- QLOG_ERROR() << filename
- << "contains a '-tweakers' field, but it's not an array";
- return out;
- }
- out.shouldOverwriteTweakers = true;
- QJsonArray tweakers = root.value("-tweakers").toArray();
- for (auto tweakerVal : tweakers)
- {
- if (!tweakerVal.isString())
- {
- QLOG_ERROR() << filename
- << "contains a '-tweakers' field entry that's not a string";
- return out;
- }
- out.removeTweakers.append(tweakerVal.toString());
- }
- }
-
- if (root.contains("libraries"))
- {
- out.shouldOverwriteLibs = true;
- QJsonValue librariesVal = root.value("libraries");
- if (!librariesVal.isArray())
- {
- QLOG_ERROR() << filename
- << "contains a 'libraries' field, but its not an array";
- return out;
- }
- QJsonArray librariesArray = librariesVal.toArray();
- for (auto libVal : librariesArray)
- {
- if (!libVal.isObject())
- {
- QLOG_ERROR() << filename << "contains a library that's not an object";
- return out;
- }
- QJsonObject libObj = libVal.toObject();
- bool error;
- Library lib = fromLibraryJson(libObj, filename, error);
- if (error)
- {
- QLOG_ERROR() << "Error while reading a library entry in" << filename;
- return out;
- }
- out.overwriteLibs.append(lib);
- }
- }
- if (root.contains("+libraries"))
- {
- QJsonValue librariesVal = root.value("+libraries");
- if (!librariesVal.isArray())
- {
- QLOG_ERROR() << filename
- << "contains a '+libraries' field, but its not an array";
- return out;
- }
- QJsonArray librariesArray = librariesVal.toArray();
- for (auto libVal : librariesArray)
- {
- if (!libVal.isObject())
- {
- QLOG_ERROR() << filename << "contains a library that's not an object";
- return out;
- }
- QJsonObject libObj = libVal.toObject();
- bool error;
- Library lib = fromLibraryJson(libObj, filename, error);
- if (error)
- {
- QLOG_ERROR() << "Error while reading a library entry in" << filename;
- return out;
- }
- if (!libObj.contains("insert"))
- {
- QLOG_ERROR() << "Missing 'insert' field in '+libraries' field in"
- << filename;
- return out;
- }
- QJsonValue insertVal = libObj.value("insert");
- QString insertString;
- {
- if (insertVal.isString())
- {
- insertString = insertVal.toString();
- }
- else if (insertVal.isObject())
- {
- QJsonObject insertObj = insertVal.toObject();
- if (insertObj.isEmpty())
- {
- QLOG_ERROR() << "One library has an empty insert object in"
- << filename;
- return out;
- }
- insertString = insertObj.keys().first();
- lib.insertData = insertObj.value(insertString).toString();
- }
- }
- if (insertString == "apply")
- {
- lib.insertType = Library::Apply;
- }
- else if (insertString == "prepend")
- {
- lib.insertType = Library::Prepend;
- }
- else if (insertString == "append")
- {
- lib.insertType = Library::Prepend;
- }
- else if (insertString == "replace")
- {
- lib.insertType = Library::Replace;
- }
- else
- {
- QLOG_ERROR() << "A '+' library in" << filename
- << "contains an invalid insert type";
- return out;
- }
- if (libObj.contains("MMC-depend") && libObj.value("MMC-depend").isString())
- {
- const QString dependString = libObj.value("MMC-depend").toString();
- if (dependString == "hard")
- {
- lib.dependType = Library::Hard;
- }
- else if (dependString == "soft")
- {
- lib.dependType = Library::Soft;
- }
- else
- {
- QLOG_ERROR() << "A '+' library in" << filename
- << "contains an invalid depend type";
- return out;
- }
- }
- out.addLibs.append(lib);
- }
- }
- if (root.contains("-libraries"))
- {
- QJsonValue librariesVal = root.value("-libraries");
- if (!librariesVal.isArray())
- {
- QLOG_ERROR() << filename
- << "contains a '-libraries' field, but its not an array";
- return out;
- }
- QJsonArray librariesArray = librariesVal.toArray();
- for (auto libVal : librariesArray)
- {
- if (!libVal.isObject())
- {
- QLOG_ERROR() << filename << "contains a library that's not an object";
- return out;
- }
- QJsonObject libObj = libVal.toObject();
- if (!libObj.contains("name"))
- {
- QLOG_ERROR() << filename << "contains a library without a name";
- return out;
- }
- if (!libObj.value("name").isString())
- {
- QLOG_ERROR() << filename
- << "contains a library without a valid 'name' field";
- return out;
- }
- out.removeLibs.append(libObj.value("name").toString());
- }
- }
-
- isError = false;
- return out;
- }
-
- static std::shared_ptr<OneSixLibrary> createLibrary(const Library &lib)
- {
- std::shared_ptr<OneSixLibrary> out(new OneSixLibrary(lib.name));
- if (!lib.url.isEmpty())
- {
- out->setBaseUrl(lib.url);
- }
- out->setHint(lib.hint);
- if (!lib.absoluteUrl.isEmpty())
- {
- out->setAbsoluteUrl(lib.absoluteUrl);
- }
- out->setAbsoluteUrl(lib.absoluteUrl);
- out->extract_excludes = lib.excludes;
- for (auto native : lib.natives)
- {
- out->addNative(native.first, native.second);
- }
- out->setRules(lib.rules);
- out->finalize();
- return out;
- }
- int findLibrary(QList<std::shared_ptr<OneSixLibrary>> haystack, const QString &needle)
- {
- for (int i = 0; i < haystack.size(); ++i)
- {
- if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix)
- .indexIn(haystack.at(i)->rawName()) != -1)
- {
- return i;
- }
- }
- return -1;
- }
- void applyTo(OneSixVersion *version, bool &isError)
- {
- isError = true;
- if (!version->id.isNull() && !mcVersion.isNull())
- {
- if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) == -1)
- {
- QLOG_ERROR() << filename << "is for a different version of Minecraft";
- return;
- }
- }
-
- if (!id.isNull())
- {
- version->id = id;
- }
- if (!mainClass.isNull())
- {
- version->mainClass = mainClass;
- }
- if (!processArguments.isNull())
- {
- version->processArguments = processArguments;
- }
- if (!type.isNull())
- {
- version->type = type;
- }
- if (!releaseTime.isNull())
- {
- version->releaseTime = releaseTime;
- }
- if (!time.isNull())
- {
- version->time = time;
- }
- if (!assets.isNull())
- {
- version->assets = assets;
- }
- if (minimumLauncherVersion >= 0)
- {
- version->minimumLauncherVersion = minimumLauncherVersion;
- }
- if (!overwriteMinecraftArguments.isNull())
- {
- version->minecraftArguments = overwriteMinecraftArguments;
- }
- if (!addMinecraftArguments.isNull())
- {
- version->minecraftArguments += addMinecraftArguments;
- }
- if (!removeMinecraftArguments.isNull())
- {
- version->minecraftArguments.remove(removeMinecraftArguments);
- }
- if (shouldOverwriteTweakers)
- {
- version->tweakers = overwriteTweakers;
- }
- for (auto tweaker : addTweakers)
- {
- version->tweakers += tweaker;
- }
- for (auto tweaker : removeTweakers)
- {
- version->tweakers.removeAll(tweaker);
- }
- if (shouldOverwriteLibs)
- {
- version->libraries.clear();
- for (auto lib : overwriteLibs)
- {
- version->libraries.append(createLibrary(lib));
- }
- }
- for (auto lib : addLibs)
- {
- switch (lib.insertType)
- {
- case Library::Apply:
- {
-
- int index = findLibrary(version->libraries, lib.name);
- if (index >= 0)
- {
- auto library = version->libraries[index];
- if (!lib.url.isNull())
- {
- library->setBaseUrl(lib.url);
- }
- if (!lib.hint.isNull())
- {
- library->setHint(lib.hint);
- }
- if (!lib.absoluteUrl.isNull())
- {
- library->setAbsoluteUrl(lib.absoluteUrl);
- }
- if (lib.applyExcludes)
- {
- library->extract_excludes = lib.excludes;
- }
- if (lib.applyNatives)
- {
- library->clearSuffixes();
- for (auto native : lib.natives)
- {
- library->addNative(native.first, native.second);
- }
- }
- if (lib.applyRules)
- {
- library->setRules(lib.rules);
- }
- library->finalize();
- }
- else
- {
- QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)";
- }
- break;
- }
- case Library::Append:
- case Library::Prepend:
- {
-
- const int startOfVersion = lib.name.lastIndexOf(':') + 1;
- const int index =
- findLibrary(version->libraries,
- QString(lib.name).replace(startOfVersion, INT_MAX, '*'));
- if (index < 0)
- {
- if (lib.insertType == Library::Append)
- {
- version->libraries.append(createLibrary(lib));
- }
- else
- {
- version->libraries.prepend(createLibrary(lib));
- }
- }
- else
- {
- auto otherLib = version->libraries.at(index);
- const Util::Version ourVersion = lib.name.mid(startOfVersion, INT_MAX);
- const Util::Version otherVersion = otherLib->version();
- // if the existing version is a hard dependency we can either use it or
- // fail, but we can't change it
- if (otherLib->dependType == OneSixLibrary::Hard)
- {
- // we need a higher version, or we're hard to and the versions aren't
- // equal
- if (ourVersion > otherVersion ||
- (lib.dependType == Library::Hard && ourVersion != otherVersion))
- {
- QLOG_ERROR() << "Error resolving library dependencies between"
- << otherLib->rawName() << "and" << lib.name << "in"
- << filename;
- return;
- }
- else
- {
- // the library is already existing, so we don't have to do anything
- }
- }
- else if (otherLib->dependType == OneSixLibrary::Soft)
- {
- // if we are higher it means we should update
- if (ourVersion > otherVersion)
- {
- auto library = createLibrary(lib);
- if (Util::Version(otherLib->minVersion) < ourVersion)
- {
- library->minVersion = ourVersion.toString();
- }
- version->libraries.replace(index, library);
- }
- else
- {
- // our version is smaller than the existing version, but we require
- // it: fail
- if (lib.dependType == Library::Hard)
- {
- QLOG_ERROR() << "Error resolving library dependencies between"
- << otherLib->rawName() << "and" << lib.name << "in"
- << filename;
- return;
- }
- }
- }
- }
- break;
- }
- case Library::Replace:
- {
- int index = findLibrary(version->libraries, lib.insertData);
- if (index >= 0)
- {
- version->libraries.replace(index, createLibrary(lib));
- }
- else
- {
- QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)";
- }
- break;
- }
- }
- }
- for (auto lib : removeLibs)
- {
- int index = findLibrary(version->libraries, lib);
- if (index >= 0)
- {
- version->libraries.removeAt(index);
- }
- else
- {
- QLOG_WARN() << "Couldn't find" << lib << "(skipping)";
- }
- }
-
- OneSixVersion::VersionFile versionFile;
- versionFile.name = name;
- versionFile.id = fileId;
- versionFile.version = this->version;
- versionFile.mcVersion = mcVersion;
- versionFile.filename = filename;
- versionFile.order = order;
- version->versionFiles.append(versionFile);
-
- isError = false;
- }
-};
-
OneSixVersionBuilder::OneSixVersionBuilder()
{
}
-bool OneSixVersionBuilder::build(OneSixVersion *version, OneSixInstance *instance,
- QWidget *widgetParent, const bool onlyVanilla)
+void OneSixVersionBuilder::build(VersionFinal *version, OneSixInstance *instance,
+ const bool onlyVanilla, const QStringList &external)
{
OneSixVersionBuilder builder;
builder.m_version = version;
builder.m_instance = instance;
- builder.m_widgetParent = widgetParent;
- return builder.build(onlyVanilla);
+ builder.buildInternal(onlyVanilla, external);
}
-bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj)
+void OneSixVersionBuilder::readJsonAndApplyToVersion(VersionFinal *version,
+ const QJsonObject &obj)
{
OneSixVersionBuilder builder;
builder.m_version = version;
builder.m_instance = 0;
- builder.m_widgetParent = 0;
- return builder.read(obj);
+ builder.readJsonAndApply(obj);
}
-bool OneSixVersionBuilder::build(const bool onlyVanilla)
+void OneSixVersionBuilder::buildInternal(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;
+ auto file =
+ parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json"));
+ file->name = QFileInfo(fileName).fileName();
+ file->fileId = "org.multimc.external." + file->name;
+ file->version = QString();
+ file->mcVersion = QString();
+ file->applyTo(m_version);
+ m_version->versionFiles.append(file);
+ }
+ // 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;
- if (!read(QFileInfo(root.absoluteFilePath("custom.json")), false, &file))
- {
- return false;
- }
- file.name = "custom.json";
- file.filename = "custom.json";
- file.fileId = "org.multimc.custom.json";
- file.version = 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("custom.json")));
- return false;
- }
+ auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("custom.json")), false);
+ file->name = "custom.json";
+ file->filename = "custom.json";
+ file->fileId = "org.multimc.custom.json";
+ file->version = QString();
+ file->applyTo(m_version);
+ m_version->versionFiles.append(file);
+ // 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
- {
- // version.json -> patches/*.json -> user.json
-
- // version.json
+ do
{
+ // version.json
QLOG_INFO() << "Reading version.json";
- VersionFile file;
- if (!read(QFileInfo(root.absoluteFilePath("version.json")), false, &file))
- {
- return false;
- }
- file.name = "version.json";
- file.fileId = "org.multimc.version.json";
- file.version = m_instance->intendedVersionId();
- file.mcVersion = m_instance->intendedVersionId();
- 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("version.json")));
- return false;
- }
- }
-
- if (!onlyVanilla)
- {
+ auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("version.json")), false);
+ file->name = "Minecraft";
+ file->fileId = "org.multimc.version.json";
+ file->version = m_instance->intendedVersionId();
+ file->mcVersion = m_instance->intendedVersionId();
+ file->applyTo(m_version);
+ m_version->versionFiles.append(file);
+ // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more
+ // info.").arg(root.absoluteFilePath("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);
+ // load all, put into map for ordering, apply in the right order
- 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())
+ QMap<int, QPair<QString, VersionFilePtr>> files;
+ for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files))
+ {
+ QLOG_INFO() << "Reading" << info.fileName();
+ auto file = parseJsonFile(info, true);
+ if (files.contains(file->order))
{
- QLOG_DEBUG() << "Applying file with order" << order;
- auto filePair = files[order];
- bool isError = false;
- filePair.second.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(filePair.first));
- return false;
- }
+ throw VersionBuildError(QObject::tr("%1 has the same order as %2").arg(
+ file->fileId, files[file->order].second->fileId));
}
+ files.insert(file->order, qMakePair(info.fileName(), file));
}
-
-#if 0
- // user.json
+ for (auto order : files.keys())
{
- 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;
- }
- }
+ QLOG_DEBUG() << "Applying file with order" << order;
+ auto &filePair = files[order];
+ filePair.second->applyTo(m_version);
+ m_version->versionFiles.append(filePair.second);
}
-#endif
- }
- }
+ } while (0);
// some final touches
+ finalizeVersion();
+}
+
+void OneSixVersionBuilder::finalizeVersion()
+{
+ if (m_version->assets.isEmpty())
{
- if (m_version->assets.isEmpty())
+ m_version->assets = "legacy";
+ }
+ if (m_version->minecraftArguments.isEmpty())
+ {
+ QString toCompare = m_version->processArguments.toLower();
+ if (toCompare == "legacy")
{
- m_version->assets = "legacy";
+ m_version->minecraftArguments = " ${auth_player_name} ${auth_session}";
}
- if (m_version->minecraftArguments.isEmpty())
+ else if (toCompare == "username_session")
{
- QString toCompare = m_version->processArguments.toLower();
- if (toCompare == "legacy")
- {
- m_version->minecraftArguments = " ${auth_player_name} ${auth_session}";
- }
- else if (toCompare == "username_session")
- {
- m_version->minecraftArguments =
- "--username ${auth_player_name} --session ${auth_session}";
- }
- else if (toCompare == "username_session_version")
- {
- m_version->minecraftArguments = "--username ${auth_player_name} "
- "--session ${auth_session} "
- "--version ${profile_name}";
- }
+ m_version->minecraftArguments =
+ "--username ${auth_player_name} --session ${auth_session}";
+ }
+ else if (toCompare == "username_session_version")
+ {
+ m_version->minecraftArguments = "--username ${auth_player_name} "
+ "--session ${auth_session} "
+ "--version ${profile_name}";
}
}
-
- return true;
}
-bool OneSixVersionBuilder::read(const QJsonObject &obj)
+void OneSixVersionBuilder::readJsonAndApply(const QJsonObject &obj)
{
m_version->clear();
- bool isError = false;
- VersionFile file = VersionFile::fromJson(QJsonDocument(obj), QString(), false, isError);
- if (isError)
- {
- QMessageBox::critical(
- m_widgetParent, QObject::tr("Error"),
- QObject::tr("Error while reading. Please check MultiMC-0.log for more info."));
- return false;
- }
- file.applyTo(m_version, isError);
- if (isError)
- {
- QMessageBox::critical(
- m_widgetParent, QObject::tr("Error"),
- QObject::tr("Error while applying. Please check MultiMC-0.log for more info."));
- return false;
- }
+ auto file = VersionFile::fromJson(QJsonDocument(obj), QString(), false);
+ // QObject::tr("Error while reading. Please check MultiMC-0.log for more info."));
- return true;
+ file->applyTo(m_version);
+ m_version->versionFiles.append(file);
+ // QObject::tr("Error while applying. Please check MultiMC-0.log for more info."));
+ // QObject::tr("The version descriptors of this instance are not compatible with the current
+ // version of MultiMC"));
}
-bool OneSixVersionBuilder::read(const QFileInfo &fileInfo, const bool requireOrder,
- VersionFile *out)
+VersionFilePtr OneSixVersionBuilder::parseJsonFile(const QFileInfo &fileInfo,
+ const bool requireOrder, bool isFTB)
{
QFile file(fileInfo.absoluteFilePath());
if (!file.open(QFile::ReadOnly))
{
- QMessageBox::critical(
- m_widgetParent, QObject::tr("Error"),
- QObject::tr("Unable to open %1: %2").arg(file.fileName(), file.errorString()));
- return false;
+ throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.")
+ .arg(fileInfo.fileName(), file.errorString()));
}
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
if (error.error != QJsonParseError::NoError)
{
- QMessageBox::critical(m_widgetParent, QObject::tr("Error"),
- QObject::tr("Unable to parse %1: %2 at %3")
- .arg(file.fileName(), error.errorString())
- .arg(error.offset));
- return false;
- }
- bool isError = false;
- *out = VersionFile::fromJson(doc, file.fileName(), requireOrder, isError);
- if (isError)
- {
- QMessageBox::critical(
- m_widgetParent, QObject::tr("Error"),
- QObject::tr("Error while reading %1. Please check MultiMC-0.log for more info.")
- .arg(file.fileName()));
- ;
+ throw JSONValidationError(QObject::tr("Unable to process the version file %1: %2 at %3.")
+ .arg(fileInfo.fileName(), error.errorString())
+ .arg(error.offset));
}
- return true;
+ return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB);
+ // QObject::tr("Error while reading %1. Please check MultiMC-0.log for more
+ // info.").arg(file.fileName());
}
QMap<QString, int> OneSixVersionBuilder::readOverrideOrders(OneSixInstance *instance)
{
QMap<QString, int> out;
- if (QDir(instance->instanceRoot()).exists("order.json"))
+
+ // make sure the order file exists
+ if (!QDir(instance->instanceRoot()).exists("order.json"))
+ return out;
+
+ // and it can be opened
+ QFile orderFile(instance->instanceRoot() + "/order.json");
+ if (!orderFile.open(QFile::ReadOnly))
{
- QFile orderFile(instance->instanceRoot() + "/order.json");
- if (!orderFile.open(QFile::ReadOnly))
- {
- QLOG_ERROR() << "Couldn't open" << orderFile.fileName() << " for reading:" << orderFile.errorString();
- QLOG_WARN() << "Ignoring overriden order";
- }
- else
+ QLOG_ERROR() << "Couldn't open" << orderFile.fileName()
+ << " for reading:" << orderFile.errorString();
+ QLOG_WARN() << "Ignoring overriden order";
+ return out;
+ }
+
+ // and it's valid JSON
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error);
+ if (error.error != QJsonParseError::NoError)
+ {
+ QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString();
+ QLOG_WARN() << "Ignoring overriden order";
+ return out;
+ }
+
+ // and then read it and process it if all above is true.
+ try
+ {
+ auto obj = MMCJson::ensureObject(doc);
+ for (auto it = obj.begin(); it != obj.end(); ++it)
{
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error);
- if (error.error != QJsonParseError::NoError || !doc.isObject())
+ if (it.key().startsWith("org.multimc."))
{
- QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString();
- QLOG_WARN() << "Ignoring overriden order";
- }
- else
- {
- QJsonObject obj = doc.object();
- for (auto it = obj.begin(); it != obj.end(); ++it)
- {
- if (it.key().startsWith("org.multimc."))
- {
- continue;
- }
- out.insert(it.key(), it.value().toDouble());
- }
+ continue;
}
+ out.insert(it.key(), MMCJson::ensureInteger(it.value()));
}
}
+ catch (JSONValidationError &err)
+ {
+ QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format";
+ QLOG_WARN() << "Ignoring overriden order";
+ return out;
+ }
return out;
}
-bool OneSixVersionBuilder::writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance)
+
+bool OneSixVersionBuilder::writeOverrideOrders(const QMap<QString, int> &order,
+ OneSixInstance *instance)
{
QJsonObject obj;
for (auto it = order.cbegin(); it != order.cend(); ++it)
@@ -1069,7 +267,8 @@ bool OneSixVersionBuilder::writeOverrideOrders(const QMap<QString, int> &order,
QFile orderFile(instance->instanceRoot() + "/order.json");
if (!orderFile.open(QFile::WriteOnly))
{
- QLOG_ERROR() << "Couldn't open" << orderFile.fileName() << "for writing:" << orderFile.errorString();
+ QLOG_ERROR() << "Couldn't open" << orderFile.fileName()
+ << "for writing:" << orderFile.errorString();
return false;
}
orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented));
diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h
index ab0966df..8be3d9d3 100644
--- a/logic/OneSixVersionBuilder.h
+++ b/logic/OneSixVersionBuilder.h
@@ -17,31 +17,32 @@
#include <QString>
#include <QMap>
+#include "VersionFile.h"
-class OneSixVersion;
+class VersionFinal;
class OneSixInstance;
-class QWidget;
class QJsonObject;
class QFileInfo;
-class VersionFile;
class OneSixVersionBuilder
{
OneSixVersionBuilder();
public:
- static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool onlyVanilla);
- static bool read(OneSixVersion *version, const QJsonObject &obj);
+ static void build(VersionFinal *version, OneSixInstance *instance, const bool onlyVanilla,
+ const QStringList &external);
+ static void readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj);
+
static QMap<QString, int> readOverrideOrders(OneSixInstance *instance);
static bool writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance);
private:
- OneSixVersion *m_version;
+ VersionFinal *m_version;
OneSixInstance *m_instance;
- QWidget *m_widgetParent;
-
- bool build(const bool onlyVanilla);
- bool read(const QJsonObject &obj);
- bool read(const QFileInfo &fileInfo, const bool requireOrder, VersionFile *out);
+ void buildInternal(const bool onlyVanilla, const QStringList &external);
+ void readJsonAndApply(const QJsonObject &obj);
+ void finalizeVersion();
+ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder,
+ bool isFTB = false);
};
diff --git a/logic/VersionFile.cpp b/logic/VersionFile.cpp
new file mode 100644
index 00000000..831b086e
--- /dev/null
+++ b/logic/VersionFile.cpp
@@ -0,0 +1,535 @@
+#include <QJsonArray>
+#include <QJsonDocument>
+
+#include <modutils.h>
+
+#include "logger/QsLog.h"
+#include "logic/VersionFile.h"
+#include "logic/OneSixLibrary.h"
+#include "logic/VersionFinal.h"
+#include "MMCJson.h"
+
+using namespace MMCJson;
+
+#define CURRENT_MINIMUM_LAUNCHER_VERSION 14
+
+RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &filename)
+{
+ RawLibraryPtr out(new RawLibrary());
+ if (!libObj.contains("name"))
+ {
+ throw JSONValidationError(filename +
+ "contains a library that doesn't have a 'name' field");
+ }
+ out->name = libObj.value("name").toString();
+
+ auto readString = [libObj, filename](const QString & key, QString & variable)
+ {
+ if (libObj.contains(key))
+ {
+ QJsonValue val = libObj.value(key);
+ if (!val.isString())
+ {
+ QLOG_WARN() << key << "is not a string in" << filename << "(skipping)";
+ }
+ else
+ {
+ variable = val.toString();
+ }
+ }
+ };
+
+ readString("url", out->url);
+ readString("MMC-hint", out->hint);
+ readString("MMC-absulute_url", out->absoluteUrl);
+ readString("MMC-absoluteUrl", out->absoluteUrl);
+ if (libObj.contains("extract"))
+ {
+ out->applyExcludes = true;
+ auto extractObj = ensureObject(libObj.value("extract"));
+ for (auto excludeVal : ensureArray(extractObj.value("exclude")))
+ {
+ out->excludes.append(ensureString(excludeVal));
+ }
+ }
+ if (libObj.contains("natives"))
+ {
+ out->applyNatives = true;
+ QJsonObject nativesObj = ensureObject(libObj.value("natives"));
+ for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it)
+ {
+ if (!it.value().isString())
+ {
+ QLOG_WARN() << filename << "contains an invalid native (skipping)";
+ }
+ OpSys opSys = OpSys_fromString(it.key());
+ if (opSys != Os_Other)
+ {
+ out->natives.append(qMakePair(opSys, it.value().toString()));
+ }
+ }
+ }
+ if (libObj.contains("rules"))
+ {
+ out->applyRules = true;
+ out->rules = rulesFromJsonV4(libObj);
+ }
+ return out;
+}
+
+VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename,
+ const bool requireOrder, const bool isFTB)
+{
+ VersionFilePtr out(new VersionFile());
+ if (doc.isEmpty() || doc.isNull())
+ {
+ throw JSONValidationError(filename + " is empty or null");
+ }
+ if (!doc.isObject())
+ {
+ throw JSONValidationError("The root of " + filename + " is not an object");
+ }
+
+ QJsonObject root = doc.object();
+
+ if (requireOrder)
+ {
+ if (root.contains("order"))
+ {
+ out->order = ensureInteger(root.value("order"));
+ }
+ else
+ {
+ // FIXME: evaluate if we don't want to throw exceptions here instead
+ QLOG_ERROR() << filename << "doesn't contain an order field";
+ }
+ }
+
+ out->name = root.value("name").toString();
+ out->fileId = root.value("fileId").toString();
+ out->version = root.value("version").toString();
+ out->mcVersion = root.value("mcVersion").toString();
+ out->filename = filename;
+
+ auto readString = [root, filename](const QString & key, QString & variable)
+ {
+ if (root.contains(key))
+ {
+ variable = ensureString(root.value(key));
+ }
+ };
+
+ // FIXME: This should be ignored when applying.
+ if (!isFTB)
+ {
+ readString("id", out->id);
+ }
+
+ readString("mainClass", out->mainClass);
+ readString("processArguments", out->processArguments);
+ readString("minecraftArguments", out->overwriteMinecraftArguments);
+ readString("+minecraftArguments", out->addMinecraftArguments);
+ readString("-minecraftArguments", out->removeMinecraftArguments);
+ readString("type", out->type);
+ readString("releaseTime", out->releaseTime);
+ readString("time", out->time);
+ readString("assets", out->assets);
+
+ if (root.contains("minimumLauncherVersion"))
+ {
+ out->minimumLauncherVersion = ensureInteger(root.value("minimumLauncherVersion"));
+ }
+
+ if (root.contains("tweakers"))
+ {
+ out->shouldOverwriteTweakers = true;
+ for (auto tweakerVal : ensureArray(root.value("tweakers")))
+ {
+ out->overwriteTweakers.append(ensureString(tweakerVal));
+ }
+ }
+
+ if (root.contains("+tweakers"))
+ {
+ for (auto tweakerVal : ensureArray(root.value("+tweakers")))
+ {
+ out->addTweakers.append(ensureString(tweakerVal));
+ }
+ }
+
+ if (root.contains("-tweakers"))
+ {
+ for (auto tweakerVal : ensureArray(root.value("-tweakers")))
+ {
+ out->removeTweakers.append(ensureString(tweakerVal));
+ }
+ }
+
+ if (root.contains("libraries"))
+ {
+ // FIXME: This should be done when applying.
+ out->shouldOverwriteLibs = !isFTB;
+ for (auto libVal : ensureArray(root.value("libraries")))
+ {
+ auto libObj = ensureObject(libVal);
+
+ auto lib = RawLibrary::fromJson(libObj, filename);
+ // FIXME: This should be done when applying.
+ if (isFTB)
+ {
+ lib->hint = "local";
+ lib->insertType = RawLibrary::Prepend;
+ out->addLibs.prepend(lib);
+ }
+ else
+ {
+ out->overwriteLibs.append(lib);
+ }
+ }
+ }
+
+ if (root.contains("+libraries"))
+ {
+ for (auto libVal : ensureArray(root.value("+libraries")))
+ {
+ QJsonObject libObj = ensureObject(libVal);
+ QJsonValue insertVal = ensureExists(libObj.value("insert"));
+
+ // parse the library
+ auto lib = RawLibrary::fromJson(libObj, filename);
+
+ // TODO: utility functions for handling this case. templates?
+ QString insertString;
+ {
+ if (insertVal.isString())
+ {
+ insertString = insertVal.toString();
+ }
+ else if (insertVal.isObject())
+ {
+ QJsonObject insertObj = insertVal.toObject();
+ if (insertObj.isEmpty())
+ {
+ throw JSONValidationError("One library has an empty insert object in " +
+ filename);
+ }
+ insertString = insertObj.keys().first();
+ lib->insertData = insertObj.value(insertString).toString();
+ }
+ }
+ if (insertString == "apply")
+ {
+ lib->insertType = RawLibrary::Apply;
+ }
+ else if (insertString == "prepend")
+ {
+ lib->insertType = RawLibrary::Prepend;
+ }
+ else if (insertString == "append")
+ {
+ lib->insertType = RawLibrary::Prepend;
+ }
+ else if (insertString == "replace")
+ {
+ lib->insertType = RawLibrary::Replace;
+ }
+ else
+ {
+ throw JSONValidationError("A '+' library in " + filename +
+ " contains an invalid insert type");
+ }
+ if (libObj.contains("MMC-depend"))
+ {
+ const QString dependString = ensureString(libObj.value("MMC-depend"));
+ if (dependString == "hard")
+ {
+ lib->dependType = RawLibrary::Hard;
+ }
+ else if (dependString == "soft")
+ {
+ lib->dependType = RawLibrary::Soft;
+ }
+ else
+ {
+ throw JSONValidationError("A '+' library in " + filename +
+ " contains an invalid depend type");
+ }
+ }
+ out->addLibs.append(lib);
+ }
+ }
+ if (root.contains("-libraries"))
+ {
+ for (auto libVal : ensureArray(root.value("-libraries")))
+ {
+ auto libObj = ensureObject(libVal);
+ out->removeLibs.append(ensureString(libObj.value("name")));
+ }
+ }
+ return out;
+}
+
+OneSixLibraryPtr VersionFile::createLibrary(RawLibraryPtr lib)
+{
+ std::shared_ptr<OneSixLibrary> out(new OneSixLibrary(lib->name));
+ if (!lib->url.isEmpty())
+ {
+ out->setBaseUrl(lib->url);
+ }
+ out->setHint(lib->hint);
+ if (!lib->absoluteUrl.isEmpty())
+ {
+ out->setAbsoluteUrl(lib->absoluteUrl);
+ }
+ out->setAbsoluteUrl(lib->absoluteUrl);
+ out->extract_excludes = lib->excludes;
+ for (auto native : lib->natives)
+ {
+ out->addNative(native.first, native.second);
+ }
+ out->setRules(lib->rules);
+ out->finalize();
+ return out;
+}
+
+int VersionFile::findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle)
+{
+ for (int i = 0; i < haystack.size(); ++i)
+ {
+ if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix)
+ .indexIn(haystack.at(i)->rawName()) != -1)
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void VersionFile::applyTo(VersionFinal *version)
+{
+ if (minimumLauncherVersion != -1)
+ {
+ if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION)
+ {
+ throw LauncherVersionError(minimumLauncherVersion, CURRENT_MINIMUM_LAUNCHER_VERSION);
+ }
+ }
+
+ if (!version->id.isNull() && !mcVersion.isNull())
+ {
+ if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) ==
+ -1)
+ {
+ throw MinecraftVersionMismatch(fileId, mcVersion, version->id);
+ }
+ }
+
+ if (!id.isNull())
+ {
+ version->id = id;
+ }
+ if (!mainClass.isNull())
+ {
+ version->mainClass = mainClass;
+ }
+ if (!processArguments.isNull())
+ {
+ version->processArguments = processArguments;
+ }
+ if (!type.isNull())
+ {
+ version->type = type;
+ }
+ if (!releaseTime.isNull())
+ {
+ version->releaseTime = releaseTime;
+ }
+ if (!time.isNull())
+ {
+ version->time = time;
+ }
+ if (!assets.isNull())
+ {
+ version->assets = assets;
+ }
+ if (minimumLauncherVersion >= 0)
+ {
+ version->minimumLauncherVersion = minimumLauncherVersion;
+ }
+ if (!overwriteMinecraftArguments.isNull())
+ {
+ version->minecraftArguments = overwriteMinecraftArguments;
+ }
+ if (!addMinecraftArguments.isNull())
+ {
+ version->minecraftArguments += addMinecraftArguments;
+ }
+ if (!removeMinecraftArguments.isNull())
+ {
+ version->minecraftArguments.remove(removeMinecraftArguments);
+ }
+ if (shouldOverwriteTweakers)
+ {
+ version->tweakers = overwriteTweakers;
+ }
+ for (auto tweaker : addTweakers)
+ {
+ version->tweakers += tweaker;
+ }
+ for (auto tweaker : removeTweakers)
+ {
+ version->tweakers.removeAll(tweaker);
+ }
+ if (shouldOverwriteLibs)
+ {
+ version->libraries.clear();
+ for (auto lib : overwriteLibs)
+ {
+ version->libraries.append(createLibrary(lib));
+ }
+ }
+ for (auto lib : addLibs)
+ {
+ switch (lib->insertType)
+ {
+ case RawLibrary::Apply:
+ {
+
+ int index = findLibrary(version->libraries, lib->name);
+ if (index >= 0)
+ {
+ auto library = version->libraries[index];
+ if (!lib->url.isNull())
+ {
+ library->setBaseUrl(lib->url);
+ }
+ if (!lib->hint.isNull())
+ {
+ library->setHint(lib->hint);
+ }
+ if (!lib->absoluteUrl.isNull())
+ {
+ library->setAbsoluteUrl(lib->absoluteUrl);
+ }
+ if (lib->applyExcludes)
+ {
+ library->extract_excludes = lib->excludes;
+ }
+ if (lib->applyNatives)
+ {
+ library->clearSuffixes();
+ for (auto native : lib->natives)
+ {
+ library->addNative(native.first, native.second);
+ }
+ }
+ if (lib->applyRules)
+ {
+ library->setRules(lib->rules);
+ }
+ library->finalize();
+ }
+ else
+ {
+ QLOG_WARN() << "Couldn't find" << lib->name << "(skipping)";
+ }
+ break;
+ }
+ case RawLibrary::Append:
+ case RawLibrary::Prepend:
+ {
+
+ const int startOfVersion = lib->name.lastIndexOf(':') + 1;
+ const int index = findLibrary(
+ version->libraries, QString(lib->name).replace(startOfVersion, INT_MAX, '*'));
+ if (index < 0)
+ {
+ if (lib->insertType == RawLibrary::Append)
+ {
+ version->libraries.append(createLibrary(lib));
+ }
+ else
+ {
+ version->libraries.prepend(createLibrary(lib));
+ }
+ }
+ else
+ {
+ auto otherLib = version->libraries.at(index);
+ const Util::Version ourVersion = lib->name.mid(startOfVersion, INT_MAX);
+ const Util::Version otherVersion = otherLib->version();
+ // if the existing version is a hard dependency we can either use it or
+ // fail, but we can't change it
+ if (otherLib->dependType == OneSixLibrary::Hard)
+ {
+ // we need a higher version, or we're hard to and the versions aren't
+ // equal
+ if (ourVersion > otherVersion ||
+ (lib->dependType == RawLibrary::Hard && ourVersion != otherVersion))
+ {
+ throw VersionBuildError(
+ QObject::tr(
+ "Error resolving library dependencies between %1 and %2 in %3.")
+ .arg(otherLib->rawName(), lib->name, filename));
+ }
+ else
+ {
+ // the library is already existing, so we don't have to do anything
+ }
+ }
+ else if (otherLib->dependType == OneSixLibrary::Soft)
+ {
+ // if we are higher it means we should update
+ if (ourVersion > otherVersion)
+ {
+ auto library = createLibrary(lib);
+ if (Util::Version(otherLib->minVersion) < ourVersion)
+ {
+ library->minVersion = ourVersion.toString();
+ }
+ version->libraries.replace(index, library);
+ }
+ else
+ {
+ // our version is smaller than the existing version, but we require
+ // it: fail
+ if (lib->dependType == RawLibrary::Hard)
+ {
+ throw VersionBuildError(QObject::tr(
+ "Error resolving library dependencies between %1 and %2 in %3.")
+ .arg(otherLib->rawName(), lib->name,
+ filename));
+ }
+ }
+ }
+ }
+ break;
+ }
+ case RawLibrary::Replace:
+ {
+ int index = findLibrary(version->libraries, lib->insertData);
+ if (index >= 0)
+ {
+ version->libraries.replace(index, createLibrary(lib));
+ }
+ else
+ {
+ QLOG_WARN() << "Couldn't find" << lib->insertData << "(skipping)";
+ }
+ break;
+ }
+ }
+ }
+ for (auto lib : removeLibs)
+ {
+ int index = findLibrary(version->libraries, lib);
+ if (index >= 0)
+ {
+ version->libraries.removeAt(index);
+ }
+ else
+ {
+ QLOG_WARN() << "Couldn't find" << lib << "(skipping)";
+ }
+ }
+}
diff --git a/logic/VersionFile.h b/logic/VersionFile.h
new file mode 100644
index 00000000..67d22b23
--- /dev/null
+++ b/logic/VersionFile.h
@@ -0,0 +1,127 @@
+#pragma once
+
+#include <QString>
+#include <QStringList>
+#include <memory>
+#include "logic/OpSys.h"
+#include "logic/OneSixRule.h"
+#include "MMCError.h"
+
+class VersionFinal;
+
+class VersionBuildError : public MMCError
+{
+public:
+ VersionBuildError(QString cause) : MMCError(cause) {};
+ virtual ~VersionBuildError() {};
+};
+
+/**
+ * the base version file was meant for a newer version of the vanilla launcher than we support
+ */
+class LauncherVersionError : public VersionBuildError
+{
+public:
+ LauncherVersionError(int actual, int supported)
+ : VersionBuildError(QObject::tr(
+ "The base version file of this instance was meant for a newer (%1) "
+ "version of the vanilla launcher than this version of MultiMC supports (%2).")
+ .arg(actual)
+ .arg(supported)) {};
+ virtual ~LauncherVersionError() {};
+};
+
+/**
+ * some patch was intended for a different version of minecraft
+ */
+class MinecraftVersionMismatch : public VersionBuildError
+{
+public:
+ MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion)
+ : VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft "
+ "(%2) than that of the instance (%3).")
+ .arg(fileId)
+ .arg(mcVersion)
+ .arg(parentMcVersion)) {};
+ virtual ~MinecraftVersionMismatch() {};
+};
+
+struct RawLibrary;
+typedef std::shared_ptr<RawLibrary> RawLibraryPtr;
+struct RawLibrary
+{
+ QString name;
+ QString url;
+ QString hint;
+ QString absoluteUrl;
+ bool applyExcludes = false;
+ QStringList excludes;
+ bool applyNatives = false;
+ QList<QPair<OpSys, QString>> natives;
+ bool applyRules = false;
+ QList<std::shared_ptr<Rule>> rules;
+
+ // user for '+' libraries
+ enum InsertType
+ {
+ Apply,
+ Append,
+ Prepend,
+ Replace
+ };
+ InsertType insertType = Append;
+ QString insertData;
+ enum DependType
+ {
+ Soft,
+ Hard
+ };
+ DependType dependType = Soft;
+
+ static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename);
+};
+
+struct VersionFile;
+typedef std::shared_ptr<VersionFile> VersionFilePtr;
+struct VersionFile
+{
+public: /* methods */
+ static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename,
+ const bool requireOrder, const bool isFTB = false);
+
+ static OneSixLibraryPtr createLibrary(RawLibraryPtr lib);
+ int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle);
+ void applyTo(VersionFinal *version);
+
+public: /* data */
+ int order = 0;
+ QString name;
+ QString fileId;
+ QString version;
+ // TODO use the mcVersion to determine if a version file should be removed on update
+ QString mcVersion;
+ QString filename;
+ // TODO requirements
+ // QMap<QString, QString> requirements;
+ QString id;
+ QString mainClass;
+ QString overwriteMinecraftArguments;
+ QString addMinecraftArguments;
+ QString removeMinecraftArguments;
+ QString processArguments;
+ QString type;
+ QString releaseTime;
+ QString time;
+ QString assets;
+ int minimumLauncherVersion = -1;
+
+ bool shouldOverwriteTweakers = false;
+ QStringList overwriteTweakers;
+ QStringList addTweakers;
+ QStringList removeTweakers;
+
+ bool shouldOverwriteLibs = false;
+ QList<RawLibraryPtr> overwriteLibs;
+ QList<RawLibraryPtr> addLibs;
+ QList<QString> removeLibs;
+}; \ No newline at end of file
diff --git a/logic/VersionFinal.cpp b/logic/VersionFinal.cpp
new file mode 100644
index 00000000..a057ecdd
--- /dev/null
+++ b/logic/VersionFinal.cpp
@@ -0,0 +1,183 @@
+/* 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 "VersionFinal.h"
+
+#include <QDebug>
+#include <QFile>
+
+#include "OneSixVersionBuilder.h"
+
+VersionFinal::VersionFinal(OneSixInstance *instance, QObject *parent)
+ : QAbstractListModel(parent), m_instance(instance)
+{
+ clear();
+}
+
+bool VersionFinal::reload(const bool onlyVanilla, const QStringList &external)
+{
+ //FIXME: source of epic failure.
+ beginResetModel();
+ OneSixVersionBuilder::build(this, m_instance, onlyVanilla, external);
+ endResetModel();
+}
+
+void VersionFinal::clear()
+{
+ beginResetModel();
+ id.clear();
+ time.clear();
+ releaseTime.clear();
+ type.clear();
+ assets.clear();
+ processArguments.clear();
+ minecraftArguments.clear();
+ minimumLauncherVersion = 0xDEADBEAF;
+ mainClass.clear();
+ libraries.clear();
+ tweakers.clear();
+ versionFiles.clear();
+ endResetModel();
+}
+
+bool VersionFinal::canRemove(const int index) const
+{
+ if (index < versionFiles.size())
+ {
+ return versionFiles.at(index)->fileId != "org.multimc.version.json";
+ }
+ return false;
+}
+
+QString VersionFinal::versionFileId(const int index) const
+{
+ if (index < 0 || index >= versionFiles.size())
+ {
+ return QString();
+ }
+ return versionFiles.at(index)->fileId;
+}
+
+bool VersionFinal::remove(const int index)
+{
+ if (canRemove(index))
+ {
+ return QFile::remove(versionFiles.at(index)->filename);
+ }
+ return false;
+}
+
+QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNormalLibs()
+{
+ QList<std::shared_ptr<OneSixLibrary> > output;
+ for (auto lib : libraries)
+ {
+ if (lib->isActive() && !lib->isNative())
+ {
+ output.append(lib);
+ }
+ }
+ return output;
+}
+
+QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNativeLibs()
+{
+ QList<std::shared_ptr<OneSixLibrary> > output;
+ for (auto lib : libraries)
+ {
+ if (lib->isActive() && lib->isNative())
+ {
+ output.append(lib);
+ }
+ }
+ return output;
+}
+
+std::shared_ptr<VersionFinal> VersionFinal::fromJson(const QJsonObject &obj)
+{
+ std::shared_ptr<VersionFinal> version(new VersionFinal(0));
+ try
+ {
+ OneSixVersionBuilder::readJsonAndApplyToVersion(version.get(), obj);
+ }
+ catch(MMCError & err)
+ {
+ return 0;
+ }
+ return version;
+}
+
+QVariant VersionFinal::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ int row = index.row();
+ int column = index.column();
+
+ if (row < 0 || row >= versionFiles.size())
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ {
+ switch (column)
+ {
+ case 0:
+ return versionFiles.at(row)->name;
+ case 1:
+ return versionFiles.at(row)->version;
+ default:
+ return QVariant();
+ }
+ }
+ return QVariant();
+}
+
+QVariant VersionFinal::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal)
+ {
+ if (role == Qt::DisplayRole)
+ {
+ switch (section)
+ {
+ case 0:
+ return tr("Name");
+ case 1:
+ return tr("Version");
+ default:
+ return QVariant();
+ }
+ }
+ }
+ return QVariant();
+}
+
+Qt::ItemFlags VersionFinal::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+ return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+}
+
+int VersionFinal::rowCount(const QModelIndex &parent) const
+{
+ return versionFiles.size();
+}
+
+int VersionFinal::columnCount(const QModelIndex &parent) const
+{
+ return 2;
+}
diff --git a/logic/OneSixVersion.h b/logic/VersionFinal.h
index ba7695d5..99fd5ff0 100644
--- a/logic/OneSixVersion.h
+++ b/logic/VersionFinal.h
@@ -22,14 +22,15 @@
#include <memory>
#include "OneSixLibrary.h"
+#include "VersionFile.h"
class OneSixInstance;
-class OneSixVersion : public QAbstractListModel
+class VersionFinal : public QAbstractListModel
{
Q_OBJECT
public:
- explicit OneSixVersion(OneSixInstance *instance, QObject *parent = 0);
+ explicit VersionFinal(OneSixInstance *instance, QObject *parent = 0);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
@@ -37,7 +38,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(const bool onlyVanilla = false, const QStringList &external = QStringList());
void clear();
void dump() const;
@@ -54,7 +55,7 @@ public:
QList<std::shared_ptr<OneSixLibrary>> getActiveNormalLibs();
QList<std::shared_ptr<OneSixLibrary>> getActiveNativeLibs();
- static std::shared_ptr<OneSixVersion> fromJson(const QJsonObject &obj);
+ static std::shared_ptr<VersionFinal> fromJson(const QJsonObject &obj);
// data members
public:
@@ -118,20 +119,8 @@ public:
*/
// QList<Rule> rules;
- struct VersionFile
- {
- QString name;
- QString id;
- QString version;
- QString mcVersion;
- QString filename;
- int order;
- };
- QList<VersionFile> versionFiles;
+ QList<VersionFilePtr> versionFiles;
private:
OneSixInstance *m_instance;
};
-
-QDebug operator<<(QDebug &dbg, const OneSixVersion *version);
-QDebug operator<<(QDebug &dbg, const OneSixLibrary *library);
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..ef95eefd
--- /dev/null
+++ b/logic/lists/LiteLoaderVersionList.cpp
@@ -0,0 +1,223 @@
+/* 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;
+}
+
+LLListLoadTask::~LLListLoadTask()
+{
+}
+
+void LLListLoadTask::executeTask()
+{
+ setStatus(tr("Loading LiteLoader version list..."));
+ auto job = new NetJob("Version index");
+ // we do not care if the version is stale or not.
+ auto liteloaderEntry = MMC->metacache()->resolveEntry("liteloader", "versions.json");
+
+ // verify by poking the server.
+ liteloaderEntry->stale = true;
+
+ job->addNetAction(listDownload = CacheDownload::make(QUrl(URLConstants::LITELOADER_URL),
+ liteloaderEntry));
+
+ connect(listDownload.get(), SIGNAL(failed(int)), SLOT(listFailed()));
+
+ listJob.reset(job);
+ connect(listJob.get(), SIGNAL(succeeded()), SLOT(listDownloaded()));
+ connect(listJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
+ listJob->start();
+}
+
+void LLListLoadTask::listFailed()
+{
+ emitFailed("Failed to load LiteLoader version list.");
+ return;
+}
+
+void LLListLoadTask::listDownloaded()
+{
+ QByteArray data;
+ {
+ auto dlJob = listDownload;
+ auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->getTargetFilepath();
+ QFile listFile(filename);
+ if (!listFile.open(QIODevice::ReadOnly))
+ {
+ emitFailed("Failed to open the LiteLoader version list.");
+ return;
+ }
+ data = listFile.readAll();
+ listFile.close();
+ dlJob.reset();
+ }
+
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
+
+ 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;
+ }
+
+ auto meta = root.value("meta").toObject();
+ QString description = meta.value("description").toString(tr("This is a lightweight loader for mods that don't change game mechanics."));
+ QString defaultUrl = meta.value("url").toString("http://dl.liteloader.com");
+ QString authors = meta.value("authors").toString("Mumfrey");
+ auto 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();
+ version->authors = authors;
+ version->description = description;
+ version->defaultUrl = defaultUrl;
+ 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..bfc913e5
--- /dev/null
+++ b/logic/lists/LiteLoaderVersionList.h
@@ -0,0 +1,112 @@
+/* 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 <QString>
+#include <QStringList>
+#include "BaseVersionList.h"
+#include "logic/tasks/Task.h"
+#include "logic/BaseVersion.h"
+#include <logic/net/NetJob.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;
+ }
+
+ // important info
+ QString version;
+ QString file;
+ QString mcVersion;
+ QString md5;
+ int timestamp;
+ bool isLatest;
+ QString tweakClass;
+ QStringList libraries;
+
+ // meta
+ QString defaultUrl;
+ QString description;
+ QString authors;
+};
+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();
+ void listFailed();
+
+protected:
+ NetJobPtr listJob;
+ CacheDownloadPtr listDownload;
+ 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.cpp b/logic/net/URLConstants.cpp
new file mode 100644
index 00000000..14b28085
--- /dev/null
+++ b/logic/net/URLConstants.cpp
@@ -0,0 +1,19 @@
+#include "URLConstants.h"
+namespace URLConstants
+{
+const QString AWS_DOWNLOAD_BASE("s3.amazonaws.com/Minecraft.Download/");
+const QString AWS_DOWNLOAD_VERSIONS(AWS_DOWNLOAD_BASE + "versions/");
+const QString AWS_DOWNLOAD_LIBRARIES(AWS_DOWNLOAD_BASE + "libraries/");
+const QString AWS_DOWNLOAD_INDEXES(AWS_DOWNLOAD_BASE + "indexes/");
+const QString ASSETS_BASE("assets.minecraft.net/");
+const QString RESOURCE_BASE("resources.download.minecraft.net/");
+const QString LIBRARY_BASE("libraries.minecraft.net/");
+const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/");
+const QString AUTH_BASE("authserver.mojang.com/");
+const QString FORGE_LEGACY_URL("http://files.minecraftforge.net/minecraftforge/json");
+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");
+const QString IMGUR_BASE_URL("https://api.imgur.com/3/");
+} \ No newline at end of file
diff --git a/logic/net/URLConstants.h b/logic/net/URLConstants.h
index 55c8d527..c1064115 100644
--- a/logic/net/URLConstants.h
+++ b/logic/net/URLConstants.h
@@ -19,19 +19,19 @@
namespace URLConstants
{
-const QString AWS_DOWNLOAD_BASE("s3.amazonaws.com/Minecraft.Download/");
-const QString AWS_DOWNLOAD_VERSIONS(AWS_DOWNLOAD_BASE + "versions/");
-const QString AWS_DOWNLOAD_LIBRARIES(AWS_DOWNLOAD_BASE + "libraries/");
-const QString AWS_DOWNLOAD_INDEXES(AWS_DOWNLOAD_BASE + "indexes/");
-const QString ASSETS_BASE("assets.minecraft.net/");
-//const QString MCN_BASE("sonicrules.org/mcnweb.py");
-const QString RESOURCE_BASE("resources.download.minecraft.net/");
-const QString LIBRARY_BASE("libraries.minecraft.net/");
-const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/");
-const QString AUTH_BASE("authserver.mojang.com/");
-const QString FORGE_LEGACY_URL("http://files.minecraftforge.net/minecraftforge/json");
-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 IMGUR_BASE_URL("https://api.imgur.com/3/");
+extern const QString AWS_DOWNLOAD_BASE;
+extern const QString AWS_DOWNLOAD_VERSIONS;
+extern const QString AWS_DOWNLOAD_LIBRARIES;
+extern const QString AWS_DOWNLOAD_INDEXES;
+extern const QString ASSETS_BASE;
+extern const QString RESOURCE_BASE;
+extern const QString LIBRARY_BASE;
+extern const QString SKINS_BASE;
+extern const QString AUTH_BASE;
+extern const QString FORGE_LEGACY_URL;
+extern const QString FORGE_GRADLE_URL;
+extern const QString MOJANG_STATUS_URL;
+extern const QString MOJANG_STATUS_NEWS_URL;
+extern const QString LITELOADER_URL;
+extern const QString IMGUR_BASE_URL;
}
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;
+};
diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt
new file mode 100644
index 00000000..7463ae40
--- /dev/null
+++ b/translations/CMakeLists.txt
@@ -0,0 +1,20 @@
+set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM 1)
+
+### translation stuff
+
+file(GLOB TRANSLATION_FILES ${CMAKE_CURRENT_LIST_DIR}/*.ts)
+set(FILES_TO_TRANSLATE_ABSOLUTE)
+foreach(file ${FILES_TO_TRANSLATE})
+ list(APPEND FILES_TO_TRANSLATE_ABSOLUTE "${CMAKE_SOURCE_DIR}/${file}")
+endforeach()
+
+qt5_create_translation(TRANSLATION_MESSAGES ${FILES_TO_TRANSLATE_ABSOLUTE} ${TRANSLATION_FILES})
+qt5_add_translation(TRANSLATION_QM ${TRANSLATION_FILES})
+add_custom_target(translations_update DEPENDS ${TRANSLATION_MESSAGES})
+add_custom_target(translations DEPENDS ${TRANSLATION_QM})
+
+IF(APPLE AND UNIX) ## OSX
+ install(FILES ${TRANSLATION_QM} DESTINATION MultiMC.app/Contents/MacOS/translations)
+ELSE()
+ install(FILES ${TRANSLATION_QM} DESTINATION translations)
+ENDIF()
diff --git a/translations/mmc_de.ts b/translations/mmc_de.ts
index 67ea67f6..6fb26eb5 100644
--- a/translations/mmc_de.ts
+++ b/translations/mmc_de.ts
@@ -8,12 +8,11 @@
<translation type="vanished">Dialog</translation>
</message>
<message>
- <location filename="../gui/dialogs/AboutDialog.ui" line="+89"/>
<source>MultiMC</source>
- <translation>MultiMC</translation>
+ <translation type="vanished">MultiMC</translation>
</message>
<message>
- <location line="+22"/>
+ <location filename="../gui/dialogs/AboutDialog.ui" line="+111"/>
<source>About</source>
<translation>Über</translation>
</message>
@@ -27,7 +26,37 @@
<translation>Über MultiMC</translation>
</message>
<message>
- <location line="+100"/>
+ <location line="+69"/>
+ <source>MultiMC 5</source>
+ <translation type="unfinished">MultiMC 5</translation>
+ </message>
+ <message>
+ <location line="+28"/>
+ <source>Version:</source>
+ <translation type="unfinished">Version:</translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Version Type:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Platform:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Build Number:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Channel:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+13"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;MultiMC is a custom launcher that makes managing Minecraft easier by allowing you to have multiple instances of Minecraft at once.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;MultiMC ist ein alternativer Launcher, der das Management von Minecraft vereinfacht, indem er es dir erlaubt, mehrere Installationen von Minecraft zu verwalten.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
@@ -42,7 +71,197 @@
<translation></translation>
</message>
<message>
- <location line="+28"/>
+ <location line="+41"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; font-weight:600;&quot;&gt;MultiMC&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;Andrew Okin &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:forkk@forkk.net&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;forkk@forkk.net&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;Petr Mrázek &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:peterix@gmail.com&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;peterix@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;Sky &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://www.twitter.com/drayshak&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@drayshak&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;; font-size:10pt; font-weight:600;&quot;&gt;With thanks to&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;Orochimarufan &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:orochimarufan.x3@gmail.com&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;orochimarufan.x3@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;TakSuyu &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:taksuyu@gmail.com&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;taksuyu@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;Kilobyte &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:stiepen22@gmx.de&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;stiepen22@gmx.de&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;Jan (02JanDal) &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:02jandal@gmail.com&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;02jandal@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;Robotbrain &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/skylordelros&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@skylordelros&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;Rootbear75 &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/rootbear75&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@rootbear75&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:10pt;&quot;&gt;&amp;gt; (build server)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+64"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;DejaVu Sans Mono&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:18pt; font-weight:600;&quot;&gt;MultiMC&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Copyright 2012-2014 MultiMC Contributors&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Licensed under the Apache License, Version 2.0 (the &amp;quot;License&amp;quot;);&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;you may not use this file except in compliance with the License.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;You may obtain a copy of the License at&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt; http://www.apache.org/licenses/LICENSE-2.0&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Unless required by applicable law or agreed to in writing, software&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;distributed under the License is distributed on an &amp;quot;AS IS&amp;quot; BASIS,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;See the License for the specific language governing permissions and&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;limitations under the License.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:18pt; font-weight:600;&quot;&gt;QSLog&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Copyright (c) 2010, Razvan Petru&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;All rights reserved.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Redistribution and use in source and binary forms, with or without modification,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;are permitted provided that the following conditions are met:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;* Redistributions of source code must retain the above copyright notice, this&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; list of conditions and the following disclaimer.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;* Redistributions in binary form must reproduce the above copyright notice, this&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; list of conditions and the following disclaimer in the documentation and/or other&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; materials provided with the distribution.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;* The name of the contributors may not be used to endorse or promote products&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; derived from this software without specific prior written permission.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &amp;quot;AS IS&amp;quot; AND&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;OF THE POSSIBILITY OF SUCH DAMAGE.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:18pt; font-weight:600;&quot;&gt;Group View (instance view)&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; /*&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Copyright (C) 2007 Rafael Fernández López &amp;lt;ereslibre@kde.org&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Copyright (C) 2007 John Tapsell &amp;lt;tapsell@kde.org&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * This library is free software; you can redistribute it and/or&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * modify it under the terms of the GNU Library General Public&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * License as published by the Free Software Foundation; either&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * version 2 of the License, or (at your option) any later version.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * This library is distributed in the hope that it will be useful,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Library General Public License for more details.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * You should have received a copy of the GNU Library General Public License&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * along with this library; see the file COPYING.LIB. If not, write to&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Boston, MA 02110-1301, USA.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; */&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:18pt; font-weight:600;&quot;&gt;Pack200&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;The GNU General Public License (GPL)&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Version 2, June 1991&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;+ &amp;quot;CLASSPATH&amp;quot; EXCEPTION TO THE GPL&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Certain source files distributed by Oracle America and/or its affiliates are&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;subject to the following clarification and special exception to the GPL, but&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;only where Oracle has expressly included in the particular source file&apos;s header&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;the words &amp;quot;Oracle designates this particular file as subject to the &amp;quot;Classpath&amp;quot;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;exception as provided by Oracle in the LICENSE file that accompanied this code.&amp;quot;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; Linking this library statically or dynamically with other modules is making&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; a combined work based on this library. Thus, the terms and conditions of&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; the GNU General Public License cover the whole combination.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; As a special exception, the copyright holders of this library give you&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; permission to link this library with independent modules to produce an&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; executable, regardless of the license terms of these independent modules,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; and to copy and distribute the resulting executable under terms of your&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; choice, provided that you also meet, for each linked independent module,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; the terms and conditions of the license of that module. An independent&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; module is a module which is not derived from or based on this library. If&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; you modify this library, you may extend this exception to your version of&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; the library, but you are not obligated to do so. If you do not wish to do&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; so, delete this exception statement from your version.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:18pt; font-weight:600;&quot;&gt;Quazip&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Copyright (C) 2005-2011 Sergey A. Tachenov&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This program is free software; you can redistribute it and/or modify it&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;under the terms of the GNU Lesser General Public License as published by&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;the Free Software Foundation; either version 2 of the License, or (at&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;your option) any later version.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This program is distributed in the hope that it will be useful, but&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;General Public License for more details.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;You should have received a copy of the GNU Lesser General Public License&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;along with this program; if not, write to the Free Software Foundation,&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;See COPYING file for the full LGPL text.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Original ZIP package is copyrighted by Gilles Vollant, see&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;quazip/(un)zip.h files for details, basically it&apos;s zlib license.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:18pt; font-weight:600;&quot;&gt;xz-minidec&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;/*&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * XZ decompressor&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Authors: Lasse Collin &amp;lt;lasse.collin@tukaani.org&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Igor Pavlov &amp;lt;http://7-zip.org/&amp;gt;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * This file has been put into the public domain.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * You can do whatever you want with this file.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; */&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:18pt; font-weight:600;&quot;&gt;Java IconLoader class&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Copyright (c) 2011, Chris Molini&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;All rights reserved.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Redistribution and use in source and binary forms, with or without&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;modification, are permitted provided that the following conditions are met:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Redistributions of source code must retain the above copyright&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; notice, this list of conditions and the following disclaimer.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Redistributions in binary form must reproduce the above copyright&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; notice, this list of conditions and the following disclaimer in the&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; documentation and/or other materials provided with the distribution.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Neither the name of the &amp;lt;organization&amp;gt; nor the&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; names of its contributors may be used to endorse or promote products&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; derived from this software without specific prior written permission.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &amp;quot;AS IS&amp;quot; AND&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;DISCLAIMED. IN NO EVENT SHALL &amp;lt;COPYRIGHT HOLDER&amp;gt; BE LIABLE FOR ANY&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+175"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;We keep MultiMC open source because we think it&apos;s important to be able to see the source code for a project like this, and we do so using the Apache license.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Part of the reason for using the Apache license is we don&apos;t want people using the &amp;quot;MultiMC&amp;quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &amp;quot;MultiMC&amp;quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The Apache license covers reasonable use for the name - a mention of the project&apos;s origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork &lt;span style=&quot; font-weight:600;&quot;&gt;without&lt;/span&gt; implying that you have our blessing.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
@@ -59,7 +278,7 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Jan (02JanDal) &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:02jandal@gmail.com&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;02jandal@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Robotbrain &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/skylordelros&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@skylordelros&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Rootbear75 &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/rootbear75&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@rootbear75&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt; (build server)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+ <translation type="vanished">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:7.8pt; font-weight:400; font-style:normal;&quot;&gt;
@@ -77,13 +296,12 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Rootbear75 &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/rootbear75&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@rootbear75&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt; (bau server)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
- <location line="+25"/>
+ <location line="-214"/>
<source>No Language file loaded.</source>
<extracomment>Hey, Translator, feel free to put credit to you here</extracomment>
<translation>Deutsche Sprachdatei von Kilobyte (siehe oben). Aktualisiert von xnrand (nsfw auf IRC), Jan und ACGaming.</translation>
</message>
<message>
- <location line="+39"/>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
@@ -209,7 +427,7 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * This file has been put into the public domain.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * You can do whatever you want with this file.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; */&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+ <translation type="vanished">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;DejaVu Sans Mono&apos;; font-size:7.8pt; font-weight:400; font-style:normal;&quot;&gt;
@@ -336,12 +554,11 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; */&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
- <location line="+140"/>
+ <location line="+208"/>
<source>Forking/Redistribution</source>
<translation>Abspaltung/Weiterverbreitung</translation>
</message>
<message>
- <location line="+6"/>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
@@ -351,7 +568,7 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:11pt;&quot;&gt;Part of the reason for using the Apache license is we don&apos;t want people using the &amp;quot;MultiMC&amp;quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &amp;quot;MultiMC&amp;quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;Bitstream Vera Sans&apos;; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:11pt;&quot;&gt;The Apache license covers reasonable use for the name - a mention of the project&apos;s origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork &lt;/span&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:11pt; font-weight:600;&quot;&gt;without&lt;/span&gt;&lt;span style=&quot; font-family:&apos;Bitstream Vera Sans&apos;; font-size:11pt;&quot;&gt; implying that you have our blessing.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+ <translation type="vanished">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:7.8pt; font-weight:400; font-style:normal;&quot;&gt;
@@ -366,7 +583,7 @@ p, li { white-space: pre-wrap; }
<translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://github.com/Forkk/MultiMC5&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://github.com/Forkk/MultiMC5&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
- <location line="-219"/>
+ <location line="-242"/>
<source>Credits</source>
<translation>Dank an</translation>
</message>
@@ -477,7 +694,7 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;POSSIBILITY OF SUCH DAMAGE.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
- <location line="+193"/>
+ <location line="+222"/>
<source>About Qt</source>
<translation>Über Qt</translation>
</message>
@@ -486,6 +703,31 @@ p, li { white-space: pre-wrap; }
<source>Close</source>
<translation>Schließen</translation>
</message>
+ <message>
+ <location filename="../gui/dialogs/AboutDialog.cpp" line="+32"/>
+ <source>Version</source>
+ <translation type="unfinished">Version</translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Version Type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Platform</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Build Number</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Channel</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>AccountListDialog</name>
@@ -585,6 +827,27 @@ p, li { white-space: pre-wrap; }
</message>
</context>
<context>
+ <name>BaseExternalTool</name>
+ <message>
+ <location filename="../logic/tools/BaseExternalTool.cpp" line="+48"/>
+ <source>MCEdit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+0"/>
+ <source>Choose which world to open:</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>BaseProfiler</name>
+ <message>
+ <location filename="../logic/tools/BaseProfiler.cpp" line="+29"/>
+ <source>Profiler aborted</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>ConsoleWindow</name>
<message>
<location filename="../gui/ConsoleWindow.ui" line="+14"/>
@@ -597,6 +860,11 @@ p, li { white-space: pre-wrap; }
<translation>Log hochladen</translation>
</message>
<message>
+ <location line="+7"/>
+ <source>Manage Screenshots</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<location line="+20"/>
<source>&amp;Kill Minecraft</source>
<translation>&amp;Minecraft töten</translation>
@@ -611,11 +879,22 @@ p, li { white-space: pre-wrap; }
<translation type="vanished">Minecraft töten</translation>
</message>
<message>
+ <location filename="../gui/ConsoleWindow.cpp" line="+58"/>
+ <source>Console window for </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+120"/>
<source>Close</source>
- <translation type="vanished">Schließen</translation>
+ <translation>Schließen</translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Hide</source>
+ <translation type="unfinished"></translation>
</message>
<message>
- <location filename="../gui/ConsoleWindow.cpp" line="+161"/>
+ <location line="+49"/>
<source>Kill Minecraft?</source>
<translation>Minecraft töten?</translation>
</message>
@@ -641,7 +920,7 @@ p, li { white-space: pre-wrap; }
<context>
<name>DownloadUpdateTask</name>
<message>
- <location filename="../logic/updater/DownloadUpdateTask.cpp" line="+80"/>
+ <location filename="../logic/updater/DownloadUpdateTask.cpp" line="+81"/>
<source>Finding information about the current version...</source>
<translation>Finde Informationen zur benutzten Version...</translation>
</message>
@@ -681,12 +960,12 @@ p, li { white-space: pre-wrap; }
<translation>Bearbeite Dateilisten - Rechne aus, wie das Update installiert werden soll...</translation>
</message>
<message>
- <location line="+206"/>
+ <location line="+213"/>
<source>Failed to write update script file.</source>
<translation>Fehler beim Schreiben des Updatescripts.</translation>
</message>
<message>
- <location line="+43"/>
+ <location line="+31"/>
<source>Failed to download update files.</source>
<translation>Fehler beim Herunterladen der Updatedateien.</translation>
</message>
@@ -813,7 +1092,29 @@ p, li { white-space: pre-wrap; }
<translation>Konsole automatisch schließen, nachdem das Spiel beendet wurde?</translation>
</message>
<message>
- <location line="+159"/>
+ <location line="+45"/>
+ <source>The maximum amount of memory Minecraft is allowed to use.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <location line="+36"/>
+ <location line="+22"/>
+ <source> MB</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-25"/>
+ <source>The amount of memory Minecraft is started with.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+22"/>
+ <source>The amount of memory available to store loaded Java classes.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+74"/>
<source>Browse...</source>
<translation>Durchsuchen...</translation>
</message>
@@ -831,7 +1132,7 @@ p, li { white-space: pre-wrap; }
<translation type="vanished">Automatisch einloggen, wenn das Instanzsymbol doppelt gecklickt wurde?</translation>
</message>
<message>
- <location line="-142"/>
+ <location line="-160"/>
<source>Java</source>
<translation>Java</translation>
</message>
@@ -841,7 +1142,7 @@ p, li { white-space: pre-wrap; }
<translation>Arbeitsspeicher</translation>
</message>
<message>
- <location line="+28"/>
+ <location line="+34"/>
<source>Minimum memory allocation:</source>
<translation>Min. Arbeitsspeicher:</translation>
</message>
@@ -851,7 +1152,7 @@ p, li { white-space: pre-wrap; }
<translation>Max. Arbeitsspeicher:</translation>
</message>
<message>
- <location line="+39"/>
+ <location line="+51"/>
<source>PermGen:</source>
<translation>PermGen:</translation>
</message>
@@ -926,6 +1227,32 @@ p, li { white-space: pre-wrap; }
</message>
</context>
<context>
+ <name>JProfiler</name>
+ <message>
+ <location filename="../logic/tools/JProfiler.cpp" line="+24"/>
+ <source>Listening on port: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Profiler aborted</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>JVisualVM</name>
+ <message>
+ <location filename="../logic/tools/JVisualVM.cpp" line="+21"/>
+ <source>JVisualVM started</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Profiler aborted</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>JavaListLoadTask</name>
<message>
<location filename="../logic/lists/JavaVersionList.cpp" line="+175"/>
@@ -934,6 +1261,14 @@ p, li { white-space: pre-wrap; }
</message>
</context>
<context>
+ <name>LLListLoadTask</name>
+ <message>
+ <location filename="../logic/lists/LiteLoaderVersionList.cpp" line="+104"/>
+ <source>Loading LiteLoader version list...</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>LWJGLSelectDialog</name>
<message>
<location filename="../gui/dialogs/LwjglSelectDialog.ui" line="+14"/>
@@ -1022,7 +1357,7 @@ p, li { white-space: pre-wrap; }
<translation>Texturenpakete</translation>
</message>
<message>
- <location filename="../gui/dialogs/OneSixModEditDialog.cpp" line="+303"/>
+ <location filename="../gui/dialogs/OneSixModEditDialog.cpp" line="+401"/>
<location filename="../gui/dialogs/LegacyModEditDialog.cpp" line="+258"/>
<source>Select Loader Mods</source>
<extracomment>Title of regular mod selection dialog</extracomment>
@@ -1060,7 +1395,7 @@ p, li { white-space: pre-wrap; }
<context>
<name>LegacyUpdate</name>
<message>
- <location filename="../logic/LegacyUpdate.cpp" line="+79"/>
+ <location filename="../logic/LegacyUpdate.cpp" line="+80"/>
<source>Downloading new LWJGL...</source>
<translation>LWJGL wird heruntergeladen...</translation>
</message>
@@ -1085,7 +1420,7 @@ p, li { white-space: pre-wrap; }
<translation>Neue minecraft.jar wird heruntergeladen...</translation>
</message>
<message>
- <location line="+34"/>
+ <location line="+33"/>
<source>Installing mods: Adding </source>
<translation>Mod-Installation: Hinzufügen </translation>
</message>
@@ -1209,7 +1544,7 @@ p, li { white-space: pre-wrap; }
<context>
<name>MCVListLoadTask</name>
<message>
- <location filename="../logic/lists/MinecraftVersionList.cpp" line="+142"/>
+ <location filename="../logic/lists/MinecraftVersionList.cpp" line="+146"/>
<source>Loading instance version list...</source>
<translation>Lade Liste von Minecraft-Versionen...</translation>
</message>
@@ -1232,24 +1567,24 @@ p, li { white-space: pre-wrap; }
<translation>Instanz-Werkzeugleiste</translation>
</message>
<message>
- <location line="+40"/>
+ <location line="+43"/>
<source>News Toolbar</source>
<translation>Nachrichten-Werkzeugleiste</translation>
</message>
<message>
- <location line="+34"/>
+ <location line="+35"/>
<source>Add Instance</source>
<translation>Instanz hinzufügen</translation>
</message>
<message>
<location line="+3"/>
<location line="+3"/>
- <location line="+332"/>
+ <location line="+342"/>
<source>Add a new instance.</source>
<translation>Neue Instanz erstellen.</translation>
</message>
<message>
- <location line="-323"/>
+ <location line="-332"/>
<source>View Instance Folder</source>
<translation>Instanzordner öffnen</translation>
</message>
@@ -1260,7 +1595,7 @@ p, li { white-space: pre-wrap; }
<translation>Instanzordner im Dateimanager öffnen.</translation>
</message>
<message>
- <location line="+9"/>
+ <location line="+10"/>
<source>Refresh</source>
<translation>Aktualisieren</translation>
</message>
@@ -1271,7 +1606,7 @@ p, li { white-space: pre-wrap; }
<translation>Instanzliste neuladen.</translation>
</message>
<message>
- <location line="+9"/>
+ <location line="+10"/>
<source>View Central Mods Folder</source>
<translation>Zenstralen Mod-Ordner öffnen</translation>
</message>
@@ -1282,7 +1617,7 @@ p, li { white-space: pre-wrap; }
<translation>Zentralen Mod-Ordner in einem Dateimanager öffnen.</translation>
</message>
<message>
- <location line="+9"/>
+ <location line="+10"/>
<source>Check for Updates</source>
<translation>Auf Updates überprüfen</translation>
</message>
@@ -1293,19 +1628,19 @@ p, li { white-space: pre-wrap; }
<translation>Auf Updates für MultiMC prüfen</translation>
</message>
<message>
- <location line="+9"/>
- <location line="+133"/>
+ <location line="+10"/>
+ <location line="+136"/>
<source>Settings</source>
<translation>Einstellungen</translation>
</message>
<message>
- <location line="-130"/>
+ <location line="-133"/>
<location line="+3"/>
<source>Change settings.</source>
<translation>Einstellungen ändern.</translation>
</message>
<message>
- <location line="+12"/>
+ <location line="+13"/>
<source>Report a Bug</source>
<translation>Fehler melden</translation>
</message>
@@ -1324,7 +1659,7 @@ p, li { white-space: pre-wrap; }
<translation type="vanished">Den MultiMC-Entwicklerblog öffnen, um Neuigkeiten über MultiMC zu erhalten.</translation>
</message>
<message>
- <location line="+9"/>
+ <location line="+10"/>
<source>More News</source>
<translation>Mehr Nachrichten</translation>
</message>
@@ -1340,7 +1675,7 @@ p, li { white-space: pre-wrap; }
<translation>Öffne den MultiMC-Entwicklerblog, um weitere Neuigkeiten über MultiMC zu erhalten.</translation>
</message>
<message>
- <location line="+9"/>
+ <location line="+10"/>
<location line="+6"/>
<source>About MultiMC</source>
<translation>Über MultiMC</translation>
@@ -1358,11 +1693,12 @@ p, li { white-space: pre-wrap; }
<message>
<location line="+3"/>
<location line="+3"/>
+ <location line="+213"/>
<source>Launch the selected instance.</source>
<translation>Die ausgewählte Instanz starten.</translation>
</message>
<message>
- <location line="+5"/>
+ <location line="-208"/>
<source>Instance Name</source>
<translation>Instanzname</translation>
</message>
@@ -1499,17 +1835,32 @@ p, li { white-space: pre-wrap; }
<translation>Den Konfigurationsordner im Dateimanager anzeigen</translation>
</message>
<message>
- <location line="+12"/>
+ <location line="+13"/>
<source>Meow</source>
<translation>Miau</translation>
</message>
<message>
<location line="+3"/>
- <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-weight:600; color:#ff0004;&quot;&gt;Catnarok!&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;Or just a cat with a ball of yarn?&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;WHO KNOWS?!&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;:/icons/instances/tnt&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
- <translation></translation>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;It&apos;s a fluffy kitty :3&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
</message>
<message>
- <location line="+9"/>
+ <location line="+32"/>
+ <source>Launch the selected instance in offline mode.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Manage Screenshots</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;View and upload screenshots for this instance&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-33"/>
<source>Copy Instance</source>
<translation>Kopiere Instanz</translation>
</message>
@@ -1520,7 +1871,7 @@ p, li { white-space: pre-wrap; }
</message>
<message>
<location line="+8"/>
- <location filename="../gui/MainWindow.cpp" line="+201"/>
+ <location filename="../gui/MainWindow.cpp" line="+239"/>
<source>Manage Accounts</source>
<translation>Verwalte Konten</translation>
</message>
@@ -1534,17 +1885,22 @@ p, li { white-space: pre-wrap; }
<translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-weight:600; color:#ff0004;&quot;&gt;Catnatok!&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;Or just a cat with a ball of yarn?&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;WHO KNOWS?!&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;:/icons/instances/tnt&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
- <location filename="../gui/MainWindow.cpp" line="-9"/>
+ <location filename="../gui/MainWindow.cpp" line="-31"/>
<source>No instance selected</source>
<translation>Keine Instanz ausgewählt</translation>
</message>
<message>
- <location line="+17"/>
+ <location line="+1"/>
+ <source>No status available</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+38"/>
<source>Accounts</source>
<translation>Konten</translation>
</message>
<message>
- <location line="+67"/>
+ <location line="+69"/>
<source>No update found.</source>
<translation>Keine neue Version gefunden.</translation>
</message>
@@ -1556,7 +1912,38 @@ You are using the latest version.</source>
Du verwendest bereits die neueste Version.</translation>
</message>
<message>
- <location line="+52"/>
+ <location line="+34"/>
+ <source>Rename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+20"/>
+ <location line="+947"/>
+ <source>Launch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-945"/>
+ <source>Profilers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Profiler not setup correctly. Go into settings, &quot;External Tools&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Tools</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Tool not setup correctly. Go into settings, &quot;External Tools&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+28"/>
<source>No accounts added!</source>
<translation>Keine Konten angegeben!</translation>
</message>
@@ -1566,7 +1953,7 @@ Du verwendest bereits die neueste Version.</translation>
<translation>Kein voreingestelltes Konto</translation>
</message>
<message>
- <location line="+95"/>
+ <location line="+94"/>
<source>Loading news...</source>
<translation>Nachrichten werden geladen...</translation>
</message>
@@ -1576,7 +1963,17 @@ Du verwendest bereits die neueste Version.</translation>
<translation>Keine Nachrichten verfügbar.</translation>
</message>
<message>
- <location line="+105"/>
+ <location line="+122"/>
+ <source>Notification</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Don&apos;t show again</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+97"/>
<location line="+7"/>
<location line="+7"/>
<location line="+12"/>
@@ -1584,20 +1981,22 @@ Du verwendest bereits die neueste Version.</translation>
<location line="+37"/>
<location line="+7"/>
<location line="+7"/>
- <location line="+436"/>
+ <location line="+423"/>
+ <location line="+30"/>
+ <location line="+80"/>
<source>Error</source>
<translation>Fehler</translation>
</message>
<message>
- <location line="-486"/>
- <location line="+487"/>
+ <location line="-583"/>
+ <location line="+584"/>
<source>MultiMC cannot download Minecraft or update instances unless you have at least one account added.
Please add your Mojang or Minecraft account.</source>
<translation>MultiMC kann Minecraft nicht herunterladen und keine Instanzen aktualisieren, solange du kein Konto erstellt hast.
Bitte füge dein Mojang- oder Minecraft-Konto hinzu.</translation>
</message>
<message>
- <location line="-390"/>
+ <location line="-472"/>
<source>Group name</source>
<translation>Gruppenname</translation>
</message>
@@ -1607,7 +2006,7 @@ Bitte füge dein Mojang- oder Minecraft-Konto hinzu.</translation>
<translation>Neuen Gruppennamen eingeben.</translation>
</message>
<message>
- <location line="+91"/>
+ <location line="+92"/>
<source>CAREFUL</source>
<translation>ACHTUNG</translation>
</message>
@@ -1629,7 +2028,7 @@ Die folgende Instanz löschen:</translation>
<translation>Neuen Instanznamen eingeben.</translation>
</message>
<message>
- <location line="+89"/>
+ <location line="+98"/>
<source>No Accounts</source>
<translation>Keine Konten</translation>
</message>
@@ -1644,17 +2043,65 @@ Die folgende Instanz löschen:</translation>
<translation>Welches Konto möchtest du benutzen?</translation>
</message>
<message>
- <location line="+17"/>
+ <location line="+22"/>
<source>Your account is currently not logged in. Please enter your password to log in again.</source>
<translation>Dein Konto ist momentan nicht angemeldet. Bitte gib dein Passwort an, um dich anzumelden.</translation>
</message>
<message>
+ <location line="+47"/>
+ <source>Player name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Choose your offline mode player name.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+69"/>
+ <source>Couldn&apos;t start profiler: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+9"/>
+ <source>Waiting for profiler...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<location line="+7"/>
+ <source>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.
+
+%1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Waiting</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Couldn&apos;t start the profiler: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+289"/>
+ <source>Failed to load screenshots!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Done uploading!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../gui/MainWindow.ui" line="+5"/>
+ <location filename="../gui/MainWindow.cpp" line="-428"/>
<source>Play Offline</source>
<translation>Offline spielen</translation>
</message>
<message>
- <location line="+90"/>
+ <location filename="../gui/MainWindow.cpp" line="+150"/>
<source>Error updating instance</source>
<translation>Fehler beim Aktualisieren der Instanz</translation>
</message>
@@ -1699,12 +2146,12 @@ Die folgende Instanz löschen:</translation>
<translation>Instanzeinstellungen</translation>
</message>
<message>
- <location line="+38"/>
+ <location line="+41"/>
<source>Rename Instance</source>
<translation>Instanz umbenennen</translation>
</message>
<message>
- <location line="+77"/>
+ <location line="+80"/>
<source>Select a Java version</source>
<translation>Wähle eine Java-Version</translation>
</message>
@@ -1722,7 +2169,7 @@ Die folgende Instanz löschen:</translation>
<context>
<name>MinecraftProcess</name>
<message>
- <location filename="../logic/MinecraftProcess.cpp" line="+139"/>
+ <location filename="../logic/MinecraftProcess.cpp" line="+237"/>
<source>Minecraft exited with exitcode %1.</source>
<extracomment>Message displayed on instance exit</extracomment>
<translation>Minecraft wurde mit Status %1 beendet.</translation>
@@ -1740,7 +2187,45 @@ Die folgende Instanz löschen:</translation>
<translation>Minecraft wurde durch den Nutzer getötet.</translation>
</message>
<message>
- <location line="+52"/>
+ <location line="+9"/>
+ <source>Running Post-Launch command: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+16"/>
+ <source>Post-Launch command failed with code %1.
+
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Post-Launch command ran successfully.
+
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+21"/>
+ <source>Running Pre-Launch command: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+18"/>
+ <source>Pre-Launch command failed with code %1.
+
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Pre-Launch command ran successfully.
+
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+42"/>
<source>Could not launch minecraft!</source>
<extracomment>Error message displayed if instace can&apos;t start</extracomment>
<translation>Konnte Minecraft nicht starten!</translation>
@@ -1849,7 +2334,7 @@ Die folgende Instanz löschen:</translation>
<context>
<name>OneSixFTBInstanceForge</name>
<message>
- <location filename="../logic/OneSixFTBInstance.cpp" line="+37"/>
+ <location filename="../logic/OneSixFTBInstance.cpp" line="+40"/>
<source>Downloading Forge...</source>
<translation>Forge wird heruntergeladen...</translation>
</message>
@@ -1864,7 +2349,7 @@ Die folgende Instanz löschen:</translation>
<translation>Fehlschlag beim Laden der Versions-Konfiguration</translation>
</message>
<message>
- <location line="+8"/>
+ <location line="+6"/>
<source>Couldn&apos;t install Forge</source>
<translation>Fehler beim Installieren von Forge</translation>
</message>
@@ -1881,19 +2366,18 @@ Die folgende Instanz löschen:</translation>
<translation type="obsolete">Bibliothek</translation>
</message>
<message>
- <location filename="../gui/dialogs/OneSixModEditDialog.ui" line="+179"/>
+ <location filename="../gui/dialogs/OneSixModEditDialog.ui" line="+158"/>
<source>Loader Mods</source>
<translation>Mods</translation>
</message>
<message>
- <location line="-50"/>
- <location line="+80"/>
+ <location line="+30"/>
<location line="+67"/>
<source>&amp;Add</source>
<translation>&amp;Hinzufügen</translation>
</message>
<message>
- <location line="-262"/>
+ <location line="-241"/>
<source>Manage Mods</source>
<translation>Verwalte Mods</translation>
</message>
@@ -1903,7 +2387,7 @@ Die folgende Instanz löschen:</translation>
<translation>Version</translation>
</message>
<message>
- <location line="+20"/>
+ <location line="+23"/>
<source>Main Class:</source>
<translation>Hauptklasse:</translation>
</message>
@@ -1923,49 +2407,66 @@ Die folgende Instanz löschen:</translation>
<translation>Installiere LiteLoader</translation>
</message>
<message>
+ <location line="+14"/>
+ <source>Reload</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Remove</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<location line="+7"/>
+ <source>Reset order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+14"/>
+ <source>Move up</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Move down</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<source>Create an customized copy of the base version</source>
- <translation>Eine modifizierte Kopie der Version erstellen</translation>
+ <translation type="vanished">Eine modifizierte Kopie der Version erstellen</translation>
</message>
<message>
- <location line="+3"/>
<source>Customize</source>
- <translation>Benutzerdefiniert</translation>
+ <translation type="vanished">Benutzerdefiniert</translation>
</message>
<message>
- <location line="+10"/>
<source>Revert to original base version</source>
- <translation>Benutzerdefinierte Einstellungen zurücksetzen</translation>
+ <translation type="vanished">Benutzerdefinierte Einstellungen zurücksetzen</translation>
</message>
<message>
- <location line="+3"/>
<source>Revert</source>
- <translation>Zurücksetzen</translation>
+ <translation type="vanished">Zurücksetzen</translation>
</message>
<message>
- <location line="+20"/>
<source>Add new libraries</source>
- <translation>Füge neue Bibliotheken hinzu</translation>
+ <translation type="vanished">Füge neue Bibliotheken hinzu</translation>
</message>
<message>
- <location line="+13"/>
<source>Remove selected libraries</source>
- <translation>Entferne ausgewählte Bibliotheken</translation>
+ <translation type="vanished">Entferne ausgewählte Bibliotheken</translation>
</message>
<message>
- <location line="+3"/>
- <location line="+74"/>
+ <location line="+60"/>
<location line="+67"/>
<source>&amp;Remove</source>
<translation>&amp;Entfernen</translation>
</message>
<message>
- <location line="-127"/>
<source>Open custom.json</source>
- <translation>Öffne custom.json</translation>
+ <translation type="vanished">Öffne custom.json</translation>
</message>
<message>
- <location line="+80"/>
+ <location line="-47"/>
<location line="+67"/>
<source>&amp;View Folder</source>
<translation>&amp;Ordner öffnen</translation>
@@ -1976,49 +2477,78 @@ Die folgende Instanz löschen:</translation>
<translation>Ressourcenpakete</translation>
</message>
<message>
- <location filename="../gui/dialogs/OneSixModEditDialog.cpp" line="-205"/>
- <location line="+34"/>
+ <location filename="../gui/dialogs/OneSixModEditDialog.cpp" line="-292"/>
+ <source>Couldn&apos;t remove file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+44"/>
+ <location line="+37"/>
+ <source>Couldn&apos;t save the new order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+13"/>
+ <location line="+57"/>
<source>Revert?</source>
<translation>Zurücksetzen?</translation>
</message>
<message>
- <location line="-34"/>
+ <location line="-57"/>
+ <location line="+57"/>
+ <source>This action will remove your custom.json. Continue?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-48"/>
+ <source>No Forge versions are currently available for Minecraft </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+55"/>
+ <source>Select LiteLoader version</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>No LiteLoader versions are currently available for Minecraft </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<source>Do you want to revert the version of this instance to its original configuration?</source>
- <translation>Möchtest du wirklich die Version dieser Instanz zurücksetzen?</translation>
+ <translation type="vanished">Möchtest du wirklich die Version dieser Instanz zurücksetzen?</translation>
</message>
<message>
- <location line="+20"/>
+ <location line="-160"/>
+ <location line="+44"/>
+ <location line="+37"/>
<source>Error</source>
<translation>Fehler</translation>
</message>
<message>
- <location line="+0"/>
<source>Unable to open custom.json, check the settings</source>
- <translation>Fehler beim Öffnen der custom.json-Datei, überprüfe deine Einstellungen</translation>
+ <translation type="vanished">Fehler beim Öffnen der custom.json-Datei, überprüfe deine Einstellungen</translation>
</message>
<message>
- <location line="+7"/>
+ <location line="+20"/>
<source>Select Forge version</source>
<translation>Wähle Forge-Version</translation>
</message>
<message>
- <location line="+8"/>
<source>This will revert any changes you did to the version up to this point. Is that OK?</source>
- <translation>Dies wird alle Änderungen, die du vorgenommen hast, zurücksetzen. Bist du damit einverstanden?</translation>
+ <translation type="vanished">Dies wird alle Änderungen, die du vorgenommen hast, zurücksetzen. Bist du damit einverstanden?</translation>
</message>
<message>
- <location line="+69"/>
- <location line="+15"/>
+ <location line="+70"/>
<source>LiteLoader</source>
<translation>LiteLoader</translation>
</message>
<message>
- <location line="-14"/>
<source>There is no information available on how to install LiteLoader into this version of Minecraft</source>
- <translation>Es gibt momentan keine Informationen zur Installation von LiteLoader für diese Version von Minecraft</translation>
+ <translation type="vanished">Es gibt momentan keine Informationen zur Installation von LiteLoader für diese Version von Minecraft</translation>
</message>
<message>
- <location line="+15"/>
+ <location line="+1"/>
<source>For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details.</source>
<translation>Aus unbekannten Gründen ist die Installation von LiteLoader fehlgeschlagen. Sieh dir die MultiMC-Logdateien an, um weitere Details zu erhalten.</translation>
</message>
@@ -2026,18 +2556,16 @@ Die folgende Instanz löschen:</translation>
<context>
<name>OneSixUpdate</name>
<message>
- <location filename="../logic/OneSixUpdate.cpp" line="+60"/>
- <location line="+32"/>
<source>Testing the Java installation...</source>
- <translation>Java-Installation wird getestet...</translation>
+ <translation type="vanished">Java-Installation wird getestet...</translation>
</message>
<message>
- <location line="+39"/>
+ <location filename="../logic/OneSixUpdate.cpp" line="+82"/>
<source>Getting the version files from Mojang...</source>
<translation>Versionsdateien von Mojang werden heruntergeladen...</translation>
</message>
<message>
- <location line="+68"/>
+ <location line="+69"/>
<source>Updating assets index...</source>
<translation>Datenindex wird aktualisiert...</translation>
</message>
@@ -2047,14 +2575,26 @@ Die folgende Instanz löschen:</translation>
<translation>Daten werden von Mojang geholt...</translation>
</message>
<message>
- <location line="+34"/>
+ <location line="+32"/>
<source>Getting the library files from Mojang...</source>
<translation>Bibliotheken werden von Mojang geholt...</translation>
</message>
<message>
- <location line="+88"/>
<source>Preparing for launch...</source>
- <translation>Start wird vorbereitet...</translation>
+ <translation type="vanished">Start wird vorbereitet...</translation>
+ </message>
+</context>
+<context>
+ <name>OneSixVersion</name>
+ <message>
+ <location filename="../logic/OneSixVersion.cpp" line="+173"/>
+ <source>Name</source>
+ <translation type="unfinished">Name</translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Version</source>
+ <translation type="unfinished">Version</translation>
</message>
</context>
<context>
@@ -2101,11 +2641,96 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<source>The mod author didn&apos;t provide a website link for this mod.</source>
<translation>Der Autor der Modifikation hat keine URL hinterlegt.</translation>
</message>
+ <message>
+ <location filename="../logic/tools/JVisualVM.cpp" line="+36"/>
+ <location filename="../logic/tools/JProfiler.cpp" line="+32"/>
+ <source>Empty path</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Invalid path to JVisualVM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../logic/tools/JProfiler.cpp" line="+6"/>
+ <location filename="../logic/tools/MCEditTool.cpp" line="+68"/>
+ <source>Path does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Invalid JProfiler install</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../logic/tools/MCEditTool.cpp" line="-6"/>
+ <source>Path is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+11"/>
+ <source>Path does not seem to be a MCEdit path</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../logic/lists/LiteLoaderVersionList.h" line="+36"/>
+ <source>Latest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../logic/OneSixVersionBuilder.cpp" line="+891"/>
+ <location line="+42"/>
+ <location line="+47"/>
+ <location line="+8"/>
+ <location line="+7"/>
+ <location line="+15"/>
+ <location line="+8"/>
+ <location line="+11"/>
+ <source>Error</source>
+ <translation type="unfinished">Fehler</translation>
+ </message>
+ <message>
+ <location line="-137"/>
+ <location line="+42"/>
+ <source>Error while applying %1. Please check MultiMC-0.log for more info.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+47"/>
+ <source>Error while reading. Please check MultiMC-0.log for more info.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Error while applying. Please check MultiMC-0.log for more info.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>The version descriptors of this instance are not compatible with the current version of MultiMC</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <source>Unable to open %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Unable to parse %1: %2 at %3</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+11"/>
+ <source>Error while reading %1. Please check MultiMC-0.log for more info.</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>RefreshTask</name>
<message>
- <location filename="../logic/auth/flows/RefreshTask.cpp" line="+148"/>
+ <location filename="../logic/auth/flows/RefreshTask.cpp" line="+146"/>
<source>Refreshing login token...</source>
<translation>Erneuerung des Login-Tokens...</translation>
</message>
@@ -2116,6 +2741,63 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
</message>
</context>
<context>
+ <name>ScreenshotDialog</name>
+ <message>
+ <location filename="../gui/dialogs/ScreenshotDialog.ui" line="+14"/>
+ <source>Screenshot Manager</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+40"/>
+ <source>Upload</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Delete</source>
+ <translation type="unfinished">Löschen</translation>
+ </message>
+ <message>
+ <location line="+20"/>
+ <source>Close</source>
+ <translation type="unfinished">Schließen</translation>
+ </message>
+ <message>
+ <location filename="../gui/dialogs/ScreenshotDialog.cpp" line="+28"/>
+ <source>&lt;a href=&quot;https://imgur.com/a/%1&quot;&gt;Visit album&lt;/a&gt;&lt;br/&gt;Delete hash: %2 (save this if you want to be able to edit/delete the album)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+41"/>
+ <source>Failed to upload screenshots!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ScreenshotList</name>
+ <message>
+ <location filename="../logic/screenshots/ScreenshotList.cpp" line="+98"/>
+ <location line="+10"/>
+ <source>Error!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-9"/>
+ <source>Failed to delete screenshots!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Unable to refresh list: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>SettingsDialog</name>
<message>
<location filename="../gui/dialogs/SettingsDialog.ui" line="+20"/>
@@ -2123,12 +2805,11 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<translation>Einstellungen</translation>
</message>
<message>
- <location line="+20"/>
<source>General</source>
- <translation>Allgemein</translation>
+ <translation type="vanished">Allgemein</translation>
</message>
<message>
- <location line="+9"/>
+ <location line="+273"/>
<source>Sorting Mode</source>
<translation>Sortiermodus</translation>
</message>
@@ -2143,22 +2824,21 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<translation>Nach Namen</translation>
</message>
<message>
- <location line="+13"/>
+ <location line="-263"/>
<source>Update Settings</source>
<translation>Updateeinstellungen</translation>
</message>
<message>
- <location line="+6"/>
<source>Use development builds?</source>
- <translation>Entwicklungsversionen benutzen?</translation>
+ <translation type="vanished">Entwicklungsversionen benutzen?</translation>
</message>
<message>
- <location line="+7"/>
+ <location line="+6"/>
<source>Check for updates when MultiMC starts?</source>
<translation>Beim Start nach Updates suchen?</translation>
</message>
<message>
- <location line="+97"/>
+ <location line="+121"/>
<source>Folders</source>
<translation>Ordner</translation>
</message>
@@ -2174,13 +2854,31 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<location line="+20"/>
<location line="+14"/>
<location line="+17"/>
- <location line="+26"/>
- <location line="+289"/>
+ <location line="+98"/>
+ <location line="+307"/>
+ <location line="+240"/>
+ <location line="+37"/>
+ <location line="+37"/>
<source>...</source>
<translation>...</translation>
</message>
<message>
- <location line="-469"/>
+ <location line="-919"/>
+ <source>Features</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+19"/>
+ <source>Update Channel:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+14"/>
+ <source>No channel selected.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+13"/>
<source>FTB</source>
<translation>FTB</translation>
</message>
@@ -2215,7 +2913,17 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<translation>Symbole:</translation>
</message>
<message>
- <location line="+17"/>
+ <location line="+31"/>
+ <source>User Interface</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+14"/>
+ <source>Language (needs restart):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+44"/>
<source>External Editors (leave empty for system default)</source>
<translation>Externe Editor-Anwendungen (leer lassen, um die System-Voreinstellung zu benutzen)</translation>
</message>
@@ -2265,11 +2973,128 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<translation>Konsole automatisch schließen, nachdem das Spiel beendet wurde?</translation>
</message>
<message>
+ <location line="+265"/>
+ <source>Network settings.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Network</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Proxy</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Uses your system&apos;s default proxy settings.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Default</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>None</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>SOCKS5</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>HTTP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+13"/>
+ <source>Address and Port</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>127.0.0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+26"/>
+ <source>Authentication</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+9"/>
+ <source>Username:</source>
+ <translation type="unfinished">Nutzername:</translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Password:</source>
+ <translation type="unfinished">Passwort:</translation>
+ </message>
+ <message>
+ <location line="+14"/>
+ <source>Note: Proxy username and password are stored in plain text inside MultiMC&apos;s configuration file!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+30"/>
+ <source>External Tools</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>JProfiler</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+20"/>
+ <location line="+37"/>
+ <location line="+37"/>
+ <source>Check</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <source>&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;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>JVisualVM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+27"/>
+ <source>&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;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>MCEdit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+27"/>
+ <source>&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;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<source>Login automatically when an instance icon is double clicked?</source>
<translation type="vanished">Automatisch einloggen, wenn das Instanzsymbol doppelt geklickt wurde?</translation>
</message>
<message>
- <location line="+24"/>
+ <location line="-507"/>
<source>Java</source>
<translation>Java</translation>
</message>
@@ -2279,7 +3104,19 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<translation>Arbeitsspeicher</translation>
</message>
<message>
- <location line="+22"/>
+ <location line="+6"/>
+ <source>The maximum amount of memory Minecraft is allowed to use.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <location line="+36"/>
+ <location line="+29"/>
+ <source> MB</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-46"/>
<source>Minimum memory allocation:</source>
<translation>Min. Arbeitspeicher:</translation>
</message>
@@ -2289,12 +3126,22 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<translation>Max. Arbeitspeicher:</translation>
</message>
<message>
- <location line="+23"/>
+ <location line="+7"/>
+ <source>The amount of memory Minecraft is started with.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+22"/>
<source>PermGen:</source>
<translation>PermGen:</translation>
</message>
<message>
- <location line="+26"/>
+ <location line="+7"/>
+ <source>The amount of memory available to store loaded Java classes.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+25"/>
<source>Java Settings</source>
<translation>Java-Einstellungen</translation>
</message>
@@ -2347,17 +3194,17 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<translation>Der Vor-Start-Befehl wird ausgeführt, bevor die Instanz startet, der Nach-Ende-Befehl, nachdem die Instanz beendet wurde. Beide werden im Hauptverzeichnis von MultiMC gestartet. Verfügbare Umgebungsvariablen: INST_ID, INST_DIR, INST_NAME.</translation>
</message>
<message>
- <location filename="../gui/dialogs/SettingsDialog.cpp" line="+77"/>
+ <location filename="../gui/dialogs/SettingsDialog.cpp" line="+102"/>
<source>FTB Launcher Directory</source>
<translation>FTB-Launcher-Ordner</translation>
</message>
<message>
- <location line="+13"/>
+ <location line="+14"/>
<source>FTB Directory</source>
<translation>FTB-Ordner</translation>
</message>
<message>
- <location line="+13"/>
+ <location line="+12"/>
<source>Instance Directory</source>
<translation>Instanz-Ordner</translation>
</message>
@@ -2382,27 +3229,103 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
<translation>JSON-Editor</translation>
</message>
<message>
- <location line="+23"/>
+ <location line="+22"/>
<source>Invalid</source>
<translation>Ungültig</translation>
</message>
<message>
- <location line="+0"/>
+ <location line="+1"/>
<source>The file chosen does not seem to be an executable</source>
<translation>Die ausgewählte Datei scheint keine Anwendung zu sein</translation>
</message>
<message>
- <location line="+34"/>
+ <location line="+201"/>
+ <source>English</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+144"/>
+ <source>JProfiler Directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <location line="+16"/>
+ <location line="+23"/>
+ <location line="+16"/>
+ <location line="+28"/>
+ <location line="+17"/>
+ <source>Error</source>
+ <translation type="unfinished">Fehler</translation>
+ </message>
+ <message>
+ <location line="-99"/>
+ <location line="+16"/>
+ <source>Error while checking JProfiler install:
+%1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <location line="+39"/>
+ <location line="+45"/>
+ <source>OK</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-84"/>
+ <source>JProfiler setup seems to be OK</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>JVisualVM Executable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+9"/>
+ <location line="+16"/>
+ <source>Error while checking JVisualVM install:
+%1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>JVisualVM setup seems to be OK</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+12"/>
+ <source>MCEdit Application</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>MCEdit Directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <location line="+17"/>
+ <source>Error while checking MCEdit install:
+%1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>MCEdit setup seems to be OK</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<source>Development builds</source>
- <translation>Entwicklungsversionen</translation>
+ <translation type="vanished">Entwicklungsversionen</translation>
</message>
<message>
- <location line="+1"/>
<source>Development builds contain experimental features and may be unstable. Are you sure you want to enable them?</source>
- <translation>Entwicklungsversionen enthalten experimentelle Features und können instabil sein. Möchtest du sie dennoch aktivieren?</translation>
+ <translation type="vanished">Entwicklungsversionen enthalten experimentelle Features und können instabil sein. Möchtest du sie dennoch aktivieren?</translation>
</message>
<message>
- <location line="+132"/>
+ <location line="-170"/>
<source>Select a Java version</source>
<translation>Wähle eine Java-Version</translation>
</message>
@@ -2481,6 +3404,14 @@ Diese Mitteilung wird so lange angezeigt, bis du die Option entfernt hast.</tran
</message>
</context>
<context>
+ <name>VersionListView</name>
+ <message>
+ <location filename="../gui/widgets/VersionListView.cpp" line="+27"/>
+ <source>No versions are currently available.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>VersionSelectDialog</name>
<message>
<source>Dialog</source>