diff options
Diffstat (limited to 'logic')
-rw-r--r-- | logic/BaseInstaller.cpp | 9 | ||||
-rw-r--r-- | logic/BaseInstaller.h | 2 | ||||
-rw-r--r-- | logic/BaseInstance.cpp | 16 | ||||
-rw-r--r-- | logic/BaseInstance.h | 27 | ||||
-rw-r--r-- | logic/BaseInstance_p.h | 1 | ||||
-rw-r--r-- | logic/BaseVersion.h | 4 | ||||
-rw-r--r-- | logic/BaseVersionList.cpp (renamed from logic/lists/BaseVersionList.cpp) | 2 | ||||
-rw-r--r-- | logic/BaseVersionList.h (renamed from logic/lists/BaseVersionList.h) | 0 | ||||
-rw-r--r-- | logic/InstanceFactory.cpp | 79 | ||||
-rw-r--r-- | logic/InstanceLauncher.cpp | 2 | ||||
-rw-r--r-- | logic/InstanceList.cpp (renamed from logic/lists/InstanceList.cpp) | 28 | ||||
-rw-r--r-- | logic/InstanceList.h (renamed from logic/lists/InstanceList.h) | 0 | ||||
-rw-r--r-- | logic/LegacyFTBInstance.cpp | 5 | ||||
-rw-r--r-- | logic/LegacyFTBInstance.h | 1 | ||||
-rw-r--r-- | logic/LegacyInstance.cpp | 57 | ||||
-rw-r--r-- | logic/LegacyInstance.h | 14 | ||||
-rw-r--r-- | logic/LegacyUpdate.cpp | 85 | ||||
-rw-r--r-- | logic/LegacyUpdate.h | 9 | ||||
-rw-r--r-- | logic/LwjglVersionList.cpp (renamed from logic/lists/LwjglVersionList.cpp) | 0 | ||||
-rw-r--r-- | logic/LwjglVersionList.h (renamed from logic/lists/LwjglVersionList.h) | 0 | ||||
-rw-r--r-- | logic/MMCJson.cpp | 24 | ||||
-rw-r--r-- | logic/MMCJson.h | 19 | ||||
-rw-r--r-- | logic/MinecraftProcess.cpp | 11 | ||||
-rw-r--r-- | logic/MinecraftVersion.h | 89 | ||||
-rw-r--r-- | logic/ModList.cpp | 1 | ||||
-rw-r--r-- | logic/NostalgiaInstance.cpp | 36 | ||||
-rw-r--r-- | logic/NostalgiaInstance.h | 29 | ||||
-rw-r--r-- | logic/OneSixFTBInstance.cpp | 16 | ||||
-rw-r--r-- | logic/OneSixFTBInstance.h | 1 | ||||
-rw-r--r-- | logic/OneSixInstance.cpp | 301 | ||||
-rw-r--r-- | logic/OneSixInstance.h | 44 | ||||
-rw-r--r-- | logic/OneSixInstance_p.h | 13 | ||||
-rw-r--r-- | logic/OneSixUpdate.cpp | 357 | ||||
-rw-r--r-- | logic/OneSixUpdate.h | 21 | ||||
-rw-r--r-- | logic/OneSixVersionBuilder.cpp | 252 | ||||
-rw-r--r-- | logic/OneSixVersionBuilder.h | 47 | ||||
-rw-r--r-- | logic/URNResolver.cpp | 98 | ||||
-rw-r--r-- | logic/URNResolver.h | 18 | ||||
-rw-r--r-- | logic/VersionFile.h | 127 | ||||
-rw-r--r-- | logic/VersionFilterData.cpp | 68 | ||||
-rw-r--r-- | logic/VersionFilterData.h | 26 | ||||
-rw-r--r-- | logic/VersionFinal.cpp | 377 | ||||
-rw-r--r-- | logic/forge/ForgeInstaller.cpp (renamed from logic/ForgeInstaller.cpp) | 194 | ||||
-rw-r--r-- | logic/forge/ForgeInstaller.h (renamed from logic/ForgeInstaller.h) | 22 | ||||
-rw-r--r-- | logic/forge/ForgeMirror.h (renamed from logic/net/ForgeMirror.h) | 0 | ||||
-rw-r--r-- | logic/forge/ForgeMirrors.cpp (renamed from logic/net/ForgeMirrors.cpp) | 0 | ||||
-rw-r--r-- | logic/forge/ForgeMirrors.h (renamed from logic/net/ForgeMirrors.h) | 8 | ||||
-rw-r--r-- | logic/forge/ForgeVersion.cpp | 55 | ||||
-rw-r--r-- | logic/forge/ForgeVersion.h | 33 | ||||
-rw-r--r-- | logic/forge/ForgeVersionList.cpp (renamed from logic/lists/ForgeVersionList.cpp) | 84 | ||||
-rw-r--r-- | logic/forge/ForgeVersionList.h (renamed from logic/lists/ForgeVersionList.h) | 53 | ||||
-rw-r--r-- | logic/forge/ForgeXzDownload.cpp (renamed from logic/net/ForgeXzDownload.cpp) | 0 | ||||
-rw-r--r-- | logic/forge/ForgeXzDownload.h (renamed from logic/net/ForgeXzDownload.h) | 4 | ||||
-rw-r--r-- | logic/forge/LegacyForge.cpp (renamed from logic/LegacyForge.cpp) | 0 | ||||
-rw-r--r-- | logic/forge/LegacyForge.h (renamed from logic/LegacyForge.h) | 2 | ||||
-rw-r--r-- | logic/java/JavaChecker.cpp (renamed from logic/JavaChecker.cpp) | 0 | ||||
-rw-r--r-- | logic/java/JavaChecker.h (renamed from logic/JavaChecker.h) | 0 | ||||
-rw-r--r-- | logic/java/JavaCheckerJob.cpp (renamed from logic/JavaCheckerJob.cpp) | 0 | ||||
-rw-r--r-- | logic/java/JavaCheckerJob.h (renamed from logic/JavaCheckerJob.h) | 0 | ||||
-rw-r--r-- | logic/java/JavaUtils.cpp (renamed from logic/JavaUtils.cpp) | 18 | ||||
-rw-r--r-- | logic/java/JavaUtils.h (renamed from logic/JavaUtils.h) | 2 | ||||
-rw-r--r-- | logic/java/JavaVersionList.cpp (renamed from logic/lists/JavaVersionList.cpp) | 10 | ||||
-rw-r--r-- | logic/java/JavaVersionList.h (renamed from logic/lists/JavaVersionList.h) | 4 | ||||
-rw-r--r-- | logic/lists/MinecraftVersionList.cpp | 290 | ||||
-rw-r--r-- | logic/liteloader/LiteLoaderInstaller.cpp (renamed from logic/LiteLoaderInstaller.cpp) | 8 | ||||
-rw-r--r-- | logic/liteloader/LiteLoaderInstaller.h (renamed from logic/LiteLoaderInstaller.h) | 5 | ||||
-rw-r--r-- | logic/liteloader/LiteLoaderVersionList.cpp (renamed from logic/lists/LiteLoaderVersionList.cpp) | 0 | ||||
-rw-r--r-- | logic/liteloader/LiteLoaderVersionList.h (renamed from logic/lists/LiteLoaderVersionList.h) | 6 | ||||
-rw-r--r-- | logic/minecraft/InstanceVersion.cpp | 537 | ||||
-rw-r--r-- | logic/minecraft/InstanceVersion.h (renamed from logic/VersionFinal.h) | 73 | ||||
-rw-r--r-- | logic/minecraft/JarMod.cpp | 56 | ||||
-rw-r--r-- | logic/minecraft/JarMod.h | 18 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersion.cpp | 143 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersion.h | 103 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersionList.cpp | 602 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersionList.h (renamed from logic/lists/MinecraftVersionList.h) | 42 | ||||
-rw-r--r-- | logic/minecraft/OneSixLibrary.cpp (renamed from logic/OneSixLibrary.cpp) | 79 | ||||
-rw-r--r-- | logic/minecraft/OneSixLibrary.h (renamed from logic/OneSixLibrary.h) | 34 | ||||
-rw-r--r-- | logic/minecraft/OneSixRule.cpp (renamed from logic/OneSixRule.cpp) | 17 | ||||
-rw-r--r-- | logic/minecraft/OneSixRule.h (renamed from logic/OneSixRule.h) | 8 | ||||
-rw-r--r-- | logic/minecraft/OpSys.cpp (renamed from logic/OpSys.cpp) | 0 | ||||
-rw-r--r-- | logic/minecraft/OpSys.h (renamed from logic/OpSys.h) | 0 | ||||
-rw-r--r-- | logic/minecraft/ParseUtils.cpp | 24 | ||||
-rw-r--r-- | logic/minecraft/ParseUtils.h | 14 | ||||
-rw-r--r-- | logic/minecraft/RawLibrary.cpp | 205 | ||||
-rw-r--r-- | logic/minecraft/RawLibrary.h | 64 | ||||
-rw-r--r-- | logic/minecraft/VersionBuildError.h | 58 | ||||
-rw-r--r-- | logic/minecraft/VersionBuilder.cpp | 349 | ||||
-rw-r--r-- | logic/minecraft/VersionBuilder.h | 56 | ||||
-rw-r--r-- | logic/minecraft/VersionFile.cpp (renamed from logic/VersionFile.cpp) | 378 | ||||
-rw-r--r-- | logic/minecraft/VersionFile.h | 103 | ||||
-rw-r--r-- | logic/minecraft/VersionPatch.h | 31 | ||||
-rw-r--r-- | logic/minecraft/VersionSource.h | 9 | ||||
-rw-r--r-- | logic/net/NetJob.h | 1 | ||||
-rw-r--r-- | logic/screenshots/ImgurAlbumCreation.cpp | 4 | ||||
-rw-r--r-- | logic/screenshots/ImgurUpload.cpp | 9 | ||||
-rw-r--r-- | logic/screenshots/Screenshot.cpp | 14 | ||||
-rw-r--r-- | logic/screenshots/Screenshot.h | 16 | ||||
-rw-r--r-- | logic/screenshots/ScreenshotList.cpp | 113 | ||||
-rw-r--r-- | logic/screenshots/ScreenshotList.h | 70 | ||||
-rw-r--r-- | logic/tasks/ProgressProvider.h | 1 |
101 files changed, 3940 insertions, 2405 deletions
diff --git a/logic/BaseInstaller.cpp b/logic/BaseInstaller.cpp index 669fd0ac..5660eb07 100644 --- a/logic/BaseInstaller.cpp +++ b/logic/BaseInstaller.cpp @@ -13,15 +13,10 @@ * limitations under the License. */ -#include "BaseInstaller.h" - #include <QFile> -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" - -#include "cmdutils.h" +#include "logic/BaseInstaller.h" +#include "logic/OneSixInstance.h" BaseInstaller::BaseInstaller() { diff --git a/logic/BaseInstaller.h b/logic/BaseInstaller.h index d59833cc..9531fbff 100644 --- a/logic/BaseInstaller.h +++ b/logic/BaseInstaller.h @@ -29,7 +29,7 @@ class BaseInstaller { public: BaseInstaller(); - + virtual ~BaseInstaller(){}; bool isApplied(OneSixInstance *on); virtual bool add(OneSixInstance *to); diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index 5fa62593..15bf5ab6 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -27,7 +27,7 @@ #include "pathutils.h" #include <cmdutils.h> -#include "lists/MinecraftVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" #include "logic/icons/IconList.h" BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, @@ -58,6 +58,8 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, // Java Settings settings().registerSetting("OverrideJava", false); + settings().registerSetting("OverrideJavaLocation", false); + settings().registerSetting("OverrideJavaArgs", false); settings().registerOverride(globalSettings->getSetting("JavaPath")); settings().registerOverride(globalSettings->getSetting("JvmArgs")); @@ -104,6 +106,18 @@ QString BaseInstance::id() const return QFileInfo(instanceRoot()).fileName(); } +bool BaseInstance::isRunning() const +{ + I_D(BaseInstance); + return d->m_isRunning; +} + +void BaseInstance::setRunning(bool running) const +{ + I_D(BaseInstance); + d->m_isRunning = running; +} + QString BaseInstance::instanceType() const { I_D(BaseInstance); diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 16791592..5f5378e7 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -22,9 +22,10 @@ #include <settingsobject.h> #include "inifile.h" -#include "lists/BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/auth/MojangAccount.h" +class ModList; class QDialog; class QDir; class Task; @@ -64,6 +65,9 @@ public: /// be unique. virtual QString id() const; + virtual void setRunning(bool running) const; + virtual bool isRunning() const; + /// get the type of this instance QString instanceType() const; @@ -89,7 +93,7 @@ public: void setGroupInitial(QString val); void setGroupPost(QString val); - QStringList extraArguments() const; + virtual QStringList extraArguments() const; virtual QString intendedVersionId() const = 0; virtual bool setIntendedVersionId(QString version) = 0; @@ -110,6 +114,19 @@ public: virtual bool shouldUpdate() const = 0; virtual void setShouldUpdate(bool val) = 0; + ////// Mod Lists ////// + virtual std::shared_ptr<ModList> resourcePackList() + { + return nullptr; + } + virtual std::shared_ptr<ModList> texturePackList() + { + return nullptr; + } + + /// Traits. Normally inside the version, depends on instance implementation. + virtual QSet <QString> traits() = 0; + /// Get the curent base jar of this instance. By default, it's the /// versions/$version/$version.jar QString baseJar() const; @@ -169,12 +186,6 @@ public: /// 'prepareForLaunch' virtual void cleanupAfterRun() = 0; - /// create a mod edit dialog for the instance - virtual QDialog *createModEditDialog(QWidget *parent) = 0; - - /// is a particular action enabled with this instance selected? - virtual bool menuActionEnabled(QString action_name) const = 0; - virtual QString getStatusbarDescription() = 0; /// FIXME: this really should be elsewhere... diff --git a/logic/BaseInstance_p.h b/logic/BaseInstance_p.h index 999ff545..6498454f 100644 --- a/logic/BaseInstance_p.h +++ b/logic/BaseInstance_p.h @@ -32,4 +32,5 @@ public: QString m_group; std::shared_ptr<SettingsObject> m_settings; QSet<BaseInstance::InstanceFlag> m_flags; + bool m_isRunning = false; }; diff --git a/logic/BaseVersion.h b/logic/BaseVersion.h index 43f5942a..ed63f551 100644 --- a/logic/BaseVersion.h +++ b/logic/BaseVersion.h @@ -16,6 +16,8 @@ #pragma once #include <memory> +#include <QString> +#include <QMetaType> /*! * An abstract base class for versions. @@ -52,4 +54,4 @@ struct BaseVersion typedef std::shared_ptr<BaseVersion> BaseVersionPtr; -Q_DECLARE_METATYPE(BaseVersionPtr)
\ No newline at end of file +Q_DECLARE_METATYPE(BaseVersionPtr) diff --git a/logic/lists/BaseVersionList.cpp b/logic/BaseVersionList.cpp index 6e2c5282..b34750b5 100644 --- a/logic/lists/BaseVersionList.cpp +++ b/logic/BaseVersionList.cpp @@ -13,7 +13,7 @@ * limitations under the License. */ -#include "logic/lists/BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/BaseVersion.h" BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent) diff --git a/logic/lists/BaseVersionList.h b/logic/BaseVersionList.h index 21b44e8d..21b44e8d 100644 --- a/logic/lists/BaseVersionList.h +++ b/logic/BaseVersionList.h diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index 95fd855b..c0a392e0 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -13,28 +13,26 @@ * limitations under the License. */ -#include "InstanceFactory.h" - #include <QDir> #include <QFileInfo> - -#include "BaseInstance.h" -#include "LegacyInstance.h" -#include "LegacyFTBInstance.h" -#include "OneSixInstance.h" -#include "OneSixFTBInstance.h" -#include "NostalgiaInstance.h" -#include "OneSixInstance.h" -#include "BaseVersion.h" -#include "MinecraftVersion.h" - -#include "inifile.h" +#include <inifile.h> #include <inisettingsobject.h> #include <setting.h> -#include "pathutils.h" +#include <pathutils.h> #include "logger/QsLog.h" +#include "logic/InstanceFactory.h" + +#include "logic/BaseInstance.h" +#include "logic/LegacyInstance.h" +#include "logic/LegacyFTBInstance.h" +#include "logic/OneSixInstance.h" +#include "logic/OneSixFTBInstance.h" +#include "logic/OneSixInstance.h" +#include "logic/BaseVersion.h" +#include "logic/minecraft/MinecraftVersion.h" + InstanceFactory InstanceFactory::loader; InstanceFactory::InstanceFactory() : QObject(NULL) @@ -51,7 +49,7 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(InstancePtr &inst, QString inst_type = m_settings->get("InstanceType").toString(); // FIXME: replace with a map lookup, where instance classes register their types - if (inst_type == "OneSix") + if (inst_type == "OneSix" || inst_type == "Nostalgia") { inst.reset(new OneSixInstance(instDir, m_settings, this)); } @@ -59,10 +57,6 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(InstancePtr &inst, { inst.reset(new LegacyInstance(instDir, m_settings, this)); } - else if (inst_type == "Nostalgia") - { - inst.reset(new NostalgiaInstance(instDir, m_settings, this)); - } else if (inst_type == "LegacyFTB") { inst.reset(new LegacyFTBInstance(instDir, m_settings, this)); @@ -98,55 +92,26 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(InstancePtr &in if (type == NormalInst) { - switch (mcVer->type) - { - case MinecraftVersion::Legacy: - // TODO new instance type - m_settings->set("InstanceType", "Legacy"); - inst.reset(new LegacyInstance(instDir, m_settings, this)); - inst->setIntendedVersionId(version->descriptor()); - inst->setShouldUseCustomBaseJar(false); - break; - case MinecraftVersion::OneSix: - m_settings->set("InstanceType", "OneSix"); - inst.reset(new OneSixInstance(instDir, m_settings, this)); - inst->setIntendedVersionId(version->descriptor()); - inst->setShouldUseCustomBaseJar(false); - break; - case MinecraftVersion::Nostalgia: - m_settings->set("InstanceType", "Nostalgia"); - inst.reset(new NostalgiaInstance(instDir, m_settings, this)); - inst->setIntendedVersionId(version->descriptor()); - inst->setShouldUseCustomBaseJar(false); - break; - default: - { - delete m_settings; - return InstanceFactory::NoSuchVersion; - } - } + m_settings->set("InstanceType", "OneSix"); + inst.reset(new OneSixInstance(instDir, m_settings, this)); + inst->setIntendedVersionId(version->descriptor()); + inst->setShouldUseCustomBaseJar(false); } else if (type == FTBInstance) { - switch (mcVer->type) + if(mcVer->usesLegacyLauncher()) { - case MinecraftVersion::Legacy: m_settings->set("InstanceType", "LegacyFTB"); inst.reset(new LegacyFTBInstance(instDir, m_settings, this)); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); - break; - case MinecraftVersion::OneSix: + } + else + { m_settings->set("InstanceType", "OneSixFTB"); inst.reset(new OneSixFTBInstance(instDir, m_settings, this)); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); - break; - default: - { - delete m_settings; - return InstanceFactory::NoSuchVersion; - } } } else diff --git a/logic/InstanceLauncher.cpp b/logic/InstanceLauncher.cpp index c0079d80..9170c87f 100644 --- a/logic/InstanceLauncher.cpp +++ b/logic/InstanceLauncher.cpp @@ -22,7 +22,7 @@ #include "gui/dialogs/ProgressDialog.h" #include "logic/MinecraftProcess.h" -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" InstanceLauncher::InstanceLauncher(QString instId) : QObject(), instId(instId) { diff --git a/logic/lists/InstanceList.cpp b/logic/InstanceList.cpp index 1ff0d2ec..5a988fd3 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/InstanceList.cpp @@ -27,13 +27,13 @@ #include <pathutils.h> #include "MultiMC.h" -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" #include "logic/icons/IconList.h" -#include "logic/lists/MinecraftVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logger/QsLog.h" -#include <gui/groupview/GroupView.h> +#include "gui/groupview/GroupView.h" const static int GROUP_FILE_FORMAT_VERSION = 1; @@ -46,16 +46,6 @@ InstanceList::InstanceList(const QString &instDir, QObject *parent) { QDir::current().mkpath(m_instDir); } - - /* - * FIXME HACK: instances sometimes need to be created at launch. They need the versions for - * that. - * - * Remove this. it has no business of reloading the whole list. The instances which - * need it should track such events themselves and CHANGE THEIR DATA ONLY! - */ - connect(MMC->minecraftlist().get(), &MinecraftVersionList::modelReset, this, - &InstanceList::loadList); } InstanceList::~InstanceList() @@ -296,17 +286,16 @@ void InstanceList::loadGroupList(QMap<QString, QString> &groupMap) QList<FTBRecord> InstanceList::discoverFTBInstances() { QList<FTBRecord> records; - QDir dir = QDir(MMC->settings()->get("FTBLauncherRoot").toString()); + QDir dir = QDir(MMC->settings()->get("FTBLauncherDataRoot").toString()); QDir dataDir = QDir(MMC->settings()->get("FTBRoot").toString()); - if (!dir.exists()) + if (!dataDir.exists()) { - QLOG_INFO() << "The FTB launcher directory specified does not exist. Please check your " - "settings."; + QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings"; return records; } - else if (!dataDir.exists()) + else if (!dir.exists()) { - QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings"; + QLOG_INFO() << "The FTB launcher data directory specified does not exist. Please check your settings"; return records; } dir.cd("ModPacks"); @@ -337,6 +326,7 @@ QList<FTBRecord> InstanceList::discoverFTBInstances() record.instanceDir = dataDir.absoluteFilePath(record.dirName); record.templateDir = dir.absoluteFilePath(record.dirName); QDir test(record.instanceDir); + QLOG_DEBUG() << dataDir.absolutePath() << record.instanceDir << record.dirName; if (!test.exists()) continue; record.name = attrs.value("name").toString(); diff --git a/logic/lists/InstanceList.h b/logic/InstanceList.h index f0bbb7ec..f0bbb7ec 100644 --- a/logic/lists/InstanceList.h +++ b/logic/InstanceList.h diff --git a/logic/LegacyFTBInstance.cpp b/logic/LegacyFTBInstance.cpp index 3c3356c9..73a1f73d 100644 --- a/logic/LegacyFTBInstance.cpp +++ b/logic/LegacyFTBInstance.cpp @@ -14,11 +14,6 @@ QString LegacyFTBInstance::getStatusbarDescription() return "Legacy FTB: " + intendedVersionId(); } -bool LegacyFTBInstance::menuActionEnabled(QString action_name) const -{ - return false; -} - QString LegacyFTBInstance::id() const { return "FTB/" + BaseInstance::id(); diff --git a/logic/LegacyFTBInstance.h b/logic/LegacyFTBInstance.h index 70f60535..a2fe1ead 100644 --- a/logic/LegacyFTBInstance.h +++ b/logic/LegacyFTBInstance.h @@ -9,6 +9,5 @@ public: explicit LegacyFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent = 0); virtual QString getStatusbarDescription(); - virtual bool menuActionEnabled(QString action_name) const; virtual QString id() const; }; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 6648e059..5c15616a 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -28,8 +28,13 @@ #include "logic/MinecraftProcess.h" #include "logic/LegacyUpdate.h" #include "logic/icons/IconList.h" - -#include "gui/dialogs/LegacyModEditDialog.h" +#include "gui/pages/LegacyUpgradePage.h" +#include "gui/pages/ModFolderPage.h" +#include "gui/pages/LegacyJarModPage.h" +#include <gui/pages/TexturePackPage.h> +#include <gui/pages/InstanceSettingsPage.h> +#include <gui/pages/NotesPage.h> +#include <gui/pages/ScreenshotsPage.h> LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) @@ -42,6 +47,27 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, settings->registerSetting("IntendedJarVersion", ""); } +QList<BasePage *> LegacyInstance::getPages() +{ + QList<BasePage *> values; + values.append(new LegacyUpgradePage(this)); + values.append(new LegacyJarModPage(this)); + values.append(new ModFolderPage(this, loaderModList(), "mods", "plugin-blue", tr("Loader mods"), + "Loader-mods")); + values.append(new ModFolderPage(this, coreModList(), "coremods", "plugin-green", tr("Core mods"), + "Core-mods")); + values.append(new TexturePackPage(this)); + values.append(new NotesPage(this)); + values.append(new ScreenshotsPage(this)); + values.append(new InstanceSettingsPage(this)); + return values; +} + +QString LegacyInstance::dialogTitle() +{ + return tr("Edit Instance (%1)").arg(name()); +} + std::shared_ptr<Task> LegacyInstance::doUpdate() { // make sure the jar mods list is initialized by asking for it. @@ -50,7 +76,7 @@ std::shared_ptr<Task> LegacyInstance::doUpdate() return std::shared_ptr<Task>(new LegacyUpdate(this, this)); } -bool LegacyInstance::prepareForLaunch(AuthSessionPtr account, QString & launchScript) +bool LegacyInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScript) { QIcon icon = MMC->icons()->getIcon(iconKey()); auto pixmap = icon.pixmap(128, 128); @@ -136,11 +162,6 @@ std::shared_ptr<ModList> LegacyInstance::texturePackList() return d->texture_pack_list; } -QDialog *LegacyInstance::createModEditDialog(QWidget *parent) -{ - return new LegacyModEditDialog(this, parent); -} - QString LegacyInstance::jarModsDir() const { return PathCombine(instanceRoot(), "instMods"); @@ -263,27 +284,11 @@ QString LegacyInstance::defaultCustomBaseJar() const return PathCombine(binDir(), "mcbackup.jar"); } -bool LegacyInstance::menuActionEnabled(QString action_name) const -{ - if (flags().contains(VersionBrokenFlag)) - { - return false; - } - if (action_name == "actionChangeInstMCVersion") - { - return false; - } - return true; -} - QString LegacyInstance::getStatusbarDescription() { if (flags().contains(VersionBrokenFlag)) { - return "Legacy : " + intendedVersionId() + " (broken)"; + return tr("Legacy : %1 (broken)").arg(intendedVersionId()); } - if (shouldUpdate()) - return "Legacy : " + currentVersionId() + " -> " + intendedVersionId(); - else - return "Legacy : " + currentVersionId(); + return tr("Legacy : %1").arg(intendedVersionId()); } diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index aa80968e..76a8c24d 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -16,11 +16,12 @@ #pragma once #include "BaseInstance.h" +#include "gui/pages/BasePageProvider.h" class ModList; class Task; -class LegacyInstance : public BaseInstance +class LegacyInstance : public BaseInstance, public BasePageProvider { Q_OBJECT public: @@ -34,6 +35,10 @@ public: //! Path to the instance's modlist file. QString modListFile() const; + ////// Edit Instance Dialog stuff ////// + virtual QList<BasePage *> getPages(); + virtual QString dialogTitle(); + ////// Mod Lists ////// std::shared_ptr<ModList> jarModList(); std::shared_ptr<ModList> coreModList(); @@ -75,18 +80,21 @@ public: return false; } + virtual QSet<QString> traits() + { + return {"legacy-instance", "texturepacks"}; + }; + virtual bool shouldUpdate() const override; virtual void setShouldUpdate(bool val) override; virtual std::shared_ptr<Task> doUpdate() override; virtual bool prepareForLaunch(AuthSessionPtr account, QString & launchScript) override; virtual void cleanupAfterRun() override; - virtual QDialog *createModEditDialog(QWidget *parent) override; virtual QString defaultBaseJar() const override; virtual QString defaultCustomBaseJar() const override; - bool menuActionEnabled(QString action_name) const; virtual QString getStatusbarDescription() override; protected diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index 15c99234..00ee795d 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -13,69 +13,27 @@ * limitations under the License. */ -#include "LegacyUpdate.h" -#include "lists/LwjglVersionList.h" -#include "lists/MinecraftVersionList.h" -#include "BaseInstance.h" -#include "LegacyInstance.h" -#include "MultiMC.h" -#include "ModList.h" +#include <QStringList> + #include <pathutils.h> #include <quazip.h> #include <quazipfile.h> #include <JlCompress.h> + +#include "logic/LegacyUpdate.h" +#include "logic/LwjglVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/BaseInstance.h" +#include "logic/LegacyInstance.h" +#include "MultiMC.h" +#include "logic/ModList.h" + #include "logger/QsLog.h" #include "logic/net/URLConstants.h" -#include <QStringList> + LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { - // 1.3 - 1.3.2 - auto libs13 = QList<FMLlib>{ - {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}}; - - fmlLibsMapping["1.3.2"] = libs13; - - auto libs14 = QList<FMLlib>{ - {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}, - {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}}; - - fmlLibsMapping["1.4"] = libs14; - fmlLibsMapping["1.4.1"] = libs14; - fmlLibsMapping["1.4.2"] = libs14; - fmlLibsMapping["1.4.3"] = libs14; - fmlLibsMapping["1.4.4"] = libs14; - fmlLibsMapping["1.4.5"] = libs14; - fmlLibsMapping["1.4.6"] = libs14; - fmlLibsMapping["1.4.7"] = libs14; - - fmlLibsMapping["1.5"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - - fmlLibsMapping["1.5.1"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - - fmlLibsMapping["1.5.2"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; } void LegacyUpdate::executeTask() @@ -110,6 +68,7 @@ void LegacyUpdate::fmllibsStart() bool forge_present = false; QString version = inst->intendedVersionId(); + auto & fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; if (!fmlLibsMapping.contains(version)) { lwjglStart(); @@ -152,7 +111,7 @@ void LegacyUpdate::fmllibsStart() // now check the lib folder inside the instance for files. for (auto &lib : libList) { - QFileInfo libInfo(PathCombine(inst->libDir(), lib.name)); + QFileInfo libInfo(PathCombine(inst->libDir(), lib.filename)); if (libInfo.exists()) continue; fmlLibsToProcess.append(lib); @@ -171,9 +130,9 @@ void LegacyUpdate::fmllibsStart() auto metacache = MMC->metacache(); for (auto &lib : fmlLibsToProcess) { - auto entry = metacache->resolveEntry("fmllibs", lib.name); - QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.name - : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.name; + 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)); } @@ -196,16 +155,16 @@ void LegacyUpdate::fmllibsFinished() for (auto &lib : fmlLibsToProcess) { progress(index, fmlLibsToProcess.size()); - auto entry = metacache->resolveEntry("fmllibs", lib.name); - auto path = PathCombine(inst->libDir(), lib.name); + 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.name))) + if (!QFile::copy(entry->getFullPath(), PathCombine(inst->libDir(), lib.filename))) { - emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.name)); + emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); return; } index++; @@ -265,8 +224,6 @@ void LegacyUpdate::lwjglStart() connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); connect(worker.get(), SIGNAL(finished(QNetworkReply *)), SLOT(lwjglFinished(QNetworkReply *))); - // connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), - // SLOT(downloadError(QNetworkReply::NetworkError))); } void LegacyUpdate::lwjglFinished(QNetworkReply *reply) diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h index 5b073cb7..140ee1e3 100644 --- a/logic/LegacyUpdate.h +++ b/logic/LegacyUpdate.h @@ -21,19 +21,13 @@ #include "logic/net/NetJob.h" #include "logic/tasks/Task.h" +#include "logic/VersionFilterData.h" class MinecraftVersion; class BaseInstance; class QuaZip; class Mod; -struct FMLlib -{ - QString name; - QString checksum; - bool ours; -}; - class LegacyUpdate : public Task { Q_OBJECT @@ -84,5 +78,4 @@ private: NetJobPtr legacyDownloadJob; BaseInstance *m_inst = nullptr; QList<FMLlib> fmlLibsToProcess; - QMap<QString, QList<FMLlib>> fmlLibsMapping; }; diff --git a/logic/lists/LwjglVersionList.cpp b/logic/LwjglVersionList.cpp index df46d7be..df46d7be 100644 --- a/logic/lists/LwjglVersionList.cpp +++ b/logic/LwjglVersionList.cpp diff --git a/logic/lists/LwjglVersionList.h b/logic/LwjglVersionList.h index fa57e8eb..fa57e8eb 100644 --- a/logic/lists/LwjglVersionList.h +++ b/logic/LwjglVersionList.h diff --git a/logic/MMCJson.cpp b/logic/MMCJson.cpp index 65423436..8de88b6b 100644 --- a/logic/MMCJson.cpp +++ b/logic/MMCJson.cpp @@ -1,5 +1,6 @@ #include "MMCJson.h" #include <QString> +#include <QStringList> #include <math.h> bool MMCJson::ensureBoolean(const QJsonValue val, const QString what) @@ -11,7 +12,7 @@ bool MMCJson::ensureBoolean(const QJsonValue val, const QString what) QJsonValue MMCJson::ensureExists(QJsonValue val, const QString what) { - if(val.isNull()) + if(val.isUndefined() || val.isUndefined()) throw JSONValidationError(what + " does not exist"); return val; } @@ -59,3 +60,24 @@ QString MMCJson::ensureString(const QJsonValue val, const QString what) return val.toString(); } +void MMCJson::writeString(QJsonObject &to, QString key, QString value) +{ + if(value.size()) + { + to.insert(key, value); + } +} + +void MMCJson::writeStringList(QJsonObject &to, QString key, QStringList values) +{ + if(values.size()) + { + QJsonArray array; + for(auto value: values) + { + array.append(value); + } + to.insert(key, array); + } +} + diff --git a/logic/MMCJson.h b/logic/MMCJson.h index 71ded435..8408f29b 100644 --- a/logic/MMCJson.h +++ b/logic/MMCJson.h @@ -43,4 +43,23 @@ int ensureInteger(const QJsonValue val, QString what = "value"); /// make sure the value is converted into a double precision floating number. throw otherwise. double ensureDouble(const QJsonValue val, QString what = "value"); + +void writeString(QJsonObject & to, QString key, QString value); + +void writeStringList (QJsonObject & to, QString key, QStringList values); + +template <typename T> +void writeObjectList (QJsonObject & to, QString key, QList<T> values) +{ + if(values.size()) + { + QJsonArray array; + for(auto value: values) + { + array.append(value->toJson()); + } + to.insert(key, array); + } +} } + diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index b268a4cc..a3ffedba 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -71,6 +71,9 @@ MinecraftProcess::MinecraftProcess(InstancePtr inst) : m_instance(inst) connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput, this, &MinecraftProcess::on_prepost_stdOut); } + + // a process has been constructed for the instance. It is running from MultiMC POV + m_instance->setRunning(true); } void MinecraftProcess::setWorkdir(QString path) @@ -254,6 +257,8 @@ void MinecraftProcess::finish(int code, ExitStatus status) // run post-exit postLaunch(); m_instance->cleanupAfterRun(); + // no longer running... + m_instance->setRunning(false); emit ended(m_instance, code, status); } @@ -304,6 +309,8 @@ bool MinecraftProcess::preLaunch() m_instance->cleanupAfterRun(); emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), m_prepostlaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); return false; } else @@ -343,6 +350,8 @@ bool MinecraftProcess::postLaunch() MessageLevel::Error); emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), m_prepostlaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); } else emit log(tr("Post-Launch command ran successfully.\n\n")); @@ -460,6 +469,8 @@ void MinecraftProcess::arm() emit log(tr("Could not launch minecraft!"), MessageLevel::Error); m_instance->cleanupAfterRun(); emit launch_failed(m_instance); + // not running, failed + m_instance->setRunning(false); return; } // send the launch script to the launcher part diff --git a/logic/MinecraftVersion.h b/logic/MinecraftVersion.h deleted file mode 100644 index 504381a8..00000000 --- a/logic/MinecraftVersion.h +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright 2013 Andrew Okin - * - * 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 "BaseVersion.h" -#include <QStringList> - -struct MinecraftVersion : public BaseVersion -{ - /*! - * Gets the version's timestamp. - * This is primarily used for sorting versions in a list. - */ - qint64 timestamp; - - /// The URL that this version will be downloaded from. maybe. - QString download_url; - - /// This version's type. Used internally to identify what kind of version this is. - enum VersionType - { - OneSix, - Legacy, - Nostalgia - } type; - - /// is this the latest version? - bool is_latest = false; - - /// is this a snapshot? - bool is_snapshot = false; - - QString m_name; - - QString m_descriptor; - - virtual QString descriptor() - { - return m_descriptor; - } - - virtual QString name() - { - return m_name; - } - - virtual QString typeString() const - { - QStringList pre_final; - if (is_latest == true) - { - pre_final.append("Latest"); - } - switch (type) - { - case OneSix: - pre_final.append("OneSix"); - break; - case Legacy: - pre_final.append("Legacy"); - break; - case Nostalgia: - pre_final.append("Nostalgia"); - break; - - default: - pre_final.append(QString("Type(%1)").arg(type)); - break; - } - if (is_snapshot == true) - { - pre_final.append("Snapshot"); - } - return pre_final.join(' '); - } -}; diff --git a/logic/ModList.cpp b/logic/ModList.cpp index 79b56986..f7770920 100644 --- a/logic/ModList.cpp +++ b/logic/ModList.cpp @@ -26,6 +26,7 @@ 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); diff --git a/logic/NostalgiaInstance.cpp b/logic/NostalgiaInstance.cpp deleted file mode 100644 index 52820725..00000000 --- a/logic/NostalgiaInstance.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright 2013 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 "NostalgiaInstance.h" - -NostalgiaInstance::NostalgiaInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent) - : OneSixInstance(rootDir, settings, parent) -{ -} - -QString NostalgiaInstance::getStatusbarDescription() -{ - if (flags().contains(VersionBrokenFlag)) - { - return "Nostalgia : " + intendedVersionId() + " (broken)"; - } - return "Nostalgia : " + intendedVersionId(); -} - -bool NostalgiaInstance::menuActionEnabled(QString action_name) const -{ - return false; -} diff --git a/logic/NostalgiaInstance.h b/logic/NostalgiaInstance.h deleted file mode 100644 index f95531d2..00000000 --- a/logic/NostalgiaInstance.h +++ /dev/null @@ -1,29 +0,0 @@ -/* Copyright 2013 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 "OneSixInstance.h" - -class NostalgiaInstance : public OneSixInstance -{ - Q_OBJECT -public: - explicit NostalgiaInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent = 0); - virtual ~NostalgiaInstance() {}; - virtual QString getStatusbarDescription(); - virtual bool menuActionEnabled(QString action_name) const; -}; diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp index 172830bb..ef951987 100644 --- a/logic/OneSixFTBInstance.cpp +++ b/logic/OneSixFTBInstance.cpp @@ -1,12 +1,12 @@ #include "OneSixFTBInstance.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/minecraft/VersionBuilder.h" #include "tasks/SequentialTask.h" -#include "ForgeInstaller.h" -#include "lists/ForgeVersionList.h" +#include "forge/ForgeInstaller.h" +#include "forge/ForgeVersionList.h" #include "OneSixInstance_p.h" -#include "OneSixVersionBuilder.h" #include "MultiMC.h" #include "pathutils.h" @@ -100,6 +100,7 @@ QDir OneSixFTBInstance::librariesPath() const { return QDir(MMC->settings()->get("FTBRoot").toString() + "/libraries"); } + QDir OneSixFTBInstance::versionsPath() const { return QDir(MMC->settings()->get("FTBRoot").toString() + "/versions"); @@ -107,7 +108,6 @@ QDir OneSixFTBInstance::versionsPath() const QStringList OneSixFTBInstance::externalPatches() const { - I_D(OneSixInstance); return QStringList() << versionsPath().absoluteFilePath(intendedVersionId() + "/" + intendedVersionId() + ".json") << minecraftRoot() + "/pack.json"; } @@ -125,10 +125,6 @@ QString OneSixFTBInstance::getStatusbarDescription() } return "OneSix FTB: " + intendedVersionId(); } -bool OneSixFTBInstance::menuActionEnabled(QString action_name) const -{ - return false; -} std::shared_ptr<Task> OneSixFTBInstance::doUpdate() { diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h index 440dc9f1..ecfa2231 100644 --- a/logic/OneSixFTBInstance.h +++ b/logic/OneSixFTBInstance.h @@ -16,7 +16,6 @@ public: void copy(const QDir &newDir) override; virtual QString getStatusbarDescription(); - virtual bool menuActionEnabled(QString action_name) const; virtual std::shared_ptr<Task> doUpdate() override; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 6f3018cb..7bac3424 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -13,52 +13,84 @@ * limitations under the License. */ -#include "OneSixInstance.h" - #include <QIcon> - -#include "OneSixInstance_p.h" -#include "OneSixUpdate.h" -#include "VersionFinal.h" -#include "pathutils.h" +#include <pathutils.h> #include "logger/QsLog.h" -#include "assets/AssetsUtils.h" #include "MultiMC.h" -#include "icons/IconList.h" -#include "MinecraftProcess.h" -#include "gui/dialogs/OneSixModEditDialog.h" -#include <MMCError.h> +#include "MMCError.h" + +#include "logic/OneSixInstance.h" -OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) +#include "logic/OneSixInstance_p.h" +#include "logic/OneSixUpdate.h" +#include "logic/minecraft/InstanceVersion.h" +#include "minecraft/VersionBuildError.h" + +#include "logic/assets/AssetsUtils.h" +#include "icons/IconList.h" +#include "logic/MinecraftProcess.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> + +OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent) : BaseInstance(new OneSixInstancePrivate(), rootDir, settings, parent) { I_D(OneSixInstance); d->m_settings->registerSetting("IntendedVersion", ""); - d->m_settings->registerSetting("ShouldUpdate", false); - d->version.reset(new VersionFinal(this, this)); - d->vanillaVersion.reset(new VersionFinal(this, this)); + d->version.reset(new InstanceVersion(this, this)); } void OneSixInstance::init() { - // FIXME: why is this decided here? what does this even mean? - if (QDir(instanceRoot()).exists("version.json")) + try { - try - { - reloadVersion(); - } - catch(MMCError & e) - { - // QLOG_ERROR() << "Caught exception on instance init: " << e.cause(); - } + reloadVersion(); } - else + catch (MMCError &e) { - clearVersion(); + QLOG_ERROR() << "Caught exception on instance init: " << e.cause(); } } +QList<BasePage *> OneSixInstance::getPages() +{ + QList<BasePage *> values; + values.append(new VersionPage(this)); + values.append(new ModFolderPage(this, loaderModList(), "mods", "plugin-blue", tr("Loader mods"), + "Loader-mods")); + values.append(new ModFolderPage(this, coreModList(), "coremods", "plugin-green", tr("Core mods"), + "Core-mods")); + values.append(new ResourcePackPage(this)); + values.append(new TexturePackPage(this)); + values.append(new NotesPage(this)); + values.append(new ScreenshotsPage(this)); + values.append(new InstanceSettingsPage(this)); + return values; +} + +QString OneSixInstance::dialogTitle() +{ + return tr("Edit Instance (%1)").arg(name()); +} + +QSet<QString> OneSixInstance::traits() +{ + auto version = getFullVersion(); + if (!version) + { + return {"version-incomplete"}; + } + else + return version->traits; +} + std::shared_ptr<Task> OneSixInstance::doUpdate() { return std::shared_ptr<Task>(new OneSixUpdate(this)); @@ -88,7 +120,7 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with) return result; } -QDir OneSixInstance::reconstructAssets(std::shared_ptr<VersionFinal> version) +QDir OneSixInstance::reconstructAssets(std::shared_ptr<InstanceVersion> version) { QDir assetsDir = QDir("assets/"); QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes")); @@ -126,7 +158,7 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<VersionFinal> version) QString original_path = PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); QFile original(original_path); - if(!original.exists()) + if (!original.exists()) continue; if (!target.exists()) { @@ -138,7 +170,7 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<VersionFinal> version) bool couldCopy = original.copy(target_path); QLOG_DEBUG() << " Copying" << original_path << "to" << target_path - << QString::number(couldCopy); // << original.errorString(); + << QString::number(couldCopy); // << original.errorString(); } } @@ -189,7 +221,7 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) return parts; } -bool OneSixInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScript) +bool OneSixInstance::prepareForLaunch(AuthSessionPtr session, QString &launchScript) { I_D(OneSixInstance); @@ -200,44 +232,79 @@ bool OneSixInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScr auto version = d->version; if (!version) return nullptr; + + // libraries and class path. { auto libs = version->getActiveNormalLibs(); for (auto lib : libs) { launchScript += "cp " + librariesPath().absoluteFilePath(lib->storagePath()) + "\n"; } - QString targetstr = version->id + "/" + version->id + ".jar"; - launchScript += "cp " + versionsPath().absoluteFilePath(targetstr) + "\n"; + QString minecraftjarpath; + if (version->hasJarMods()) + { + for (auto jarmod : version->jarMods) + { + launchScript += "cp " + jarmodsPath().absoluteFilePath(jarmod->name) + "\n"; + } + minecraftjarpath = version->id + "/" + version->id + "-stripped.jar"; + } + else + { + minecraftjarpath = version->id + "/" + version->id + ".jar"; + } + launchScript += "cp " + versionsPath().absoluteFilePath(minecraftjarpath) + "\n"; + } + if (!version->mainClass.isEmpty()) + { + launchScript += "mainClass " + version->mainClass + "\n"; + } + if (!version->appletClass.isEmpty()) + { + launchScript += "appletClass " + version->appletClass + "\n"; } - launchScript += "mainClass " + version->mainClass + "\n"; - for (auto param : processMinecraftArgs(account)) + // generic minecraft params + for (auto param : processMinecraftArgs(session)) { launchScript += "param " + param + "\n"; } - // Set the width and height for 1.6 instances - bool maximize = settings().get("LaunchMaximized").toBool(); - if (maximize) + // window size, title and state, legacy { - // this is probably a BAD idea - // launchScript += "param --fullscreen\n"; + 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"; } - else + + // legacy auth + { + launchScript += "userName " + session->player_name + "\n"; + launchScript += "sessionId " + session->session + "\n"; + } + + // native libraries (mostly LWJGL) { - launchScript += - "param --width\nparam " + settings().get("MinecraftWinWidth").toString() + "\n"; - launchScript += - "param --height\nparam " + settings().get("MinecraftWinHeight").toString() + "\n"; + QDir natives_dir(PathCombine(instanceRoot(), "natives/")); + for (auto native : version->getActiveNativeLibs()) + { + QFileInfo finfo(PathCombine("libraries", native->storagePath())); + launchScript += "ext " + finfo.absoluteFilePath() + "\n"; + } + launchScript += "natives " + natives_dir.absolutePath() + "\n"; } - QDir natives_dir(PathCombine(instanceRoot(), "natives/")); - launchScript += "windowTitle " + windowTitle() + "\n"; - for(auto native: version->getActiveNativeLibs()) + + // traits. including legacyLaunch and others ;) + for (auto trait : version->traits) { - QFileInfo finfo(PathCombine("libraries", native->storagePath())); - launchScript += "ext " + finfo.absoluteFilePath() + "\n"; + launchScript += "traits " + trait + "\n"; } - launchScript += "natives " + natives_dir.absolutePath() + "\n"; launchScript += "launcher onesix\n"; return true; } @@ -260,6 +327,17 @@ std::shared_ptr<ModList> OneSixInstance::loaderModList() return d->loader_mod_list; } +std::shared_ptr<ModList> OneSixInstance::coreModList() +{ + I_D(OneSixInstance); + if (!d->core_mod_list) + { + d->core_mod_list.reset(new ModList(coreModsDir())); + } + d->core_mod_list->update(); + return d->core_mod_list; +} + std::shared_ptr<ModList> OneSixInstance::resourcePackList() { I_D(OneSixInstance); @@ -271,15 +349,20 @@ std::shared_ptr<ModList> OneSixInstance::resourcePackList() return d->resource_pack_list; } -QDialog *OneSixInstance::createModEditDialog(QWidget *parent) +std::shared_ptr<ModList> OneSixInstance::texturePackList() { - return new OneSixModEditDialog(this, parent); + I_D(OneSixInstance); + if (!d->texture_pack_list) + { + d->texture_pack_list.reset(new ModList(texturePacksDir())); + } + d->texture_pack_list->update(); + return d->texture_pack_list; } bool OneSixInstance::setIntendedVersionId(QString version) { settings().set("IntendedVersion", version); - setShouldUpdate(true); QFile::remove(PathCombine(instanceRoot(), "version.json")); clearVersion(); return true; @@ -290,34 +373,31 @@ QString OneSixInstance::intendedVersionId() const return settings().get("IntendedVersion").toString(); } -void OneSixInstance::setShouldUpdate(bool val) +void OneSixInstance::setShouldUpdate(bool) { - settings().set("ShouldUpdate", val); } bool OneSixInstance::shouldUpdate() const { - QVariant var = settings().get("ShouldUpdate"); - if (!var.isValid() || var.toBool() == false) - { - return intendedVersionId() != currentVersionId(); - } return true; } bool OneSixInstance::versionIsCustom() { - QDir patches(PathCombine(instanceRoot(), "patches/")); - return (patches.exists() && patches.count() >= 0) - || QFile::exists(PathCombine(instanceRoot(), "custom.json")) - || QFile::exists(PathCombine(instanceRoot(), "user.json")); + I_D(const OneSixInstance); + auto ver = d->version; + if (ver) + { + return !ver->isVanilla(); + } + return false; } bool OneSixInstance::versionIsFTBPack() { I_D(const OneSixInstance); auto ver = d->version; - if(ver) + if (ver) { return ver->hasFtbPack(); } @@ -335,17 +415,18 @@ void OneSixInstance::reloadVersion() try { - d->version->reload(false, externalPatches()); - d->vanillaVersion->reload(true, externalPatches()); + d->version->reload(externalPatches()); d->m_flags.remove(VersionBrokenFlag); emit versionReloaded(); } - catch(MMCError & error) + catch (VersionIncomplete &error) + { + } + catch (MMCError &error) { d->version->clear(); - d->vanillaVersion->clear(); d->m_flags.insert(VersionBrokenFlag); - //TODO: rethrow to show some error message(s)? + // TODO: rethrow to show some error message(s)? emit versionReloaded(); throw; } @@ -355,22 +436,15 @@ void OneSixInstance::clearVersion() { I_D(OneSixInstance); d->version->clear(); - d->vanillaVersion->clear(); emit versionReloaded(); } -std::shared_ptr<VersionFinal> OneSixInstance::getFullVersion() const +std::shared_ptr<InstanceVersion> OneSixInstance::getFullVersion() const { I_D(const OneSixInstance); return d->version; } -std::shared_ptr<VersionFinal> OneSixInstance::getVanillaVersion() const -{ - I_D(const OneSixInstance); - return d->vanillaVersion; -} - QString OneSixInstance::defaultBaseJar() const { return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; @@ -381,37 +455,38 @@ QString OneSixInstance::defaultCustomBaseJar() const return PathCombine(instanceRoot(), "custom.jar"); } -bool OneSixInstance::menuActionEnabled(QString action_name) const +QString OneSixInstance::getStatusbarDescription() { - if (flags().contains(VersionBrokenFlag)) + QStringList traits; + if (versionIsCustom()) { - return false; + traits.append(tr("custom")); } - if (action_name == "actionChangeInstLWJGLVersion") + if (flags().contains(VersionBrokenFlag)) { - return false; + traits.append(tr("broken")); } - return true; -} -QString OneSixInstance::getStatusbarDescription() -{ - QString descr = "OneSix : " + intendedVersionId(); - if (versionIsCustom()) + if (traits.size()) { - descr += " (custom)"; + return tr("Minecraft %1 (%2)").arg(intendedVersionId()).arg(traits.join(", ")); } - if (flags().contains(VersionBrokenFlag)) + else { - descr += " (broken)"; + return tr("Minecraft %1").arg(intendedVersionId()); } - return descr; } 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"); @@ -429,7 +504,7 @@ bool OneSixInstance::providesVersionFile() const bool OneSixInstance::reload() { - if(BaseInstance::reload()) + if (BaseInstance::reload()) { try { @@ -449,12 +524,46 @@ 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 = getFullVersion(); + if (!version) + return list; + if (version->hasJarMods()) + { + list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", + "-Dfml.ignorePatchDiscrepancies=true"}); + } + return list; +} diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index 13a392c2..75caef1f 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -17,10 +17,11 @@ #include "BaseInstance.h" -#include "VersionFinal.h" -#include "ModList.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/ModList.h" +#include "gui/pages/BasePageProvider.h" -class OneSixInstance : public BaseInstance +class OneSixInstance : public BaseInstance, public BasePageProvider { Q_OBJECT public: @@ -30,13 +31,25 @@ public: virtual void init() override; + ////// Edit Instance Dialog stuff ////// + virtual QList<BasePage *> getPages(); + virtual QString dialogTitle(); + ////// Mod Lists ////// std::shared_ptr<ModList> loaderModList(); - std::shared_ptr<ModList> resourcePackList(); - - ////// Directories ////// + std::shared_ptr<ModList> coreModList(); + std::shared_ptr<ModList> resourcePackList() override; + std::shared_ptr<ModList> texturePackList() override; + + virtual QSet<QString> traits(); + + ////// Directories and files ////// + QString jarModsDir() const; QString resourcePacksDir() const; + QString texturePacksDir() const; QString loaderModsDir() const; + QString coreModsDir() const; + QString libDir() const; virtual QString instanceConfigFolder() const override; virtual std::shared_ptr<Task> doUpdate() override; @@ -52,42 +65,43 @@ public: virtual bool shouldUpdate() const override; virtual void setShouldUpdate(bool val) override; - virtual QDialog *createModEditDialog(QWidget *parent) override; - /** - * reload the full version json files. return true on success! + * reload the full version json files. * * throws various exceptions :3 */ void reloadVersion(); + /// clears all version information in preparation for an update void clearVersion(); + /// get the current full version info - std::shared_ptr<VersionFinal> getFullVersion() const; - /// gets the current version info, but only for version.json - std::shared_ptr<VersionFinal> getVanillaVersion() const; + std::shared_ptr<InstanceVersion> getFullVersion() const; + /// is the current version original, or custom? virtual bool versionIsCustom() override; + /// does this instance have an FTB pack patch inside? bool versionIsFTBPack(); virtual QString defaultBaseJar() const override; virtual QString defaultCustomBaseJar() const override; - virtual bool menuActionEnabled(QString action_name) const override; virtual QString getStatusbarDescription() override; + virtual QDir jarmodsPath() const; virtual QDir librariesPath() const; virtual QDir versionsPath() const; virtual QStringList externalPatches() const; virtual bool providesVersionFile() const; bool reload() override; - + virtual QStringList extraArguments() const override; + signals: void versionReloaded(); private: QStringList processMinecraftArgs(AuthSessionPtr account); - QDir reconstructAssets(std::shared_ptr<VersionFinal> version); + QDir reconstructAssets(std::shared_ptr<InstanceVersion> version); }; diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h index c70de07c..3c4ef324 100644 --- a/logic/OneSixInstance_p.h +++ b/logic/OneSixInstance_p.h @@ -15,16 +15,19 @@ #pragma once -#include "BaseInstance_p.h" -#include "VersionFinal.h" -#include "ModList.h" +#include "logic/BaseInstance_p.h" + +class ModList; +class InstanceVersion; class OneSixInstancePrivate : public BaseInstancePrivate { public: virtual ~OneSixInstancePrivate() {}; - std::shared_ptr<VersionFinal> version; - std::shared_ptr<VersionFinal> vanillaVersion; + std::shared_ptr<InstanceVersion> version; + std::shared_ptr<ModList> jar_mod_list; std::shared_ptr<ModList> loader_mod_list; + std::shared_ptr<ModList> core_mod_list; std::shared_ptr<ModList> resource_pack_list; + std::shared_ptr<ModList> texture_pack_list; }; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index d083c2ba..abc5f9c1 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -22,28 +22,24 @@ #include <QFileInfo> #include <QTextStream> #include <QDataStream> - -#include "BaseInstance.h" -#include "lists/MinecraftVersionList.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" -#include "net/ForgeMirrors.h" -#include "net/URLConstants.h" -#include "assets/AssetsUtils.h" - -#include "pathutils.h" +#include <pathutils.h> #include <JlCompress.h> -OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) - : Task(parent), m_inst(inst) +#include "logic/BaseInstance.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/minecraft/InstanceVersion.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" + +OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { } void OneSixUpdate::executeTask() { - QString intendedVersion = m_inst->intendedVersionId(); - // Make directories QDir mcDir(m_inst->minecraftRoot()); if (!mcDir.exists() && !mcDir.mkpath(".")) @@ -52,102 +48,44 @@ void OneSixUpdate::executeTask() return; } - if (m_inst->shouldUpdate()) + // Get a pointer to the version object that corresponds to the instance's version. + targetVersion = std::dynamic_pointer_cast<MinecraftVersion>( + MMC->minecraftlist()->findVersion(m_inst->intendedVersionId())); + if (targetVersion == nullptr) { - // Get a pointer to the version object that corresponds to the instance's version. - targetVersion = std::dynamic_pointer_cast<MinecraftVersion>( - MMC->minecraftlist()->findVersion(intendedVersion)); - if (targetVersion == nullptr) - { - // don't do anything if it was invalid - emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); - return; - } - versionFileStart(); + // don't do anything if it was invalid + emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); + return; } - else + if (m_inst->providesVersionFile() || !targetVersion->needsUpdate()) { jarlibStart(); + return; } -} - -void OneSixUpdate::versionFileStart() -{ - if (m_inst->providesVersionFile()) + versionUpdateTask = MMC->minecraftlist()->createUpdateTask(m_inst->intendedVersionId()); + if (!versionUpdateTask) { jarlibStart(); return; } - QLOG_INFO() << m_inst->name() << ": getting version file."; - setStatus(tr("Getting the version files from Mojang...")); - - QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + - targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; - auto job = new NetJob("Version index"); - job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); - specificVersionDownloadJob.reset(job); - connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(versionFileFinished())); - connect(specificVersionDownloadJob.get(), SIGNAL(failed()), SLOT(versionFileFailed())); - connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + 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))); - specificVersionDownloadJob->start(); -} - -void OneSixUpdate::versionFileFinished() -{ - NetActionPtr DlJob = specificVersionDownloadJob->first(); - - QString version_id = targetVersion->descriptor(); - QString inst_dir = m_inst->instanceRoot(); - // save the version file in $instanceId/version.json - { - QString version1 = PathCombine(inst_dir, "/version.json"); - ensureFilePathExists(version1); - // FIXME: detect errors here, download to a temp file, swap - QSaveFile vfile1(version1); - if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) - { - emitFailed(tr("Can't open %1 for writing.").arg(version1)); - return; - } - auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data; - qint64 actual = 0; - if ((actual = vfile1.write(data)) != data.size()) - { - emitFailed(tr("Failed to write into %1. Written %2 out of %3.").arg(version1).arg(actual).arg(data.size())); - return; - } - if (!vfile1.commit()) - { - emitFailed(tr("Can't commit changes to %1").arg(version1)); - return; - } - } - - // the version is downloaded safely. update is 'done' at this point - m_inst->setShouldUpdate(false); - - // delete any custom version inside the instance (it's no longer relevant, we did an update) - QString custom = PathCombine(inst_dir, "/custom.json"); - QFile finfo(custom); - if (finfo.exists()) - { - finfo.remove(); - } - // NOTE: Version is reloaded in jarlibStart - jarlibStart(); + setStatus(tr("Getting the version files from Mojang...")); + versionUpdateTask->start(); } -void OneSixUpdate::versionFileFailed() +void OneSixUpdate::versionUpdateFailed(QString reason) { - emitFailed(tr("Failed to download the version description. Try again.")); + emitFailed(reason); } void OneSixUpdate::assetIndexStart() { setStatus(tr("Updating assets index...")); OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<VersionFinal> version = inst->getFullVersion(); + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); QString assetName = version->assets; QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json"; QString localPath = assetName + ".json"; @@ -171,7 +109,7 @@ void OneSixUpdate::assetIndexFinished() AssetsIndex index; OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<VersionFinal> version = inst->getFullVersion(); + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); QString assetName = version->assets; QString asset_fname = "assets/indexes/" + assetName + ".json"; @@ -235,19 +173,19 @@ void OneSixUpdate::jarlibStart() { inst->reloadVersion(); } - catch(MMCError & e) + catch (MMCError &e) { emitFailed(e.cause()); return; } - catch(...) + 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<VersionFinal> version = inst->getFullVersion(); + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); // minecraft.jar for this version { QString version_id = version->id; @@ -259,7 +197,7 @@ void OneSixUpdate::jarlibStart() auto metacache = MMC->metacache(); auto entry = metacache->resolveEntry("versions", localPath); job->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); - + jarHashOnEntry = entry->md5sum; jarlibDownloadJob.reset(job); } @@ -274,7 +212,7 @@ void OneSixUpdate::jarlibStart() { if (lib->hint() == "local") { - if(!lib->filesExist(m_inst->librariesPath())) + if (!lib->filesExist(m_inst->librariesPath())) brokenLocalLibs.append(lib); continue; } @@ -311,16 +249,19 @@ void OneSixUpdate::jarlibStart() f(raw_storage, raw_dl); } } - if(!brokenLocalLibs.empty()) + if (!brokenLocalLibs.empty()) { jarlibDownloadJob.reset(); QStringList failed; - for(auto brokenLib : brokenLocalLibs) + 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)); + 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 @@ -341,12 +282,222 @@ void OneSixUpdate::jarlibStart() void OneSixUpdate::jarlibFinished() { - assetIndexStart(); + OneSixInstance *inst = (OneSixInstance *)m_inst; + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); + + // create stripped jar, if needed + if (version->hasJarMods()) + { + // FIXME: good candidate for moving elsewhere (jar location resolving/version caching). + QString version_id = version->id; + QString localPath = version_id + "/" + version_id + ".jar"; + QString strippedPath = version_id + "/" + version_id + "-stripped.jar"; + auto metacache = MMC->metacache(); + auto entry = metacache->resolveEntry("versions", localPath); + auto entryStripped = metacache->resolveEntry("versions", strippedPath); + + QString fullJarPath = entry->getFullPath(); + QString fullStrippedJarPath = entryStripped->getFullPath(); + QFileInfo finfo(fullStrippedJarPath); + if (entry->md5sum != jarHashOnEntry || !finfo.exists()) + { + stripJar(fullJarPath, fullStrippedJarPath); + } + } + 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)); + emitFailed( + tr("Failed to download the following files:\n%1\n\nPlease try again.").arg(failed_all)); +} + +void OneSixUpdate::stripJar(QString origPath, QString newPath) +{ + QFileInfo runnableJar(newPath); + if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) + { + emitFailed("Failed to delete old minecraft.jar"); + return; + } + + // TaskStep(); // STEP 1 + setStatus(tr("Creating stripped jar: Opening minecraft.jar ...")); + + QuaZip zipOut(runnableJar.filePath()); + if (!zipOut.open(QuaZip::mdCreate)) + { + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to open the minecraft.jar for stripping"); + return; + } + // Modify the jar + setStatus(tr("Creating stripped jar: Adding files...")); + if (!MergeZipFiles(&zipOut, origPath)) + { + zipOut.close(); + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to add " + origPath + " to the jar."); + return; + } +} + +bool OneSixUpdate::MergeZipFiles(QuaZip *into, QString from) +{ + setStatus(tr("Installing mods: Adding ") + from + " ..."); + + QuaZip modZip(from); + modZip.open(QuaZip::mdUnzip); + + QuaZipFile fileInsideMod(&modZip); + QuaZipFile zipOutFile(into); + for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) + { + QString filename = modZip.getCurrentFileName(); + if (filename.contains("META-INF")) + { + QLOG_INFO() << "Skipping META-INF " << filename << " from " << from; + continue; + } + QLOG_INFO() << "Adding file " << filename << " from " << from; + + if (!fileInsideMod.open(QIODevice::ReadOnly)) + { + QLOG_ERROR() << "Failed to open " << filename << " from " << from; + return false; + } + /* + QuaZipFileInfo old_info; + fileInsideMod.getFileInfo(&old_info); + */ + QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); + /* + info_out.externalAttr = old_info.externalAttr; + */ + if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) + { + QLOG_ERROR() << "Failed to open " << filename << " in the jar"; + fileInsideMod.close(); + return false; + } + if (!JlCompress::copyData(fileInsideMod, zipOutFile)) + { + zipOutFile.close(); + fileInsideMod.close(); + QLOG_ERROR() << "Failed to copy data of " << filename << " into the jar"; + return false; + } + zipOutFile.close(); + fileInsideMod.close(); + } + return true; +} + +void OneSixUpdate::fmllibsStart() +{ + // Get the mod list + OneSixInstance *inst = (OneSixInstance *)m_inst; + std::shared_ptr<InstanceVersion> fullversion = inst->getFullVersion(); + 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 = MMC->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 = MMC->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 index eac882b5..139143db 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -21,6 +21,8 @@ #include "logic/net/NetJob.h" #include "logic/tasks/Task.h" +#include "logic/VersionFilterData.h" +#include <quazip.h> class MinecraftVersion; class OneSixInstance; @@ -34,14 +36,16 @@ public: private slots: - void versionFileStart(); - void versionFileFinished(); - void versionFileFailed(); + void versionUpdateFailed(QString reason); void jarlibStart(); void jarlibFinished(); void jarlibFailed(); + void fmllibsStart(); + void fmllibsFinished(); + void fmllibsFailed(); + void assetIndexStart(); void assetIndexFinished(); void assetIndexFailed(); @@ -49,11 +53,18 @@ slots: void assetsFinished(); void assetsFailed(); + void stripJar(QString origPath, QString newPath); + bool MergeZipFiles(QuaZip *into, QString from); private: - NetJobPtr specificVersionDownloadJob; NetJobPtr jarlibDownloadJob; + NetJobPtr legacyDownloadJob; - // target version, determined during this task + /// target version, determined during this task std::shared_ptr<MinecraftVersion> targetVersion; + /// the task that is spawned for version updates + std::shared_ptr<Task> versionUpdateTask; + OneSixInstance *m_inst = nullptr; + QString jarHashOnEntry; + QList<FMLlib> fmlLibsToProcess; }; diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp deleted file mode 100644 index be3a7da4..00000000 --- a/logic/OneSixVersionBuilder.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* Copyright 2013 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 "OneSixVersionBuilder.h" - -#include <QList> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonDocument> -#include <QFile> -#include <QFileInfo> -#include <QMessageBox> -#include <QObject> -#include <QDir> -#include <QDebug> - -#include "VersionFinal.h" -#include "OneSixInstance.h" -#include "OneSixRule.h" -#include "VersionFile.h" -#include "MMCJson.h" -#include "modutils.h" -#include "logger/QsLog.h" - -OneSixVersionBuilder::OneSixVersionBuilder() -{ -} - -void OneSixVersionBuilder::build(VersionFinal *version, OneSixInstance *instance, - const bool onlyVanilla, const QStringList &external) -{ - OneSixVersionBuilder builder; - builder.m_version = version; - builder.m_instance = instance; - builder.buildInternal(onlyVanilla, external); -} - -void OneSixVersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, - const QJsonObject &obj) -{ - OneSixVersionBuilder builder; - builder.m_version = version; - builder.m_instance = 0; - builder.readJsonAndApply(obj); -} - -void OneSixVersionBuilder::buildInternal(const bool onlyVanilla, const QStringList &external) -{ - m_version->versionFiles.clear(); - - QDir root(m_instance->instanceRoot()); - QDir patches(root.absoluteFilePath("patches/")); - - // if we do external files, do just those. - if (!external.isEmpty()) - { - int externalOrder = -1; - for (auto fileName : external) - { - QLOG_INFO() << "Reading" << fileName; - auto file = - parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); - file->name = QFileInfo(fileName).fileName(); - file->fileId = "org.multimc.external." + file->name; - file->order = (externalOrder += 1); - file->version = QString(); - file->mcVersion = QString(); - m_version->versionFiles.append(file); - } - } - // else, if there's custom json, we just do that. - else if (QFile::exists(root.absoluteFilePath("custom.json"))) - { - QLOG_INFO() << "Reading custom.json"; - auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("custom.json")), false); - file->name = "custom.json"; - file->filename = "custom.json"; - file->fileId = "org.multimc.custom.json"; - file->order = -1; - file->version = QString(); - m_version->versionFiles.append(file); - // QObject::tr("The version descriptors of this instance are not compatible with the - // current version of MultiMC")); - // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more info.") - } - // version.json -> patches/*.json -> user.json - else - do - { - // version.json - QLOG_INFO() << "Reading version.json"; - auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("version.json")), false); - file->name = "Minecraft"; - file->fileId = "org.multimc.version.json"; - file->order = -1; - file->version = m_instance->intendedVersionId(); - file->mcVersion = m_instance->intendedVersionId(); - m_version->versionFiles.append(file); - // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more - // info.").arg(root.absoluteFilePath("version.json"))); - - if (onlyVanilla) - break; - - // patches/ - // load all, put into map for ordering, apply in the right order - - QMap<int, QPair<QString, VersionFilePtr>> files; - for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - QLOG_INFO() << "Reading" << info.fileName(); - auto file = parseJsonFile(info, true); - if (files.contains(file->order)) - { - throw VersionBuildError(QObject::tr("%1 has the same order as %2").arg( - file->fileId, files[file->order].second->fileId)); - } - files.insert(file->order, qMakePair(info.fileName(), file)); - } - for (auto order : files.keys()) - { - auto &filePair = files[order]; - m_version->versionFiles.append(filePair.second); - } - } while (0); - - // some final touches - m_version->finalize(); -} - - - -void OneSixVersionBuilder::readJsonAndApply(const QJsonObject &obj) -{ - m_version->clear(); - - auto file = VersionFile::fromJson(QJsonDocument(obj), QString(), false); - // QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); - - file->applyTo(m_version); - m_version->versionFiles.append(file); - // QObject::tr("Error while applying. Please check MultiMC-0.log for more info.")); - // QObject::tr("The version descriptors of this instance are not compatible with the current - // version of MultiMC")); -} - -VersionFilePtr OneSixVersionBuilder::parseJsonFile(const QFileInfo &fileInfo, - const bool requireOrder, bool isFTB) -{ - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") - .arg(fileInfo.fileName(), file.errorString())); - } - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - throw JSONValidationError(QObject::tr("Unable to process the version file %1: %2 at %3.") - .arg(fileInfo.fileName(), error.errorString()) - .arg(error.offset)); - } - return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); - // QObject::tr("Error while reading %1. Please check MultiMC-0.log for more - // info.").arg(file.fileName()); -} - -QMap<QString, int> OneSixVersionBuilder::readOverrideOrders(OneSixInstance *instance) -{ - QMap<QString, int> out; - - // make sure the order file exists - if (!QDir(instance->instanceRoot()).exists("order.json")) - return out; - - // and it can be opened - QFile orderFile(instance->instanceRoot() + "/order.json"); - if (!orderFile.open(QFile::ReadOnly)) - { - QLOG_ERROR() << "Couldn't open" << orderFile.fileName() - << " for reading:" << orderFile.errorString(); - QLOG_WARN() << "Ignoring overriden order"; - return out; - } - - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); - QLOG_WARN() << "Ignoring overriden order"; - return out; - } - - // and then read it and process it if all above is true. - try - { - auto obj = MMCJson::ensureObject(doc); - for (auto it = obj.begin(); it != obj.end(); ++it) - { - if (it.key().startsWith("org.multimc.")) - { - continue; - } - out.insert(it.key(), MMCJson::ensureInteger(it.value())); - } - } - catch (JSONValidationError &err) - { - QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; - QLOG_WARN() << "Ignoring overriden order"; - return out; - } - return out; -} - -bool OneSixVersionBuilder::writeOverrideOrders(const QMap<QString, int> &order, - OneSixInstance *instance) -{ - QJsonObject obj; - for (auto it = order.cbegin(); it != order.cend(); ++it) - { - if (it.key().startsWith("org.multimc.")) - { - continue; - } - obj.insert(it.key(), it.value()); - } - QFile orderFile(instance->instanceRoot() + "/order.json"); - if (!orderFile.open(QFile::WriteOnly)) - { - QLOG_ERROR() << "Couldn't open" << orderFile.fileName() - << "for writing:" << orderFile.errorString(); - return false; - } - orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); - return true; -} diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h deleted file mode 100644 index 7a799e5b..00000000 --- a/logic/OneSixVersionBuilder.h +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QString> -#include <QMap> -#include "VersionFile.h" - -class VersionFinal; -class OneSixInstance; -class QJsonObject; -class QFileInfo; - -class OneSixVersionBuilder -{ - OneSixVersionBuilder(); -public: - static void build(VersionFinal *version, OneSixInstance *instance, const bool onlyVanilla, - const QStringList &external); - static void readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj); - - static QMap<QString, int> readOverrideOrders(OneSixInstance *instance); - static bool writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance); - -private: - VersionFinal *m_version; - OneSixInstance *m_instance; - - void buildInternal(const bool onlyVanilla, const QStringList &external); - void readJsonAndApply(const QJsonObject &obj); - - VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, - bool isFTB = false); -}; diff --git a/logic/URNResolver.cpp b/logic/URNResolver.cpp new file mode 100644 index 00000000..b6bdcf41 --- /dev/null +++ b/logic/URNResolver.cpp @@ -0,0 +1,98 @@ +#include "URNResolver.h" +#include <logger/QsLog.h> +#include "MultiMC.h" +#include "logic/forge/ForgeVersionList.h" +#include "logic/forge/ForgeVersion.h" + +QString unescapeNSS(QString RawNSS) +{ + QString NSS; + NSS.reserve(RawNSS.size()); + enum + { + Normal, + FirstHex, + SecondHex + } ParseState = Normal; + + QByteArray translator(" "); + + for (auto ch : RawNSS) + { + if(ParseState == Normal) + { + if(ch == '%') + { + ParseState = FirstHex; + continue; + } + else + { + NSS.append(ch); + } + } + if(ParseState == FirstHex) + { + translator[0] = ch.toLower().unicode(); + ParseState = SecondHex; + } + else if(ParseState == SecondHex) + { + translator[1] = ch.toLower().unicode(); + auto result = QByteArray::fromHex(translator); + if (result[0] == '\0') + return NSS; + NSS.append(result); + ParseState = Normal; + } + } + return NSS; +} + +bool URNResolver::parse(const QString &URN, QString &NID, QString &NSS) +{ + QRegExp URNPattern( + "^urn:([a-z0-9][a-z0-9-]{0,31}):(([a-z0-9()+,\\-.:=@;$_!*']|%[0-9a-f]{2})+).*", + Qt::CaseInsensitive); + if (URNPattern.indexIn(URN) == -1) + return false; + auto captures = URNPattern.capturedTexts(); + QString RawNID = captures[1]; + QString RawNSS = captures[2]; + + NID = RawNID.toLower(); + NSS = unescapeNSS(RawNSS); + return true; +} + +URNResolver::URNResolver() +{ +} + +QVariant URNResolver::resolve(QString URN) +{ + QString NID, NSS; + parse(URN, NID, NSS); + + if(NID != "x-mmc") + return QVariant(); + auto parts = NSS.split(":"); + if(parts.size() < 1) + return QVariant(); + unsigned int version = parts[0].toUInt(); + switch(version) + { + case 1: + return resolveV1(parts.mid(1)); + default: + return QVariant(); + } +} + +/** + * TODO: implement. + */ +QVariant URNResolver::resolveV1(QStringList parts) +{ + return QVariant(); +} diff --git a/logic/URNResolver.h b/logic/URNResolver.h new file mode 100644 index 00000000..dfc7f5eb --- /dev/null +++ b/logic/URNResolver.h @@ -0,0 +1,18 @@ +#pragma once +#include <QString> +#include <QMap> +#include <memory> +#include <QVariant> + +class URNResolver; +typedef std::shared_ptr<URNResolver> URNResolverPtr; + +class URNResolver +{ +public: + URNResolver(); + QVariant resolve (QString URN); + static bool parse (const QString &URN, QString &NID, QString &NSS); +private: + QVariant resolveV1 (QStringList parts); +}; diff --git a/logic/VersionFile.h b/logic/VersionFile.h deleted file mode 100644 index 169a2066..00000000 --- a/logic/VersionFile.h +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once - -#include <QString> -#include <QStringList> -#include <memory> -#include "logic/OpSys.h" -#include "logic/OneSixRule.h" -#include "MMCError.h" - -class VersionFinal; - -class VersionBuildError : public MMCError -{ -public: - VersionBuildError(QString cause) : MMCError(cause) {}; - virtual ~VersionBuildError() noexcept {} -}; - -/** - * the base version file was meant for a newer version of the vanilla launcher than we support - */ -class LauncherVersionError : public VersionBuildError -{ -public: - LauncherVersionError(int actual, int supported) - : VersionBuildError(QObject::tr( - "The base version file of this instance was meant for a newer (%1) " - "version of the vanilla launcher than this version of MultiMC supports (%2).") - .arg(actual) - .arg(supported)) {}; - virtual ~LauncherVersionError() noexcept {} -}; - -/** - * some patch was intended for a different version of minecraft - */ -class MinecraftVersionMismatch : public VersionBuildError -{ -public: - MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion) - : VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft " - "(%2) than that of the instance (%3).") - .arg(fileId) - .arg(mcVersion) - .arg(parentMcVersion)) {}; - virtual ~MinecraftVersionMismatch() noexcept {} -}; - -struct RawLibrary; -typedef std::shared_ptr<RawLibrary> RawLibraryPtr; -struct RawLibrary -{ - QString name; - QString url; - QString hint; - QString absoluteUrl; - bool applyExcludes = false; - QStringList excludes; - bool applyNatives = false; - QList<QPair<OpSys, QString>> natives; - bool applyRules = false; - QList<std::shared_ptr<Rule>> rules; - - // user for '+' libraries - enum InsertType - { - Apply, - Append, - Prepend, - Replace - }; - InsertType insertType = Append; - QString insertData; - enum DependType - { - Soft, - Hard - }; - DependType dependType = Soft; - - static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename); -}; - -struct VersionFile; -typedef std::shared_ptr<VersionFile> VersionFilePtr; -struct VersionFile -{ -public: /* methods */ - static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename, - const bool requireOrder, const bool isFTB = false); - - static OneSixLibraryPtr createLibrary(RawLibraryPtr lib); - int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle); - void applyTo(VersionFinal *version); - -public: /* data */ - int order = 0; - QString name; - QString fileId; - QString version; - // TODO use the mcVersion to determine if a version file should be removed on update - QString mcVersion; - QString filename; - // TODO requirements - // QMap<QString, QString> requirements; - QString id; - QString mainClass; - QString overwriteMinecraftArguments; - QString addMinecraftArguments; - QString removeMinecraftArguments; - QString processArguments; - QString type; - QString releaseTime; - QString time; - QString assets; - int minimumLauncherVersion = -1; - - bool shouldOverwriteTweakers = false; - QStringList overwriteTweakers; - QStringList addTweakers; - QStringList removeTweakers; - - bool shouldOverwriteLibs = false; - QList<RawLibraryPtr> overwriteLibs; - QList<RawLibraryPtr> addLibs; - QList<QString> removeLibs; -}; diff --git a/logic/VersionFilterData.cpp b/logic/VersionFilterData.cpp new file mode 100644 index 00000000..8b521266 --- /dev/null +++ b/logic/VersionFilterData.cpp @@ -0,0 +1,68 @@ +#include "VersionFilterData.h" +#include "minecraft/ParseUtils.h" + +extern VersionFilterData g_VersionFilterData = VersionFilterData(); + +VersionFilterData::VersionFilterData() +{ + // 1.3.* + auto libs13 = + QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}}; + + fmlLibsMapping["1.3.2"] = libs13; + + // 1.4.* + auto libs14 = QList<FMLlib>{ + {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}, + {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}}; + + fmlLibsMapping["1.4"] = libs14; + fmlLibsMapping["1.4.1"] = libs14; + fmlLibsMapping["1.4.2"] = libs14; + fmlLibsMapping["1.4.3"] = libs14; + fmlLibsMapping["1.4.4"] = libs14; + fmlLibsMapping["1.4.5"] = libs14; + fmlLibsMapping["1.4.6"] = libs14; + fmlLibsMapping["1.4.7"] = libs14; + + // 1.5 + fmlLibsMapping["1.5"] = QList<FMLlib>{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + + // 1.5.1 + fmlLibsMapping["1.5.1"] = QList<FMLlib>{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + + // 1.5.2 + fmlLibsMapping["1.5.2"] = QList<FMLlib>{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + + // don't use installers for those. + forgeInstallerBlacklist = QSet<QString>({"1.5.2"}); + // these won't show up in version lists because they are extremely bad and dangerous + legacyBlacklist = QSet<QString>({"rd-160052"}); + /* + * nothing older than this will be accepted from Mojang servers + * (these versions need to be tested by us first) + */ + legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); +} diff --git a/logic/VersionFilterData.h b/logic/VersionFilterData.h new file mode 100644 index 00000000..e010adc7 --- /dev/null +++ b/logic/VersionFilterData.h @@ -0,0 +1,26 @@ +#pragma once +#include <QMap> +#include <QString> +#include <QSet> +#include <QDateTime> + +struct FMLlib +{ + QString filename; + QString checksum; + bool ours; +}; + +struct VersionFilterData +{ + VersionFilterData(); + // mapping between minecraft versions and FML libraries required + QMap<QString, QList<FMLlib>> fmlLibsMapping; + // set of minecraft versions for which using forge installers is blacklisted + QSet<QString> forgeInstallerBlacklist; + // set of 'legacy' versions that will not show up in the version lists. + QSet<QString> legacyBlacklist; + // no new versions below this date will be accepted from Mojang servers + QDateTime legacyCutoffDate; +}; +extern VersionFilterData g_VersionFilterData; diff --git a/logic/VersionFinal.cpp b/logic/VersionFinal.cpp deleted file mode 100644 index dedf2ce5..00000000 --- a/logic/VersionFinal.cpp +++ /dev/null @@ -1,377 +0,0 @@ -/* Copyright 2013 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 "VersionFinal.h" - -#include <QDebug> -#include <QFile> -#include <QDir> - -#include "OneSixVersionBuilder.h" -#include "OneSixInstance.h" - -template <typename A, typename B> QMap<A, B> invert(const QMap<B, A> &in) -{ - QMap<A, B> out; - for (auto it = in.begin(); it != in.end(); ++it) - { - out.insert(it.value(), it.key()); - } - return out; -} - -VersionFinal::VersionFinal(OneSixInstance *instance, QObject *parent) - : QAbstractListModel(parent), m_instance(instance) -{ - clear(); -} - -void VersionFinal::reload(const bool onlyVanilla, const QStringList &external) -{ - //FIXME: source of epic failure. - beginResetModel(); - OneSixVersionBuilder::build(this, m_instance, onlyVanilla, external); - reapply(true); - endResetModel(); -} - -void VersionFinal::clear() -{ - id.clear(); - time.clear(); - releaseTime.clear(); - type.clear(); - assets.clear(); - processArguments.clear(); - minecraftArguments.clear(); - minimumLauncherVersion = 0xDEADBEAF; - mainClass.clear(); - libraries.clear(); - tweakers.clear(); -} - -bool VersionFinal::canRemove(const int index) const -{ - if (index < versionFiles.size()) - { - return versionFiles.at(index)->fileId != "org.multimc.version.json"; - } - return false; -} - -bool VersionFinal::remove(const int index) -{ - if (canRemove(index) && QFile::remove(versionFiles.at(index)->filename)) - { - beginResetModel(); - versionFiles.removeAt(index); - reapply(true); - endResetModel(); - return true; - } - return false; -} - -bool VersionFinal::remove(const QString id) -{ - int i = 0; - for (auto file : versionFiles) - { - if (file->fileId == id) - { - return remove(i); - } - i++; - } - return false; -} - -QString VersionFinal::versionFileId(const int index) const -{ - if (index < 0 || index >= versionFiles.size()) - { - return QString(); - } - return versionFiles.at(index)->fileId; -} - -VersionFilePtr VersionFinal::versionFile(const QString &id) -{ - for (auto file : versionFiles) - { - if (file->fileId == id) - { - return file; - } - } - return 0; -} - -bool VersionFinal::hasFtbPack() -{ - return versionFile("org.multimc.ftb.pack.json") != nullptr; -} - -bool VersionFinal::removeFtbPack() -{ - return remove("org.multimc.ftb.pack.json"); -} - -QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNormalLibs() -{ - QList<std::shared_ptr<OneSixLibrary> > output; - for (auto lib : libraries) - { - if (lib->isActive() && !lib->isNative()) - { - output.append(lib); - } - } - return output; -} -QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNativeLibs() -{ - QList<std::shared_ptr<OneSixLibrary> > output; - for (auto lib : libraries) - { - if (lib->isActive() && lib->isNative()) - { - output.append(lib); - } - } - return output; -} - -std::shared_ptr<VersionFinal> VersionFinal::fromJson(const QJsonObject &obj) -{ - std::shared_ptr<VersionFinal> version(new VersionFinal(0)); - try - { - OneSixVersionBuilder::readJsonAndApplyToVersion(version.get(), obj); - } - catch(MMCError & err) - { - return 0; - } - return version; -} - -QVariant VersionFinal::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= versionFiles.size()) - return QVariant(); - - if (role == Qt::DisplayRole) - { - switch (column) - { - case 0: - return versionFiles.at(row)->name; - case 1: - return versionFiles.at(row)->version; - default: - return QVariant(); - } - } - return QVariant(); -} -QVariant VersionFinal::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) - { - if (role == Qt::DisplayRole) - { - switch (section) - { - case 0: - return tr("Name"); - case 1: - return tr("Version"); - default: - return QVariant(); - } - } - } - return QVariant(); -} -Qt::ItemFlags VersionFinal::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; -} - -int VersionFinal::rowCount(const QModelIndex &parent) const -{ - return versionFiles.size(); -} - -int VersionFinal::columnCount(const QModelIndex &parent) const -{ - return 2; -} - -bool VersionFinal::isCustom() -{ - return QDir(m_instance->instanceRoot()).exists("custom.json"); -} -bool VersionFinal::revertToBase() -{ - return QDir(m_instance->instanceRoot()).remove("custom.json"); -} - -QMap<QString, int> VersionFinal::getExistingOrder() const -{ - - QMap<QString, int> order; - // default - { - for (auto file : versionFiles) - { - order.insert(file->fileId, file->order); - } - } - // overriden - { - QMap<QString, int> overridenOrder = OneSixVersionBuilder::readOverrideOrders(m_instance); - for (auto id : order.keys()) - { - if (overridenOrder.contains(id)) - { - order[id] = overridenOrder[id]; - } - } - } - return order; -} - -void VersionFinal::move(const int index, const MoveDirection direction) -{ - int theirIndex; - if (direction == MoveUp) - { - theirIndex = index - 1; - } - else - { - theirIndex = index + 1; - } - if (theirIndex < 0 || theirIndex >= versionFiles.size()) - { - return; - } - const QString ourId = versionFileId(index); - const QString theirId = versionFileId(theirIndex); - if (ourId.isNull() || ourId.startsWith("org.multimc.") || - theirId.isNull() || theirId.startsWith("org.multimc.")) - { - return; - } - - VersionFilePtr we = versionFiles[index]; - VersionFilePtr them = versionFiles[theirIndex]; - if (!we || !them) - { - return; - } - beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex); - versionFiles.replace(theirIndex, we); - versionFiles.replace(index, them); - endMoveRows(); - - auto order = getExistingOrder(); - order[ourId] = theirIndex; - order[theirId] = index; - - if (!OneSixVersionBuilder::writeOverrideOrders(order, m_instance)) - { - throw MMCError(tr("Couldn't save the new order")); - } - else - { - reapply(); - } -} -void VersionFinal::resetOrder() -{ - QDir(m_instance->instanceRoot()).remove("order.json"); - reapply(); -} - -void VersionFinal::reapply(const bool alreadyReseting) -{ - if (!alreadyReseting) - { - beginResetModel(); - } - - clear(); - - auto existingOrders = getExistingOrder(); - QList<int> orders = existingOrders.values(); - std::sort(orders.begin(), orders.end()); - QList<VersionFilePtr> newVersionFiles; - for (auto order : orders) - { - auto file = versionFile(existingOrders.key(order)); - newVersionFiles.append(file); - file->applyTo(this); - } - versionFiles.swap(newVersionFiles); - finalize(); - if (!alreadyReseting) - { - endResetModel(); - } -} - -void VersionFinal::finalize() -{ - // HACK: deny april fools. my head hurts enough already. - QDate now = QDate::currentDate(); - bool isAprilFools = now.month() == 4 && now.day() == 1; - if (assets.endsWith("_af") && !isAprilFools) - { - assets = assets.left(assets.length() - 3); - } - if (assets.isEmpty()) - { - assets = "legacy"; - } - if (minecraftArguments.isEmpty()) - { - QString toCompare = processArguments.toLower(); - if (toCompare == "legacy") - { - minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - minecraftArguments = "--username ${auth_player_name} " - "--session ${auth_session} " - "--version ${profile_name}"; - } - } -} diff --git a/logic/ForgeInstaller.cpp b/logic/forge/ForgeInstaller.cpp index 94b3f319..7f952408 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/forge/ForgeInstaller.cpp @@ -14,9 +14,15 @@ */ #include "ForgeInstaller.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "net/HttpMetaCache.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/net/HttpMetaCache.h" +#include "logic/tasks/Task.h" +#include "logic/OneSixInstance.h" +#include "logic/forge/ForgeVersionList.h" +#include "logic/VersionFilterData.h" +#include "gui/dialogs/ProgressDialog.h" + #include <quazip.h> #include <quazipfile.h> #include <pathutils.h> @@ -24,23 +30,17 @@ #include <QRegularExpression> #include <QRegularExpressionMatch> #include "MultiMC.h" -#include "tasks/Task.h" -#include "OneSixInstance.h" -#include "lists/ForgeVersionList.h" -#include "gui/dialogs/ProgressDialog.h" - #include <QJsonDocument> #include <QJsonArray> #include <QSaveFile> #include <QCryptographicHash> -ForgeInstaller::ForgeInstaller() - : BaseInstaller() +ForgeInstaller::ForgeInstaller() : BaseInstaller() { } void ForgeInstaller::prepare(const QString &filename, const QString &universalUrl) { - std::shared_ptr<VersionFinal> newVersion; + std::shared_ptr<InstanceVersion> newVersion; m_universal_url = universalUrl; QuaZip zip(filename); @@ -73,7 +73,7 @@ void ForgeInstaller::prepare(const QString &filename, const QString &universalUr // read the forge version info { - newVersion = VersionFinal::fromJson(versionInfoVal.toObject()); + newVersion = InstanceVersion::fromJson(versionInfoVal.toObject()); if (!newVersion) return; } @@ -115,8 +115,8 @@ void ForgeInstaller::prepare(const QString &filename, const QString &universalUr } file.close(); - m_forge_version = newVersion; - realVersionId = m_forge_version->id = installObj.value("minecraft").toString(); + m_forge_json = newVersion; + realVersionId = m_forge_json->id = installObj.value("minecraft").toString(); } bool ForgeInstaller::add(OneSixInstance *to) { @@ -128,7 +128,7 @@ bool ForgeInstaller::add(OneSixInstance *to) QJsonObject obj; obj.insert("order", 5); - if (!m_forge_version) + if (!m_forge_json) return false; int sliding_insert_window = 0; { @@ -136,7 +136,7 @@ bool ForgeInstaller::add(OneSixInstance *to) // for each library in the version we are adding (except for the blacklisted) QSet<QString> blacklist{"lwjgl", "lwjgl_util", "lwjgl-platform"}; - for (auto lib : m_forge_version->libraries) + for (auto lib : m_forge_json->libraries) { QString libName = lib->name(); // WARNING: This could actually break. @@ -157,7 +157,7 @@ bool ForgeInstaller::add(OneSixInstance *to) bool found = false; bool equals = false; // find an entry that matches this one - for (auto tolib : to->getVanillaVersion()->libraries) + for (auto tolib : to->getFullVersion()->vanillaLibraries) { if (tolib->name() != libName) continue; @@ -187,8 +187,8 @@ bool ForgeInstaller::add(OneSixInstance *to) librariesPlus.prepend(libObj); } obj.insert("+libraries", librariesPlus); - obj.insert("mainClass", m_forge_version->mainClass); - QString args = m_forge_version->minecraftArguments; + obj.insert("mainClass", m_forge_json->mainClass); + QString args = m_forge_json->minecraftArguments; QStringList tweakers; { QRegularExpression expression("--tweakClass ([a-zA-Z0-9\\.]*)"); @@ -200,7 +200,7 @@ bool ForgeInstaller::add(OneSixInstance *to) match = expression.match(args); } } - if (!args.isEmpty() && args != to->getVanillaVersion()->minecraftArguments) + if (!args.isEmpty() && args != to->getFullVersion()->vanillaMinecraftArguments) { obj.insert("minecraftArguments", args); } @@ -208,10 +208,10 @@ bool ForgeInstaller::add(OneSixInstance *to) { obj.insert("+tweakers", QJsonArray::fromStringList(tweakers)); } - if (!m_forge_version->processArguments.isEmpty() && - m_forge_version->processArguments != to->getVanillaVersion()->processArguments) + if (!m_forge_json->processArguments.isEmpty() && + m_forge_json->processArguments != to->getFullVersion()->vanillaProcessArguments) { - obj.insert("processArguments", m_forge_version->processArguments); + obj.insert("processArguments", m_forge_json->processArguments); } } @@ -233,11 +233,65 @@ bool ForgeInstaller::add(OneSixInstance *to) return true; } +bool ForgeInstaller::addLegacy(OneSixInstance *to) +{ + if (!BaseInstaller::add(to)) + { + return false; + } + auto entry = MMC->metacache()->resolveEntry("minecraftforge", m_forge_version->filename()); + finalPath = PathCombine(to->jarModsDir(), m_forge_version->filename()); + if (!ensureFilePathExists(finalPath)) + { + return false; + } + if (!QFile::copy(entry->getFullPath(),finalPath)) + { + return false; + } + QJsonObject obj; + obj.insert("order", 5); + { + QJsonArray jarmodsPlus; + { + QJsonObject libObj; + libObj.insert("name", m_forge_version->universal_filename); + jarmodsPlus.append(libObj); + } + obj.insert("+jarMods", jarmodsPlus); + } + + obj.insert("name", QString("Forge")); + obj.insert("fileId", id()); + obj.insert("version", m_forge_version->jobbuildver); + obj.insert("mcVersion", to->intendedVersionId()); + if (g_VersionFilterData.fmlLibsMapping.contains(m_forge_version->mcver)) + { + QJsonArray traitsPlus; + traitsPlus.append(QString("legacyFML")); + obj.insert("+traits", traitsPlus); + } + auto fullversion = to->getFullVersion(); + fullversion->remove("net.minecraftforge"); + + QFile file(filename(to->instanceRoot())); + if (!file.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(QJsonDocument(obj).toJson()); + file.close(); + return true; +} + class ForgeInstallTask : public Task { Q_OBJECT public: - ForgeInstallTask(ForgeInstaller *installer, OneSixInstance *instance, BaseVersionPtr version, QObject *parent = 0) + ForgeInstallTask(ForgeInstaller *installer, OneSixInstance *instance, + BaseVersionPtr version, QObject *parent = 0) : Task(parent), m_installer(installer), m_instance(instance), m_version(version) { } @@ -245,57 +299,59 @@ public: protected: void executeTask() override { + setStatus(tr("Installing forge...")); + ForgeVersionPtr forgeVersion = std::dynamic_pointer_cast<ForgeVersion>(m_version); + if (!forgeVersion) { - setStatus(tr("Installing forge...")); - ForgeVersionPtr forgeVersion = - std::dynamic_pointer_cast<ForgeVersion>(m_version); - if (!forgeVersion) - { - emitFailed(tr("Unknown error occured")); - return; - } - auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); - if (entry->stale) + emitFailed(tr("Unknown error occured")); + return; + } + prepare(forgeVersion); + } + void prepare(ForgeVersionPtr forgeVersion) + { + auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename()); + auto installFunction = [this, entry, forgeVersion]() + { + if (!install(entry, forgeVersion)) { - NetJob *fjob = new NetJob("Forge download"); - fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); - connect(fjob, &NetJob::progress, [this](qint64 current, qint64 total){setProgress(100 * current / qMax((qint64)1, total));}); - connect(fjob, &NetJob::status, [this](const QString &msg){setStatus(msg);}); - connect(fjob, &NetJob::failed, [this](){emitFailed(tr("Failure to download forge"));}); - connect(fjob, &NetJob::succeeded, [this, entry, forgeVersion]() - { - if (!install(entry, forgeVersion)) - { - QLOG_ERROR() << "Failure installing forge"; - emitFailed(tr("Failure to install forge")); - } - else - { - reload(); - } - }); - fjob->start(); + QLOG_ERROR() << "Failure installing forge"; + emitFailed(tr("Failure to install forge")); } else { - if (!install(entry, forgeVersion)) - { - QLOG_ERROR() << "Failure installing forge"; - emitFailed(tr("Failure to install forge")); - } - else - { - reload(); - } + reload(); } + }; + + if (entry->stale) + { + NetJob *fjob = new NetJob("Forge download"); + fjob->addNetAction(CacheDownload::make(forgeVersion->url(), entry)); + connect(fjob, &NetJob::progress, [this](qint64 current, qint64 total) + { setProgress(100 * current / qMax((qint64)1, total)); }); + connect(fjob, &NetJob::status, [this](const QString & msg) + { setStatus(msg); }); + connect(fjob, &NetJob::failed, [this]() + { emitFailed(tr("Failure to download forge")); }); + connect(fjob, &NetJob::succeeded, installFunction); + fjob->start(); + } + else + { + installFunction(); } } - bool install(const std::shared_ptr<MetaEntry> &entry, const ForgeVersionPtr &forgeVersion) { - QString forgePath = entry->getFullPath(); - m_installer->prepare(forgePath, forgeVersion->universal_url); - return m_installer->add(m_instance); + if (forgeVersion->usesInstaller()) + { + QString forgePath = entry->getFullPath(); + m_installer->prepare(forgePath, forgeVersion->universal_url); + return m_installer->add(m_instance); + } + else + return m_installer->addLegacy(m_instance); } void reload() { @@ -320,8 +376,14 @@ private: BaseVersionPtr m_version; }; -ProgressProvider *ForgeInstaller::createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) +ProgressProvider *ForgeInstaller::createInstallTask(OneSixInstance *instance, + BaseVersionPtr version, QObject *parent) { + if (!version) + { + return nullptr; + } + m_forge_version = std::dynamic_pointer_cast<ForgeVersion>(version); return new ForgeInstallTask(this, instance, version, parent); } diff --git a/logic/ForgeInstaller.h b/logic/forge/ForgeInstaller.h index 05cc994b..14aeeb51 100644 --- a/logic/ForgeInstaller.h +++ b/logic/forge/ForgeInstaller.h @@ -15,28 +15,34 @@ #pragma once -#include "BaseInstaller.h" +#include "logic/BaseInstaller.h" #include <QString> #include <memory> -class VersionFinal; +class InstanceVersion; +class ForgeInstallTask; +class ForgeVersion; class ForgeInstaller : public BaseInstaller { + friend class ForgeInstallTask; public: ForgeInstaller(); + virtual ~ForgeInstaller(){}; + virtual ProgressProvider *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) override; +protected: + virtual QString id() const override { return "net.minecraftforge"; } void prepare(const QString &filename, const QString &universalUrl); bool add(OneSixInstance *to) override; - - QString id() const override { return "net.minecraftforge"; } - - ProgressProvider *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) override; + bool addLegacy(OneSixInstance *to); private: - // the version, read from the installer - std::shared_ptr<VersionFinal> m_forge_version; + // the parsed version json, read from the installer + std::shared_ptr<InstanceVersion> m_forge_json; + // the actual forge version + std::shared_ptr<ForgeVersion> m_forge_version; QString internalPath; QString finalPath; QString realVersionId; diff --git a/logic/net/ForgeMirror.h b/logic/forge/ForgeMirror.h index 2518dffe..2518dffe 100644 --- a/logic/net/ForgeMirror.h +++ b/logic/forge/ForgeMirror.h diff --git a/logic/net/ForgeMirrors.cpp b/logic/forge/ForgeMirrors.cpp index b224306f..b224306f 100644 --- a/logic/net/ForgeMirrors.cpp +++ b/logic/forge/ForgeMirrors.cpp diff --git a/logic/net/ForgeMirrors.h b/logic/forge/ForgeMirrors.h index 6784fba1..d25762db 100644 --- a/logic/net/ForgeMirrors.h +++ b/logic/forge/ForgeMirrors.h @@ -15,10 +15,10 @@ #pragma once -#include "NetAction.h" -#include "HttpMetaCache.h" -#include "ForgeXzDownload.h" -#include "NetJob.h" +#include "logic/net/NetAction.h" +#include "logic/net/HttpMetaCache.h" +#include "logic/net/NetJob.h" +#include "logic/forge/ForgeXzDownload.h" #include <QFile> #include <QTemporaryFile> typedef std::shared_ptr<class ForgeMirrors> ForgeMirrorsPtr; diff --git a/logic/forge/ForgeVersion.cpp b/logic/forge/ForgeVersion.cpp new file mode 100644 index 00000000..3131ec39 --- /dev/null +++ b/logic/forge/ForgeVersion.cpp @@ -0,0 +1,55 @@ +#include "ForgeVersion.h" +#include "logic/VersionFilterData.h" +#include <QObject> + +QString ForgeVersion::name() +{ + return "Forge " + jobbuildver; +} + +QString ForgeVersion::descriptor() +{ + return universal_filename; +} + +QString ForgeVersion::typeString() const +{ + if (is_recommended) + return QObject::tr("Recommended"); + return QString(); +} + +bool ForgeVersion::operator<(BaseVersion &a) +{ + ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); + if (!pa) + return true; + return m_buildnr < pa->m_buildnr; +} + +bool ForgeVersion::operator>(BaseVersion &a) +{ + ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); + if (!pa) + return false; + return m_buildnr > pa->m_buildnr; +} + +bool ForgeVersion::usesInstaller() +{ + if(installer_url.isEmpty()) + return false; + if(g_VersionFilterData.forgeInstallerBlacklist.contains(mcver)) + return false; + return true; +} + +QString ForgeVersion::filename() +{ + return usesInstaller() ? installer_filename : universal_filename; +} + +QString ForgeVersion::url() +{ + return usesInstaller() ? installer_url : universal_url; +} diff --git a/logic/forge/ForgeVersion.h b/logic/forge/ForgeVersion.h new file mode 100644 index 00000000..466f46bd --- /dev/null +++ b/logic/forge/ForgeVersion.h @@ -0,0 +1,33 @@ +#pragma once +#include <QString> +#include <memory> +#include "logic/BaseVersion.h" + +struct ForgeVersion; +typedef std::shared_ptr<ForgeVersion> ForgeVersionPtr; + +struct ForgeVersion : public BaseVersion +{ + virtual QString descriptor() override; + virtual QString name() override; + virtual QString typeString() const override; + virtual bool operator<(BaseVersion &a) override; + virtual bool operator>(BaseVersion &a) override; + + QString filename(); + QString url(); + + bool usesInstaller(); + + int m_buildnr = 0; + QString branch; + QString universal_url; + QString changelog_url; + QString installer_url; + QString jobbuildver; + QString mcver; + QString mcver_sane; + QString universal_filename; + QString installer_filename; + bool is_recommended = false; +}; diff --git a/logic/lists/ForgeVersionList.cpp b/logic/forge/ForgeVersionList.cpp index 4902dc64..efb30ba6 100644 --- a/logic/lists/ForgeVersionList.cpp +++ b/logic/forge/ForgeVersionList.cpp @@ -13,9 +13,10 @@ * limitations under the License. */ -#include "ForgeVersionList.h" -#include <logic/net/NetJob.h> -#include <logic/net/URLConstants.h> +#include "logic/forge/ForgeVersionList.h" +#include "logic/forge/ForgeVersion.h" +#include "logic/net/NetJob.h" +#include "logic/net/URLConstants.h" #include "MultiMC.h" #include <QtNetwork> @@ -71,7 +72,7 @@ QVariant ForgeVersionList::data(const QModelIndex &index, int role) const return version->name(); case 1: - return version->mcver; + return version->mcver_sane; case 2: return version->typeString(); @@ -236,7 +237,7 @@ bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out) if (!build_nr) continue; QJsonArray files = obj.value("files").toArray(); - QString url, jobbuildver, mcver, buildtype, filename; + QString url, jobbuildver, mcver, buildtype, universal_filename; QString changelog_url, installer_url; QString installer_filename; bool valid = false; @@ -254,7 +255,7 @@ bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out) url = file.value("url").toString(); jobbuildver = file.value("jobbuildver").toString(); int lastSlash = url.lastIndexOf('/'); - filename = url.mid(lastSlash + 1); + universal_filename = url.mid(lastSlash + 1); valid = true; } else if (buildtype == "changelog") @@ -281,15 +282,9 @@ bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out) fVersion->changelog_url = changelog_url; fVersion->installer_url = installer_url; fVersion->jobbuildver = jobbuildver; - fVersion->mcver = mcver; - if (installer_filename.isEmpty()) - { - fVersion->filename = filename; - } - else - { - fVersion->filename = installer_filename; - } + fVersion->mcver = fVersion->mcver_sane = mcver; + fVersion->installer_filename = installer_filename; + fVersion->universal_filename = universal_filename; fVersion->m_buildnr = build_nr; out.append(fVersion); } @@ -341,9 +336,17 @@ bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out) std::shared_ptr<ForgeVersion> fVersion(new ForgeVersion()); fVersion->m_buildnr = number.value("build").toDouble(); fVersion->jobbuildver = number.value("version").toString(); + fVersion->branch = number.value("branch").toString(""); fVersion->mcver = number.value("mcversion").toString(); - fVersion->filename = ""; - QString filename, installer_filename; + fVersion->universal_filename = ""; + fVersion->installer_filename = ""; + // HACK: here, we fix the minecraft version used by forge. + // HACK: this will inevitably break (later) + // FIXME: replace with a dictionary + fVersion->mcver_sane = fVersion->mcver; + fVersion->mcver_sane.replace("_pre", "-pre"); + + QString universal_filename, installer_filename; QJsonArray files = number.value("files").toArray(); for (auto fIt = files.begin(); fIt != files.end(); ++fIt) { @@ -353,37 +356,47 @@ bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out) { continue; } - if (file.at(1).toString() == "installer") + + QString extension = file.at(0).toString(); + QString part = file.at(1).toString(); + QString checksum = file.at(2).toString(); + + // insane form of mcver is used here + QString longVersion = fVersion->mcver + "-" + fVersion->jobbuildver; + if (!fVersion->branch.isEmpty()) + { + longVersion = longVersion + "-" + fVersion->branch; + } + QString filename = artifact + "-" + longVersion + "-" + part + "." + extension; + + QString url = QString("%1/%2/%3") + .arg(webpath) + .arg(longVersion) + .arg(filename); + + if (part == "installer") { - fVersion->installer_url = QString("%1/%2-%3/%4-%2-%3-installer.%5").arg( - webpath, fVersion->mcver, fVersion->jobbuildver, artifact, - file.at(0).toString()); - installer_filename = QString("%1-%2-%3-installer.%4").arg( - artifact, fVersion->mcver, fVersion->jobbuildver, file.at(0).toString()); + fVersion->installer_url = url; + installer_filename = filename; } - else if (file.at(1).toString() == "universal") + else if (part == "universal") { - fVersion->universal_url = QString("%1/%2-%3/%4-%2-%3-universal.%5").arg( - webpath, fVersion->mcver, fVersion->jobbuildver, artifact, - file.at(0).toString()); - filename = QString("%1-%2-%3-universal.%4").arg( - artifact, fVersion->mcver, fVersion->jobbuildver, file.at(0).toString()); + fVersion->universal_url = url; + universal_filename = filename; } - else if (file.at(1).toString() == "changelog") + else if (part == "changelog") { - fVersion->changelog_url = QString("%1/%2-%3/%4-%2-%3-changelog.%5").arg( - webpath, fVersion->mcver, fVersion->jobbuildver, artifact, - file.at(0).toString()); + fVersion->changelog_url = url; } } if (fVersion->installer_url.isEmpty() && fVersion->universal_url.isEmpty()) { continue; } - fVersion->filename = fVersion->installer_url.isEmpty() ? filename : installer_filename; + fVersion->universal_filename = universal_filename; + fVersion->installer_filename = installer_filename; out.append(fVersion); } - return true; } @@ -425,6 +438,7 @@ void ForgeListLoadTask::listFailed() QLOG_ERROR() << "Getting forge version list failed for reasons unknown."; } } + void ForgeListLoadTask::gradleListFailed() { auto reply = gradleListDownload->m_reply; diff --git a/logic/lists/ForgeVersionList.h b/logic/forge/ForgeVersionList.h index b19d3f56..477edb3d 100644 --- a/logic/lists/ForgeVersionList.h +++ b/logic/forge/ForgeVersionList.h @@ -18,57 +18,12 @@ #include <QObject> #include <QAbstractListModel> #include <QUrl> - #include <QNetworkReply> -#include "BaseVersionList.h" + +#include "logic/BaseVersionList.h" #include "logic/tasks/Task.h" #include "logic/net/NetJob.h" - -class ForgeVersion; -typedef std::shared_ptr<ForgeVersion> ForgeVersionPtr; - -struct ForgeVersion : public BaseVersion -{ - virtual QString descriptor() override - { - return filename; - } - ; - virtual QString name() override - { - return "Forge " + jobbuildver; - } - ; - virtual QString typeString() const override - { - if (installer_url.isEmpty()) - return "Universal"; - else - return "Installer"; - } - - virtual bool operator<(BaseVersion &a) override - { - ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); - if(!pa) - return true; - return m_buildnr < pa->m_buildnr; - } - virtual bool operator>(BaseVersion &a) override - { - ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); - if(!pa) - return false; - return m_buildnr > pa->m_buildnr; - } - int m_buildnr = 0; - QString universal_url; - QString changelog_url; - QString installer_url; - QString jobbuildver; - QString mcver; - QString filename; -}; +#include "logic/forge/ForgeVersion.h" class ForgeVersionList : public BaseVersionList { @@ -86,6 +41,8 @@ public: virtual BaseVersionPtr getLatestStable() const; + ForgeVersionPtr findVersionByVersionNr(QString version); + virtual QVariant data(const QModelIndex &index, int role) const; virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; virtual int columnCount(const QModelIndex &parent) const; diff --git a/logic/net/ForgeXzDownload.cpp b/logic/forge/ForgeXzDownload.cpp index 359ad858..359ad858 100644 --- a/logic/net/ForgeXzDownload.cpp +++ b/logic/forge/ForgeXzDownload.cpp diff --git a/logic/net/ForgeXzDownload.h b/logic/forge/ForgeXzDownload.h index 7bdfb6d9..f2564380 100644 --- a/logic/net/ForgeXzDownload.h +++ b/logic/forge/ForgeXzDownload.h @@ -15,8 +15,8 @@ #pragma once -#include "NetAction.h" -#include "HttpMetaCache.h" +#include "logic/net/NetAction.h" +#include "logic/net/HttpMetaCache.h" #include <QFile> #include <QTemporaryFile> #include "ForgeMirror.h" diff --git a/logic/LegacyForge.cpp b/logic/forge/LegacyForge.cpp index 94212ae4..94212ae4 100644 --- a/logic/LegacyForge.cpp +++ b/logic/forge/LegacyForge.cpp diff --git a/logic/LegacyForge.h b/logic/forge/LegacyForge.h index f4165ffa..ec49f63c 100644 --- a/logic/LegacyForge.h +++ b/logic/forge/LegacyForge.h @@ -15,7 +15,7 @@ #pragma once -#include "Mod.h" +#include "logic/Mod.h" class MinecraftForge : public Mod { diff --git a/logic/JavaChecker.cpp b/logic/java/JavaChecker.cpp index b87ee3d5..b87ee3d5 100644 --- a/logic/JavaChecker.cpp +++ b/logic/java/JavaChecker.cpp diff --git a/logic/JavaChecker.h b/logic/java/JavaChecker.h index e19895f7..e19895f7 100644 --- a/logic/JavaChecker.h +++ b/logic/java/JavaChecker.h diff --git a/logic/JavaCheckerJob.cpp b/logic/java/JavaCheckerJob.cpp index b0aea758..b0aea758 100644 --- a/logic/JavaCheckerJob.cpp +++ b/logic/java/JavaCheckerJob.cpp diff --git a/logic/JavaCheckerJob.h b/logic/java/JavaCheckerJob.h index 132a92d4..132a92d4 100644 --- a/logic/JavaCheckerJob.h +++ b/logic/java/JavaCheckerJob.h diff --git a/logic/JavaUtils.cpp b/logic/java/JavaUtils.cpp index 3a3046bd..09719c73 100644 --- a/logic/JavaUtils.cpp +++ b/logic/java/JavaUtils.cpp @@ -16,7 +16,6 @@ #include <QStringList> #include <QString> #include <QDir> -#include <QMessageBox> #include <QStringList> #include <setting.h> @@ -24,11 +23,10 @@ #include "MultiMC.h" -#include "JavaUtils.h" #include "logger/QsLog.h" -#include "gui/dialogs/VersionSelectDialog.h" -#include "JavaCheckerJob.h" -#include "lists/JavaVersionList.h" +#include "logic/java/JavaUtils.h" +#include "logic/java/JavaCheckerJob.h" +#include "logic/java/JavaVersionList.h" JavaUtils::JavaUtils() { @@ -124,7 +122,7 @@ QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString javaVersion->id = subKeyName; javaVersion->arch = archType; javaVersion->path = - QDir(PathCombine(value, "bin")).absoluteFilePath("java.exe"); + QDir(PathCombine(value, "bin")).absoluteFilePath("javaw.exe"); javas.append(javaVersion); } @@ -154,12 +152,12 @@ QList<QString> JavaUtils::FindJavaPaths() KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); java_candidates.append(JRE64s); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/java.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/java.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe")); java_candidates.append(JDK64s); java_candidates.append(JRE32s); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/java.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/java.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe")); java_candidates.append(JDK32s); java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); diff --git a/logic/JavaUtils.h b/logic/java/JavaUtils.h index 22a68ef3..af92100f 100644 --- a/logic/JavaUtils.h +++ b/logic/java/JavaUtils.h @@ -21,7 +21,7 @@ #include <osutils.h> #include "JavaCheckerJob.h" #include "JavaChecker.h" -#include "lists/JavaVersionList.h" +#include "JavaVersionList.h" #if WINDOWS #include <windows.h> diff --git a/logic/lists/JavaVersionList.cpp b/logic/java/JavaVersionList.cpp index 4fd0bc19..dcb6ced6 100644 --- a/logic/lists/JavaVersionList.cpp +++ b/logic/java/JavaVersionList.cpp @@ -13,16 +13,16 @@ * limitations under the License. */ -#include "JavaVersionList.h" -#include "MultiMC.h" - #include <QtNetwork> #include <QtXml> #include <QRegExp> +#include "MultiMC.h" #include "logger/QsLog.h" -#include "logic/JavaCheckerJob.h" -#include "logic/JavaUtils.h" + +#include "logic/java/JavaVersionList.h" +#include "logic/java/JavaCheckerJob.h" +#include "logic/java/JavaUtils.h" JavaVersionList::JavaVersionList(QObject *parent) : BaseVersionList(parent) { diff --git a/logic/lists/JavaVersionList.h b/logic/java/JavaVersionList.h index e6cc8e5f..a46f33a2 100644 --- a/logic/lists/JavaVersionList.h +++ b/logic/java/JavaVersionList.h @@ -18,9 +18,9 @@ #include <QObject> #include <QAbstractListModel> -#include "BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/tasks/Task.h" -#include "logic/JavaCheckerJob.h" +#include "logic/java/JavaCheckerJob.h" class JavaListLoadTask; diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp deleted file mode 100644 index b9d60c61..00000000 --- a/logic/lists/MinecraftVersionList.cpp +++ /dev/null @@ -1,290 +0,0 @@ -/* Copyright 2013 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 "MinecraftVersionList.h" -#include "MultiMC.h" -#include "logic/net/URLConstants.h" - -#include <QtXml> - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> -#include <QJsonParseError> - -#include <QtAlgorithms> - -#include <QtNetwork> - -MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) -{ -} - -Task *MinecraftVersionList::getLoadTask() -{ - return new MCVListLoadTask(this); -} - -bool MinecraftVersionList::isLoaded() -{ - return m_loaded; -} - -const BaseVersionPtr MinecraftVersionList::at(int i) const -{ - return m_vlist.at(i); -} - -int MinecraftVersionList::count() const -{ - return m_vlist.count(); -} - -static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) -{ - auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); - auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); - return left->timestamp > right->timestamp; -} - -void MinecraftVersionList::sortInternal() -{ - qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); -} - -void MinecraftVersionList::sort() -{ - beginResetModel(); - sortInternal(); - endResetModel(); -} - -BaseVersionPtr MinecraftVersionList::getLatestStable() const -{ - for (int i = 0; i < m_vlist.length(); i++) - { - auto ver = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist.at(i)); - if (ver->is_latest && !ver->is_snapshot) - { - return m_vlist.at(i); - } - } - return BaseVersionPtr(); -} - -void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions) -{ - beginResetModel(); - m_vlist = versions; - m_loaded = true; - sortInternal(); - endResetModel(); -} - -inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) -{ - QDomNodeList elementList = parent.elementsByTagName(tagname); - if (elementList.count()) - return elementList.at(0).toElement(); - else - return QDomElement(); -} - -inline QDateTime timeFromS3Time(QString str) -{ - return QDateTime::fromString(str, Qt::ISODate); -} - -MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) -{ - m_list = vlist; - m_currentStable = NULL; - vlistReply = nullptr; - legacyWhitelist.insert("1.5.2"); - legacyWhitelist.insert("1.5.1"); - legacyWhitelist.insert("1.5"); - legacyWhitelist.insert("1.4.7"); - legacyWhitelist.insert("1.4.6"); - legacyWhitelist.insert("1.4.5"); - legacyWhitelist.insert("1.4.4"); - legacyWhitelist.insert("1.4.3"); - legacyWhitelist.insert("1.4.2"); - legacyWhitelist.insert("1.4.1"); - legacyWhitelist.insert("1.4"); - legacyWhitelist.insert("1.3.2"); - legacyWhitelist.insert("1.3.1"); - legacyWhitelist.insert("1.3"); - legacyWhitelist.insert("1.2.5"); - legacyWhitelist.insert("1.2.4"); - legacyWhitelist.insert("1.2.3"); - legacyWhitelist.insert("1.2.2"); - legacyWhitelist.insert("1.2.1"); - legacyWhitelist.insert("1.1"); - legacyWhitelist.insert("1.0.1"); - legacyWhitelist.insert("1.0"); -} - -MCVListLoadTask::~MCVListLoadTask() -{ -} - -void MCVListLoadTask::executeTask() -{ - setStatus(tr("Loading instance version list...")); - auto worker = MMC->qnam(); - vlistReply = worker->get(QNetworkRequest(QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json"))); - connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); -} - -void MCVListLoadTask::list_downloaded() -{ - if (vlistReply->error() != QNetworkReply::NoError) - { - vlistReply->deleteLater(); - emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString()); - return; - } - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError); - vlistReply->deleteLater(); - - if (jsonError.error != QJsonParseError::NoError) - { - emitFailed("Error parsing version list JSON:" + jsonError.errorString()); - return; - } - - if (!jsonDoc.isObject()) - { - emitFailed("Error parsing version list JSON: jsonDoc is not an object"); - return; - } - - QJsonObject root = jsonDoc.object(); - - // Get the ID of the latest release and the latest snapshot. - if (!root.value("latest").isObject()) - { - emitFailed("Error parsing version list JSON: version list is missing 'latest' object"); - return; - } - - QJsonObject latest = root.value("latest").toObject(); - - QString latestReleaseID = latest.value("release").toString(""); - QString latestSnapshotID = latest.value("snapshot").toString(""); - if (latestReleaseID.isEmpty()) - { - emitFailed("Error parsing version list JSON: latest release field is missing"); - return; - } - if (latestSnapshotID.isEmpty()) - { - emitFailed("Error parsing version list JSON: latest snapshot field is missing"); - return; - } - - // Now, get the array of versions. - if (!root.value("versions").isArray()) - { - emitFailed( - "Error parsing version list JSON: version list object is missing 'versions' array"); - return; - } - QJsonArray versions = root.value("versions").toArray(); - - QList<BaseVersionPtr> tempList; - for (int i = 0; i < versions.count(); i++) - { - bool is_snapshot = false; - bool is_latest = false; - - // Load the version info. - if (!versions[i].isObject()) - { - // FIXME: log this somewhere - continue; - } - QJsonObject version = versions[i].toObject(); - QString versionID = version.value("id").toString(""); - QString versionTimeStr = version.value("releaseTime").toString(""); - QString versionTypeStr = version.value("type").toString(""); - if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) - { - // FIXME: log this somewhere - continue; - } - - // Parse the timestamp. - QDateTime versionTime = timeFromS3Time(versionTimeStr); - if (!versionTime.isValid()) - { - // FIXME: log this somewhere - continue; - } - // Parse the type. - MinecraftVersion::VersionType versionType; - // OneSix or Legacy. use filter to determine type - if (versionTypeStr == "release") - { - versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy - : MinecraftVersion::OneSix; - is_latest = (versionID == latestReleaseID); - is_snapshot = false; - } - else if (versionTypeStr == "snapshot") // It's a snapshot... yay - { - versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy - : MinecraftVersion::OneSix; - is_latest = (versionID == latestSnapshotID); - is_snapshot = true; - } - else if (versionTypeStr == "old_alpha") - { - versionType = MinecraftVersion::Nostalgia; - is_latest = false; - is_snapshot = false; - } - else if (versionTypeStr == "old_beta") - { - versionType = MinecraftVersion::Legacy; - is_latest = false; - is_snapshot = false; - } - else - { - // FIXME: log this somewhere - continue; - } - // Get the download URL. - QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; - - // Now, we construct the version object and add it to the list. - std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); - mcVersion->m_name = mcVersion->m_descriptor = versionID; - mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); - mcVersion->download_url = dlUrl; - mcVersion->is_latest = is_latest; - mcVersion->is_snapshot = is_snapshot; - mcVersion->type = versionType; - tempList.append(mcVersion); - } - m_list->updateListData(tempList); - - emitSucceeded(); - return; -} diff --git a/logic/LiteLoaderInstaller.cpp b/logic/liteloader/LiteLoaderInstaller.cpp index 99cc5643..ea1a4396 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/liteloader/LiteLoaderInstaller.cpp @@ -20,11 +20,11 @@ #include "logger/QsLog.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/OneSixInstance.h" #include "MultiMC.h" -#include "lists/LiteLoaderVersionList.h" +#include "logic/liteloader/LiteLoaderVersionList.h" LiteLoaderInstaller::LiteLoaderInstaller() : BaseInstaller() { diff --git a/logic/LiteLoaderInstaller.h b/logic/liteloader/LiteLoaderInstaller.h index 3ab5acb2..43ad6b83 100644 --- a/logic/LiteLoaderInstaller.h +++ b/logic/liteloader/LiteLoaderInstaller.h @@ -15,12 +15,11 @@ #pragma once -#include "BaseInstaller.h" - #include <QString> #include <QMap> -#include "logic/lists/LiteLoaderVersionList.h" +#include "logic/BaseInstaller.h" +#include "logic/liteloader/LiteLoaderVersionList.h" class LiteLoaderInstaller : public BaseInstaller { diff --git a/logic/lists/LiteLoaderVersionList.cpp b/logic/liteloader/LiteLoaderVersionList.cpp index ef95eefd..ef95eefd 100644 --- a/logic/lists/LiteLoaderVersionList.cpp +++ b/logic/liteloader/LiteLoaderVersionList.cpp diff --git a/logic/lists/LiteLoaderVersionList.h b/logic/liteloader/LiteLoaderVersionList.h index bfc913e5..0aecc3e1 100644 --- a/logic/lists/LiteLoaderVersionList.h +++ b/logic/liteloader/LiteLoaderVersionList.h @@ -19,10 +19,10 @@ #include <QString> #include <QStringList> -#include "BaseVersionList.h" -#include "logic/tasks/Task.h" #include "logic/BaseVersion.h" -#include <logic/net/NetJob.h> +#include "logic/BaseVersionList.h" +#include "logic/tasks/Task.h" +#include "logic/net/NetJob.h" class LLListLoadTask; class QNetworkReply; diff --git a/logic/minecraft/InstanceVersion.cpp b/logic/minecraft/InstanceVersion.cpp new file mode 100644 index 00000000..ca0e3796 --- /dev/null +++ b/logic/minecraft/InstanceVersion.cpp @@ -0,0 +1,537 @@ +/* Copyright 2013 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 <QDebug> +#include <QFile> +#include <QDir> +#include <QUuid> +#include <QJsonDocument> +#include <QJsonArray> +#include <pathutils.h> + +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/VersionBuilder.h" +#include "logic/OneSixInstance.h" + +InstanceVersion::InstanceVersion(OneSixInstance *instance, QObject *parent) + : QAbstractListModel(parent), m_instance(instance) +{ + clear(); +} + +void InstanceVersion::reload(const QStringList &external) +{ + m_externalPatches = external; + beginResetModel(); + VersionBuilder::build(this, m_instance, m_externalPatches); + reapply(true); + endResetModel(); +} + +void InstanceVersion::clear() +{ + id.clear(); + m_updateTimeString.clear(); + m_updateTime = QDateTime(); + m_releaseTimeString.clear(); + m_releaseTime = QDateTime(); + type.clear(); + assets.clear(); + processArguments.clear(); + minecraftArguments.clear(); + minimumLauncherVersion = 0xDEADBEAF; + mainClass.clear(); + appletClass.clear(); + libraries.clear(); + tweakers.clear(); + jarMods.clear(); + traits.clear(); +} + +bool InstanceVersion::canRemove(const int index) const +{ + return VersionPatches.at(index)->isMoveable(); +} + +bool InstanceVersion::preremove(VersionPatchPtr patch) +{ + bool ok = true; + for(auto & jarmod: patch->getJarMods()) + { + QString fullpath =PathCombine(m_instance->jarModsDir(), jarmod->name); + QFileInfo finfo (fullpath); + if(finfo.exists()) + ok &= QFile::remove(fullpath); + } + return ok; +} + +bool InstanceVersion::remove(const int index) +{ + if (!canRemove(index)) + return false; + if(!preremove(VersionPatches[index])) + { + return false; + } + if(!QFile::remove(VersionPatches.at(index)->getPatchFilename())) + return false; + beginRemoveRows(QModelIndex(), index, index); + VersionPatches.removeAt(index); + endRemoveRows(); + reapply(true); + saveCurrentOrder(); + return true; +} + +bool InstanceVersion::remove(const QString id) +{ + int i = 0; + for (auto patch : VersionPatches) + { + if (patch->getPatchID() == id) + { + return remove(i); + } + i++; + } + return false; +} + +QString InstanceVersion::versionFileId(const int index) const +{ + if (index < 0 || index >= VersionPatches.size()) + { + return QString(); + } + return VersionPatches.at(index)->getPatchID(); +} + +VersionPatchPtr InstanceVersion::versionPatch(const QString &id) +{ + for (auto file : VersionPatches) + { + if (file->getPatchID() == id) + { + return file; + } + } + return 0; +} + +VersionPatchPtr InstanceVersion::versionPatch(int index) +{ + if(index < 0 || index >= VersionPatches.size()) + return 0; + return VersionPatches[index]; +} + + +bool InstanceVersion::hasJarMods() +{ + return !jarMods.isEmpty(); +} + +bool InstanceVersion::hasFtbPack() +{ + return versionPatch("org.multimc.ftb.pack.json") != nullptr; +} + +bool InstanceVersion::removeFtbPack() +{ + return remove("org.multimc.ftb.pack.json"); +} + +bool InstanceVersion::isVanilla() +{ + QDir patches(PathCombine(m_instance->instanceRoot(), "patches/")); + if(VersionPatches.size() > 1) + return false; + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) + return false; + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "version.json"))) + return false; + return true; +} + +bool InstanceVersion::revertToVanilla() +{ + beginResetModel(); + // remove custom.json, if present + QString customPath = PathCombine(m_instance->instanceRoot(), "custom.json"); + if(QFile::exists(customPath)) + { + if(!QFile::remove(customPath)) + { + endResetModel(); + return false; + } + } + // remove version.json, if present + QString versionPath = PathCombine(m_instance->instanceRoot(), "version.json"); + if(QFile::exists(versionPath)) + { + if(!QFile::remove(versionPath)) + { + endResetModel(); + return false; + } + } + // remove patches, if present + auto it = VersionPatches.begin(); + while (it != VersionPatches.end()) + { + if ((*it)->isMoveable()) + { + if(!preremove(*it)) + { + endResetModel(); + saveCurrentOrder(); + return false; + } + if(!QFile::remove((*it)->getPatchFilename())) + { + endResetModel(); + saveCurrentOrder(); + return false; + } + it = VersionPatches.erase(it); + } + else + it++; + } + reapply(true); + endResetModel(); + saveCurrentOrder(); + return true; +} + +bool InstanceVersion::hasDeprecatedVersionFiles() +{ + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) + return true; + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "version.json"))) + return true; + return false; +} + +bool InstanceVersion::removeDeprecatedVersionFiles() +{ + beginResetModel(); + // remove custom.json, if present + QString customPath = PathCombine(m_instance->instanceRoot(), "custom.json"); + if(QFile::exists(customPath)) + { + if(!QFile::remove(customPath)) + { + endResetModel(); + return false; + } + } + // remove version.json, if present + QString versionPath = PathCombine(m_instance->instanceRoot(), "version.json"); + if(QFile::exists(versionPath)) + { + if(!QFile::remove(versionPath)) + { + endResetModel(); + return false; + } + } + endResetModel(); + return true; +} + +QList<std::shared_ptr<OneSixLibrary> > InstanceVersion::getActiveNormalLibs() +{ + QList<std::shared_ptr<OneSixLibrary> > output; + for (auto lib : libraries) + { + if (lib->isActive() && !lib->isNative()) + { + output.append(lib); + } + } + return output; +} +QList<std::shared_ptr<OneSixLibrary> > InstanceVersion::getActiveNativeLibs() +{ + QList<std::shared_ptr<OneSixLibrary> > output; + for (auto lib : libraries) + { + if (lib->isActive() && lib->isNative()) + { + output.append(lib); + } + } + return output; +} + +std::shared_ptr<InstanceVersion> InstanceVersion::fromJson(const QJsonObject &obj) +{ + std::shared_ptr<InstanceVersion> version(new InstanceVersion(0)); + try + { + VersionBuilder::readJsonAndApplyToVersion(version.get(), obj); + } + catch(MMCError & err) + { + return 0; + } + return version; +} + +QVariant InstanceVersion::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= VersionPatches.size()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + switch (column) + { + case 0: + return VersionPatches.at(row)->getPatchName(); + case 1: + return VersionPatches.at(row)->getPatchVersion(); + default: + return QVariant(); + } + } + return QVariant(); +} +QVariant InstanceVersion::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) + { + switch (section) + { + case 0: + return tr("Name"); + case 1: + return tr("Version"); + default: + return QVariant(); + } + } + } + return QVariant(); +} +Qt::ItemFlags InstanceVersion::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +int InstanceVersion::rowCount(const QModelIndex &parent) const +{ + return VersionPatches.size(); +} + +int InstanceVersion::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +void InstanceVersion::saveCurrentOrder() const +{ + PatchOrder order; + for(auto item: VersionPatches) + { + if(!item->isMoveable()) + continue; + order.append(item->getPatchID()); + } + VersionBuilder::writeOverrideOrders(m_instance, order); +} + +void InstanceVersion::move(const int index, const MoveDirection direction) +{ + int theirIndex; + if (direction == MoveUp) + { + theirIndex = index - 1; + } + else + { + theirIndex = index + 1; + } + + if (index < 0 || index >= VersionPatches.size()) + return; + if (theirIndex >= rowCount()) + theirIndex = rowCount() - 1; + if (theirIndex == -1) + theirIndex = rowCount() - 1; + if (index == theirIndex) + return; + int togap = theirIndex > index ? theirIndex + 1 : theirIndex; + + auto from = versionPatch(index); + auto to = versionPatch(theirIndex); + + if (!from || !to || !to->isMoveable() || !from->isMoveable()) + { + return; + } + beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); + VersionPatches.swap(index, theirIndex); + endMoveRows(); + saveCurrentOrder(); + reapply(); +} +void InstanceVersion::resetOrder() +{ + QDir(m_instance->instanceRoot()).remove("order.json"); + reload(m_externalPatches); +} + +void InstanceVersion::reapply(const bool alreadyReseting) +{ + clear(); + for(auto file: VersionPatches) + { + file->applyTo(this); + } + finalize(); +} + +void InstanceVersion::finalize() +{ + // HACK: deny april fools. my head hurts enough already. + QDate now = QDate::currentDate(); + bool isAprilFools = now.month() == 4 && now.day() == 1; + if (assets.endsWith("_af") && !isAprilFools) + { + assets = assets.left(assets.length() - 3); + } + if (assets.isEmpty()) + { + assets = "legacy"; + } + auto finalizeArguments = [&]( QString & minecraftArguments, const QString & processArguments ) -> void + { + if (!minecraftArguments.isEmpty()) + return; + QString toCompare = processArguments.toLower(); + if (toCompare == "legacy") + { + minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + minecraftArguments = "--username ${auth_player_name} " + "--session ${auth_session} " + "--version ${profile_name}"; + } + }; + finalizeArguments(vanillaMinecraftArguments, vanillaProcessArguments); + finalizeArguments(minecraftArguments, processArguments); +} + +void InstanceVersion::installJarMods(QStringList selectedFiles) +{ + for(auto filename: selectedFiles) + { + installJarModByFilename(filename); + } +} + +void InstanceVersion::installJarModByFilename(QString filepath) +{ + QString patchDir = PathCombine(m_instance->instanceRoot(), "patches"); + if(!ensureFolderPathExists(patchDir)) + { + // THROW... + return; + } + + if (!ensureFolderPathExists(m_instance->jarModsDir())) + { + // THROW... + return; + } + + QFileInfo sourceInfo(filepath); + auto uuid = QUuid::createUuid(); + QString id = uuid.toString().remove('{').remove('}'); + QString target_filename = id + ".jar"; + QString target_id = "org.multimc.jarmod." + id; + QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; + QString finalPath = PathCombine(m_instance->jarModsDir(), target_filename); + + QFileInfo targetInfo(finalPath); + if(targetInfo.exists()) + { + // THROW + return; + } + + if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) + { + // THROW + return; + } + + auto f = std::make_shared<VersionFile>(); + auto jarMod = std::make_shared<Jarmod>(); + jarMod->name = target_filename; + f->jarMods.append(jarMod); + f->name = target_name; + f->fileId = target_id; + f->order = getFreeOrderNumber(); + + QFile file(PathCombine(patchDir, target_id + ".json")); + if (!file.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return; + // THROW + } + file.write(f->toJson(true).toJson()); + file.close(); + int index = VersionPatches.size(); + beginInsertRows(QModelIndex(), index, index); + VersionPatches.append(f); + endInsertRows(); + saveCurrentOrder(); +} + +int InstanceVersion::getFreeOrderNumber() +{ + int largest = 100; + // yes, I do realize this is dumb. The order thing itself is dumb. and to be removed next. + for(auto thing: VersionPatches) + { + int order = thing->getOrder(); + if(order > largest) + largest = order; + } + return largest + 1; +} diff --git a/logic/VersionFinal.h b/logic/minecraft/InstanceVersion.h index 41fd23bd..6b69ab47 100644 --- a/logic/VersionFinal.h +++ b/logic/minecraft/InstanceVersion.h @@ -23,14 +23,15 @@ #include "OneSixLibrary.h" #include "VersionFile.h" +#include "JarMod.h" class OneSixInstance; -class VersionFinal : public QAbstractListModel +class InstanceVersion : public QAbstractListModel { Q_OBJECT public: - explicit VersionFinal(OneSixInstance *instance, QObject *parent = 0); + explicit InstanceVersion(OneSixInstance *instance, QObject *parent = 0); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; @@ -38,22 +39,32 @@ public: virtual int columnCount(const QModelIndex &parent) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; - void reload(const bool onlyVanilla = false, const QStringList &external = QStringList()); + void reload(const QStringList &external = QStringList()); void clear(); bool canRemove(const int index) const; QString versionFileId(const int index) const; - // does this instance have an all overriding custom.json - bool isCustom(); - // remove custom.json - bool revertToBase(); - // does this instance have an FTB pack patch file? + // is this version unmodded vanilla minecraft? + bool isVanilla(); + // remove any customizations on top of vanilla + bool revertToVanilla(); + + // does this version consist of obsolete files? + bool hasDeprecatedVersionFiles(); + // remove obsolete files + bool removeDeprecatedVersionFiles(); + + // does this version have an FTB pack patch file? bool hasFtbPack(); // remove FTB pack bool removeFtbPack(); + // does this version have any jar mods? + bool hasJarMods(); + void installJarMods(QStringList selectedFiles); + void installJarModByFilename(QString filepath); enum MoveDirection { MoveUp, MoveDown }; void move(const int index, const MoveDirection direction); @@ -72,16 +83,24 @@ public: QList<std::shared_ptr<OneSixLibrary>> getActiveNormalLibs(); QList<std::shared_ptr<OneSixLibrary>> getActiveNativeLibs(); - static std::shared_ptr<VersionFinal> fromJson(const QJsonObject &obj); + static std::shared_ptr<InstanceVersion> fromJson(const QJsonObject &obj); +private: + bool preremove(VersionPatchPtr patch); + // data members public: /// the ID - determines which jar to use! ACTUALLY IMPORTANT! QString id; - /// Last updated time - as a string - QString time; - /// Release time - as a string - QString releaseTime; + + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + /// Release type - "release" or "snapshot" QString type; /// Assets type - "legacy" or a version ID @@ -91,6 +110,8 @@ public: * ex: "username_session_version" */ QString processArguments; + /// Same as above, but only for vanilla + QString vanillaProcessArguments; /** * arguments that should be used for launching minecraft * @@ -98,6 +119,8 @@ public: * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" */ QString minecraftArguments; + /// Same as above, but only for vanilla + QString vanillaMinecraftArguments; /** * the minimum launcher version required by this version ... current is 4 (at point of * writing) @@ -111,9 +134,22 @@ public: * The main class to load first */ QString mainClass; - + /** + * The applet class, for some very old minecraft releases + */ + QString appletClass; + /// the list of libs - both active and inactive, native and java QList<std::shared_ptr<OneSixLibrary>> libraries; + + /// same, but only vanilla. + QList<std::shared_ptr<OneSixLibrary>> vanillaLibraries; + + /// traits, collected from all the version files (version files can only add) + QSet<QString> traits; + + /// A list of jar mods. version files can add those. + QList<JarmodPtr> jarMods; /* FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. @@ -136,10 +172,13 @@ public: */ // QList<Rule> rules; - QList<VersionFilePtr> versionFiles; - VersionFilePtr versionFile(const QString &id); + QList<VersionPatchPtr> VersionPatches; + VersionPatchPtr versionPatch(const QString &id); + VersionPatchPtr versionPatch(int index); private: + QStringList m_externalPatches; OneSixInstance *m_instance; - QMap<QString, int> getExistingOrder() const; + void saveCurrentOrder() const; + int getFreeOrderNumber(); }; diff --git a/logic/minecraft/JarMod.cpp b/logic/minecraft/JarMod.cpp new file mode 100644 index 00000000..18a9411c --- /dev/null +++ b/logic/minecraft/JarMod.cpp @@ -0,0 +1,56 @@ +#include "JarMod.h" +#include "logic/MMCJson.h" +using namespace MMCJson; + +JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename) +{ + JarmodPtr out(new Jarmod()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a jarmod that doesn't have a 'name' field"); + } + out->name = libObj.value("name").toString(); + + auto readString = [libObj, filename](const QString & key, QString & variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("url", out->baseurl); + readString("MMC-hint", out->hint); + readString("MMC-absoluteUrl", out->absoluteUrl); + if(!out->baseurl.isEmpty() && out->absoluteUrl.isEmpty()) + { + out->absoluteUrl = out->baseurl + out->name; + } + return out; +} + +QJsonObject Jarmod::toJson() +{ + QJsonObject out; + writeString(out, "name", name); + writeString(out, "url", baseurl); + writeString(out, "MMC-absoluteUrl", absoluteUrl); + writeString(out, "MMC-hint", hint); + return out; +} + +QString Jarmod::url() +{ + if(!absoluteUrl.isEmpty()) + return absoluteUrl; + else return baseurl + name; +} diff --git a/logic/minecraft/JarMod.h b/logic/minecraft/JarMod.h new file mode 100644 index 00000000..c438dbcd --- /dev/null +++ b/logic/minecraft/JarMod.h @@ -0,0 +1,18 @@ +#pragma once +#include <QString> +#include <QJsonObject> +#include <memory> +class Jarmod; +typedef std::shared_ptr<Jarmod> JarmodPtr; +class Jarmod +{ +public: /* methods */ + static JarmodPtr fromJson(const QJsonObject &libObj, const QString &filename); + QJsonObject toJson(); + QString url(); +public: /* data */ + QString name; + QString baseurl; + QString hint; + QString absoluteUrl; +}; diff --git a/logic/minecraft/MinecraftVersion.cpp b/logic/minecraft/MinecraftVersion.cpp new file mode 100644 index 00000000..488a180a --- /dev/null +++ b/logic/minecraft/MinecraftVersion.cpp @@ -0,0 +1,143 @@ +#include "MinecraftVersion.h" +#include "InstanceVersion.h" +#include "VersionBuildError.h" +#include "VersionBuilder.h" + +bool MinecraftVersion::usesLegacyLauncher() +{ + return m_traits.contains("legacyLaunch") || m_traits.contains("aplhaLaunch"); +} + +QString MinecraftVersion::descriptor() +{ + return m_descriptor; +} + +QString MinecraftVersion::name() +{ + return m_name; +} + +QString MinecraftVersion::typeString() const +{ + if(m_type == "snapshot") + { + return QObject::tr("Snapshot"); + } + else if (m_type == "release") + { + return QObject::tr("Regular release"); + } + else if (m_type == "old_alpha") + { + return QObject::tr("Alpha"); + } + else if (m_type == "old_beta") + { + return QObject::tr("Beta"); + } + else + { + return QString(); + } +} + +bool MinecraftVersion::hasJarMods() +{ + return false; +} + +bool MinecraftVersion::isMinecraftVersion() +{ + return true; +} + +// 1. assume the local file is good. load, check. If it's good, apply. +// 2. if discrepancies are found, fall out and fail (impossible to apply incomplete version). +void MinecraftVersion::applyFileTo(InstanceVersion *version) +{ + QFileInfo versionFile(QString("versions/%1/%1.dat").arg(m_descriptor)); + + auto versionObj = VersionBuilder::parseBinaryJsonFile(versionFile); + versionObj->applyTo(version); +} + +void MinecraftVersion::applyTo(InstanceVersion *version) +{ + // do we have this one cached? + if (m_versionSource == Local) + { + applyFileTo(version); + return; + } + // if not builtin, do not proceed any further. + if (m_versionSource != Builtin) + { + throw VersionIncomplete(QObject::tr( + "Minecraft version %1 could not be applied: version files are missing.").arg(m_descriptor)); + } + if (!m_descriptor.isNull()) + { + version->id = m_descriptor; + } + if (!m_mainClass.isNull()) + { + version->mainClass = m_mainClass; + } + if (!m_appletClass.isNull()) + { + version->appletClass = m_appletClass; + } + if (!m_processArguments.isNull()) + { + version->vanillaProcessArguments = m_processArguments; + version->processArguments = m_processArguments; + } + if (!m_type.isNull()) + { + version->type = m_type; + } + if (!m_releaseTimeString.isNull()) + { + version->m_releaseTimeString = m_releaseTimeString; + version->m_releaseTime = m_releaseTime; + } + if (!m_updateTimeString.isNull()) + { + version->m_updateTimeString = m_updateTimeString; + version->m_updateTime = m_updateTime; + } + version->traits.unite(m_traits); +} + +int MinecraftVersion::getOrder() +{ + return order; +} + +void MinecraftVersion::setOrder(int order) +{ + this->order = order; +} + +QList<JarmodPtr> MinecraftVersion::getJarMods() +{ + return QList<JarmodPtr>(); +} + +QString MinecraftVersion::getPatchName() +{ + return "Minecraft"; +} +QString MinecraftVersion::getPatchVersion() +{ + return m_descriptor; +} +QString MinecraftVersion::getPatchID() +{ + return "net.minecraft"; +} +QString MinecraftVersion::getPatchFilename() +{ + return QString(); +} diff --git a/logic/minecraft/MinecraftVersion.h b/logic/minecraft/MinecraftVersion.h new file mode 100644 index 00000000..82073a97 --- /dev/null +++ b/logic/minecraft/MinecraftVersion.h @@ -0,0 +1,103 @@ +/* Copyright 2013 Andrew Okin + * + * 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 <QStringList> +#include <QSet> +#include <QDateTime> + +#include "logic/BaseVersion.h" +#include "VersionPatch.h" +#include "VersionFile.h" +#include "VersionSource.h" + +class InstanceVersion; +class MinecraftVersion; +typedef std::shared_ptr<MinecraftVersion> MinecraftVersionPtr; + +class MinecraftVersion : public BaseVersion, public VersionPatch +{ +public: /* methods */ + bool usesLegacyLauncher(); + virtual QString descriptor() override; + virtual QString name() override; + virtual QString typeString() const override; + virtual bool hasJarMods() override; + virtual bool isMinecraftVersion() override; + virtual void applyTo(InstanceVersion *version) override; + virtual int getOrder(); + virtual void setOrder(int order); + virtual QList<JarmodPtr> getJarMods() override; + virtual QString getPatchID() override; + virtual QString getPatchVersion() override; + virtual QString getPatchName() override; + virtual QString getPatchFilename() override; + bool needsUpdate() + { + return m_versionSource == Remote; + } + bool hasUpdate() + { + return m_versionSource == Remote || (m_versionSource == Local && upstreamUpdate); + } + +private: /* methods */ + void applyFileTo(InstanceVersion *version); + +public: /* data */ + /// The URL that this version will be downloaded from. maybe. + QString download_url; + + VersionSource m_versionSource = Builtin; + + /// the human readable version name + QString m_name; + + /// the version ID. + QString m_descriptor; + + /// version traits. added by MultiMC + QSet<QString> m_traits; + + /// The main class this version uses (if any, can be empty). + QString m_mainClass; + + /// The applet class this version uses (if any, can be empty). + QString m_appletClass; + + /// The process arguments used by this version + QString m_processArguments; + + /// The type of this release + QString m_type; + + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + + /// MD5 hash of the minecraft jar + QString m_jarChecksum; + + /// order of this file... default = -2 + int order = -2; + + /// an update available from Mojang + MinecraftVersionPtr upstreamUpdate; +}; diff --git a/logic/minecraft/MinecraftVersionList.cpp b/logic/minecraft/MinecraftVersionList.cpp new file mode 100644 index 00000000..3aa1ac01 --- /dev/null +++ b/logic/minecraft/MinecraftVersionList.cpp @@ -0,0 +1,602 @@ +/* Copyright 2013 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 <QtXml> +#include "logic/MMCJson.h" +#include <QtAlgorithms> +#include <QtNetwork> + +#include "MultiMC.h" +#include "MMCError.h" + +#include "MinecraftVersionList.h" +#include "logic/net/URLConstants.h" + +#include "ParseUtils.h" +#include "VersionBuilder.h" +#include <logic/VersionFilterData.h> +#include <pathutils.h> + +static const char * localVersionCache = "versions/versions.dat"; + +class ListLoadError : public MMCError +{ +public: + ListLoadError(QString cause) : MMCError(cause) {}; + virtual ~ListLoadError() noexcept + { + } +}; + +MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) +{ + loadBuiltinList(); + loadCachedList(); +} + +Task *MinecraftVersionList::getLoadTask() +{ + return new MCVListLoadTask(this); +} + +bool MinecraftVersionList::isLoaded() +{ + return m_loaded; +} + +const BaseVersionPtr MinecraftVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +int MinecraftVersionList::count() const +{ + return m_vlist.count(); +} + +static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) +{ + auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); + auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); + return left->m_releaseTime > right->m_releaseTime; +} + +void MinecraftVersionList::sortInternal() +{ + qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); +} + +void MinecraftVersionList::loadCachedList() +{ + QFile localIndex(localVersionCache); + if (!localIndex.exists()) + { + return; + } + if (!localIndex.open(QIODevice::ReadOnly)) + { + // FIXME: this is actually a very bad thing! How do we deal with this? + QLOG_ERROR() << "The minecraft version cache can't be read."; + return; + } + auto data = localIndex.readAll(); + try + { + localIndex.close(); + QJsonDocument jsonDoc = QJsonDocument::fromBinaryData(data); + if (jsonDoc.isNull()) + { + throw ListLoadError(tr("Error reading the version list.")); + } + loadMojangList(jsonDoc, Local); + } + catch (MMCError &e) + { + // the cache has gone bad for some reason... flush it. + QLOG_ERROR() << "The minecraft version cache is corrupted. Flushing cache."; + localIndex.remove(); + return; + } + m_hasLocalIndex = true; +} + +void MinecraftVersionList::loadBuiltinList() +{ + QLOG_INFO() << "Loading builtin version list."; + // grab the version list data from internal resources. + QResource versionList(":/versions/minecraft.json"); + QFile filez(versionList.absoluteFilePath()); + filez.open(QIODevice::ReadOnly); + auto data = filez.readAll(); + + // parse the data as json + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + QJsonObject root = jsonDoc.object(); + + // parse all the versions + for (const auto version : MMCJson::ensureArray(root.value("versions"))) + { + QJsonObject versionObj = version.toObject(); + QString versionID = versionObj.value("id").toString(""); + QString versionTypeStr = versionObj.value("type").toString(""); + if (versionID.isEmpty() || versionTypeStr.isEmpty()) + { + QLOG_ERROR() << "Parsed version is missing ID or type"; + continue; + } + + if (g_VersionFilterData.legacyBlacklist.contains(versionID)) + { + QLOG_WARN() << "Blacklisted legacy version ignored: " << versionID; + continue; + } + + // Now, we construct the version object and add it to the list. + std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; + + // Parse the timestamp. + if (!parse_timestamp(versionObj.value("releaseTime").toString(""), + mcVersion->m_releaseTimeString, mcVersion->m_releaseTime)) + { + QLOG_ERROR() << "Error while parsing version" << versionID + << ": invalid version timestamp"; + continue; + } + + // Get the download URL. + mcVersion->download_url = + "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + + mcVersion->m_versionSource = Builtin; + mcVersion->m_type = versionTypeStr; + mcVersion->m_appletClass = versionObj.value("appletClass").toString(""); + mcVersion->m_mainClass = versionObj.value("mainClass").toString(""); + mcVersion->m_jarChecksum = versionObj.value("checksum").toString(""); + mcVersion->m_processArguments = versionObj.value("processArguments").toString("legacy"); + if (versionObj.contains("+traits")) + { + for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits"))) + { + mcVersion->m_traits.insert(MMCJson::ensureString(traitVal)); + } + } + m_lookup[versionID] = mcVersion; + m_vlist.append(mcVersion); + } +} + +void MinecraftVersionList::loadMojangList(QJsonDocument jsonDoc, VersionSource source) +{ + QLOG_INFO() << "Loading" << ((source == Remote) ? "remote" : "local") << "version list."; + + if (!jsonDoc.isObject()) + { + throw ListLoadError(tr("Error parsing version list JSON: jsonDoc is not an object")); + } + + QJsonObject root = jsonDoc.object(); + + try + { + QJsonObject latest = MMCJson::ensureObject(root.value("latest")); + m_latestReleaseID = MMCJson::ensureString(latest.value("release")); + m_latestSnapshotID = MMCJson::ensureString(latest.value("snapshot")); + } + catch (MMCError &err) + { + QLOG_ERROR() + << tr("Error parsing version list JSON: couldn't determine latest versions"); + } + + // Now, get the array of versions. + if (!root.value("versions").isArray()) + { + throw ListLoadError(tr("Error parsing version list JSON: version list object is " + "missing 'versions' array")); + } + QJsonArray versions = root.value("versions").toArray(); + + QList<BaseVersionPtr> tempList; + for (auto version : versions) + { + // Load the version info. + if (!version.isObject()) + { + QLOG_ERROR() << "Error while parsing version list : invalid JSON structure"; + continue; + } + + QJsonObject versionObj = version.toObject(); + QString versionID = versionObj.value("id").toString(""); + if (versionID.isEmpty()) + { + QLOG_ERROR() << "Error while parsing version : version ID is missing"; + continue; + } + + if (g_VersionFilterData.legacyBlacklist.contains(versionID)) + { + QLOG_WARN() << "Blacklisted legacy version ignored: " << versionID; + continue; + } + + // Now, we construct the version object and add it to the list. + std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; + + if (!parse_timestamp(versionObj.value("releaseTime").toString(""), + mcVersion->m_releaseTimeString, mcVersion->m_releaseTime)) + { + QLOG_ERROR() << "Error while parsing version" << versionID + << ": invalid release timestamp"; + continue; + } + if (!parse_timestamp(versionObj.value("time").toString(""), + mcVersion->m_updateTimeString, mcVersion->m_updateTime)) + { + QLOG_ERROR() << "Error while parsing version" << versionID + << ": invalid update timestamp"; + continue; + } + + if (mcVersion->m_releaseTime < g_VersionFilterData.legacyCutoffDate) + { + continue; + } + + // depends on where we load the version from -- network request or local file? + mcVersion->m_versionSource = source; + + QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + mcVersion->download_url = dlUrl; + QString versionTypeStr = versionObj.value("type").toString(""); + if (versionTypeStr.isEmpty()) + { + // FIXME: log this somewhere + continue; + } + // OneSix or Legacy. use filter to determine type + if (versionTypeStr == "release") + { + } + else if (versionTypeStr == "snapshot") // It's a snapshot... yay + { + } + else if (versionTypeStr == "old_alpha") + { + } + else if (versionTypeStr == "old_beta") + { + } + else + { + // FIXME: log this somewhere + continue; + } + mcVersion->m_type = versionTypeStr; + tempList.append(mcVersion); + } + updateListData(tempList); + if(source == Remote) + { + m_loaded = true; + } +} + +void MinecraftVersionList::sort() +{ + beginResetModel(); + sortInternal(); + endResetModel(); +} + +BaseVersionPtr MinecraftVersionList::getLatestStable() const +{ + if(m_lookup.contains(m_latestReleaseID)) + return m_lookup[m_latestReleaseID]; + return BaseVersionPtr(); +} + +void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions) +{ + beginResetModel(); + for (auto version : versions) + { + auto descr = version->descriptor(); + + if (!m_lookup.contains(descr)) + { + m_lookup[version->descriptor()] = version; + m_vlist.append(version); + continue; + } + auto orig = std::dynamic_pointer_cast<MinecraftVersion>(m_lookup[descr]); + auto added = std::dynamic_pointer_cast<MinecraftVersion>(version); + // updateListData is called after Mojang list loads. those can be local or remote + // remote comes always after local + // any other options are ignored + if (orig->m_versionSource != Local || added->m_versionSource != Remote) + { + continue; + } + // is it actually an update? + if (orig->m_updateTime >= added->m_updateTime) + { + // nope. + continue; + } + // alright, it's an update. put it inside the original, for further processing. + orig->upstreamUpdate = added; + } + sortInternal(); + endResetModel(); +} + +inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) +{ + QDomNodeList elementList = parent.elementsByTagName(tagname); + if (elementList.count()) + return elementList.at(0).toElement(); + else + return QDomElement(); +} + +MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) +{ + m_list = vlist; + m_currentStable = NULL; + vlistReply = nullptr; +} + +void MCVListLoadTask::executeTask() +{ + setStatus(tr("Loading instance version list...")); + auto worker = MMC->qnam(); + vlistReply = worker->get(QNetworkRequest( + QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json"))); + connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); +} + +void MCVListLoadTask::list_downloaded() +{ + if (vlistReply->error() != QNetworkReply::NoError) + { + vlistReply->deleteLater(); + emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString()); + return; + } + + auto data = vlistReply->readAll(); + vlistReply->deleteLater(); + try + { + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + throw ListLoadError( + tr("Error parsing version list JSON: %1").arg(jsonError.errorString())); + } + m_list->loadMojangList(jsonDoc, Remote); + } + catch (MMCError &e) + { + emitFailed(e.cause()); + return; + } + + emitSucceeded(); + return; +} + +MCVListVersionUpdateTask::MCVListVersionUpdateTask(MinecraftVersionList *vlist, + QString updatedVersion) + : Task() +{ + m_list = vlist; + versionToUpdate = updatedVersion; +} + +void MCVListVersionUpdateTask::executeTask() +{ + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionToUpdate + "/" + + versionToUpdate + ".json"; + auto job = new NetJob("Version index"); + job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); + specificVersionDownloadJob.reset(job); + connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(json_downloaded())); + connect(specificVersionDownloadJob.get(), SIGNAL(failed(QString)), SIGNAL(failed(QString))); + connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + specificVersionDownloadJob->start(); +} + +void MCVListVersionUpdateTask::json_downloaded() +{ + NetActionPtr DlJob = specificVersionDownloadJob->first(); + auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data; + specificVersionDownloadJob.reset(); + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed(tr("The download version file is not valid.")); + return; + } + VersionFilePtr file; + try + { + file = VersionFile::fromJson(jsonDoc, "net.minecraft.json", false); + } + catch (MMCError &e) + { + emitFailed(tr("Couldn't process version file: %1").arg(e.cause())); + return; + } + QList<RawLibraryPtr> filteredLibs; + QList<RawLibraryPtr> lwjglLibs; + QSet<QString> lwjglFilter = { + "net.java.jinput:jinput", "net.java.jinput:jinput-platform", + "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", + "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; + for (auto lib : file->overwriteLibs) + { + if (lwjglFilter.contains(lib->fullname())) + { + lwjglLibs.append(lib); + } + else + { + filteredLibs.append(lib); + } + } + file->overwriteLibs = filteredLibs; + + // TODO: recognize and add LWJGL versions here. + + file->fileId = "net.minecraft"; + + // now dump the file to disk + auto doc = file->toJson(false); + auto newdata = doc.toBinaryData(); + QLOG_INFO() << newdata; + QString targetPath = "versions/" + versionToUpdate + "/" + versionToUpdate + ".dat"; + ensureFilePathExists(targetPath); + QSaveFile vfile1(targetPath); + if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) + { + emitFailed(tr("Can't open %1 for writing.").arg(targetPath)); + return; + } + qint64 actual = 0; + if ((actual = vfile1.write(newdata)) != newdata.size()) + { + emitFailed(tr("Failed to write into %1. Written %2 out of %3.") + .arg(targetPath) + .arg(actual) + .arg(newdata.size())); + return; + } + if (!vfile1.commit()) + { + emitFailed(tr("Can't commit changes to %1").arg(targetPath)); + return; + } + + m_list->finalizeUpdate(versionToUpdate); + emitSucceeded(); +} + +std::shared_ptr<Task> MinecraftVersionList::createUpdateTask(QString version) +{ + return std::shared_ptr<Task>(new MCVListVersionUpdateTask(this, version)); +} + +void MinecraftVersionList::saveCachedList() +{ + // FIXME: throw. + if (!ensureFilePathExists(localVersionCache)) + return; + QSaveFile tfile(localVersionCache); + if (!tfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + return; + QJsonObject toplevel; + QJsonArray entriesArr; + for (auto version : m_vlist) + { + auto mcversion = std::dynamic_pointer_cast<MinecraftVersion>(version); + // do not save the remote versions. + if (mcversion->m_versionSource != Local) + continue; + QJsonObject entryObj; + + entryObj.insert("id", mcversion->descriptor()); + entryObj.insert("time", mcversion->m_updateTimeString); + entryObj.insert("releaseTime", mcversion->m_releaseTimeString); + entryObj.insert("type", mcversion->m_type); + entriesArr.append(entryObj); + } + toplevel.insert("versions", entriesArr); + + { + bool someLatest = false; + QJsonObject latestObj; + if(!m_latestReleaseID.isNull()) + { + latestObj.insert("release", m_latestReleaseID); + someLatest = true; + } + if(!m_latestSnapshotID.isNull()) + { + latestObj.insert("snapshot", m_latestSnapshotID); + someLatest = true; + } + if(someLatest) + { + toplevel.insert("latest", latestObj); + } + } + + QJsonDocument doc(toplevel); + QByteArray jsonData = doc.toBinaryData(); + qint64 result = tfile.write(jsonData); + if (result == -1) + return; + if (result != jsonData.size()) + return; + tfile.commit(); +} + +void MinecraftVersionList::finalizeUpdate(QString version) +{ + int idx = -1; + for (int i = 0; i < m_vlist.size(); i++) + { + if (version == m_vlist[i]->descriptor()) + { + idx = i; + break; + } + } + if (idx == -1) + { + return; + } + + auto updatedVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist[idx]); + + if (updatedVersion->m_versionSource == Builtin) + return; + + if (updatedVersion->upstreamUpdate) + { + auto updatedWith = updatedVersion->upstreamUpdate; + updatedWith->m_versionSource = Local; + m_vlist[idx] = updatedWith; + m_lookup[version] = updatedWith; + } + else + { + updatedVersion->m_versionSource = Local; + } + + dataChanged(index(idx), index(idx)); + + saveCachedList(); +} diff --git a/logic/lists/MinecraftVersionList.h b/logic/minecraft/MinecraftVersionList.h index 167f4d11..4753ce05 100644 --- a/logic/lists/MinecraftVersionList.h +++ b/logic/minecraft/MinecraftVersionList.h @@ -19,11 +19,13 @@ #include <QList> #include <QSet> -#include "BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/tasks/Task.h" -#include "logic/MinecraftVersion.h" +#include "logic/minecraft/MinecraftVersion.h" +#include <logic/net/NetJob.h> class MCVListLoadTask; +class MCVListVersionUpdateTask; class QNetworkReply; class MinecraftVersionList : public BaseVersionList @@ -31,11 +33,19 @@ class MinecraftVersionList : public BaseVersionList Q_OBJECT private: void sortInternal(); + void loadBuiltinList(); + void loadMojangList(QJsonDocument jsonDoc, VersionSource source); + void loadCachedList(); + void saveCachedList(); + void finalizeUpdate(QString version); public: friend class MCVListLoadTask; + friend class MCVListVersionUpdateTask; explicit MinecraftVersionList(QObject *parent = 0); + std::shared_ptr<Task> createUpdateTask(QString version); + virtual Task *getLoadTask(); virtual bool isLoaded(); virtual const BaseVersionPtr at(int i) const; @@ -46,8 +56,12 @@ public: protected: QList<BaseVersionPtr> m_vlist; + QMap<QString, BaseVersionPtr> m_lookup; bool m_loaded = false; + bool m_hasLocalIndex = false; + QString m_latestReleaseID = "INVALID"; + QString m_latestSnapshotID = "INVALID"; protected slots: @@ -60,9 +74,9 @@ class MCVListLoadTask : public Task public: explicit MCVListLoadTask(MinecraftVersionList *vlist); - ~MCVListLoadTask(); + virtual ~MCVListLoadTask() override{}; - virtual void executeTask(); + virtual void executeTask() override; protected slots: @@ -72,5 +86,23 @@ protected: QNetworkReply *vlistReply; MinecraftVersionList *m_list; MinecraftVersion *m_currentStable; - QSet<QString> legacyWhitelist; +}; + +class MCVListVersionUpdateTask : public Task +{ + Q_OBJECT + +public: + explicit MCVListVersionUpdateTask(MinecraftVersionList *vlist, QString updatedVersion); + virtual ~MCVListVersionUpdateTask() override{}; + virtual void executeTask() override; + +protected +slots: + void json_downloaded(); + +protected: + NetJobPtr specificVersionDownloadJob; + QString versionToUpdate; + MinecraftVersionList *m_list; }; diff --git a/logic/OneSixLibrary.cpp b/logic/minecraft/OneSixLibrary.cpp index 45fa169e..7f69d9f8 100644 --- a/logic/OneSixLibrary.cpp +++ b/logic/minecraft/OneSixLibrary.cpp @@ -23,6 +23,23 @@ #include <JlCompress.h> #include "logger/QsLog.h" +OneSixLibrary::OneSixLibrary(RawLibraryPtr base) +{ + m_name = base->m_name; + m_base_url = base->m_base_url; + m_hint = base->m_hint; + m_absolute_url = base->m_absolute_url; + extract_excludes = base->extract_excludes; + m_native_suffixes = base->m_native_suffixes; + m_rules = base->m_rules; + finalize(); +} + +OneSixLibraryPtr OneSixLibrary::fromRawLibrary(RawLibraryPtr lib) +{ + return OneSixLibraryPtr(new OneSixLibrary(lib)); +} + void OneSixLibrary::finalize() { QStringList parts = m_name.split(':'); @@ -30,7 +47,7 @@ void OneSixLibrary::finalize() relative.replace('.', '/'); relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; - if (!m_is_native) + if (!isNative()) relative += ".jar"; else { @@ -65,7 +82,7 @@ void OneSixLibrary::finalize() } m_is_active = (result == Allow); } - if (m_is_native) + if (isNative()) { m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); m_decenttype = "Native"; @@ -84,13 +101,8 @@ void OneSixLibrary::setBaseUrl(const QString &base_url) { m_base_url = base_url; } -void OneSixLibrary::setIsNative() -{ - m_is_native = true; -} void OneSixLibrary::addNative(OpSys os, const QString &suffix) { - m_is_native = true; m_native_suffixes[os] = suffix; } void OneSixLibrary::clearSuffixes() @@ -105,10 +117,6 @@ bool OneSixLibrary::isActive() const { return m_is_active; } -bool OneSixLibrary::isNative() const -{ - return m_is_native; -} QString OneSixLibrary::downloadUrl() const { if (m_absolute_url.size()) @@ -223,52 +231,3 @@ bool OneSixLibrary::extractTo(QString target_dir) } return true; } - -QJsonObject OneSixLibrary::toJson() -{ - QJsonObject libRoot; - libRoot.insert("name", m_name); - if (m_absolute_url.size()) - libRoot.insert("MMC-absoluteUrl", m_absolute_url); - if (m_hint.size()) - libRoot.insert("MMC-hint", m_hint); - if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty()) - { - libRoot.insert("url", m_base_url); - } - if (isNative() && m_native_suffixes.size()) - { - QJsonObject nativeList; - auto iter = m_native_suffixes.begin(); - while (iter != m_native_suffixes.end()) - { - nativeList.insert(OpSys_toString(iter.key()), iter.value()); - iter++; - } - libRoot.insert("natives", nativeList); - } - if (isNative() && extract_excludes.size()) - { - QJsonArray excludes; - QJsonObject extract; - for (auto exclude : extract_excludes) - { - excludes.append(exclude); - } - extract.insert("exclude", excludes); - libRoot.insert("extract", extract); - } - if (m_rules.size()) - { - QJsonArray allRules; - for (auto &rule : m_rules) - { - QJsonObject ruleObj = rule->toJson(); - allRules.append(ruleObj); - } - libRoot.insert("rules", allRules); - } - return libRoot; -} diff --git a/logic/OneSixLibrary.h b/logic/minecraft/OneSixLibrary.h index 61d4c8e2..3d38985b 100644 --- a/logic/OneSixLibrary.h +++ b/logic/minecraft/OneSixLibrary.h @@ -23,28 +23,17 @@ #include <memory> #include "logic/net/URLConstants.h" -#include "OpSys.h" +#include "logic/minecraft/OpSys.h" +#include "logic/minecraft/RawLibrary.h" class Rule; class OneSixLibrary; typedef std::shared_ptr<OneSixLibrary> OneSixLibraryPtr; -class OneSixLibrary +class OneSixLibrary : public RawLibrary { private: - // basic values used internally (so far) - QString m_name; - QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; - QList<std::shared_ptr<Rule>> m_rules; - - // custom values - /// absolute URL. takes precedence over m_download_path, if defined - QString m_absolute_url; - /// download hint - how to actually get the library - QString m_hint; - - // derived values used for real things /// a decent name fit for display QString m_decentname; /// a decent version fit for display @@ -57,13 +46,9 @@ private: QString m_download_url; /// is this lib actually active on the current OS? bool m_is_active = false; - /// is the library a native? - bool m_is_native = false; - /// native suffixes per OS - QMap<OpSys, QString> m_native_suffixes; public: - QStringList extract_excludes; + QString minVersion; enum DependType @@ -80,15 +65,16 @@ public: m_name = name; dependType = type; } - + /// Constructor + OneSixLibrary(RawLibraryPtr base); + static OneSixLibraryPtr fromRawLibrary(RawLibraryPtr lib); + /// Returns the raw name field QString rawName() const { return m_name; } - QJsonObject toJson(); - /** * finalize the library, processing the input values into derived values and state * @@ -116,8 +102,6 @@ public: /// Set the url base for downloads void setBaseUrl(const QString &base_url); - /// Call this to mark the library as 'native' (it's a zip archive with DLLs) - void setIsNative(); /// Attach a name suffix to the specified OS native void addNative(OpSys os, const QString &suffix); /// Clears all suffixes @@ -127,8 +111,6 @@ public: /// Returns true if the library should be loaded (or extracted, in case of natives) bool isActive() const; - /// Returns true if the library is native - bool isNative() const; /// Get the URL to download the library from QString downloadUrl() const; /// Get the relative path where the library should be saved diff --git a/logic/OneSixRule.cpp b/logic/minecraft/OneSixRule.cpp index d8d13b50..93c49e5e 100644 --- a/logic/OneSixRule.cpp +++ b/logic/minecraft/OneSixRule.cpp @@ -18,6 +18,15 @@ #include "OneSixRule.h" +RuleAction RuleAction_fromString(QString name) +{ + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; +} + QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules) { QList<std::shared_ptr<Rule>> rules; @@ -79,11 +88,3 @@ QJsonObject OsRule::toJson() return ruleObj; } -RuleAction RuleAction_fromString(QString name) -{ - if (name == "allow") - return Allow; - if (name == "disallow") - return Disallow; - return Defer; -} diff --git a/logic/OneSixRule.h b/logic/minecraft/OneSixRule.h index 426e2886..a18093b0 100644 --- a/logic/OneSixRule.h +++ b/logic/minecraft/OneSixRule.h @@ -16,8 +16,13 @@ #pragma once #include <QString> +#include <QList> +#include <QJsonObject> +#include <memory> +#include "OpSys.h" -#include "logic/OneSixLibrary.h" +class OneSixLibrary; +class Rule; enum RuleAction { @@ -26,7 +31,6 @@ enum RuleAction Defer }; -RuleAction RuleAction_fromString(QString); QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules); class Rule diff --git a/logic/OpSys.cpp b/logic/minecraft/OpSys.cpp index e001b7f3..e001b7f3 100644 --- a/logic/OpSys.cpp +++ b/logic/minecraft/OpSys.cpp diff --git a/logic/OpSys.h b/logic/minecraft/OpSys.h index 363c87d7..363c87d7 100644 --- a/logic/OpSys.h +++ b/logic/minecraft/OpSys.h diff --git a/logic/minecraft/ParseUtils.cpp b/logic/minecraft/ParseUtils.cpp new file mode 100644 index 00000000..f94de6ff --- /dev/null +++ b/logic/minecraft/ParseUtils.cpp @@ -0,0 +1,24 @@ +#include <QDateTime> +#include <QString> +#include "ParseUtils.h" +#include <logic/MMCJson.h> + +QDateTime timeFromS3Time(QString str) +{ + return QDateTime::fromString(str, Qt::ISODate); +} + +bool parse_timestamp (const QString & raw, QString & save_here, QDateTime & parse_here) +{ + save_here = raw; + if (save_here.isEmpty()) + { + return false; + } + parse_here = timeFromS3Time(save_here); + if (!parse_here.isValid()) + { + return false; + } + return true; +} diff --git a/logic/minecraft/ParseUtils.h b/logic/minecraft/ParseUtils.h new file mode 100644 index 00000000..bd2f6ffb --- /dev/null +++ b/logic/minecraft/ParseUtils.h @@ -0,0 +1,14 @@ +#pragma once +#include <QString> +#include <QDateTime> + +/** + * parse the S3 timestamp in 'raw' and fill the forwarded variables. + * return true/false for success/failure + */ +bool parse_timestamp (const QString &raw, QString &save_here, QDateTime &parse_here); + +/** + * take the timestamp used by S3 and turn it into QDateTime + */ +QDateTime timeFromS3Time(QString str); diff --git a/logic/minecraft/RawLibrary.cpp b/logic/minecraft/RawLibrary.cpp new file mode 100644 index 00000000..7e0ebff0 --- /dev/null +++ b/logic/minecraft/RawLibrary.cpp @@ -0,0 +1,205 @@ +#include "logic/MMCJson.h" +using namespace MMCJson; + +#include "RawLibrary.h" + +RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &filename) +{ + RawLibraryPtr out(new RawLibrary()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a library that doesn't have a 'name' field"); + } + out->m_name = libObj.value("name").toString(); + + auto readString = [libObj, filename](const QString & key, QString & variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("url", out->m_base_url); + readString("MMC-hint", out->m_hint); + readString("MMC-absulute_url", out->m_absolute_url); + readString("MMC-absoluteUrl", out->m_absolute_url); + if (libObj.contains("extract")) + { + out->applyExcludes = true; + auto extractObj = ensureObject(libObj.value("extract")); + for (auto excludeVal : ensureArray(extractObj.value("exclude"))) + { + out->extract_excludes.append(ensureString(excludeVal)); + } + } + if (libObj.contains("natives")) + { + QJsonObject nativesObj = ensureObject(libObj.value("natives")); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + if (!it.value().isString()) + { + QLOG_WARN() << filename << "contains an invalid native (skipping)"; + } + OpSys opSys = OpSys_fromString(it.key()); + if (opSys != Os_Other) + { + out->m_native_suffixes[opSys] = it.value().toString(); + } + } + } + if (libObj.contains("rules")) + { + out->applyRules = true; + out->m_rules = rulesFromJsonV4(libObj); + } + return out; +} + +RawLibraryPtr RawLibrary::fromJsonPlus(const QJsonObject &libObj, const QString &filename) +{ + auto lib = RawLibrary::fromJson(libObj, filename); + if (libObj.contains("insert")) + { + QJsonValue insertVal = ensureExists(libObj.value("insert"), "library insert rule"); + QString insertString; + { + if (insertVal.isString()) + { + insertString = insertVal.toString(); + } + else if (insertVal.isObject()) + { + QJsonObject insertObj = insertVal.toObject(); + if (insertObj.isEmpty()) + { + throw JSONValidationError("One library has an empty insert object in " + + filename); + } + insertString = insertObj.keys().first(); + lib->insertData = insertObj.value(insertString).toString(); + } + } + if (insertString == "apply") + { + lib->insertType = RawLibrary::Apply; + } + else if (insertString == "prepend") + { + lib->insertType = RawLibrary::Prepend; + } + else if (insertString == "append") + { + lib->insertType = RawLibrary::Append; + } + else if (insertString == "replace") + { + lib->insertType = RawLibrary::Replace; + } + else + { + throw JSONValidationError("A '+' library in " + filename + + " contains an invalid insert type"); + } + } + if (libObj.contains("MMC-depend")) + { + const QString dependString = ensureString(libObj.value("MMC-depend")); + if (dependString == "hard") + { + lib->dependType = RawLibrary::Hard; + } + else if (dependString == "soft") + { + lib->dependType = RawLibrary::Soft; + } + else + { + throw JSONValidationError("A '+' library in " + filename + + " contains an invalid depend type"); + } + } + return lib; +} + +bool RawLibrary::isNative() const +{ + return m_native_suffixes.size() != 0; +} + +QJsonObject RawLibrary::toJson() +{ + QJsonObject libRoot; + libRoot.insert("name", m_name); + if (m_absolute_url.size()) + libRoot.insert("MMC-absoluteUrl", m_absolute_url); + if (m_hint.size()) + libRoot.insert("MMC-hint", m_hint); + if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty()) + { + libRoot.insert("url", m_base_url); + } + if (isNative()) + { + QJsonObject nativeList; + auto iter = m_native_suffixes.begin(); + while (iter != m_native_suffixes.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + if (extract_excludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : extract_excludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + } + if (m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + return libRoot; +} + +QString RawLibrary::fullname() +{ + QStringList parts = m_name.split(':'); + return parts[0] + ":" + parts[1]; +} + +QString RawLibrary::group() +{ + QStringList parts = m_name.split(':'); + return parts[0]; +} + +QString RawLibrary::version() +{ + QStringList parts = m_name.split(':'); + return parts[2]; +} diff --git a/logic/minecraft/RawLibrary.h b/logic/minecraft/RawLibrary.h new file mode 100644 index 00000000..f5a28c61 --- /dev/null +++ b/logic/minecraft/RawLibrary.h @@ -0,0 +1,64 @@ +#pragma once +#include <QString> +#include <QPair> +#include <QList> +#include <QStringList> +#include <QMap> +#include <memory> + +#include "logic/minecraft/OneSixRule.h" +#include "logic/minecraft/OpSys.h" +#include "logic/net/URLConstants.h" + +class RawLibrary; +typedef std::shared_ptr<RawLibrary> RawLibraryPtr; + +class RawLibrary +{ +public: /* methods */ + /// read and create a basic library + static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename); + /// read and create a MultiMC '+' library. Those have some extra fields. + static RawLibraryPtr fromJsonPlus(const QJsonObject &libObj, const QString &filename); + QJsonObject toJson(); + + QString fullname(); + QString version(); + QString group(); + +public: /* data */ + QString m_name; + QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; + /// type hint - modifies how the library is treated + QString m_hint; + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; + + bool applyExcludes = false; + QStringList extract_excludes; + + /// Returns true if the library is native + bool isNative() const; + /// native suffixes per OS + QMap<OpSys, QString> m_native_suffixes; + + bool applyRules = false; + QList<std::shared_ptr<Rule>> m_rules; + + // used for '+' libraries + enum InsertType + { + Apply, + Append, + Prepend, + Replace + } insertType = Append; + QString insertData; + + // soft or hard dependency? hard means 'needs equal', soft means 'needs equal or newer' + enum DependType + { + Soft, + Hard + } dependType = Soft; +}; diff --git a/logic/minecraft/VersionBuildError.h b/logic/minecraft/VersionBuildError.h new file mode 100644 index 00000000..ae479851 --- /dev/null +++ b/logic/minecraft/VersionBuildError.h @@ -0,0 +1,58 @@ +#include "MMCError.h" + +class VersionBuildError : public MMCError +{ +public: + VersionBuildError(QString cause) : MMCError(cause) {}; + virtual ~VersionBuildError() noexcept + { + } +}; + +/** + * the base version file was meant for a newer version of the vanilla launcher than we support + */ +class LauncherVersionError : public VersionBuildError +{ +public: + LauncherVersionError(int actual, int supported) + : VersionBuildError(QObject::tr( + "The base version file of this instance was meant for a newer (%1) " + "version of the vanilla launcher than this version of MultiMC supports (%2).") + .arg(actual) + .arg(supported)) {}; + virtual ~LauncherVersionError() noexcept + { + } +}; + +/** + * some patch was intended for a different version of minecraft + */ +class MinecraftVersionMismatch : public VersionBuildError +{ +public: + MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion) + : VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft " + "(%2) than that of the instance (%3).") + .arg(fileId) + .arg(mcVersion) + .arg(parentMcVersion)) {}; + virtual ~MinecraftVersionMismatch() noexcept + { + } +}; + +/** + * files required for the version are not (yet?) present + */ +class VersionIncomplete : public VersionBuildError +{ +public: + VersionIncomplete(QString missingPatch) + : VersionBuildError(QObject::tr("Version is incomplete: missing %1.") + .arg(missingPatch)) {}; + virtual ~VersionIncomplete() noexcept + { + } +};
\ No newline at end of file diff --git a/logic/minecraft/VersionBuilder.cpp b/logic/minecraft/VersionBuilder.cpp new file mode 100644 index 00000000..ddcf6333 --- /dev/null +++ b/logic/minecraft/VersionBuilder.cpp @@ -0,0 +1,349 @@ +/* Copyright 2013 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 <QList> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonDocument> +#include <QFile> +#include <QFileInfo> +#include <QMessageBox> +#include <QObject> +#include <QDir> +#include <QDebug> +#include <qresource.h> +#include <modutils.h> + +#include "MultiMC.h" +#include "logic/minecraft/VersionBuilder.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixRule.h" +#include "logic/minecraft/VersionPatch.h" +#include "logic/minecraft/VersionFile.h" +#include "VersionBuildError.h" +#include "MinecraftVersionList.h" + +#include "logic/OneSixInstance.h" +#include "logic/MMCJson.h" + +#include "logger/QsLog.h" + +VersionBuilder::VersionBuilder() +{ +} + +void VersionBuilder::build(InstanceVersion *version, OneSixInstance *instance, + const QStringList &external) +{ + VersionBuilder builder; + builder.m_version = version; + builder.m_instance = instance; + builder.external_patches = external; + builder.buildInternal(); +} + +void VersionBuilder::readJsonAndApplyToVersion(InstanceVersion *version, const QJsonObject &obj) +{ + VersionBuilder builder; + builder.m_version = version; + builder.m_instance = 0; + builder.readJsonAndApply(obj); +} + +void VersionBuilder::buildFromCustomJson() +{ + QLOG_INFO() << "Building version from custom.json within the instance."; + QLOG_INFO() << "Reading custom.json"; + auto file = parseJsonFile(QFileInfo(instance_root.absoluteFilePath("custom.json")), false); + file->name = "custom.json"; + file->filename = "custom.json"; + file->fileId = "org.multimc.custom.json"; + file->order = -1; + file->version = QString(); + m_version->VersionPatches.append(file); + m_version->finalize(); + return; +} + +void VersionBuilder::buildFromVersionJson() +{ + QLOG_INFO() << "Building version from version.json and patches within the instance."; + QLOG_INFO() << "Reading version.json"; + auto file = parseJsonFile(QFileInfo(instance_root.absoluteFilePath("version.json")), false); + file->name = "Minecraft"; + file->fileId = "org.multimc.version.json"; + file->order = -1; + file->version = m_instance->intendedVersionId(); + file->mcVersion = m_instance->intendedVersionId(); + m_version->VersionPatches.append(file); + + // load all patches, put into map for ordering, apply in the right order + readInstancePatches(); + + // some final touches + m_version->finalize(); +} + +void VersionBuilder::readInstancePatches() +{ + PatchOrder userOrder; + readOverrideOrders(m_instance, userOrder); + QDir patches(instance_root.absoluteFilePath("patches/")); + + // first, load things by sort order. + for (auto id : userOrder) + { + // ignore builtins + if (id == "net.minecraft") + continue; + if (id == "org.lwjgl") + continue; + // parse the file + QString filename = patches.absoluteFilePath(id + ".json"); + QLOG_INFO() << "Reading" << filename << "by user order"; + auto file = parseJsonFile(QFileInfo(filename), false); + // sanity check. prevent tampering with files. + if (file->fileId != id) + { + throw VersionBuildError( + QObject::tr("load id %1 does not match internal id %2").arg(id, file->fileId)); + } + m_version->VersionPatches.append(file); + } + // now load the rest by internal preference. + QMap<int, QPair<QString, VersionFilePtr>> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + // parse the file + QLOG_INFO() << "Reading" << info.fileName(); + auto file = parseJsonFile(info, true); + // ignore builtins + if (file->fileId == "net.minecraft") + continue; + if (file->fileId == "org.lwjgl") + continue; + // do not load what we already loaded in the first pass + if (userOrder.contains(file->fileId)) + continue; + if (files.contains(file->order)) + { + // FIXME: do not throw? + throw VersionBuildError(QObject::tr("%1 has the same order as %2") + .arg(file->fileId, files[file->order].second->fileId)); + } + files.insert(file->order, qMakePair(info.fileName(), file)); + } + for (auto order : files.keys()) + { + auto &filePair = files[order]; + m_version->VersionPatches.append(filePair.second); + } +} + +void VersionBuilder::buildFromExternalPatches() +{ + QLOG_INFO() << "Building version from external files."; + int externalOrder = -1; + for (auto fileName : external_patches) + { + QLOG_INFO() << "Reading" << fileName; + auto file = parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); + file->name = QFileInfo(fileName).fileName(); + file->fileId = "org.multimc.external." + file->name; + file->order = (externalOrder += 1); + file->version = QString(); + file->mcVersion = QString(); + m_version->VersionPatches.append(file); + } + // some final touches + m_version->finalize(); +} + +void VersionBuilder::buildFromMultilayer() +{ + QLOG_INFO() << "Building version from multilayered sources."; + // just the builtin stuff for now + auto minecraftList = MMC->minecraftlist(); + auto mcversion = minecraftList->findVersion(m_instance->intendedVersionId()); + auto minecraftPatch = std::dynamic_pointer_cast<VersionPatch>(mcversion); + if (!minecraftPatch) + { + throw VersionIncomplete("net.minecraft"); + } + minecraftPatch->setOrder(-2); + m_version->VersionPatches.append(minecraftPatch); + + // TODO: this is obviously fake. + QResource LWJGL(":/versions/LWJGL/2.9.1.json"); + auto lwjgl = parseJsonFile(LWJGL.absoluteFilePath(), false, false); + auto lwjglPatch = std::dynamic_pointer_cast<VersionPatch>(lwjgl); + if (!lwjglPatch) + { + throw VersionIncomplete("org.lwjgl"); + } + lwjglPatch->setOrder(-1); + m_version->VersionPatches.append(lwjglPatch); + + // load all patches, put into map for ordering, apply in the right order + readInstancePatches(); + + m_version->finalize(); +} + +void VersionBuilder::buildInternal() +{ + m_version->VersionPatches.clear(); + instance_root = QDir(m_instance->instanceRoot()); + // if we do external files, do just those. + if (!external_patches.isEmpty()) + { + buildFromExternalPatches(); + } + // else, if there's custom json, we just do that. + else if (QFile::exists(instance_root.absoluteFilePath("custom.json"))) + { + buildFromCustomJson(); + } + // version.json -> patches/*.json + else if (QFile::exists(instance_root.absoluteFilePath("version.json"))) + { + buildFromVersionJson(); + } + else + { + buildFromMultilayer(); + } +} + +void VersionBuilder::readJsonAndApply(const QJsonObject &obj) +{ + m_version->clear(); + + auto file = VersionFile::fromJson(QJsonDocument(obj), QString(), false); + + file->applyTo(m_version); + m_version->VersionPatches.append(file); +} + +VersionFilePtr VersionBuilder::parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, + bool isFTB) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") + .arg(fileInfo.fileName(), file.errorString())); + } + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + throw JSONValidationError( + QObject::tr("Unable to process the version file %1: %2 at %3.") + .arg(fileInfo.fileName(), error.errorString()) + .arg(error.offset)); + } + return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); +} + +VersionFilePtr VersionBuilder::parseBinaryJsonFile(const QFileInfo &fileInfo) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") + .arg(fileInfo.fileName(), file.errorString())); + } + QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); + file.close(); + if (doc.isNull()) + { + file.remove(); + throw JSONValidationError( + QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); + } + return VersionFile::fromJson(doc, file.fileName(), false, false); +} + +static const int currentOrderFileVersion = 1; + +bool VersionBuilder::readOverrideOrders(OneSixInstance *instance, PatchOrder &order) +{ + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::ReadOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << " for reading:" << orderFile.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return false; + } + + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return false; + } + + // and then read it and process it if all above is true. + try + { + auto obj = MMCJson::ensureObject(doc); + // check order file version. + auto version = MMCJson::ensureInteger(obj.value("version"), "version"); + if (version != currentOrderFileVersion) + { + throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") + .arg(currentOrderFileVersion)); + } + auto orderArray = MMCJson::ensureArray(obj.value("order")); + for(auto item: orderArray) + { + order.append(MMCJson::ensureString(item)); + } + } + catch (JSONValidationError &err) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; + QLOG_WARN() << "Ignoring overriden order"; + order.clear(); + return false; + } + return true; +} + +bool VersionBuilder::writeOverrideOrders(OneSixInstance *instance, const PatchOrder &order) +{ + QJsonObject obj; + obj.insert("version", currentOrderFileVersion); + QJsonArray orderArray; + for(auto str: order) + { + orderArray.append(str); + } + obj.insert("order", orderArray); + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << "for writing:" << orderFile.errorString(); + return false; + } + orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); + return true; +} diff --git a/logic/minecraft/VersionBuilder.h b/logic/minecraft/VersionBuilder.h new file mode 100644 index 00000000..350179b9 --- /dev/null +++ b/logic/minecraft/VersionBuilder.h @@ -0,0 +1,56 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QString> +#include <QMap> +#include "VersionFile.h" + +class InstanceVersion; +class OneSixInstance; +class QJsonObject; +class QFileInfo; + +typedef QStringList PatchOrder; + +class VersionBuilder +{ + VersionBuilder(); +public: + static void build(InstanceVersion *version, OneSixInstance *instance, const QStringList &external); + static void readJsonAndApplyToVersion(InstanceVersion *version, const QJsonObject &obj); + static VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, bool isFTB = false); + static VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo); + + bool readOverrideOrders(OneSixInstance *instance, PatchOrder &order); + static bool writeOverrideOrders(OneSixInstance *instance, const PatchOrder &order); + +private: + InstanceVersion *m_version; + OneSixInstance *m_instance; + QStringList external_patches; + QDir instance_root; + + void buildInternal(); + void buildFromExternalPatches(); + void buildFromCustomJson(); + void buildFromVersionJson(); + void buildFromMultilayer(); + + void readInstancePatches(); + + void readJsonAndApply(const QJsonObject &obj); +}; diff --git a/logic/VersionFile.cpp b/logic/minecraft/VersionFile.cpp index cd2a4f9c..93f57116 100644 --- a/logic/VersionFile.cpp +++ b/logic/minecraft/VersionFile.cpp @@ -1,84 +1,41 @@ #include <QJsonArray> #include <QJsonDocument> - #include <modutils.h> #include "logger/QsLog.h" -#include "logic/VersionFile.h" -#include "logic/OneSixLibrary.h" -#include "logic/VersionFinal.h" -#include "MMCJson.h" +#include "logic/minecraft/VersionFile.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/JarMod.h" +#include "ParseUtils.h" + +#include "logic/MMCJson.h" using namespace MMCJson; +#include "VersionBuildError.h" + #define CURRENT_MINIMUM_LAUNCHER_VERSION 14 -RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &filename) +int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) { - RawLibraryPtr out(new RawLibrary()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + - "contains a library that doesn't have a 'name' field"); - } - out->name = libObj.value("name").toString(); - - auto readString = [libObj, filename](const QString & key, QString & variable) - { - if (libObj.contains(key)) - { - QJsonValue val = libObj.value(key); - if (!val.isString()) - { - QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; - } - else - { - variable = val.toString(); - } - } - }; - - readString("url", out->url); - readString("MMC-hint", out->hint); - readString("MMC-absulute_url", out->absoluteUrl); - readString("MMC-absoluteUrl", out->absoluteUrl); - if (libObj.contains("extract")) - { - out->applyExcludes = true; - auto extractObj = ensureObject(libObj.value("extract")); - for (auto excludeVal : ensureArray(extractObj.value("exclude"))) - { - out->excludes.append(ensureString(excludeVal)); - } - } - if (libObj.contains("natives")) + int retval = -1; + for (int i = 0; i < haystack.size(); ++i) { - out->applyNatives = true; - QJsonObject nativesObj = ensureObject(libObj.value("natives")); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + QString chunk = haystack.at(i)->rawName(); + if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) { - if (!it.value().isString()) - { - QLOG_WARN() << filename << "contains an invalid native (skipping)"; - } - OpSys opSys = OpSys_fromString(it.key()); - if (opSys != Os_Other) - { - out->natives.append(qMakePair(opSys, it.value().toString())); - } + // only one is allowed. + if (retval != -1) + return -1; + retval = i; } } - if (libObj.contains("rules")) - { - out->applyRules = true; - out->rules = rulesFromJsonV4(libObj); - } - return out; + return retval; } VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename, - const bool requireOrder, const bool isFTB) + const bool requireOrder, const bool isFTB) { VersionFilePtr out(new VersionFile()); if (doc.isEmpty() || doc.isNull()) @@ -87,7 +44,6 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } if (!doc.isObject()) { - throw JSONValidationError("The root of " + filename + " is not an object"); } QJsonObject root = doc.object(); @@ -111,7 +67,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi out->mcVersion = root.value("mcVersion").toString(); out->filename = filename; - auto readString = [root, filename](const QString & key, QString & variable) + auto readString = [root](const QString & key, QString & variable) { if (root.contains(key)) { @@ -119,6 +75,16 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } }; + auto readStringRet = [root](const QString & key)->QString + { + if (root.contains(key)) + { + return ensureString(root.value(key)); + } + return QString(); + } + ; + // FIXME: This should be ignored when applying. if (!isFTB) { @@ -126,13 +92,16 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } readString("mainClass", out->mainClass); + readString("appletClass", out->appletClass); readString("processArguments", out->processArguments); readString("minecraftArguments", out->overwriteMinecraftArguments); readString("+minecraftArguments", out->addMinecraftArguments); readString("-minecraftArguments", out->removeMinecraftArguments); readString("type", out->type); - readString("releaseTime", out->releaseTime); - readString("time", out->time); + + parse_timestamp(readStringRet("releaseTime"), out->m_releaseTimeString, out->m_releaseTime); + parse_timestamp(readStringRet("time"), out->m_updateTimeString, out->m_updateTime); + readString("assets", out->assets); if (root.contains("minimumLauncherVersion")) @@ -165,6 +134,14 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } } + if (root.contains("+traits")) + { + for (auto tweakerVal : ensureArray(root.value("+traits"))) + { + out->traits.insert(ensureString(tweakerVal)); + } + } + if (root.contains("libraries")) { // FIXME: This should be done when applying. @@ -177,7 +154,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi // FIXME: This should be done when applying. if (isFTB) { - lib->hint = "local"; + lib->m_hint = "local"; lib->insertType = RawLibrary::Prepend; out->addLibs.prepend(lib); } @@ -188,76 +165,29 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } } + if (root.contains("+jarMods")) + { + for (auto libVal : ensureArray(root.value("+jarMods"))) + { + QJsonObject libObj = ensureObject(libVal); + // parse the jarmod + auto lib = Jarmod::fromJson(libObj, filename); + // and add to jar mods + out->jarMods.append(lib); + } + } + if (root.contains("+libraries")) { for (auto libVal : ensureArray(root.value("+libraries"))) { QJsonObject libObj = ensureObject(libVal); - QJsonValue insertVal = ensureExists(libObj.value("insert")); - // parse the library - auto lib = RawLibrary::fromJson(libObj, filename); - - // TODO: utility functions for handling this case. templates? - QString insertString; - { - if (insertVal.isString()) - { - insertString = insertVal.toString(); - } - else if (insertVal.isObject()) - { - QJsonObject insertObj = insertVal.toObject(); - if (insertObj.isEmpty()) - { - throw JSONValidationError("One library has an empty insert object in " + - filename); - } - insertString = insertObj.keys().first(); - lib->insertData = insertObj.value(insertString).toString(); - } - } - if (insertString == "apply") - { - lib->insertType = RawLibrary::Apply; - } - else if (insertString == "prepend") - { - lib->insertType = RawLibrary::Prepend; - } - else if (insertString == "append") - { - lib->insertType = RawLibrary::Prepend; - } - else if (insertString == "replace") - { - lib->insertType = RawLibrary::Replace; - } - else - { - throw JSONValidationError("A '+' library in " + filename + - " contains an invalid insert type"); - } - if (libObj.contains("MMC-depend")) - { - const QString dependString = ensureString(libObj.value("MMC-depend")); - if (dependString == "hard") - { - lib->dependType = RawLibrary::Hard; - } - else if (dependString == "soft") - { - lib->dependType = RawLibrary::Soft; - } - else - { - throw JSONValidationError("A '+' library in " + filename + - " contains an invalid depend type"); - } - } + auto lib = RawLibrary::fromJsonPlus(libObj, filename); out->addLibs.append(lib); } } + if (root.contains("-libraries")) { for (auto libVal : ensureArray(root.value("-libraries"))) @@ -269,53 +199,81 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi return out; } -OneSixLibraryPtr VersionFile::createLibrary(RawLibraryPtr lib) +QJsonDocument VersionFile::toJson(bool saveOrder) { - std::shared_ptr<OneSixLibrary> out(new OneSixLibrary(lib->name)); - if (!lib->url.isEmpty()) + QJsonObject root; + if (saveOrder) + { + root.insert("order", order); + } + writeString(root, "name", name); + writeString(root, "fileId", fileId); + writeString(root, "version", version); + writeString(root, "mcVersion", mcVersion); + writeString(root, "id", id); + writeString(root, "mainClass", mainClass); + writeString(root, "appletClass", appletClass); + writeString(root, "processArguments", processArguments); + writeString(root, "minecraftArguments", overwriteMinecraftArguments); + writeString(root, "+minecraftArguments", addMinecraftArguments); + writeString(root, "-minecraftArguments", removeMinecraftArguments); + writeString(root, "type", type); + writeString(root, "assets", assets); + if (isMinecraftVersion()) + { + writeString(root, "releaseTime", m_releaseTimeString); + writeString(root, "time", m_updateTimeString); + } + if (minimumLauncherVersion != -1) { - out->setBaseUrl(lib->url); + root.insert("minimumLauncherVersion", minimumLauncherVersion); } - out->setHint(lib->hint); - if (!lib->absoluteUrl.isEmpty()) + writeStringList(root, "tweakers", overwriteTweakers); + writeStringList(root, "+tweakers", addTweakers); + writeStringList(root, "-tweakers", removeTweakers); + writeStringList(root, "+traits", traits.toList()); + writeObjectList(root, "libraries", overwriteLibs); + writeObjectList(root, "+libraries", addLibs); + writeObjectList(root, "+jarMods", jarMods); + // FIXME: removed libs are special snowflakes. + if (removeLibs.size()) { - out->setAbsoluteUrl(lib->absoluteUrl); + QJsonArray array; + for (auto lib : removeLibs) + { + QJsonObject rmlibobj; + rmlibobj.insert("name", lib); + array.append(rmlibobj); + } + root.insert("-libraries", array); } - out->setAbsoluteUrl(lib->absoluteUrl); - out->extract_excludes = lib->excludes; - for (auto native : lib->natives) + // write the contents to a json document. { - out->addNative(native.first, native.second); + QJsonDocument out; + out.setObject(root); + return out; } - out->setRules(lib->rules); - out->finalize(); - return out; } -int VersionFile::findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) +bool VersionFile::isMinecraftVersion() { - int retval = -1; - for (int i = 0; i < haystack.size(); ++i) - { - QString chunk = haystack.at(i)->rawName(); - if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) - { - // only one is allowed. - if(retval != -1) - return -1; - retval = i; - } - } - return retval; + return (fileId == "org.multimc.version.json") || (fileId == "net.minecraft") || + (fileId == "org.multimc.custom.json"); +} + +bool VersionFile::hasJarMods() +{ + return !jarMods.isEmpty(); } -void VersionFile::applyTo(VersionFinal *version) +void VersionFile::applyTo(InstanceVersion *version) { if (minimumLauncherVersion != -1) { if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) { - throw LauncherVersionError(minimumLauncherVersion, CURRENT_MINIMUM_LAUNCHER_VERSION); + throw LauncherVersionError(minimumLauncherVersion, + CURRENT_MINIMUM_LAUNCHER_VERSION); } } @@ -336,21 +294,34 @@ void VersionFile::applyTo(VersionFinal *version) { version->mainClass = mainClass; } - if (!processArguments.isNull()) + if (!appletClass.isNull()) { - version->processArguments = processArguments; + version->appletClass = appletClass; } - if (!type.isNull()) - { - version->type = type; - } - if (!releaseTime.isNull()) + if (!processArguments.isNull()) { - version->releaseTime = releaseTime; + if (isMinecraftVersion()) + { + version->vanillaProcessArguments = processArguments; + } + version->processArguments = processArguments; } - if (!time.isNull()) + if (isMinecraftVersion()) { - version->time = time; + if (!type.isNull()) + { + version->type = type; + } + if (!m_releaseTimeString.isNull()) + { + version->m_releaseTimeString = m_releaseTimeString; + version->m_releaseTime = m_releaseTime; + } + if (!m_updateTimeString.isNull()) + { + version->m_updateTimeString = m_updateTimeString; + version->m_updateTime = m_updateTime; + } } if (!assets.isNull()) { @@ -358,10 +329,15 @@ void VersionFile::applyTo(VersionFinal *version) } if (minimumLauncherVersion >= 0) { - version->minimumLauncherVersion = minimumLauncherVersion; + if (version->minimumLauncherVersion < minimumLauncherVersion) + version->minimumLauncherVersion = minimumLauncherVersion; } if (!overwriteMinecraftArguments.isNull()) { + if (isMinecraftVersion()) + { + version->vanillaMinecraftArguments = overwriteMinecraftArguments; + } version->minecraftArguments = overwriteMinecraftArguments; } if (!addMinecraftArguments.isNull()) @@ -384,13 +360,20 @@ void VersionFile::applyTo(VersionFinal *version) { version->tweakers.removeAll(tweaker); } + version->jarMods.append(jarMods); + version->traits.unite(traits); if (shouldOverwriteLibs) { - version->libraries.clear(); + QList<OneSixLibraryPtr> libs; for (auto lib : overwriteLibs) { - version->libraries.append(createLibrary(lib)); + libs.append(OneSixLibrary::fromRawLibrary(lib)); + } + if (isMinecraftVersion()) + { + version->vanillaLibraries = libs; } + version->libraries = libs; } for (auto lib : addLibs) { @@ -399,43 +382,46 @@ void VersionFile::applyTo(VersionFinal *version) case RawLibrary::Apply: { // QLOG_INFO() << "Applying lib " << lib->name; - int index = findLibrary(version->libraries, lib->name); + int index = findLibrary(version->libraries, lib->m_name); if (index >= 0) { auto library = version->libraries[index]; - if (!lib->url.isNull()) + if (!lib->m_base_url.isNull()) { - library->setBaseUrl(lib->url); + library->setBaseUrl(lib->m_base_url); } - if (!lib->hint.isNull()) + if (!lib->m_hint.isNull()) { - library->setHint(lib->hint); + library->setHint(lib->m_hint); } - if (!lib->absoluteUrl.isNull()) + if (!lib->m_absolute_url.isNull()) { - library->setAbsoluteUrl(lib->absoluteUrl); + library->setAbsoluteUrl(lib->m_absolute_url); } if (lib->applyExcludes) { - library->extract_excludes = lib->excludes; + library->extract_excludes = lib->extract_excludes; } - if (lib->applyNatives) + if (lib->isNative()) { - library->clearSuffixes(); + // library->clearSuffixes(); + library->m_native_suffixes = lib->m_native_suffixes; + /* for (auto native : lib->natives) { library->addNative(native.first, native.second); } + */ } if (lib->applyRules) { - library->setRules(lib->rules); + library->setRules(lib->m_rules); } library->finalize(); } else { - QLOG_WARN() << "Couldn't find" << lib->name << "(skipping)"; + QLOG_WARN() << "Couldn't find" << lib->m_name << "(skipping)"; } break; } @@ -443,24 +429,24 @@ void VersionFile::applyTo(VersionFinal *version) case RawLibrary::Prepend: { // QLOG_INFO() << "Adding lib " << lib->name; - const int startOfVersion = lib->name.lastIndexOf(':') + 1; + const int startOfVersion = lib->m_name.lastIndexOf(':') + 1; const int index = findLibrary( - version->libraries, QString(lib->name).replace(startOfVersion, INT_MAX, '*')); + version->libraries, QString(lib->m_name).replace(startOfVersion, INT_MAX, '*')); if (index < 0) { if (lib->insertType == RawLibrary::Append) { - version->libraries.append(createLibrary(lib)); + version->libraries.append(OneSixLibrary::fromRawLibrary(lib)); } else { - version->libraries.prepend(createLibrary(lib)); + version->libraries.prepend(OneSixLibrary::fromRawLibrary(lib)); } } else { auto otherLib = version->libraries.at(index); - const Util::Version ourVersion = lib->name.mid(startOfVersion, INT_MAX); + const Util::Version ourVersion = lib->m_name.mid(startOfVersion, INT_MAX); const Util::Version otherVersion = otherLib->version(); // if the existing version is a hard dependency we can either use it or // fail, but we can't change it @@ -474,7 +460,7 @@ void VersionFile::applyTo(VersionFinal *version) throw VersionBuildError( QObject::tr( "Error resolving library dependencies between %1 and %2 in %3.") - .arg(otherLib->rawName(), lib->name, filename)); + .arg(otherLib->rawName(), lib->m_name, filename)); } else { @@ -486,7 +472,7 @@ void VersionFile::applyTo(VersionFinal *version) // if we are higher it means we should update if (ourVersion > otherVersion) { - auto library = createLibrary(lib); + auto library = OneSixLibrary::fromRawLibrary(lib); if (Util::Version(otherLib->minVersion) < ourVersion) { library->minVersion = ourVersion.toString(); @@ -501,7 +487,7 @@ void VersionFile::applyTo(VersionFinal *version) { throw VersionBuildError(QObject::tr( "Error resolving library dependencies between %1 and %2 in %3.") - .arg(otherLib->rawName(), lib->name, + .arg(otherLib->rawName(), lib->m_name, filename)); } } @@ -512,10 +498,10 @@ void VersionFile::applyTo(VersionFinal *version) case RawLibrary::Replace: { QString toReplace; - if(lib->insertData.isEmpty()) + if (lib->insertData.isEmpty()) { - const int startOfVersion = lib->name.lastIndexOf(':') + 1; - toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*'); + const int startOfVersion = lib->m_name.lastIndexOf(':') + 1; + toReplace = QString(lib->m_name).replace(startOfVersion, INT_MAX, '*'); } else toReplace = lib->insertData; @@ -523,7 +509,7 @@ void VersionFile::applyTo(VersionFinal *version) int index = findLibrary(version->libraries, toReplace); if (index >= 0) { - version->libraries.replace(index, createLibrary(lib)); + version->libraries.replace(index, OneSixLibrary::fromRawLibrary(lib)); } else { diff --git a/logic/minecraft/VersionFile.h b/logic/minecraft/VersionFile.h new file mode 100644 index 00000000..186f4335 --- /dev/null +++ b/logic/minecraft/VersionFile.h @@ -0,0 +1,103 @@ +#pragma once + +#include <QString> +#include <QStringList> +#include <QDateTime> +#include <memory> +#include "logic/minecraft/OpSys.h" +#include "logic/minecraft/OneSixRule.h" +#include "VersionPatch.h" +#include "MMCError.h" +#include "OneSixLibrary.h" +#include "JarMod.h" + +class InstanceVersion; +struct VersionFile; + +typedef std::shared_ptr<VersionFile> VersionFilePtr; +class VersionFile : public VersionPatch +{ +public: /* methods */ + static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename, + const bool requireOrder, const bool isFTB = false); + QJsonDocument toJson(bool saveOrder); + + virtual void applyTo(InstanceVersion *version) override; + virtual bool isMinecraftVersion() override; + virtual bool hasJarMods() override; + virtual int getOrder() override + { + return order; + } + virtual void setOrder(int order) override + { + this->order = order; + } + virtual QList<JarmodPtr> getJarMods() override + { + return jarMods; + } + virtual QString getPatchID() override + { + return fileId; + } + virtual QString getPatchName() override + { + return name; + } + virtual QString getPatchVersion() override + { + return version; + } + virtual QString getPatchFilename() override + { + return filename; + } + +public: /* data */ + int order = 0; + QString name; + QString fileId; + QString version; + // TODO use the mcVersion to determine if a version file should be removed on update + QString mcVersion; + QString filename; + // TODO requirements + // QMap<QString, QString> requirements; + QString id; + QString mainClass; + QString appletClass; + QString overwriteMinecraftArguments; + QString addMinecraftArguments; + QString removeMinecraftArguments; + QString processArguments; + QString type; + + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + + /// asset group used by this ... thing. + QString assets; + int minimumLauncherVersion = -1; + + bool shouldOverwriteTweakers = false; + QStringList overwriteTweakers; + QStringList addTweakers; + QStringList removeTweakers; + + bool shouldOverwriteLibs = false; + QList<RawLibraryPtr> overwriteLibs; + QList<RawLibraryPtr> addLibs; + QList<QString> removeLibs; + + QSet<QString> traits; + + QList<JarmodPtr> jarMods; +}; + + diff --git a/logic/minecraft/VersionPatch.h b/logic/minecraft/VersionPatch.h new file mode 100644 index 00000000..1dd30e79 --- /dev/null +++ b/logic/minecraft/VersionPatch.h @@ -0,0 +1,31 @@ +#pragma once + +#include <memory> +#include <QList> +#include "JarMod.h" + +class InstanceVersion; +class VersionPatch +{ +public: + virtual ~VersionPatch(){}; + virtual void applyTo(InstanceVersion *version) = 0; + + virtual bool isMinecraftVersion() = 0; + virtual bool hasJarMods() = 0; + virtual QList<JarmodPtr> getJarMods() = 0; + + virtual bool isMoveable() + { + return getOrder() >= 0; + } + virtual void setOrder(int order) = 0; + virtual int getOrder() = 0; + + virtual QString getPatchID() = 0; + virtual QString getPatchName() = 0; + virtual QString getPatchVersion() = 0; + virtual QString getPatchFilename() = 0; +}; + +typedef std::shared_ptr<VersionPatch> VersionPatchPtr; diff --git a/logic/minecraft/VersionSource.h b/logic/minecraft/VersionSource.h new file mode 100644 index 00000000..75b2c24b --- /dev/null +++ b/logic/minecraft/VersionSource.h @@ -0,0 +1,9 @@ +#pragma once + +/// where is a version from? +enum VersionSource +{ + Builtin, //!< version loaded from the internal resources. + Local, //!< version loaded from a file in the cache. + Remote, //!< incomplete version on a remote server. +};
\ No newline at end of file diff --git a/logic/net/NetJob.h b/logic/net/NetJob.h index 2df8428b..d05e7b6f 100644 --- a/logic/net/NetJob.h +++ b/logic/net/NetJob.h @@ -21,7 +21,6 @@ #include "MD5EtagDownload.h" #include "CacheDownload.h" #include "HttpMetaCache.h" -#include "ForgeXzDownload.h" #include "logic/tasks/ProgressProvider.h" class NetJob; diff --git a/logic/screenshots/ImgurAlbumCreation.cpp b/logic/screenshots/ImgurAlbumCreation.cpp index e473952e..91cf4bb1 100644 --- a/logic/screenshots/ImgurAlbumCreation.cpp +++ b/logic/screenshots/ImgurAlbumCreation.cpp @@ -4,8 +4,8 @@ #include <QJsonDocument> #include <QJsonObject> #include <QUrl> +#include <QStringList> -#include "logic/screenshots//ScreenshotList.h" #include "logic/net/URLConstants.h" #include "MultiMC.h" #include "logger/QsLog.h" @@ -28,7 +28,7 @@ void ImgurAlbumCreation::start() QStringList ids; for (auto shot : m_screenshots) { - ids.append(shot->imgurId); + ids.append(shot->m_imgurId); } const QByteArray data = "ids=" + ids.join(',').toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden"; diff --git a/logic/screenshots/ImgurUpload.cpp b/logic/screenshots/ImgurUpload.cpp index 62033ef5..f305aec0 100644 --- a/logic/screenshots/ImgurUpload.cpp +++ b/logic/screenshots/ImgurUpload.cpp @@ -8,7 +8,6 @@ #include <QFile> #include <QUrl> -#include "logic/screenshots/ScreenshotList.h" #include "logic/net/URLConstants.h" #include "MultiMC.h" #include "logger/QsLog.h" @@ -27,7 +26,7 @@ void ImgurUpload::start() request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); request.setRawHeader("Accept", "application/json"); - QFile f(m_shot->file); + QFile f(m_shot->m_file.absoluteFilePath()); if (!f.open(QFile::ReadOnly)) { emit failed(m_index_within_job); @@ -46,7 +45,7 @@ void ImgurUpload::start() multipart->append(typePart); QHttpPart namePart; namePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"name\""); - namePart.setBody(m_shot->timestamp.toString(Qt::ISODate).toUtf8()); + namePart.setBody(m_shot->m_file.baseName().toUtf8()); multipart->append(namePart); auto worker = MMC->qnam(); @@ -84,8 +83,8 @@ void ImgurUpload::downloadFinished() emit failed(m_index_within_job); return; } - m_shot->imgurId = object.value("data").toObject().value("id").toString(); - m_shot->url = object.value("data").toObject().value("link").toString(); + m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); + m_shot->m_url = object.value("data").toObject().value("link").toString(); m_status = Job_Finished; emit succeeded(m_index_within_job); return; diff --git a/logic/screenshots/Screenshot.cpp b/logic/screenshots/Screenshot.cpp deleted file mode 100644 index 882e491f..00000000 --- a/logic/screenshots/Screenshot.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "Screenshot.h" -#include <QImage> -#include <QIcon> -QIcon ScreenShot::getImage() -{ - if(!imageloaded) - { - QImage image(file); - QImage thumbnail = image.scaledToWidth(256, Qt::SmoothTransformation); - m_image = QIcon(QPixmap::fromImage(thumbnail)); - imageloaded = true; - } - return m_image; -} diff --git a/logic/screenshots/Screenshot.h b/logic/screenshots/Screenshot.h index 815c0d47..b48cbe99 100644 --- a/logic/screenshots/Screenshot.h +++ b/logic/screenshots/Screenshot.h @@ -2,18 +2,18 @@ #include <QDateTime> #include <QString> +#include <QFileInfo> #include <memory> -#include <QIcon> struct ScreenShot { - QIcon getImage(); - QIcon m_image; - bool imageloaded = false; - QDateTime timestamp; - QString file; - QString url; - QString imgurId; + ScreenShot(QFileInfo file) + { + m_file = file; + } + QFileInfo m_file; + QString m_url; + QString m_imgurId; }; typedef std::shared_ptr<ScreenShot> ScreenshotPtr; diff --git a/logic/screenshots/ScreenshotList.cpp b/logic/screenshots/ScreenshotList.cpp deleted file mode 100644 index a34f4d46..00000000 --- a/logic/screenshots/ScreenshotList.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "ScreenshotList.h" -#include "gui/dialogs/ScreenshotDialog.h" - -#include <QDir> -#include <QIcon> -#include <QList> -#include "gui/dialogs/ProgressDialog.h" -#include "gui/dialogs/CustomMessageBox.h" - -ScreenshotList::ScreenshotList(InstancePtr instance, QObject *parent) - : QAbstractListModel(parent), m_instance(instance) -{ -} - -int ScreenshotList::rowCount(const QModelIndex &) const -{ - return m_screenshots.size(); -} - -QVariant ScreenshotList::data(const QModelIndex &index, int role) const -{ - if (index.row() >= m_screenshots.size() || index.row() < 0) - return QVariant(); - - switch (role) - { - case Qt::DecorationRole: - return m_screenshots.at(index.row())->getImage(); - case Qt::DisplayRole: - return m_screenshots.at(index.row())->timestamp.toString("yyyy-MM-dd HH:mm:ss"); - case Qt::ToolTipRole: - return m_screenshots.at(index.row())->timestamp.toString("yyyy-MM-dd HH:mm:ss"); - case Qt::TextAlignmentRole: - return (int)(Qt::AlignHCenter | Qt::AlignVCenter); - default: - return QVariant(); - } -} - -QVariant ScreenshotList::headerData(int section, Qt::Orientation orientation, int role) const -{ - return QVariant(); -} - -Qt::ItemFlags ScreenshotList::flags(const QModelIndex &index) const -{ - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; -} - -Task *ScreenshotList::load() -{ - return new ScreenshotLoadTask(this); -} - -ScreenshotLoadTask::ScreenshotLoadTask(ScreenshotList *list) : m_list(list) -{ -} - -ScreenshotLoadTask::~ScreenshotLoadTask() -{ -} - -void ScreenshotLoadTask::executeTask() -{ - auto dir = QDir(m_list->instance()->minecraftRoot()); - if (!dir.cd("screenshots")) - { - emitFailed("Selected instance does not have any screenshots!"); - return; - } - dir.setNameFilters(QStringList() << "*.png"); - this->m_results.clear(); - for (auto file : dir.entryList()) - { - ScreenShot *shot = new ScreenShot(); - shot->timestamp = QDateTime::fromString(file, "yyyy-MM-dd_HH.mm.ss.png"); - shot->file = dir.absoluteFilePath(file); - m_results.append(ScreenshotPtr(shot)); - } - m_list->loadShots(m_results); - emitSucceeded(); -} - -void ScreenshotList::deleteSelected(ScreenshotDialog *dialog) -{ - auto screens = dialog->selected(); - if (screens.isEmpty()) - { - return; - } - beginResetModel(); - QList<std::shared_ptr<ScreenShot>>::const_iterator it; - for (it = screens.cbegin(); it != screens.cend(); it++) - { - auto shot = *it; - if (!QFile(shot->file).remove()) - { - CustomMessageBox::selectable(dialog, tr("Error!"), - tr("Failed to delete screenshots!"), - QMessageBox::Warning)->exec(); - break; - } - } - ProgressDialog refresh(dialog); - Task *t = load(); - if (refresh.exec(t) != QDialog::Accepted) - { - CustomMessageBox::selectable(dialog, tr("Error!"), - tr("Unable to refresh list: %1").arg(t->failReason()), - QMessageBox::Warning)->exec(); - } - endResetModel(); -} diff --git a/logic/screenshots/ScreenshotList.h b/logic/screenshots/ScreenshotList.h deleted file mode 100644 index dc26a698..00000000 --- a/logic/screenshots/ScreenshotList.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include <QAbstractListModel> -#include "logic/BaseInstance.h" -#include "logic/tasks/Task.h" - -#include "Screenshot.h" - -class ScreenshotList : public QAbstractListModel -{ - Q_OBJECT -public: - ScreenshotList(InstancePtr instance, QObject *parent = 0); - - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role) const; - - int rowCount(const QModelIndex &parent) const; - - Qt::ItemFlags flags(const QModelIndex &index) const; - - Task *load(); - - void loadShots(QList<ScreenshotPtr> shots) - { - m_screenshots = shots; - } - - QList<ScreenshotPtr> screenshots() const - { - return m_screenshots; - } - - InstancePtr instance() const - { - return m_instance; - } - - void deleteSelected(class ScreenshotDialog *dialog); - -signals: - -public -slots: - -private: - QList<ScreenshotPtr> m_screenshots; - InstancePtr m_instance; -}; - -class ScreenshotLoadTask : public Task -{ - Q_OBJECT - -public: - explicit ScreenshotLoadTask(ScreenshotList *list); - ~ScreenshotLoadTask(); - - QList<ScreenshotPtr> screenShots() const - { - return m_results; - } - -protected: - virtual void executeTask(); - -private: - ScreenshotList *m_list; - QList<ScreenshotPtr> m_results; -}; diff --git a/logic/tasks/ProgressProvider.h b/logic/tasks/ProgressProvider.h index 15e453a3..dcb71139 100644 --- a/logic/tasks/ProgressProvider.h +++ b/logic/tasks/ProgressProvider.h @@ -32,6 +32,7 @@ signals: void status(QString status); public: + virtual ~ProgressProvider() {}; virtual QString getStatus() const = 0; virtual void getProgress(qint64 ¤t, qint64 &total) = 0; virtual bool isRunning() const = 0; |