summaryrefslogtreecommitdiffstats
path: root/libraries/logic/minecraft/forge/ForgeInstaller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/logic/minecraft/forge/ForgeInstaller.cpp')
-rw-r--r--libraries/logic/minecraft/forge/ForgeInstaller.cpp458
1 files changed, 458 insertions, 0 deletions
diff --git a/libraries/logic/minecraft/forge/ForgeInstaller.cpp b/libraries/logic/minecraft/forge/ForgeInstaller.cpp
new file mode 100644
index 00000000..353328ab
--- /dev/null
+++ b/libraries/logic/minecraft/forge/ForgeInstaller.cpp
@@ -0,0 +1,458 @@
+/* Copyright 2013-2015 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 "ForgeInstaller.h"
+#include "ForgeVersionList.h"
+
+#include "minecraft/MinecraftProfile.h"
+#include "minecraft/GradleSpecifier.h"
+#include "net/HttpMetaCache.h"
+#include "tasks/Task.h"
+#include "minecraft/onesix/OneSixInstance.h"
+#include <minecraft/onesix/OneSixVersionFormat.h>
+#include "minecraft/VersionFilterData.h"
+#include "minecraft/MinecraftVersion.h"
+#include "Env.h"
+#include "Exception.h"
+#include <FileSystem.h>
+
+#include <quazip.h>
+#include <quazipfile.h>
+#include <QStringList>
+#include <QRegularExpression>
+#include <QRegularExpressionMatch>
+
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QSaveFile>
+#include <QCryptographicHash>
+
+ForgeInstaller::ForgeInstaller() : BaseInstaller()
+{
+}
+
+void ForgeInstaller::prepare(const QString &filename, const QString &universalUrl)
+{
+ VersionFilePtr newVersion;
+ m_universal_url = universalUrl;
+
+ QuaZip zip(filename);
+ if (!zip.open(QuaZip::mdUnzip))
+ return;
+
+ QuaZipFile file(&zip);
+
+ // read the install profile
+ if (!zip.setCurrentFile("install_profile.json"))
+ return;
+
+ QJsonParseError jsonError;
+ if (!file.open(QIODevice::ReadOnly))
+ return;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(file.readAll(), &jsonError);
+ file.close();
+ if (jsonError.error != QJsonParseError::NoError)
+ return;
+
+ if (!jsonDoc.isObject())
+ return;
+
+ QJsonObject root = jsonDoc.object();
+
+ auto installVal = root.value("install");
+ auto versionInfoVal = root.value("versionInfo");
+ if (!installVal.isObject() || !versionInfoVal.isObject())
+ return;
+
+ try
+ {
+ newVersion = OneSixVersionFormat::versionFileFromJson(QJsonDocument(versionInfoVal.toObject()), QString(), false);
+ }
+ catch(Exception &err)
+ {
+ qWarning() << "Forge: Fatal error while parsing version file:" << err.what();
+ return;
+ }
+
+ for(auto problem: newVersion->getProblems())
+ {
+ qWarning() << "Forge: Problem found: " << problem.getDescription();
+ }
+ if(newVersion->getProblemSeverity() == ProblemSeverity::PROBLEM_ERROR)
+ {
+ qWarning() << "Forge: Errors found while parsing version file";
+ return;
+ }
+
+ QJsonObject installObj = installVal.toObject();
+ QString libraryName = installObj.value("path").toString();
+ internalPath = installObj.value("filePath").toString();
+ m_forgeVersionString = installObj.value("version").toString().remove("Forge", Qt::CaseInsensitive).trimmed();
+
+ // where do we put the library? decode the mojang path
+ GradleSpecifier lib(libraryName);
+
+ auto cacheentry = ENV.metacache()->resolveEntry("libraries", lib.toPath());
+ finalPath = "libraries/" + lib.toPath();
+ if (!FS::ensureFilePathExists(finalPath))
+ return;
+
+ if (!zip.setCurrentFile(internalPath))
+ return;
+ if (!file.open(QIODevice::ReadOnly))
+ return;
+ {
+ QByteArray data = file.readAll();
+ // extract file
+ QSaveFile extraction(finalPath);
+ if (!extraction.open(QIODevice::WriteOnly))
+ return;
+ if (extraction.write(data) != data.size())
+ return;
+ if (!extraction.commit())
+ return;
+ QCryptographicHash md5sum(QCryptographicHash::Md5);
+ md5sum.addData(data);
+
+ cacheentry->setStale(false);
+ cacheentry->setMD5Sum(md5sum.result().toHex().constData());
+ ENV.metacache()->updateEntry(cacheentry);
+ }
+ file.close();
+
+ m_forge_json = newVersion;
+}
+
+bool ForgeInstaller::add(OneSixInstance *to)
+{
+ if (!BaseInstaller::add(to))
+ {
+ return false;
+ }
+
+ if (!m_forge_json)
+ {
+ return false;
+ }
+
+ // A blacklist
+ QSet<QString> blacklist{"authlib", "realms"};
+ QList<QString> xzlist{"org.scala-lang", "com.typesafe"};
+
+ // get the minecraft version from the instance
+ VersionFilePtr minecraft;
+ auto minecraftPatch = to->getMinecraftProfile()->versionPatch("net.minecraft");
+ if(minecraftPatch)
+ {
+ minecraft = std::dynamic_pointer_cast<VersionFile>(minecraftPatch);
+ if(!minecraft)
+ {
+ auto mcWrap = std::dynamic_pointer_cast<MinecraftVersion>(minecraftPatch);
+ if(mcWrap)
+ {
+ minecraft = mcWrap->getVersionFile();
+ }
+ }
+ }
+
+ // for each library in the version we are adding (except for the blacklisted)
+ QMutableListIterator<LibraryPtr> iter(m_forge_json->libraries);
+ while (iter.hasNext())
+ {
+ auto library = iter.next();
+ QString libName = library->artifactId();
+ QString libVersion = library->version();
+ QString rawName = library->rawName();
+
+ // ignore lwjgl libraries.
+ if (g_VersionFilterData.lwjglWhitelist.contains(library->artifactPrefix()))
+ {
+ iter.remove();
+ continue;
+ }
+ // ignore other blacklisted (realms, authlib)
+ if (blacklist.contains(libName))
+ {
+ iter.remove();
+ continue;
+ }
+ // if minecraft version was found, ignore everything that is already in the minecraft version
+ if(minecraft)
+ {
+ bool found = false;
+ for (auto & lib: minecraft->libraries)
+ {
+ if(library->artifactPrefix() == lib->artifactPrefix() && library->version() == lib->version())
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ continue;
+ }
+
+ // if this is the actual forge lib, set an absolute url for the download
+ if (m_forge_version->type == ForgeVersion::Gradle)
+ {
+ if (libName == "forge")
+ {
+ library->setClassifier("universal");
+ }
+ else if (libName == "minecraftforge")
+ {
+ QString forgeCoord("net.minecraftforge:forge:%1:universal");
+ // using insane form of the MC version...
+ QString longVersion = m_forge_version->mcver + "-" + m_forge_version->jobbuildver;
+ GradleSpecifier spec(forgeCoord.arg(longVersion));
+ library->setRawName(spec);
+ }
+ }
+ else
+ {
+ if (libName.contains("minecraftforge"))
+ {
+ library->setAbsoluteUrl(m_universal_url);
+ }
+ }
+
+ // mark bad libraries based on the xzlist above
+ for (auto entry : xzlist)
+ {
+ qDebug() << "Testing " << rawName << " : " << entry;
+ if (rawName.startsWith(entry))
+ {
+ library->setHint("forge-pack-xz");
+ break;
+ }
+ }
+ }
+ QString &args = m_forge_json->minecraftArguments;
+ QStringList tweakers;
+ {
+ QRegularExpression expression("--tweakClass ([a-zA-Z0-9\\.]*)");
+ QRegularExpressionMatch match = expression.match(args);
+ while (match.hasMatch())
+ {
+ tweakers.append(match.captured(1));
+ args.remove(match.capturedStart(), match.capturedLength());
+ match = expression.match(args);
+ }
+ if(tweakers.size())
+ {
+ args.operator=(args.trimmed());
+ m_forge_json->addTweakers = tweakers;
+ }
+ }
+ if(minecraft && args == minecraft->minecraftArguments)
+ {
+ args.clear();
+ }
+
+ m_forge_json->name = "Forge";
+ m_forge_json->fileId = id();
+ m_forge_json->version = m_forgeVersionString;
+ m_forge_json->dependsOnMinecraftVersion = to->intendedVersionId();
+ m_forge_json->order = 5;
+
+ // reset some things we do not want to be passed along.
+ m_forge_json->m_releaseTime = QDateTime();
+ m_forge_json->m_updateTime = QDateTime();
+ m_forge_json->minimumLauncherVersion = -1;
+ m_forge_json->type.clear();
+ m_forge_json->minecraftArguments.clear();
+ m_forge_json->minecraftVersion.clear();
+
+ QSaveFile file(filename(to->instanceRoot()));
+ if (!file.open(QFile::WriteOnly))
+ {
+ qCritical() << "Error opening" << file.fileName()
+ << "for reading:" << file.errorString();
+ return false;
+ }
+ file.write(OneSixVersionFormat::versionFileToJson(m_forge_json, true).toJson());
+ file.commit();
+
+ return true;
+}
+
+bool ForgeInstaller::addLegacy(OneSixInstance *to)
+{
+ if (!BaseInstaller::add(to))
+ {
+ return false;
+ }
+ auto entry = ENV.metacache()->resolveEntry("minecraftforge", m_forge_version->filename());
+ finalPath = FS::PathCombine(to->jarModsDir(), m_forge_version->filename());
+ if (!FS::ensureFilePathExists(finalPath))
+ {
+ return false;
+ }
+ if (!QFile::copy(entry->getFullPath(), finalPath))
+ {
+ return false;
+ }
+ QJsonObject obj;
+ obj.insert("order", 5);
+ {
+ QJsonArray jarmodsPlus;
+ {
+ QJsonObject libObj;
+ libObj.insert("name", m_forge_version->universal_filename);
+ jarmodsPlus.append(libObj);
+ }
+ obj.insert("+jarMods", jarmodsPlus);
+ }
+
+ obj.insert("name", QString("Forge"));
+ obj.insert("fileId", id());
+ obj.insert("version", m_forge_version->jobbuildver);
+ obj.insert("mcVersion", to->intendedVersionId());
+ if (g_VersionFilterData.fmlLibsMapping.contains(m_forge_version->mcver))
+ {
+ QJsonArray traitsPlus;
+ traitsPlus.append(QString("legacyFML"));
+ obj.insert("+traits", traitsPlus);
+ }
+ auto fullversion = to->getMinecraftProfile();
+ fullversion->remove("net.minecraftforge");
+
+ QFile file(filename(to->instanceRoot()));
+ if (!file.open(QFile::WriteOnly))
+ {
+ qCritical() << "Error opening" << file.fileName()
+ << "for reading:" << file.errorString();
+ return false;
+ }
+ file.write(QJsonDocument(obj).toJson());
+ file.close();
+ return true;
+}
+
+class ForgeInstallTask : public Task
+{
+ Q_OBJECT
+public:
+ ForgeInstallTask(ForgeInstaller *installer, OneSixInstance *instance,
+ BaseVersionPtr version, QObject *parent = 0)
+ : Task(parent), m_installer(installer), m_instance(instance), m_version(version)
+ {
+ }
+
+protected:
+ void executeTask() override
+ {
+ setStatus(tr("Installing Forge..."));
+ ForgeVersionPtr forgeVersion = std::dynamic_pointer_cast<ForgeVersion>(m_version);
+ if (!forgeVersion)
+ {
+ emitFailed(tr("Unknown error occured"));
+ return;
+ }
+ prepare(forgeVersion);
+ }
+ void prepare(ForgeVersionPtr forgeVersion)
+ {
+ auto entry = ENV.metacache()->resolveEntry("minecraftforge", forgeVersion->filename());
+ auto installFunction = [this, entry, forgeVersion]()
+ {
+ if (!install(entry, forgeVersion))
+ {
+ qCritical() << "Failure installing Forge";
+ emitFailed(tr("Failure to install Forge"));
+ }
+ else
+ {
+ reload();
+ }
+ };
+
+ /*
+ * HACK IF the local non-stale file is too small, mark is as stale
+ *
+ * This fixes some problems with bad files acquired because of unhandled HTTP redirects
+ * in old versions of MultiMC.
+ */
+ if (!entry->isStale())
+ {
+ QFileInfo localFile(entry->getFullPath());
+ if (localFile.size() <= 0x4000)
+ {
+ entry->setStale(true);
+ }
+ }
+
+ if (entry->isStale())
+ {
+ NetJob *fjob = new NetJob("Forge download");
+ fjob->addNetAction(CacheDownload::make(forgeVersion->url(), entry));
+ connect(fjob, &NetJob::progress, this, &Task::setProgress);
+ connect(fjob, &NetJob::status, this, &Task::setStatus);
+ connect(fjob, &NetJob::failed, [this](QString reason)
+ { emitFailed(tr("Failure to download Forge:\n%1").arg(reason)); });
+ connect(fjob, &NetJob::succeeded, installFunction);
+ fjob->start();
+ }
+ else
+ {
+ installFunction();
+ }
+ }
+ bool install(const std::shared_ptr<MetaEntry> &entry, const ForgeVersionPtr &forgeVersion)
+ {
+ if (forgeVersion->usesInstaller())
+ {
+ QString forgePath = entry->getFullPath();
+ m_installer->prepare(forgePath, forgeVersion->universal_url);
+ return m_installer->add(m_instance);
+ }
+ else
+ return m_installer->addLegacy(m_instance);
+ }
+ void reload()
+ {
+ try
+ {
+ m_instance->reloadProfile();
+ emitSucceeded();
+ }
+ catch (Exception &e)
+ {
+ emitFailed(e.cause());
+ }
+ catch (...)
+ {
+ emitFailed(tr("Failed to load the version description file for reasons unknown."));
+ }
+ }
+
+private:
+ ForgeInstaller *m_installer;
+ OneSixInstance *m_instance;
+ BaseVersionPtr m_version;
+};
+
+Task *ForgeInstaller::createInstallTask(OneSixInstance *instance,
+ BaseVersionPtr version, QObject *parent)
+{
+ if (!version)
+ {
+ return nullptr;
+ }
+ m_forge_version = std::dynamic_pointer_cast<ForgeVersion>(version);
+ return new ForgeInstallTask(this, instance, version, parent);
+}
+
+#include "ForgeInstaller.moc"