summaryrefslogtreecommitdiffstats
path: root/logic
diff options
context:
space:
mode:
Diffstat (limited to 'logic')
-rw-r--r--logic/BaseInstaller.cpp2
-rw-r--r--logic/BaseInstaller.h1
-rw-r--r--logic/BaseInstance.cpp28
-rw-r--r--logic/BaseInstance.h21
-rw-r--r--logic/BaseInstance_p.h3
-rw-r--r--logic/ForgeInstaller.cpp6
-rw-r--r--logic/ForgeInstaller.h4
-rw-r--r--logic/InstanceFactory.cpp5
-rw-r--r--logic/LegacyFTBInstance.cpp4
-rw-r--r--logic/LegacyInstance.cpp12
-rw-r--r--logic/LiteLoaderInstaller.cpp45
-rw-r--r--logic/LiteLoaderInstaller.h13
-rw-r--r--logic/MMCJson.cpp60
-rw-r--r--logic/MMCJson.h46
-rw-r--r--logic/MinecraftProcess.cpp213
-rw-r--r--logic/MinecraftProcess.h19
-rw-r--r--logic/NostalgiaInstance.cpp4
-rw-r--r--logic/OneSixFTBInstance.cpp167
-rw-r--r--logic/OneSixFTBInstance.h9
-rw-r--r--logic/OneSixInstance.cpp106
-rw-r--r--logic/OneSixInstance.h25
-rw-r--r--logic/OneSixInstance_p.h6
-rw-r--r--logic/OneSixLibrary.h3
-rw-r--r--logic/OneSixUpdate.cpp55
-rw-r--r--logic/OneSixUpdate.h6
-rw-r--r--logic/OneSixVersion.cpp221
-rw-r--r--logic/OneSixVersionBuilder.cpp1101
-rw-r--r--logic/OneSixVersionBuilder.h23
-rw-r--r--logic/VersionFile.cpp535
-rw-r--r--logic/VersionFile.h127
-rw-r--r--logic/VersionFinal.cpp183
-rw-r--r--logic/VersionFinal.h (renamed from logic/OneSixVersion.h)23
-rw-r--r--logic/lists/InstanceList.cpp2
-rw-r--r--logic/lists/LiteLoaderVersionList.cpp223
-rw-r--r--logic/lists/LiteLoaderVersionList.h112
-rw-r--r--logic/lists/MinecraftVersionList.cpp2
-rw-r--r--logic/net/URLConstants.cpp19
-rw-r--r--logic/net/URLConstants.h30
-rw-r--r--logic/tools/BaseExternalTool.cpp77
-rw-r--r--logic/tools/BaseExternalTool.h57
-rw-r--r--logic/tools/BaseProfiler.cpp35
-rw-r--r--logic/tools/BaseProfiler.h36
-rw-r--r--logic/tools/JProfiler.cpp78
-rw-r--r--logic/tools/JProfiler.h23
-rw-r--r--logic/tools/JVisualVM.cpp74
-rw-r--r--logic/tools/JVisualVM.h23
-rw-r--r--logic/tools/MCEditTool.cpp77
-rw-r--r--logic/tools/MCEditTool.h23
48 files changed, 2503 insertions, 1464 deletions
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;
+};