aboutsummaryrefslogtreecommitdiff
path: root/logic/minecraft/forge
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2016-02-27 19:58:40 +0100
committerPetr Mrázek <peterix@gmail.com>2016-02-27 19:58:40 +0100
commit17ad1e64f824fba6d8f153191effdb2af7d387c8 (patch)
tree3c0996cab5cff0dcf3807afe08df66dda3a50ec4 /logic/minecraft/forge
parent71e4b147ec3b8c5f3e2a004920ffbc0a3b6e18c8 (diff)
downloadPrismLauncher-17ad1e64f824fba6d8f153191effdb2af7d387c8.tar.gz
PrismLauncher-17ad1e64f824fba6d8f153191effdb2af7d387c8.tar.bz2
PrismLauncher-17ad1e64f824fba6d8f153191effdb2af7d387c8.zip
NOISSUE move files into paths that make more sense
Diffstat (limited to 'logic/minecraft/forge')
-rw-r--r--logic/minecraft/forge/ForgeInstaller.cpp442
-rw-r--r--logic/minecraft/forge/ForgeInstaller.h52
-rw-r--r--logic/minecraft/forge/ForgeMirror.h10
-rw-r--r--logic/minecraft/forge/ForgeMirrors.cpp118
-rw-r--r--logic/minecraft/forge/ForgeMirrors.h61
-rw-r--r--logic/minecraft/forge/ForgeVersion.cpp55
-rw-r--r--logic/minecraft/forge/ForgeVersion.h42
-rw-r--r--logic/minecraft/forge/ForgeVersionList.cpp450
-rw-r--r--logic/minecraft/forge/ForgeVersionList.h90
-rw-r--r--logic/minecraft/forge/ForgeXzDownload.cpp389
-rw-r--r--logic/minecraft/forge/ForgeXzDownload.h66
-rw-r--r--logic/minecraft/forge/LegacyForge.cpp56
-rw-r--r--logic/minecraft/forge/LegacyForge.h25
13 files changed, 1856 insertions, 0 deletions
diff --git a/logic/minecraft/forge/ForgeInstaller.cpp b/logic/minecraft/forge/ForgeInstaller.cpp
new file mode 100644
index 00000000..a9283e5d
--- /dev/null
+++ b/logic/minecraft/forge/ForgeInstaller.cpp
@@ -0,0 +1,442 @@
+/* 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/VersionFilterData.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)
+{
+ std::shared_ptr<MinecraftProfile> 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;
+
+ // read the forge version info
+ {
+ newVersion = MinecraftProfile::fromJson(versionInfoVal.toObject());
+ if (!newVersion)
+ return;
+ }
+
+ QJsonObject installObj = installVal.toObject();
+ QString libraryName = installObj.value("path").toString();
+ internalPath = installObj.value("filePath").toString();
+ m_forgeVersionString = installObj.value("version").toString().remove("Forge").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->stale = false;
+ cacheentry->md5sum = md5sum.result().toHex().constData();
+ ENV.metacache()->updateEntry(cacheentry);
+ }
+ file.close();
+
+ m_forge_json = newVersion;
+ m_forge_json->id = installObj.value("minecraft").toString();
+}
+
+bool ForgeInstaller::add(OneSixInstance *to)
+{
+ if (!BaseInstaller::add(to))
+ {
+ return false;
+ }
+
+ QJsonObject obj;
+ obj.insert("order", 5);
+
+ if (!m_forge_json)
+ return false;
+ int sliding_insert_window = 0;
+ {
+ QJsonArray librariesPlus;
+ // A blacklist
+ QSet<QString> blacklist{"authlib", "realms"};
+ //
+ QList<QString> xzlist{"org.scala-lang", "com.typesafe"};
+ // for each library in the version we are adding (except for the blacklisted)
+ for (auto lib : m_forge_json->libraries)
+ {
+ QString libName = lib->artifactId();
+ QString rawName = lib->rawName();
+
+ // ignore lwjgl libraries.
+ if (g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix()))
+ continue;
+ // ignore other blacklisted (realms, authlib)
+ if (blacklist.contains(libName))
+ continue;
+
+ // WARNING: This could actually break.
+ // if this is the actual forge lib, set an absolute url for the download
+ if (m_forge_version->type == ForgeVersion::Gradle)
+ {
+ if (libName == "forge")
+ {
+ lib->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));
+ lib->setRawName(spec);
+ }
+ }
+ else
+ {
+ if (libName.contains("minecraftforge"))
+ {
+ lib->setAbsoluteUrl(m_universal_url);
+ }
+ }
+
+ // WARNING: This could actually break.
+ // mark bad libraries based on the xzlist above
+ for (auto entry : xzlist)
+ {
+ qDebug() << "Testing " << rawName << " : " << entry;
+ if (rawName.startsWith(entry))
+ {
+ lib->setHint("forge-pack-xz");
+ break;
+ }
+ }
+
+ QJsonObject libObj = lib->toJson();
+
+ bool found = false;
+ bool equals = false;
+ // find an entry that matches this one
+ for (auto tolib : to->getMinecraftProfile()->vanillaLibraries)
+ {
+ if (tolib->artifactId() != libName)
+ continue;
+ found = true;
+ if (tolib->toJson() == libObj)
+ {
+ equals = true;
+ }
+ // replace lib
+ libObj.insert("insert", QString("replace"));
+ break;
+ }
+ if (equals)
+ {
+ continue;
+ }
+ if (!found)
+ {
+ // add lib
+ libObj.insert("insert", QString("prepend"));
+ if (lib->artifactId() == "minecraftforge" || lib->artifactId() == "forge")
+ {
+ libObj.insert("MMC-depend", QString("hard"));
+ }
+ sliding_insert_window++;
+ }
+ librariesPlus.prepend(libObj);
+ }
+ obj.insert("+libraries", librariesPlus);
+ obj.insert("mainClass", m_forge_json->mainClass);
+ 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 (!args.isEmpty() && args != to->getMinecraftProfile()->vanillaMinecraftArguments)
+ {
+ obj.insert("minecraftArguments", args);
+ }
+ if (!tweakers.isEmpty())
+ {
+ obj.insert("+tweakers", QJsonArray::fromStringList(tweakers));
+ }
+ if (!m_forge_json->processArguments.isEmpty() &&
+ m_forge_json->processArguments != to->getMinecraftProfile()->vanillaProcessArguments)
+ {
+ obj.insert("processArguments", m_forge_json->processArguments);
+ }
+ }
+
+ obj.insert("name", QString("Forge"));
+ obj.insert("fileId", id());
+ obj.insert("version", m_forgeVersionString);
+ obj.insert("mcVersion", to->intendedVersionId());
+
+ 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;
+}
+
+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->stale)
+ {
+ QFileInfo localFile(entry->getFullPath());
+ if (localFile.size() <= 0x4000)
+ {
+ entry->stale = true;
+ }
+ }
+
+ if (entry->stale)
+ {
+ 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"
diff --git a/logic/minecraft/forge/ForgeInstaller.h b/logic/minecraft/forge/ForgeInstaller.h
new file mode 100644
index 00000000..0de762b6
--- /dev/null
+++ b/logic/minecraft/forge/ForgeInstaller.h
@@ -0,0 +1,52 @@
+/* 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.
+ */
+
+#pragma once
+
+#include "BaseInstaller.h"
+
+#include <QString>
+#include <memory>
+
+#include "multimc_logic_export.h"
+
+class MinecraftProfile;
+class ForgeInstallTask;
+struct ForgeVersion;
+
+class MULTIMC_LOGIC_EXPORT ForgeInstaller : public BaseInstaller
+{
+ friend class ForgeInstallTask;
+public:
+ ForgeInstaller();
+ virtual ~ForgeInstaller(){}
+ virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) override;
+ virtual QString id() const override { return "net.minecraftforge"; }
+
+protected:
+ void prepare(const QString &filename, const QString &universalUrl);
+ bool add(OneSixInstance *to) override;
+ bool addLegacy(OneSixInstance *to);
+
+private:
+ // the parsed version json, read from the installer
+ std::shared_ptr<MinecraftProfile> m_forge_json;
+ // the actual forge version
+ std::shared_ptr<ForgeVersion> m_forge_version;
+ QString internalPath;
+ QString finalPath;
+ QString m_forgeVersionString;
+ QString m_universal_url;
+};
diff --git a/logic/minecraft/forge/ForgeMirror.h b/logic/minecraft/forge/ForgeMirror.h
new file mode 100644
index 00000000..2518dffe
--- /dev/null
+++ b/logic/minecraft/forge/ForgeMirror.h
@@ -0,0 +1,10 @@
+#pragma once
+#include <QString>
+
+struct ForgeMirror
+{
+ QString name;
+ QString logo_url;
+ QString website_url;
+ QString mirror_url;
+}; \ No newline at end of file
diff --git a/logic/minecraft/forge/ForgeMirrors.cpp b/logic/minecraft/forge/ForgeMirrors.cpp
new file mode 100644
index 00000000..a2fc2c62
--- /dev/null
+++ b/logic/minecraft/forge/ForgeMirrors.cpp
@@ -0,0 +1,118 @@
+#include "Env.h"
+#include "ForgeMirrors.h"
+#include <QDebug>
+#include <algorithm>
+#include <random>
+
+ForgeMirrors::ForgeMirrors(QList<ForgeXzDownloadPtr> &libs, NetJobPtr parent_job,
+ QString mirrorlist)
+{
+ m_libs = libs;
+ m_parent_job = parent_job;
+ m_url = QUrl(mirrorlist);
+ m_status = Job_NotStarted;
+}
+
+void ForgeMirrors::start()
+{
+ qDebug() << "Downloading " << m_url.toString();
+ QNetworkRequest request(m_url);
+ request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
+ auto worker = ENV.qnam();
+ QNetworkReply *rep = worker->get(request);
+
+ m_reply.reset(rep);
+ connect(rep, SIGNAL(downloadProgress(qint64, qint64)),
+ SLOT(downloadProgress(qint64, qint64)));
+ connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
+ connect(rep, SIGNAL(error(QNetworkReply::NetworkError)),
+ SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead()));
+}
+
+void ForgeMirrors::downloadError(QNetworkReply::NetworkError error)
+{
+ // error happened during download.
+ qCritical() << "Error getting URL:" << m_url.toString().toLocal8Bit()
+ << "Network error: " << error;
+ m_status = Job_Failed;
+}
+
+void ForgeMirrors::downloadFinished()
+{
+ // if the download succeeded
+ if (m_status != Job_Failed)
+ {
+ // nothing went wrong... ?
+ parseMirrorList();
+ return;
+ }
+ // else the download failed, we use a fixed list
+ else
+ {
+ m_status = Job_Finished;
+ m_reply.reset();
+ deferToFixedList();
+ return;
+ }
+}
+
+void ForgeMirrors::deferToFixedList()
+{
+ m_mirrors.clear();
+ m_mirrors.append(
+ {"Minecraft Forge", "http://files.minecraftforge.net/forge_logo.png",
+ "http://files.minecraftforge.net/", "http://files.minecraftforge.net/maven/"});
+ m_mirrors.append({"Creeper Host",
+ "http://files.minecraftforge.net/forge_logo.png",
+ "https://www.creeperhost.net/link.php?id=1",
+ "http://new.creeperrepo.net/forge/maven/"});
+ injectDownloads();
+ emit succeeded(m_index_within_job);
+}
+
+void ForgeMirrors::parseMirrorList()
+{
+ m_status = Job_Finished;
+ auto data = m_reply->readAll();
+ m_reply.reset();
+ auto dataLines = data.split('\n');
+ for(auto line: dataLines)
+ {
+ auto elements = line.split('!');
+ if (elements.size() == 4)
+ {
+ m_mirrors.append({elements[0],elements[1],elements[2],elements[3]});
+ }
+ }
+ if(!m_mirrors.size())
+ deferToFixedList();
+ injectDownloads();
+ emit succeeded(m_index_within_job);
+}
+
+void ForgeMirrors::injectDownloads()
+{
+ // shuffle the mirrors randomly
+ std::random_device rd;
+ std::mt19937 rng(rd());
+ std::shuffle(m_mirrors.begin(), m_mirrors.end(), rng);
+
+ // tell parent to download the libs
+ for(auto lib: m_libs)
+ {
+ lib->setMirrors(m_mirrors);
+ m_parent_job->addNetAction(lib);
+ }
+}
+
+void ForgeMirrors::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+ m_total_progress = bytesTotal;
+ m_progress = bytesReceived;
+ emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
+}
+
+void ForgeMirrors::downloadReadyRead()
+{
+}
diff --git a/logic/minecraft/forge/ForgeMirrors.h b/logic/minecraft/forge/ForgeMirrors.h
new file mode 100644
index 00000000..0312829b
--- /dev/null
+++ b/logic/minecraft/forge/ForgeMirrors.h
@@ -0,0 +1,61 @@
+/* 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.
+ */
+
+#pragma once
+
+#include "ForgeXzDownload.h"
+
+#include "net/NetAction.h"
+#include "net/HttpMetaCache.h"
+#include "net/NetJob.h"
+
+#include <QFile>
+#include <QTemporaryFile>
+
+typedef std::shared_ptr<class ForgeMirrors> ForgeMirrorsPtr;
+
+class ForgeMirrors : public NetAction
+{
+ Q_OBJECT
+public:
+ QList<ForgeXzDownloadPtr> m_libs;
+ NetJobPtr m_parent_job;
+ QList<ForgeMirror> m_mirrors;
+
+public:
+ explicit ForgeMirrors(QList<ForgeXzDownloadPtr> &libs, NetJobPtr parent_job,
+ QString mirrorlist);
+ static ForgeMirrorsPtr make(QList<ForgeXzDownloadPtr> &libs, NetJobPtr parent_job,
+ QString mirrorlist)
+ {
+ return ForgeMirrorsPtr(new ForgeMirrors(libs, parent_job, mirrorlist));
+ }
+ virtual ~ForgeMirrors(){};
+protected
+slots:
+ virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+ virtual void downloadError(QNetworkReply::NetworkError error);
+ virtual void downloadFinished();
+ virtual void downloadReadyRead();
+
+private:
+ void parseMirrorList();
+ void deferToFixedList();
+ void injectDownloads();
+
+public
+slots:
+ virtual void start();
+};
diff --git a/logic/minecraft/forge/ForgeVersion.cpp b/logic/minecraft/forge/ForgeVersion.cpp
new file mode 100644
index 00000000..b859a28c
--- /dev/null
+++ b/logic/minecraft/forge/ForgeVersion.cpp
@@ -0,0 +1,55 @@
+#include "ForgeVersion.h"
+#include "minecraft/VersionFilterData.h"
+#include <QObject>
+
+QString ForgeVersion::name()
+{
+ return "Forge " + jobbuildver;
+}
+
+QString ForgeVersion::descriptor()
+{
+ return universal_filename;
+}
+
+QString ForgeVersion::typeString() const
+{
+ if (is_recommended)
+ return QObject::tr("Recommended");
+ return QString();
+}
+
+bool ForgeVersion::operator<(BaseVersion &a)
+{
+ ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a);
+ if (!pa)
+ return true;
+ return m_buildnr < pa->m_buildnr;
+}
+
+bool ForgeVersion::operator>(BaseVersion &a)
+{
+ ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a);
+ if (!pa)
+ return false;
+ return m_buildnr > pa->m_buildnr;
+}
+
+bool ForgeVersion::usesInstaller()
+{
+ if(installer_url.isEmpty())
+ return false;
+ if(g_VersionFilterData.forgeInstallerBlacklist.contains(mcver))
+ return false;
+ return true;
+}
+
+QString ForgeVersion::filename()
+{
+ return usesInstaller() ? installer_filename : universal_filename;
+}
+
+QString ForgeVersion::url()
+{
+ return usesInstaller() ? installer_url : universal_url;
+}
diff --git a/logic/minecraft/forge/ForgeVersion.h b/logic/minecraft/forge/ForgeVersion.h
new file mode 100644
index 00000000..e77d32f1
--- /dev/null
+++ b/logic/minecraft/forge/ForgeVersion.h
@@ -0,0 +1,42 @@
+#pragma once
+#include <QString>
+#include <memory>
+#include "BaseVersion.h"
+
+struct ForgeVersion;
+typedef std::shared_ptr<ForgeVersion> ForgeVersionPtr;
+
+struct ForgeVersion : public BaseVersion
+{
+ virtual QString descriptor() override;
+ virtual QString name() override;
+ virtual QString typeString() const override;
+ virtual bool operator<(BaseVersion &a) override;
+ virtual bool operator>(BaseVersion &a) override;
+
+ QString filename();
+ QString url();
+
+ enum
+ {
+ Invalid,
+ Legacy,
+ Gradle
+ } type = Invalid;
+
+ bool usesInstaller();
+
+ int m_buildnr = 0;
+ QString branch;
+ QString universal_url;
+ QString changelog_url;
+ QString installer_url;
+ QString jobbuildver;
+ QString mcver;
+ QString mcver_sane;
+ QString universal_filename;
+ QString installer_filename;
+ bool is_recommended = false;
+};
+
+Q_DECLARE_METATYPE(ForgeVersionPtr)
diff --git a/logic/minecraft/forge/ForgeVersionList.cpp b/logic/minecraft/forge/ForgeVersionList.cpp
new file mode 100644
index 00000000..9b418310
--- /dev/null
+++ b/logic/minecraft/forge/ForgeVersionList.cpp
@@ -0,0 +1,450 @@
+/* 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 "ForgeVersionList.h"
+#include "ForgeVersion.h"
+
+#include "net/NetJob.h"
+#include "net/URLConstants.h"
+#include "Env.h"
+
+#include <QtNetwork>
+#include <QtXml>
+#include <QRegExp>
+
+#include <QDebug>
+
+ForgeVersionList::ForgeVersionList(QObject *parent) : BaseVersionList(parent)
+{
+}
+
+Task *ForgeVersionList::getLoadTask()
+{
+ return new ForgeListLoadTask(this);
+}
+
+bool ForgeVersionList::isLoaded()
+{
+ return m_loaded;
+}
+
+const BaseVersionPtr ForgeVersionList::at(int i) const
+{
+ return m_vlist.at(i);
+}
+
+int ForgeVersionList::count() const
+{
+ return m_vlist.count();
+}
+
+int ForgeVersionList::columnCount(const QModelIndex &parent) const
+{
+ return 1;
+}
+
+QVariant ForgeVersionList::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (index.row() > count())
+ return QVariant();
+
+ auto version = std::dynamic_pointer_cast<ForgeVersion>(m_vlist[index.row()]);
+ switch (role)
+ {
+ case VersionPointerRole:
+ return qVariantFromValue(m_vlist[index.row()]);
+
+ case VersionRole:
+ return version->name();
+
+ case VersionIdRole:
+ return version->descriptor();
+
+ case ParentGameVersionRole:
+ return version->mcver_sane;
+
+ case RecommendedRole:
+ return version->is_recommended;
+
+ case BranchRole:
+ return version->branch;
+
+ default:
+ return QVariant();
+ }
+}
+
+QList<BaseVersionList::ModelRoles> ForgeVersionList::providesRoles()
+{
+ return {VersionPointerRole, VersionRole, VersionIdRole, ParentGameVersionRole, RecommendedRole, BranchRole};
+}
+
+BaseVersionPtr ForgeVersionList::getLatestStable() const
+{
+ return BaseVersionPtr();
+}
+
+void ForgeVersionList::updateListData(QList<BaseVersionPtr> versions)
+{
+ beginResetModel();
+ m_vlist = versions;
+ m_loaded = true;
+ endResetModel();
+ // NOW SORT!!
+ // sort();
+}
+
+void ForgeVersionList::sortVersions()
+{
+ // NO-OP for now
+}
+
+ForgeListLoadTask::ForgeListLoadTask(ForgeVersionList *vlist) : Task()
+{
+ m_list = vlist;
+}
+
+void ForgeListLoadTask::executeTask()
+{
+ setStatus(tr("Fetching Forge version lists..."));
+ auto job = new NetJob("Version index");
+ // we do not care if the version is stale or not.
+ auto forgeListEntry = ENV.metacache()->resolveEntry("minecraftforge", "list.json");
+ auto gradleForgeListEntry = ENV.metacache()->resolveEntry("minecraftforge", "json");
+
+ // verify by poking the server.
+ forgeListEntry->stale = true;
+ gradleForgeListEntry->stale = true;
+
+ job->addNetAction(listDownload = CacheDownload::make(QUrl(URLConstants::FORGE_LEGACY_URL),
+ forgeListEntry));
+ job->addNetAction(gradleListDownload = CacheDownload::make(
+ QUrl(URLConstants::FORGE_GRADLE_URL), gradleForgeListEntry));
+
+ connect(listDownload.get(), SIGNAL(failed(int)), SLOT(listFailed()));
+ connect(gradleListDownload.get(), SIGNAL(failed(int)), SLOT(gradleListFailed()));
+
+ listJob.reset(job);
+ connect(listJob.get(), SIGNAL(succeeded()), SLOT(listDownloaded()));
+ connect(listJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
+ listJob->start();
+}
+
+bool ForgeListLoadTask::abort()
+{
+ return listJob->abort();
+}
+
+bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out)
+{
+ QByteArray data;
+ {
+ auto dlJob = listDownload;
+ auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->getTargetFilepath();
+ QFile listFile(filename);
+ if (!listFile.open(QIODevice::ReadOnly))
+ {
+ return false;
+ }
+ data = listFile.readAll();
+ dlJob.reset();
+ }
+
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
+
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ emitFailed("Error parsing version list JSON:" + jsonError.errorString());
+ return false;
+ }
+
+ if (!jsonDoc.isObject())
+ {
+ emitFailed("Error parsing version list JSON: JSON root is not an object");
+ return false;
+ }
+
+ QJsonObject root = jsonDoc.object();
+
+ // Now, get the array of versions.
+ if (!root.value("builds").isArray())
+ {
+ emitFailed(
+ "Error parsing version list JSON: version list object is missing 'builds' array");
+ return false;
+ }
+ QJsonArray builds = root.value("builds").toArray();
+
+ for (int i = 0; i < builds.count(); i++)
+ {
+ // Load the version info.
+ if (!builds[i].isObject())
+ {
+ // FIXME: log this somewhere
+ continue;
+ }
+ QJsonObject obj = builds[i].toObject();
+ int build_nr = obj.value("build").toDouble(0);
+ if (!build_nr)
+ continue;
+ QJsonArray files = obj.value("files").toArray();
+ QString url, jobbuildver, mcver, buildtype, universal_filename;
+ QString changelog_url, installer_url;
+ QString installer_filename;
+ bool valid = false;
+ for (int j = 0; j < files.count(); j++)
+ {
+ if (!files[j].isObject())
+ {
+ continue;
+ }
+ QJsonObject file = files[j].toObject();
+ buildtype = file.value("buildtype").toString();
+ if ((buildtype == "client" || buildtype == "universal") && !valid)
+ {
+ mcver = file.value("mcver").toString();
+ url = file.value("url").toString();
+ jobbuildver = file.value("jobbuildver").toString();
+ int lastSlash = url.lastIndexOf('/');
+ universal_filename = url.mid(lastSlash + 1);
+ valid = true;
+ }
+ else if (buildtype == "changelog")
+