diff options
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) @@ -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 ¤t, 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 ¤t, const QModelIndex & m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); updateInstanceToolIcon(m_selectedInstance->iconKey()); + updateToolsMenu(); + MMC->settings()->set("SelectedInstance", m_selectedInstance->id()); } else diff --git a/gui/MainWindow.h b/gui/MainWindow.h index 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 ¤t, const QModelIndex &previous) +void OneSixModEditDialog::versionCurrent(const QModelIndex ¤t, + 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><html><head/><body><p><a href="http://www.ej-technologies.com/products/jprofiler/overview.html"><span style=" text-decoration: underline; color:#0000ff;">http://www.ej-technologies.com/products/jprofiler/overview.html</span></a></p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>JVisualVM</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_11"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QLineEdit" name="jvisualvmPathEdit"/> + </item> + <item> + <widget class="QPushButton" name="jvisualvmPathBtn"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="jvisualvmCheckBtn"> + <property name="text"> + <string>Check</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="jvisualvmLink"> + <property name="text"> + <string><html><head/><body><p><a href="http://visualvm.java.net/"><span style=" text-decoration: underline; color:#0000ff;">http://visualvm.java.net/</span></a></p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>MCEdit</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_12"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QLineEdit" name="mceditPathEdit"/> + </item> + <item> + <widget class="QPushButton" name="mceditPathBtn"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="mceditCheckBtn"> + <property name="text"> + <string>Check</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="mceditLink"> + <property name="text"> + <string><html><head/><body><p><a href="http://www.mcedit.net/"><span style=" text-decoration: underline; color:#0000ff;">http://www.mcedit.net/</span></a></p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> </widget> </item> <item> @@ -938,7 +1069,7 @@ </connection> </connections> <buttongroups> - <buttongroup name="proxyGroup"/> <buttongroup name="sortingModeGroup"/> + <buttongroup name="proxyGroup"/> </buttongroups> </ui> diff --git a/gui/dialogs/VersionSelectDialog.cpp b/gui/dialogs/VersionSelectDialog.cpp index 0f379f56..3277fd2f 100644 --- a/gui/dialogs/VersionSelectDialog.cpp +++ b/gui/dialogs/VersionSelectDialog.cpp @@ -82,6 +82,7 @@ void VersionSelectDialog::loadList() Task *loadTask = m_vlist->getLoadTask(); loadTask->setParent(taskDlg); taskDlg->exec(loadTask); + delete taskDlg; } BaseVersionPtr VersionSelectDialog::selectedVersion() const diff --git a/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><html><head/><body><p>MultiMC is a custom launcher that makes managing Minecraft easier by allowing you to have multiple instances of Minecraft at once.</p></body></html></source> <translation><html><head/><body><p>MultiMC ist ein alternativer Launcher, der das Management von Minecraft vereinfacht, indem er es dir erlaubt, mehrere Installationen von Minecraft zu verwalten.</p></body></html></translation> </message> @@ -42,7 +71,197 @@ <translation></translation> </message> <message> - <location line="+28"/> + <location line="+41"/> + <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;">MultiMC</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Andrew Okin &lt;</span><a href="mailto:forkk@forkk.net"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">forkk@forkk.net</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Petr Mrázek &lt;</span><a href="mailto:peterix@gmail.com"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">peterix@gmail.com</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Sky &lt;</span><a href="https://www.twitter.com/drayshak"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">@drayshak</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p> +<p style="-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:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt; font-weight:600;">With thanks to</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Orochimarufan &lt;</span><a href="mailto:orochimarufan.x3@gmail.com"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">orochimarufan.x3@gmail.com</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">TakSuyu &lt;</span><a href="mailto:taksuyu@gmail.com"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">taksuyu@gmail.com</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Kilobyte &lt;</span><a href="mailto:stiepen22@gmx.de"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">stiepen22@gmx.de</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Jan (02JanDal) &lt;</span><a href="mailto:02jandal@gmail.com"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">02jandal@gmail.com</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Robotbrain &lt;</span><a href="https://twitter.com/skylordelros"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">@skylordelros</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Rootbear75 &lt;</span><a href="https://twitter.com/rootbear75"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">@rootbear75</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt; (build server)</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+64"/> + <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:11pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">MultiMC</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2012-2014 MultiMC Contributors</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">you may not use this file except in compliance with the License.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">You may obtain a copy of the License at</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> http://www.apache.org/licenses/LICENSE-2.0</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Unless required by applicable law or agreed to in writing, software</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">See the License for the specific language governing permissions and</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">limitations under the License.</span></p> +<p style="-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;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">QSLog</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Copyright (c) 2010, Razvan Petru</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">All rights reserved.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Redistribution and use in source and binary forms, with or without modification,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">are permitted provided that the following conditions are met:</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">* Redistributions of source code must retain the above copyright notice, this</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> list of conditions and the following disclaimer.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">* Redistributions in binary form must reproduce the above copyright notice, this</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> list of conditions and the following disclaimer in the documentation and/or other</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> materials provided with the distribution.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">* The name of the contributors may not be used to endorse or promote products</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> derived from this software without specific prior written permission.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot; AND</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">OF THE POSSIBILITY OF SUCH DAMAGE.</span></p> +<p style="-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;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">Group View (instance view)</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> /*</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * Copyright (C) 2007 Rafael Fernández López &lt;ereslibre@kde.org&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * Copyright (C) 2007 John Tapsell &lt;tapsell@kde.org&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> *</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * This library is free software; you can redistribute it and/or</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * modify it under the terms of the GNU Library General Public</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * License as published by the Free Software Foundation; either</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * version 2 of the License, or (at your option) any later version.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> *</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * This library is distributed in the hope that it will be useful,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * but WITHOUT ANY WARRANTY; without even the implied warranty of</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * Library General Public License for more details.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> *</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * You should have received a copy of the GNU Library General Public License</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * along with this library; see the file COPYING.LIB. If not, write to</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * Boston, MA 02110-1301, USA.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> */</span></p> +<p style="-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;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">Pack200</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The GNU General Public License (GPL)</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 2, June 1991</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">+ &quot;CLASSPATH&quot; EXCEPTION TO THE GPL</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Certain source files distributed by Oracle America and/or its affiliates are</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">subject to the following clarification and special exception to the GPL, but</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">only where Oracle has expressly included in the particular source file's header</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">the words &quot;Oracle designates this particular file as subject to the &quot;Classpath&quot;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">exception as provided by Oracle in the LICENSE file that accompanied this code.&quot;</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> Linking this library statically or dynamically with other modules is making</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> a combined work based on this library. Thus, the terms and conditions of</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> the GNU General Public License cover the whole combination.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> As a special exception, the copyright holders of this library give you</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> permission to link this library with independent modules to produce an</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> executable, regardless of the license terms of these independent modules,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> and to copy and distribute the resulting executable under terms of your</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> choice, provided that you also meet, for each linked independent module,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> the terms and conditions of the license of that module. An independent</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> module is a module which is not derived from or based on this library. If</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> you modify this library, you may extend this exception to your version of</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> the library, but you are not obligated to do so. If you do not wish to do</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> so, delete this exception statement from your version.</span></p> +<p style="-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;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">Quazip</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Copyright (C) 2005-2011 Sergey A. Tachenov</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This program is free software; you can redistribute it and/or modify it</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">under the terms of the GNU Lesser General Public License as published by</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">the Free Software Foundation; either version 2 of the License, or (at</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">your option) any later version.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This program is distributed in the hope that it will be useful, but</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">WITHOUT ANY WARRANTY; without even the implied warranty of</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">General Public License for more details.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You should have received a copy of the GNU Lesser General Public License</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">along with this program; if not, write to the Free Software Foundation,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">See COPYING file for the full LGPL text.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Original ZIP package is copyrighted by Gilles Vollant, see</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">quazip/(un)zip.h files for details, basically it's zlib license.</span></p> +<p style="-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;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">xz-minidec</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">/*</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * XZ decompressor</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> *</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * Authors: Lasse Collin &lt;lasse.collin@tukaani.org&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * Igor Pavlov &lt;http://7-zip.org/&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> *</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * This file has been put into the public domain.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * You can do whatever you want with this file.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> */</span></p> +<p style="-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;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">Java IconLoader class</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Copyright (c) 2011, Chris Molini</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">All rights reserved.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Redistribution and use in source and binary forms, with or without</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">modification, are permitted provided that the following conditions are met:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * Redistributions of source code must retain the above copyright</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> notice, this list of conditions and the following disclaimer.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * Redistributions in binary form must reproduce the above copyright</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> notice, this list of conditions and the following disclaimer in the</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> documentation and/or other materials provided with the distribution.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * Neither the name of the &lt;organization&gt; nor the</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> names of its contributors may be used to endorse or promote products</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> derived from this software without specific prior written permission.</span></p> +<p style="-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;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot; AND</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">DISCLAIMED. IN NO EVENT SHALL &lt;COPYRIGHT HOLDER&gt; BE LIABLE FOR ANY</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</span></p> +<p style="-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;"><br /></p> +<p style="-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;"><br /></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+175"/> + <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork <span style=" font-weight:600;">without</span> implying that you have our blessing.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -59,7 +278,7 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Jan (02JanDal) &lt;</span><a href="mailto:02jandal@gmail.com"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">02jandal@gmail.com</span></a><span style=" font-size:10pt;">&gt;</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Robotbrain &lt;</span><a href="https://twitter.com/skylordelros"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">@skylordelros</span></a><span style=" font-size:10pt;">&gt;</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Rootbear75 &lt;</span><a href="https://twitter.com/rootbear75"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">@rootbear75</span></a><span style=" font-size:10pt;">&gt; (build server)</span></p></body></html></source> - <translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <translation type="vanished"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> @@ -77,13 +296,12 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Rootbear75 &lt;</span><a href="https://twitter.com/rootbear75"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">@rootbear75</span></a><span style=" font-size:10pt;">&gt; (bau server)</span></p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -209,7 +427,7 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * This file has been put into the public domain.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> * You can do whatever you want with this file.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> */</span></p></body></html></source> - <translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <translation type="vanished"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:7.8pt; font-weight:400; font-style:normal;"> @@ -336,12 +554,11 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> */</span></p></body></html></translation> </message> <message> - <location line="+140"/> + <location line="+208"/> <source>Forking/Redistribution</source> <translation>Abspaltung/Weiterverbreitung</translation> </message> <message> - <location line="+6"/> <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -351,7 +568,7 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).</span></p> <p style="-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:'Bitstream Vera Sans'; font-size:11pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork </span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:600;">without</span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;"> implying that you have our blessing.</span></p></body></html></source> - <translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <translation type="vanished"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> @@ -366,7 +583,7 @@ p, li { white-space: pre-wrap; } <translation type="obsolete"><html><head/><body><p><a href="http://github.com/Forkk/MultiMC5"><span style=" text-decoration: underline; color:#0000ff;">http://github.com/Forkk/MultiMC5</span></a></p></body></html></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; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">POSSIBILITY OF SUCH DAMAGE.</span></p></body></html></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>&Kill Minecraft</source> <translation>&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><html><head/><body><p align="center"><span style=" font-weight:600; color:#ff0004;">Catnarok!</span></p><p align="center">Or just a cat with a ball of yarn?</p><p align="center"><span style=" font-style:italic;">WHO KNOWS?!</span></p><p align="center"><img src=":/icons/instances/tnt"/></p></body></html></source> - <translation></translation> + <source><html><head/><body><p align="center">It's a fluffy kitty :3</p></body></html></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><html><head/><body><p>View and upload screenshots for this instance</p></body></html></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"><html><head/><body><p align="center"><span style=" font-weight:600; color:#ff0004;">Catnatok!</span></p><p align="center">Or just a cat with a ball of yarn?</p><p align="center"><span style=" font-style:italic;">WHO KNOWS?!</span></p><p align="center"><img src=":/icons/instances/tnt"/></p></body></html></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, "External Tools".</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, "External Tools".</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'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'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'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'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'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>&Add</source> <translation>&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>&Remove</source> <translation>&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>&View Folder</source> <translation>&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't remove file</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+44"/> + <location line="+37"/> + <source>Couldn'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'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><a href="https://imgur.com/a/%1">Visit album</a><br/>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'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'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><html><head/><body><p><a href="http://www.ej-technologies.com/products/jprofiler/overview.html"><span style=" text-decoration: underline; color:#0000ff;">http://www.ej-technologies.com/products/jprofiler/overview.html</span></a></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+10"/> + <source>JVisualVM</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+27"/> + <source><html><head/><body><p><a href="http://visualvm.java.net/"><span style=" text-decoration: underline; color:#0000ff;">http://visualvm.java.net/</span></a></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+10"/> + <source>MCEdit</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+27"/> + <source><html><head/><body><p><a href="http://www.mcedit.net/"><span style=" text-decoration: underline; color:#0000ff;">http://www.mcedit.net/</span></a></p></body></html></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> |