From 141e0a02a0a0c4bbc4cc2e900560db5048366104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Wed, 4 Feb 2015 21:10:10 +0100 Subject: SCRATCH move things to the right places --- logic/BaseInstaller.cpp | 2 +- logic/BaseInstance.h | 7 +- logic/InstanceList.cpp | 12 +- logic/JarUtils.cpp | 158 ------- logic/JarUtils.h | 18 - logic/LegacyInstance.cpp | 346 ---------------- logic/LegacyInstance.h | 127 ------ logic/LegacyUpdate.cpp | 468 --------------------- logic/LegacyUpdate.h | 72 ---- logic/LwjglVersionList.cpp | 189 --------- logic/LwjglVersionList.h | 154 ------- logic/Mod.cpp | 377 ----------------- logic/Mod.h | 130 ------ logic/ModList.cpp | 611 ---------------------------- logic/ModList.h | 158 ------- logic/OneSixInstance.cpp | 472 --------------------- logic/OneSixInstance.h | 109 ----- logic/OneSixUpdate.cpp | 445 -------------------- logic/OneSixUpdate.h | 68 ---- logic/SkinUtils.cpp | 47 --- logic/SkinUtils.h | 23 -- logic/assets/AssetsUtils.cpp | 217 ---------- logic/assets/AssetsUtils.h | 39 -- logic/forge/ForgeInstaller.cpp | 2 +- logic/forge/LegacyForge.h | 2 +- logic/ftb/LegacyFTBInstance.h | 2 +- logic/ftb/OneSixFTBInstance.h | 2 +- logic/liteloader/LiteLoaderInstaller.cpp | 2 +- logic/minecraft/AssetsUtils.cpp | 217 ++++++++++ logic/minecraft/AssetsUtils.h | 39 ++ logic/minecraft/JarUtils.cpp | 158 +++++++ logic/minecraft/JarUtils.h | 18 + logic/minecraft/LegacyInstance.cpp | 346 ++++++++++++++++ logic/minecraft/LegacyInstance.h | 127 ++++++ logic/minecraft/LegacyUpdate.cpp | 467 +++++++++++++++++++++ logic/minecraft/LegacyUpdate.h | 72 ++++ logic/minecraft/LwjglVersionList.cpp | 189 +++++++++ logic/minecraft/LwjglVersionList.h | 154 +++++++ logic/minecraft/MinecraftInstance.h | 3 + logic/minecraft/MinecraftProfile.cpp | 1 - logic/minecraft/Mod.cpp | 377 +++++++++++++++++ logic/minecraft/Mod.h | 130 ++++++ logic/minecraft/ModList.cpp | 610 +++++++++++++++++++++++++++ logic/minecraft/ModList.h | 158 +++++++ logic/minecraft/OneSixInstance.cpp | 472 +++++++++++++++++++++ logic/minecraft/OneSixInstance.h | 109 +++++ logic/minecraft/OneSixProfileStrategy.cpp | 2 +- logic/minecraft/OneSixUpdate.cpp | 445 ++++++++++++++++++++ logic/minecraft/OneSixUpdate.h | 68 ++++ logic/minecraft/SkinUtils.cpp | 47 +++ logic/minecraft/SkinUtils.h | 23 ++ logic/minecraft/VersionBuilder.cpp | 2 +- logic/notifications/NotificationChecker.cpp | 121 ++++++ logic/notifications/NotificationChecker.h | 54 +++ logic/updater/NotificationChecker.cpp | 121 ------ logic/updater/NotificationChecker.h | 54 --- 56 files changed, 4420 insertions(+), 4423 deletions(-) delete mode 100644 logic/JarUtils.cpp delete mode 100644 logic/JarUtils.h delete mode 100644 logic/LegacyInstance.cpp delete mode 100644 logic/LegacyInstance.h delete mode 100644 logic/LegacyUpdate.cpp delete mode 100644 logic/LegacyUpdate.h delete mode 100644 logic/LwjglVersionList.cpp delete mode 100644 logic/LwjglVersionList.h delete mode 100644 logic/Mod.cpp delete mode 100644 logic/Mod.h delete mode 100644 logic/ModList.cpp delete mode 100644 logic/ModList.h delete mode 100644 logic/OneSixInstance.cpp delete mode 100644 logic/OneSixInstance.h delete mode 100644 logic/OneSixUpdate.cpp delete mode 100644 logic/OneSixUpdate.h delete mode 100644 logic/SkinUtils.cpp delete mode 100644 logic/SkinUtils.h delete mode 100644 logic/assets/AssetsUtils.cpp delete mode 100644 logic/assets/AssetsUtils.h create mode 100644 logic/minecraft/AssetsUtils.cpp create mode 100644 logic/minecraft/AssetsUtils.h create mode 100644 logic/minecraft/JarUtils.cpp create mode 100644 logic/minecraft/JarUtils.h create mode 100644 logic/minecraft/LegacyInstance.cpp create mode 100644 logic/minecraft/LegacyInstance.h create mode 100644 logic/minecraft/LegacyUpdate.cpp create mode 100644 logic/minecraft/LegacyUpdate.h create mode 100644 logic/minecraft/LwjglVersionList.cpp create mode 100644 logic/minecraft/LwjglVersionList.h create mode 100644 logic/minecraft/Mod.cpp create mode 100644 logic/minecraft/Mod.h create mode 100644 logic/minecraft/ModList.cpp create mode 100644 logic/minecraft/ModList.h create mode 100644 logic/minecraft/OneSixInstance.cpp create mode 100644 logic/minecraft/OneSixInstance.h create mode 100644 logic/minecraft/OneSixUpdate.cpp create mode 100644 logic/minecraft/OneSixUpdate.h create mode 100644 logic/minecraft/SkinUtils.cpp create mode 100644 logic/minecraft/SkinUtils.h create mode 100644 logic/notifications/NotificationChecker.cpp create mode 100644 logic/notifications/NotificationChecker.h delete mode 100644 logic/updater/NotificationChecker.cpp delete mode 100644 logic/updater/NotificationChecker.h (limited to 'logic') diff --git a/logic/BaseInstaller.cpp b/logic/BaseInstaller.cpp index 0416c52b..b2cdefbc 100644 --- a/logic/BaseInstaller.cpp +++ b/logic/BaseInstaller.cpp @@ -16,7 +16,7 @@ #include #include "logic/BaseInstaller.h" -#include "logic/OneSixInstance.h" +#include "logic/minecraft/OneSixInstance.h" BaseInstaller::BaseInstaller() { diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index cd1dcdf0..3a4fa4a6 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -24,18 +24,13 @@ #include "logic/settings/INIFile.h" #include "logic/BaseVersionList.h" #include "logic/auth/MojangAccount.h" -#include "Mod.h" -class ModList; -class QDialog; class QDir; class Task; class BaseProcess; -class OneSixUpdate; -class BaseInstancePrivate; +class BaseInstance; // pointer for lazy people -class BaseInstance; typedef std::shared_ptr InstancePtr; /*! diff --git a/logic/InstanceList.cpp b/logic/InstanceList.cpp index ef6594d9..10faf1ca 100644 --- a/logic/InstanceList.cpp +++ b/logic/InstanceList.cpp @@ -25,16 +25,18 @@ #include #include #include +#include #include "logic/InstanceList.h" #include "logic/icons/IconList.h" -#include "logic/minecraft/MinecraftVersionList.h" #include "logic/BaseInstance.h" -#include "logic/ftb/FTBPlugin.h" + +//FIXME: this really doesn't belong *here* +#include "logic/minecraft/OneSixInstance.h" +#include "logic/minecraft/LegacyInstance.h" +#include "minecraft/MinecraftVersion.h" #include "settings/INISettingsObject.h" -#include "OneSixInstance.h" -#include "LegacyInstance.h" -#include +#include "logic/ftb/FTBPlugin.h" const static int GROUP_FILE_FORMAT_VERSION = 1; diff --git a/logic/JarUtils.cpp b/logic/JarUtils.cpp deleted file mode 100644 index e73af53c..00000000 --- a/logic/JarUtils.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "JarUtils.h" -#include -#include -#include -#include - -namespace JarUtils { - -bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, - std::function 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& 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 addedFiles; - - // Modify the jar - QListIterator 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/JarUtils.h b/logic/JarUtils.h deleted file mode 100644 index 2e8bd2a7..00000000 --- a/logic/JarUtils.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include -#include -#include -#include "Mod.h" -#include - -class QuaZip; -namespace JarUtils -{ - bool noFilter(QString); - bool metaInfFilter(QString key); - - bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, - std::function filter); - - bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods); -} diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp deleted file mode 100644 index 1d4d6150..00000000 --- a/logic/LegacyInstance.cpp +++ /dev/null @@ -1,346 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include - -#include "LegacyInstance.h" - -#include "logic/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 -#include -#include -#include - -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 LegacyInstance::getPages() -{ - QList 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 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(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(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 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 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 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 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 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/LegacyInstance.h b/logic/LegacyInstance.h deleted file mode 100644 index 353718c1..00000000 --- a/logic/LegacyInstance.h +++ /dev/null @@ -1,127 +0,0 @@ -/* 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 getPages(); - virtual QString dialogTitle(); - - ////// Mod Lists ////// - std::shared_ptr jarModList() const ; - virtual QList< Mod > getJarMods() const override; - std::shared_ptr coreModList() const; - std::shared_ptr loaderModList() const; - std::shared_ptr 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 traits() - { - return {"legacy-instance", "texturepacks"}; - }; - - virtual bool shouldUpdate() const override; - virtual void setShouldUpdate(bool val) override; - virtual std::shared_ptr doUpdate() override; - - virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override; - virtual void cleanupAfterRun() override; - - virtual QString getStatusbarDescription() override; - -protected: - mutable std::shared_ptr jar_mod_list; - mutable std::shared_ptr core_mod_list; - mutable std::shared_ptr loader_mod_list; - mutable std::shared_ptr texture_pack_list; - std::shared_ptr m_lwjglFolderSetting; -protected -slots: - virtual void jarModsChanged(); -}; diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp deleted file mode 100644 index 8b970f08..00000000 --- a/logic/LegacyUpdate.cpp +++ /dev/null @@ -1,468 +0,0 @@ -/* 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 - -#include -#include -#include -#include - -#include "logic/LegacyUpdate.h" -#include "logic/LwjglVersionList.h" -#include "logic/minecraft/MinecraftVersionList.h" -#include "logic/BaseInstance.h" -#include "logic/LegacyInstance.h" -#include "logic/Env.h" -#include "logic/ModList.h" - -#include -#include "logic/net/URLConstants.h" -#include "JarUtils.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(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(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(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 cookies = - qvariant_cast>(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(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/LegacyUpdate.h b/logic/LegacyUpdate.h deleted file mode 100644 index 78e456a1..00000000 --- a/logic/LegacyUpdate.h +++ /dev/null @@ -1,72 +0,0 @@ -/* 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 -#include -#include - -#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 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 fmlLibsToProcess; -}; diff --git a/logic/LwjglVersionList.cpp b/logic/LwjglVersionList.cpp deleted file mode 100644 index 9e101b74..00000000 --- a/logic/LwjglVersionList.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* 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 -#include -#include - -#include - -#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 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(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/LwjglVersionList.h b/logic/LwjglVersionList.h deleted file mode 100644 index 9205b964..00000000 --- a/logic/LwjglVersionList.h +++ /dev/null @@ -1,154 +0,0 @@ -/* 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 -#include -#include -#include - -#include -#include "BaseVersion.h" -#include "BaseVersionList.h" - -class LWJGLVersion; -typedef std::shared_ptr 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 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/Mod.cpp b/logic/Mod.cpp deleted file mode 100644 index 7fa4905e..00000000 --- a/logic/Mod.cpp +++ /dev/null @@ -1,377 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include - -#include "Mod.h" -#include -#include "logic/settings/INIFile.h" -#include - -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/Mod.h b/logic/Mod.h deleted file mode 100644 index 5b815bc1..00000000 --- a/logic/Mod.h +++ /dev/null @@ -1,130 +0,0 @@ -/* 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 - -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/ModList.cpp b/logic/ModList.cpp deleted file mode 100644 index 68d74e79..00000000 --- a/logic/ModList.cpp +++ /dev/null @@ -1,611 +0,0 @@ -/* 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 "LegacyInstance.h" -#include -#include -#include -#include -#include -#include -#include - -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 &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 orderedMods; - QList 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/ModList.h b/logic/ModList.h deleted file mode 100644 index 792ce062..00000000 --- a/logic/ModList.h +++ /dev/null @@ -1,158 +0,0 @@ -/* 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 -#include -#include -#include - -#include "logic/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 & allMods() - { - return mods; - } - -private: - void internalSort(QList & what); - struct OrderItem - { - QString id; - bool enabled = false; - }; - typedef QList 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 mods; -}; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp deleted file mode 100644 index 642a6aec..00000000 --- a/logic/OneSixInstance.cpp +++ /dev/null @@ -1,472 +0,0 @@ -/* 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 -#include -#include -#include "MMCError.h" - -#include "logic/OneSixInstance.h" - -#include "logic/OneSixUpdate.h" -#include "logic/minecraft/MinecraftProfile.h" -#include "minecraft/VersionBuildError.h" -#include "logic/minecraft/MinecraftProcess.h" -#include "minecraft/OneSixProfileStrategy.h" - -#include "logic/assets/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 OneSixInstance::getPages() -{ - QList 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 OneSixInstance::traits() -{ - auto version = getMinecraftProfile(); - if (!version) - { - return {"version-incomplete"}; - } - else - return version->traits; -} - -std::shared_ptr OneSixInstance::doUpdate() -{ - return std::shared_ptr(new OneSixUpdate(this)); -} - -QString replaceTokensIn(QString text, QMap 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 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(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 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 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 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 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 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 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::getSharedPtr() -{ - return std::dynamic_pointer_cast(BaseInstance::getSharedPtr()); -} diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h deleted file mode 100644 index 75e2dd3d..00000000 --- a/logic/OneSixInstance.h +++ /dev/null @@ -1,109 +0,0 @@ -/* 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/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 getPages(); - virtual QString dialogTitle(); - - ////// Mod Lists ////// - std::shared_ptr loaderModList() const; - std::shared_ptr coreModList() const; - std::shared_ptr resourcePackList() const override; - std::shared_ptr texturePackList() const override; - virtual QList getJarMods() const override; - virtual void createProfile(); - - virtual QSet 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 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 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 getSharedPtr(); - -signals: - void versionReloaded(); - -private: - QStringList processMinecraftArgs(AuthSessionPtr account); - -protected: - std::shared_ptr m_version; - mutable std::shared_ptr m_loader_mod_list; - mutable std::shared_ptr m_core_mod_list; - mutable std::shared_ptr m_resource_pack_list; - mutable std::shared_ptr m_texture_pack_list; -}; - -Q_DECLARE_METATYPE(std::shared_ptr) diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp deleted file mode 100644 index 5ec36305..00000000 --- a/logic/OneSixUpdate.cpp +++ /dev/null @@ -1,445 +0,0 @@ -/* 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 - -#include -#include -#include -#include -#include -#include - -#include "logic/BaseInstance.h" -#include "logic/minecraft/MinecraftVersionList.h" -#include "logic/minecraft/MinecraftProfile.h" -#include "logic/minecraft/OneSixLibrary.h" -#include "logic/OneSixInstance.h" -#include "logic/forge/ForgeMirrors.h" -#include "logic/net/URLConstants.h" -#include "logic/assets/AssetsUtils.h" -#include "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( - 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(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 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 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 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 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 ForgeLibs; - QList> 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 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 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/OneSixUpdate.h b/logic/OneSixUpdate.h deleted file mode 100644 index e3571e5a..00000000 --- a/logic/OneSixUpdate.h +++ /dev/null @@ -1,68 +0,0 @@ -/* 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 -#include -#include - -#include "logic/net/NetJob.h" -#include "logic/tasks/Task.h" -#include "logic/minecraft/VersionFilterData.h" -#include - -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 targetVersion; - /// the task that is spawned for version updates - std::shared_ptr versionUpdateTask; - - OneSixInstance *m_inst = nullptr; - QString jarHashOnEntry; - QList fmlLibsToProcess; -}; diff --git a/logic/SkinUtils.cpp b/logic/SkinUtils.cpp deleted file mode 100644 index 68d94117..00000000 --- a/logic/SkinUtils.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* 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/SkinUtils.h" -#include "net/HttpMetaCache.h" -#include "logic/Env.h" - -#include -#include -#include -#include - -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/SkinUtils.h b/logic/SkinUtils.h deleted file mode 100644 index bab9d45e..00000000 --- a/logic/SkinUtils.h +++ /dev/null @@ -1,23 +0,0 @@ -/* 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 - -namespace SkinUtils -{ -QPixmap getFaceFromCache(QString username, int height = 64, int width = 64); -} diff --git a/logic/assets/AssetsUtils.cpp b/logic/assets/AssetsUtils.cpp deleted file mode 100644 index 9f33b1bd..00000000 --- a/logic/assets/AssetsUtils.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include - -#include "AssetsUtils.h" -#include - -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 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/assets/AssetsUtils.h b/logic/assets/AssetsUtils.h deleted file mode 100644 index ea12136d..00000000 --- a/logic/assets/AssetsUtils.h +++ /dev/null @@ -1,39 +0,0 @@ -/* 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 -#include - -struct AssetObject -{ - QString hash; - qint64 size; -}; - -struct AssetsIndex -{ - QMap 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/forge/ForgeInstaller.cpp b/logic/forge/ForgeInstaller.cpp index b6fb426a..cbd96869 100644 --- a/logic/forge/ForgeInstaller.cpp +++ b/logic/forge/ForgeInstaller.cpp @@ -18,7 +18,7 @@ #include "logic/minecraft/OneSixLibrary.h" #include "logic/net/HttpMetaCache.h" #include "logic/tasks/Task.h" -#include "logic/OneSixInstance.h" +#include "logic/minecraft/OneSixInstance.h" #include "logic/forge/ForgeVersionList.h" #include "logic/minecraft/VersionFilterData.h" #include "logic/Env.h" diff --git a/logic/forge/LegacyForge.h b/logic/forge/LegacyForge.h index 7795ee0d..5bc43fbf 100644 --- a/logic/forge/LegacyForge.h +++ b/logic/forge/LegacyForge.h @@ -15,7 +15,7 @@ #pragma once -#include "logic/Mod.h" +#include "logic/minecraft/Mod.h" class MinecraftForge : public Mod { diff --git a/logic/ftb/LegacyFTBInstance.h b/logic/ftb/LegacyFTBInstance.h index 32e85030..80c6c5ab 100644 --- a/logic/ftb/LegacyFTBInstance.h +++ b/logic/ftb/LegacyFTBInstance.h @@ -1,6 +1,6 @@ #pragma once -#include "logic/LegacyInstance.h" +#include "logic/minecraft/LegacyInstance.h" class LegacyFTBInstance : public LegacyInstance { diff --git a/logic/ftb/OneSixFTBInstance.h b/logic/ftb/OneSixFTBInstance.h index d5cdc362..7af7c770 100644 --- a/logic/ftb/OneSixFTBInstance.h +++ b/logic/ftb/OneSixFTBInstance.h @@ -1,6 +1,6 @@ #pragma once -#include "logic/OneSixInstance.h" +#include "logic/minecraft/OneSixInstance.h" class OneSixLibrary; diff --git a/logic/liteloader/LiteLoaderInstaller.cpp b/logic/liteloader/LiteLoaderInstaller.cpp index b105be08..fadcc790 100644 --- a/logic/liteloader/LiteLoaderInstaller.cpp +++ b/logic/liteloader/LiteLoaderInstaller.cpp @@ -22,7 +22,7 @@ #include "logic/minecraft/MinecraftProfile.h" #include "logic/minecraft/OneSixLibrary.h" -#include "logic/OneSixInstance.h" +#include "logic/minecraft/OneSixInstance.h" #include "logic/liteloader/LiteLoaderVersionList.h" LiteLoaderInstaller::LiteLoaderInstaller() : BaseInstaller() 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 +#include +#include +#include +#include +#include +#include +#include + +#include "AssetsUtils.h" +#include + +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 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 +#include + +struct AssetObject +{ + QString hash; + qint64 size; +}; + +struct AssetsIndex +{ + QMap 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 +#include +#include +#include + +namespace JarUtils { + +bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, + std::function 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& 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 addedFiles; + + // Modify the jar + QListIterator 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 +#include +#include +#include "Mod.h" +#include + +class QuaZip; +namespace JarUtils +{ + bool noFilter(QString); + bool metaInfFilter(QString key); + + bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, + std::function filter); + + bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& 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 +#include +#include +#include +#include +#include + +#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 +#include +#include +#include + +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 LegacyInstance::getPages() +{ + QList 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 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(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(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 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 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 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 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 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 getPages(); + virtual QString dialogTitle(); + + ////// Mod Lists ////// + std::shared_ptr jarModList() const ; + virtual QList< Mod > getJarMods() const override; + std::shared_ptr coreModList() const; + std::shared_ptr loaderModList() const; + std::shared_ptr 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 traits() + { + return {"legacy-instance", "texturepacks"}; + }; + + virtual bool shouldUpdate() const override; + virtual void setShouldUpdate(bool val) override; + virtual std::shared_ptr doUpdate() override; + + virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override; + virtual void cleanupAfterRun() override; + + virtual QString getStatusbarDescription() override; + +protected: + mutable std::shared_ptr jar_mod_list; + mutable std::shared_ptr core_mod_list; + mutable std::shared_ptr loader_mod_list; + mutable std::shared_ptr texture_pack_list; + std::shared_ptr 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 +#include +#include +#include +#include +#include + +#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(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(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(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 cookies = + qvariant_cast>(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(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 +#include +#include + +#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 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 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 +#include +#include + +#include + +#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 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(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 +#include +#include +#include +#include + +#include "logic/BaseVersion.h" +#include "logic/BaseVersionList.h" + +class LWJGLVersion; +typedef std::shared_ptr 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 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 +#include +#include +#include +#include +#include +#include +#include + +#include "Mod.h" +#include +#include "logic/settings/INIFile.h" +#include + +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 + +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 +#include +#include +#include +#include +#include +#include + +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 &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 orderedMods; + QList 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 +#include +#include +#include + +#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 & allMods() + { + return mods; + } + +private: + void internalSort(QList & what); + struct OrderItem + { + QString id; + bool enabled = false; + }; + typedef QList 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 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 +#include +#include +#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 OneSixInstance::getPages() +{ + QList 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 OneSixInstance::traits() +{ + auto version = getMinecraftProfile(); + if (!version) + { + return {"version-incomplete"}; + } + else + return version->traits; +} + +std::shared_ptr OneSixInstance::doUpdate() +{ + return std::shared_ptr(new OneSixUpdate(this)); +} + +QString replaceTokensIn(QString text, QMap 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 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(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 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 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 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 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 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 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::getSharedPtr() +{ + return std::dynamic_pointer_cast(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 getPages(); + virtual QString dialogTitle(); + + ////// Mod Lists ////// + std::shared_ptr loaderModList() const; + std::shared_ptr coreModList() const; + std::shared_ptr resourcePackList() const override; + std::shared_ptr texturePackList() const override; + virtual QList getJarMods() const override; + virtual void createProfile(); + + virtual QSet 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 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 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 getSharedPtr(); + +signals: + void versionReloaded(); + +private: + QStringList processMinecraftArgs(AuthSessionPtr account); + +protected: + std::shared_ptr m_version; + mutable std::shared_ptr m_loader_mod_list; + mutable std::shared_ptr m_core_mod_list; + mutable std::shared_ptr m_resource_pack_list; + mutable std::shared_ptr m_texture_pack_list; +}; + +Q_DECLARE_METATYPE(std::shared_ptr) 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 + +#include +#include +#include +#include +#include +#include + +#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( + 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(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 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 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 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 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 ForgeLibs; + QList> 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 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 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 +#include +#include + +#include "logic/net/NetJob.h" +#include "logic/tasks/Task.h" +#include "logic/minecraft/VersionFilterData.h" +#include + +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 targetVersion; + /// the task that is spawned for version updates + std::shared_ptr versionUpdateTask; + + OneSixInstance *m_inst = nullptr; + QString jarHashOnEntry; + QList 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 +#include +#include +#include + +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 + +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 diff --git a/logic/notifications/NotificationChecker.cpp b/logic/notifications/NotificationChecker.cpp new file mode 100644 index 00000000..3f8b17ba --- /dev/null +++ b/logic/notifications/NotificationChecker.cpp @@ -0,0 +1,121 @@ +#include "NotificationChecker.h" + +#include +#include +#include + +#include "logic/Env.h" +#include "BuildConfig.h" +#include "logic/net/CacheDownload.h" +#include + +NotificationChecker::NotificationChecker(QObject *parent) + : QObject(parent), m_notificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL)) +{ + // this will call checkForNotifications once the event loop is running + QMetaObject::invokeMethod(this, "checkForNotifications", Qt::QueuedConnection); +} + +QUrl NotificationChecker::notificationsUrl() const +{ + return m_notificationsUrl; +} +void NotificationChecker::setNotificationsUrl(const QUrl ¬ificationsUrl) +{ + m_notificationsUrl = notificationsUrl; +} + +QList NotificationChecker::notificationEntries() const +{ + return m_entries; +} + +void NotificationChecker::checkForNotifications() +{ + if (!m_notificationsUrl.isValid()) + { + qCritical() << "Failed to check for notifications. No notifications URL set." + << "If you'd like to use MultiMC's notification system, please pass the " + "URL to CMake at compile time."; + return; + } + if (m_checkJob) + { + return; + } + m_checkJob.reset(new NetJob("Checking for notifications")); + auto entry = ENV.metacache()->resolveEntry("root", "notifications.json"); + entry->stale = true; + m_checkJob->addNetAction(m_download = CacheDownload::make(m_notificationsUrl, entry)); + connect(m_download.get(), &CacheDownload::succeeded, this, + &NotificationChecker::downloadSucceeded); + m_checkJob->start(); +} + +void NotificationChecker::downloadSucceeded(int) +{ + m_entries.clear(); + + QFile file(m_download->getTargetFilepath()); + if (file.open(QFile::ReadOnly)) + { + QJsonArray root = QJsonDocument::fromJson(file.readAll()).array(); + for (auto it = root.begin(); it != root.end(); ++it) + { + QJsonObject obj = (*it).toObject(); + NotificationEntry entry; + entry.id = obj.value("id").toDouble(); + entry.message = obj.value("message").toString(); + entry.channel = obj.value("channel").toString(); + entry.platform = obj.value("platform").toString(); + entry.from = obj.value("from").toString(); + entry.to = obj.value("to").toString(); + const QString type = obj.value("type").toString("critical"); + if (type == "critical") + { + entry.type = NotificationEntry::Critical; + } + else if (type == "warning") + { + entry.type = NotificationEntry::Warning; + } + else if (type == "information") + { + entry.type = NotificationEntry::Information; + } + m_entries.append(entry); + } + } + + m_checkJob.reset(); + + emit notificationCheckFinished(); +} + +bool NotificationChecker::NotificationEntry::applies() const +{ + bool channelApplies = channel.isEmpty() || channel == BuildConfig.VERSION_CHANNEL; + bool platformApplies = platform.isEmpty() || platform == BuildConfig.BUILD_PLATFORM; + bool fromApplies = + from.isEmpty() || from == BuildConfig.FULL_VERSION_STR || !versionLessThan(BuildConfig.FULL_VERSION_STR, from); + bool toApplies = + to.isEmpty() || to == BuildConfig.FULL_VERSION_STR || !versionLessThan(to, BuildConfig.FULL_VERSION_STR); + return channelApplies && platformApplies && fromApplies && toApplies; +} + +bool NotificationChecker::NotificationEntry::versionLessThan(const QString &v1, + const QString &v2) +{ + QStringList l1 = v1.split('.'); + QStringList l2 = v2.split('.'); + while (!l1.isEmpty() && !l2.isEmpty()) + { + int one = l1.isEmpty() ? 0 : l1.takeFirst().toInt(); + int two = l2.isEmpty() ? 0 : l2.takeFirst().toInt(); + if (one != two) + { + return one < two; + } + } + return false; +} diff --git a/logic/notifications/NotificationChecker.h b/logic/notifications/NotificationChecker.h new file mode 100644 index 00000000..915ee54d --- /dev/null +++ b/logic/notifications/NotificationChecker.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "logic/net/NetJob.h" +#include "logic/net/CacheDownload.h" + +class NotificationChecker : public QObject +{ + Q_OBJECT + +public: + explicit NotificationChecker(QObject *parent = 0); + + QUrl notificationsUrl() const; + void setNotificationsUrl(const QUrl ¬ificationsUrl); + + struct NotificationEntry + { + int id; + QString message; + enum + { + Critical, + Warning, + Information + } type; + QString channel; + QString platform; + QString from; + QString to; + bool applies() const; + static bool versionLessThan(const QString &v1, const QString &v2); + }; + + QList notificationEntries() const; + +public +slots: + void checkForNotifications(); + +private +slots: + void downloadSucceeded(int); + +signals: + void notificationCheckFinished(); + +private: + QList m_entries; + QUrl m_notificationsUrl; + NetJobPtr m_checkJob; + CacheDownloadPtr m_download; +}; diff --git a/logic/updater/NotificationChecker.cpp b/logic/updater/NotificationChecker.cpp deleted file mode 100644 index 3f8b17ba..00000000 --- a/logic/updater/NotificationChecker.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "NotificationChecker.h" - -#include -#include -#include - -#include "logic/Env.h" -#include "BuildConfig.h" -#include "logic/net/CacheDownload.h" -#include - -NotificationChecker::NotificationChecker(QObject *parent) - : QObject(parent), m_notificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL)) -{ - // this will call checkForNotifications once the event loop is running - QMetaObject::invokeMethod(this, "checkForNotifications", Qt::QueuedConnection); -} - -QUrl NotificationChecker::notificationsUrl() const -{ - return m_notificationsUrl; -} -void NotificationChecker::setNotificationsUrl(const QUrl ¬ificationsUrl) -{ - m_notificationsUrl = notificationsUrl; -} - -QList NotificationChecker::notificationEntries() const -{ - return m_entries; -} - -void NotificationChecker::checkForNotifications() -{ - if (!m_notificationsUrl.isValid()) - { - qCritical() << "Failed to check for notifications. No notifications URL set." - << "If you'd like to use MultiMC's notification system, please pass the " - "URL to CMake at compile time."; - return; - } - if (m_checkJob) - { - return; - } - m_checkJob.reset(new NetJob("Checking for notifications")); - auto entry = ENV.metacache()->resolveEntry("root", "notifications.json"); - entry->stale = true; - m_checkJob->addNetAction(m_download = CacheDownload::make(m_notificationsUrl, entry)); - connect(m_download.get(), &CacheDownload::succeeded, this, - &NotificationChecker::downloadSucceeded); - m_checkJob->start(); -} - -void NotificationChecker::downloadSucceeded(int) -{ - m_entries.clear(); - - QFile file(m_download->getTargetFilepath()); - if (file.open(QFile::ReadOnly)) - { - QJsonArray root = QJsonDocument::fromJson(file.readAll()).array(); - for (auto it = root.begin(); it != root.end(); ++it) - { - QJsonObject obj = (*it).toObject(); - NotificationEntry entry; - entry.id = obj.value("id").toDouble(); - entry.message = obj.value("message").toString(); - entry.channel = obj.value("channel").toString(); - entry.platform = obj.value("platform").toString(); - entry.from = obj.value("from").toString(); - entry.to = obj.value("to").toString(); - const QString type = obj.value("type").toString("critical"); - if (type == "critical") - { - entry.type = NotificationEntry::Critical; - } - else if (type == "warning") - { - entry.type = NotificationEntry::Warning; - } - else if (type == "information") - { - entry.type = NotificationEntry::Information; - } - m_entries.append(entry); - } - } - - m_checkJob.reset(); - - emit notificationCheckFinished(); -} - -bool NotificationChecker::NotificationEntry::applies() const -{ - bool channelApplies = channel.isEmpty() || channel == BuildConfig.VERSION_CHANNEL; - bool platformApplies = platform.isEmpty() || platform == BuildConfig.BUILD_PLATFORM; - bool fromApplies = - from.isEmpty() || from == BuildConfig.FULL_VERSION_STR || !versionLessThan(BuildConfig.FULL_VERSION_STR, from); - bool toApplies = - to.isEmpty() || to == BuildConfig.FULL_VERSION_STR || !versionLessThan(to, BuildConfig.FULL_VERSION_STR); - return channelApplies && platformApplies && fromApplies && toApplies; -} - -bool NotificationChecker::NotificationEntry::versionLessThan(const QString &v1, - const QString &v2) -{ - QStringList l1 = v1.split('.'); - QStringList l2 = v2.split('.'); - while (!l1.isEmpty() && !l2.isEmpty()) - { - int one = l1.isEmpty() ? 0 : l1.takeFirst().toInt(); - int two = l2.isEmpty() ? 0 : l2.takeFirst().toInt(); - if (one != two) - { - return one < two; - } - } - return false; -} diff --git a/logic/updater/NotificationChecker.h b/logic/updater/NotificationChecker.h deleted file mode 100644 index 915ee54d..00000000 --- a/logic/updater/NotificationChecker.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include - -#include "logic/net/NetJob.h" -#include "logic/net/CacheDownload.h" - -class NotificationChecker : public QObject -{ - Q_OBJECT - -public: - explicit NotificationChecker(QObject *parent = 0); - - QUrl notificationsUrl() const; - void setNotificationsUrl(const QUrl ¬ificationsUrl); - - struct NotificationEntry - { - int id; - QString message; - enum - { - Critical, - Warning, - Information - } type; - QString channel; - QString platform; - QString from; - QString to; - bool applies() const; - static bool versionLessThan(const QString &v1, const QString &v2); - }; - - QList notificationEntries() const; - -public -slots: - void checkForNotifications(); - -private -slots: - void downloadSucceeded(int); - -signals: - void notificationCheckFinished(); - -private: - QList m_entries; - QUrl m_notificationsUrl; - NetJobPtr m_checkJob; - CacheDownloadPtr m_download; -}; -- cgit v1.2.3