summaryrefslogtreecommitdiffstats
path: root/logic/minecraft
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2015-02-04 21:10:10 +0100
committerPetr Mrázek <peterix@gmail.com>2015-04-12 20:57:17 +0200
commit141e0a02a0a0c4bbc4cc2e900560db5048366104 (patch)
treed61b0ac7fedc656d2906f084b9a384b8047e8617 /logic/minecraft
parent473971b6e7a79ed38fa68dffacd207fda874fdc0 (diff)
downloadMultiMC-141e0a02a0a0c4bbc4cc2e900560db5048366104.tar
MultiMC-141e0a02a0a0c4bbc4cc2e900560db5048366104.tar.gz
MultiMC-141e0a02a0a0c4bbc4cc2e900560db5048366104.tar.lz
MultiMC-141e0a02a0a0c4bbc4cc2e900560db5048366104.tar.xz
MultiMC-141e0a02a0a0c4bbc4cc2e900560db5048366104.zip
SCRATCH move things to the right places
Diffstat (limited to 'logic/minecraft')
-rw-r--r--logic/minecraft/AssetsUtils.cpp217
-rw-r--r--logic/minecraft/AssetsUtils.h39
-rw-r--r--logic/minecraft/JarUtils.cpp158
-rw-r--r--logic/minecraft/JarUtils.h18
-rw-r--r--logic/minecraft/LegacyInstance.cpp346
-rw-r--r--logic/minecraft/LegacyInstance.h127
-rw-r--r--logic/minecraft/LegacyUpdate.cpp467
-rw-r--r--logic/minecraft/LegacyUpdate.h72
-rw-r--r--logic/minecraft/LwjglVersionList.cpp189
-rw-r--r--logic/minecraft/LwjglVersionList.h154
-rw-r--r--logic/minecraft/MinecraftInstance.h3
-rw-r--r--logic/minecraft/MinecraftProfile.cpp1
-rw-r--r--logic/minecraft/Mod.cpp377
-rw-r--r--logic/minecraft/Mod.h130
-rw-r--r--logic/minecraft/ModList.cpp610
-rw-r--r--logic/minecraft/ModList.h158
-rw-r--r--logic/minecraft/OneSixInstance.cpp472
-rw-r--r--logic/minecraft/OneSixInstance.h109
-rw-r--r--logic/minecraft/OneSixProfileStrategy.cpp2
-rw-r--r--logic/minecraft/OneSixUpdate.cpp445
-rw-r--r--logic/minecraft/OneSixUpdate.h68
-rw-r--r--logic/minecraft/SkinUtils.cpp47
-rw-r--r--logic/minecraft/SkinUtils.h23
-rw-r--r--logic/minecraft/VersionBuilder.cpp2
24 files changed, 4231 insertions, 3 deletions
diff --git a/logic/minecraft/AssetsUtils.cpp b/logic/minecraft/AssetsUtils.cpp
new file mode 100644
index 00000000..9f33b1bd
--- /dev/null
+++ b/logic/minecraft/AssetsUtils.cpp
@@ -0,0 +1,217 @@
+/* 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 <QDir>
+#include <QDirIterator>
+#include <QCryptographicHash>
+#include <QJsonParseError>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QVariant>
+#include <QDebug>
+
+#include "AssetsUtils.h"
+#include <pathutils.h>
+
+namespace AssetsUtils
+{
+int findLegacyAssets()
+{
+ QDir assets_dir("assets");
+ if (!assets_dir.exists())
+ return 0;
+ assets_dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
+ int base_length = assets_dir.path().length();
+
+ QList<QString> blacklist = {"indexes", "objects", "virtual"};
+
+ QDirIterator iterator(assets_dir, QDirIterator::Subdirectories);
+ int found = 0;
+ while (iterator.hasNext())
+ {
+ QString currentDir = iterator.next();
+ currentDir = currentDir.remove(0, base_length + 1);
+
+ bool ignore = false;
+ for (QString blacklisted : blacklist)
+ {
+ if (currentDir.startsWith(blacklisted))
+ ignore = true;
+ }
+
+ if (!iterator.fileInfo().isDir() && !ignore)
+ {
+ found++;
+ }
+ }
+
+ return found;
+}
+
+/*
+ * Returns true on success, with index populated
+ * index is undefined otherwise
+ */
+bool loadAssetsIndexJson(QString path, AssetsIndex *index)
+{
+ /*
+ {
+ "objects": {
+ "icons/icon_16x16.png": {
+ "hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a",
+ "size": 3665
+ },
+ ...
+ }
+ }
+ }
+ */
+
+ QFile file(path);
+
+ // Try to open the file and fail if we can't.
+ // TODO: We should probably report this error to the user.
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ qCritical() << "Failed to read assets index file" << path;
+ return false;
+ }
+
+ // Read the file and close it.
+ QByteArray jsonData = file.readAll();
+ file.close();
+
+ QJsonParseError parseError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
+
+ // Fail if the JSON is invalid.
+ if (parseError.error != QJsonParseError::NoError)
+ {
+ qCritical() << "Failed to parse assets index file:" << parseError.errorString()
+ << "at offset " << QString::number(parseError.offset);
+ return false;
+ }
+
+ // Make sure the root is an object.
+ if (!jsonDoc.isObject())
+ {
+ qCritical() << "Invalid assets index JSON: Root should be an array.";
+ return false;
+ }
+
+ QJsonObject root = jsonDoc.object();
+
+ QJsonValue isVirtual = root.value("virtual");
+ if (!isVirtual.isUndefined())
+ {
+ index->isVirtual = isVirtual.toBool(false);
+ }
+
+ QJsonValue objects = root.value("objects");
+ QVariantMap map = objects.toVariant().toMap();
+
+ for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
+ {
+ // qDebug() << iter.key();
+
+ QVariant variant = iter.value();
+ QVariantMap nested_objects = variant.toMap();
+
+ AssetObject object;
+
+ for (QVariantMap::const_iterator nested_iter = nested_objects.begin();
+ nested_iter != nested_objects.end(); ++nested_iter)
+ {
+ // qDebug() << nested_iter.key() << nested_iter.value().toString();
+ QString key = nested_iter.key();
+ QVariant value = nested_iter.value();
+
+ if (key == "hash")
+ {
+ object.hash = value.toString();
+ }
+ else if (key == "size")
+ {
+ object.size = value.toDouble();
+ }
+ }
+
+ index->objects.insert(iter.key(), object);
+ }
+
+ return true;
+}
+
+QDir reconstructAssets(QString assetsId)
+{
+ QDir assetsDir = QDir("assets/");
+ QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes"));
+ QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects"));
+ QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual"));
+
+ QString indexPath = PathCombine(indexDir.path(), assetsId + ".json");
+ QFile indexFile(indexPath);
+ QDir virtualRoot(PathCombine(virtualDir.path(), assetsId));
+
+ if (!indexFile.exists())
+ {
+ qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets";
+ return virtualRoot;
+ }
+
+ qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path()
+ << objectDir.path() << virtualDir.path() << virtualRoot.path();
+
+ AssetsIndex index;
+ bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index);
+
+ if (loadAssetsIndex && index.isVirtual)
+ {
+ qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path();
+
+ for (QString map : index.objects.keys())
+ {
+ AssetObject asset_object = index.objects.value(map);
+ QString target_path = PathCombine(virtualRoot.path(), map);
+ QFile target(target_path);
+
+ QString tlk = asset_object.hash.left(2);
+
+ QString original_path =
+ PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash);
+ QFile original(original_path);
+ if (!original.exists())
+ continue;
+ if (!target.exists())
+ {
+ QFileInfo info(target_path);
+ QDir target_dir = info.dir();
+ // qDebug() << target_dir;
+ if (!target_dir.exists())
+ QDir("").mkpath(target_dir.path());
+
+ bool couldCopy = original.copy(target_path);
+ qDebug() << " Copying" << original_path << "to" << target_path
+ << QString::number(couldCopy); // << original.errorString();
+ }
+ }
+
+ // TODO: Write last used time to virtualRoot/.lastused
+ }
+
+ return virtualRoot;
+}
+
+}
diff --git a/logic/minecraft/AssetsUtils.h b/logic/minecraft/AssetsUtils.h
new file mode 100644
index 00000000..ea12136d
--- /dev/null
+++ b/logic/minecraft/AssetsUtils.h
@@ -0,0 +1,39 @@
+/* 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 <QString>
+#include <QMap>
+
+struct AssetObject
+{
+ QString hash;
+ qint64 size;
+};
+
+struct AssetsIndex
+{
+ QMap<QString, AssetObject> objects;
+ bool isVirtual = false;
+};
+
+namespace AssetsUtils
+{
+bool loadAssetsIndexJson(QString file, AssetsIndex* index);
+int findLegacyAssets();
+/// Reconstruct a virtual assets folder for the given assets ID and return the folder
+QDir reconstructAssets(QString assetsId);
+}
diff --git a/logic/minecraft/JarUtils.cpp b/logic/minecraft/JarUtils.cpp
new file mode 100644
index 00000000..59326aba
--- /dev/null
+++ b/logic/minecraft/JarUtils.cpp
@@ -0,0 +1,158 @@
+#include "logic/minecraft/JarUtils.h"
+#include <quazip.h>
+#include <quazipfile.h>
+#include <JlCompress.h>
+#include <QDebug>
+
+namespace JarUtils {
+
+bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
+ std::function<bool(QString)> filter)
+{
+ QuaZip modZip(from.filePath());
+ modZip.open(QuaZip::mdUnzip);
+
+ QuaZipFile fileInsideMod(&modZip);
+ QuaZipFile zipOutFile(into);
+ for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
+ {
+ QString filename = modZip.getCurrentFileName();
+ if (!filter(filename))
+ {
+ qDebug() << "Skipping file " << filename << " from "
+ << from.fileName() << " - filtered";
+ continue;
+ }
+ if (contained.contains(filename))
+ {
+ qDebug() << "Skipping already contained file " << filename << " from "
+ << from.fileName();
+ continue;
+ }
+ contained.insert(filename);
+
+ if (!fileInsideMod.open(QIODevice::ReadOnly))
+ {
+ qCritical() << "Failed to open " << filename << " from " << from.fileName();
+ return false;
+ }
+
+ QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
+
+ if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
+ {
+ qCritical() << "Failed to open " << filename << " in the jar";
+ fileInsideMod.close();
+ return false;
+ }
+ if (!JlCompress::copyData(fileInsideMod, zipOutFile))
+ {
+ zipOutFile.close();
+ fileInsideMod.close();
+ qCritical() << "Failed to copy data of " << filename << " into the jar";
+ return false;
+ }
+ zipOutFile.close();
+ fileInsideMod.close();
+ }
+ return true;
+}
+
+bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods)
+{
+ QuaZip zipOut(targetJarPath);
+ if (!zipOut.open(QuaZip::mdCreate))
+ {
+ QFile::remove(targetJarPath);
+ qCritical() << "Failed to open the minecraft.jar for modding";
+ return false;
+ }
+ // Files already added to the jar.
+ // These files will be skipped.
+ QSet<QString> addedFiles;
+
+ // Modify the jar
+ QListIterator<Mod> i(mods);
+ i.toBack();
+ while (i.hasPrevious())
+ {
+ const Mod &mod = i.previous();
+ // do not merge disabled mods.
+ if (!mod.enabled())
+ continue;
+ if (mod.type() == Mod::MOD_ZIPFILE)
+ {
+ if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles, noFilter))
+ {
+ zipOut.close();
+ QFile::remove(targetJarPath);
+ qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
+ return false;
+ }
+ }
+ else if (mod.type() == Mod::MOD_SINGLEFILE)
+ {
+ auto filename = mod.filename();
+ if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(),
+ filename.fileName()))
+ {
+ zipOut.close();
+ QFile::remove(targetJarPath);
+ qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
+ return false;
+ }
+ addedFiles.insert(filename.fileName());
+ }
+ else if (mod.type() == Mod::MOD_FOLDER)
+ {
+ auto filename = mod.filename();
+ QString what_to_zip = filename.absoluteFilePath();
+ QDir dir(what_to_zip);
+ dir.cdUp();
+ QString parent_dir = dir.absolutePath();
+ if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, true, addedFiles))
+ {
+ zipOut.close();
+ QFile::remove(targetJarPath);
+ qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
+ return false;
+ }
+ qDebug() << "Adding folder " << filename.fileName() << " from "
+ << filename.absoluteFilePath();
+ }
+ }
+
+ if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, metaInfFilter))
+ {
+ zipOut.close();
+ QFile::remove(targetJarPath);
+ qCritical() << "Failed to insert minecraft.jar contents.";
+ return false;
+ }
+
+ // Recompress the jar
+ zipOut.close();
+ if (zipOut.getZipError() != 0)
+ {
+ QFile::remove(targetJarPath);
+ qCritical() << "Failed to finalize minecraft.jar!";
+ return false;
+ }
+ return true;
+}
+
+bool noFilter(QString)
+{
+ return true;
+}
+
+bool metaInfFilter(QString key)
+{
+ if(key.contains("META-INF"))
+ {
+ return false;
+ }
+ return true;
+}
+
+}
diff --git a/logic/minecraft/JarUtils.h b/logic/minecraft/JarUtils.h
new file mode 100644
index 00000000..2e8bd2a7
--- /dev/null
+++ b/logic/minecraft/JarUtils.h
@@ -0,0 +1,18 @@
+#pragma once
+#include <QString>
+#include <QFileInfo>
+#include <QSet>
+#include "Mod.h"
+#include <functional>
+
+class QuaZip;
+namespace JarUtils
+{
+ bool noFilter(QString);
+ bool metaInfFilter(QString key);
+
+ bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
+ std::function<bool(QString)> filter);
+
+ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
+}
diff --git a/logic/minecraft/LegacyInstance.cpp b/logic/minecraft/LegacyInstance.cpp
new file mode 100644
index 00000000..c0fe1513
--- /dev/null
+++ b/logic/minecraft/LegacyInstance.cpp
@@ -0,0 +1,346 @@
+/* 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 <QFileInfo>
+#include <QDir>
+#include <QImage>
+#include <logic/settings/Setting.h>
+#include <pathutils.h>
+#include <cmdutils.h>
+
+#include "LegacyInstance.h"
+
+#include "logic/minecraft/LegacyUpdate.h"
+#include "logic/icons/IconList.h"
+#include "logic/minecraft/MinecraftProcess.h"
+#include "gui/pages/LegacyUpgradePage.h"
+#include "gui/pages/ModFolderPage.h"
+#include "gui/pages/LegacyJarModPage.h"
+#include <gui/pages/TexturePackPage.h>
+#include <gui/pages/InstanceSettingsPage.h>
+#include <gui/pages/NotesPage.h>
+#include <gui/pages/ScreenshotsPage.h>
+
+LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
+ : MinecraftInstance(globalSettings, settings, rootDir)
+{
+ m_lwjglFolderSetting = globalSettings->getSetting("LWJGLDir");
+ settings->registerSetting("NeedsRebuild", true);
+ settings->registerSetting("ShouldUpdate", false);
+ settings->registerSetting("JarVersion", "Unknown");
+ settings->registerSetting("LwjglVersion", "2.9.0");
+ settings->registerSetting("IntendedJarVersion", "");
+ /*
+ * custom base jar has no default. it is determined in code... see the accessor methods for
+ *it
+ *
+ * for instances that DO NOT have the CustomBaseJar setting (legacy instances),
+ * [.]minecraft/bin/mcbackup.jar is the default base jar
+ */
+ settings->registerSetting("UseCustomBaseJar", true);
+ settings->registerSetting("CustomBaseJar", "");
+}
+
+QList<BasePage *> LegacyInstance::getPages()
+{
+ QList<BasePage *> values;
+ // FIXME: actually implement the legacy instance upgrade, then enable this.
+ //values.append(new LegacyUpgradePage(this));
+ values.append(new LegacyJarModPage(this));
+ values.append(new ModFolderPage(this, loaderModList(), "mods", "loadermods", tr("Loader mods"),
+ "Loader-mods"));
+ values.append(new ModFolderPage(this, coreModList(), "coremods", "coremods", tr("Core mods"),
+ "Loader-mods"));
+ values.append(new TexturePackPage(this));
+ values.append(new NotesPage(this));
+ values.append(new ScreenshotsPage(PathCombine(minecraftRoot(), "screenshots")));
+ values.append(new InstanceSettingsPage(this));
+ return values;
+}
+
+QString LegacyInstance::dialogTitle()
+{
+ return tr("Edit Instance (%1)").arg(name());
+}
+
+QString LegacyInstance::baseJar() const
+{
+ bool customJar = m_settings->get("UseCustomBaseJar").toBool();
+ if (customJar)
+ {
+ return customBaseJar();
+ }
+ else
+ return defaultBaseJar();
+}
+
+QString LegacyInstance::customBaseJar() const
+{
+ QString value = m_settings->get("CustomBaseJar").toString();
+ if (value.isNull() || value.isEmpty())
+ {
+ return defaultCustomBaseJar();
+ }
+ return value;
+}
+
+void LegacyInstance::setCustomBaseJar(QString val)
+{
+ if (val.isNull() || val.isEmpty() || val == defaultCustomBaseJar())
+ m_settings->reset("CustomBaseJar");
+ else
+ m_settings->set("CustomBaseJar", val);
+}
+
+void LegacyInstance::setShouldUseCustomBaseJar(bool val)
+{
+ m_settings->set("UseCustomBaseJar", val);
+}
+
+bool LegacyInstance::shouldUseCustomBaseJar() const
+{
+ return m_settings->get("UseCustomBaseJar").toBool();
+}
+
+
+std::shared_ptr<Task> LegacyInstance::doUpdate()
+{
+ // make sure the jar mods list is initialized by asking for it.
+ auto list = jarModList();
+ // create an update task
+ return std::shared_ptr<Task>(new LegacyUpdate(this, this));
+}
+
+BaseProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account)
+{
+ QString launchScript;
+ QIcon icon = ENV.icons()->getIcon(iconKey());
+ auto pixmap = icon.pixmap(128, 128);
+ pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG");
+
+ // create the launch script
+ {
+ // window size
+ QString windowParams;
+ if (settings().get("LaunchMaximized").toBool())
+ windowParams = "max";
+ else
+ windowParams = QString("%1x%2")
+ .arg(settings().get("MinecraftWinWidth").toInt())
+ .arg(settings().get("MinecraftWinHeight").toInt());
+
+ QString lwjgl = QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion())
+ .absolutePath();
+ launchScript += "userName " + account->player_name + "\n";
+ launchScript += "sessionId " + account->session + "\n";
+ launchScript += "windowTitle " + windowTitle() + "\n";
+ launchScript += "windowParams " + windowParams + "\n";
+ launchScript += "lwjgl " + lwjgl + "\n";
+ launchScript += "launcher legacy\n";
+ }
+ auto process = MinecraftProcess::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr()));
+ process->setLaunchScript(launchScript);
+ process->setWorkdir(minecraftRoot());
+ process->setLogin(account);
+ return process;
+}
+
+void LegacyInstance::cleanupAfterRun()
+{
+ // FIXME: delete the launcher and icons and whatnot.
+}
+
+std::shared_ptr<ModList> LegacyInstance::coreModList() const
+{
+ if (!core_mod_list)
+ {
+ core_mod_list.reset(new ModList(coreModsDir()));
+ }
+ core_mod_list->update();
+ return core_mod_list;
+}
+
+std::shared_ptr<ModList> LegacyInstance::jarModList() const
+{
+ if (!jar_mod_list)
+ {
+ auto list = new ModList(jarModsDir(), modListFile());
+ connect(list, SIGNAL(changed()), SLOT(jarModsChanged()));
+ jar_mod_list.reset(list);
+ }
+ jar_mod_list->update();
+ return jar_mod_list;
+}
+
+QList<Mod> LegacyInstance::getJarMods() const
+{
+ return jarModList()->allMods();
+}
+
+void LegacyInstance::jarModsChanged()
+{
+ qDebug() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt.";
+ setShouldRebuild(true);
+}
+
+std::shared_ptr<ModList> LegacyInstance::loaderModList() const
+{
+ if (!loader_mod_list)
+ {
+ loader_mod_list.reset(new ModList(loaderModsDir()));
+ }
+ loader_mod_list->update();
+ return loader_mod_list;
+}
+
+std::shared_ptr<ModList> LegacyInstance::texturePackList() const
+{
+ if (!texture_pack_list)
+ {
+ texture_pack_list.reset(new ModList(texturePacksDir()));
+ }
+ texture_pack_list->update();
+ return texture_pack_list;
+}
+
+QString LegacyInstance::jarModsDir() const
+{
+ return PathCombine(instanceRoot(), "instMods");
+}
+
+QString LegacyInstance::binDir() const
+{
+ return PathCombine(minecraftRoot(), "bin");
+}
+
+QString LegacyInstance::libDir() const
+{
+ return PathCombine(minecraftRoot(), "lib");
+}
+
+QString LegacyInstance::savesDir() const
+{
+ return PathCombine(minecraftRoot(), "saves");
+}
+
+QString LegacyInstance::loaderModsDir() const
+{
+ return PathCombine(minecraftRoot(), "mods");
+}
+
+QString LegacyInstance::coreModsDir() const
+{
+ return PathCombine(minecraftRoot(), "coremods");
+}
+
+QString LegacyInstance::resourceDir() const
+{
+ return PathCombine(minecraftRoot(), "resources");
+}
+QString LegacyInstance::texturePacksDir() const
+{
+ return PathCombine(minecraftRoot(), "texturepacks");
+}
+
+QString LegacyInstance::runnableJar() const
+{
+ return PathCombine(binDir(), "minecraft.jar");
+}
+
+QString LegacyInstance::modListFile() const
+{
+ return PathCombine(instanceRoot(), "modlist");
+}
+
+QString LegacyInstance::instanceConfigFolder() const
+{
+ return PathCombine(minecraftRoot(), "config");
+}
+
+bool LegacyInstance::shouldRebuild() const
+{
+ return m_settings->get("NeedsRebuild").toBool();
+}
+
+void LegacyInstance::setShouldRebuild(bool val)
+{
+ m_settings->set("NeedsRebuild", val);
+}
+
+QString LegacyInstance::currentVersionId() const
+{
+ return m_settings->get("JarVersion").toString();
+}
+
+QString LegacyInstance::lwjglVersion() const
+{
+ return m_settings->get("LwjglVersion").toString();
+}
+
+void LegacyInstance::setLWJGLVersion(QString val)
+{
+ m_settings->set("LwjglVersion", val);
+}
+
+QString LegacyInstance::intendedVersionId() const
+{
+ return m_settings->get("IntendedJarVersion").toString();
+}
+
+bool LegacyInstance::setIntendedVersionId(QString version)
+{
+ settings().set("IntendedJarVersion", version);
+ setShouldUpdate(true);
+ return true;
+}
+
+bool LegacyInstance::shouldUpdate() const
+{
+ QVariant var = settings().get("ShouldUpdate");
+ if (!var.isValid() || var.toBool() == false)
+ {
+ return intendedVersionId() != currentVersionId();
+ }
+ return true;
+}
+
+void LegacyInstance::setShouldUpdate(bool val)
+{
+ settings().set("ShouldUpdate", val);
+}
+
+QString LegacyInstance::defaultBaseJar() const
+{
+ return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar";
+}
+
+QString LegacyInstance::defaultCustomBaseJar() const
+{
+ return PathCombine(binDir(), "mcbackup.jar");
+}
+
+QString LegacyInstance::getStatusbarDescription()
+{
+ if (flags() & VersionBrokenFlag)
+ {
+ return tr("Legacy : %1 (broken)").arg(intendedVersionId());
+ }
+ return tr("Legacy : %1").arg(intendedVersionId());
+}
+
+QString LegacyInstance::lwjglFolder() const
+{
+ return m_lwjglFolderSetting->get().toString();
+}
diff --git a/logic/minecraft/LegacyInstance.h b/logic/minecraft/LegacyInstance.h
new file mode 100644
index 00000000..353718c1
--- /dev/null
+++ b/logic/minecraft/LegacyInstance.h
@@ -0,0 +1,127 @@
+/* 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 "logic/minecraft/MinecraftInstance.h"
+#include "gui/pages/BasePageProvider.h"
+
+class ModList;
+class Task;
+
+class LegacyInstance : public MinecraftInstance, public BasePageProvider
+{
+ Q_OBJECT
+public:
+
+ explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
+
+ virtual void init() {};
+
+ /// Path to the instance's minecraft.jar
+ QString runnableJar() const;
+
+ //! Path to the instance's modlist file.
+ QString modListFile() const;
+
+ ////// Edit Instance Dialog stuff //////
+ virtual QList<BasePage *> getPages();
+ virtual QString dialogTitle();
+
+ ////// Mod Lists //////
+ std::shared_ptr<ModList> jarModList() const ;
+ virtual QList< Mod > getJarMods() const override;
+ std::shared_ptr<ModList> coreModList() const;
+ std::shared_ptr<ModList> loaderModList() const;
+ std::shared_ptr<ModList> texturePackList() const override;
+
+ ////// Directories //////
+ QString libDir() const;
+ QString savesDir() const;
+ QString texturePacksDir() const;
+ QString jarModsDir() const;
+ QString binDir() const;
+ QString loaderModsDir() const;
+ QString coreModsDir() const;
+ QString resourceDir() const;
+ virtual QString instanceConfigFolder() const override;
+
+ /// Get the curent base jar of this instance. By default, it's the
+ /// versions/$version/$version.jar
+ QString baseJar() const;
+
+ /// the default base jar of this instance
+ QString defaultBaseJar() const;
+ /// the default custom base jar of this instance
+ QString defaultCustomBaseJar() const;
+
+ /*!
+ * Whether or not custom base jar is used
+ */
+ bool shouldUseCustomBaseJar() const;
+ void setShouldUseCustomBaseJar(bool val);
+
+ /*!
+ * The value of the custom base jar
+ */
+ QString customBaseJar() const;
+ void setCustomBaseJar(QString val);
+
+ /*!
+ * Whether or not the instance's minecraft.jar needs to be rebuilt.
+ * If this is true, when the instance launches, its jar mods will be
+ * re-added to a fresh minecraft.jar file.
+ */
+ bool shouldRebuild() const;
+ void setShouldRebuild(bool val);
+
+ virtual QString currentVersionId() const override;
+
+ //! The version of LWJGL that this instance uses.
+ QString lwjglVersion() const;
+
+ //! Where the lwjgl versions foor this instance can be found... HACK HACK HACK
+ QString lwjglFolder() const;
+
+ /// st the version of LWJGL libs this instance will use
+ void setLWJGLVersion(QString val);
+
+ virtual QString intendedVersionId() const override;
+ virtual bool setIntendedVersionId(QString version) override;
+
+ virtual QSet<QString> traits()
+ {
+ return {"legacy-instance", "texturepacks"};
+ };
+
+ virtual bool shouldUpdate() const override;
+ virtual void setShouldUpdate(bool val) override;
+ virtual std::shared_ptr<Task> doUpdate() override;
+
+ virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override;
+ virtual void cleanupAfterRun() override;
+
+ virtual QString getStatusbarDescription() override;
+
+protected:
+ mutable std::shared_ptr<ModList> jar_mod_list;
+ mutable std::shared_ptr<ModList> core_mod_list;
+ mutable std::shared_ptr<ModList> loader_mod_list;
+ mutable std::shared_ptr<ModList> texture_pack_list;
+ std::shared_ptr<Setting> m_lwjglFolderSetting;
+protected
+slots:
+ virtual void jarModsChanged();
+};
diff --git a/logic/minecraft/LegacyUpdate.cpp b/logic/minecraft/LegacyUpdate.cpp
new file mode 100644
index 00000000..d853536a
--- /dev/null
+++ b/logic/minecraft/LegacyUpdate.cpp
@@ -0,0 +1,467 @@
+/* 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 <QStringList>
+#include <pathutils.h>
+#include <quazip.h>
+#include <quazipfile.h>
+#include <JlCompress.h>
+#include <QDebug>
+
+#include "logic/Env.h"
+#include "logic/BaseInstance.h"
+#include "logic/net/URLConstants.h"
+
+#include "logic/minecraft/JarUtils.h"
+#include "logic/minecraft/LegacyUpdate.h"
+#include "logic/minecraft/LwjglVersionList.h"
+#include "logic/minecraft/MinecraftVersionList.h"
+#include "logic/minecraft/ModList.h"
+#include "logic/minecraft/LegacyInstance.h"
+
+LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
+{
+}
+
+void LegacyUpdate::executeTask()
+{
+ fmllibsStart();
+}
+
+void LegacyUpdate::fmllibsStart()
+{
+ // Get the mod list
+ LegacyInstance *inst = (LegacyInstance *)m_inst;
+ auto modList = inst->jarModList();
+
+ bool forge_present = false;
+
+ QString version = inst->intendedVersionId();
+ auto & fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
+ if (!fmlLibsMapping.contains(version))
+ {
+ lwjglStart();
+ return;
+ }
+
+ auto &libList = fmlLibsMapping[version];
+
+ // determine if we need some libs for FML or forge
+ setStatus(tr("Checking for FML libraries..."));
+ for (unsigned i = 0; i < modList->size(); i++)
+ {
+ auto &mod = modList->operator[](i);
+
+ // do not use disabled mods.
+ if (!mod.enabled())
+ continue;
+
+ if (mod.type() != Mod::MOD_ZIPFILE)
+ continue;
+
+ if (mod.mmc_id().contains("forge", Qt::CaseInsensitive))
+ {
+ forge_present = true;
+ break;
+ }
+ if (mod.mmc_id().contains("fml", Qt::CaseInsensitive))
+ {
+ forge_present = true;
+ break;
+ }
+ }
+ // we don't...
+ if (!forge_present)
+ {
+ lwjglStart();
+ return;
+ }
+
+ // now check the lib folder inside the instance for files.
+ for (auto &lib : libList)
+ {
+ QFileInfo libInfo(PathCombine(inst->libDir(), lib.filename));
+ if (libInfo.exists())
+ continue;
+ fmlLibsToProcess.append(lib);
+ }
+
+ // if everything is in place, there's nothing to do here...
+ if (fmlLibsToProcess.isEmpty())
+ {
+ lwjglStart();
+ return;
+ }
+
+ // download missing libs to our place
+ setStatus(tr("Dowloading FML libraries..."));
+ auto dljob = new NetJob("FML libraries");
+ auto metacache = ENV.metacache();
+ for (auto &lib : fmlLibsToProcess)
+ {
+ auto entry = metacache->resolveEntry("fmllibs", lib.filename);
+ QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename
+ : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename;
+ dljob->addNetAction(CacheDownload::make(QUrl(urlString), entry));
+ }
+
+ connect(dljob, SIGNAL(succeeded()), SLOT(fmllibsFinished()));
+ connect(dljob, SIGNAL(failed()), SLOT(fmllibsFailed()));
+ connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
+ legacyDownloadJob.reset(dljob);
+ legacyDownloadJob->start();
+}
+
+void LegacyUpdate::fmllibsFinished()
+{
+ legacyDownloadJob.reset();
+ if(!fmlLibsToProcess.isEmpty())
+ {
+ setStatus(tr("Copying FML libraries into the instance..."));
+ LegacyInstance *inst = (LegacyInstance *)m_inst;
+ auto metacache = ENV.metacache();
+ int index = 0;
+ for (auto &lib : fmlLibsToProcess)
+ {
+ progress(index, fmlLibsToProcess.size());
+ auto entry = metacache->resolveEntry("fmllibs", lib.filename);
+ auto path = PathCombine(inst->libDir(), lib.filename);
+ if(!ensureFilePathExists(path))
+ {
+ emitFailed(tr("Failed creating FML library folder inside the instance."));
+ return;
+ }
+ if (!QFile::copy(entry->getFullPath(), PathCombine(inst->libDir(), lib.filename)))
+ {
+ emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename));
+ return;
+ }
+ index++;
+ }
+ progress(index, fmlLibsToProcess.size());
+ }
+ lwjglStart();
+}
+
+void LegacyUpdate::fmllibsFailed()
+{
+ emitFailed("Game update failed: it was impossible to fetch the required FML libraries.");
+ return;
+}
+
+void LegacyUpdate::lwjglStart()
+{
+ LegacyInstance *inst = (LegacyInstance *)m_inst;
+
+ lwjglVersion = inst->lwjglVersion();
+ lwjglTargetPath = PathCombine(inst->lwjglFolder(), lwjglVersion);
+ lwjglNativesPath = PathCombine(lwjglTargetPath, "natives");
+
+ // if the 'done' file exists, we don't have to download this again
+ QFileInfo doneFile(PathCombine(lwjglTargetPath, "done"));
+ if (doneFile.exists())
+ {
+ jarStart();
+ return;
+ }
+
+ auto list = std::dynamic_pointer_cast<LWJGLVersionList>(ENV.getVersionList("org.lwjgl.legacy"));
+ if (!list->isLoaded())
+ {
+ emitFailed("Too soon! Let the LWJGL list load :)");
+ return;
+ }
+
+ setStatus(tr("Downloading new LWJGL..."));
+ auto version = std::dynamic_pointer_cast<LWJGLVersion>(list->findVersion(lwjglVersion));
+ if (!version)
+ {
+ emitFailed("Game update failed: the selected LWJGL version is invalid.");
+ return;
+ }
+
+ QString url = version->url();
+ QUrl realUrl(url);
+ QString hostname = realUrl.host();
+ auto worker = ENV.qnam();
+ QNetworkRequest req(realUrl);
+ req.setRawHeader("Host", hostname.toLatin1());
+ req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
+ QNetworkReply *rep = worker->get(req);
+
+ m_reply = std::shared_ptr<QNetworkReply>(rep);
+ connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
+ connect(worker.get(), SIGNAL(finished(QNetworkReply *)),
+ SLOT(lwjglFinished(QNetworkReply *)));
+}
+
+void LegacyUpdate::lwjglFinished(QNetworkReply *reply)
+{
+ if (m_reply.get() != reply)
+ {
+ return;
+ }
+ if (reply->error() != QNetworkReply::NoError)
+ {
+ emitFailed("Failed to download: " + reply->errorString() +
+ "\nSometimes you have to wait a bit if you download many LWJGL versions in "
+ "a row. YMMV");
+ return;
+ }
+ auto worker = ENV.qnam();
+ // Here i check if there is a cookie for me in the reply and extract it
+ QList<QNetworkCookie> cookies =
+ qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader));
+ if (cookies.count() != 0)
+ {
+ // you must tell which cookie goes with which url
+ worker->cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net"));
+ }
+
+ // here you can check for the 302 or whatever other header i need
+ QVariant newLoc = reply->header(QNetworkRequest::LocationHeader);
+ if (newLoc.isValid())
+ {
+ QString redirectedTo = reply->header(QNetworkRequest::LocationHeader).toString();
+ QUrl realUrl(redirectedTo);
+ QString hostname = realUrl.host();
+ QNetworkRequest req(redirectedTo);
+ req.setRawHeader("Host", hostname.toLatin1());
+ req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
+ QNetworkReply *rep = worker->get(req);
+ connect(rep, SIGNAL(downloadProgress(qint64, qint64)),
+ SIGNAL(progress(qint64, qint64)));
+ m_reply = std::shared_ptr<QNetworkReply>(rep);
+ return;
+ }
+ QFile saveMe("lwjgl.zip");
+ saveMe.open(QIODevice::WriteOnly);
+ saveMe.write(m_reply->readAll());
+ saveMe.close();
+ setStatus(tr("Installing new LWJGL..."));
+ extractLwjgl();
+ jarStart();
+}
+void LegacyUpdate::extractLwjgl()
+{
+ // make sure the directories are there
+
+ bool success = ensureFolderPathExists(lwjglNativesPath);
+
+ if (!success)
+ {
+ emitFailed("Failed to extract the lwjgl libs - error when creating required folders.");
+ return;
+ }
+
+ QuaZip zip("lwjgl.zip");
+ if (!zip.open(QuaZip::mdUnzip))
+ {
+ emitFailed("Failed to extract the lwjgl libs - not a valid archive.");
+ return;
+ }
+
+ // and now we are going to access files inside it
+ QuaZipFile file(&zip);
+ const QString jarNames[] = {"jinput.jar", "lwjgl_util.jar", "lwjgl.jar"};
+ for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile())
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ emitFailed("Failed to extract the lwjgl libs - error while reading archive.");
+ return;
+ }
+ QuaZipFileInfo info;
+ QString name = file.getActualFileName();
+ if (name.endsWith('/'))
+ {
+ file.close();
+ continue;
+ }
+ QString destFileName;
+ // Look for the jars
+ for (int i = 0; i < 3; i++)
+ {
+ if (name.endsWith(jarNames[i]))
+ {
+ destFileName = PathCombine(lwjglTargetPath, jarNames[i]);
+ }
+ }
+ // Not found? look for the natives
+ if (destFileName.isEmpty())
+ {
+#ifdef Q_OS_WIN32
+ QString nativesDir = "windows";
+#else
+#ifdef Q_OS_MAC
+ QString nativesDir = "macosx";
+#else
+ QString nativesDir = "linux";
+#endif
+#endif
+ if (name.contains(nativesDir))
+ {
+ int lastSlash = name.lastIndexOf('/');
+ int lastBackSlash = name.lastIndexOf('\\');
+ if (lastSlash != -1)
+ name = name.mid(lastSlash + 1);
+ else if (lastBackSlash != -1)
+ name = name.mid(lastBackSlash + 1);
+ destFileName = PathCombine(lwjglNativesPath, name);
+ }
+ }
+ // Now if destFileName is still empty, go to the next file.
+ if (!destFileName.isEmpty())
+ {
+ setStatus(tr("Installing new LWJGL - extracting ") + name + "...");
+ QFile output(destFileName);
+ output.open(QIODevice::WriteOnly);
+ output.write(file.readAll()); // FIXME: wste of memory!?
+ output.close();
+ }
+ file.close(); // do not forget to close!
+ }
+ zip.close();
+ m_reply.reset();
+ QFile doneFile(PathCombine(lwjglTargetPath, "done"));
+ doneFile.open(QIODevice::WriteOnly);
+ doneFile.write("done.");
+ doneFile.close();
+}
+
+void LegacyUpdate::lwjglFailed()
+{
+ emitFailed("Bad stuff happened while trying to get the lwjgl libs...");
+}
+
+void LegacyUpdate::jarStart()
+{
+ LegacyInstance *inst = (LegacyInstance *)m_inst;
+ if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar())
+ {
+ ModTheJar();
+ return;
+ }
+
+ setStatus(tr("Checking for jar updates..."));
+ // Make directories
+ QDir binDir(inst->binDir());
+ if (!binDir.exists() && !binDir.mkpath("."))
+ {
+ emitFailed("Failed to create bin folder.");
+ return;
+ }
+
+ // Build a list of URLs that will need to be downloaded.
+ setStatus(tr("Downloading new minecraft.jar ..."));
+
+ QString version_id = inst->intendedVersionId();
+ QString localPath = version_id + "/" + version_id + ".jar";
+ QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + localPath;
+
+ auto dljob = new NetJob("Minecraft.jar for version " + version_id);
+
+ auto metacache = ENV.metacache();
+ auto entry = metacache->resolveEntry("versions", localPath);
+ dljob->addNetAction(CacheDownload::make(QUrl(urlstr), entry));
+ connect(dljob, SIGNAL(succeeded()), SLOT(jarFinished()));
+ connect(dljob, SIGNAL(failed()), SLOT(jarFailed()));
+ connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
+ legacyDownloadJob.reset(dljob);
+ legacyDownloadJob->start();
+}
+
+void LegacyUpdate::jarFinished()
+{
+ // process the jar
+ ModTheJar();
+}
+
+void LegacyUpdate::jarFailed()
+{
+ // bad, bad
+ emitFailed("Failed to download the minecraft jar. Try again later.");
+}
+
+void LegacyUpdate::ModTheJar()
+{
+ LegacyInstance *inst = (LegacyInstance *)m_inst;
+
+ if (!inst->shouldRebuild())
+ {
+ emitSucceeded();
+ return;
+ }
+
+ // Get the mod list
+ auto modList = inst->getJarMods();
+
+ QFileInfo runnableJar(inst->runnableJar());
+ QFileInfo baseJar(inst->baseJar());
+ bool base_is_custom = inst->shouldUseCustomBaseJar();
+
+ // Nothing to do if there are no jar mods to install, no backup and just the mc jar
+ if (base_is_custom)
+ {
+ // yes, this can happen if the instance only has the runnable jar and not the base jar
+ // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe
+ // because that's not something mmc4 guarantees
+ if (runnableJar.isFile() && !baseJar.exists() && modList.empty())
+ {
+ inst->setShouldRebuild(false);
+ emitSucceeded();
+ return;
+ }
+
+ setStatus(tr("Installing mods: Backing up minecraft.jar ..."));
+ if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath()))
+ {
+ emitFailed("It seems both the active and base jar are gone. A fresh base jar will "
+ "be used on next run.");
+ inst->setShouldRebuild(true);
+ inst->setShouldUpdate(true);
+ inst->setShouldUseCustomBaseJar(false);
+ return;
+ }
+ }
+
+ if (!baseJar.exists())
+ {
+ emitFailed("The base jar " + baseJar.filePath() + " does not exist");
+ return;
+ }
+
+ if (runnableJar.exists() && !QFile::remove(runnableJar.filePath()))
+ {
+ emitFailed("Failed to delete old minecraft.jar");
+ return;
+ }
+
+ setStatus(tr("Installing mods: Opening minecraft.jar ..."));
+
+ QString outputJarPath = runnableJar.filePath();
+ QString inputJarPath = baseJar.filePath();
+
+ if(!JarUtils::createModdedJar(inputJarPath, outputJarPath, modList))
+ {
+ emitFailed(tr("Failed to create the custom Minecraft jar file."));
+ return;
+ }
+ inst->setShouldRebuild(false);
+ // inst->UpdateVersion(true);
+ emitSucceeded();
+ return;
+}
diff --git a/logic/minecraft/LegacyUpdate.h b/logic/minecraft/LegacyUpdate.h
new file mode 100644
index 00000000..78e456a1
--- /dev/null
+++ b/logic/minecraft/LegacyUpdate.h
@@ -0,0 +1,72 @@
+/* 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 <QObject>
+#include <QList>
+#include <QUrl>
+
+#include "logic/net/NetJob.h"
+#include "logic/tasks/Task.h"
+#include "logic/minecraft/VersionFilterData.h"
+
+class MinecraftVersion;
+class BaseInstance;
+class QuaZip;
+class Mod;
+
+class LegacyUpdate : public Task
+{
+ Q_OBJECT
+public:
+ explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0);
+ virtual void executeTask();
+
+private
+slots:
+ void lwjglStart();
+ void lwjglFinished(QNetworkReply *);
+ void lwjglFailed();
+
+ void jarStart();
+ void jarFinished();
+ void jarFailed();
+
+ void fmllibsStart();
+ void fmllibsFinished();
+ void fmllibsFailed();
+
+ void extractLwjgl();
+
+ void ModTheJar();
+
+private:
+
+ std::shared_ptr<QNetworkReply> m_reply;
+
+ // target version, determined during this task
+ // MinecraftVersion *targetVersion;
+ QString lwjglURL;
+ QString lwjglVersion;
+
+ QString lwjglTargetPath;
+ QString lwjglNativesPath;
+
+private:
+ NetJobPtr legacyDownloadJob;
+ BaseInstance *m_inst = nullptr;
+ QList<FMLlib> fmlLibsToProcess;
+};
diff --git a/logic/minecraft/LwjglVersionList.cpp b/logic/minecraft/LwjglVersionList.cpp
new file mode 100644
index 00000000..9e101b74
--- /dev/null
+++ b/logic/minecraft/LwjglVersionList.cpp
@@ -0,0 +1,189 @@
+/* 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 "LwjglVersionList.h"
+#include "logic/Env.h"
+
+#include <QtNetwork>
+#include <QtXml>
+#include <QRegExp>
+
+#include <QDebug>
+
+#define RSS_URL "http://sourceforge.net/projects/java-game-lib/rss"
+
+LWJGLVersionList::LWJGLVersionList(QObject *parent) : BaseVersionList(parent)
+{
+ setLoading(false);
+}
+
+QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (index.row() > count())
+ return QVariant();
+
+ const PtrLWJGLVersion version = m_vlist.at(index.row());
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ return version->name();
+
+ case Qt::ToolTipRole:
+ return version->url();
+
+ default:
+ return QVariant();
+ }
+}
+
+QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ return tr("Version");
+
+ case Qt::ToolTipRole:
+ return tr("LWJGL version name.");
+
+ default:
+ return QVariant();
+ }
+}
+
+int LWJGLVersionList::columnCount(const QModelIndex &parent) const
+{
+ return 1;
+}
+
+bool LWJGLVersionList::isLoading() const
+{
+ return m_loading;
+}
+
+void LWJGLVersionList::loadList()
+{
+ Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)");
+
+ setLoading(true);
+ auto worker = ENV.qnam();
+ QNetworkRequest req(QUrl(RSS_URL));
+ req.setRawHeader("Accept", "application/rss+xml, text/xml, */*");
+ req.setRawHeader("User-Agent", "MultiMC/5.0 (Uncached)");
+ reply = worker->get(req);
+ connect(reply, SIGNAL(finished()), SLOT(netRequestComplete()));
+}
+
+inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
+{
+ QDomNodeList elementList = parent.elementsByTagName(tagname);
+ if (elementList.count())
+ return elementList.at(0).toElement();
+ else
+ return QDomElement();
+}
+
+void LWJGLVersionList::netRequestComplete()
+{
+ if (reply->error() == QNetworkReply::NoError)
+ {
+ QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip");
+ Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", "LWJGL regex is invalid");
+
+ QDomDocument doc;
+
+ QString xmlErrorMsg;
+ int errorLine;
+ auto rawData = reply->readAll();
+ if (!doc.setContent(rawData, false, &xmlErrorMsg, &errorLine))
+ {
+ failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " +
+ QString::number(errorLine));
+ setLoading(false);
+ return;
+ }
+
+ QDomNodeList items = doc.elementsByTagName("item");
+
+ QList<PtrLWJGLVersion> tempList;
+
+ for (int i = 0; i < items.length(); i++)
+ {
+ Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list",
+ "XML element isn't an element... wat?");
+
+ QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link");
+ if (linkElement.isNull())
+ {
+ qDebug() << "Link element" << i << "in RSS feed doesn't exist! Skipping.";
+ continue;
+ }
+
+ QString link = linkElement.text();
+
+ // Make sure it's a download link.
+ if (link.endsWith("/download") && link.contains(lwjglRegex))
+ {
+ QString name = link.mid(lwjglRegex.indexIn(link) + 6);
+ // Subtract 4 here to remove the .zip file extension.
+ name = name.left(lwjglRegex.matchedLength() - 10);
+
+ QUrl url(link);
+ if (!url.isValid())
+ {
+ qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping.";
+ continue;
+ }
+ qDebug() << "Discovered LWGL version" << name << "at" << link;
+ tempList.append(std::make_shared<LWJGLVersion>(name, link));
+ }
+ }
+
+ beginResetModel();
+ m_vlist.swap(tempList);
+ endResetModel();
+
+ qDebug() << "Loaded LWJGL list.";
+ finished();
+ }
+ else
+ {
+ failed("Failed to load LWJGL list. Network error: " + reply->errorString());
+ }
+
+ setLoading(false);
+ reply->deleteLater();
+}
+
+void LWJGLVersionList::failed(QString msg)
+{
+ qCritical() << msg;
+ emit loadListFailed(msg);
+}
+
+void LWJGLVersionList::finished()
+{
+ emit loadListFinished();
+}
+
+void LWJGLVersionList::setLoading(bool loading)
+{
+ m_loading = loading;
+ emit loadingStateUpdated(m_loading);
+}
diff --git a/logic/minecraft/LwjglVersionList.h b/logic/minecraft/LwjglVersionList.h
new file mode 100644
index 00000000..c364fa13
--- /dev/null
+++ b/logic/minecraft/LwjglVersionList.h
@@ -0,0 +1,154 @@
+/* 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 <QObject>
+#include <QAbstractListModel>
+#include <QUrl>
+#include <QNetworkReply>
+#include <memory>
+
+#include "logic/BaseVersion.h"
+#include "logic/BaseVersionList.h"
+
+class LWJGLVersion;
+typedef std::shared_ptr<LWJGLVersion> PtrLWJGLVersion;
+
+class LWJGLVersion : public BaseVersion
+{
+public:
+ LWJGLVersion(const QString &name, const QString &url)
+ : m_name(name), m_url(url)
+ {
+ }
+
+ virtual QString descriptor()
+ {
+ return m_name;
+ }
+
+ virtual QString name()
+ {
+ return m_name;
+ }
+
+ virtual QString typeString() const
+ {
+ return QObject::tr("Upstream");
+ }
+
+ QString url() const
+ {
+ return m_url;
+ }
+
+protected:
+ QString m_name;
+ QString m_url;
+};
+
+class LWJGLVersionList : public BaseVersionList
+{
+ Q_OBJECT
+public:
+ explicit LWJGLVersionList(QObject *parent = 0);
+
+ bool isLoaded()
+ {
+ return m_vlist.length() > 0;
+ }
+ virtual const BaseVersionPtr at(int i) const override
+ {
+ return m_vlist[i];
+ }
+
+ virtual Task* getLoadTask()
+ {
+ return nullptr;
+ }
+
+ virtual void sort() {};
+
+ virtual void updateListData(QList< BaseVersionPtr > versions) {};
+
+ int count() const
+ {
+ return m_vlist.length();
+ }
+
+ virtual QVariant data(const QModelIndex &index, int role) const;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ virtual int rowCount(const QModelIndex &parent) const
+ {
+ return count();
+ }
+ virtual int columnCount(const QModelIndex &parent) const;
+
+ virtual bool isLoading() const;
+ virtual bool errored() const
+ {
+ return m_errored;
+ }
+
+ virtual QString lastErrorMsg() const
+ {
+ return m_lastErrorMsg;
+ }
+
+public
+slots:
+ /*!
+ * Loads the version list.
+ * This is done asynchronously. On success, the loadListFinished() signal will
+ * be emitted. The list model will be reset as well, resulting in the modelReset()
+ * signal being emitted. Note that the model will be reset before loadListFinished() is
+ * emitted.
+ * If loading the list failed, the loadListFailed(QString msg),
+ * signal will be emitted.
+ */
+ virtual void loadList();
+
+signals:
+ /*!
+ * Emitted when the list either starts or finishes loading.
+ * \param loading Whether or not the list is loading.
+ */
+ void loadingStateUpdated(bool loading);
+
+ void loadListFinished();
+
+ void loadListFailed(QString msg);
+
+private:
+ QList<PtrLWJGLVersion> m_vlist;
+
+ QNetworkReply *m_netReply;
+ QNetworkReply *reply;
+
+ bool m_loading;
+ bool m_errored;
+ QString m_lastErrorMsg;
+
+ void failed(QString msg);
+
+ void finished();
+
+ void setLoading(bool loading);
+
+private
+slots:
+ virtual void netRequestComplete();
+};
diff --git a/logic/minecraft/MinecraftInstance.h b/logic/minecraft/MinecraftInstance.h
index a6c786fb..63519ae8 100644
--- a/logic/minecraft/MinecraftInstance.h
+++ b/logic/minecraft/MinecraftInstance.h
@@ -1,5 +1,8 @@
#pragma once
#include "logic/BaseInstance.h"
+#include "logic/minecraft/Mod.h"
+
+class ModList;
class MinecraftInstance: public BaseInstance
{
diff --git a/logic/minecraft/MinecraftProfile.cpp b/logic/minecraft/MinecraftProfile.cpp
index 9b97b95f..27277ab2 100644
--- a/logic/minecraft/MinecraftProfile.cpp
+++ b/logic/minecraft/MinecraftProfile.cpp
@@ -23,7 +23,6 @@
#include "logic/minecraft/VersionBuilder.h"
#include "ProfileUtils.h"
#include "NullProfileStrategy.h"
-#include "logic/OneSixInstance.h"
MinecraftProfile::MinecraftProfile(ProfileStrategy *strategy)
: QAbstractListModel()
diff --git a/logic/minecraft/Mod.cpp b/logic/minecraft/Mod.cpp
new file mode 100644
index 00000000..7fa4905e
--- /dev/null
+++ b/logic/minecraft/Mod.cpp
@@ -0,0 +1,377 @@
+/* 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 <QDir>
+#include <QString>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonValue>
+#include <quazip.h>
+#include <quazipfile.h>
+
+#include "Mod.h"
+#include <pathutils.h>
+#include "logic/settings/INIFile.h"
+#include <QDebug>
+
+Mod::Mod(const QFileInfo &file)
+{
+ repath(file);
+}
+
+void Mod::repath(const QFileInfo &file)
+{
+ m_file = file;
+ QString name_base = file.fileName();
+
+ m_type = Mod::MOD_UNKNOWN;
+
+ if (m_file.isDir())
+ {
+ m_type = MOD_FOLDER;
+ m_name = name_base;
+ m_mmc_id = name_base;
+ }
+ else if (m_file.isFile())
+ {
+ if (name_base.endsWith(".disabled"))
+ {
+ m_enabled = false;
+ name_base.chop(9);
+ }
+ else
+ {
+ m_enabled = true;
+ }
+ m_mmc_id = name_base;
+ if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
+ {
+ m_type = MOD_ZIPFILE;
+ name_base.chop(4);
+ }
+ else if (name_base.endsWith(".litemod"))
+ {
+ m_type = MOD_LITEMOD;
+ name_base.chop(8);
+ }
+ else
+ {
+ m_type = MOD_SINGLEFILE;
+ }
+ m_name = name_base;
+ }
+
+ if (m_type == MOD_ZIPFILE)
+ {
+ QuaZip zip(m_file.filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return;
+
+ QuaZipFile file(&zip);
+
+ if (zip.setCurrentFile("mcmod.info"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ ReadMCModInfo(file.readAll());
+ file.close();
+ zip.close();
+ return;
+ }
+ else if (zip.setCurrentFile("forgeversion.properties"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ ReadForgeInfo(file.readAll());
+ file.close();
+ zip.close();
+ return;
+ }
+
+ zip.close();
+ }
+ else if (m_type == MOD_FOLDER)
+ {
+ QFileInfo mcmod_info(PathCombine(m_file.filePath(), "mcmod.info"));
+ if (mcmod_info.isFile())
+ {
+ QFile mcmod(mcmod_info.filePath());
+ if (!mcmod.open(QIODevice::ReadOnly))
+ return;
+ auto data = mcmod.readAll();
+ if (data.isEmpty() || data.isNull())
+ return;
+ ReadMCModInfo(data);
+ }
+ }
+ else if (m_type == MOD_LITEMOD)
+ {
+ QuaZip zip(m_file.filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return;
+
+ QuaZipFile file(&zip);
+
+ if (zip.setCurrentFile("litemod.json"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ ReadLiteModInfo(file.readAll());
+ file.close();
+ }
+ zip.close();
+ }
+}
+
+// NEW format
+// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
+
+// OLD format:
+// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
+void Mod::ReadMCModInfo(QByteArray contents)
+{
+ auto getInfoFromArray = [&](QJsonArray arr)->void
+ {
+ if (!arr.at(0).isObject())
+ return;
+ auto firstObj = arr.at(0).toObject();
+ m_mod_id = firstObj.value("modid").toString();
+ m_name = firstObj.value("name").toString();
+ m_version = firstObj.value("version").toString();
+ m_homeurl = firstObj.value("url").toString();
+ m_updateurl = firstObj.value("updateUrl").toString();
+ m_homeurl = m_homeurl.trimmed();
+ if(!m_homeurl.isEmpty())
+ {
+ // fix up url.
+ if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") &&
+ !m_homeurl.startsWith("ftp://"))
+ {
+ m_homeurl.prepend("http://");
+ }
+ }
+ m_description = firstObj.value("description").toString();
+ QJsonArray authors = firstObj.value("authorList").toArray();
+ if (authors.size() == 0)
+ authors = firstObj.value("authors").toArray();
+
+ if (authors.size() == 0)
+ m_authors = "";
+ else if (authors.size() >= 1)
+ {
+ m_authors = authors.at(0).toString();
+ for (int i = 1; i < authors.size(); i++)
+ {
+ m_authors += ", " + authors.at(i).toString();
+ }
+ }
+ m_credits = firstObj.value("credits").toString();
+ return;
+ }
+ ;
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ // this is the very old format that had just the array
+ if (jsonDoc.isArray())
+ {
+ getInfoFromArray(jsonDoc.array());
+ }
+ else if (jsonDoc.isObject())
+ {
+ auto val = jsonDoc.object().value("modinfoversion");
+ if(val.isUndefined())
+ val = jsonDoc.object().value("modListVersion");
+ int version = val.toDouble();
+ if (version != 2)
+ {
+ qCritical() << "BAD stuff happened to mod json:";
+ qCritical() << contents;
+ return;
+ }
+ auto arrVal = jsonDoc.object().value("modlist");
+ if(arrVal.isUndefined())
+ arrVal = jsonDoc.object().value("modList");
+ if (arrVal.isArray())
+ {
+ getInfoFromArray(arrVal.toArray());
+ }
+ }
+}
+
+void Mod::ReadForgeInfo(QByteArray contents)
+{
+ // Read the data
+ m_name = "Minecraft Forge";
+ m_mod_id = "Forge";
+ m_homeurl = "http://www.minecraftforge.net/forum/";
+ INIFile ini;
+ if (!ini.loadFile(contents))
+ return;
+
+ QString major = ini.get("forge.major.number", "0").toString();
+ QString minor = ini.get("forge.minor.number", "0").toString();
+ QString revision = ini.get("forge.revision.number", "0").toString();
+ QString build = ini.get("forge.build.number", "0").toString();
+
+ m_version = major + "." + minor + "." + revision + "." + build;
+}
+
+void Mod::ReadLiteModInfo(QByteArray contents)
+{
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ auto object = jsonDoc.object();
+ if (object.contains("name"))
+ {
+ m_mod_id = m_name = object.value("name").toString();
+ }
+ if (object.contains("version"))
+ {
+ m_version = object.value("version").toString("");
+ }
+ else
+ {
+ m_version = object.value("revision").toString("");
+ }
+ m_mcversion = object.value("mcversion").toString();
+ m_authors = object.value("author").toString();
+ m_description = object.value("description").toString();
+ m_homeurl = object.value("url").toString();
+}
+
+bool Mod::replace(Mod &with)
+{
+ if (!destroy())
+ return false;
+ bool success = false;
+ auto t = with.type();
+
+ if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE || t == MOD_LITEMOD)
+ {
+ qDebug() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath();
+ success = QFile::copy(with.m_file.filePath(), m_file.filePath());
+ }
+ if (t == MOD_FOLDER)
+ {
+ success = copyPath(with.m_file.filePath(), m_file.path());
+ }
+ if (success)
+ {
+ m_name = with.m_name;
+ m_mmc_id = with.m_mmc_id;
+ m_mod_id = with.m_mod_id;
+ m_version = with.m_version;
+ m_mcversion = with.m_mcversion;
+ m_description = with.m_description;
+ m_authors = with.m_authors;
+ m_credits = with.m_credits;
+ m_homeurl = with.m_homeurl;
+ m_type = with.m_type;
+ m_file.refresh();
+ }
+ return success;
+}
+
+bool Mod::destroy()
+{
+ if (m_type == MOD_FOLDER)
+ {
+ QDir d(m_file.filePath());
+ if (d.removeRecursively())
+ {
+ m_type = MOD_UNKNOWN;
+ return true;
+ }
+ return false;
+ }
+ else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD)
+ {
+ QFile f(m_file.filePath());
+ if (f.remove())
+ {
+ m_type = MOD_UNKNOWN;
+ return true;
+ }
+ return false;
+ }
+ return true;
+}
+
+QString Mod::version() const
+{
+ switch (type())
+ {
+ case MOD_ZIPFILE:
+ case MOD_LITEMOD:
+ return m_version;
+ case MOD_FOLDER:
+ return "Folder";
+ case MOD_SINGLEFILE:
+ return "File";
+ default:
+ return "VOID";
+ }
+}
+
+bool Mod::enable(bool value)
+{
+ if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
+ return false;
+
+ if (m_enabled == value)
+ return false;
+
+ QString path = m_file.absoluteFilePath();
+ if (value)
+ {
+ QFile foo(path);
+ if (!path.endsWith(".disabled"))
+ return false;
+ path.chop(9);
+ if (!foo.rename(path))
+ return false;
+ }
+ else
+ {
+ QFile foo(path);
+ path += ".disabled";
+ if (!foo.rename(path))
+ return false;
+ }
+ m_file = QFileInfo(path);
+ m_enabled = value;
+ return true;
+}
+bool Mod::operator==(const Mod &other) const
+{
+ return mmc_id() == other.mmc_id();
+}
+bool Mod::strongCompare(const Mod &other) const
+{
+ return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type();
+}
diff --git a/logic/minecraft/Mod.h b/logic/minecraft/Mod.h
new file mode 100644
index 00000000..5b815bc1
--- /dev/null
+++ b/logic/minecraft/Mod.h
@@ -0,0 +1,130 @@
+/* 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 <QFileInfo>
+
+class Mod
+{
+public:
+ enum ModType
+ {
+ MOD_UNKNOWN, //!< Indicates an unspecified mod type.
+ MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
+ MOD_SINGLEFILE, //!< The mod is a single file (not a zip file).
+ MOD_FOLDER, //!< The mod is in a folder on the filesystem.
+ MOD_LITEMOD, //!< The mod is a litemod
+ };
+
+ Mod(const QFileInfo &file);
+
+ QFileInfo filename() const
+ {
+ return m_file;
+ }
+ QString mmc_id() const
+ {
+ return m_mmc_id;
+ }
+ QString mod_id() const
+ {
+ return m_mod_id;
+ }
+ ModType type() const
+ {
+ return m_type;
+ }
+ QString mcversion() const
+ {
+ return m_mcversion;
+ }
+ ;
+ bool valid()
+ {
+ return m_type != MOD_UNKNOWN;
+ }
+ QString name() const
+ {
+ return m_name;
+ }
+
+ QString version() const;
+
+ QString homeurl() const
+ {
+ return m_homeurl;
+ }
+
+ QString description() const
+ {
+ return m_description;
+ }
+
+ QString authors() const
+ {
+ return m_authors;
+ }
+
+ QString credits() const
+ {
+ return m_credits;
+ }
+
+ bool enabled() const
+ {
+ return m_enabled;
+ }
+
+ bool enable(bool value);
+
+ // delete all the files of this mod
+ bool destroy();
+ // replace this mod with a copy of the other
+ bool replace(Mod &with);
+ // change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
+ void repath(const QFileInfo &file);
+
+ // WEAK compare operator - used for replacing mods
+ bool operator==(const Mod &other) const;
+ bool strongCompare(const Mod &other) const;
+
+private:
+ void ReadMCModInfo(QByteArray contents);
+ void ReadForgeInfo(QByteArray contents);
+ void ReadLiteModInfo(QByteArray contents);
+
+protected:
+
+ // FIXME: what do do with those? HMM...
+ /*
+ void ReadModInfoData(QString info);
+ void ReadForgeInfoData(QString infoFileData);
+ */
+
+ QFileInfo m_file;
+ QString m_mmc_id;
+ QString m_mod_id;
+ bool m_enabled = true;
+ QString m_name;
+ QString m_version;
+ QString m_mcversion;
+ QString m_homeurl;
+ QString m_updateurl;
+ QString m_description;
+ QString m_authors;
+ QString m_credits;
+
+ ModType m_type;
+};
diff --git a/logic/minecraft/ModList.cpp b/logic/minecraft/ModList.cpp
new file mode 100644
index 00000000..c943c254
--- /dev/null
+++ b/logic/minecraft/ModList.cpp
@@ -0,0 +1,610 @@
+/* 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 "ModList.h"
+#include <pathutils.h>
+#include <QMimeData>
+#include <QUrl>
+#include <QUuid>
+#include <QString>
+#include <QFileSystemWatcher>
+#include <QDebug>
+
+ModList::ModList(const QString &dir, const QString &list_file)
+ : QAbstractListModel(), m_dir(dir), m_list_file(list_file)
+{
+ ensureFolderPathExists(m_dir.absolutePath());
+ m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
+ QDir::NoSymLinks);
+ m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
+ m_list_id = QUuid::createUuid().toString();
+ m_watcher = new QFileSystemWatcher(this);
+ is_watching = false;
+ connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
+ SLOT(directoryChanged(QString)));
+}
+
+void ModList::startWatching()
+{
+ is_watching = m_watcher->addPath(m_dir.absolutePath());
+ if (is_watching)
+ {
+ qDebug() << "Started watching " << m_dir.absolutePath();
+ }
+ else
+ {
+ qDebug() << "Failed to start watching " << m_dir.absolutePath();
+ }
+}
+
+void ModList::stopWatching()
+{
+ is_watching = !m_watcher->removePath(m_dir.absolutePath());
+ if (!is_watching)
+ {
+ qDebug() << "Stopped watching " << m_dir.absolutePath();
+ }
+ else
+ {
+ qDebug() << "Failed to stop watching " << m_dir.absolutePath();
+ }
+}
+
+void ModList::internalSort(QList<Mod> &what)
+{
+ auto predicate = [](const Mod &left, const Mod &right)
+ {
+ if (left.name() == right.name())
+ {
+ return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0;
+ }
+ return left.name().localeAwareCompare(right.name()) < 0;
+ };
+ std::sort(what.begin(), what.end(), predicate);
+}
+
+bool ModList::update()
+{
+ if (!isValid())
+ return false;
+
+ QList<Mod> orderedMods;
+ QList<Mod> newMods;
+ m_dir.refresh();
+ auto folderContents = m_dir.entryInfoList();
+ bool orderOrStateChanged = false;
+
+ // first, process the ordered items (if any)
+ OrderList listOrder = readListFile();
+ for (auto item : listOrder)
+ {
+ QFileInfo infoEnabled(m_dir.filePath(item.id));
+ QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled"));
+ int idxEnabled = folderContents.indexOf(infoEnabled);
+ int idxDisabled = folderContents.indexOf(infoDisabled);
+ bool isEnabled;
+ // if both enabled and disabled versions are present, it's a special case...
+ if (idxEnabled >= 0 && idxDisabled >= 0)
+ {
+ // we only process the one we actually have in the order file.
+ // and exactly as we have it.
+ // THIS IS A CORNER CASE
+ isEnabled = item.enabled;
+ }
+ else
+ {
+ // only one is present.
+ // we pick the one that we found.
+ // we assume the mod was enabled/disabled by external means
+ isEnabled = idxEnabled >= 0;
+ }
+ int idx = isEnabled ? idxEnabled : idxDisabled;
+ QFileInfo &info = isEnabled ? infoEnabled : infoDisabled;
+ // if the file from the index file exists
+ if (idx != -1)
+ {
+ // remove from the actual folder contents list
+ folderContents.takeAt(idx);
+ // append the new mod
+ orderedMods.append(Mod(info));
+ if (isEnabled != item.enabled)
+ orderOrStateChanged = true;
+ }
+ else
+ {
+ orderOrStateChanged = true;
+ }
+ }
+ // if there are any untracked files...
+ if (folderContents.size())
+ {
+ // the order surely changed!
+ for (auto entry : folderContents)
+ {
+ newMods.append(Mod(entry));
+ }
+ internalSort(newMods);
+ orderedMods.append(newMods);
+ orderOrStateChanged = true;
+ }
+ // otherwise, if we were already tracking some mods
+ else if (mods.size())
+ {
+ // if the number doesn't match, order changed.
+ if (mods.size() != orderedMods.size())
+ orderOrStateChanged = true;
+ // if it does match, compare the mods themselves
+ else
+ for (int i = 0; i < mods.size(); i++)
+ {
+ if (!mods[i].strongCompare(orderedMods[i]))
+ {
+ orderOrStateChanged = true;
+ break;
+ }
+ }
+ }
+ beginResetModel();
+ mods.swap(orderedMods);
+ endResetModel();
+ if (orderOrStateChanged && !m_list_file.isEmpty())
+ {
+ qDebug() << "Mod list " << m_list_file << " changed!";
+ saveListFile();
+ emit changed();
+ }
+ return true;
+}
+
+void ModList::directoryChanged(QString path)
+{
+ update();
+}
+
+ModList::OrderList ModList::readListFile()
+{
+ OrderList itemList;
+ if (m_list_file.isNull() || m_list_file.isEmpty())
+ return itemList;
+
+ QFile textFile(m_list_file);
+ if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
+ return OrderList();
+
+ QTextStream textStream;
+ textStream.setAutoDetectUnicode(true);
+ textStream.setDevice(&textFile);
+ while (true)
+ {
+ QString line = textStream.readLine();
+ if (line.isNull() || line.isEmpty())
+ break;
+ else
+ {
+ OrderItem it;
+ it.enabled = !line.endsWith(".disabled");
+ if (!it.enabled)
+ {
+ line.chop(9);
+ }
+ it.id = line;
+ itemList.append(it);
+ }
+ }
+ textFile.close();
+ return itemList;
+}
+
+bool ModList::saveListFile()
+{
+ if (m_list_file.isNull() || m_list_file.isEmpty())
+ return false;
+ QFile textFile(m_list_file);
+ if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
+ return false;
+ QTextStream textStream;
+ textStream.setGenerateByteOrderMark(true);
+ textStream.setCodec("UTF-8");
+ textStream.setDevice(&textFile);
+ for (auto mod : mods)
+ {
+ textStream << mod.mmc_id();
+ if (!mod.enabled())
+ textStream << ".disabled";
+ textStream << endl;
+ }
+ textFile.close();
+ return false;
+}
+
+bool ModList::isValid()
+{
+ return m_dir.exists() && m_dir.isReadable();
+}
+
+bool ModList::installMod(const QFileInfo &filename, int index)
+{
+ if (!filename.exists() || !filename.isReadable() || index < 0)
+ {
+ return false;
+ }
+ Mod m(filename);
+ if (!m.valid())
+ return false;
+
+ // if it's already there, replace the original mod (in place)
+ int idx = mods.indexOf(m);
+ if (idx != -1)
+ {
+ int idx2 = mods.indexOf(m, idx + 1);
+ if (idx2 != -1)
+ return false;
+ if (mods[idx].replace(m))
+ {
+
+ auto left = this->index(index);
+ auto right = this->index(index, columnCount(QModelIndex()) - 1);
+ emit dataChanged(left, right);
+ saveListFile();
+ update();
+ return true;
+ }
+ return false;
+ }
+
+ auto type = m.type();
+ if (type == Mod::MOD_UNKNOWN)
+ return false;
+ if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
+ {
+ QString newpath = PathCombine(m_dir.path(), filename.fileName());
+ if (!QFile::copy(filename.filePath(), newpath))
+ return false;
+ m.repath(newpath);
+ beginInsertRows(QModelIndex(), index, index);
+ mods.insert(index, m);
+ endInsertRows();
+ saveListFile();
+ update();
+ return true;
+ }
+ else if (type == Mod::MOD_FOLDER)
+ {
+
+ QString from = filename.filePath();
+ QString to = PathCombine(m_dir.path(), filename.fileName());
+ if (!copyPath(from, to))
+ return false;
+ m.repath(to);
+ beginInsertRows(QModelIndex(), index, index);
+ mods.insert(index, m);
+ endInsertRows();
+ saveListFile();
+ update();
+ return true;
+ }
+ return false;
+}
+
+bool ModList::deleteMod(int index)
+{
+ if (index >= mods.size() || index < 0)
+ return false;
+ Mod &m = mods[index];
+ if (m.destroy())
+ {
+ beginRemoveRows(QModelIndex(), index, index);
+ mods.removeAt(index);
+ endRemoveRows();
+ saveListFile();
+ emit changed();
+ return true;
+ }
+ return false;
+}
+
+bool ModList::deleteMods(int first, int last)
+{
+ for (int i = first; i <= last; i++)
+ {
+ Mod &m = mods[i];
+ m.destroy();
+ }
+ beginRemoveRows(QModelIndex(), first, last);
+ mods.erase(mods.begin() + first, mods.begin() + last + 1);
+ endRemoveRows();
+ saveListFile();
+ emit changed();
+ return true;
+}
+
+bool ModList::moveModTo(int from, int to)
+{
+ if (from < 0 || from >= mods.size())
+ return false;
+ if (to >= rowCount())
+ to = rowCount() - 1;
+ if (to == -1)
+ to = rowCount() - 1;
+ if (from == to)
+ return false;
+ int togap = to > from ? to + 1 : to;
+ beginMoveRows(QModelIndex(), from, from, QModelIndex(), togap);
+ mods.move(from, to);
+ endMoveRows();
+ saveListFile();
+ emit changed();
+ return true;
+}
+
+bool ModList::moveModUp(int from)
+{
+ if (from > 0)
+ return moveModTo(from, from - 1);
+ return false;
+}
+
+bool ModList::moveModsUp(int first, int last)
+{
+ if (first == 0)
+ return false;
+
+ beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1);
+ mods.move(first - 1, last);
+ endMoveRows();
+ saveListFile();
+ emit changed();
+ return true;
+}
+
+bool ModList::moveModDown(int from)
+{
+ if (from < 0)
+ return false;
+ if (from < mods.size() - 1)
+ return moveModTo(from, from + 1);
+ return false;
+}
+
+bool ModList::moveModsDown(int first, int last)
+{
+ if (last == mods.size() - 1)
+ return false;
+
+ beginMoveRows(QModelIndex(), first, last, QModelIndex(), last + 2);
+ mods.move(last + 1, first);
+ endMoveRows();
+ saveListFile();
+ emit changed();
+ return true;
+}
+
+int ModList::columnCount(const QModelIndex &parent) const
+{
+ return 3;
+}
+
+QVariant ModList::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ int row = index.row();
+ int column = index.column();
+
+ if (row < 0 || row >= mods.size())
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ switch (column)
+ {
+ case NameColumn:
+ return mods[row].name();
+ case VersionColumn:
+ return mods[row].version();
+
+ default:
+ return QVariant();
+ }
+
+ case Qt::ToolTipRole:
+ return mods[row].mmc_id();
+
+ case Qt::CheckStateRole:
+ switch (column)
+ {
+ case ActiveColumn:
+ return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
+ default:
+ return QVariant();
+ }
+ default:
+ return QVariant();
+ }
+}
+
+bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
+ {
+ return false;
+ }
+
+ if (role == Qt::CheckStateRole)
+ {
+ auto &mod = mods[index.row()];
+ if (mod.enable(!mod.enabled()))
+ {
+ emit dataChanged(index, index);
+ return true;
+ }
+ }
+ return false;
+}
+
+QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ switch (section)
+ {
+ case ActiveColumn:
+ return QString();
+ case NameColumn:
+ return tr("Name");
+ case VersionColumn:
+ return tr("Version");
+ default:
+ return QVariant();
+ }
+
+ case Qt::ToolTipRole:
+ switch (section)
+ {
+ case ActiveColumn:
+ return tr("Is the mod enabled?");
+ case NameColumn:
+ return tr("The name of the mod.");
+ case VersionColumn:
+ return tr("The version of the mod.");
+ default:
+ return QVariant();
+ }
+ default:
+ return QVariant();
+ }
+ return QVariant();
+}
+
+Qt::ItemFlags ModList::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
+ if (index.isValid())
+ return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
+ defaultFlags;
+ else
+ return Qt::ItemIsDropEnabled | defaultFlags;
+}
+
+QStringList ModList::mimeTypes() const
+{
+ QStringList types;
+ types << "text/uri-list";
+ types << "text/plain";
+ return types;
+}
+
+Qt::DropActions ModList::supportedDropActions() const
+{
+ // copy from outside, move from within and other mod lists
+ return Qt::CopyAction | Qt::MoveAction;
+}
+
+Qt::DropActions ModList::supportedDragActions() const
+{
+ // move to other mod lists or VOID
+ return Qt::MoveAction;
+}
+
+QMimeData *ModList::mimeData(const QModelIndexList &indexes) const
+{
+ QMimeData *data = new QMimeData();
+
+ if (indexes.size() == 0)
+ return data;
+
+ auto idx = indexes[0];
+ int row = idx.row();
+ if (row < 0 || row >= mods.size())
+ return data;
+
+ QStringList params;
+ params << m_list_id << QString::number(row);
+ data->setText(params.join('|'));
+ return data;
+}
+bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
+ const QModelIndex &parent)
+{
+ if (action == Qt::IgnoreAction)
+ return true;
+ // check if the action is supported
+ if (!data || !(action & supportedDropActions()))
+ return false;
+ if (parent.isValid())
+ {
+ row = parent.row();
+ column = parent.column();
+ }
+
+ if (row > rowCount())
+ row = rowCount();
+ if (row == -1)
+ row = rowCount();
+ if (column == -1)
+ column = 0;
+ qDebug() << "Drop row: " << row << " column: " << column;
+
+ // files dropped from outside?
+ if (data->hasUrls())
+ {
+ bool was_watching = is_watching;
+ if (was_watching)
+ stopWatching();
+ auto urls = data->urls();
+ for (auto url : urls)
+ {
+ // only local files may be dropped...
+ if (!url.isLocalFile())
+ continue;
+ QString filename = url.toLocalFile();
+ installMod(filename, row);
+ qDebug() << "installing: " << filename;
+ // if there is no ordering, re-sort the list
+ if (m_list_file.isEmpty())
+ {
+ beginResetModel();
+ internalSort(mods);
+ endResetModel();
+ }
+ }
+ if (was_watching)
+ startWatching();
+ return true;
+ }
+ else if (data->hasText())
+ {
+ QString sourcestr = data->text();
+ auto list = sourcestr.split('|');
+ if (list.size() != 2)
+ return false;
+ QString remoteId = list[0];
+ int remoteIndex = list[1].toInt();
+ qDebug() << "move: " << sourcestr;
+ // no moving of things between two lists
+ if (remoteId != m_list_id)
+ return false;
+ // no point moving to the same place...
+ if (row == remoteIndex)
+ return false;
+ // otherwise, move the mod :D
+ moveModTo(remoteIndex, row);
+ return true;
+ }
+ return false;
+}
diff --git a/logic/minecraft/ModList.h b/logic/minecraft/ModList.h
new file mode 100644
index 00000000..e227356b
--- /dev/null
+++ b/logic/minecraft/ModList.h
@@ -0,0 +1,158 @@
+/* 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 <QList>
+#include <QString>
+#include <QDir>
+#include <QAbstractListModel>
+
+#include "logic/minecraft/Mod.h"
+
+class LegacyInstance;
+class BaseInstance;
+class QFileSystemWatcher;
+
+/**
+ * A legacy mod list.
+ * Backed by a folder.
+ */
+class ModList : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ enum Columns
+ {
+ ActiveColumn = 0,
+ NameColumn,
+ VersionColumn
+ };
+ ModList(const QString &dir, const QString &list_file = QString());
+
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual bool setData(const QModelIndex &index, const QVariant &value,
+ int role = Qt::EditRole);
+
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
+ {
+ return size();
+ }
+ ;
+ virtual QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ virtual int columnCount(const QModelIndex &parent) const;
+
+ size_t size() const
+ {
+ return mods.size();
+ }
+ ;
+ bool empty() const
+ {
+ return size() == 0;
+ }
+ Mod &operator[](size_t index)
+ {
+ return mods[index];
+ }
+
+ /// Reloads the mod list and returns true if the list changed.
+ virtual bool update();
+
+ /**
+ * Adds the given mod to the list at the given index - if the list supports custom ordering
+ */
+ virtual bool installMod(const QFileInfo &filename, int index = 0);
+
+ /// Deletes the mod at the given index.
+ virtual bool deleteMod(int index);
+
+ /// Deletes all the selected mods
+ virtual bool deleteMods(int first, int last);
+
+ /**
+ * move the mod at index to the position N
+ * 0 is the beginning of the list, length() is the end of the list.
+ */
+ virtual bool moveModTo(int from, int to);
+
+ /**
+ * move the mod at index one position upwards
+ */
+ virtual bool moveModUp(int from);
+ virtual bool moveModsUp(int first, int last);
+
+ /**
+ * move the mod at index one position downwards
+ */
+ virtual bool moveModDown(int from);
+ virtual bool moveModsDown(int first, int last);
+
+ /// flags, mostly to support drag&drop
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const;
+ /// get data for drag action
+ virtual QMimeData *mimeData(const QModelIndexList &indexes) const;
+ /// get the supported mime types
+ virtual QStringList mimeTypes() const;
+ /// process data from drop action
+ virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
+ const QModelIndex &parent);
+ /// what drag actions do we support?
+ virtual Qt::DropActions supportedDragActions() const;
+
+ /// what drop actions do we support?
+ virtual Qt::DropActions supportedDropActions() const;
+
+ void startWatching();
+ void stopWatching();
+
+ virtual bool isValid();
+
+ QDir dir()
+ {
+ return m_dir;
+ }
+
+ const QList<Mod> & allMods()
+ {
+ return mods;
+ }
+
+private:
+ void internalSort(QList<Mod> & what);
+ struct OrderItem
+ {
+ QString id;
+ bool enabled = false;
+ };
+ typedef QList<OrderItem> OrderList;
+ OrderList readListFile();
+ bool saveListFile();
+private
+slots:
+ void directoryChanged(QString path);
+
+signals:
+ void changed();
+
+protected:
+ QFileSystemWatcher *m_watcher;
+ bool is_watching;
+ QDir m_dir;
+ QString m_list_file;
+ QString m_list_id;
+ QList<Mod> mods;
+};
diff --git a/logic/minecraft/OneSixInstance.cpp b/logic/minecraft/OneSixInstance.cpp
new file mode 100644
index 00000000..ebba22d6
--- /dev/null
+++ b/logic/minecraft/OneSixInstance.cpp
@@ -0,0 +1,472 @@
+/* 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 <QIcon>
+#include <pathutils.h>
+#include <QDebug>
+#include "MMCError.h"
+
+#include "logic/minecraft/OneSixInstance.h"
+
+#include "logic/minecraft/OneSixUpdate.h"
+#include "logic/minecraft/MinecraftProfile.h"
+#include "logic/minecraft/VersionBuildError.h"
+#include "logic/minecraft/MinecraftProcess.h"
+#include "logic/minecraft/OneSixProfileStrategy.h"
+
+#include "logic/minecraft/AssetsUtils.h"
+#include "logic/icons/IconList.h"
+#include "gui/pagedialog/PageDialog.h"
+#include "gui/pages/VersionPage.h"
+#include "gui/pages/ModFolderPage.h"
+#include "gui/pages/ResourcePackPage.h"
+#include "gui/pages/TexturePackPage.h"
+#include "gui/pages/InstanceSettingsPage.h"
+#include "gui/pages/NotesPage.h"
+#include "gui/pages/ScreenshotsPage.h"
+#include "gui/pages/OtherLogsPage.h"
+OneSixInstance::OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
+ : MinecraftInstance(globalSettings, settings, rootDir)
+{
+ m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
+}
+
+void OneSixInstance::init()
+{
+ createProfile();
+}
+
+void OneSixInstance::createProfile()
+{
+ m_version.reset(new MinecraftProfile(new OneSixProfileStrategy(this)));
+}
+
+
+QList<BasePage *> OneSixInstance::getPages()
+{
+ QList<BasePage *> values;
+ values.append(new VersionPage(this));
+ values.append(new ModFolderPage(this, loaderModList(), "mods", "loadermods",
+ tr("Loader mods"), "Loader-mods"));
+ values.append(new CoreModFolderPage(this, coreModList(), "coremods", "coremods",
+ tr("Core mods"), "Core-mods"));
+ values.append(new ResourcePackPage(this));
+ values.append(new TexturePackPage(this));
+ values.append(new NotesPage(this));
+ values.append(new ScreenshotsPage(PathCombine(minecraftRoot(), "screenshots")));
+ values.append(new InstanceSettingsPage(this));
+ values.append(new OtherLogsPage(minecraftRoot()));
+ return values;
+}
+
+QString OneSixInstance::dialogTitle()
+{
+ return tr("Edit Instance (%1)").arg(name());
+}
+
+QSet<QString> OneSixInstance::traits()
+{
+ auto version = getMinecraftProfile();
+ if (!version)
+ {
+ return {"version-incomplete"};
+ }
+ else
+ return version->traits;
+}
+
+std::shared_ptr<Task> OneSixInstance::doUpdate()
+{
+ return std::shared_ptr<Task>(new OneSixUpdate(this));
+}
+
+QString replaceTokensIn(QString text, QMap<QString, QString> with)
+{
+ QString result;
+ QRegExp token_regexp("\\$\\{(.+)\\}");
+ token_regexp.setMinimal(true);
+ QStringList list;
+ int tail = 0;
+ int head = 0;
+ while ((head = token_regexp.indexIn(text, head)) != -1)
+ {
+ result.append(text.mid(tail, head - tail));
+ QString key = token_regexp.cap(1);
+ auto iter = with.find(key);
+ if (iter != with.end())
+ {
+ result.append(*iter);
+ }
+ head += token_regexp.matchedLength();
+ tail = head;
+ }
+ result.append(text.mid(tail));
+ return result;
+}
+
+QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session)
+{
+ QString args_pattern = m_version->minecraftArguments;
+ for (auto tweaker : m_version->tweakers)
+ {
+ args_pattern += " --tweakClass " + tweaker;
+ }
+
+ QMap<QString, QString> token_mapping;
+ // yggdrasil!
+ token_mapping["auth_username"] = session->username;
+ token_mapping["auth_session"] = session->session;
+ token_mapping["auth_access_token"] = session->access_token;
+ token_mapping["auth_player_name"] = session->player_name;
+ token_mapping["auth_uuid"] = session->uuid;
+
+ // blatant self-promotion.
+ token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5";
+
+ QString absRootDir = QDir(minecraftRoot()).absolutePath();
+ token_mapping["game_directory"] = absRootDir;
+ QString absAssetsDir = QDir("assets/").absolutePath();
+ token_mapping["game_assets"] = AssetsUtils::reconstructAssets(m_version->assets).absolutePath();
+
+ token_mapping["user_properties"] = session->serializeUserProperties();
+ token_mapping["user_type"] = session->user_type;
+ // 1.7.3+ assets tokens
+ token_mapping["assets_root"] = absAssetsDir;
+ token_mapping["assets_index_name"] = m_version->assets;
+
+ QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
+ for (int i = 0; i < parts.length(); i++)
+ {
+ parts[i] = replaceTokensIn(parts[i], token_mapping);
+ }
+ return parts;
+}
+
+BaseProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session)
+{
+ QString launchScript;
+ QIcon icon = ENV.icons()->getIcon(iconKey());
+ auto pixmap = icon.pixmap(128, 128);
+ pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG");
+
+ if (!m_version)
+ return nullptr;
+
+ // libraries and class path.
+ {
+ auto libs = m_version->getActiveNormalLibs();
+ for (auto lib : libs)
+ {
+ launchScript += "cp " + librariesPath().absoluteFilePath(lib->storagePath()) + "\n";
+ }
+ auto jarMods = getJarMods();
+ if (!jarMods.isEmpty())
+ {
+ launchScript += "cp " + QDir(instanceRoot()).absoluteFilePath("temp.jar") + "\n";
+ }
+ else
+ {
+ QString relpath = m_version->id + "/" + m_version->id + ".jar";
+ launchScript += "cp " + versionsPath().absoluteFilePath(relpath) + "\n";
+ }
+ }
+ if (!m_version->mainClass.isEmpty())
+ {
+ launchScript += "mainClass " + m_version->mainClass + "\n";
+ }
+ if (!m_version->appletClass.isEmpty())
+ {
+ launchScript += "appletClass " + m_version->appletClass + "\n";
+ }
+
+ // generic minecraft params
+ for (auto param : processMinecraftArgs(session))
+ {
+ launchScript += "param " + param + "\n";
+ }
+
+ // window size, title and state, legacy
+ {
+ QString windowParams;
+ if (settings().get("LaunchMaximized").toBool())
+ windowParams = "max";
+ else
+ windowParams = QString("%1x%2")
+ .arg(settings().get("MinecraftWinWidth").toInt())
+ .arg(settings().get("MinecraftWinHeight").toInt());
+ launchScript += "windowTitle " + windowTitle() + "\n";
+ launchScript += "windowParams " + windowParams + "\n";
+ }
+
+ // legacy auth
+ {
+ launchScript += "userName " + session->player_name + "\n";
+ launchScript += "sessionId " + session->session + "\n";
+ }
+
+ // native libraries (mostly LWJGL)
+ {
+ QDir natives_dir(PathCombine(instanceRoot(), "natives/"));
+ for (auto native : m_version->getActiveNativeLibs())
+ {
+ QFileInfo finfo(PathCombine("libraries", native->storagePath()));
+ launchScript += "ext " + finfo.absoluteFilePath() + "\n";
+ }
+ launchScript += "natives " + natives_dir.absolutePath() + "\n";
+ }
+
+ // traits. including legacyLaunch and others ;)
+ for (auto trait : m_version->traits)
+ {
+ launchScript += "traits " + trait + "\n";
+ }
+ launchScript += "launcher onesix\n";
+
+ auto process = MinecraftProcess::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr()));
+ process->setLaunchScript(launchScript);
+ process->setWorkdir(minecraftRoot());
+ process->setLogin(session);
+ return process;
+}
+
+void OneSixInstance::cleanupAfterRun()
+{
+ QString target_dir = PathCombine(instanceRoot(), "natives/");
+ QDir dir(target_dir);
+ dir.removeRecursively();
+}
+
+std::shared_ptr<ModList> OneSixInstance::loaderModList() const
+{
+ if (!m_loader_mod_list)
+ {
+ m_loader_mod_list.reset(new ModList(loaderModsDir()));
+ }
+ m_loader_mod_list->update();
+ return m_loader_mod_list;
+}
+
+std::shared_ptr<ModList> OneSixInstance::coreModList() const
+{
+ if (!m_core_mod_list)
+ {
+ m_core_mod_list.reset(new ModList(coreModsDir()));
+ }
+ m_core_mod_list->update();
+ return m_core_mod_list;
+}
+
+std::shared_ptr<ModList> OneSixInstance::resourcePackList() const
+{
+ if (!m_resource_pack_list)
+ {
+ m_resource_pack_list.reset(new ModList(resourcePacksDir()));
+ }
+ m_resource_pack_list->update();
+ return m_resource_pack_list;
+}
+
+std::shared_ptr<ModList> OneSixInstance::texturePackList() const
+{
+ if (!m_texture_pack_list)
+ {
+ m_texture_pack_list.reset(new ModList(texturePacksDir()));
+ }
+ m_texture_pack_list->update();
+ return m_texture_pack_list;
+}
+
+bool OneSixInstance::setIntendedVersionId(QString version)
+{
+ settings().set("IntendedVersion", version);
+ if(getMinecraftProfile())
+ {
+ clearProfile();
+ }
+ return true;
+}
+
+QList< Mod > OneSixInstance::getJarMods() const
+{
+ QList<Mod> mods;
+ for (auto jarmod : m_version->jarMods)
+ {
+ QString filePath = jarmodsPath().absoluteFilePath(jarmod->name);
+ mods.push_back(Mod(QFileInfo(filePath)));
+ }
+ return mods;
+}
+
+
+QString OneSixInstance::intendedVersionId() const
+{
+ return settings().get("IntendedVersion").toString();
+}
+
+void OneSixInstance::setShouldUpdate(bool)
+{
+}
+
+bool OneSixInstance::shouldUpdate() const
+{
+ return true;
+}
+
+QString OneSixInstance::currentVersionId() const
+{
+ return intendedVersionId();
+}
+
+void OneSixInstance::reloadProfile()
+{
+ try
+ {
+ m_version->reload();
+ unsetFlag(VersionBrokenFlag);
+ emit versionReloaded();
+ }
+ catch (VersionIncomplete &error)
+ {
+ }
+ catch (MMCError &error)
+ {
+ m_version->clear();
+ setFlag(VersionBrokenFlag);
+ // TODO: rethrow to show some error message(s)?
+ emit versionReloaded();
+ throw;
+ }
+}
+
+void OneSixInstance::clearProfile()
+{
+ m_version->clear();
+ emit versionReloaded();
+}
+
+std::shared_ptr<MinecraftProfile> OneSixInstance::getMinecraftProfile() const
+{
+ return m_version;
+}
+
+QString OneSixInstance::getStatusbarDescription()
+{
+ QStringList traits;
+ if (flags() & VersionBrokenFlag)
+ {
+ traits.append(tr("broken"));
+ }
+
+ if (traits.size())
+ {
+ return tr("Minecraft %1 (%2)").arg(intendedVersionId()).arg(traits.join(", "));
+ }
+ else
+ {
+ return tr("Minecraft %1").arg(intendedVersionId());
+ }
+}
+
+QDir OneSixInstance::librariesPath() const
+{
+ return QDir::current().absoluteFilePath("libraries");
+}
+
+QDir OneSixInstance::jarmodsPath() const
+{
+ return QDir(jarModsDir());
+}
+
+QDir OneSixInstance::versionsPath() const
+{
+ return QDir::current().absoluteFilePath("versions");
+}
+
+bool OneSixInstance::providesVersionFile() const
+{
+ return false;
+}
+
+bool OneSixInstance::reload()
+{
+ if (BaseInstance::reload())
+ {
+ try
+ {
+ reloadProfile();
+ return true;
+ }
+ catch (...)
+ {
+ return false;
+ }
+ }
+ return false;
+}
+
+QString OneSixInstance::loaderModsDir() const
+{
+ return PathCombine(minecraftRoot(), "mods");
+}
+
+QString OneSixInstance::coreModsDir() const
+{
+ return PathCombine(minecraftRoot(), "coremods");
+}
+
+QString OneSixInstance::resourcePacksDir() const
+{
+ return PathCombine(minecraftRoot(), "resourcepacks");
+}
+
+QString OneSixInstance::texturePacksDir() const
+{
+ return PathCombine(minecraftRoot(), "texturepacks");
+}
+
+QString OneSixInstance::instanceConfigFolder() const
+{
+ return PathCombine(minecraftRoot(), "config");
+}
+
+QString OneSixInstance::jarModsDir() const
+{
+ return PathCombine(instanceRoot(), "jarmods");
+}
+
+QString OneSixInstance::libDir() const
+{
+ return PathCombine(minecraftRoot(), "lib");
+}
+
+QStringList OneSixInstance::extraArguments() const
+{
+ auto list = BaseInstance::extraArguments();
+ auto version = getMinecraftProfile();
+ if (!version)
+ return list;
+ auto jarMods = getJarMods();
+ if (!jarMods.isEmpty())
+ {
+ list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true",
+ "-Dfml.ignorePatchDiscrepancies=true"});
+ }
+ return list;
+}
+
+std::shared_ptr<OneSixInstance> OneSixInstance::getSharedPtr()
+{
+ return std::dynamic_pointer_cast<OneSixInstance>(BaseInstance::getSharedPtr());
+}
diff --git a/logic/minecraft/OneSixInstance.h b/logic/minecraft/OneSixInstance.h
new file mode 100644
index 00000000..3a9e528e
--- /dev/null
+++ b/logic/minecraft/OneSixInstance.h
@@ -0,0 +1,109 @@
+/* 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 "logic/minecraft/MinecraftInstance.h"
+
+#include "logic/minecraft/MinecraftProfile.h"
+#include "logic/minecraft/ModList.h"
+#include "gui/pages/BasePageProvider.h"
+
+class OneSixInstance : public MinecraftInstance, public BasePageProvider
+{
+ Q_OBJECT
+public:
+ explicit OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
+ virtual ~OneSixInstance(){};
+
+ virtual void init();
+
+ ////// Edit Instance Dialog stuff //////
+ virtual QList<BasePage *> getPages();
+ virtual QString dialogTitle();
+
+ ////// Mod Lists //////
+ std::shared_ptr<ModList> loaderModList() const;
+ std::shared_ptr<ModList> coreModList() const;
+ std::shared_ptr<ModList> resourcePackList() const override;
+ std::shared_ptr<ModList> texturePackList() const override;
+ virtual QList<Mod> getJarMods() const override;
+ virtual void createProfile();
+
+ virtual QSet<QString> traits();
+
+ ////// Directories and files //////
+ QString jarModsDir() const;
+ QString resourcePacksDir() const;
+ QString texturePacksDir() const;
+ QString loaderModsDir() const;
+ QString coreModsDir() const;
+ QString libDir() const;
+ virtual QString instanceConfigFolder() const override;
+
+ virtual std::shared_ptr<Task> doUpdate() override;
+ virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override;
+
+ virtual void cleanupAfterRun() override;
+
+ virtual QString intendedVersionId() const override;
+ virtual bool setIntendedVersionId(QString version) override;
+
+ virtual QString currentVersionId() const override;
+
+ virtual bool shouldUpdate() const override;
+ virtual void setShouldUpdate(bool val) override;
+
+ /**
+ * reload the profile, including version json files.
+ *
+ * throws various exceptions :3
+ */
+ void reloadProfile();
+
+ /// clears all version information in preparation for an update
+ void clearProfile();
+
+ /// get the current full version info
+ std::shared_ptr<MinecraftProfile> getMinecraftProfile() const;
+
+ virtual QString getStatusbarDescription() override;
+
+ virtual QDir jarmodsPath() const;
+ virtual QDir librariesPath() const;
+ virtual QDir versionsPath() const;
+ virtual bool providesVersionFile() const;
+
+ bool reload() override;
+
+ virtual QStringList extraArguments() const override;
+
+ std::shared_ptr<OneSixInstance> getSharedPtr();
+
+signals:
+ void versionReloaded();
+
+private:
+ QStringList processMinecraftArgs(AuthSessionPtr account);
+
+protected:
+ std::shared_ptr<MinecraftProfile> m_version;
+ mutable std::shared_ptr<ModList> m_loader_mod_list;
+ mutable std::shared_ptr<ModList> m_core_mod_list;
+ mutable std::shared_ptr<ModList> m_resource_pack_list;
+ mutable std::shared_ptr<ModList> m_texture_pack_list;
+};
+
+Q_DECLARE_METATYPE(std::shared_ptr<OneSixInstance>)
diff --git a/logic/minecraft/OneSixProfileStrategy.cpp b/logic/minecraft/OneSixProfileStrategy.cpp
index 9fcd0336..6d80963f 100644
--- a/logic/minecraft/OneSixProfileStrategy.cpp
+++ b/logic/minecraft/OneSixProfileStrategy.cpp
@@ -1,6 +1,6 @@
#include "logic/minecraft/OneSixProfileStrategy.h"
#include "logic/minecraft/VersionBuildError.h"
-#include "logic/OneSixInstance.h"
+#include "logic/minecraft/OneSixInstance.h"
#include "logic/minecraft/MinecraftVersionList.h"
#include "logic/Env.h"
diff --git a/logic/minecraft/OneSixUpdate.cpp b/logic/minecraft/OneSixUpdate.cpp
new file mode 100644
index 00000000..f9ab6c65
--- /dev/null
+++ b/logic/minecraft/OneSixUpdate.cpp
@@ -0,0 +1,445 @@
+/* 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 "logic/Env.h"
+#include "OneSixUpdate.h"
+
+#include <QtNetwork>
+
+#include <QFile>
+#include <QFileInfo>
+#include <QTextStream>
+#include <QDataStream>
+#include <pathutils.h>
+#include <JlCompress.h>
+
+#include "logic/BaseInstance.h"
+#include "logic/minecraft/MinecraftVersionList.h"
+#include "logic/minecraft/MinecraftProfile.h"
+#include "logic/minecraft/OneSixLibrary.h"
+#include "logic/minecraft/OneSixInstance.h"
+#include "logic/forge/ForgeMirrors.h"
+#include "logic/net/URLConstants.h"
+#include "logic/minecraft/AssetsUtils.h"
+#include "logic/minecraft/JarUtils.h"
+
+OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
+{
+}
+
+void OneSixUpdate::executeTask()
+{
+ // Make directories
+ QDir mcDir(m_inst->minecraftRoot());
+ if (!mcDir.exists() && !mcDir.mkpath("."))
+ {
+ emitFailed(tr("Failed to create folder for minecraft binaries."));
+ return;
+ }
+
+ // Get a pointer to the version object that corresponds to the instance's version.
+ targetVersion = std::dynamic_pointer_cast<MinecraftVersion>(
+ ENV.getVersion("net.minecraft", m_inst->intendedVersionId()));
+ if (targetVersion == nullptr)
+ {
+ // don't do anything if it was invalid
+ emitFailed(tr("The specified Minecraft version is invalid. Choose a different one."));
+ return;
+ }
+ if (m_inst->providesVersionFile() || !targetVersion->needsUpdate())
+ {
+ qDebug() << "Instance either provides a version file or doesn't need an update.";
+ jarlibStart();
+ return;
+ }
+ versionUpdateTask = std::dynamic_pointer_cast<MinecraftVersionList>(ENV.getVersionList("net.minecraft"))->createUpdateTask(m_inst->intendedVersionId());
+ if (!versionUpdateTask)
+ {
+ qDebug() << "Didn't spawn an update task.";
+ jarlibStart();
+ return;
+ }
+ connect(versionUpdateTask.get(), SIGNAL(succeeded()), SLOT(jarlibStart()));
+ connect(versionUpdateTask.get(), SIGNAL(failed(QString)), SLOT(versionUpdateFailed(QString)));
+ connect(versionUpdateTask.get(), SIGNAL(progress(qint64, qint64)),
+ SIGNAL(progress(qint64, qint64)));
+ setStatus(tr("Getting the version files from Mojang..."));
+ versionUpdateTask->start();
+}
+
+void OneSixUpdate::versionUpdateFailed(QString reason)
+{
+ emitFailed(reason);
+}
+
+void OneSixUpdate::assetIndexStart()
+{
+ setStatus(tr("Updating assets index..."));
+ OneSixInstance *inst = (OneSixInstance *)m_inst;
+ std::shared_ptr<MinecraftProfile> version = inst->getMinecraftProfile();
+ QString assetName = version->assets;
+ QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json";
+ QString localPath = assetName + ".json";
+ auto job = new NetJob(tr("Asset index for %1").arg(inst->name()));
+
+ auto metacache = ENV.metacache();
+ auto entry = metacache->resolveEntry("asset_indexes", localPath);
+ job->addNetAction(CacheDownload::make(indexUrl, entry));
+ jarlibDownloadJob.reset(job);
+
+ connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished()));
+ connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetIndexFailed()));
+ connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)),
+ SIGNAL(progress(qint64, qint64)));
+
+ jarlibDownloadJob->start();
+}
+
+void OneSixUpdate::assetIndexFinished()
+{
+ AssetsIndex index;
+
+ OneSixInstance *inst = (OneSixInstance *)m_inst;
+ std::shared_ptr<MinecraftProfile> version = inst->getMinecraftProfile();
+ QString assetName = version->assets;
+
+ QString asset_fname = "assets/indexes/" + assetName + ".json";
+ if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index))
+ {
+ auto metacache = ENV.metacache();
+ auto entry = metacache->resolveEntry("asset_indexes", assetName + ".json");
+ metacache->evictEntry(entry);
+ emitFailed(tr("Failed to read the assets index!"));
+ }
+
+ QList<Md5EtagDownloadPtr> dls;
+ for (auto object : index.objects.values())
+ {
+ QString objectName = object.hash.left(2) + "/" + object.hash;
+ QFileInfo objectFile("assets/objects/" + objectName);
+ if ((!objectFile.isFile()) || (objectFile.size() != object.size))
+ {
+ auto objectDL = MD5EtagDownload::make(
+ QUrl("http://" + URLConstants::RESOURCE_BASE + objectName),
+ objectFile.filePath());
+ objectDL->m_total_progress = object.size;
+ dls.append(objectDL);
+ }
+ }
+ if (dls.size())
+ {
+ setStatus(tr("Getting the assets files from Mojang..."));
+ auto job = new NetJob(tr("Assets for %1").arg(inst->name()));
+ for (auto dl : dls)
+ job->addNetAction(dl);
+ jarlibDownloadJob.reset(job);
+ connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished()));
+ connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetsFailed()));
+ connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)),
+ SIGNAL(progress(qint64, qint64)));
+ jarlibDownloadJob->start();
+ return;
+ }
+ assetsFinished();
+}
+
+void OneSixUpdate::assetIndexFailed()
+{
+ emitFailed(tr("Failed to download the assets index!"));
+}
+
+void OneSixUpdate::assetsFinished()
+{
+ emitSucceeded();
+}
+
+void OneSixUpdate::assetsFailed()
+{
+ emitFailed(tr("Failed to download assets!"));
+}
+
+void OneSixUpdate::jarlibStart()
+{
+ setStatus(tr("Getting the library files from Mojang..."));
+ qDebug() << m_inst->name() << ": downloading libraries";
+ OneSixInstance *inst = (OneSixInstance *)m_inst;
+ try
+ {
+ inst->reloadProfile();
+ }
+ catch (MMCError &e)
+ {
+ emitFailed(e.cause());
+ return;
+ }
+ catch (...)
+ {
+ 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<MinecraftProfile> version = inst->getMinecraftProfile();
+ // minecraft.jar for this version
+ {
+ QString version_id = version->id;
+ QString localPath = version_id + "/" + version_id + ".jar";
+ QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + localPath;
+
+ auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name()));
+
+ auto metacache = ENV.metacache();
+ auto entry = metacache->resolveEntry("versions", localPath);
+ job->addNetAction(CacheDownload::make(QUrl(urlstr), entry));
+ jarHashOnEntry = entry->md5sum;
+
+ jarlibDownloadJob.reset(job);
+ }
+
+ auto libs = version->getActiveNativeLibs();
+ libs.append(version->getActiveNormalLibs());
+
+ auto metacache = ENV.metacache();
+ QList<ForgeXzDownloadPtr> ForgeLibs;
+ QList<std::shared_ptr<OneSixLibrary>> brokenLocalLibs;
+
+ for (auto lib : libs)
+ {
+ if (lib->hint() == "local")
+ {
+ if (!lib->filesExist(m_inst->librariesPath()))
+ brokenLocalLibs.append(lib);
+ continue;
+ }
+
+ QString raw_storage = lib->storagePath();
+ QString raw_dl = lib->downloadUrl();
+
+ auto f = [&](QString storage, QString dl)
+ {
+ auto entry = metacache->resolveEntry("libraries", storage);
+ if (entry->stale)
+ {
+ if (lib->hint() == "forge-pack-xz")
+ {
+ ForgeLibs.append(ForgeXzDownload::make(storage, entry));
+ }
+ else
+ {
+ jarlibDownloadJob->addNetAction(CacheDownload::make(dl, entry));
+ }
+ }
+ };
+ if (raw_storage.contains("${arch}"))
+ {
+ QString cooked_storage = raw_storage;
+ QString cooked_dl = raw_dl;
+ f(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"));
+ cooked_storage = raw_storage;
+ cooked_dl = raw_dl;
+ f(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"));
+ }
+ else
+ {
+ f(raw_storage, raw_dl);
+ }
+ }
+ if (!brokenLocalLibs.empty())
+ {
+ jarlibDownloadJob.reset();
+ QStringList failed;
+ for (auto brokenLib : brokenLocalLibs)
+ {
+ failed.append(brokenLib->files());
+ }
+ QString failed_all = failed.join("\n");
+ emitFailed(tr("Some libraries marked as 'local' are missing their jar "
+ "files:\n%1\n\nYou'll have to correct this problem manually. If this is "
+ "an externally tracked instance, make sure to run it at least once "
+ "outside of MultiMC.").arg(failed_all));
+ return;
+ }
+ // TODO: think about how to propagate this from the original json file... or IF AT ALL
+ QString forgeMirrorList = "http://files.minecraftforge.net/mirror-brand.list";
+ if (!ForgeLibs.empty())
+ {
+ jarlibDownloadJob->addNetAction(
+ ForgeMirrors::make(ForgeLibs, jarlibDownloadJob, forgeMirrorList));
+ }
+
+ connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished()));
+ connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(jarlibFailed()));
+ connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)),
+ SIGNAL(progress(qint64, qint64)));
+
+ jarlibDownloadJob->start();
+}
+
+void OneSixUpdate::jarlibFinished()
+{
+ OneSixInstance *inst = (OneSixInstance *)m_inst;
+ std::shared_ptr<MinecraftProfile> version = inst->getMinecraftProfile();
+
+ // nuke obsolete stripped jar(s) if needed
+ QString version_id = version->id;
+ QString strippedPath = version_id + "/" + version_id + "-stripped.jar";
+ QFile strippedJar(strippedPath);
+ if(strippedJar.exists())
+ {
+ strippedJar.remove();
+ }
+ auto finalJarPath = QDir(m_inst->instanceRoot()).absoluteFilePath("temp.jar");
+ QFile finalJar(finalJarPath);
+ if(finalJar.exists())
+ {
+ if(!finalJar.remove())
+ {
+ emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath));
+ return;
+ }
+ }
+
+ // create temporary modded jar, if needed
+ auto jarMods = inst->getJarMods();
+ if(jarMods.size())
+ {
+ auto sourceJarPath = m_inst->versionsPath().absoluteFilePath(version->id + "/" + version->id + ".jar");
+ QString localPath = version_id + "/" + version_id + ".jar";
+ auto metacache = ENV.metacache();
+ auto entry = metacache->resolveEntry("versions", localPath);
+ QString fullJarPath = entry->getFullPath();
+ if(!JarUtils::createModdedJar(sourceJarPath, finalJarPath, jarMods))
+ {
+ emitFailed(tr("Failed to create the custom Minecraft jar file."));
+ return;
+ }
+ }
+ if (version->traits.contains("legacyFML"))
+ {
+ fmllibsStart();
+ }
+ else
+ {
+ assetIndexStart();
+ }
+}
+
+void OneSixUpdate::jarlibFailed()
+{
+ QStringList failed = jarlibDownloadJob->getFailedFiles();
+ QString failed_all = failed.join("\n");
+ emitFailed(
+ tr("Failed to download the following files:\n%1\n\nPlease try again.").arg(failed_all));
+}
+
+void OneSixUpdate::fmllibsStart()
+{
+ // Get the mod list
+ OneSixInstance *inst = (OneSixInstance *)m_inst;
+ std::shared_ptr<MinecraftProfile> fullversion = inst->getMinecraftProfile();
+ bool forge_present = false;
+
+ QString version = inst->intendedVersionId();
+ auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
+ if (!fmlLibsMapping.contains(version))
+ {
+ assetIndexStart();
+ return;
+ }
+
+ auto &libList = fmlLibsMapping[version];
+
+ // determine if we need some libs for FML or forge
+ setStatus(tr("Checking for FML libraries..."));
+ forge_present = (fullversion->versionPatch("net.minecraftforge") != nullptr);
+ // we don't...
+ if (!forge_present)
+ {
+ assetIndexStart();
+ return;
+ }
+
+ // now check the lib folder inside the instance for files.
+ for (auto &lib : libList)
+ {
+ QFileInfo libInfo(PathCombine(inst->libDir(), lib.filename));
+ if (libInfo.exists())
+ continue;
+ fmlLibsToProcess.append(lib);
+ }
+
+ // if everything is in place, there's nothing to do here...
+ if (fmlLibsToProcess.isEmpty())
+ {
+ assetIndexStart();
+ return;
+ }
+
+ // download missing libs to our place
+ setStatus(tr("Dowloading FML libraries..."));
+ auto dljob = new NetJob("FML libraries");
+ auto metacache = ENV.metacache();
+ for (auto &lib : fmlLibsToProcess)
+ {
+ auto entry = metacache->resolveEntry("fmllibs", lib.filename);
+ QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename
+ : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename;
+ dljob->addNetAction(CacheDownload::make(QUrl(urlString), entry));
+ }
+
+ connect(dljob, SIGNAL(succeeded()), SLOT(fmllibsFinished()));
+ connect(dljob, SIGNAL(failed()), SLOT(fmllibsFailed()));
+ connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
+ legacyDownloadJob.reset(dljob);
+ legacyDownloadJob->start();
+}
+
+void OneSixUpdate::fmllibsFinished()
+{
+ legacyDownloadJob.reset();
+ if (!fmlLibsToProcess.isEmpty())
+ {
+ setStatus(tr("Copying FML libraries into the instance..."));
+ OneSixInstance *inst = (OneSixInstance *)m_inst;
+ auto metacache = ENV.metacache();
+ int index = 0;
+ for (auto &lib : fmlLibsToProcess)
+ {
+ progress(index, fmlLibsToProcess.size());
+ auto entry = metacache->resolveEntry("fmllibs", lib.filename);
+ auto path = PathCombine(inst->libDir(), lib.filename);
+ if (!ensureFilePathExists(path))
+ {
+ emitFailed(tr("Failed creating FML library folder inside the instance."));
+ return;
+ }
+ if (!QFile::copy(entry->getFullPath(), PathCombine(inst->libDir(), lib.filename)))
+ {
+ emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename));
+ return;
+ }
+ index++;
+ }
+ progress(index, fmlLibsToProcess.size());
+ }
+ assetIndexStart();
+}
+
+void OneSixUpdate::fmllibsFailed()
+{
+ emitFailed("Game update failed: it was impossible to fetch the required FML libraries.");
+ return;
+}
+
diff --git a/logic/minecraft/OneSixUpdate.h b/logic/minecraft/OneSixUpdate.h
new file mode 100644
index 00000000..e3571e5a
--- /dev/null
+++ b/logic/minecraft/OneSixUpdate.h
@@ -0,0 +1,68 @@
+/* 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 <QObject>
+#include <QList>
+#include <QUrl>
+
+#include "logic/net/NetJob.h"
+#include "logic/tasks/Task.h"
+#include "logic/minecraft/VersionFilterData.h"
+#include <quazip.h>
+
+class MinecraftVersion;
+class OneSixInstance;
+
+class OneSixUpdate : public Task
+{
+ Q_OBJECT
+public:
+ explicit OneSixUpdate(OneSixInstance *inst, QObject *parent = 0);
+ virtual void executeTask();
+
+private
+slots:
+ void versionUpdateFailed(QString reason);
+
+ void jarlibStart();
+ void jarlibFinished();
+ void jarlibFailed();
+
+ void fmllibsStart();
+ void fmllibsFinished();
+ void fmllibsFailed();
+
+ void assetIndexStart();
+ void assetIndexFinished();
+ void assetIndexFailed();
+
+ void assetsFinished();
+ void assetsFailed();
+
+private:
+ NetJobPtr jarlibDownloadJob;
+ NetJobPtr legacyDownloadJob;
+
+ /// target version, determined during this task
+ std::shared_ptr<MinecraftVersion> targetVersion;
+ /// the task that is spawned for version updates
+ std::shared_ptr<Task> versionUpdateTask;
+
+ OneSixInstance *m_inst = nullptr;
+ QString jarHashOnEntry;
+ QList<FMLlib> fmlLibsToProcess;
+};
diff --git a/logic/minecraft/SkinUtils.cpp b/logic/minecraft/SkinUtils.cpp
new file mode 100644
index 00000000..f44c9c70
--- /dev/null
+++ b/logic/minecraft/SkinUtils.cpp
@@ -0,0 +1,47 @@
+/* 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 "logic/minecraft/SkinUtils.h"
+#include "logic/net/HttpMetaCache.h"
+#include "logic/Env.h"
+
+#include <QFile>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+
+namespace SkinUtils
+{
+/*
+ * Given a username, return a pixmap of the cached skin (if it exists), QPixmap() otherwise
+ */
+QPixmap getFaceFromCache(QString username, int height, int width)
+{
+ QFile fskin(ENV.metacache()
+ ->resolveEntry("skins", username + ".png")
+ ->getFullPath());
+
+ if (fskin.exists())
+ {
+ QPixmap skin(fskin.fileName());
+ if(!skin.isNull())
+ {
+ return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio);
+ }
+ }
+
+ return QPixmap();
+}
+}
diff --git a/logic/minecraft/SkinUtils.h b/logic/minecraft/SkinUtils.h
new file mode 100644
index 00000000..bab9d45e
--- /dev/null
+++ b/logic/minecraft/SkinUtils.h
@@ -0,0 +1,23 @@
+/* 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 <QPixmap>
+
+namespace SkinUtils
+{
+QPixmap getFaceFromCache(QString username, int height = 64, int width = 64);
+}
diff --git a/logic/minecraft/VersionBuilder.cpp b/logic/minecraft/VersionBuilder.cpp
index f3a0d490..bc29c187 100644
--- a/logic/minecraft/VersionBuilder.cpp
+++ b/logic/minecraft/VersionBuilder.cpp
@@ -35,7 +35,7 @@
#include "MinecraftVersionList.h"
#include "ProfileUtils.h"
-#include "logic/OneSixInstance.h"
+#include "logic/minecraft/OneSixInstance.h"
#include "logic/MMCJson.h"
#include <QDebug>