/* 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 "minecraft/MinecraftProfile.h" #include "minecraft/OneSixLibrary.h" #include "net/HttpMetaCache.h" #include "tasks/Task.h" #include "minecraft/OneSixInstance.h" #include "forge/ForgeVersionList.h" #include "minecraft/VersionFilterData.h" #include "Env.h" #include "Exception.h" #include #include #include #include #include #include #include #include #include #include ForgeInstaller::ForgeInstaller() : BaseInstaller() { } void ForgeInstaller::prepare(const QString &filename, const QString &universalUrl) { std::shared_ptr 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 OneSixLibrary lib(libraryName); auto cacheentry = ENV.metacache()->resolveEntry("libraries", lib.storageSuffix()); finalPath = "libraries/" + lib.storageSuffix(); 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 blacklist{"authlib", "realms"}; // QList 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(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 &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(version); return new ForgeInstallTask(this, instance, version, parent); } #include "ForgeInstaller.moc"