diff options
Diffstat (limited to 'api/logic')
217 files changed, 6411 insertions, 6158 deletions
diff --git a/api/logic/BaseInstaller.cpp b/api/logic/BaseInstaller.cpp index a2a575dc..51f66293 100644 --- a/api/logic/BaseInstaller.cpp +++ b/api/logic/BaseInstaller.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,19 @@ #include <QFile> #include "BaseInstaller.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" BaseInstaller::BaseInstaller() { } -bool BaseInstaller::isApplied(OneSixInstance *on) +bool BaseInstaller::isApplied(MinecraftInstance *on) { return QFile::exists(filename(on->instanceRoot())); } -bool BaseInstaller::add(OneSixInstance *to) +bool BaseInstaller::add(MinecraftInstance *to) { if (!patchesDir(to->instanceRoot()).exists()) { @@ -46,7 +46,7 @@ bool BaseInstaller::add(OneSixInstance *to) return true; } -bool BaseInstaller::remove(OneSixInstance *from) +bool BaseInstaller::remove(MinecraftInstance *from) { return QFile::remove(filename(from->instanceRoot())); } diff --git a/api/logic/BaseInstaller.h b/api/logic/BaseInstaller.h index 65a2436f..afe11d55 100644 --- a/api/logic/BaseInstaller.h +++ b/api/logic/BaseInstaller.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ #include "multimc_logic_export.h" -class OneSixInstance; +class MinecraftInstance; class QDir; class QString; class QObject; @@ -32,12 +32,12 @@ class MULTIMC_LOGIC_EXPORT BaseInstaller public: BaseInstaller(); virtual ~BaseInstaller(){}; - bool isApplied(OneSixInstance *on); + bool isApplied(MinecraftInstance *on); - virtual bool add(OneSixInstance *to); - virtual bool remove(OneSixInstance *from); + virtual bool add(MinecraftInstance *to); + virtual bool remove(MinecraftInstance *from); - virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0; + virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0; protected: virtual QString id() const = 0; diff --git a/api/logic/BaseInstance.cpp b/api/logic/BaseInstance.cpp index ee9e919d..7e652e0d 100644 --- a/api/logic/BaseInstance.cpp +++ b/api/logic/BaseInstance.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -197,7 +197,7 @@ bool BaseInstance::canLaunch() const return (!hasVersionBroken() && !isRunning()); } -bool BaseInstance::reload() +bool BaseInstance::reloadSettings() { return m_settings->reload(); } @@ -279,6 +279,7 @@ QString BaseInstance::windowTitle() const return "MultiMC: " + name(); } +// FIXME: why is this here? move it to MinecraftInstance!!! QStringList BaseInstance::extraArguments() const { return Commandline::splitArgs(settings()->get("JvmArgs").toString()); diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h index 27b167a6..282bfb70 100644 --- a/api/logic/BaseInstance.h +++ b/api/logic/BaseInstance.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,8 @@ #include "MessageLevel.h" #include "pathmatcher/IPathMatcher.h" +#include "net/Mode.h" + #include "multimc_logic_export.h" class QDir; @@ -67,9 +69,8 @@ public: /// virtual destructor to make sure the destruction is COMPLETE virtual ~BaseInstance() {}; - virtual void copy(SettingsObjectPtr newSettings, const QDir &newDir) {} - virtual void init() = 0; + virtual void saveNow() = 0; /// nuke thoroughly - deletes the instance contents, notifies the list/model which is /// responsible of cleaning up the husk @@ -129,25 +130,8 @@ public: virtual QStringList extraArguments() const; - virtual QString intendedVersionId() const = 0; - virtual bool setIntendedVersionId(QString version) = 0; - - /*! - * The instance's current version. - * This value represents the instance's current version. If this value is - * different from the intendedVersion, the instance should be updated. - * \warning Don't change this value unless you know what you're doing. - */ - virtual QString currentVersionId() const = 0; - - /*! - * Whether or not 'the game' should be downloaded when the instance is launched. - */ - virtual bool shouldUpdate() const = 0; - virtual void setShouldUpdate(bool val) = 0; - /// Traits. Normally inside the version, depends on instance implementation. - virtual QSet <QString> traits() = 0; + virtual QSet <QString> traits() const = 0; /** * Gets the time that the instance was last launched. @@ -160,12 +144,6 @@ public: InstancePtr getSharedPtr(); /*! - * \brief Gets a pointer to this instance's version list. - * \return A pointer to the available version list for this instance. - */ - virtual std::shared_ptr<BaseVersionList> versionList() const = 0; - - /*! * \brief Gets this instance's settings object. * This settings object stores instance-specific settings. * \return A pointer to this instance's settings object. @@ -173,7 +151,7 @@ public: virtual SettingsObjectPtr settings() const; /// returns a valid update task - virtual shared_qobject_ptr<Task> createUpdateTask() = 0; + virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0; /// returns a valid launcher (task container) virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0; @@ -182,12 +160,6 @@ public: std::shared_ptr<LaunchTask> getLaunchTask(); /*! - * Returns a task that should be done right before launch - * This task should do any extra preparations needed - */ - virtual std::shared_ptr<Task> createJarModdingTask() = 0; - - /*! * Create envrironment variables for running the instance */ virtual QProcessEnvironment createEnvironment() = 0; @@ -251,10 +223,11 @@ public: } } - bool canLaunch() const; + virtual bool canLaunch() const; + virtual bool canEdit() const = 0; virtual bool canExport() const = 0; - virtual bool reload(); + bool reloadSettings(); /** * 'print' a verbose desription of the instance into a QStringList diff --git a/api/logic/BaseInstanceProvider.h b/api/logic/BaseInstanceProvider.h index f6833650..34489c5d 100644 --- a/api/logic/BaseInstanceProvider.h +++ b/api/logic/BaseInstanceProvider.h @@ -37,7 +37,7 @@ public: { return QString(); } - virtual bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName) + virtual bool commitStagedInstance(const QString & path, const QString& instanceName, const QString & groupName) { return false; } diff --git a/api/logic/BaseVersion.h b/api/logic/BaseVersion.h index 0f99b1a3..e49d6277 100644 --- a/api/logic/BaseVersion.h +++ b/api/logic/BaseVersion.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/BaseVersionList.cpp b/api/logic/BaseVersionList.cpp index 8b424c11..31a635d7 100644 --- a/api/logic/BaseVersionList.cpp +++ b/api/logic/BaseVersionList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/BaseVersionList.h b/api/logic/BaseVersionList.h index fa1e0861..b609e039 100644 --- a/api/logic/BaseVersionList.h +++ b/api/logic/BaseVersionList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt index 2eda34fe..404044d8 100644 --- a/api/logic/CMakeLists.txt +++ b/api/logic/CMakeLists.txt @@ -206,22 +206,14 @@ set(MINECRAFT_SOURCES minecraft/auth/flows/RefreshTask.cpp minecraft/auth/flows/ValidateTask.h minecraft/auth/flows/ValidateTask.cpp - minecraft/onesix/OneSixUpdate.h - minecraft/onesix/OneSixUpdate.cpp - minecraft/onesix/OneSixInstance.h - minecraft/onesix/OneSixInstance.cpp - minecraft/onesix/OneSixProfileStrategy.cpp - minecraft/onesix/OneSixProfileStrategy.h - minecraft/onesix/OneSixVersionFormat.cpp - minecraft/onesix/OneSixVersionFormat.h - minecraft/onesix/update/AssetUpdateTask.h - minecraft/onesix/update/AssetUpdateTask.cpp - minecraft/onesix/update/FMLLibrariesTask.cpp - minecraft/onesix/update/FMLLibrariesTask.h - minecraft/onesix/update/FoldersTask.cpp - minecraft/onesix/update/FoldersTask.h - minecraft/onesix/update/LibrariesTask.cpp - minecraft/onesix/update/LibrariesTask.h + minecraft/update/AssetUpdateTask.h + minecraft/update/AssetUpdateTask.cpp + minecraft/update/FMLLibrariesTask.cpp + minecraft/update/FMLLibrariesTask.h + minecraft/update/FoldersTask.cpp + minecraft/update/FoldersTask.h + minecraft/update/LibrariesTask.cpp + minecraft/update/LibrariesTask.h minecraft/launch/ClaimAccount.cpp minecraft/launch/ClaimAccount.h minecraft/launch/CreateServerResourcePacksFolder.cpp @@ -238,35 +230,42 @@ set(MINECRAFT_SOURCES minecraft/launch/PrintInstanceInfo.h minecraft/legacy/LegacyModList.h minecraft/legacy/LegacyModList.cpp - minecraft/legacy/LegacyUpdate.h - minecraft/legacy/LegacyUpdate.cpp minecraft/legacy/LegacyInstance.h minecraft/legacy/LegacyInstance.cpp - minecraft/legacy/LwjglVersionList.h - minecraft/legacy/LwjglVersionList.cpp + minecraft/legacy/LegacyUpgradeTask.h + minecraft/legacy/LegacyUpgradeTask.cpp minecraft/GradleSpecifier.h - minecraft/MinecraftProfile.cpp - minecraft/MinecraftProfile.h - minecraft/MojangVersionFormat.cpp - minecraft/MojangVersionFormat.h minecraft/MinecraftInstance.cpp minecraft/MinecraftInstance.h + minecraft/LaunchProfile.cpp + minecraft/LaunchProfile.h + minecraft/Component.cpp + minecraft/Component.h + minecraft/ComponentList.cpp + minecraft/ComponentList.h + minecraft/ComponentUpdateTask.cpp + minecraft/ComponentUpdateTask.h + minecraft/MinecraftLoadAndCheck.h + minecraft/MinecraftLoadAndCheck.cpp + minecraft/MinecraftUpdate.h + minecraft/MinecraftUpdate.cpp + minecraft/MojangVersionFormat.cpp + minecraft/MojangVersionFormat.h minecraft/Rule.cpp minecraft/Rule.h + minecraft/OneSixVersionFormat.cpp + minecraft/OneSixVersionFormat.h minecraft/OpSys.cpp minecraft/OpSys.h minecraft/ParseUtils.cpp minecraft/ParseUtils.h minecraft/ProfileUtils.cpp minecraft/ProfileUtils.h - minecraft/ProfileStrategy.h minecraft/Library.cpp minecraft/Library.h minecraft/MojangDownloadInfo.h minecraft/VersionFile.cpp minecraft/VersionFile.h - minecraft/ProfilePatch.cpp - minecraft/ProfilePatch.h minecraft/VersionFilterData.h minecraft/VersionFilterData.cpp minecraft/Mod.h @@ -278,18 +277,6 @@ set(MINECRAFT_SOURCES minecraft/WorldList.h minecraft/WorldList.cpp - # FTB - minecraft/ftb/OneSixFTBInstance.h - minecraft/ftb/OneSixFTBInstance.cpp - minecraft/ftb/LegacyFTBInstance.h - minecraft/ftb/LegacyFTBInstance.cpp - minecraft/ftb/FTBProfileStrategy.h - minecraft/ftb/FTBProfileStrategy.cpp - minecraft/ftb/FTBInstanceProvider.cpp - minecraft/ftb/FTBInstanceProvider.h - minecraft/ftb/FTBPlugin.h - minecraft/ftb/FTBPlugin.cpp - # Flame minecraft/flame/PackManifest.h minecraft/flame/PackManifest.cpp @@ -350,8 +337,6 @@ set(TASKS_SOURCES # Tasks tasks/Task.h tasks/Task.cpp - tasks/ThreadTask.h - tasks/ThreadTask.cpp tasks/SequentialTask.h tasks/SequentialTask.cpp ) @@ -433,6 +418,20 @@ set(META_SOURCES meta/Index.h ) +set(MODPLATFORM_SOURCES + # Modplatform sources + modplatform/FtbPackDownloader.h + modplatform/FtbPackDownloader.cpp + + modplatform/FtbPackFetchTask.h + modplatform/FtbPackFetchTask.cpp + modplatform/FtbPackInstallTask.h + modplatform/FtbPackInstallTask.cpp + + modplatform/PackHelpers.h + +) + add_unit_test(Index SOURCES meta/Index_test.cpp LIBS MultiMC_logic @@ -461,6 +460,7 @@ set(LOGIC_SOURCES ${TOOLS_SOURCES} ${META_SOURCES} ${ICONS_SOURCES} + ${MODPLATFORM_SOURCES} ) add_library(MultiMC_logic SHARED ${LOGIC_SOURCES}) @@ -469,8 +469,15 @@ set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISI generate_export_header(MultiMC_logic) # Link -target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip ${NBT_NAME} ${ZLIB_LIBRARIES}) +target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES}) qt5_use_modules(MultiMC_logic Core Xml Network Concurrent) # Mark and export headers target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}") + +# Install it +install( + TARGETS MultiMC_logic + RUNTIME DESTINATION ${LIBRARY_DEST_DIR} + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} +) diff --git a/api/logic/Commandline.cpp b/api/logic/Commandline.cpp index 751182af..eac9db09 100644 --- a/api/logic/Commandline.cpp +++ b/api/logic/Commandline.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Authors: Orochimarufan <orochimarufan.x3@gmail.com> * diff --git a/api/logic/Commandline.h b/api/logic/Commandline.h index 6c473015..c8c8be29 100644 --- a/api/logic/Commandline.h +++ b/api/logic/Commandline.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Authors: Orochimarufan <orochimarufan.x3@gmail.com> * diff --git a/api/logic/Env.cpp b/api/logic/Env.cpp index 59d4c4a8..cf321af2 100644 --- a/api/logic/Env.cpp +++ b/api/logic/Env.cpp @@ -13,13 +13,11 @@ #include <QDebug> -class Env::Private +struct Env::Private { -public: QNetworkAccessManager m_qnam; shared_qobject_ptr<HttpMetaCache> m_metacache; std::shared_ptr<IIconList> m_iconlist; - QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists; shared_qobject_ptr<Meta::Index> m_metadataIndex; QString m_jarsPath; }; @@ -75,32 +73,6 @@ void Env::registerIconList(std::shared_ptr<IIconList> iconlist) d->m_iconlist = iconlist; } -BaseVersionPtr Env::getVersion(QString component, QString version) -{ - auto list = getVersionList(component); - if(!list) - { - return nullptr; - } - return list->findVersion(version); -} - -std::shared_ptr< BaseVersionList > Env::getVersionList(QString component) -{ - auto iter = d->m_versionLists.find(component); - if(iter != d->m_versionLists.end()) - { - return *iter; - } - //return std::make_shared<NullVersionList>(); - return nullptr; -} - -void Env::registerVersionList(QString name, std::shared_ptr< BaseVersionList > vlist) -{ - d->m_versionLists[name] = vlist; -} - shared_qobject_ptr<Meta::Index> Env::metadataIndex() { if (!d->m_metadataIndex) @@ -206,5 +178,3 @@ void Env::setJarsPath(const QString& path) { d->m_jarsPath = path; } - -#include "Env.moc" diff --git a/api/logic/Env.h b/api/logic/Env.h index 08b1dd0d..276d762d 100644 --- a/api/logic/Env.h +++ b/api/logic/Env.h @@ -24,11 +24,12 @@ class Index; #endif #define ENV (Env::getInstance()) + class MULTIMC_LOGIC_EXPORT Env { friend class MultiMC; private: - class Private; + struct Private; Env(); ~Env(); static void dispose(); @@ -47,14 +48,6 @@ public: /// Updates the application proxy settings from the settings object. void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password); - /// get a version list by name - std::shared_ptr<BaseVersionList> getVersionList(QString component); - - /// get a version by list name and version name - std::shared_ptr<BaseVersion> getVersion(QString component, QString version); - - void registerVersionList(QString name, std::shared_ptr<BaseVersionList> vlist); - void registerIconList(std::shared_ptr<IIconList> iconlist); shared_qobject_ptr<Meta::Index> metadataIndex(); diff --git a/api/logic/FileSystem.cpp b/api/logic/FileSystem.cpp index b3115988..4b47f415 100644 --- a/api/logic/FileSystem.cpp +++ b/api/logic/FileSystem.cpp @@ -3,11 +3,27 @@ #include "FileSystem.h" #include <QDir> +#include <QFile> #include <QSaveFile> #include <QFileInfo> #include <QDebug> #include <QUrl> #include <QStandardPaths> +#include <QTextStream> + +#if defined Q_OS_WIN32 + #include <windows.h> + #include <string> + #include <sys/utime.h> + #include <winnls.h> + #include <shobjidl.h> + #include <objbase.h> + #include <objidl.h> + #include <shlguid.h> + #include <shlobj.h> +#else + #include <utime.h> +#endif namespace FS { @@ -62,21 +78,13 @@ QByteArray read(const QString &filename) bool updateTimestamp(const QString& filename) { - QFile file(filename); - if (!file.exists()) - { - return false; - } - if (!file.open(QIODevice::ReadWrite)) - { - return false; - } - const quint64 size = file.size(); - file.seek(size); - file.write( QByteArray(1, '0') ); - file.resize(size); - return true; - +#ifdef Q_OS_WIN32 + std::wstring filename_utf_16 = filename.toStdWString(); + return (_wutime64(filename_utf_16.c_str(), nullptr) == 0); +#else + QByteArray filenameBA = QFile::encodeName(filename); + return (utime(filenameBA.data(), nullptr) == 0); +#endif } bool ensureFilePathExists(QString filenamepath) @@ -163,11 +171,6 @@ bool copy::operator()(const QString &offset) return true; } - -#if defined Q_OS_WIN32 -#include <windows.h> -#include <string> -#endif bool deletePath(QString path) { bool OK = true; @@ -225,7 +228,7 @@ bool deletePath(QString path) } -QString PathCombine(QString path1, QString path2) +QString PathCombine(const QString & path1, const QString & path2) { if(!path1.size()) return path2; @@ -234,11 +237,16 @@ QString PathCombine(QString path1, QString path2) return QDir::cleanPath(path1 + QDir::separator() + path2); } -QString PathCombine(QString path1, QString path2, QString path3) +QString PathCombine(const QString & path1, const QString & path2, const QString & path3) { return PathCombine(PathCombine(path1, path2), path3); } +QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4) +{ + return PathCombine(PathCombine(path1, path2, path3), path4); +} + QString AbsolutePath(QString path) { return QFileInfo(path).absolutePath(); @@ -332,21 +340,9 @@ bool checkProblemticPathJava(QDir folder) return pathfoldername.contains("!", Qt::CaseInsensitive); } -#include <QStandardPaths> -#include <QFile> -#include <QTextStream> - // Win32 crap #if defined Q_OS_WIN -#include <windows.h> -#include <winnls.h> -#include <shobjidl.h> -#include <objbase.h> -#include <objidl.h> -#include <shlguid.h> -#include <shlobj.h> - bool called_coinit = false; HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) diff --git a/api/logic/FileSystem.h b/api/logic/FileSystem.h index a09ee557..de8774ff 100644 --- a/api/logic/FileSystem.h +++ b/api/logic/FileSystem.h @@ -83,8 +83,9 @@ private: */ MULTIMC_LOGIC_EXPORT bool deletePath(QString path); -MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2); -MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2, QString path3); +MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2); +MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3); +MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4); MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path); diff --git a/api/logic/FolderInstanceProvider.cpp b/api/logic/FolderInstanceProvider.cpp index ea0d4ef0..a6d3bdc8 100644 --- a/api/logic/FolderInstanceProvider.cpp +++ b/api/logic/FolderInstanceProvider.cpp @@ -1,7 +1,7 @@ #include "FolderInstanceProvider.h" #include "settings/INISettingsObject.h" #include "FileSystem.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" #include "minecraft/legacy/LegacyInstance.h" #include "NullInstance.h" @@ -12,6 +12,7 @@ #include <QJsonObject> #include <QJsonArray> #include <QUuid> +#include <QTimer> const static int GROUP_FILE_FORMAT_VERSION = 1; @@ -33,11 +34,13 @@ struct WatchLock FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir) : BaseInstanceProvider(settings) { - m_instDir = instDir; - if (!QDir::current().exists(m_instDir)) + // Create aand normalize path + if (!QDir::current().exists(instDir)) { - QDir::current().mkpath(m_instDir); + QDir::current().mkpath(instDir); } + // NOTE: canonicalPath requires the path to exist. Do not move this above the creation block! + m_instDir = QDir(instDir).canonicalPath(); m_watcher = new QFileSystemWatcher(this); connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged); m_watcher->addPath(m_instDir); @@ -46,7 +49,7 @@ FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QList< InstanceId > FolderInstanceProvider::discoverInstances() { QList<InstanceId> out; - QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks); + QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); while (iter.hasNext()) { QString subDir = iter.next(); @@ -88,7 +91,7 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id) if (inst_type == "OneSix" || inst_type == "Nostalgia") { - inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instanceRoot)); + inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); } else if (inst_type == "Legacy") { @@ -110,24 +113,6 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id) return inst; } -#include "InstanceImportTask.h" -Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon) -{ - return new InstanceImportTask(m_globalSettings, sourceUrl, this, instName, instIcon, instGroup); -} - -#include "InstanceCreationTask.h" -Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon) -{ - return new InstanceCreationTask(m_globalSettings, this, version, instName, instIcon, instGroup); -} - -#include "InstanceCopyTask.h" -Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves) -{ - return new InstanceCopyTask(m_globalSettings, this, oldInstance, instName, instIcon, instGroup, copySaves); -} - void FolderInstanceProvider::saveGroupList() { WatchLock foo(m_watcher, m_instDir); @@ -298,7 +283,7 @@ void FolderInstanceProvider::instanceDirContentsChanged(const QString& path) void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value) { - QString newInstDir = value.toString(); + QString newInstDir = QDir(value.toString()).canonicalPath(); if(newInstDir != m_instDir) { if(m_groupsLoaded) @@ -311,6 +296,164 @@ void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVaria } } +template <typename T> +static void clamp(T& current, T min, T max) +{ + if (current < min) + { + current = min; + } + else if(current > max) + { + current = max; + } +} + +// List of numbers from min to max. Next is exponent times bigger than previous. +class ExponentialSeries +{ +public: + ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2) + { + m_current = m_min = min; + m_max = max; + m_exponent = exponent; + } + void reset() + { + m_current = m_min; + } + unsigned operator()() + { + unsigned retval = m_current; + m_current *= m_exponent; + clamp(m_current, m_min, m_max); + return retval; + } + unsigned m_current; + unsigned m_min; + unsigned m_max; + unsigned m_exponent; +}; + +/* + * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. + * Basically, it starts messing things up while MultiMC is extracting/creating instances + * and causes that horrible failure that is NTFS to lock files in place because they are open. + */ +class FolderInstanceStaging : public Task +{ +Q_OBJECT + const unsigned minBackoff = 1; + const unsigned maxBackoff = 16; +public: + FolderInstanceStaging ( + FolderInstanceProvider * parent, + Task * child, + const QString & stagingPath, + const QString& instanceName, + const QString& groupName ) + : backoff(minBackoff, maxBackoff) + { + m_parent = parent; + m_child.reset(child); + connect(child, &Task::succeeded, this, &FolderInstanceStaging::childSucceded); + connect(child, &Task::failed, this, &FolderInstanceStaging::childFailed); + connect(child, &Task::status, this, &FolderInstanceStaging::setStatus); + connect(child, &Task::progress, this, &FolderInstanceStaging::setProgress); + m_instanceName = instanceName; + m_groupName = groupName; + m_stagingPath = stagingPath; + m_backoffTimer.setSingleShot(true); + connect(&m_backoffTimer, &QTimer::timeout, this, &FolderInstanceStaging::childSucceded); + } + +protected: + virtual void executeTask() override + { + m_child->start(); + } + QStringList warnings() const override + { + return m_child->warnings(); + } + +private slots: + void childSucceded() + { + unsigned sleepTime = backoff(); + if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) + { + emitSucceeded(); + return; + } + // we actually failed, retry? + if(sleepTime == maxBackoff) + { + emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); + return; + } + qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime; + m_backoffTimer.start(sleepTime * 500); + } + void childFailed(const QString & reason) + { + m_parent->destroyStagingPath(m_stagingPath); + emitFailed(reason); + } + +private: + ExponentialSeries backoff; + QString m_stagingPath; + FolderInstanceProvider * m_parent; + unique_qobject_ptr<Task> m_child; + QString m_instanceName; + QString m_groupName; + QTimer m_backoffTimer; +}; + +#include "InstanceImportTask.h" +Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon) +{ + auto stagingPath = getStagedInstancePath(); + auto task = new InstanceImportTask(m_globalSettings, sourceUrl, stagingPath, instName, instIcon, instGroup); + return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup); +} + +#include "InstanceCreationTask.h" +Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon) +{ + auto stagingPath = getStagedInstancePath(); + auto task = new InstanceCreationTask(m_globalSettings, stagingPath, version, instName, instIcon, instGroup); + return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup); +} + +#include <modplatform/FtbPackInstallTask.h> +Task * FolderInstanceProvider::ftbCreationTask(FtbPackDownloader *downloader, const QString& instName, const QString& instGroup, const QString& instIcon) +{ + auto stagingPath = getStagedInstancePath(); + auto task = new FtbPackInstallTask(downloader, m_globalSettings, stagingPath, instName, instIcon, instGroup); + return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup); +} + +#include "InstanceCopyTask.h" +Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves) +{ + auto stagingPath = getStagedInstancePath(); + auto task = new InstanceCopyTask(m_globalSettings, stagingPath, oldInstance, instName, instIcon, instGroup, copySaves); + return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup); +} + +// FIXME: find a better place for this +#include "minecraft/legacy/LegacyUpgradeTask.h" +Task * FolderInstanceProvider::legacyUpgradeTask(const InstancePtr& oldInstance) +{ + auto stagingPath = getStagedInstancePath(); + QString newName = tr("%1 (Migrated)").arg(oldInstance->name()); + auto task = new LegacyUpgradeTask(m_globalSettings, stagingPath, oldInstance, newName); + return new FolderInstanceStaging(this, task, stagingPath, newName, oldInstance->group()); +} + QString FolderInstanceProvider::getStagedInstancePath() { QString key = QUuid::createUuid().toString(); @@ -324,21 +467,16 @@ QString FolderInstanceProvider::getStagedInstancePath() return path; } -bool FolderInstanceProvider::commitStagedInstance(const QString& keyPath, const QString& path, const QString& instanceName, - const QString& groupName) +bool FolderInstanceProvider::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName) { - if(!path.contains(keyPath)) - { - qWarning() << "It is not possible to commit" << path << "because it is not in" << keyPath; - return false; - } QDir dir; QString instID = FS::DirNameFromString(instanceName, m_instDir); { WatchLock lock(m_watcher, m_instDir); - if(!dir.rename(path, FS::PathCombine(m_instDir, instID))) + QString destination = FS::PathCombine(m_instDir, instID); + if(!dir.rename(path, destination)) { - destroyStagingPath(keyPath); + qWarning() << "Failed to move" << path << "to" << destination; return false; } groupMap[instID] = groupName; @@ -354,3 +492,4 @@ bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath) return FS::deletePath(keyPath); } +#include "FolderInstanceProvider.moc" diff --git a/api/logic/FolderInstanceProvider.h b/api/logic/FolderInstanceProvider.h index f350a96d..5117affc 100644 --- a/api/logic/FolderInstanceProvider.h +++ b/api/logic/FolderInstanceProvider.h @@ -2,6 +2,7 @@ #include "BaseInstanceProvider.h" #include <QMap> +#include <modplatform/FtbPackDownloader.h> class QFileSystemWatcher; @@ -28,6 +29,12 @@ public: // import zipped instance into this provider Task * zipImportTask(const QUrl sourceUrl, const QString &instName, const QString &instGroup, const QString &instIcon); + //create FtbInstance + Task * ftbCreationTask(FtbPackDownloader *downloader, const QString &instName, const QString &instGroup, const QString &instIcon); + + // migrate an instance to the current format + Task * legacyUpgradeTask(const InstancePtr& oldInstance); + /** * Create a new empty staging area for instance creation and @return a path/key top commit it later. * Used by instance manipulation tasks. @@ -37,7 +44,7 @@ public: * Commit the staging area given by @keyPath to the provider - used when creation succeeds. * Used by instance manipulation tasks. */ - bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName) override; + bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName) override; /** * Destroy a previously created staging area given by @keyPath - used when creation fails. * Used by instance manipulation tasks. diff --git a/api/logic/InstanceCopyTask.cpp b/api/logic/InstanceCopyTask.cpp index b1bd39ef..9ede65f5 100644 --- a/api/logic/InstanceCopyTask.cpp +++ b/api/logic/InstanceCopyTask.cpp @@ -6,10 +6,10 @@ #include "pathmatcher/RegexpMatcher.h" #include <QtConcurrentRun> -InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider* target, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves) +InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves) { m_globalSettings = settings; - m_target = target; + m_stagingPath = stagingPath; m_origInstance = origInstance; m_instName = instName; m_instIcon = instIcon; @@ -27,7 +27,7 @@ InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvi void InstanceCopyTask::executeTask() { setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - m_stagingPath = m_target->getStagedInstancePath(); + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); folderCopy.followSymlinks(false).blacklist(m_matcher.get()); @@ -42,7 +42,6 @@ void InstanceCopyTask::copyFinished() auto successful = m_copyFuture.result(); if(!successful) { - m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Instance folder copy failed.")); return; } @@ -50,19 +49,14 @@ void InstanceCopyTask::copyFinished() auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); instanceSettings->registerSetting("InstanceType", "Legacy"); - // FIXME: and this too? errors??? - m_origInstance->copy(instanceSettings, m_stagingPath); - InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); inst->setName(m_instName); inst->setIconKey(m_instIcon); - m_target->commitStagedInstance(m_stagingPath, m_stagingPath, m_instName, m_instGroup); emitSucceeded(); } void InstanceCopyTask::copyAborted() { - m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Instance folder copy has been aborted.")); return; } diff --git a/api/logic/InstanceCopyTask.h b/api/logic/InstanceCopyTask.h index 28fd3f40..dc46bfec 100644 --- a/api/logic/InstanceCopyTask.h +++ b/api/logic/InstanceCopyTask.h @@ -17,7 +17,7 @@ class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public Task { Q_OBJECT public: - explicit InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider * target, InstancePtr origInstance, const QString &instName, + explicit InstanceCopyTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString &instName, const QString &instIcon, const QString &instGroup, bool copySaves); protected: @@ -28,7 +28,6 @@ protected: private: /* data */ SettingsObjectPtr m_globalSettings; - BaseInstanceProvider * m_target = nullptr; InstancePtr m_origInstance; QString m_instName; QString m_instIcon; diff --git a/api/logic/InstanceCreationTask.cpp b/api/logic/InstanceCreationTask.cpp index e7b0de7c..8a68815a 100644 --- a/api/logic/InstanceCreationTask.cpp +++ b/api/logic/InstanceCreationTask.cpp @@ -4,13 +4,14 @@ #include "FileSystem.h" //FIXME: remove this -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" -InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider* target, BaseVersionPtr version, +InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, const QString & stagingPath, BaseVersionPtr version, const QString& instName, const QString& instIcon, const QString& instGroup) { m_globalSettings = settings; - m_target = target; + m_stagingPath = stagingPath; m_instName = instName; m_instIcon = instIcon; m_instGroup = instGroup; @@ -20,27 +21,19 @@ InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInsta void InstanceCreationTask::executeTask() { setStatus(tr("Creating instance from version %1").arg(m_version->name())); - /* - auto minecraftVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_version); - if(!minecraftVersion) { - emitFailed(tr("The supplied version is not a Minecraft version.")); - return ; + auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); + instanceSettings->suspendSave(); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); + auto components = inst.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_version->descriptor(), true); + inst.setName(m_instName); + inst.setIconKey(m_instIcon); + inst.init(); + instanceSettings->resumeSave(); } - */ - - QString stagingPath = m_target->getStagedInstancePath(); - QDir rootDir(stagingPath); - - auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(stagingPath, "instance.cfg")); - instanceSettings->registerSetting("InstanceType", "Legacy"); - - instanceSettings->set("InstanceType", "OneSix"); - InstancePtr inst(new OneSixInstance(m_globalSettings, instanceSettings, stagingPath)); - inst->setIntendedVersionId(m_version->descriptor()); - inst->setName(m_instName); - inst->setIconKey(m_instIcon); - inst->init(); - m_target->commitStagedInstance(stagingPath, stagingPath, m_instName, m_instGroup); emitSucceeded(); } diff --git a/api/logic/InstanceCreationTask.h b/api/logic/InstanceCreationTask.h index b4ade320..49fd4615 100644 --- a/api/logic/InstanceCreationTask.h +++ b/api/logic/InstanceCreationTask.h @@ -7,13 +7,11 @@ #include "settings/SettingsObject.h" #include "BaseVersion.h" -class BaseInstanceProvider; - class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public Task { Q_OBJECT public: - explicit InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider * target, BaseVersionPtr version, const QString &instName, + explicit InstanceCreationTask(SettingsObjectPtr settings, const QString & stagingPath, BaseVersionPtr version, const QString &instName, const QString &instIcon, const QString &instGroup); protected: @@ -22,7 +20,7 @@ protected: private: /* data */ SettingsObjectPtr m_globalSettings; - BaseInstanceProvider * m_target; + QString m_stagingPath; BaseVersionPtr m_version; QString m_instName; QString m_instIcon; diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp index f1b3d5aa..2b481300 100644 --- a/api/logic/InstanceImportTask.cpp +++ b/api/logic/InstanceImportTask.cpp @@ -1,5 +1,3 @@ -#include "minecraft/onesix/OneSixInstance.h" - #include "InstanceImportTask.h" #include "BaseInstance.h" #include "BaseInstanceProvider.h" @@ -12,16 +10,18 @@ #include <QtConcurrentRun> // FIXME: this does not belong here, it's Minecraft/Flame specific +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" #include "minecraft/flame/FileResolvingTask.h" #include "minecraft/flame/PackManifest.h" #include "Json.h" -InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, +InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, const QString & stagingPath, const QString &instName, const QString &instIcon, const QString &instGroup) { m_globalSettings = settings; m_sourceUrl = sourceUrl; - m_target = target; + m_stagingPath = stagingPath; m_instName = instName; m_instIcon = instIcon; m_instGroup = instGroup; @@ -34,7 +34,7 @@ void InstanceImportTask::executeTask() if (m_sourceUrl.isLocalFile()) { m_archivePath = m_sourceUrl.toLocalFile(); - extractAndTweak(); + processZipPack(); } else { @@ -57,7 +57,7 @@ void InstanceImportTask::executeTask() void InstanceImportTask::downloadSucceeded() { - extractAndTweak(); + processZipPack(); m_filesNetJob.reset(); } @@ -72,34 +72,47 @@ void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) setProgress(current / 2, total); } -static QFileInfo findRecursive(const QString &dir, const QString &name) -{ - for (const auto info : QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, QDir::DirsLast)) - { - if (info.isFile() && info.fileName() == name) - { - return info; - } - else if (info.isDir()) - { - const QFileInfo res = findRecursive(info.absoluteFilePath(), name); - if (res.isFile() && res.exists()) - { - return res; - } - } - } - return QFileInfo(); -} - -void InstanceImportTask::extractAndTweak() +void InstanceImportTask::processZipPack() { setStatus(tr("Extracting modpack")); - m_stagingPath = m_target->getStagedInstancePath(); QDir extractDir(m_stagingPath); qDebug() << "Attempting to create instance from" << m_archivePath; - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, m_archivePath, extractDir.absolutePath()); + // open the zip and find relevant files in it + m_packZip.reset(new QuaZip(m_archivePath)); + if (!m_packZip->open(QuaZip::mdUnzip)) + { + emitFailed(tr("Unable to open supplied modpack zip file.")); + return; + } + + QStringList blacklist = {"instance.cfg", "manifest.json"}; + QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); + QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); + QString root; + if(!mmcFound.isNull()) + { + // process as MultiMC instance/pack + qDebug() << "MultiMC:" << mmcFound; + root = mmcFound; + m_modpackType = ModpackType::MultiMC; + } + else if(!flameFound.isNull()) + { + // process as Flame pack + qDebug() << "Flame:" << flameFound; + root = flameFound; + m_modpackType = ModpackType::Flame; + } + + if(m_modpackType == ModpackType::Unknown) + { + emitFailed(tr("Archive does not contain a recognized modpack type.")); + return; + } + + // make sure we extract just the pack + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath()); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted); m_extractFutureWatcher.setFuture(m_extractFuture); @@ -107,9 +120,9 @@ void InstanceImportTask::extractAndTweak() void InstanceImportTask::extractFinished() { + m_packZip.reset(); if (m_extractFuture.result().isEmpty()) { - m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Failed to extract modpack")); return; } @@ -137,7 +150,7 @@ void InstanceImportTask::extractFinished() { if(!QFile::setPermissions(filepath, permissions)) { - qWarning() << "Could not fix" << filepath; + logWarning(tr("Could not fix permissions for %1").arg(filepath)); } else { @@ -146,34 +159,27 @@ void InstanceImportTask::extractFinished() } } - const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg"); - const QFileInfo flameJson = findRecursive(extractDir.absolutePath(), "manifest.json"); - if (instanceCfgFile.isFile()) - { - qDebug() << "Pack appears to be exported from MultiMC."; - processMultiMC(instanceCfgFile); - } - else if (flameJson.isFile()) + switch(m_modpackType) { - qDebug() << "Pack appears to be from 'Flame'."; - processFlame(flameJson); - } - else - { - qCritical() << "Archive does not contain a recognized modpack type."; - m_target->destroyStagingPath(m_stagingPath); - emitFailed(tr("Archive does not contain a recognized modpack type.")); + case ModpackType::Flame: + processFlame(); + return; + case ModpackType::MultiMC: + processMultiMC(); + return; + case ModpackType::Unknown: + emitFailed(tr("Archive does not contain a recognized modpack type.")); + return; } } void InstanceImportTask::extractAborted() { - m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Instance import has been aborted.")); return; } -void InstanceImportTask::processFlame(const QFileInfo & manifest) +void InstanceImportTask::processFlame() { const static QMap<QString,QString> forgemap = { {"1.2.5", "3.4.9.171"}, @@ -184,24 +190,30 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest) Flame::Manifest pack; try { - Flame::loadManifest(pack, manifest.absoluteFilePath()); + QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); + Flame::loadManifest(pack, configPath); + QFile::remove(configPath); } catch (JSONValidationError & e) { - m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); return; } - m_packRoot = manifest.absolutePath(); if(!pack.overrides.isEmpty()) { - QString overridePath = FS::PathCombine(m_packRoot, pack.overrides); - QString mcPath = FS::PathCombine(m_packRoot, "minecraft"); - if (!QFile::rename(overridePath, mcPath)) + QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); + if (QFile::exists(overridePath)) { - m_target->destroyStagingPath(m_stagingPath); - emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides); - return; + QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); + if (!QFile::rename(overridePath, mcPath)) + { + emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides); + return; + } + } + else + { + logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); } } @@ -215,22 +227,24 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest) forgeVersion = id; continue; } - qWarning() << "Unknown mod loader in manifest:" << id; + logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); } - QString configPath = FS::PathCombine(m_packRoot, "instance.cfg"); + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceSettings = std::make_shared<INISettingsObject>(configPath); instanceSettings->registerSetting("InstanceType", "Legacy"); instanceSettings->set("InstanceType", "OneSix"); - OneSixInstance instance(m_globalSettings, instanceSettings, m_packRoot); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); auto mcVersion = pack.minecraft.version; // Hack to correct some 'special sauce'... if(mcVersion.endsWith('.')) { mcVersion.remove(QRegExp("[.]+$")); - qWarning() << "Mysterious trailing dots removed from Minecraft version while importing pack."; + logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); } - instance.setComponentVersion("net.minecraft", mcVersion); + auto components = instance.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", mcVersion, true); if(!forgeVersion.isEmpty()) { // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. @@ -242,10 +256,10 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest) } else { - qWarning() << "Could not map recommended forge version for" << mcVersion; + logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion)); } } - instance.setComponentVersion("net.minecraftforge", forgeVersion); + components->setComponentVersion("net.minecraftforge", forgeVersion); } if (m_instIcon != "default") { @@ -268,7 +282,7 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest) } } instance.init(); - QString jarmodsPath = FS::PathCombine(m_packRoot, "minecraft", "jarmods"); + QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); QFileInfo jarmodsInfo(jarmodsPath); if(jarmodsInfo.isDir()) { @@ -281,7 +295,7 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest) qDebug() << info.fileName(); jarMods.push_back(info.absoluteFilePath()); } - auto profile = instance.getMinecraftProfile(); + auto profile = instance.getComponentList(); profile->installJarMods(jarMods); // nuke the original files FS::deletePath(jarmodsPath); @@ -294,26 +308,49 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest) m_filesNetJob.reset(new NetJob(tr("Mod download"))); for(auto result: results.files) { - auto path = FS::PathCombine(m_packRoot, "minecraft/mods", result.fileName); - auto dl = Net::Download::makeFile(result.url,path); - m_filesNetJob->addNetAction(dl); + QString filename = result.fileName; + if(!result.required) + { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath , relpath); + + switch(result.type) + { + case Flame::File::Type::Folder: + { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: + { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_filesNetJob->addNetAction(dl); + break; + } + case Flame::File::Type::Modpack: + logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } } m_modIdResolver.reset(); connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() { m_filesNetJob.reset(); - if (!m_target->commitStagedInstance(m_stagingPath, m_packRoot, m_instName, m_instGroup)) - { - m_target->destroyStagingPath(m_stagingPath); - emitFailed(tr("Unable to commit instance")); - return; - } emitSucceeded(); } ); connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) { - m_target->destroyStagingPath(m_stagingPath); m_filesNetJob.reset(); emitFailed(reason); }); @@ -327,7 +364,6 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest) ); connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { - m_target->destroyStagingPath(m_stagingPath); m_modIdResolver.reset(); emitFailed(tr("Unable to resolve mod IDs:\n") + reason); }); @@ -342,14 +378,14 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest) m_modIdResolver->start(); } -void InstanceImportTask::processMultiMC(const QFileInfo & config) +void InstanceImportTask::processMultiMC() { // FIXME: copy from FolderInstanceProvider!!! FIX IT!!! - auto instanceSettings = std::make_shared<INISettingsObject>(config.absoluteFilePath()); + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared<INISettingsObject>(configPath); instanceSettings->registerSetting("InstanceType", "Legacy"); - QString actualDir = config.absolutePath(); - NullInstance instance(m_globalSettings, instanceSettings, actualDir); + NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath); // reset time played on import... because packs. instance.resetTimePlayed(); @@ -377,11 +413,5 @@ void InstanceImportTask::processMultiMC(const QFileInfo & config) iconList->installIcons({importIconPath}); } } - if (!m_target->commitStagedInstance(m_stagingPath, actualDir, m_instName, m_instGroup)) - { - m_target->destroyStagingPath(m_stagingPath); - emitFailed(tr("Unable to commit instance")); - return; - } emitSucceeded(); } diff --git a/api/logic/InstanceImportTask.h b/api/logic/InstanceImportTask.h index d5192299..99397009 100644 --- a/api/logic/InstanceImportTask.h +++ b/api/logic/InstanceImportTask.h @@ -9,6 +9,7 @@ #include "settings/SettingsObject.h" #include "QObjectPtr.h" +class QuaZip; class BaseInstanceProvider; namespace Flame { @@ -19,7 +20,7 @@ class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task { Q_OBJECT public: - explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, const QString &instName, + explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, const QString & stagingPath, const QString &instName, const QString &instIcon, const QString &instGroup); protected: @@ -27,9 +28,9 @@ protected: virtual void executeTask() override; private: - void extractAndTweak(); - void processMultiMC(const QFileInfo &config); - void processFlame(const QFileInfo &manifest); + void processZipPack(); + void processMultiMC(); + void processFlame(); private slots: void downloadSucceeded(); @@ -43,14 +44,18 @@ private: /* data */ NetJobPtr m_filesNetJob; shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver; QUrl m_sourceUrl; - BaseInstanceProvider * m_target; QString m_archivePath; bool m_downloadRequired = false; - QString m_packRoot; QString m_instName; QString m_instIcon; QString m_instGroup; QString m_stagingPath; + std::unique_ptr<QuaZip> m_packZip; QFuture<QStringList> m_extractFuture; QFutureWatcher<QStringList> m_extractFutureWatcher; + enum class ModpackType{ + Unknown, + MultiMC, + Flame + } m_modpackType = ModpackType::Unknown; }; diff --git a/api/logic/InstanceList.cpp b/api/logic/InstanceList.cpp index e929293f..75b523e4 100644 --- a/api/logic/InstanceList.cpp +++ b/api/logic/InstanceList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,9 @@ #include "FolderInstanceProvider.h" -InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent) - : QAbstractListModel(parent), m_instDir(instDir) +InstanceList::InstanceList(QObject *parent) + : QAbstractListModel(parent) { - m_globalSettings = globalSettings; resumeWatch(); } @@ -241,6 +240,14 @@ InstanceList::InstListError InstanceList::loadList(bool complete) return NoError; } +void InstanceList::saveNow() +{ + for(auto & item: m_instances) + { + item->saveNow(); + } +} + void InstanceList::add(const QList<InstancePtr> &t) { beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1); @@ -338,5 +345,3 @@ void InstanceList::propertiesChanged(BaseInstance *inst) emit dataChanged(index(i), index(i)); } } - -#include "InstanceList.moc" diff --git a/api/logic/InstanceList.h b/api/logic/InstanceList.h index ea4717ff..bb879c83 100644 --- a/api/logic/InstanceList.h +++ b/api/logic/InstanceList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,16 +27,14 @@ #include "QObjectPtr.h" -class QFileSystemWatcher; class BaseInstance; -class QDir; class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel { Q_OBJECT public: - explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0); + explicit InstanceList(QObject *parent = 0); virtual ~InstanceList(); public: @@ -73,6 +71,7 @@ public: } InstListError loadList(bool complete = false); + void saveNow(); /// Add an instance provider. Takes ownership of it. Should only be done before the first load. void addInstanceProvider(BaseInstanceProvider * provider); @@ -100,9 +99,7 @@ private: protected: int m_watchLevel = 0; QSet<BaseInstanceProvider *> m_updatedProviders; - QString m_instDir; QList<InstancePtr> m_instances; QSet<QString> m_groups; - SettingsObjectPtr m_globalSettings; QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers; }; diff --git a/api/logic/LoggedProcess.h b/api/logic/LoggedProcess.h index bf3dc60e..6a80365f 100644 --- a/api/logic/LoggedProcess.h +++ b/api/logic/LoggedProcess.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/MMCZip.cpp b/api/logic/MMCZip.cpp index 3badbbd1..e0c64877 100644 --- a/api/logic/MMCZip.cpp +++ b/api/logic/MMCZip.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -168,7 +168,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const } // ours -QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString &root) +QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root) { QuaZipDir rootDir(zip, root); for(auto fileName: rootDir.entryList(QDir::Files)) @@ -178,7 +178,7 @@ QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString } for(auto fileName: rootDir.entryList(QDir::Dirs)) { - QString result = findFileInZip(zip, what, root + fileName); + QString result = findFolderOfFileInZip(zip, what, root + fileName); if(!result.isEmpty()) { return result; diff --git a/api/logic/MMCZip.h b/api/logic/MMCZip.h index eac8d741..68094b2c 100644 --- a/api/logic/MMCZip.h +++ b/api/logic/MMCZip.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,7 @@ namespace MMCZip * * \return the path prefix where the file is */ - QString MULTIMC_LOGIC_EXPORT findFileInZip(QuaZip * zip, const QString & what, const QString &root = QString()); + QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString("")); /** * Find a multiple files of the same name in archive by file name diff --git a/api/logic/NullInstance.h b/api/logic/NullInstance.h index b530acd3..64965277 100644 --- a/api/logic/NullInstance.h +++ b/api/logic/NullInstance.h @@ -10,30 +10,17 @@ public: setVersionBroken(true); } virtual ~NullInstance() {}; - virtual bool setIntendedVersionId(QString) override + virtual void init() override { - return false; } - virtual QString currentVersionId() const override - { - return "Null"; - }; - virtual QString intendedVersionId() const override - { - return "Null"; - }; - virtual void init() override + virtual void saveNow() override { - }; + } virtual QString getStatusbarDescription() override { return tr("Unknown instance type"); }; - virtual bool shouldUpdate() const override - { - return false; - }; - virtual QSet< QString > traits() override + virtual QSet< QString > traits() const override { return {}; }; @@ -45,21 +32,10 @@ public: { return nullptr; } - virtual shared_qobject_ptr< Task > createUpdateTask() override + virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override { return nullptr; } - virtual std::shared_ptr<Task> createJarModdingTask() override - { - return nullptr; - } - virtual void setShouldUpdate(bool) override - { - }; - virtual std::shared_ptr< BaseVersionList > versionList() const override - { - return nullptr; - }; virtual QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); @@ -84,6 +60,14 @@ public: { return false; } + bool canEdit() const override + { + return false; + } + bool canLaunch() const override + { + return false; + } QStringList verboseDescription(AuthSessionPtr session) override { QStringList out; diff --git a/api/logic/ProblemProvider.h b/api/logic/ProblemProvider.h index b30e1776..978710f0 100644 --- a/api/logic/ProblemProvider.h +++ b/api/logic/ProblemProvider.h @@ -1,5 +1,7 @@ #pragma once +#include "multimc_logic_export.h" + enum class ProblemSeverity { None, @@ -7,42 +9,28 @@ enum class ProblemSeverity Error }; -class PatchProblem +struct PatchProblem { -public: - PatchProblem(ProblemSeverity severity, const QString & description) - { - m_severity = severity; - m_description = description; - } - const QString & getDescription() const - { - return m_description; - } - const ProblemSeverity getSeverity() const - { - return m_severity; - } -private: ProblemSeverity m_severity; QString m_description; }; -class ProblemProvider +class MULTIMC_LOGIC_EXPORT ProblemProvider { public: - virtual const QList<PatchProblem> getProblems() = 0; - virtual ProblemSeverity getProblemSeverity() = 0; + virtual ~ProblemProvider() {}; + virtual const QList<PatchProblem> getProblems() const = 0; + virtual ProblemSeverity getProblemSeverity() const = 0; }; -class ProblemContainer : public ProblemProvider +class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider { public: - const QList<PatchProblem> getProblems() override + const QList<PatchProblem> getProblems() const override { return m_problems; } - ProblemSeverity getProblemSeverity() override + ProblemSeverity getProblemSeverity() const override { return m_problemSeverity; } @@ -52,7 +40,7 @@ public: { m_problemSeverity = severity; } - m_problems.append(PatchProblem(severity, description)); + m_problems.append({severity, description}); } private: diff --git a/api/logic/Version_test.cpp b/api/logic/Version_test.cpp index 1e7920ad..b8e05768 100644 --- a/api/logic/Version_test.cpp +++ b/api/logic/Version_test.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/java/JavaChecker.cpp b/api/logic/java/JavaChecker.cpp index ebbd80be..f0b71e48 100644 --- a/api/logic/java/JavaChecker.cpp +++ b/api/logic/java/JavaChecker.cpp @@ -1,4 +1,5 @@ #include "JavaChecker.h" +#include "JavaUtils.h" #include <FileSystem.h> #include <Commandline.h> #include <QFile> @@ -42,6 +43,7 @@ void JavaChecker::performCheck() process->setArguments(args); process->setProgram(m_path); process->setProcessChannelMode(QProcess::SeparateChannels); + process->setProcessEnvironment(CleanEnviroment()); qDebug() << "Running java checker: " + m_path + args.join(" ");; connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); diff --git a/api/logic/java/JavaCheckerJob.cpp b/api/logic/java/JavaCheckerJob.cpp index 01b5b28d..fabb5aaa 100644 --- a/api/logic/java/JavaCheckerJob.cpp +++ b/api/logic/java/JavaCheckerJob.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,20 +22,19 @@ void JavaCheckerJob::partFinished(JavaCheckResult result) num_finished++; qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" << javacheckers.size(); - emit progress(num_finished, javacheckers.size()); + setProgress(num_finished, javacheckers.size()); javaresults.replace(result.id, result); if (num_finished == javacheckers.size()) { - emit finished(javaresults); + emitSucceeded(); } } void JavaCheckerJob::executeTask() { qDebug() << m_job_name.toLocal8Bit() << " started."; - m_running = true; for (auto iter : javacheckers) { javaresults.append(JavaCheckResult()); diff --git a/api/logic/java/JavaCheckerJob.h b/api/logic/java/JavaCheckerJob.h index 58a98190..cac2b638 100644 --- a/api/logic/java/JavaCheckerJob.h +++ b/api/logic/java/JavaCheckerJob.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ class JavaCheckerJob; typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr; +// FIXME: this just seems horribly redundant class JavaCheckerJob : public Task { Q_OBJECT @@ -31,41 +32,19 @@ public: bool addJavaCheckerAction(JavaCheckerPtr base) { javacheckers.append(base); - total_progress++; // if this is already running, the action needs to be started right away! if (isRunning()) { - setProgress(current_progress, total_progress); - connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); - + setProgress(num_finished, javacheckers.size()); + connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); base->performCheck(); } return true; } - - JavaCheckerPtr operator[](int index) + QList<JavaCheckResult> getResults() { - return javacheckers[index]; + return javaresults; } - ; - JavaCheckerPtr first() - { - if (javacheckers.size()) - return javacheckers[0]; - return JavaCheckerPtr(); - } - int size() const - { - return javacheckers.size(); - } - virtual bool isRunning() const override - { - return m_running; - } - -signals: - void started(); - void finished(QList<JavaCheckResult>); private slots: void partFinished(JavaCheckResult result); @@ -77,8 +56,5 @@ private: QString m_job_name; QList<JavaCheckerPtr> javacheckers; QList<JavaCheckResult> javaresults; - qint64 current_progress = 0; - qint64 total_progress = 0; int num_finished = 0; - bool m_running = false; }; diff --git a/api/logic/java/JavaInstallList.cpp b/api/logic/java/JavaInstallList.cpp index 44ac861d..9d2e2f8b 100644 --- a/api/logic/java/JavaInstallList.cpp +++ b/api/logic/java/JavaInstallList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -150,7 +150,7 @@ void JavaListLoadTask::executeTask() QList<QString> candidate_paths = ju.FindJavaPaths(); m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection")); - connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>))); + connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::progress, this, &Task::setProgress); qDebug() << "Probing the following Java paths: "; @@ -170,9 +170,10 @@ void JavaListLoadTask::executeTask() m_job->start(); } -void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results) +void JavaListLoadTask::javaCheckerFinished() { QList<JavaInstallPtr> candidates; + auto results = m_job->getResults(); qDebug() << "Found the following valid Java installations:"; for(JavaCheckResult result : results) diff --git a/api/logic/java/JavaInstallList.h b/api/logic/java/JavaInstallList.h index 934e588b..39f37b80 100644 --- a/api/logic/java/JavaInstallList.h +++ b/api/logic/java/JavaInstallList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ public: void executeTask() override; public slots: - void javaCheckerFinished(QList<JavaCheckResult> results); + void javaCheckerFinished(); protected: std::shared_ptr<JavaCheckerJob> m_job; diff --git a/api/logic/java/JavaUtils.cpp b/api/logic/java/JavaUtils.cpp index 0c2e72d7..4a77bc7e 100644 --- a/api/logic/java/JavaUtils.cpp +++ b/api/logic/java/JavaUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,105 @@ #include <QDebug> #include "java/JavaUtils.h" -#include "java/JavaCheckerJob.h" #include "java/JavaInstallList.h" #include "FileSystem.h" +#define IBUS "@im=ibus" + JavaUtils::JavaUtils() { } +static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH) +{ + QDir mmcBin(QCoreApplication::applicationDirPath()); + auto items = LD_LIBRARY_PATH.split(':'); + QStringList final; + for(auto & item: items) + { + QDir test(item); + if(test == mmcBin) + { + qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; + continue; + } + final.append(item); + } + return final.join(':'); +} + +QProcessEnvironment CleanEnviroment() +{ + // prepare the process environment + QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); + QProcessEnvironment env; + + QStringList ignored = + { + "JAVA_ARGS", + "CLASSPATH", + "CONFIGPATH", + "JAVA_HOME", + "JRE_HOME", + "_JAVA_OPTIONS", + "JAVA_OPTIONS", + "JAVA_TOOL_OPTIONS" + }; + for(auto key: rawenv.keys()) + { + auto value = rawenv.value(key); + // filter out dangerous java crap + if(ignored.contains(key)) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // filter MultiMC-related things + if(key.startsWith("QT_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } +#ifdef Q_OS_LINUX + // Do not pass LD_* variables to java. They were intended for MultiMC + if(key.startsWith("LD_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // Strip IBus + // IBus is a Linux IME framework. For some reason, it breaks MC? + if (key == "XMODIFIERS" && value.contains(IBUS)) + { + QString save = value; + value.replace(IBUS, ""); + qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; + } + if(key == "GAME_PRELOAD") + { + env.insert("LD_PRELOAD", value); + continue; + } + if(key == "GAME_LIBRARY_PATH") + { + env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); + continue; + } +#endif + // qDebug() << "Env: " << key << value; + env.insert(key, value); + } +#ifdef Q_OS_LINUX + // HACK: Workaround for QTBUG42500 + if(!env.contains("LD_LIBRARY_PATH")) + { + env.insert("LD_LIBRARY_PATH", ""); + } +#endif + + return env; +} + JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch) { JavaInstallPtr javaVersion(new JavaInstall()); diff --git a/api/logic/java/JavaUtils.h b/api/logic/java/JavaUtils.h index 4418ac26..b43e93cf 100644 --- a/api/logic/java/JavaUtils.h +++ b/api/logic/java/JavaUtils.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ #include <QStringList> -#include "JavaCheckerJob.h" #include "JavaChecker.h" #include "JavaInstallList.h" @@ -27,6 +26,8 @@ #include "multimc_logic_export.h" +QProcessEnvironment CleanEnviroment(); + class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject { Q_OBJECT diff --git a/api/logic/java/JavaVersion.cpp b/api/logic/java/JavaVersion.cpp index 8c1bb430..27050da3 100644 --- a/api/logic/java/JavaVersion.cpp +++ b/api/logic/java/JavaVersion.cpp @@ -60,9 +60,18 @@ bool JavaVersion::operator<(const JavaVersion &rhs) { if(m_parseable && rhs.m_parseable) { - if(m_major < rhs.m_major) + auto major = m_major; + auto rmajor = rhs.m_major; + + // HACK: discourage using java 9 + if(major > 8) + major = -major; + if(rmajor > 8) + rmajor = -rmajor; + + if(major < rmajor) return true; - if(m_major > rhs.m_major) + if(major > rmajor) return false; if(m_minor < rhs.m_minor) return true; diff --git a/api/logic/java/launch/CheckJava.cpp b/api/logic/java/launch/CheckJava.cpp index f78e1cff..24f26682 100644 --- a/api/logic/java/launch/CheckJava.cpp +++ b/api/logic/java/launch/CheckJava.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/java/launch/CheckJava.h b/api/logic/java/launch/CheckJava.h index 3c812277..82508cd4 100644 --- a/api/logic/java/launch/CheckJava.h +++ b/api/logic/java/launch/CheckJava.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/launch/LaunchStep.cpp b/api/logic/launch/LaunchStep.cpp index 7b6ffc23..01f72a0a 100644 --- a/api/logic/launch/LaunchStep.cpp +++ b/api/logic/launch/LaunchStep.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/launch/LaunchStep.h b/api/logic/launch/LaunchStep.h index d645bd6c..80778e34 100644 --- a/api/logic/launch/LaunchStep.h +++ b/api/logic/launch/LaunchStep.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/launch/LaunchTask.cpp b/api/logic/launch/LaunchTask.cpp index 23c28f50..99c16721 100644 --- a/api/logic/launch/LaunchTask.cpp +++ b/api/logic/launch/LaunchTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Authors: Orochimarufan <orochimarufan.x3@gmail.com> * @@ -83,7 +83,7 @@ void LaunchTask::onStepFinished() } auto step = m_steps[currentStep]; - if(step->successful()) + if(step->wasSuccessful()) { // end? if(currentStep == m_steps.size() - 1) diff --git a/api/logic/launch/LaunchTask.h b/api/logic/launch/LaunchTask.h index 2d95f98f..746d6d19 100644 --- a/api/logic/launch/LaunchTask.h +++ b/api/logic/launch/LaunchTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Authors: Orochimarufan <orochimarufan.x3@gmail.com> * diff --git a/api/logic/launch/LogModel.cpp b/api/logic/launch/LogModel.cpp index 042feeab..72b076e9 100644 --- a/api/logic/launch/LogModel.cpp +++ b/api/logic/launch/LogModel.cpp @@ -69,6 +69,11 @@ void LogModel::suspend(bool suspend) m_suspended = suspend; } +bool LogModel::suspended() +{ + return m_suspended; +} + void LogModel::clear() { beginResetModel(); @@ -147,3 +152,16 @@ void LogModel::setOverflowMessage(const QString& overflowMessage) { m_overflowMessage = overflowMessage; } + +void LogModel::setLineWrap(bool state) +{ + if(m_lineWrap != state) + { + m_lineWrap = state; + } +} + +bool LogModel::wrapLines() const +{ + return m_lineWrap; +} diff --git a/api/logic/launch/LogModel.h b/api/logic/launch/LogModel.h index 57cd23b0..e6deac89 100644 --- a/api/logic/launch/LogModel.h +++ b/api/logic/launch/LogModel.h @@ -17,7 +17,9 @@ public: void append(MessageLevel::Enum, QString line); void clear(); + void suspend(bool suspend); + bool suspended(); QString toPlainText(); @@ -26,6 +28,9 @@ public: void setStopOnOverflow(bool stop); void setOverflowMessage(const QString & overflowMessage); + void setLineWrap(bool state); + bool wrapLines() const; + enum Roles { LevelRole = Qt::UserRole @@ -48,6 +53,7 @@ private: /* data */ bool m_stopOnOverflow = false; QString m_overflowMessage = "OVERFLOW"; bool m_suspended = false; + bool m_lineWrap = true; private: Q_DISABLE_COPY(LogModel) diff --git a/api/logic/launch/steps/PostLaunchCommand.cpp b/api/logic/launch/steps/PostLaunchCommand.cpp index 6c7c963c..bc3b3ffe 100644 --- a/api/logic/launch/steps/PostLaunchCommand.cpp +++ b/api/logic/launch/steps/PostLaunchCommand.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/launch/steps/PostLaunchCommand.h b/api/logic/launch/steps/PostLaunchCommand.h index 432867eb..36b3b96f 100644 --- a/api/logic/launch/steps/PostLaunchCommand.h +++ b/api/logic/launch/steps/PostLaunchCommand.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/launch/steps/PreLaunchCommand.cpp b/api/logic/launch/steps/PreLaunchCommand.cpp index 222800a7..7c37d5cb 100644 --- a/api/logic/launch/steps/PreLaunchCommand.cpp +++ b/api/logic/launch/steps/PreLaunchCommand.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/launch/steps/PreLaunchCommand.h b/api/logic/launch/steps/PreLaunchCommand.h index f57d2e2f..880cc076 100644 --- a/api/logic/launch/steps/PreLaunchCommand.h +++ b/api/logic/launch/steps/PreLaunchCommand.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/launch/steps/TextPrint.h b/api/logic/launch/steps/TextPrint.h index 63d01412..3e3e06cb 100644 --- a/api/logic/launch/steps/TextPrint.h +++ b/api/logic/launch/steps/TextPrint.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/launch/steps/Update.cpp b/api/logic/launch/steps/Update.cpp index 956230f4..d057febb 100644 --- a/api/logic/launch/steps/Update.cpp +++ b/api/logic/launch/steps/Update.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ void Update::executeTask() emitFailed(tr("Task aborted.")); return; } - m_updateTask.reset(m_parent->instance()->createUpdateTask()); + m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode)); if(m_updateTask) { connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); @@ -42,7 +42,7 @@ void Update::proceed() void Update::updateFinished() { - if(m_updateTask->successful()) + if(m_updateTask->wasSuccessful()) { m_updateTask.reset(); emitSucceeded(); diff --git a/api/logic/launch/steps/Update.h b/api/logic/launch/steps/Update.h index d855a1db..7a14011a 100644 --- a/api/logic/launch/steps/Update.h +++ b/api/logic/launch/steps/Update.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,14 @@ #include <QObjectPtr.h> #include <LoggedProcess.h> #include <java/JavaChecker.h> +#include <net/Mode.h> // FIXME: stupid. should be defined by the instance type? or even completely abstracted away... class Update: public LaunchStep { Q_OBJECT public: - explicit Update(LaunchTask *parent):LaunchStep(parent) {}; + explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {}; virtual ~Update() {}; void executeTask() override; @@ -40,4 +41,5 @@ private slots: private: shared_qobject_ptr<Task> m_updateTask; bool m_aborted = false; + Net::Mode m_mode = Net::Mode::Offline; }; diff --git a/api/logic/meta/BaseEntity.cpp b/api/logic/meta/BaseEntity.cpp index 439256b5..5c2339cb 100644 --- a/api/logic/meta/BaseEntity.cpp +++ b/api/logic/meta/BaseEntity.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,7 +74,7 @@ Meta::BaseEntity::~BaseEntity() QUrl Meta::BaseEntity::url() const { - return QUrl("https://meta.multimc.org").resolved(localFilename()); + return QUrl("https://v1.meta.multimc.org").resolved(localFilename()); } bool Meta::BaseEntity::loadLocalFile() @@ -99,7 +99,7 @@ bool Meta::BaseEntity::loadLocalFile() } } -void Meta::BaseEntity::load() +void Meta::BaseEntity::load(Net::Mode loadType) { // load local file if nothing is loaded yet if(!isLoaded()) @@ -110,7 +110,7 @@ void Meta::BaseEntity::load() } } // if we need remote update, run the update task - if(!shouldStartRemoteUpdate()) + if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) { return; } @@ -160,5 +160,3 @@ shared_qobject_ptr<Task> Meta::BaseEntity::getCurrentTask() } return nullptr; } - -#include "BaseEntity.moc" diff --git a/api/logic/meta/BaseEntity.h b/api/logic/meta/BaseEntity.h index 4483beab..418c979f 100644 --- a/api/logic/meta/BaseEntity.h +++ b/api/logic/meta/BaseEntity.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ #include "QObjectPtr.h" #include "multimc_logic_export.h" +#include "net/Mode.h" class Task; namespace Meta @@ -45,7 +46,6 @@ public: /* types */ public: virtual ~BaseEntity(); - virtual void merge(const std::shared_ptr<BaseEntity> &other) = 0; virtual void parse(const QJsonObject &obj) = 0; virtual QString localFilename() const = 0; @@ -54,7 +54,7 @@ public: bool isLoaded() const; bool shouldStartRemoteUpdate() const; - void load(); + void load(Net::Mode loadType); shared_qobject_ptr<Task> getCurrentTask(); protected: /* methods */ diff --git a/api/logic/meta/Index.cpp b/api/logic/meta/Index.cpp index 0749651a..6e1e34cd 100644 --- a/api/logic/meta/Index.cpp +++ b/api/logic/meta/Index.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,7 +103,7 @@ void Index::parse(const QJsonObject& obj) parseIndex(obj, this); } -void Index::merge(const Ptr &other) +void Index::merge(const std::shared_ptr<Index> &other) { const QVector<VersionListPtr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists; // initial load, no need to merge @@ -124,7 +124,7 @@ void Index::merge(const Ptr &other) { if (m_uids.contains(list->uid())) { - m_uids[list->uid()]->merge(list); + m_uids[list->uid()]->mergeFromIndex(list); } else { diff --git a/api/logic/meta/Index.h b/api/logic/meta/Index.h index 9811e152..0ec43f96 100644 --- a/api/logic/meta/Index.h +++ b/api/logic/meta/Index.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,7 @@ public: QVector<VersionListPtr> lists() const { return m_lists; } public: // for usage by parsers only - void merge(const BaseEntity::Ptr &other) override; + void merge(const std::shared_ptr<Index> &other); void parse(const QJsonObject &obj) override; private: diff --git a/api/logic/meta/JsonFormat.cpp b/api/logic/meta/JsonFormat.cpp index fb78941f..2183e579 100644 --- a/api/logic/meta/JsonFormat.cpp +++ b/api/logic/meta/JsonFormat.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ #include "JsonFormat.h" // FIXME: remove this from here... somehow -#include "minecraft/onesix/OneSixVersionFormat.h" +#include "minecraft/OneSixVersionFormat.h" #include "Json.h" #include "Index.h" @@ -28,8 +28,13 @@ using namespace Json; namespace Meta { +MetadataVersion currentFormatVersion() +{ + return MetadataVersion::InitialRelease; +} + // Index -static BaseEntity::Ptr parseIndexInternal(const QJsonObject &obj) +static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj) { const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages"); QVector<VersionListPtr> lists; @@ -49,24 +54,16 @@ static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj) VersionPtr version = std::make_shared<Version>(uid, requireString(obj, "version")); version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000); version->setType(ensureString(obj, "type", QString())); - version->setParentUid(ensureString(obj, "parentUid", QString())); version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); - if(obj.contains("requires")) - { - QHash<QString, QString> requires; - auto reqobj = requireObject(obj, "requires"); - auto iter = reqobj.begin(); - while(iter != reqobj.end()) - { - requires[iter.key()] = requireString(iter.value()); - iter++; - } - version->setRequires(requires); - } + version->setVolatile(ensureBoolean(obj, QString("volatile"), false)); + RequireSet requires, conflicts; + parseRequires(obj, &requires, "requires"); + parseRequires(obj, &conflicts, "conflicts"); + version->setRequires(requires, conflicts); return version; } -static BaseEntity::Ptr parseVersionInternal(const QJsonObject &obj) +static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj) { VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj); @@ -77,7 +74,7 @@ static BaseEntity::Ptr parseVersionInternal(const QJsonObject &obj) } // Version list / package -static BaseEntity::Ptr parseVersionListInternal(const QJsonObject &obj) +static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject &obj) { const QString uid = requireString(obj, "uid"); @@ -93,56 +90,129 @@ static BaseEntity::Ptr parseVersionListInternal(const QJsonObject &obj) VersionListPtr list = std::make_shared<VersionList>(uid); list->setName(ensureString(obj, "name", QString())); - list->setParentUid(ensureString(obj, "parentUid", QString())); list->setVersions(versions); return list; } -static int formatVersion(const QJsonObject &obj) +MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required) { - if (!obj.contains("formatVersion")) { - throw ParseException(QObject::tr("Missing required field: 'formatVersion'")); + if (!obj.contains("formatVersion")) + { + if(required) + { + return MetadataVersion::Invalid; + } + return MetadataVersion::InitialRelease; } - if (!obj.value("formatVersion").isDouble()) { - throw ParseException(QObject::tr("Required field has invalid type: 'formatVersion'")); + if (!obj.value("formatVersion").isDouble()) + { + return MetadataVersion::Invalid; + } + switch(obj.value("formatVersion").toInt()) + { + case 0: + case 1: + return MetadataVersion::InitialRelease; + default: + return MetadataVersion::Invalid; + } +} + +void serializeFormatVersion(QJsonObject& obj, Meta::MetadataVersion version) +{ + if(version == MetadataVersion::Invalid) + { + return; } - return obj.value("formatVersion").toInt(); + obj.insert("formatVersion", int(version)); } void parseIndex(const QJsonObject &obj, Index *ptr) { - const int version = formatVersion(obj); - switch (version) { - case 0: + const MetadataVersion version = parseFormatVersion(obj); + switch (version) + { + case MetadataVersion::InitialRelease: ptr->merge(parseIndexInternal(obj)); break; - default: - throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version)); + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); } } void parseVersionList(const QJsonObject &obj, VersionList *ptr) { - const int version = formatVersion(obj); - switch (version) { - case 0: + const MetadataVersion version = parseFormatVersion(obj); + switch (version) + { + case MetadataVersion::InitialRelease: ptr->merge(parseVersionListInternal(obj)); break; - default: - throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version)); + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); } } void parseVersion(const QJsonObject &obj, Version *ptr) { - const int version = formatVersion(obj); - switch (version) { - case 0: + const MetadataVersion version = parseFormatVersion(obj); + switch (version) + { + case MetadataVersion::InitialRelease: ptr->merge(parseVersionInternal(obj)); break; - default: - throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version)); + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); } } + +/* +[ +{"uid":"foo", "equals":"version"} +] +*/ +void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName) +{ + if(obj.contains(keyName)) + { + QSet<QString> requires; + auto reqArray = requireArray(obj, keyName); + auto iter = reqArray.begin(); + while(iter != reqArray.end()) + { + auto reqObject = requireObject(*iter); + auto uid = requireString(reqObject, "uid"); + auto equals = ensureString(reqObject, "equals", QString()); + auto suggests = ensureString(reqObject, "suggests", QString()); + ptr->insert({uid, equals, suggests}); + iter++; + } + } } +void serializeRequires(QJsonObject& obj, RequireSet* ptr, const char * keyName) +{ + if(!ptr || ptr->empty()) + { + return; + } + QJsonArray arrOut; + for(auto &iter: *ptr) + { + QJsonObject reqOut; + reqOut.insert("uid", iter.uid); + if(!iter.equalsVersion.isEmpty()) + { + reqOut.insert("equals", iter.equalsVersion); + } + if(!iter.suggests.isEmpty()) + { + reqOut.insert("suggests", iter.suggests); + } + arrOut.append(reqOut); + } + obj.insert(keyName, arrOut); +} + +} + diff --git a/api/logic/meta/JsonFormat.h b/api/logic/meta/JsonFormat.h index aaed07fc..762a36f6 100644 --- a/api/logic/meta/JsonFormat.h +++ b/api/logic/meta/JsonFormat.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ #include "Exception.h" #include "meta/BaseEntity.h" +#include <set> namespace Meta { @@ -27,14 +28,56 @@ class Index; class Version; class VersionList; +enum class MetadataVersion +{ + Invalid = -1, + InitialRelease = 1 +}; + class ParseException : public Exception { public: using Exception::Exception; }; +struct Require +{ + bool operator==(const Require & rhs) const + { + return uid == rhs.uid; + } + bool operator<(const Require & rhs) const + { + return uid < rhs.uid; + } + bool deepEquals(const Require & rhs) const + { + return uid == rhs.uid + && equalsVersion == rhs.equalsVersion + && suggests == rhs.suggests; + } + QString uid; + QString equalsVersion; + QString suggests; +}; + +inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW +{ + return qHash(key.uid, seed); +} + +using RequireSet = std::set<Require>; void parseIndex(const QJsonObject &obj, Index *ptr); void parseVersion(const QJsonObject &obj, Version *ptr); void parseVersionList(const QJsonObject &obj, VersionList *ptr); +MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required = true); +void serializeFormatVersion(QJsonObject &obj, MetadataVersion version); + +// FIXME: this has a different shape than the others...FIX IT!? +void parseRequires(const QJsonObject &obj, RequireSet * ptr, const char * keyName = "requires"); +void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyName = "requires"); +MetadataVersion currentFormatVersion(); } + +Q_DECLARE_METATYPE(std::set<Meta::Require>);
\ No newline at end of file diff --git a/api/logic/meta/Version.cpp b/api/logic/meta/Version.cpp index b00a29e7..edc70f33 100644 --- a/api/logic/meta/Version.cpp +++ b/api/logic/meta/Version.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ #include <QDateTime> #include "JsonFormat.h" -#include "minecraft/MinecraftProfile.h" +#include "minecraft/ComponentList.h" Meta::Version::Version(const QString &uid, const QString &version) : BaseVersion(), m_uid(uid), m_version(version) @@ -54,47 +54,49 @@ void Meta::Version::parse(const QJsonObject& obj) parseVersion(obj, this); } -void Meta::Version::merge(const std::shared_ptr<BaseEntity> &other) +void Meta::Version::mergeFromList(const Meta::VersionPtr& other) { - VersionPtr version = std::dynamic_pointer_cast<Version>(other); - if(version->m_providesRecommendations) + if(other->m_providesRecommendations) { - if(m_recommended != version->m_recommended) + if(m_recommended != other->m_recommended) { - setRecommended(version->m_recommended); + setRecommended(other->m_recommended); } } - if (m_type != version->m_type) + if (m_type != other->m_type) { - setType(version->m_type); + setType(other->m_type); } - if (m_time != version->m_time) + if (m_time != other->m_time) { - setTime(version->m_time); + setTime(other->m_time); } - if (m_requires != version->m_requires) + if (m_requires != other->m_requires) { - setRequires(version->m_requires); + m_requires = other->m_requires; } - if (m_parentUid != version->m_parentUid) + if (m_conflicts != other->m_conflicts) { - setParentUid(version->m_parentUid); + m_conflicts = other->m_conflicts; } - if(version->m_data) + if(m_volatile != other->m_volatile) { - setData(version->m_data); + setVolatile(other->m_volatile); } } -QString Meta::Version::localFilename() const +void Meta::Version::merge(const VersionPtr &other) { - return m_uid + '/' + m_version + ".json"; + mergeFromList(other); + if(other->m_data) + { + setData(other->m_data); + } } -void Meta::Version::setParentUid(const QString& parentUid) +QString Meta::Version::localFilename() const { - m_parentUid = parentUid; - emit requiresChanged(); + return m_uid + '/' + m_version + ".json"; } void Meta::Version::setType(const QString &type) @@ -109,12 +111,19 @@ void Meta::Version::setTime(const qint64 time) emit timeChanged(); } -void Meta::Version::setRequires(const QHash<QString, QString> &requires) +void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts) { m_requires = requires; + m_conflicts = conflicts; emit requiresChanged(); } +void Meta::Version::setVolatile(bool volatile_) +{ + m_volatile = volatile_; +} + + void Meta::Version::setData(const VersionFilePtr &data) { m_data = data; diff --git a/api/logic/meta/Version.h b/api/logic/meta/Version.h index 2f92ee9f..33bd5b35 100644 --- a/api/logic/meta/Version.h +++ b/api/logic/meta/Version.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ #include "multimc_logic_export.h" +#include "JsonFormat.h" + namespace Meta { using VersionPtr = std::shared_ptr<class Version>; @@ -48,10 +50,6 @@ public: /* con/des */ { return m_uid; } - QString parentUid() const - { - return m_parentUid; - } QString version() const { return m_version; @@ -65,7 +63,7 @@ public: /* con/des */ { return m_time; } - const QHash<QString, QString> &requires() const + const Meta::RequireSet &requires() const { return m_requires; } @@ -77,17 +75,22 @@ public: /* con/des */ { return m_recommended; } + bool isLoaded() const + { + return m_data != nullptr; + } - void merge(const std::shared_ptr<BaseEntity> &other) override; + void merge(const VersionPtr &other); + void mergeFromList(const VersionPtr &other); void parse(const QJsonObject &obj) override; QString localFilename() const override; public: // for usage by format parsers only - void setParentUid(const QString &parentUid); void setType(const QString &type); void setTime(const qint64 time); - void setRequires(const QHash<QString, QString> &requires); + void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts); + void setVolatile(bool volatile_); void setRecommended(bool recommended); void setProvidesRecommendations(); void setData(const VersionFilePtr &data); @@ -102,11 +105,12 @@ private: bool m_recommended = false; QString m_name; QString m_uid; - QString m_parentUid; QString m_version; QString m_type; qint64 m_time = 0; - QHash<QString, QString> m_requires; + Meta::RequireSet m_requires; + Meta::RequireSet m_conflicts; + bool m_volatile = false; VersionFilePtr m_data; }; } diff --git a/api/logic/meta/VersionList.cpp b/api/logic/meta/VersionList.cpp index 44687d3c..9ae02301 100644 --- a/api/logic/meta/VersionList.cpp +++ b/api/logic/meta/VersionList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ #include "Version.h" #include "JsonFormat.h" +#include "Version.h" namespace Meta { @@ -30,7 +31,7 @@ VersionList::VersionList(const QString &uid, QObject *parent) shared_qobject_ptr<Task> VersionList::getLoadTask() { - load(); + load(Net::Mode::Online); return getCurrentTask(); } @@ -75,17 +76,17 @@ QVariant VersionList::data(const QModelIndex &index, int role) const return version->version(); case ParentVersionRole: { - auto parentUid = this->parentUid(); - if(parentUid.isEmpty()) - { - return QVariant(); - } + // FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'. auto & reqs = version->requires(); - auto iter = reqs.find(parentUid); + auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req) + { + return req.uid == "net.minecraft"; + }); if (iter != reqs.end()) { - return iter.value(); + return (*iter).equalsVersion; } + return QVariant(); } case TypeRole: return version->type(); @@ -159,6 +160,7 @@ void VersionList::setVersions(const QVector<VersionPtr> &versions) setupAddedVersion(i, m_versions.at(i)); } + // FIXME: this is dumb, we have 'recommended' as part of the metadata already... auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; }); m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt; endResetModel(); @@ -169,28 +171,50 @@ void VersionList::parse(const QJsonObject& obj) parseVersionList(obj, this); } -void VersionList::merge(const BaseEntity::Ptr &other) +// FIXME: this is dumb, we have 'recommended' as part of the metadata already... +static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b) { - const VersionListPtr list = std::dynamic_pointer_cast<VersionList>(other); - if (m_name != list->m_name) + if(!a) + return b; + if(!b) + return a; + if(a->type() == b->type()) { - setName(list->m_name); + // newer of same type wins + return (a->rawTime() > b->rawTime() ? a : b); } + // 'release' type wins + return (a->type() == "release" ? a : b); +} - if(m_parentUid != list->m_parentUid) +void VersionList::mergeFromIndex(const VersionListPtr &other) +{ + if (m_name != other->m_name) { - setParentUid(list->m_parentUid); + setName(other->m_name); + } +} + +void VersionList::merge(const VersionListPtr &other) +{ + if (m_name != other->m_name) + { + setName(other->m_name); } // TODO: do not reset the whole model. maybe? beginResetModel(); m_versions.clear(); - for (const VersionPtr &version : list->m_versions) + if(other->m_versions.isEmpty()) + { + qWarning() << "Empty list loaded ..."; + } + for (const VersionPtr &version : other->m_versions) { // we already have the version. merge the contents if (m_lookup.contains(version->version())) { - m_lookup.value(version->version())->merge(version); + m_lookup.value(version->version())->mergeFromList(version); } else { @@ -199,10 +223,7 @@ void VersionList::merge(const BaseEntity::Ptr &other) // connect it. setupAddedVersion(m_versions.size(), version); m_versions.append(version); - if (!m_recommended || (version->type() == "release" && version->rawTime() > m_recommended->rawTime())) - { - m_recommended = version; - } + m_recommended = getBetterVersion(m_recommended, version); } endResetModel(); } @@ -222,11 +243,3 @@ BaseVersionPtr VersionList::getRecommended() const } } - -void Meta::VersionList::setParentUid(const QString& parentUid) -{ - m_parentUid = parentUid; -} - - -#include "VersionList.moc" diff --git a/api/logic/meta/VersionList.h b/api/logic/meta/VersionList.h index e8016314..ad8733cc 100644 --- a/api/logic/meta/VersionList.h +++ b/api/logic/meta/VersionList.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,10 +55,6 @@ public: QString localFilename() const override; - QString parentUid() const - { - return m_parentUid; - } QString uid() const { return m_uid; @@ -78,9 +74,9 @@ public: public: // for usage only by parsers void setName(const QString &name); - void setParentUid(const QString &parentUid); void setVersions(const QVector<VersionPtr> &versions); - void merge(const BaseEntity::Ptr &other) override; + void merge(const VersionListPtr &other); + void mergeFromIndex(const VersionListPtr &other); void parse(const QJsonObject &obj) override; signals: @@ -95,7 +91,6 @@ private: QVector<VersionPtr> m_versions; QHash<QString, VersionPtr> m_lookup; QString m_uid; - QString m_parentUid; QString m_name; VersionPtr m_recommended; diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp index 5191e5bd..5b25bede 100644 --- a/api/logic/minecraft/AssetsUtils.cpp +++ b/api/logic/minecraft/AssetsUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h index 34b49e7f..b7ea9cc1 100644 --- a/api/logic/minecraft/AssetsUtils.h +++ b/api/logic/minecraft/AssetsUtils.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/Component.cpp b/api/logic/minecraft/Component.cpp new file mode 100644 index 00000000..50a2ae16 --- /dev/null +++ b/api/logic/minecraft/Component.cpp @@ -0,0 +1,439 @@ +#include <meta/VersionList.h> +#include <meta/Index.h> +#include <Env.h> +#include "Component.h" + +#include "meta/Version.h" +#include "VersionFile.h" +#include "minecraft/ComponentList.h" +#include <FileSystem.h> +#include <QSaveFile> +#include "OneSixVersionFormat.h" +#include <assert.h> + +Component::Component(ComponentList * parent, const QString& uid) +{ + assert(parent); + m_parent = parent; + + m_uid = uid; +} + +Component::Component(ComponentList * parent, std::shared_ptr<Meta::Version> version) +{ + assert(parent); + m_parent = parent; + + m_metaVersion = version; + m_uid = version->uid(); + m_version = m_cachedVersion = version->version(); + m_cachedName = version->name(); + m_loaded = version->isLoaded(); +} + +Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr<VersionFile> file) +{ + assert(parent); + m_parent = parent; + + m_file = file; + m_uid = uid; + m_cachedVersion = m_file->version; + m_cachedName = m_file->name; + m_loaded = true; +} + +std::shared_ptr<Meta::Version> Component::getMeta() +{ + return m_metaVersion; +} + +void Component::applyTo(LaunchProfile* profile) +{ + // do not apply disabled components + if(!isEnabled()) + { + return; + } + auto vfile = getVersionFile(); + if(vfile) + { + vfile->applyTo(profile); + } + else + { + profile->applyProblemSeverity(getProblemSeverity()); + } +} + +std::shared_ptr<class VersionFile> Component::getVersionFile() const +{ + if(m_metaVersion) + { + if(!m_metaVersion->isLoaded()) + { + m_metaVersion->load(Net::Mode::Online); + } + return m_metaVersion->data(); + } + else + { + return m_file; + } +} + +std::shared_ptr<class Meta::VersionList> Component::getVersionList() const +{ + // FIXME: what if the metadata index isn't loaded yet? + if(ENV.metadataIndex()->hasUid(m_uid)) + { + return ENV.metadataIndex()->get(m_uid); + } + return nullptr; +} + +int Component::getOrder() +{ + if(m_orderOverride) + return m_order; + + auto vfile = getVersionFile(); + if(vfile) + { + return vfile->order; + } + return 0; +} +void Component::setOrder(int order) +{ + m_orderOverride = true; + m_order = order; +} +QString Component::getID() +{ + return m_uid; +} +QString Component::getName() +{ + if (!m_cachedName.isEmpty()) + return m_cachedName; + return m_uid; +} +QString Component::getVersion() +{ + return m_cachedVersion; +} +QString Component::getFilename() +{ + return m_parent->patchFilePathForUid(m_uid); +} +QDateTime Component::getReleaseDateTime() +{ + if(m_metaVersion) + { + return m_metaVersion->time(); + } + auto vfile = getVersionFile(); + if(vfile) + { + return vfile->releaseTime; + } + // FIXME: fake + return QDateTime::currentDateTime(); +} + +bool Component::isEnabled() +{ + return !canBeDisabled() || !m_disabled; +}; + +bool Component::canBeDisabled() +{ + return isRemovable() && !m_dependencyOnly; +} + +bool Component::setEnabled(bool state) +{ + bool intendedDisabled = !state; + if (!canBeDisabled()) + { + intendedDisabled = false; + } + if(intendedDisabled != m_disabled) + { + m_disabled = intendedDisabled; + emit dataChanged(); + return true; + } + return false; +} + +bool Component::isCustom() +{ + return m_file != nullptr; +}; + +bool Component::isCustomizable() +{ + if(m_metaVersion) + { + if(getVersionFile()) + { + return true; + } + } + return false; +} +bool Component::isRemovable() +{ + return !m_important; +} +bool Component::isRevertible() +{ + if (isCustom()) + { + if(ENV.metadataIndex()->hasUid(m_uid)) + { + return true; + } + } + return false; +} +bool Component::isMoveable() +{ + // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'. + return true; +} +bool Component::isVersionChangeable() +{ + auto list = getVersionList(); + if(list) + { + if(!list->isLoaded()) + { + list->load(Net::Mode::Online); + } + return list->count() != 0; + } + return false; +} + +void Component::setImportant(bool state) +{ + if(m_important != state) + { + m_important = state; + emit dataChanged(); + } +} + +ProblemSeverity Component::getProblemSeverity() const +{ + auto file = getVersionFile(); + if(file) + { + return file->getProblemSeverity(); + } + return ProblemSeverity::Error; +} + +const QList<PatchProblem> Component::getProblems() const +{ + auto file = getVersionFile(); + if(file) + { + return file->getProblems(); + } + return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}}; +} + +void Component::setVersion(const QString& version) +{ + if(version == m_version) + { + return; + } + m_version = version; + if(m_loaded) + { + // we are loaded and potentially have state to invalidate + if(m_file) + { + // we have a file... explicit version has been changed and there is nothing else to do. + } + else + { + // we don't have a file, therefore we are loaded with metadata + m_cachedVersion = version; + // see if the meta version is loaded + auto metaVersion = ENV.metadataIndex()->get(m_uid, version); + if(metaVersion->isLoaded()) + { + // if yes, we can continue with that. + m_metaVersion = metaVersion; + } + else + { + // if not, we need loading + m_metaVersion.reset(); + m_loaded = false; + } + updateCachedData(); + } + } + else + { + // not loaded... assume it will be sorted out later by the update task + } + emit dataChanged(); +} + +bool Component::customize() +{ + if(isCustom()) + { + return false; + } + + auto filename = getFilename(); + if(!FS::ensureFilePathExists(filename)) + { + return false; + } + // FIXME: get rid of this try-catch. + try + { + QSaveFile jsonFile(filename); + if(!jsonFile.open(QIODevice::WriteOnly)) + { + return false; + } + auto vfile = getVersionFile(); + if(!vfile) + { + return false; + } + auto document = OneSixVersionFormat::versionFileToJson(vfile); + jsonFile.write(document.toJson()); + if(!jsonFile.commit()) + { + return false; + } + m_file = vfile; + m_metaVersion.reset(); + emit dataChanged(); + } + catch (Exception &error) + { + qWarning() << "Version could not be loaded:" << error.cause(); + } + return true; +} + +bool Component::revert() +{ + if(!isCustom()) + { + // already not custom + return true; + } + auto filename = getFilename(); + bool result = true; + // just kill the file and reload + if(QFile::exists(filename)) + { + result = QFile::remove(filename); + } + if(result) + { + // file gone... + m_file.reset(); + + // check local cache for metadata... + auto version = ENV.metadataIndex()->get(m_uid, m_version); + if(version->isLoaded()) + { + m_metaVersion = version; + } + else + { + m_metaVersion.reset(); + m_loaded = false; + } + emit dataChanged(); + } + return result; +} + +/** + * deep inspecting compare for requirement sets + * By default, only uids are compared for set operations. + * This compares all fields of the Require structs in the sets. + */ +static bool deepCompare(const std::set<Meta::Require> & a, const std::set<Meta::Require> & b) +{ + // NOTE: this needs to be rewritten if the type of Meta::RequireSet changes + if(a.size() != b.size()) + { + return false; + } + for(const auto & reqA :a) + { + const auto &iter2 = b.find(reqA); + if(iter2 == b.cend()) + { + return false; + } + const auto & reqB = *iter2; + if(!reqA.deepEquals(reqB)) + { + return false; + } + } + return true; +} + +void Component::updateCachedData() +{ + auto file = getVersionFile(); + if(file) + { + bool changed = false; + if(m_cachedName != file->name) + { + m_cachedName = file->name; + changed = true; + } + if(m_cachedVersion != file->version) + { + m_cachedVersion = file->version; + changed = true; + } + if(m_cachedVolatile != file->m_volatile) + { + m_cachedVolatile = file->m_volatile; + changed = true; + } + if(!deepCompare(m_cachedRequires, file->requires)) + { + m_cachedRequires = file->requires; + changed = true; + } + if(!deepCompare(m_cachedConflicts, file->conflicts)) + { + m_cachedConflicts = file->conflicts; + changed = true; + } + if(changed) + { + emit dataChanged(); + } + } + else + { + // in case we removed all the metadata + m_cachedRequires.clear(); + m_cachedConflicts.clear(); + emit dataChanged(); + } +} diff --git a/api/logic/minecraft/Component.h b/api/logic/minecraft/Component.h new file mode 100644 index 00000000..778fbb18 --- /dev/null +++ b/api/logic/minecraft/Component.h @@ -0,0 +1,111 @@ +#pragma once + +#include <memory> +#include <QList> +#include <QJsonDocument> +#include <QDateTime> +#include "meta/JsonFormat.h" +#include "ProblemProvider.h" +#include "QObjectPtr.h" +#include "multimc_logic_export.h" + +class ComponentList; +class LaunchProfile; +namespace Meta +{ + class Version; + class VersionList; +} +class VersionFile; + +class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider +{ +Q_OBJECT +public: + Component(ComponentList * parent, const QString &uid); + + // DEPRECATED: remove these constructors? + Component(ComponentList * parent, std::shared_ptr<Meta::Version> version); + Component(ComponentList * parent, const QString & uid, std::shared_ptr<VersionFile> file); + + virtual ~Component(){}; + void applyTo(LaunchProfile *profile); + + bool isEnabled(); + bool setEnabled (bool state); + bool canBeDisabled(); + + bool isMoveable(); + bool isCustomizable(); + bool isRevertible(); + bool isRemovable(); + bool isCustom(); + bool isVersionChangeable(); + + // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code + void setOrder(int order); + int getOrder(); + + QString getID(); + QString getName(); + QString getVersion(); + std::shared_ptr<Meta::Version> getMeta(); + QDateTime getReleaseDateTime(); + + QString getFilename(); + + std::shared_ptr<class VersionFile> getVersionFile() const; + std::shared_ptr<class Meta::VersionList> getVersionList() const; + + void setImportant (bool state); + + + const QList<PatchProblem> getProblems() const override; + ProblemSeverity getProblemSeverity() const override; + + void setVersion(const QString & version); + bool customize(); + bool revert(); + + void updateCachedData(); + +signals: + void dataChanged(); + +public: /* data */ + ComponentList * m_parent; + + // BEGIN: persistent component list properties + /// ID of the component + QString m_uid; + /// version of the component - when there's a custom json override, this is also the version the component reverts to + QString m_version; + /// if true, this has been added automatically to satisfy dependencies and may be automatically removed + bool m_dependencyOnly = false; + /// if true, the component is either the main component of the instance, or otherwise important and cannot be removed. + bool m_important = false; + /// if true, the component is disabled + bool m_disabled = false; + + /// cached name for display purposes, taken from the version file (meta or local override) + QString m_cachedName; + /// cached version for display AND other purposes, taken from the version file (meta or local override) + QString m_cachedVersion; + /// cached set of requirements, taken from the version file (meta or local override) + Meta::RequireSet m_cachedRequires; + Meta::RequireSet m_cachedConflicts; + /// if true, the component is volatile and may be automatically removed when no longer needed + bool m_cachedVolatile = false; + // END: persistent component list properties + + // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code + bool m_orderOverride = false; + int m_order = 0; + + // load state + std::shared_ptr<Meta::Version> m_metaVersion; + std::shared_ptr<VersionFile> m_file; + bool m_loaded = false; +}; + +typedef shared_qobject_ptr<Component> ComponentPtr; diff --git a/api/logic/minecraft/ComponentList.cpp b/api/logic/minecraft/ComponentList.cpp new file mode 100644 index 00000000..a207e987 --- /dev/null +++ b/api/logic/minecraft/ComponentList.cpp @@ -0,0 +1,1204 @@ +/* Copyright 2013-2018 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 <QFile> +#include <QCryptographicHash> +#include <Version.h> +#include <QDir> +#include <QJsonDocument> +#include <QJsonArray> +#include <QDebug> + +#include "Exception.h" +#include <minecraft/OneSixVersionFormat.h> +#include <FileSystem.h> +#include <QSaveFile> +#include <Env.h> +#include <meta/Index.h> +#include <minecraft/MinecraftInstance.h> +#include <QUuid> +#include <QTimer> +#include <Json.h> + +#include "ComponentList.h" +#include "ComponentList_p.h" +#include "ComponentUpdateTask.h" + +ComponentList::ComponentList(MinecraftInstance * instance) + : QAbstractListModel() +{ + d.reset(new ComponentListData); + d->m_instance = instance; + d->m_saveTimer.setSingleShot(true); + d->m_saveTimer.setInterval(5000); + connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save_internal); +} + +ComponentList::~ComponentList() +{ + saveNow(); +} + +// BEGIN: component file format + +static const int currentComponentsFileVersion = 1; + +static QJsonObject componentToJsonV1(ComponentPtr component) +{ + QJsonObject obj; + // critical + obj.insert("uid", component->m_uid); + if(!component->m_version.isEmpty()) + { + obj.insert("version", component->m_version); + } + if(component->m_dependencyOnly) + { + obj.insert("dependencyOnly", true); + } + if(component->m_important) + { + obj.insert("important", true); + } + if(component->m_disabled) + { + obj.insert("disabled", true); + } + + // cached + if(!component->m_cachedVersion.isEmpty()) + { + obj.insert("cachedVersion", component->m_cachedVersion); + } + if(!component->m_cachedName.isEmpty()) + { + obj.insert("cachedName", component->m_cachedName); + } + Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires"); + Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); + if(component->m_cachedVolatile) + { + obj.insert("cachedVolatile", true); + } + return obj; +} + +static ComponentPtr componentFromJsonV1(ComponentList * parent, const QString & componentJsonPattern, const QJsonObject &obj) +{ + // critical + auto uid = Json::requireString(obj.value("uid")); + auto filePath = componentJsonPattern.arg(uid); + auto component = new Component(parent, uid); + component->m_version = Json::ensureString(obj.value("version")); + component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false); + component->m_important = Json::ensureBoolean(obj.value("important"), false); + + // cached + // TODO @RESILIENCE: ignore invalid values/structure here? + component->m_cachedVersion = Json::ensureString(obj.value("cachedVersion")); + component->m_cachedName = Json::ensureString(obj.value("cachedName")); + Meta::parseRequires(obj, &component->m_cachedRequires, "cachedRequires"); + Meta::parseRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); + component->m_cachedVolatile = Json::ensureBoolean(obj.value("volatile"), false); + bool disabled = Json::ensureBoolean(obj.value("disabled"), false); + component->setEnabled(!disabled); + return component; +} + +// Save the given component container data to a file +static bool saveComponentList(const QString & filename, const ComponentContainer & container) +{ + QJsonObject obj; + obj.insert("formatVersion", currentComponentsFileVersion); + QJsonArray orderArray; + for(auto component: container) + { + orderArray.append(componentToJsonV1(component)); + } + obj.insert("components", orderArray); + QSaveFile outFile(filename); + if (!outFile.open(QFile::WriteOnly)) + { + qCritical() << "Couldn't open" << outFile.fileName() + << "for writing:" << outFile.errorString(); + return false; + } + auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented); + if(outFile.write(data) != data.size()) + { + qCritical() << "Couldn't write all the data into" << outFile.fileName() + << "because:" << outFile.errorString(); + return false; + } + if(!outFile.commit()) + { + qCritical() << "Couldn't save" << outFile.fileName() + << "because:" << outFile.errorString(); + } + return true; +} + +// Read the given file into component containers +static bool loadComponentList(ComponentList * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container) +{ + QFile componentsFile(filename); + if (!componentsFile.exists()) + { + qWarning() << "Components file doesn't exist. This should never happen."; + return false; + } + if (!componentsFile.open(QFile::ReadOnly)) + { + qCritical() << "Couldn't open" << componentsFile.fileName() + << " for reading:" << componentsFile.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } + + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } + + // and then read it and process it if all above is true. + try + { + auto obj = Json::requireObject(doc); + // check order file version. + auto version = Json::requireInteger(obj.value("formatVersion")); + if (version != currentComponentsFileVersion) + { + throw JSONValidationError(QObject::tr("Invalid component file version, expected %1") + .arg(currentComponentsFileVersion)); + } + auto orderArray = Json::requireArray(obj.value("components")); + for(auto item: orderArray) + { + auto obj = Json::requireObject(item, "Component must be an object."); + container.append(componentFromJsonV1(parent, componentJsonPattern, obj)); + } + } + catch (JSONValidationError &err) + { + qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; + container.clear(); + return false; + } + return true; +} + +// END: component file format + +// BEGIN: save/load logic + +void ComponentList::saveNow() +{ + if(saveIsScheduled()) + { + d->m_saveTimer.stop(); + save_internal(); + } +} + +bool ComponentList::saveIsScheduled() const +{ + return d->dirty; +} + +void ComponentList::buildingFromScratch() +{ + d->loaded = true; + d->dirty = true; +} + +void ComponentList::scheduleSave() +{ + if(!d->loaded) + { + qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name(); + return; + } + if(!d->dirty) + { + d->dirty = true; + qDebug() << "Component list save is scheduled for" << d->m_instance->name(); + } + d->m_saveTimer.start(); +} + +QString ComponentList::componentsFilePath() const +{ + return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); +} + +QString ComponentList::patchesPattern() const +{ + return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json"); +} + +QString ComponentList::patchFilePathForUid(const QString& uid) const +{ + return patchesPattern().arg(uid); +} + +void ComponentList::save_internal() +{ + qDebug() << "Component list save performed now for" << d->m_instance->name(); + auto filename = componentsFilePath(); + saveComponentList(filename, d->components); + d->dirty = false; +} + +bool ComponentList::load() +{ + auto filename = componentsFilePath(); + QFile componentsFile(filename); + + // migrate old config to new one, if needed + if(!componentsFile.exists()) + { + if(!migratePreComponentConfig()) + { + // FIXME: the user should be notified... + qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name(); + return false; + } + } + + // load the new component list and swap it with the current one... + ComponentContainer newComponents; + if(!loadComponentList(this, filename, patchesPattern(), newComponents)) + { + qCritical() << "Failed to load the component config for instance" << d->m_instance->name(); + return false; + } + else + { + // FIXME: actually use fine-grained updates, not this... + beginResetModel(); + // disconnect all the old components + for(auto component: d->components) + { + disconnect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); + } + d->components.clear(); + d->componentIndex.clear(); + for(auto component: newComponents) + { + if(d->componentIndex.contains(component->m_uid)) + { + qWarning() << "Ignoring duplicate component entry" << component->m_uid; + continue; + } + connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); + d->components.append(component); + d->componentIndex[component->m_uid] = component; + } + endResetModel(); + d->loaded = true; + return true; + } +} + +void ComponentList::reload(Net::Mode netmode) +{ + // Do not reload when the update/resolve task is running. It is in control. + if(d->m_updateTask) + { + return; + } + + // flush any scheduled saves to not lose state + saveNow(); + + // FIXME: differentiate when a reapply is required by propagating state from components + invalidateLaunchProfile(); + + if(load()) + { + resolve(netmode); + } +} + +shared_qobject_ptr<Task> ComponentList::getCurrentTask() +{ + return d->m_updateTask; +} + +void ComponentList::resolve(Net::Mode netmode) +{ + auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this); + d->m_updateTask.reset(updateTask); + connect(updateTask, &ComponentUpdateTask::succeeded, this, &ComponentList::updateSucceeded); + connect(updateTask, &ComponentUpdateTask::failed, this, &ComponentList::updateFailed); + d->m_updateTask->start(); +} + + +void ComponentList::updateSucceeded() +{ + qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name(); + d->m_updateTask.reset(); + invalidateLaunchProfile(); +} + +void ComponentList::updateFailed(const QString& error) +{ + qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error; + d->m_updateTask.reset(); + invalidateLaunchProfile(); +} + +// NOTE this is really old stuff, and only needs to be used when loading the old hardcoded component-unaware format (loadPreComponentConfig). +static void upgradeDeprecatedFiles(QString root, QString instanceName) +{ + auto versionJsonPath = FS::PathCombine(root, "version.json"); + auto customJsonPath = FS::PathCombine(root, "custom.json"); + auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json"); + + QString sourceFile; + QString renameFile; + + // convert old crap. + if(QFile::exists(customJsonPath)) + { + sourceFile = customJsonPath; + renameFile = versionJsonPath; + } + else if(QFile::exists(versionJsonPath)) + { + sourceFile = versionJsonPath; + } + if(!sourceFile.isEmpty() && !QFile::exists(mcJson)) + { + if(!FS::ensureFilePathExists(mcJson)) + { + qWarning() << "Couldn't create patches folder for" << instanceName; + return; + } + if(!renameFile.isEmpty() && QFile::exists(renameFile)) + { + if(!QFile::rename(renameFile, renameFile + ".old")) + { + qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName; + return; + } + } + auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false); + ProfileUtils::removeLwjglFromPatch(file); + file->uid = "net.minecraft"; + file->version = file->minecraftVersion; + file->name = "Minecraft"; + + Meta::Require needsLwjgl; + needsLwjgl.uid = "org.lwjgl"; + file->requires.insert(needsLwjgl); + + if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson)) + { + return; + } + if(!QFile::rename(sourceFile, sourceFile + ".old")) + { + qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName; + return; + } + } +} + +/* + * Migrate old layout to the component based one... + * - Part of the version information is taken from `instance.cfg` (fed to this class from outside). + * - Part is taken from the old order.json file. + * - Part is loaded from loose json files in the instance's `patches` directory. + */ +bool ComponentList::migratePreComponentConfig() +{ + // upgrade the very old files from the beginnings of MultiMC 5 + upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name()); + + QList<ComponentPtr> components; + QSet<QString> loaded; + + auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict) + { + auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json"); + auto intendedVersion = d->getOldConfigVersion(uid); + // load up the base minecraft patch + ComponentPtr component; + if(QFile::exists(jsonFilePath)) + { + if(intendedVersion.isEmpty()) + { + intendedVersion = emptyVersion; + } + auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false); + // fix uid + file->uid = uid; + // if version is missing, add it from the outside. + if(file->version.isEmpty()) + { + file->version = intendedVersion; + } + // if this is a dependency (LWJGL), mark it also as volatile + if(asDependency) + { + file->m_volatile = true; + } + // insert requirements if needed + if(!req.uid.isEmpty()) + { + file->requires.insert(req); + } + // insert conflicts if needed + if(!conflict.uid.isEmpty()) + { + file->conflicts.insert(conflict); + } + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath); + component = new Component(this, uid, file); + component->m_version = intendedVersion; + } + else if(!intendedVersion.isEmpty()) + { + auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion); + component = new Component(this, metaVersion); + } + else + { + return; + } + component->m_dependencyOnly = asDependency; + component->m_important = !asDependency; + components.append(component); + }; + // TODO: insert depends and conflicts here if these are customized files... + Meta::Require reqLwjgl; + reqLwjgl.uid = "org.lwjgl"; + reqLwjgl.suggests = "2.9.1"; + Meta::Require conflictLwjgl3; + conflictLwjgl3.uid = "org.lwjgl3"; + Meta::Require nullReq; + addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3); + addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq); + + // first, collect all other file-based patches and load them + QMap<QString, ComponentPtr> loadedComponents; + QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches")); + for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + // parse the file + qDebug() << "Reading" << info.fileName(); + auto file = ProfileUtils::parseJsonFile(info, true); + + // correct missing or wrong uid based on the file name + QString uid = info.completeBaseName(); + + // ignore builtins, they've been handled already + if (uid == "net.minecraft") + continue; + if (uid == "org.lwjgl") + continue; + + // handle horrible corner cases + if(uid.isEmpty()) + { + // if you have a file named '.json', make it just go away. + // FIXME: @QUALITY do not ignore return value + QFile::remove(info.absoluteFilePath()); + continue; + } + file->uid = uid; + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath()); + + auto component = new Component(this, file->uid, file); + auto version = d->getOldConfigVersion(file->uid); + if(!version.isEmpty()) + { + component->m_version = version; + } + loadedComponents[file->uid] = component; + } + // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files + auto loadSpecial = [&](const QString & uid, int order) + { + auto patchVersion = d->getOldConfigVersion(uid); + if(!patchVersion.isEmpty() && !loadedComponents.contains(uid)) + { + auto patch = new Component(this, ENV.metadataIndex()->get(uid, patchVersion)); + patch->setOrder(order); + loadedComponents[uid] = patch; + } + }; + loadSpecial("net.minecraftforge", 5); + loadSpecial("com.mumfrey.liteloader", 10); + + // load the old order.json file, if present + ProfileUtils::PatchOrder userOrder; + ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder); + + // now add all the patches by user sort order + for (auto uid : userOrder) + { + // ignore builtins + if (uid == "net.minecraft") + continue; + if (uid == "org.lwjgl") + continue; + // ordering has a patch that is gone? + if(!loadedComponents.contains(uid)) + { + continue; + } + components.append(loadedComponents.take(uid)); + } + + // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json + if(!loadedComponents.isEmpty()) + { + // inserting into multimap by order number as key sorts the patches and detects duplicates + QMultiMap<int, ComponentPtr> files; + auto iter = loadedComponents.begin(); + while(iter != loadedComponents.end()) + { + files.insert((*iter)->getOrder(), *iter); + iter++; + } + + // then just extract the patches and put them in the list + for (auto order : files.keys()) + { + const auto &values = files.values(order); + for(auto &value: values) + { + // TODO: put back the insertion of problem messages here, so the user knows about the id duplication + components.append(value); + } + } + } + // new we have a complete list of components... + return saveComponentList(componentsFilePath(), components); +} + +// END: save/load + +void ComponentList::appendComponent(ComponentPtr component) +{ + insertComponent(d->components.size(), component); +} + +void ComponentList::insertComponent(size_t index, ComponentPtr component) +{ + auto id = component->getID(); + if(id.isEmpty()) + { + qWarning() << "Attempt to add a component with empty ID!"; + return; + } + if(d->componentIndex.contains(id)) + { + qWarning() << "Attempt to add a component that is already present!"; + return; + } + beginInsertRows(QModelIndex(), index, index); + d->components.insert(index, component); + d->componentIndex[id] = component; + endInsertRows(); + connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); + scheduleSave(); +} + +void ComponentList::componentDataChanged() +{ + auto objPtr = qobject_cast<Component *>(sender()); + if(!objPtr) + { + qWarning() << "ComponentList got dataChenged signal from a non-Component!"; + return; + } + // figure out which one is it... in a seriously dumb way. + int index = 0; + for (auto component: d->components) + { + if(component.get() == objPtr) + { + emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); + scheduleSave(); + return; + } + index++; + } + qWarning() << "ComponentList got dataChenged signal from a Component which does not belong to it!"; +} + +bool ComponentList::remove(const int index) +{ + auto patch = getComponent(index); + if (!patch->isRemovable()) + { + qWarning() << "Patch" << patch->getID() << "is non-removable"; + return false; + } + + if(!removeComponent_internal(patch)) + { + qCritical() << "Patch" << patch->getID() << "could not be removed"; + return false; + } + + beginRemoveRows(QModelIndex(), index, index); + d->components.removeAt(index); + d->componentIndex.remove(patch->getID()); + endRemoveRows(); + invalidateLaunchProfile(); + scheduleSave(); + return true; +} + +bool ComponentList::remove(const QString id) +{ + int i = 0; + for (auto patch : d->components) + { + if (patch->getID() == id) + { + return remove(i); + } + i++; + } + return false; +} + +bool ComponentList::customize(int index) +{ + auto patch = getComponent(index); + if (!patch->isCustomizable()) + { + qDebug() << "Patch" << patch->getID() << "is not customizable"; + return false; + } + if(!patch->customize()) + { + qCritical() << "Patch" << patch->getID() << "could not be customized"; + return false; + } + invalidateLaunchProfile(); + scheduleSave(); + return true; +} + +bool ComponentList::revertToBase(int index) +{ + auto patch = getComponent(index); + if (!patch->isRevertible()) + { + qDebug() << "Patch" << patch->getID() << "is not revertible"; + return false; + } + if(!patch->revert()) + { + qCritical() << "Patch" << patch->getID() << "could not be reverted"; + return false; + } + invalidateLaunchProfile(); + scheduleSave(); + return true; +} + +Component * ComponentList::getComponent(const QString &id) +{ + auto iter = d->componentIndex.find(id); + if (iter == d->componentIndex.end()) + { + return nullptr; + } + return (*iter).get(); +} + +Component * ComponentList::getComponent(int index) +{ + if(index < 0 || index >= d->components.size()) + { + return nullptr; + } + return d->components[index].get(); +} + +QVariant ComponentList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= d->components.size()) + return QVariant(); + + auto patch = d->components.at(row); + + switch (role) + { + case Qt::CheckStateRole: + { + switch (column) + { + case NameColumn: + return d->components.at(row)->isEnabled() ? Qt::Checked : Qt::Unchecked; + default: + return QVariant(); + } + } + case Qt::DisplayRole: + { + switch (column) + { + case NameColumn: + return d->components.at(row)->getName(); + case VersionColumn: + { + if(patch->isCustom()) + { + return QString("%1 (Custom)").arg(patch->getVersion()); + } + else + { + return patch->getVersion(); + } + } + default: + return QVariant(); + } + } + case Qt::DecorationRole: + { + switch(column) + { + case NameColumn: + { + auto severity = patch->getProblemSeverity(); + switch (severity) + { + case ProblemSeverity::Warning: + return "warning"; + case ProblemSeverity::Error: + return "error"; + default: + return QVariant(); + } + } + default: + { + return QVariant(); + } + } + } + } + return QVariant(); +} + +bool ComponentList::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index)) + { + return false; + } + + if (role == Qt::CheckStateRole) + { + auto component = d->components[index.row()]; + if (component->setEnabled(!component->isEnabled())) + { + return true; + } + } + return false; +} + +QVariant ComponentList::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) + { + switch (section) + { + case NameColumn: + return tr("Name"); + case VersionColumn: + return tr("Version"); + default: + return QVariant(); + } + } + } + return QVariant(); +} +Qt::ItemFlags ComponentList::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + + int row = index.row(); + + if (row < 0 || row >= d->components.size()) + return Qt::NoItemFlags; + + auto patch = d->components.at(row); + // TODO: this will need fine-tuning later... + if(patch->canBeDisabled()) + { + outFlags |= Qt::ItemIsUserCheckable; + } + return outFlags; +} + +int ComponentList::rowCount(const QModelIndex &parent) const +{ + return d->components.size(); +} + +int ComponentList::columnCount(const QModelIndex &parent) const +{ + return NUM_COLUMNS; +} + +void ComponentList::move(const int index, const MoveDirection direction) +{ + int theirIndex; + if (direction == MoveUp) + { + theirIndex = index - 1; + } + else + { + theirIndex = index + 1; + } + + if (index < 0 || index >= d->components.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 = getComponent(index); + auto to = getComponent(theirIndex); + + if (!from || !to || !to->isMoveable() || !from->isMoveable()) + { + return; + } + beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); + d->components.swap(index, theirIndex); + endMoveRows(); + invalidateLaunchProfile(); + scheduleSave(); +} + +void ComponentList::invalidateLaunchProfile() +{ + d->m_profile.reset(); +} + +void ComponentList::installJarMods(QStringList selectedFiles) +{ + installJarMods_internal(selectedFiles); +} + +void ComponentList::installCustomJar(QString selectedFile) +{ + installCustomJar_internal(selectedFile); +} + +bool ComponentList::installEmpty(const QString& uid, const QString& name) +{ + QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if(!FS::ensureFolderPathExists(patchDir)) + { + return false; + } + auto f = std::make_shared<VersionFile>(); + f->name = name; + f->uid = uid; + f->version = "1"; + QString patchFileName = FS::PathCombine(patchDir, uid + ".json"); + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + appendComponent(new Component(this, f->uid, f)); + scheduleSave(); + invalidateLaunchProfile(); + return true; +} + +bool ComponentList::removeComponent_internal(ComponentPtr patch) +{ + bool ok = true; + // first, remove the patch file. this ensures it's not used anymore + auto fileName = patch->getFilename(); + if(fileName.size()) + { + QFile patchFile(fileName); + if(patchFile.exists() && !patchFile.remove()) + { + qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString(); + return false; + } + } + + // FIXME: we need a generic way of removing local resources, not just jar mods... + auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool + { + if (!jarMod->isLocal()) + { + return true; + } + QStringList jar, temp1, temp2, temp3; + jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath()); + QFileInfo finfo (jar[0]); + if(finfo.exists()) + { + QFile jarModFile(jar[0]); + if(!jarModFile.remove()) + { + qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString(); + return false; + } + return true; + } + return true; + }; + + auto vFile = patch->getVersionFile(); + if(vFile) + { + auto &jarMods = vFile->jarMods; + for(auto &jarmod: jarMods) + { + ok &= preRemoveJarMod(jarmod); + } + } + return ok; +} + +bool ComponentList::installJarMods_internal(QStringList filepaths) +{ + QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if(!FS::ensureFolderPathExists(patchDir)) + { + return false; + } + + if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir())) + { + return false; + } + + for(auto filepath:filepaths) + { + 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 = FS::PathCombine(d->m_instance->jarModsDir(), target_filename); + + QFileInfo targetInfo(finalPath); + if(targetInfo.exists()) + { + return false; + } + + if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) + { + return false; + } + + auto f = std::make_shared<VersionFile>(); + auto jarMod = std::make_shared<Library>(); + jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); + jarMod->setFilename(target_filename); + jarMod->setDisplayName(sourceInfo.completeBaseName()); + jarMod->setHint("local"); + f->jarMods.append(jarMod); + f->name = target_name; + f->uid = target_id; + QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + appendComponent(new Component(this, f->uid, f)); + } + scheduleSave(); + invalidateLaunchProfile(); + return true; +} + +bool ComponentList::installCustomJar_internal(QString filepath) +{ + QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if(!FS::ensureFolderPathExists(patchDir)) + { + return false; + } + + QString libDir = d->m_instance->getLocalLibraryPath(); + if (!FS::ensureFolderPathExists(libDir)) + { + return false; + } + + auto specifier = GradleSpecifier("org.multimc:customjar:1"); + QFileInfo sourceInfo(filepath); + QString target_filename = specifier.getFileName(); + QString target_id = specifier.artifactId(); + QString target_name = sourceInfo.completeBaseName() + " (custom jar)"; + QString finalPath = FS::PathCombine(libDir, target_filename); + + QFileInfo jarInfo(finalPath); + if (jarInfo.exists()) + { + if(!QFile::remove(finalPath)) + { + return false; + } + } + if (!QFile::copy(filepath, finalPath)) + { + return false; + } + + auto f = std::make_shared<VersionFile>(); + auto jarMod = std::make_shared<Library>(); + jarMod->setRawName(specifier); + jarMod->setDisplayName(sourceInfo.completeBaseName()); + jarMod->setHint("local"); + f->mainJar = jarMod; + f->name = target_name; + f->uid = target_id; + QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + appendComponent(new Component(this, f->uid, f)); + + scheduleSave(); + invalidateLaunchProfile(); + return true; +} + +std::shared_ptr<LaunchProfile> ComponentList::getProfile() const +{ + if(!d->m_profile) + { + try + { + auto profile = std::make_shared<LaunchProfile>(); + for(auto file: d->components) + { + qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); + file->applyTo(profile.get()); + } + d->m_profile = profile; + } + catch (Exception & error) + { + qWarning() << "Couldn't apply profile patches because: " << error.cause(); + } + } + return d->m_profile; +} + +void ComponentList::setOldConfigVersion(const QString& uid, const QString& version) +{ + if(version.isEmpty()) + { + return; + } + d->m_oldConfigVersions[uid] = version; +} + +bool ComponentList::setComponentVersion(const QString& uid, const QString& version, bool important) +{ + auto iter = d->componentIndex.find(uid); + if(iter != d->componentIndex.end()) + { + ComponentPtr component = *iter; + // set existing + if(component->revert()) + { + component->setVersion(version); + component->setImportant(important); + return true; + } + return false; + } + else + { + // add new + auto component = new Component(this, uid); + component->m_version = version; + component->m_important = important; + appendComponent(component); + return true; + } +} + +QString ComponentList::getComponentVersion(const QString& uid) const +{ + const auto iter = d->componentIndex.find(uid); + if (iter != d->componentIndex.end()) + { + return (*iter)->getVersion(); + } + return QString(); +} diff --git a/api/logic/minecraft/ComponentList.h b/api/logic/minecraft/ComponentList.h new file mode 100644 index 00000000..cf4d9975 --- /dev/null +++ b/api/logic/minecraft/ComponentList.h @@ -0,0 +1,146 @@ +/* Copyright 2013-2018 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 <QAbstractListModel> + +#include <QString> +#include <QList> +#include <memory> + +#include "Library.h" +#include "LaunchProfile.h" +#include "Component.h" +#include "ProfileUtils.h" +#include "BaseVersion.h" +#include "MojangDownloadInfo.h" +#include "multimc_logic_export.h" +#include "net/Mode.h" + +class MinecraftInstance; +struct ComponentListData; +class ComponentUpdateTask; + +class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel +{ + Q_OBJECT + friend ComponentUpdateTask; +public: + enum Columns + { + NameColumn = 0, + VersionColumn, + NUM_COLUMNS + }; + + explicit ComponentList(MinecraftInstance * instance); + virtual ~ComponentList(); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex &parent) const override; + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + + /// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch. + void buildingFromScratch(); + + /// install more jar mods + void installJarMods(QStringList selectedFiles); + + /// install a jar/zip as a replacement for the main jar + void installCustomJar(QString selectedFile); + + enum MoveDirection { MoveUp, MoveDown }; + /// move component file # up or down the list + void move(const int index, const MoveDirection direction); + + /// remove component file # - including files/records + bool remove(const int index); + + /// remove component file by id - including files/records + bool remove(const QString id); + + bool customize(int index); + + bool revertToBase(int index); + + /// reload the list, reload all components, resolve dependencies + void reload(Net::Mode netmode); + + // reload all components, resolve dependencies + void resolve(Net::Mode netmode); + + /// get current running task... + shared_qobject_ptr<Task> getCurrentTask(); + + std::shared_ptr<LaunchProfile> getProfile() const; + + // NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config + void setOldConfigVersion(const QString &uid, const QString &version); + + QString getComponentVersion(const QString &uid) const; + + bool setComponentVersion(const QString &uid, const QString &version, bool important = false); + + bool installEmpty(const QString &uid, const QString &name); + + QString patchFilePathForUid(const QString &uid) const; + + /// if there is a save scheduled, do it now. + void saveNow(); + +public: + /// get the profile component by id + Component * getComponent(const QString &id); + + /// get the profile component by index + Component * getComponent(int index); + +private: + void scheduleSave(); + bool saveIsScheduled() const; + + /// apply the component patches. Catches all the errors and returns true/false for success/failure + void invalidateLaunchProfile(); + + /// Add the component to the internal list of patches + void appendComponent(ComponentPtr component); + /// insert component so that its index is ideally the specified one (returns real index) + void insertComponent(size_t index, ComponentPtr component); + + QString componentsFilePath() const; + QString patchesPattern() const; + +private slots: + void save_internal(); + void updateSucceeded(); + void updateFailed(const QString & error); + void componentDataChanged(); + +private: + bool load(); + bool installJarMods_internal(QStringList filepaths); + bool installCustomJar_internal(QString filepath); + bool removeComponent_internal(ComponentPtr patch); + + bool migratePreComponentConfig(); + +private: /* data */ + + std::unique_ptr<ComponentListData> d; +}; diff --git a/api/logic/minecraft/ComponentList_p.h b/api/logic/minecraft/ComponentList_p.h new file mode 100644 index 00000000..26ca5049 --- /dev/null +++ b/api/logic/minecraft/ComponentList_p.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Component.h" +#include <map> +#include <QTimer> +#include <QList> +#include <QMap> + +class MinecraftInstance; +using ComponentContainer = QList<ComponentPtr>; +using ComponentIndex = QMap<QString, ComponentPtr>; +using ConnectionList = QList<QMetaObject::Connection>; + +struct ComponentListData +{ + // the instance this belongs to + MinecraftInstance *m_instance; + + // the launch profile (volatile, temporary thing created on demand) + std::shared_ptr<LaunchProfile> m_profile; + + // version information migrated from instance.cfg file. Single use on migration! + std::map<QString, QString> m_oldConfigVersions; + QString getOldConfigVersion(const QString& uid) const + { + const auto iter = m_oldConfigVersions.find(uid); + if(iter != m_oldConfigVersions.cend()) + { + return (*iter).second; + } + return QString(); + } + + // persistent list of components and related machinery + ComponentContainer components; + ComponentIndex componentIndex; + bool dirty = false; + QTimer m_saveTimer; + shared_qobject_ptr<Task> m_updateTask; + bool loaded = false; +}; + diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp new file mode 100644 index 00000000..2d6ceb91 --- /dev/null +++ b/api/logic/minecraft/ComponentUpdateTask.cpp @@ -0,0 +1,691 @@ +#include "ComponentUpdateTask.h" + +#include "ComponentList_p.h" +#include "ComponentList.h" +#include "Component.h" +#include <Env.h> +#include <meta/Index.h> +#include <meta/VersionList.h> +#include <meta/Version.h> +#include "ComponentUpdateTask_p.h" +#include <cassert> +#include <Version.h> +#include "net/Mode.h" +#include "OneSixVersionFormat.h" + +/* + * This is responsible for loading the components of a component list AND resolving dependency issues between them + */ + +/* + * FIXME: the 'one shot async task' nature of this does not fit the intended usage + * Really, it should be a reactor/state machine that receives input from the application + * and dynamically adapts to changing requirements... + * + * The reactor should be the only entry into manipulating the ComponentList. + * See: https://en.wikipedia.org/wiki/Reactor_pattern + */ + +/* + * Or make this operate on a snapshot of the ComponentList state, then merge results in as long as the snapshot and ComponentList didn't change? + * If the component list changes, start over. + */ + +ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList* list, QObject* parent) + : Task(parent) +{ + d.reset(new ComponentUpdateTaskData); + d->m_list = list; + d->mode = mode; + d->netmode = netmode; +} + +ComponentUpdateTask::~ComponentUpdateTask() +{ +} + +void ComponentUpdateTask::executeTask() +{ + qDebug() << "Loading components"; + loadComponents(); +} + +namespace +{ +enum class LoadResult +{ + LoadedLocal, + RequiresRemote, + Failed +}; + +LoadResult composeLoadResult(LoadResult a, LoadResult b) +{ + if (a < b) + { + return b; + } + return a; +} + +static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) +{ + if(component->m_loaded) + { + qDebug() << component->getName() << "is already loaded"; + return LoadResult::LoadedLocal; + } + + LoadResult result = LoadResult::Failed; + auto customPatchFilename = component->getFilename(); + if(QFile::exists(customPatchFilename)) + { + // if local file exists... + + // check for uid problems inside... + bool fileChanged = false; + auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false); + if(file->uid != component->m_uid) + { + file->uid = component->m_uid; + fileChanged = true; + } + if(fileChanged) + { + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename); + } + + component->m_file = file; + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version); + component->m_metaVersion = metaVersion; + if(metaVersion->isLoaded()) + { + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + metaVersion->load(netmode); + loadTask = metaVersion->getCurrentTask(); + if(loadTask) + result = LoadResult::RequiresRemote; + else if (metaVersion->isLoaded()) + result = LoadResult::LoadedLocal; + else + result = LoadResult::Failed; + } + } + return result; +} + +// FIXME: dead code. determine if this can still be useful? +/* +static LoadResult loadComponentList(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) +{ + if(component->m_loaded) + { + qDebug() << component->getName() << "is already loaded"; + return LoadResult::LoadedLocal; + } + + LoadResult result = LoadResult::Failed; + auto metaList = ENV.metadataIndex()->get(component->m_uid); + if(metaList->isLoaded()) + { + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + metaList->load(netmode); + loadTask = metaList->getCurrentTask(); + result = LoadResult::RequiresRemote; + } + return result; +} +*/ + +static LoadResult loadIndex(shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) +{ + // FIXME: DECIDE. do we want to run the update task anyway? + if(ENV.metadataIndex()->isLoaded()) + { + qDebug() << "Index is already loaded"; + return LoadResult::LoadedLocal; + } + ENV.metadataIndex()->load(netmode); + loadTask = ENV.metadataIndex()->getCurrentTask(); + if(loadTask) + { + return LoadResult::RequiresRemote; + } + // FIXME: this is assuming the load succeeded... did it really? + return LoadResult::LoadedLocal; +} +} + +void ComponentUpdateTask::loadComponents() +{ + LoadResult result = LoadResult::LoadedLocal; + size_t taskIndex = 0; + size_t componentIndex = 0; + d->remoteLoadSuccessful = true; + // load the main index (it is needed to determine if components can revert) + { + // FIXME: tear out as a method? or lambda? + shared_qobject_ptr<Task> indexLoadTask; + auto singleResult = loadIndex(indexLoadTask, d->netmode); + result = composeLoadResult(result, singleResult); + if(indexLoadTask) + { + qDebug() << "Remote loading is being run for metadata index"; + RemoteLoadStatus status; + status.type = RemoteLoadStatus::Type::Index; + d->remoteLoadStatusList.append(status); + connect(indexLoadTask.get(), &Task::succeeded, [=]() + { + remoteLoadSucceeded(taskIndex); + }); + connect(indexLoadTask.get(), &Task::failed, [=](const QString & error) + { + remoteLoadFailed(taskIndex, error); + }); + taskIndex++; + } + } + // load all the components OR their lists... + for (auto component: d->m_list->d->components) + { + shared_qobject_ptr<Task> loadTask; + LoadResult singleResult; + RemoteLoadStatus::Type loadType; + // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that... +#if 0 + switch(d->mode) + { + case Mode::Launch: + { + singleResult = loadComponent(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::Version; + break; + } + case Mode::Resolution: + { + singleResult = loadComponentList(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::List; + break; + } + } +#else + singleResult = loadComponent(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::Version; +#endif + if(singleResult == LoadResult::LoadedLocal) + { + component->updateCachedData(); + } + result = composeLoadResult(result, singleResult); + if (loadTask) + { + qDebug() << "Remote loading is being run for" << component->getName(); + connect(loadTask.get(), &Task::succeeded, [=]() + { + remoteLoadSucceeded(taskIndex); + }); + connect(loadTask.get(), &Task::failed, [=](const QString & error) + { + remoteLoadFailed(taskIndex, error); + }); + RemoteLoadStatus status; + status.type = loadType; + status.componentListIndex = componentIndex; + d->remoteLoadStatusList.append(status); + taskIndex++; + } + componentIndex++; + } + d->remoteTasksInProgress = taskIndex; + switch(result) + { + case LoadResult::LoadedLocal: + { + // Everything got loaded. Advance to dependency resolution. + resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); + break; + } + case LoadResult::RequiresRemote: + { + // we wait for signals. + break; + } + case LoadResult::Failed: + { + emitFailed(tr("Some component metadata load tasks failed.")); + break; + } + } +} + +namespace +{ + struct RequireEx : public Meta::Require + { + size_t indexOfFirstDependee = 0; + }; + struct RequireCompositionResult + { + bool ok; + RequireEx outcome; + }; + using RequireExSet = std::set<RequireEx>; +} + +static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b) +{ + assert(a.uid == b.uid); + RequireEx out; + out.uid = a.uid; + out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee); + if(a.equalsVersion.isEmpty()) + { + out.equalsVersion = b.equalsVersion; + } + else if (b.equalsVersion.isEmpty()) + { + out.equalsVersion = a.equalsVersion; + } + else if (a.equalsVersion == b.equalsVersion) + { + out.equalsVersion = a.equalsVersion; + } + else + { + // FIXME: mark error as explicit version conflict + return {false, out}; + } + + if(a.suggests.isEmpty()) + { + out.suggests = b.suggests; + } + else if (b.suggests.isEmpty()) + { + out.suggests = a.suggests; + } + else + { + Version aVer(a.suggests); + Version bVer(b.suggests); + out.suggests = (aVer < bVer ? b.suggests : a.suggests); + } + return {true, out}; +} + +// gather the requirements from all components, finding any obvious conflicts +static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output) +{ + bool succeeded = true; + size_t componentNum = 0; + for(auto component: input) + { + auto &componentRequires = component->m_cachedRequires; + for(const auto & componentRequire: componentRequires) + { + auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){ + return req.uid == componentRequire.uid; + }); + + RequireEx componenRequireEx; + componenRequireEx.uid = componentRequire.uid; + componenRequireEx.suggests = componentRequire.suggests; + componenRequireEx.equalsVersion = componentRequire.equalsVersion; + componenRequireEx.indexOfFirstDependee = componentNum; + + if(found != output.cend()) + { + // found... process it further + auto result = composeRequirement(componenRequireEx, *found); + if(result.ok) + { + output.erase(componenRequireEx); + output.insert(result.outcome); + } + else + { + qCritical() + << "Conflicting requirements:" + << componentRequire.uid + << "versions:" + << componentRequire.equalsVersion + << ";" + << (*found).equalsVersion; + } + succeeded &= result.ok; + } + else + { + // not found, accumulate + output.insert(componenRequireEx); + } + } + componentNum++; + } + return succeeded; +} + +/// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps) +static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove) +{ + for(const auto & component: components) + { + if(!component->m_dependencyOnly) + continue; + if(!component->m_cachedVolatile) + continue; + RequireEx reqNeedle; + reqNeedle.uid = component->m_uid; + const auto iter = reqs.find(reqNeedle); + if(iter == reqs.cend()) + { + toRemove.append(component->m_uid); + } + } +} + +/** + * handles: + * - trivial addition (there is an unmet requirement and it can be trivially met by adding something) + * - trivial version conflict of dependencies == explicit version required and installed is different + * + * toAdd - set of requirements than mean adding a new component + * toChange - set of requirements that mean changing version of an existing component + */ +static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange) +{ + enum class Decision + { + Undetermined, + Met, + Missing, + VersionNotSame, + LockedVersionNotSame + } decision = Decision::Undetermined; + + QString reqStr; + bool succeeded = true; + // list the composed requirements and say if they are met or unmet + for(auto & req: input) + { + do + { + if(req.equalsVersion.isEmpty()) + { + reqStr = QString("Req: %1").arg(req.uid); + if(index.contains(req.uid)) + { + decision = Decision::Met; + } + else + { + toAdd.insert(req); + decision = Decision::Missing; + } + break; + } + else + { + reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion); + const auto & compIter = index.find(req.uid); + if(compIter == index.cend()) + { + toAdd.insert(req); + decision = Decision::Missing; + break; + } + auto & comp = (*compIter); + if(comp->getVersion() != req.equalsVersion) + { + if(comp->m_dependencyOnly) + { + decision = Decision::VersionNotSame; + } + else + { + decision = Decision::LockedVersionNotSame; + } + break; + } + decision = Decision::Met; + } + } while(false); + switch(decision) + { + case Decision::Undetermined: + qCritical() << "No decision for" << reqStr; + succeeded = false; + break; + case Decision::Met: + qDebug() << reqStr << "Is met."; + break; + case Decision::Missing: + qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee; + toAdd.insert(req); + break; + case Decision::VersionNotSame: + qDebug() << reqStr << "already has different version that can be changed."; + toChange.insert(req); + break; + case Decision::LockedVersionNotSame: + qDebug() << reqStr << "already has different version that cannot be changed."; + succeeded = false; + break; + } + } + return succeeded; +} + +// FIXME, TODO: decouple dependency resolution from loading +// FIXME: This works directly with the ComponentList internals. It shouldn't! It needs richer data types than ComponentList uses. +// FIXME: throw all this away and use a graph +void ComponentUpdateTask::resolveDependencies(bool checkOnly) +{ + qDebug() << "Resolving dependencies"; + /* + * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways: + * 1. There are conflicting dependencies on the same uid with different exact version numbers + * -> hard error + * 2. A dependency has non-matching exact version number + * -> hard error + * 3. A dependency is entirely missing and needs to be injected before the dependee(s) + * -> requirements are injected + * + * NOTE: this is a placeholder and should eventually be replaced with something 'serious' + */ + auto & components = d->m_list->d->components; + auto & componentIndex = d->m_list->d->componentIndex; + + RequireExSet allRequires; + QStringList toRemove; + do + { + allRequires.clear(); + toRemove.clear(); + if(!gatherRequirementsFromComponents(components, allRequires)) + { + emitFailed(tr("Conflicting requirements detected during dependency checking!")); + return; + } + getTrivialRemovals(components, allRequires, toRemove); + if(!toRemove.isEmpty()) + { + qDebug() << "Removing obsolete components..."; + for(auto & remove : toRemove) + { + qDebug() << "Removing" << remove; + d->m_list->remove(remove); + } + } + } while (!toRemove.isEmpty()); + RequireExSet toAdd; + RequireExSet toChange; + bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange); + if(!succeeded) + { + emitFailed(tr("Instance has conflicting dependencies.")); + return; + } + if(checkOnly) + { + if(toAdd.size() || toChange.size()) + { + emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch.")); + } + else + { + emitSucceeded(); + } + return; + } + + bool recursionNeeded = false; + if(toAdd.size()) + { + // add stuff... + for(auto &add: toAdd) + { + ComponentPtr component = new Component(d->m_list, add.uid); + if(!add.equalsVersion.isEmpty()) + { + // exact version + qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee; + component->m_version = add.equalsVersion; + } + else + { + // version needs to be decided + qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee; +// ############################################################################################################ +// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. + if(!add.suggests.isEmpty()) + { + component->m_version = add.suggests; + } + else + { + if(add.uid == "org.lwjgl") + { + component->m_version = "2.9.1"; + } + else if (add.uid == "org.lwjgl3") + { + component->m_version = "3.1.2"; + } + } +// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. +// ############################################################################################################ + } + component->m_dependencyOnly = true; + // FIXME: this should not work directly with the component list + d->m_list->insertComponent(add.indexOfFirstDependee, component); + componentIndex[add.uid] = component; + } + recursionNeeded = true; + } + if(toChange.size()) + { + // change a version of something that exists + for(auto &change: toChange) + { + // FIXME: this should not work directly with the component list + qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion; + auto component = componentIndex[change.uid]; + component->setVersion(change.equalsVersion); + } + recursionNeeded = true; + } + + if(recursionNeeded) + { + loadComponents(); + } + else + { + emitSucceeded(); + } +} + +void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) +{ + auto &taskSlot = d->remoteLoadStatusList[taskIndex]; + if(taskSlot.finished) + { + qWarning() << "Got multiple results from remote load task" << taskIndex; + return; + } + qDebug() << "Remote task" << taskIndex << "succeeded"; + taskSlot.succeeded = false; + taskSlot.finished = true; + d->remoteTasksInProgress --; + // update the cached data of the component from the downloaded version file. + if (taskSlot.type == RemoteLoadStatus::Type::Version) + { + auto component = d->m_list->getComponent(taskSlot.componentListIndex); + component->m_loaded = true; + component->updateCachedData(); + } + checkIfAllFinished(); +} + + +void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) +{ + auto &taskSlot = d->remoteLoadStatusList[taskIndex]; + if(taskSlot.finished) + { + qWarning() << "Got multiple results from remote load task" << taskIndex; + return; + } + qDebug() << "Remote task" << taskIndex << "failed: " << msg; + d->remoteLoadSuccessful = false; + taskSlot.succeeded = false; + taskSlot.finished = true; + taskSlot.error = msg; + d->remoteTasksInProgress --; + checkIfAllFinished(); +} + +void ComponentUpdateTask::checkIfAllFinished() +{ + if(d->remoteTasksInProgress) + { + // not yet... + return; + } + if(d->remoteLoadSuccessful) + { + // nothing bad happened... clear the temp load status and proceed with looking at dependencies + d->remoteLoadStatusList.clear(); + resolveDependencies(d->mode == Mode::Launch); + } + else + { + // remote load failed... report error and bail + QStringList allErrorsList; + for(auto & item: d->remoteLoadStatusList) + { + if(!item.succeeded) + { + allErrorsList.append(item.error); + } + } + auto allErrors = allErrorsList.join("\n"); + emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors)); + d->remoteLoadStatusList.clear(); + } +} diff --git a/api/logic/minecraft/ComponentUpdateTask.h b/api/logic/minecraft/ComponentUpdateTask.h new file mode 100644 index 00000000..11d122b6 --- /dev/null +++ b/api/logic/minecraft/ComponentUpdateTask.h @@ -0,0 +1,37 @@ +#pragma once + +#include "tasks/Task.h" +#include "net/Mode.h" + +#include <memory> +class ComponentList; +struct ComponentUpdateTaskData; + +class ComponentUpdateTask : public Task +{ + Q_OBJECT +public: + enum class Mode + { + Launch, + Resolution + }; + +public: + explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0); + virtual ~ComponentUpdateTask(); + +protected: + void executeTask(); + +private: + void loadComponents(); + void resolveDependencies(bool checkOnly); + + void remoteLoadSucceeded(size_t index); + void remoteLoadFailed(size_t index, const QString &msg); + void checkIfAllFinished(); + +private: + std::unique_ptr<ComponentUpdateTaskData> d; +}; diff --git a/api/logic/minecraft/ComponentUpdateTask_p.h b/api/logic/minecraft/ComponentUpdateTask_p.h new file mode 100644 index 00000000..a5216506 --- /dev/null +++ b/api/logic/minecraft/ComponentUpdateTask_p.h @@ -0,0 +1,32 @@ +#pragma once + +#include <cstddef> +#include <QString> +#include <QList> +#include "net/Mode.h" + +class ComponentList; + +struct RemoteLoadStatus +{ + enum class Type + { + Index, + List, + Version + } type = Type::Version; + size_t componentListIndex = 0; + bool finished = false; + bool succeeded = false; + QString error; +}; + +struct ComponentUpdateTaskData +{ + ComponentList * m_list = nullptr; + QList<RemoteLoadStatus> remoteLoadStatusList; + bool remoteLoadSuccessful = true; + size_t remoteTasksInProgress = 0; + ComponentUpdateTask::Mode mode; + Net::Mode netmode; +}; diff --git a/api/logic/minecraft/LaunchProfile.cpp b/api/logic/minecraft/LaunchProfile.cpp new file mode 100644 index 00000000..436a39d9 --- /dev/null +++ b/api/logic/minecraft/LaunchProfile.cpp @@ -0,0 +1,297 @@ +#include "LaunchProfile.h" +#include <Version.h> + +void LaunchProfile::clear() +{ + m_minecraftVersion.clear(); + m_minecraftVersionType.clear(); + m_minecraftAssets.reset(); + m_minecraftArguments.clear(); + m_tweakers.clear(); + m_mainClass.clear(); + m_appletClass.clear(); + m_libraries.clear(); + m_traits.clear(); + m_jarMods.clear(); + m_mainJar.reset(); + m_problemSeverity = ProblemSeverity::None; +} + +static void applyString(const QString & from, QString & to) +{ + if(from.isEmpty()) + return; + to = from; +} + +void LaunchProfile::applyMinecraftVersion(const QString& id) +{ + applyString(id, this->m_minecraftVersion); +} + +void LaunchProfile::applyAppletClass(const QString& appletClass) +{ + applyString(appletClass, this->m_appletClass); +} + +void LaunchProfile::applyMainClass(const QString& mainClass) +{ + applyString(mainClass, this->m_mainClass); +} + +void LaunchProfile::applyMinecraftArguments(const QString& minecraftArguments) +{ + applyString(minecraftArguments, this->m_minecraftArguments); +} + +void LaunchProfile::applyMinecraftVersionType(const QString& type) +{ + applyString(type, this->m_minecraftVersionType); +} + +void LaunchProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets) +{ + if(assets) + { + m_minecraftAssets = assets; + } +} + +void LaunchProfile::applyTraits(const QSet<QString>& traits) +{ + this->m_traits.unite(traits); +} + +void LaunchProfile::applyTweakers(const QStringList& tweakers) +{ + // if the applied tweakers override an existing one, skip it. this effectively moves it later in the sequence + QStringList newTweakers; + for(auto & tweaker: m_tweakers) + { + if (tweakers.contains(tweaker)) + { + continue; + } + newTweakers.append(tweaker); + } + // then just append the new tweakers (or moved original ones) + newTweakers += tweakers; + m_tweakers = newTweakers; +} + +void LaunchProfile::applyJarMods(const QList<LibraryPtr>& jarMods) +{ + this->m_jarMods.append(jarMods); +} + +static int findLibraryByName(QList<LibraryPtr> *haystack, const GradleSpecifier &needle) +{ + int retval = -1; + for (int i = 0; i < haystack->size(); ++i) + { + if (haystack->at(i)->rawName().matchName(needle)) + { + // only one is allowed. + if (retval != -1) + return -1; + retval = i; + } + } + return retval; +} + +void LaunchProfile::applyMods(const QList<LibraryPtr>& mods) +{ + QList<LibraryPtr> * list = &m_mods; + for(auto & mod: mods) + { + auto modCopy = Library::limitedCopy(mod); + + // find the mod by name. + const int index = findLibraryByName(list, mod->rawName()); + // mod not found? just add it. + if (index < 0) + { + list->append(modCopy); + return; + } + + auto existingLibrary = list->at(index); + // if we are higher it means we should update + if (Version(mod->version()) > Version(existingLibrary->version())) + { + list->replace(index, modCopy); + } + } +} + +void LaunchProfile::applyLibrary(LibraryPtr library) +{ + if(!library->isActive()) + { + return; + } + + QList<LibraryPtr> * list = &m_libraries; + if(library->isNative()) + { + list = &m_nativeLibraries; + } + + auto libraryCopy = Library::limitedCopy(library); + + // find the library by name. + const int index = findLibraryByName(list, library->rawName()); + // library not found? just add it. + if (index < 0) + { + list->append(libraryCopy); + return; + } + + auto existingLibrary = list->at(index); + // if we are higher it means we should update + if (Version(library->version()) > Version(existingLibrary->version())) + { + list->replace(index, libraryCopy); + } +} + +const LibraryPtr LaunchProfile::getMainJar() const +{ + return m_mainJar; +} + +void LaunchProfile::applyMainJar(LibraryPtr jar) +{ + if(jar) + { + m_mainJar = jar; + } +} + +void LaunchProfile::applyProblemSeverity(ProblemSeverity severity) +{ + if (m_problemSeverity < severity) + { + m_problemSeverity = severity; + } +} + +const QList<PatchProblem> LaunchProfile::getProblems() const +{ + // FIXME: implement something that actually makes sense here + return {}; +} + +QString LaunchProfile::getMinecraftVersion() const +{ + return m_minecraftVersion; +} + +QString LaunchProfile::getAppletClass() const +{ + return m_appletClass; +} + +QString LaunchProfile::getMainClass() const +{ + return m_mainClass; +} + +const QSet<QString> &LaunchProfile::getTraits() const +{ + return m_traits; +} + +const QStringList & LaunchProfile::getTweakers() const +{ + return m_tweakers; +} + +bool LaunchProfile::hasTrait(const QString& trait) const +{ + return m_traits.contains(trait); +} + +ProblemSeverity LaunchProfile::getProblemSeverity() const +{ + return m_problemSeverity; +} + +QString LaunchProfile::getMinecraftVersionType() const +{ + return m_minecraftVersionType; +} + +std::shared_ptr<MojangAssetIndexInfo> LaunchProfile::getMinecraftAssets() const +{ + if(!m_minecraftAssets) + { + return std::make_shared<MojangAssetIndexInfo>("legacy"); + } + return m_minecraftAssets; +} + +QString LaunchProfile::getMinecraftArguments() const +{ + return m_minecraftArguments; +} + +const QList<LibraryPtr> & LaunchProfile::getJarMods() const +{ + return m_jarMods; +} + +const QList<LibraryPtr> & LaunchProfile::getLibraries() const +{ + return m_libraries; +} + +const QList<LibraryPtr> & LaunchProfile::getNativeLibraries() const +{ + return m_nativeLibraries; +} + +void LaunchProfile::getLibraryFiles( + const QString& architecture, + QStringList& jars, + QStringList& nativeJars, + const QString& overridePath, + const QString& tempPath +) const +{ + QStringList native32, native64; + jars.clear(); + nativeJars.clear(); + for (auto lib : getLibraries()) + { + lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + // NOTE: order is important here, add main jar last to the lists + if(m_mainJar) + { + // FIXME: HACK!! jar modding is weird and unsystematic! + if(m_jarMods.size()) + { + QDir tempDir(tempPath); + jars.append(tempDir.absoluteFilePath("minecraft.jar")); + } + else + { + m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + } + for (auto lib : getNativeLibraries()) + { + lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + if(architecture == "32") + { + nativeJars.append(native32); + } + else if(architecture == "64") + { + nativeJars.append(native64); + } +} diff --git a/api/logic/minecraft/LaunchProfile.h b/api/logic/minecraft/LaunchProfile.h new file mode 100644 index 00000000..e7f5f4af --- /dev/null +++ b/api/logic/minecraft/LaunchProfile.h @@ -0,0 +1,99 @@ +#pragma once +#include <QString> +#include "Library.h" +#include <ProblemProvider.h> + +class LaunchProfile: public ProblemProvider +{ +public: + virtual ~LaunchProfile() {}; + +public: /* application of profile variables from patches */ + void applyMinecraftVersion(const QString& id); + void applyMainClass(const QString& mainClass); + void applyAppletClass(const QString& appletClass); + void applyMinecraftArguments(const QString& minecraftArguments); + void applyMinecraftVersionType(const QString& type); + void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); + void applyTraits(const QSet<QString> &traits); + void applyTweakers(const QStringList &tweakers); + void applyJarMods(const QList<LibraryPtr> &jarMods); + void applyMods(const QList<LibraryPtr> &jarMods); + void applyLibrary(LibraryPtr library); + void applyMainJar(LibraryPtr jar); + void applyProblemSeverity(ProblemSeverity severity); + /// clear the profile + void clear(); + +public: /* getters for profile variables */ + QString getMinecraftVersion() const; + QString getMainClass() const; + QString getAppletClass() const; + QString getMinecraftVersionType() const; + MojangAssetIndexInfo::Ptr getMinecraftAssets() const; + QString getMinecraftArguments() const; + const QSet<QString> & getTraits() const; + const QStringList & getTweakers() const; + const QList<LibraryPtr> & getJarMods() const; + const QList<LibraryPtr> & getLibraries() const; + const QList<LibraryPtr> & getNativeLibraries() const; + const LibraryPtr getMainJar() const; + void getLibraryFiles( + const QString & architecture, + QStringList & jars, + QStringList & nativeJars, + const QString & overridePath, + const QString & tempPath + ) const; + bool hasTrait(const QString & trait) const; + ProblemSeverity getProblemSeverity() const override; + const QList<PatchProblem> getProblems() const override; + +private: + /// the version of Minecraft - jar to use + QString m_minecraftVersion; + + /// Release type - "release" or "snapshot" + QString m_minecraftVersionType; + + /// Assets type - "legacy" or a version ID + MojangAssetIndexInfo::Ptr m_minecraftAssets; + + /** + * arguments that should be used for launching minecraft + * + * ex: "--username ${auth_player_name} --session ${auth_session} + * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" + */ + QString m_minecraftArguments; + + /// A list of all tweaker classes + QStringList m_tweakers; + + /// The main class to load first + QString m_mainClass; + + /// The applet class, for some very old minecraft releases + QString m_appletClass; + + /// the list of libraries + QList<LibraryPtr> m_libraries; + + /// the main jar + LibraryPtr m_mainJar; + + /// the list of libraries + QList<LibraryPtr> m_nativeLibraries; + + /// traits, collected from all the version files (version files can only add) + QSet<QString> m_traits; + + /// A list of jar mods. version files can add those. + QList<LibraryPtr> m_jarMods; + + /// the list of mods + QList<LibraryPtr> m_mods; + + ProblemSeverity m_problemSeverity = ProblemSeverity::None; + +}; diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp index 22e1bd33..cd1afde4 100644 --- a/api/logic/minecraft/Library.cpp +++ b/api/logic/minecraft/Library.cpp @@ -104,6 +104,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class } if (isForge) { + qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url; out.append(ForgeXzDownload::make(storage, entry)); } else @@ -113,11 +114,14 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); auto dl = Net::Download::makeCached(url, entry, options); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); + qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url; out.append(dl); } - else + { out.append(Net::Download::makeCached(url, entry, options)); + qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url; + } } return true; }; @@ -125,42 +129,56 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class QString raw_storage = storageSuffix(system); if(m_mojangDownloads) { - if(m_mojangDownloads->artifact) - { - auto artifact = m_mojangDownloads->artifact; - add_download(raw_storage, artifact->url, artifact->sha1); - } - if(m_nativeClassifiers.contains(system)) + if(isNative()) { - auto nativeClassifier = m_nativeClassifiers[system]; - if(nativeClassifier.contains("${arch}")) + if(m_nativeClassifiers.contains(system)) { - auto nat32Classifier = nativeClassifier; - nat32Classifier.replace("${arch}", "32"); - auto nat64Classifier = nativeClassifier; - nat64Classifier.replace("${arch}", "64"); - auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier); - if(nat32info) + auto nativeClassifier = m_nativeClassifiers[system]; + if(nativeClassifier.contains("${arch}")) { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "32"); - add_download(cooked_storage, nat32info->url, nat32info->sha1); + auto nat32Classifier = nativeClassifier; + nat32Classifier.replace("${arch}", "32"); + auto nat64Classifier = nativeClassifier; + nat64Classifier.replace("${arch}", "64"); + auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier); + if(nat32info) + { + auto cooked_storage = raw_storage; + cooked_storage.replace("${arch}", "32"); + add_download(cooked_storage, nat32info->url, nat32info->sha1); + } + auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); + if(nat64info) + { + auto cooked_storage = raw_storage; + cooked_storage.replace("${arch}", "64"); + add_download(cooked_storage, nat64info->url, nat64info->sha1); + } } - auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); - if(nat64info) + else { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "64"); - add_download(cooked_storage, nat64info->url, nat64info->sha1); + auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); + if(info) + { + add_download(raw_storage, info->url, info->sha1); + } } } else { - auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); - if(info) - { - add_download(raw_storage, info->url, info->sha1); - } + qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS"; + } + } + else + { + if(m_mojangDownloads->artifact) + { + auto artifact = m_mojangDownloads->artifact; + add_download(raw_storage, artifact->url, artifact->sha1); + } + else + { + qDebug() << "Ignoring java library" << m_name << "because it has no artifact"; } } } diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp index 3f4828c9..1aac951b 100644 --- a/api/logic/minecraft/Library_test.cpp +++ b/api/logic/minecraft/Library_test.cpp @@ -2,7 +2,7 @@ #include "TestUtil.h" #include "minecraft/MojangVersionFormat.h" -#include "minecraft/onesix/OneSixVersionFormat.h" +#include "minecraft/OneSixVersionFormat.h" #include "minecraft/Library.h" #include "net/HttpMetaCache.h" #include "FileSystem.h" @@ -75,6 +75,7 @@ slots: test.setHint("local"); auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data")); QCOMPARE(downloads.size(), 0); + qDebug() << failedFiles; QCOMPARE(failedFiles.size(), 0); QStringList jar, native, native32, native64; @@ -240,10 +241,9 @@ slots: QCOMPARE(native64, {}); QStringList failedFiles; auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); + QCOMPARE(dls.size(), 1); QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar")); - QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); + QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); } void test_onenine_native_arch() { diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp index cb080bfe..0fffc99f 100644 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ b/api/logic/minecraft/MinecraftInstance.cpp @@ -17,15 +17,24 @@ #include "launch/steps/PreLaunchCommand.h" #include "launch/steps/TextPrint.h" #include "minecraft/launch/LauncherPartLaunch.h" +#include "minecraft/launch/DirectJavaLaunch.h" #include "minecraft/launch/ModMinecraftJar.h" #include "minecraft/launch/ClaimAccount.h" #include "java/launch/CheckJava.h" -#include <meta/Index.h> -#include <meta/VersionList.h> +#include "java/JavaUtils.h" +#include "meta/Index.h" +#include "meta/VersionList.h" -#include <icons/IIconList.h> +#include "ModList.h" +#include "WorldList.h" + +#include "icons/IIconList.h" #include <QCoreApplication> +#include "ComponentList.h" +#include "AssetsUtils.h" +#include "MinecraftUpdate.h" +#include "MinecraftLoadAndCheck.h" #define IBUS "@im=ibus" @@ -87,6 +96,53 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO // Minecraft launch method auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); + + // DEPRECATED: Read what versions the user configuration thinks should be used + m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); + m_settings->registerSetting("LWJGLVersion", ""); + m_settings->registerSetting("ForgeVersion", ""); + m_settings->registerSetting("LiteloaderVersion", ""); + + m_components.reset(new ComponentList(this)); + m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString()); + auto setting = m_settings->getSetting("LWJGLVersion"); + m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString()); + m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString()); + m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString()); +} + +void MinecraftInstance::init() +{ +} + +void MinecraftInstance::saveNow() +{ + m_components->saveNow(); +} + +QString MinecraftInstance::typeName() const +{ + return "Minecraft"; +} + +std::shared_ptr<ComponentList> MinecraftInstance::getComponentList() const +{ + return m_components; +} + +QSet<QString> MinecraftInstance::traits() const +{ + auto components = getComponentList(); + if (!components) + { + return {"version-incomplete"}; + } + auto profile = components->getProfile(); + if (!profile) + { + return {"version-incomplete"}; + } + return profile->getTraits(); } QString MinecraftInstance::minecraftRoot() const @@ -94,10 +150,10 @@ QString MinecraftInstance::minecraftRoot() const QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - if (dotMCDir.exists() && !mcDir.exists()) - return dotMCDir.filePath(); - else + if (mcDir.exists() && !dotMCDir.exists()) return mcDir.filePath(); + else + return dotMCDir.filePath(); } QString MinecraftInstance::binRoot() const @@ -105,9 +161,110 @@ QString MinecraftInstance::binRoot() const return FS::PathCombine(minecraftRoot(), "bin"); } -std::shared_ptr< BaseVersionList > MinecraftInstance::versionList() const +QString MinecraftInstance::getNativePath() const +{ + QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); + return natives_dir.absolutePath(); +} + +QString MinecraftInstance::getLocalLibraryPath() const +{ + QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/")); + return libraries_dir.absolutePath(); +} + +QString MinecraftInstance::loaderModsDir() const +{ + return FS::PathCombine(minecraftRoot(), "mods"); +} + +QString MinecraftInstance::coreModsDir() const +{ + return FS::PathCombine(minecraftRoot(), "coremods"); +} + +QString MinecraftInstance::resourcePacksDir() const +{ + return FS::PathCombine(minecraftRoot(), "resourcepacks"); +} + +QString MinecraftInstance::texturePacksDir() const +{ + return FS::PathCombine(minecraftRoot(), "texturepacks"); +} + +QString MinecraftInstance::instanceConfigFolder() const +{ + return FS::PathCombine(minecraftRoot(), "config"); +} + +QString MinecraftInstance::jarModsDir() const +{ + return FS::PathCombine(instanceRoot(), "jarmods"); +} + +QString MinecraftInstance::libDir() const +{ + return FS::PathCombine(minecraftRoot(), "lib"); +} + +QString MinecraftInstance::worldDir() const +{ + return FS::PathCombine(minecraftRoot(), "saves"); +} + +QDir MinecraftInstance::librariesPath() const +{ + return QDir::current().absoluteFilePath("libraries"); +} + +QDir MinecraftInstance::jarmodsPath() const +{ + return QDir(jarModsDir()); +} + +QDir MinecraftInstance::versionsPath() const +{ + return QDir::current().absoluteFilePath("versions"); +} + +QStringList MinecraftInstance::getClassPath() const +{ + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + auto profile = m_components->getProfile(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + return jars; +} + +QString MinecraftInstance::getMainClass() const +{ + auto profile = m_components->getProfile(); + return profile->getMainClass(); +} + +QStringList MinecraftInstance::getNativeJars() const { - return ENV.metadataIndex()->get("net.minecraft"); + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + auto profile = m_components->getProfile(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + return nativeJars; +} + +QStringList MinecraftInstance::extraArguments() const +{ + auto list = BaseInstance::extraArguments(); + auto version = getComponentList(); + if (!version) + return list; + auto jarMods = getJarMods(); + if (!jarMods.isEmpty()) + { + list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", + "-Dfml.ignorePatchDiscrepancies=true"}); + } + return list; } QStringList MinecraftInstance::javaArguments() const @@ -122,6 +279,15 @@ QStringList MinecraftInstance::javaArguments() const args << "-Xdock:icon=icon.png"; args << QString("-Xdock:name=\"%1\"").arg(windowTitle()); #endif + auto traits_ = traits(); + // HACK: fix issues on macOS with 1.13 snapshots + // NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them +#ifdef Q_OS_MAC + if(traits_.contains("FirstThreadOnMacOS")) + { + args << QString("-XstartOnFirstThread"); + } +#endif // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 #ifdef Q_OS_WIN32 @@ -129,8 +295,18 @@ QStringList MinecraftInstance::javaArguments() const "minecraft.exe.heapdump"); #endif - args << QString("-Xms%1m").arg(settings()->get("MinMemAlloc").toInt()); - args << QString("-Xmx%1m").arg(settings()->get("MaxMemAlloc").toInt()); + int min = settings()->get("MinMemAlloc").toInt(); + int max = settings()->get("MaxMemAlloc").toInt(); + if(min < max) + { + args << QString("-Xms%1m").arg(min); + args << QString("-Xmx%1m").arg(max); + } + else + { + args << QString("-Xms%1m").arg(max); + args << QString("-Xmx%1m").arg(min); + } // No PermGen in newer java. JavaVersion javaVersion = getJavaVersion(); @@ -160,100 +336,282 @@ QMap<QString, QString> MinecraftInstance::getVariables() const return out; } -static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH) +QProcessEnvironment MinecraftInstance::createEnvironment() +{ + // prepare the process environment + QProcessEnvironment env = CleanEnviroment(); + + // export some infos + auto variables = getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + env.insert(it.key(), it.value()); + } + return env; +} + +static QString replaceTokensIn(QString text, QMap<QString, QString> with) { - QDir mmcBin(QCoreApplication::applicationDirPath()); - auto items = LD_LIBRARY_PATH.split(':'); - QStringList final; - for(auto & item: items) + QString result; + QRegExp token_regexp("\\$\\{(.+)\\}"); + token_regexp.setMinimal(true); + QStringList list; + int tail = 0; + int head = 0; + while ((head = token_regexp.indexIn(text, head)) != -1) { - QDir test(item); - if(test == mmcBin) + result.append(text.mid(tail, head - tail)); + QString key = token_regexp.cap(1); + auto iter = with.find(key); + if (iter != with.end()) { - qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; - continue; + result.append(*iter); } - final.append(item); + head += token_regexp.matchedLength(); + tail = head; } - return final.join(':'); + result.append(text.mid(tail)); + return result; } -QProcessEnvironment MinecraftInstance::createEnvironment() +QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) const { - // prepare the process environment - QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); - QProcessEnvironment env; - - QStringList ignored = - { - "JAVA_ARGS", - "CLASSPATH", - "CONFIGPATH", - "JAVA_HOME", - "JRE_HOME", - "_JAVA_OPTIONS", - "JAVA_OPTIONS", - "JAVA_TOOL_OPTIONS" - }; - for(auto key: rawenv.keys()) + auto profile = m_components->getProfile(); + QString args_pattern = profile->getMinecraftArguments(); + for (auto tweaker : profile->getTweakers()) + { + args_pattern += " --tweakClass " + tweaker; + } + + QMap<QString, QString> token_mapping; + // yggdrasil! + if(session) + { + token_mapping["auth_username"] = session->username; + token_mapping["auth_session"] = session->session; + token_mapping["auth_access_token"] = session->access_token; + token_mapping["auth_player_name"] = session->player_name; + token_mapping["auth_uuid"] = session->uuid; + token_mapping["user_properties"] = session->serializeUserProperties(); + token_mapping["user_type"] = session->user_type; + } + + // blatant self-promotion. + token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; + + token_mapping["version_type"] = profile->getMinecraftVersionType(); + + QString absRootDir = QDir(minecraftRoot()).absolutePath(); + token_mapping["game_directory"] = absRootDir; + QString absAssetsDir = QDir("assets/").absolutePath(); + auto assets = profile->getMinecraftAssets(); + // FIXME: this is wrong and should be run as an async task + token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath(); + + // 1.7.3+ assets tokens + token_mapping["assets_root"] = absAssetsDir; + token_mapping["assets_index_name"] = assets->id; + + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); + for (int i = 0; i < parts.length(); i++) { - auto value = rawenv.value(key); - // filter out dangerous java crap - if(ignored.contains(key)) + parts[i] = replaceTokensIn(parts[i], token_mapping); + } + return parts; +} + +QString MinecraftInstance::createLaunchScript(AuthSessionPtr session) +{ + QString launchScript; + + if (!m_components) + return QString(); + auto profile = m_components->getProfile(); + if(!profile) + return QString(); + + auto mainClass = getMainClass(); + if (!mainClass.isEmpty()) + { + launchScript += "mainClass " + mainClass + "\n"; + } + auto appletClass = profile->getAppletClass(); + if (!appletClass.isEmpty()) + { + launchScript += "appletClass " + appletClass + "\n"; + } + + // generic minecraft params + for (auto param : processMinecraftArgs(session)) + { + launchScript += "param " + param + "\n"; + } + + // window size, title and state, legacy + { + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + windowParams = "max"; + else + windowParams = QString("%1x%2") + .arg(settings()->get("MinecraftWinWidth").toInt()) + .arg(settings()->get("MinecraftWinHeight").toInt()); + launchScript += "windowTitle " + windowTitle() + "\n"; + launchScript += "windowParams " + windowParams + "\n"; + } + + // legacy auth + if(session) + { + launchScript += "userName " + session->player_name + "\n"; + launchScript += "sessionId " + session->session + "\n"; + } + + // libraries and class path. + { + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + for(auto file: jars) { - qDebug() << "Env: ignoring" << key << value; - continue; + launchScript += "cp " + file + "\n"; } - // filter MultiMC-related things - if(key.startsWith("QT_")) + for(auto file: nativeJars) { - qDebug() << "Env: ignoring" << key << value; - continue; + launchScript += "ext " + file + "\n"; } -#ifdef Q_OS_LINUX - // Do not pass LD_* variables to java. They were intended for MultiMC - if(key.startsWith("LD_")) + launchScript += "natives " + getNativePath() + "\n"; + } + + for (auto trait : profile->getTraits()) + { + launchScript += "traits " + trait + "\n"; + } + launchScript += "launcher onesix\n"; + // qDebug() << "Generated launch script:" << launchScript; + return launchScript; +} + +QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session) +{ + QStringList out; + out << "Main Class:" << " " + getMainClass() << ""; + out << "Native path:" << " " + getNativePath() << ""; + + auto profile = m_components->getProfile(); + + auto alltraits = traits(); + if(alltraits.size()) + { + out << "Traits:"; + for (auto trait : alltraits) { - qDebug() << "Env: ignoring" << key << value; - continue; + out << "traits " + trait; } - // Strip IBus - // IBus is a Linux IME framework. For some reason, it breaks MC? - if (key == "XMODIFIERS" && value.contains(IBUS)) + out << ""; + } + + // libraries and class path. + { + out << "Libraries:"; + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + auto printLibFile = [&](const QString & path) + { + QFileInfo info(path); + if(info.exists()) + { + out << " " + path; + } + else + { + out << " " + path + " (missing)"; + } + }; + for(auto file: jars) { - QString save = value; - value.replace(IBUS, ""); - qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; + printLibFile(file); } - if(key == "GAME_PRELOAD") + out << ""; + out << "Native libraries:"; + for(auto file: nativeJars) { - env.insert("LD_PRELOAD", value); - continue; + printLibFile(file); } - if(key == "GAME_LIBRARY_PATH") + out << ""; + } + + if(loaderModList()->size()) + { + out << "Mods:"; + for(auto & mod: loaderModList()->allMods()) { - env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); - continue; + if(!mod.enabled()) + continue; + if(mod.type() == Mod::MOD_FOLDER) + continue; + // TODO: proper implementation would need to descend into folders. + + out << " " + mod.filename().completeBaseName(); } -#endif - qDebug() << "Env: " << key << value; - env.insert(key, value); + out << ""; } -#ifdef Q_OS_LINUX - // HACK: Workaround for QTBUG42500 - if(!env.contains("LD_LIBRARY_PATH")) + + if(coreModList()->size()) { - env.insert("LD_LIBRARY_PATH", ""); + out << "Core Mods:"; + for(auto & coremod: coreModList()->allMods()) + { + if(!coremod.enabled()) + continue; + if(coremod.type() == Mod::MOD_FOLDER) + continue; + // TODO: proper implementation would need to descend into folders. + + out << " " + coremod.filename().completeBaseName(); + } + out << ""; } -#endif - // export some infos - auto variables = getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) + auto & jarMods = profile->getJarMods(); + if(jarMods.size()) { - env.insert(it.key(), it.value()); + out << "Jar Mods:"; + for(auto & jarmod: jarMods) + { + auto displayname = jarmod->displayName(currentSystem); + auto realname = jarmod->filename(currentSystem); + if(displayname != realname) + { + out << " " + displayname + " (" + realname + ")"; + } + else + { + out << " " + realname; + } + } + out << ""; } - return env; + + auto params = processMinecraftArgs(nullptr); + out << "Params:"; + out << " " + params.join(' '); + out << ""; + + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + { + out << "Window size: max (if available)"; + } + else + { + auto width = settings()->get("MinecraftWinWidth").toInt(); + auto height = settings()->get("MinecraftWinHeight").toInt(); + out << "Window size: " + QString::number(width) + " x " + QString::number(height); + } + out << ""; + return out; } QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session) @@ -278,7 +636,6 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>")); addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>")); addToFilter(sessionRef.uuid, tr("<PROFILE ID>")); - addToFilter(sessionRef.player_name, tr("<PROFILE NAME>")); auto i = sessionRef.u.properties.begin(); while (i != sessionRef.u.properties.end()) @@ -384,7 +741,7 @@ QString MinecraftInstance::getStatusbarDescription() } QString description; - description.append(tr("Minecraft %1 (%2)").arg(intendedVersionId()).arg(typeName())); + description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); if(totalTimePlayed() > 0) { description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); @@ -396,6 +753,22 @@ QString MinecraftInstance::getStatusbarDescription() return description; } +shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode) +{ + switch (mode) + { + case Net::Mode::Offline: + { + return shared_qobject_ptr<Task>(new MinecraftLoadAndCheck(this)); + } + case Net::Mode::Online: + { + return shared_qobject_ptr<Task>(new OneSixUpdate(this)); + } + } + return nullptr; +} + std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session) { auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr())); @@ -415,7 +788,7 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s } // check launch method - QStringList validMethods = validLaunchMethods(); + QStringList validMethods = {"LauncherPart", "DirectJava"}; QString method = launchMethod(); if(!validMethods.contains(method)) { @@ -435,11 +808,14 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s if(session->status != AuthSession::PlayableOffline) { process->appendStep(std::make_shared<ClaimAccount>(pptr, session)); - process->appendStep(std::make_shared<Update>(pptr)); + process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Online)); + } + else + { + process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Offline)); } // if there are any jar mods - if(getJarMods().size()) { auto step = std::make_shared<ModMinecraftJar>(pptr); process->appendStep(step); @@ -465,8 +841,21 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s { // actually launch the game - auto step = createMainLaunchStep(pptr, session); - process->appendStep(step); + auto method = launchMethod(); + if(method == "LauncherPart") + { + auto step = std::make_shared<LauncherPartLaunch>(pptr); + step->setWorkingDirectory(minecraftRoot()); + step->setAuthSession(session); + process->appendStep(step); + } + else if (method == "DirectJava") + { + auto step = std::make_shared<DirectJavaLaunch>(pptr); + step->setWorkingDirectory(minecraftRoot()); + step->setAuthSession(session); + process->appendStep(step); + } } // run post-exit command if that's needed @@ -495,5 +884,68 @@ JavaVersion MinecraftInstance::getJavaVersion() const return JavaVersion(settings()->get("JavaVersion").toString()); } +std::shared_ptr<ModList> MinecraftInstance::loaderModList() const +{ + if (!m_loader_mod_list) + { + m_loader_mod_list.reset(new ModList(loaderModsDir())); + } + m_loader_mod_list->update(); + return m_loader_mod_list; +} + +std::shared_ptr<ModList> MinecraftInstance::coreModList() const +{ + if (!m_core_mod_list) + { + m_core_mod_list.reset(new ModList(coreModsDir())); + } + m_core_mod_list->update(); + return m_core_mod_list; +} + +std::shared_ptr<ModList> MinecraftInstance::resourcePackList() const +{ + if (!m_resource_pack_list) + { + m_resource_pack_list.reset(new ModList(resourcePacksDir())); + } + m_resource_pack_list->update(); + return m_resource_pack_list; +} + +std::shared_ptr<ModList> MinecraftInstance::texturePackList() const +{ + if (!m_texture_pack_list) + { + m_texture_pack_list.reset(new ModList(texturePacksDir())); + } + m_texture_pack_list->update(); + return m_texture_pack_list; +} + +std::shared_ptr<WorldList> MinecraftInstance::worldList() const +{ + if (!m_world_list) + { + m_world_list.reset(new WorldList(worldDir())); + } + return m_world_list; +} + +QList< Mod > MinecraftInstance::getJarMods() const +{ + auto profile = m_components->getProfile(); + QList<Mod> mods; + for (auto jarmod : profile->getJarMods()) + { + QStringList jar, temp1, temp2, temp3; + jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); + // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); + mods.push_back(Mod(QFileInfo(jar[0]))); + } + return mods; +} + #include "MinecraftInstance.moc" diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h index 7f967ce0..446a39d5 100644 --- a/api/logic/minecraft/MinecraftInstance.h +++ b/api/logic/minecraft/MinecraftInstance.h @@ -3,88 +3,122 @@ #include <java/JavaVersion.h> #include "minecraft/Mod.h" #include <QProcess> - +#include <QDir> #include "multimc_logic_export.h" class ModList; class WorldList; class LaunchStep; +class ComponentList; class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance { + Q_OBJECT public: MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); virtual ~MinecraftInstance() {}; + virtual void init() override; + virtual void saveNow(); - /// Path to the instance's minecraft directory. - QString minecraftRoot() const; - - /// Path to the instance's minecraft/bin directory. - QString binRoot() const; + // FIXME: remove + QString typeName() const override; + // FIXME: remove + QSet<QString> traits() const override; - ////// Mod Lists ////// - virtual std::shared_ptr<ModList> resourcePackList() const - { - return nullptr; - } - virtual std::shared_ptr<ModList> texturePackList() const - { - return nullptr; - } - virtual std::shared_ptr<WorldList> worldList() const + bool canEdit() const override { - return nullptr; + return true; } - /// get all jar mods applicable to this instance's jar - virtual QList<Mod> getJarMods() const + + bool canExport() const override { - return QList<Mod>(); + return true; } - virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override; - virtual QString createLaunchScript(AuthSessionPtr session) = 0; - - //FIXME: nuke? - virtual std::shared_ptr<BaseVersionList> versionList() const override; + ////// Directories and files ////// + QString jarModsDir() const; + QString resourcePacksDir() const; + QString texturePacksDir() const; + QString loaderModsDir() const; + QString coreModsDir() const; + QString libDir() const; + QString worldDir() const; + QDir jarmodsPath() const; + QDir librariesPath() const; + QDir versionsPath() const; + QString instanceConfigFolder() const override; + QString minecraftRoot() const; // Path to the instance's minecraft directory. + QString binRoot() const; // Path to the instance's minecraft bin directory. + QString getNativePath() const; // where to put the natives during/before launch + QString getLocalLibraryPath() const; // where the instance-local libraries should be + + + ////// Profile management ////// + std::shared_ptr<ComponentList> getComponentList() const; + ////// Mod Lists ////// + std::shared_ptr<ModList> loaderModList() const; + std::shared_ptr<ModList> coreModList() const; + std::shared_ptr<ModList> resourcePackList() const; + std::shared_ptr<ModList> texturePackList() const; + std::shared_ptr<WorldList> worldList() const; + + + ////// Launch stuff ////// + shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override; + std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override; + QStringList extraArguments() const override; + QStringList verboseDescription(AuthSessionPtr session) override; + QList<Mod> getJarMods() const; + QString createLaunchScript(AuthSessionPtr session); /// get arguments passed to java QStringList javaArguments() const; /// get variables for launch command variable substitution/environment - virtual QMap<QString, QString> getVariables() const override; + QMap<QString, QString> getVariables() const override; /// create an environment for launching processes - virtual QProcessEnvironment createEnvironment() override; + QProcessEnvironment createEnvironment() override; /// guess log level from a line of minecraft log - virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; - - virtual IPathMatcher::Ptr getLogFileMatcher() override; - - virtual QString getLogFileRoot() override; + MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; - virtual QString getStatusbarDescription() override; + IPathMatcher::Ptr getLogFileMatcher() override; - virtual QStringList getClassPath() const = 0; - virtual QStringList getNativeJars() const = 0; + QString getLogFileRoot() override; - virtual QString getMainClass() const = 0; + QString getStatusbarDescription() override; - virtual QString getNativePath() const = 0; + // FIXME: remove + virtual QStringList getClassPath() const; + // FIXME: remove + virtual QStringList getNativeJars() const; + // FIXME: remove + virtual QString getMainClass() const; - virtual QString getLocalLibraryPath() const = 0; - - virtual QStringList processMinecraftArgs(AuthSessionPtr account) const = 0; + // FIXME: remove + virtual QStringList processMinecraftArgs(AuthSessionPtr account) const; virtual JavaVersion getJavaVersion() const; +signals: + void versionReloaded(); + protected: QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session); - virtual QStringList validLaunchMethods() = 0; - virtual QString launchMethod(); - virtual std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) = 0; + QStringList validLaunchMethods(); + QString launchMethod(); + private: QString prettifyTimeDuration(int64_t duration); + +protected: // data + std::shared_ptr<ComponentList> m_components; + mutable std::shared_ptr<ModList> m_loader_mod_list; + mutable std::shared_ptr<ModList> m_core_mod_list; + mutable std::shared_ptr<ModList> m_resource_pack_list; + mutable std::shared_ptr<ModList> m_texture_pack_list; + mutable std::shared_ptr<WorldList> m_world_list; }; typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr; diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.cpp b/api/logic/minecraft/MinecraftLoadAndCheck.cpp new file mode 100644 index 00000000..c64bbddf --- /dev/null +++ b/api/logic/minecraft/MinecraftLoadAndCheck.cpp @@ -0,0 +1,45 @@ +#include "MinecraftLoadAndCheck.h" +#include "MinecraftInstance.h" +#include "ComponentList.h" + +MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) +{ +} + +void MinecraftLoadAndCheck::executeTask() +{ + // add offline metadata load task + auto components = m_inst->getComponentList(); + components->reload(Net::Mode::Offline); + m_task = components->getCurrentTask(); + + if(!m_task) + { + emitSucceeded(); + return; + } + connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded); + connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); + connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); + connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); +} + +void MinecraftLoadAndCheck::subtaskSucceeded() +{ + if(isFinished()) + { + qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!"; + return; + } + emitSucceeded(); +} + +void MinecraftLoadAndCheck::subtaskFailed(QString error) +{ + if(isFinished()) + { + qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!"; + return; + } + emitFailed(error); +} diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h new file mode 100644 index 00000000..00515f2d --- /dev/null +++ b/api/logic/minecraft/MinecraftLoadAndCheck.h @@ -0,0 +1,47 @@ +/* Copyright 2013-2018 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QObject> +#include <QList> +#include <QUrl> + +#include "tasks/Task.h" +#include <quazip.h> + +#include "QObjectPtr.h" + +class MinecraftVersion; +class MinecraftInstance; + +class MinecraftLoadAndCheck : public Task +{ + Q_OBJECT +public: + explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0); + void executeTask() override; + +private slots: + void subtaskSucceeded(); + void subtaskFailed(QString error); + +private: + MinecraftInstance *m_inst = nullptr; + shared_qobject_ptr<Task> m_task; + QString m_preFailure; + QString m_fail_reason; +}; + diff --git a/api/logic/minecraft/MinecraftProfile.cpp b/api/logic/minecraft/MinecraftProfile.cpp deleted file mode 100644 index 4c1ab818..00000000 --- a/api/logic/minecraft/MinecraftProfile.cpp +++ /dev/null @@ -1,681 +0,0 @@ -/* Copyright 2013-2017 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 <QFile> -#include <QCryptographicHash> -#include <Version.h> -#include <QDir> -#include <QJsonDocument> -#include <QJsonArray> -#include <QDebug> - -#include "minecraft/MinecraftProfile.h" -#include "ProfileUtils.h" -#include "ProfileStrategy.h" -#include "Exception.h" - -MinecraftProfile::MinecraftProfile(ProfileStrategy *strategy) - : QAbstractListModel() -{ - setStrategy(strategy); - clear(); -} - -MinecraftProfile::~MinecraftProfile() -{ - if(m_strategy) - { - delete m_strategy; - } -} - -void MinecraftProfile::setStrategy(ProfileStrategy* strategy) -{ - Q_ASSERT(strategy != nullptr); - - if(m_strategy != nullptr) - { - delete m_strategy; - m_strategy = nullptr; - } - m_strategy = strategy; - m_strategy->profile = this; -} - -ProfileStrategy* MinecraftProfile::strategy() -{ - return m_strategy; -} - -void MinecraftProfile::reload() -{ - beginResetModel(); - m_strategy->load(); - reapplyPatches(); - endResetModel(); -} - -void MinecraftProfile::clear() -{ - m_minecraftVersion.clear(); - m_minecraftVersionType.clear(); - m_minecraftAssets.reset(); - m_minecraftArguments.clear(); - m_tweakers.clear(); - m_mainClass.clear(); - m_appletClass.clear(); - m_libraries.clear(); - m_traits.clear(); - m_jarMods.clear(); - m_mainJar.reset(); - m_problemSeverity = ProblemSeverity::None; -} - -void MinecraftProfile::clearPatches() -{ - beginResetModel(); - m_patches.clear(); - endResetModel(); -} - -void MinecraftProfile::appendPatch(ProfilePatchPtr patch) -{ - int index = m_patches.size(); - beginInsertRows(QModelIndex(), index, index); - m_patches.append(patch); - endInsertRows(); -} - -bool MinecraftProfile::remove(const int index) -{ - auto patch = versionPatch(index); - if (!patch->isRemovable()) - { - qDebug() << "Patch" << patch->getID() << "is non-removable"; - return false; - } - - if(!m_strategy->removePatch(patch)) - { - qCritical() << "Patch" << patch->getID() << "could not be removed"; - return false; - } - - beginRemoveRows(QModelIndex(), index, index); - m_patches.removeAt(index); - endRemoveRows(); - reapplyPatches(); - saveCurrentOrder(); - return true; -} - -bool MinecraftProfile::remove(const QString id) -{ - int i = 0; - for (auto patch : m_patches) - { - if (patch->getID() == id) - { - return remove(i); - } - i++; - } - return false; -} - -bool MinecraftProfile::customize(int index) -{ - auto patch = versionPatch(index); - if (!patch->isCustomizable()) - { - qDebug() << "Patch" << patch->getID() << "is not customizable"; - return false; - } - if(!m_strategy->customizePatch(patch)) - { - qCritical() << "Patch" << patch->getID() << "could not be customized"; - return false; - } - reapplyPatches(); - saveCurrentOrder(); - // FIXME: maybe later in unstable - // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); - return true; -} - -bool MinecraftProfile::revertToBase(int index) -{ - auto patch = versionPatch(index); - if (!patch->isRevertible()) - { - qDebug() << "Patch" << patch->getID() << "is not revertible"; - return false; - } - if(!m_strategy->revertPatch(patch)) - { - qCritical() << "Patch" << patch->getID() << "could not be reverted"; - return false; - } - reapplyPatches(); - saveCurrentOrder(); - // FIXME: maybe later in unstable - // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); - return true; -} - -ProfilePatchPtr MinecraftProfile::versionPatch(const QString &id) -{ - for (auto patch : m_patches) - { - if (patch->getID() == id) - { - return patch; - } - } - return nullptr; -} - -ProfilePatchPtr MinecraftProfile::versionPatch(int index) -{ - if(index < 0 || index >= m_patches.size()) - return nullptr; - return m_patches[index]; -} - -bool MinecraftProfile::isVanilla() -{ - for(auto patchptr: m_patches) - { - if(patchptr->isCustom()) - return false; - } - return true; -} - -bool MinecraftProfile::revertToVanilla() -{ - // remove patches, if present - auto VersionPatchesCopy = m_patches; - for(auto & it: VersionPatchesCopy) - { - if (!it->isCustom()) - { - continue; - } - if(it->isRevertible() || it->isRemovable()) - { - if(!remove(it->getID())) - { - qWarning() << "Couldn't remove" << it->getID() << "from profile!"; - reapplyPatches(); - saveCurrentOrder(); - return false; - } - } - } - reapplyPatches(); - saveCurrentOrder(); - return true; -} - -QVariant MinecraftProfile::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= m_patches.size()) - return QVariant(); - - auto patch = m_patches.at(row); - - if (role == Qt::DisplayRole) - { - switch (column) - { - case 0: - return m_patches.at(row)->getName(); - case 1: - { - if(patch->isCustom()) - { - return QString("%1 (Custom)").arg(patch->getVersion()); - } - else - { - return patch->getVersion(); - } - } - default: - return QVariant(); - } - } - if(role == Qt::DecorationRole) - { - switch(column) - { - case 0: - { - auto severity = patch->getProblemSeverity(); - switch (severity) - { - case ProblemSeverity::Warning: - return "warning"; - case ProblemSeverity::Error: - return "error"; - default: - return QVariant(); - } - } - default: - { - return QVariant(); - } - } - } - return QVariant(); -} -QVariant MinecraftProfile::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 MinecraftProfile::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; -} - -int MinecraftProfile::rowCount(const QModelIndex &parent) const -{ - return m_patches.size(); -} - -int MinecraftProfile::columnCount(const QModelIndex &parent) const -{ - return 2; -} - -void MinecraftProfile::saveCurrentOrder() const -{ - ProfileUtils::PatchOrder order; - for(auto item: m_patches) - { - if(!item->isMoveable()) - continue; - order.append(item->getID()); - } - m_strategy->saveOrder(order); -} - -void MinecraftProfile::move(const int index, const MoveDirection direction) -{ - int theirIndex; - if (direction == MoveUp) - { - theirIndex = index - 1; - } - else - { - theirIndex = index + 1; - } - - if (index < 0 || index >= m_patches.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); - m_patches.swap(index, theirIndex); - endMoveRows(); - reapplyPatches(); - saveCurrentOrder(); -} -void MinecraftProfile::resetOrder() -{ - m_strategy->resetOrder(); - reload(); -} - -bool MinecraftProfile::reapplyPatches() -{ - try - { - clear(); - for(auto file: m_patches) - { - qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); - file->applyTo(this); - } - } - catch (Exception & error) - { - clear(); - qWarning() << "Couldn't apply profile patches because: " << error.cause(); - return false; - } - return true; -} - -static void applyString(const QString & from, QString & to) -{ - if(from.isEmpty()) - return; - to = from; -} - -void MinecraftProfile::applyMinecraftVersion(const QString& id) -{ - applyString(id, this->m_minecraftVersion); -} - -void MinecraftProfile::applyAppletClass(const QString& appletClass) -{ - applyString(appletClass, this->m_appletClass); -} - -void MinecraftProfile::applyMainClass(const QString& mainClass) -{ - applyString(mainClass, this->m_mainClass); -} - -void MinecraftProfile::applyMinecraftArguments(const QString& minecraftArguments) -{ - applyString(minecraftArguments, this->m_minecraftArguments); -} - -void MinecraftProfile::applyMinecraftVersionType(const QString& type) -{ - applyString(type, this->m_minecraftVersionType); -} - -void MinecraftProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets) -{ - if(assets) - { - m_minecraftAssets = assets; - } -} - -void MinecraftProfile::applyTraits(const QSet<QString>& traits) -{ - this->m_traits.unite(traits); -} - -void MinecraftProfile::applyTweakers(const QStringList& tweakers) -{ - // FIXME: check for dupes? - // FIXME: does order matter? - for (auto tweaker : tweakers) - { - this->m_tweakers += tweaker; - } -} - -void MinecraftProfile::applyJarMods(const QList<LibraryPtr>& jarMods) -{ - this->m_jarMods.append(jarMods); -} - -static int findLibraryByName(QList<LibraryPtr> *haystack, const GradleSpecifier &needle) -{ - int retval = -1; - for (int i = 0; i < haystack->size(); ++i) - { - if (haystack->at(i)->rawName().matchName(needle)) - { - // only one is allowed. - if (retval != -1) - return -1; - retval = i; - } - } - return retval; -} - -void MinecraftProfile::applyMods(const QList<LibraryPtr>& mods) -{ - QList<LibraryPtr> * list = &m_mods; - for(auto & mod: mods) - { - auto modCopy = Library::limitedCopy(mod); - - // find the mod by name. - const int index = findLibraryByName(list, mod->rawName()); - // mod not found? just add it. - if (index < 0) - { - list->append(modCopy); - return; - } - - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(mod->version()) > Version(existingLibrary->version())) - { - list->replace(index, modCopy); - } - } -} - -void MinecraftProfile::applyLibrary(LibraryPtr library) -{ - if(!library->isActive()) - { - return; - } - - QList<LibraryPtr> * list = &m_libraries; - if(library->isNative()) - { - list = &m_nativeLibraries; - } - - auto libraryCopy = Library::limitedCopy(library); - - // find the library by name. - const int index = findLibraryByName(list, library->rawName()); - // library not found? just add it. - if (index < 0) - { - list->append(libraryCopy); - return; - } - - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(library->version()) > Version(existingLibrary->version())) - { - list->replace(index, libraryCopy); - } -} - -const LibraryPtr MinecraftProfile::getMainJar() const -{ - return m_mainJar; -} - -void MinecraftProfile::applyMainJar(LibraryPtr jar) -{ - if(jar) - { - m_mainJar = jar; - } -} - -void MinecraftProfile::applyProblemSeverity(ProblemSeverity severity) -{ - if (m_problemSeverity < severity) - { - m_problemSeverity = severity; - } -} - - -QString MinecraftProfile::getMinecraftVersion() const -{ - return m_minecraftVersion; -} - -QString MinecraftProfile::getAppletClass() const -{ - return m_appletClass; -} - -QString MinecraftProfile::getMainClass() const -{ - return m_mainClass; -} - -const QSet<QString> &MinecraftProfile::getTraits() const -{ - return m_traits; -} - -const QStringList & MinecraftProfile::getTweakers() const -{ - return m_tweakers; -} - -bool MinecraftProfile::hasTrait(const QString& trait) const -{ - return m_traits.contains(trait); -} - -ProblemSeverity MinecraftProfile::getProblemSeverity() const -{ - return m_problemSeverity; -} - -QString MinecraftProfile::getMinecraftVersionType() const -{ - return m_minecraftVersionType; -} - -std::shared_ptr<MojangAssetIndexInfo> MinecraftProfile::getMinecraftAssets() const -{ - if(!m_minecraftAssets) - { - return std::make_shared<MojangAssetIndexInfo>("legacy"); - } - return m_minecraftAssets; -} - -QString MinecraftProfile::getMinecraftArguments() const -{ - return m_minecraftArguments; -} - -const QList<LibraryPtr> & MinecraftProfile::getJarMods() const -{ - return m_jarMods; -} - -const QList<LibraryPtr> & MinecraftProfile::getLibraries() const -{ - return m_libraries; -} - -const QList<LibraryPtr> & MinecraftProfile::getNativeLibraries() const -{ - return m_nativeLibraries; -} - -void MinecraftProfile::getLibraryFiles(const QString& architecture, QStringList& jars, QStringList& nativeJars, const QString& overridePath, const QString& tempPath) const -{ - QStringList native32, native64; - jars.clear(); - nativeJars.clear(); - for (auto lib : getLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - // NOTE: order is important here, add main jar last to the lists - if(m_mainJar) - { - // FIXME: HACK!! jar modding is weird and unsystematic! - if(m_jarMods.size()) - { - QDir tempDir(tempPath); - jars.append(tempDir.absoluteFilePath("minecraft.jar")); - } - else - { - m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - } - for (auto lib : getNativeLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - if(architecture == "32") - { - nativeJars.append(native32); - } - else if(architecture == "64") - { - nativeJars.append(native64); - } -} - -void MinecraftProfile::installJarMods(QStringList selectedFiles) -{ - m_strategy->installJarMods(selectedFiles); -} - -/* - * TODO: get rid of this. Get rid of all order numbers. - */ -int MinecraftProfile::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: m_patches) - { - int order = thing->getOrder(); - if(order > largest) - largest = order; - } - return largest + 1; -} diff --git a/api/logic/minecraft/MinecraftProfile.h b/api/logic/minecraft/MinecraftProfile.h deleted file mode 100644 index 192c77f9..00000000 --- a/api/logic/minecraft/MinecraftProfile.h +++ /dev/null @@ -1,211 +0,0 @@ -/* Copyright 2013-2017 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 <QAbstractListModel> - -#include <QString> -#include <QList> -#include <memory> - -#include "Library.h" -#include "ProfilePatch.h" -#include "BaseVersion.h" -#include "MojangDownloadInfo.h" - -#include "multimc_logic_export.h" - -class ProfileStrategy; -class OneSixInstance; - - -class MULTIMC_LOGIC_EXPORT MinecraftProfile : public QAbstractListModel -{ - Q_OBJECT - -public: - explicit MinecraftProfile(ProfileStrategy *strategy); - virtual ~MinecraftProfile(); - - void setStrategy(ProfileStrategy * strategy); - ProfileStrategy *strategy(); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - virtual int columnCount(const QModelIndex &parent) const override; - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - - /// is this version unchanged by the user? - bool isVanilla(); - - /// remove any customizations on top of whatever 'vanilla' means - bool revertToVanilla(); - - /// install more jar mods - void installJarMods(QStringList selectedFiles); - - /// DEPRECATED, remove ASAP - int getFreeOrderNumber(); - - enum MoveDirection { MoveUp, MoveDown }; - /// move patch file # up or down the list - void move(const int index, const MoveDirection direction); - - /// remove patch file # - including files/records - bool remove(const int index); - - /// remove patch file by id - including files/records - bool remove(const QString id); - - bool customize(int index); - - bool revertToBase(int index); - - void resetOrder(); - - /// reload all profile patches from storage, clear the profile and apply the patches - void reload(); - - /// clear the profile - void clear(); - - /// apply the patches. Catches all the errors and returns true/false for success/failure - bool reapplyPatches(); - -public: /* application of profile variables from patches */ - void applyMinecraftVersion(const QString& id); - void applyMainClass(const QString& mainClass); - void applyAppletClass(const QString& appletClass); - void applyMinecraftArguments(const QString& minecraftArguments); - void applyMinecraftVersionType(const QString& type); - void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); - void applyTraits(const QSet<QString> &traits); - void applyTweakers(const QStringList &tweakers); - void applyJarMods(const QList<LibraryPtr> &jarMods); - void applyMods(const QList<LibraryPtr> &jarMods); - void applyLibrary(LibraryPtr library); - void applyMainJar(LibraryPtr jar); - void applyProblemSeverity(ProblemSeverity severity); - -public: /* getters for profile variables */ - QString getMinecraftVersion() const; - QString getMainClass() const; - QString getAppletClass() const; - QString getMinecraftVersionType() const; - MojangAssetIndexInfo::Ptr getMinecraftAssets() const; - QString getMinecraftArguments() const; - const QSet<QString> & getTraits() const; - const QStringList & getTweakers() const; - const QList<LibraryPtr> & getJarMods() const; - const QList<LibraryPtr> & getLibraries() const; - const QList<LibraryPtr> & getNativeLibraries() const; - const LibraryPtr getMainJar() const; - void getLibraryFiles(const QString & architecture, QStringList & jars, QStringList & nativeJars, const QString & overridePath, - const QString & tempPath) const; - bool hasTrait(const QString & trait) const; - ProblemSeverity getProblemSeverity() const; - -public: - /// get the profile patch by id - ProfilePatchPtr versionPatch(const QString &id); - - /// get the profile patch by index - ProfilePatchPtr versionPatch(int index); - - /// save the current patch order - void saveCurrentOrder() const; - - /// Remove all the patches - void clearPatches(); - - /// Add the patch object to the internal list of patches - void appendPatch(ProfilePatchPtr patch); - -private: /* data */ - /// the version of Minecraft - jar to use - QString m_minecraftVersion; - - /// Release type - "release" or "snapshot" - QString m_minecraftVersionType; - - /// Assets type - "legacy" or a version ID - MojangAssetIndexInfo::Ptr m_minecraftAssets; - - /** - * arguments that should be used for launching minecraft - * - * ex: "--username ${auth_player_name} --session ${auth_session} - * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" - */ - QString m_minecraftArguments; - - /// A list of all tweaker classes - QStringList m_tweakers; - - /// The main class to load first - QString m_mainClass; - - /// The applet class, for some very old minecraft releases - QString m_appletClass; - - /// the list of libraries - QList<LibraryPtr> m_libraries; - - /// the main jar - LibraryPtr m_mainJar; - - /// the list of libraries - QList<LibraryPtr> m_nativeLibraries; - - /// traits, collected from all the version files (version files can only add) - QSet<QString> m_traits; - - /// A list of jar mods. version files can add those. - QList<LibraryPtr> m_jarMods; - - /// the list of mods - QList<LibraryPtr> m_mods; - - ProblemSeverity m_problemSeverity = ProblemSeverity::None; - - /* - FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. - - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx", - "version": "^10\\.5\\.\\d$" - } - } - ], - "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX - 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" - } - */ - // QList<Rule> rules; - - /// list of attached profile patches - QList<ProfilePatchPtr> m_patches; - - /// strategy used for profile operations - ProfileStrategy *m_strategy = nullptr; -}; diff --git a/api/logic/minecraft/onesix/OneSixUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp index e0027032..86835fa4 100644 --- a/api/logic/minecraft/onesix/OneSixUpdate.cpp +++ b/api/logic/minecraft/MinecraftUpdate.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ #include "Env.h" #include <minecraft/forge/ForgeXzDownload.h> -#include "OneSixUpdate.h" -#include "OneSixInstance.h" +#include "MinecraftUpdate.h" +#include "MinecraftInstance.h" #include <QFile> #include <QFileInfo> @@ -24,7 +24,7 @@ #include <QDataStream> #include "BaseInstance.h" -#include "minecraft/MinecraftProfile.h" +#include "minecraft/ComponentList.h" #include "minecraft/Library.h" #include "net/URLConstants.h" #include <FileSystem.h> @@ -37,42 +37,26 @@ #include <meta/Index.h> #include <meta/Version.h> -OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) +OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { +} + +void OneSixUpdate::executeTask() +{ + m_tasks.clear(); // create folders { m_tasks.append(std::make_shared<FoldersTask>(m_inst)); } - // add metadata update tasks, if necessary + // add metadata update task if necessary { - /* - * FIXME: there are some corner cases here that remain unhandled: - * what if local load succeeds but remote fails? The version is still usable... - * We should not rely on the remote to be there... and prefer local files if it does not respond. - */ - qDebug() << "Updating patches..."; - auto profile = m_inst->getMinecraftProfile(); - m_inst->reloadProfile(); - for(int i = 0; i < profile->rowCount(); i++) + auto components = m_inst->getComponentList(); + components->reload(Net::Mode::Online); + auto task = components->getCurrentTask(); + if(task) { - auto patch = profile->versionPatch(i); - auto id = patch->getID(); - auto metadata = patch->getMeta(); - if(metadata) - { - metadata->load(); - auto task = metadata->getCurrentTask(); - if(task) - { - qDebug() << "Loading remote meta patch" << id; - m_tasks.append(task.unwrap()); - } - } - else - { - qDebug() << "Ignoring local patch" << id; - } + m_tasks.append(task.unwrap()); } } @@ -90,10 +74,7 @@ OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent) { m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst)); } -} -void OneSixUpdate::executeTask() -{ if(!m_preFailure.isEmpty()) { emitFailed(m_preFailure); @@ -109,6 +90,11 @@ void OneSixUpdate::next() emitFailed(tr("Aborted by user.")); return; } + if(m_failed_out_of_order) + { + emitFailed(m_fail_reason); + return; + } m_currentTask ++; if(m_currentTask > 0) { @@ -127,6 +113,7 @@ void OneSixUpdate::next() // if the task is already finished by the time we look at it, skip it if(task->isFinished()) { + qCritical() << "OneSixUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get(); next(); } connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded); @@ -142,11 +129,37 @@ void OneSixUpdate::next() void OneSixUpdate::subtaskSucceeded() { + if(isFinished()) + { + qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!"; + return; + } + auto senderTask = QObject::sender(); + auto currentTask = m_tasks[m_currentTask].get(); + if(senderTask != currentTask) + { + qDebug() << "OneSixUpdate: Subtask" << sender() << "succeeded out of order."; + return; + } next(); } void OneSixUpdate::subtaskFailed(QString error) { + if(isFinished()) + { + qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!"; + return; + } + auto senderTask = QObject::sender(); + auto currentTask = m_tasks[m_currentTask].get(); + if(senderTask != currentTask) + { + qDebug() << "OneSixUpdate: Subtask" << sender() << "failed out of order."; + m_failed_out_of_order = true; + m_fail_reason = error; + return; + } emitFailed(error); } diff --git a/api/logic/minecraft/onesix/OneSixUpdate.h b/api/logic/minecraft/MinecraftUpdate.h index 6bcfd41a..78c02049 100644 --- a/api/logic/minecraft/onesix/OneSixUpdate.h +++ b/api/logic/minecraft/MinecraftUpdate.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,13 +25,13 @@ #include <quazip.h> class MinecraftVersion; -class OneSixInstance; +class MinecraftInstance; class OneSixUpdate : public Task { Q_OBJECT public: - explicit OneSixUpdate(OneSixInstance *inst, QObject *parent = 0); + explicit OneSixUpdate(MinecraftInstance *inst, QObject *parent = 0); void executeTask() override; bool canAbort() const override; @@ -45,9 +45,11 @@ private: void next(); private: - OneSixInstance *m_inst = nullptr; + MinecraftInstance *m_inst = nullptr; QList<std::shared_ptr<Task>> m_tasks; QString m_preFailure; int m_currentTask = -1; bool m_abort = false; + bool m_failed_out_of_order = false; + QString m_fail_reason; }; diff --git a/api/logic/minecraft/Mod.cpp b/api/logic/minecraft/Mod.cpp index 5b4ecbb6..03e04b2b 100644 --- a/api/logic/minecraft/Mod.cpp +++ b/api/logic/minecraft/Mod.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/Mod.h b/api/logic/minecraft/Mod.h index 96ee8174..ccab1867 100644 --- a/api/logic/minecraft/Mod.h +++ b/api/logic/minecraft/Mod.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,8 @@ public: } QString name() const { - if(m_name.trimmed().isEmpty()) + QString name = m_name.trimmed(); + if(name.isEmpty() || name == "Example Mod") { return m_mmc_id; } diff --git a/api/logic/minecraft/ModList.cpp b/api/logic/minecraft/ModList.cpp index 02b09eef..6ccf20e2 100644 --- a/api/logic/minecraft/ModList.cpp +++ b/api/logic/minecraft/ModList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/ModList.h b/api/logic/minecraft/ModList.h index 8cdd2d9a..72f50edb 100644 --- a/api/logic/minecraft/ModList.h +++ b/api/logic/minecraft/ModList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/MojangVersionFormat.cpp b/api/logic/minecraft/MojangVersionFormat.cpp index f73c8e49..a0aa2894 100644 --- a/api/logic/minecraft/MojangVersionFormat.cpp +++ b/api/logic/minecraft/MojangVersionFormat.cpp @@ -1,5 +1,5 @@ #include "MojangVersionFormat.h" -#include "onesix/OneSixVersionFormat.h" +#include "OneSixVersionFormat.h" #include "MojangDownloadInfo.h" #include "Json.h" diff --git a/api/logic/minecraft/onesix/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp index 7ebf514f..f7ab25b3 100644 --- a/api/logic/minecraft/onesix/OneSixVersionFormat.cpp +++ b/api/logic/minecraft/OneSixVersionFormat.cpp @@ -52,6 +52,15 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc QJsonObject root = doc.object(); + Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false); + switch(formatVersion) + { + case Meta::MetadataVersion::InitialRelease: + break; + case Meta::MetadataVersion::Invalid: + throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format."); + } + if (requireOrder) { if (root.contains("order")) @@ -77,8 +86,6 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc } out->version = root.value("version").toString(); - out->dependsOnMinecraftVersion = root.value("mcVersion").toString(); - // out->filename = filename; MojangVersionFormat::readVersionProperties(root, out.get()); @@ -192,6 +199,30 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc out->mainJar = lib; } + if (root.contains("requires")) + { + Meta::parseRequires(root, &out->requires); + } + QString dependsOnMinecraftVersion = root.value("mcVersion").toString(); + if(!dependsOnMinecraftVersion.isEmpty()) + { + Meta::Require mcReq; + mcReq.uid = "net.minecraft"; + mcReq.equalsVersion = dependsOnMinecraftVersion; + if (out->requires.count(mcReq) == 0) + { + out->requires.insert(mcReq); + } + } + if (root.contains("conflicts")) + { + Meta::parseRequires(root, &out->conflicts); + } + if (root.contains("volatile")) + { + out->m_volatile = requireBoolean(root, "volatile"); + } + /* removed features that shouldn't be used */ if (root.contains("tweakers")) { @@ -216,19 +247,16 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc return out; } -QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch, bool saveOrder) +QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch) { QJsonObject root; - if (saveOrder) - { - root.insert("order", patch->order); - } writeString(root, "name", patch->name); writeString(root, "uid", patch->uid); writeString(root, "version", patch->version); - writeString(root, "mcVersion", patch->dependsOnMinecraftVersion); + + Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease); MojangVersionFormat::writeVersionProperties(patch.get(), root); @@ -266,6 +294,18 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch } root.insert("mods", array); } + if(!patch->requires.empty()) + { + Meta::serializeRequires(root, &patch->requires, "requires"); + } + if(!patch->conflicts.empty()) + { + Meta::serializeRequires(root, &patch->conflicts, "conflicts"); + } + if(patch->m_volatile) + { + root.insert("volatile", true); + } // write the contents to a json document. { QJsonDocument out; diff --git a/api/logic/minecraft/onesix/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h index 64f18da8..8b782db0 100644 --- a/api/logic/minecraft/onesix/OneSixVersionFormat.h +++ b/api/logic/minecraft/OneSixVersionFormat.h @@ -1,7 +1,7 @@ #pragma once #include <minecraft/VersionFile.h> -#include <minecraft/MinecraftProfile.h> +#include <minecraft/ComponentList.h> #include <minecraft/Library.h> #include <QJsonDocument> @@ -10,7 +10,7 @@ class OneSixVersionFormat public: // version files / profile patches static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder); - static QJsonDocument versionFileToJson(const VersionFilePtr &patch, bool saveOrder); + static QJsonDocument versionFileToJson(const VersionFilePtr &patch); // libraries static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); diff --git a/api/logic/minecraft/OpSys.cpp b/api/logic/minecraft/OpSys.cpp index c2c28d1b..2165fa7f 100644 --- a/api/logic/minecraft/OpSys.cpp +++ b/api/logic/minecraft/OpSys.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/OpSys.h b/api/logic/minecraft/OpSys.h index 8f43f480..e44b1e07 100644 --- a/api/logic/minecraft/OpSys.h +++ b/api/logic/minecraft/OpSys.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/ProfilePatch.cpp b/api/logic/minecraft/ProfilePatch.cpp deleted file mode 100644 index a2605278..00000000 --- a/api/logic/minecraft/ProfilePatch.cpp +++ /dev/null @@ -1,188 +0,0 @@ -#include <meta/VersionList.h> -#include <meta/Index.h> -#include <Env.h> -#include "ProfilePatch.h" - -#include "meta/Version.h" -#include "VersionFile.h" -#include "minecraft/MinecraftProfile.h" - -ProfilePatch::ProfilePatch(std::shared_ptr<Meta::Version> version) - :m_metaVersion(version) -{ -} - -ProfilePatch::ProfilePatch(std::shared_ptr<VersionFile> file, const QString& filename) - :m_file(file), m_filename(filename) -{ -} - -std::shared_ptr<Meta::Version> ProfilePatch::getMeta() -{ - return m_metaVersion; -} - -void ProfilePatch::applyTo(MinecraftProfile* profile) -{ - auto vfile = getVersionFile(); - if(vfile) - { - vfile->applyTo(profile); - } - else - { - profile->applyProblemSeverity(getProblemSeverity()); - } -} - -std::shared_ptr<class VersionFile> ProfilePatch::getVersionFile() -{ - if(m_metaVersion) - { - if(!m_metaVersion->isLoaded()) - { - m_metaVersion->load(); - } - return m_metaVersion->data(); - } - else - { - return m_file; - } -} - -std::shared_ptr<class Meta::VersionList> ProfilePatch::getVersionList() -{ - if(m_metaVersion) - { - return ENV.metadataIndex()->get(m_metaVersion->uid()); - } - return nullptr; -} - -int ProfilePatch::getOrder() -{ - if(m_orderOverride) - return m_order; - - auto vfile = getVersionFile(); - if(vfile) - { - return vfile->order; - } - return 0; -} -void ProfilePatch::setOrder(int order) -{ - m_orderOverride = true; - m_order = order; -} -QString ProfilePatch::getID() -{ - if(m_metaVersion) - return m_metaVersion->uid(); - return getVersionFile()->uid; -} -QString ProfilePatch::getName() -{ - if(m_metaVersion) - return m_metaVersion->name(); - return getVersionFile()->name; -} -QString ProfilePatch::getVersion() -{ - if(m_metaVersion) - return m_metaVersion->version(); - return getVersionFile()->version; -} -QString ProfilePatch::getFilename() -{ - return m_filename; -} -QDateTime ProfilePatch::getReleaseDateTime() -{ - if(m_metaVersion) - { - return m_metaVersion->time(); - } - return getVersionFile()->releaseTime; -} - -bool ProfilePatch::isCustom() -{ - return !m_isVanilla; -}; - -bool ProfilePatch::isCustomizable() -{ - if(m_metaVersion) - { - if(getVersionFile()) - { - return true; - } - } - return false; -} -bool ProfilePatch::isRemovable() -{ - return m_isRemovable; -} -bool ProfilePatch::isRevertible() -{ - return m_isRevertible; -} -bool ProfilePatch::isMoveable() -{ - return m_isMovable; -} -bool ProfilePatch::isVersionChangeable() -{ - auto list = getVersionList(); - if(list) - { - if(!list->isLoaded()) - { - list->load(); - } - return list->count() != 0; - } - return false; -} - -void ProfilePatch::setVanilla (bool state) -{ - m_isVanilla = state; -} -void ProfilePatch::setRemovable (bool state) -{ - m_isRemovable = state; -} -void ProfilePatch::setRevertible (bool state) -{ - m_isRevertible = state; -} -void ProfilePatch::setMovable (bool state) -{ - m_isMovable = state; -} - -ProblemSeverity ProfilePatch::getProblemSeverity() -{ - auto file = getVersionFile(); - if(file) - { - return file->getProblemSeverity(); - } - return ProblemSeverity::Error; -} - -const QList<PatchProblem> ProfilePatch::getProblems() -{ - auto file = getVersionFile(); - if(file) - { - return file->getProblems(); - } - return {PatchProblem(ProblemSeverity::Error, QObject::tr("Patch is not loaded yet."))}; -} diff --git a/api/logic/minecraft/ProfilePatch.h b/api/logic/minecraft/ProfilePatch.h deleted file mode 100644 index 59171a0a..00000000 --- a/api/logic/minecraft/ProfilePatch.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include <memory> -#include <QList> -#include <QJsonDocument> -#include <QDateTime> -#include "ProblemProvider.h" - -class MinecraftProfile; -namespace Meta -{ - class Version; - class VersionList; -} -class VersionFile; - -class ProfilePatch : public ProblemProvider -{ -public: - ProfilePatch(std::shared_ptr<Meta::Version> version); - ProfilePatch(std::shared_ptr<VersionFile> file, const QString &filename = QString()); - - virtual ~ProfilePatch(){}; - virtual void applyTo(MinecraftProfile *profile); - - virtual bool isMoveable(); - virtual bool isCustomizable(); - virtual bool isRevertible(); - virtual bool isRemovable(); - virtual bool isCustom(); - virtual bool isVersionChangeable(); - - virtual void setOrder(int order); - virtual int getOrder(); - - virtual QString getID(); - virtual QString getName(); - virtual QString getVersion(); - virtual std::shared_ptr<Meta::Version> getMeta(); - virtual QDateTime getReleaseDateTime(); - - virtual QString getFilename(); - - virtual std::shared_ptr<class VersionFile> getVersionFile(); - virtual std::shared_ptr<class Meta::VersionList> getVersionList(); - - void setVanilla (bool state); - void setRemovable (bool state); - void setRevertible (bool state); - void setMovable (bool state); - - const QList<PatchProblem> getProblems() override; - ProblemSeverity getProblemSeverity() override; - -protected: - // Properties for UI and version manipulation from UI in general - bool m_isMovable = false; - bool m_isRevertible = false; - bool m_isRemovable = false; - bool m_isVanilla = false; - - bool m_orderOverride = false; - int m_order = 0; - - std::shared_ptr<Meta::Version> m_metaVersion; - std::shared_ptr<VersionFile> m_file; - QString m_filename; -}; - -typedef std::shared_ptr<ProfilePatch> ProfilePatchPtr; diff --git a/api/logic/minecraft/ProfileStrategy.h b/api/logic/minecraft/ProfileStrategy.h deleted file mode 100644 index 26bdf661..00000000 --- a/api/logic/minecraft/ProfileStrategy.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "ProfileUtils.h" -#include "ProfilePatch.h" - -class MinecraftProfile; - -class ProfileStrategy -{ - friend class MinecraftProfile; -public: - virtual ~ProfileStrategy(){}; - - /// load the patch files into the profile - virtual void load() = 0; - - /// reset the order of patches - virtual bool resetOrder() = 0; - - /// save the order of patches, given the order - virtual bool saveOrder(ProfileUtils::PatchOrder order) = 0; - - /// install a list of jar mods into the instance - virtual bool installJarMods(QStringList filepaths) = 0; - - /// remove any files or records that constitute the version patch - virtual bool removePatch(ProfilePatchPtr jarMod) = 0; - - /// make the patch custom, if possible - virtual bool customizePatch(ProfilePatchPtr patch) = 0; - - /// revert the custom patch to 'vanilla', if possible - virtual bool revertPatch(ProfilePatchPtr patch) = 0; -protected: - MinecraftProfile *profile; -}; diff --git a/api/logic/minecraft/ProfileUtils.cpp b/api/logic/minecraft/ProfileUtils.cpp index 8c5bc052..a6d2028d 100644 --- a/api/logic/minecraft/ProfileUtils.cpp +++ b/api/logic/minecraft/ProfileUtils.cpp @@ -1,6 +1,6 @@ #include "ProfileUtils.h" #include "minecraft/VersionFilterData.h" -#include "minecraft/onesix/OneSixVersionFormat.h" +#include "minecraft/OneSixVersionFormat.h" #include "Json.h" #include <QDebug> @@ -14,38 +14,6 @@ namespace ProfileUtils static const int currentOrderFileVersion = 1; -bool writeOverrideOrders(QString path, const PatchOrder &order) -{ - QJsonObject obj; - obj.insert("version", currentOrderFileVersion); - QJsonArray orderArray; - for(auto str: order) - { - orderArray.append(str); - } - obj.insert("order", orderArray); - QSaveFile orderFile(path); - if (!orderFile.open(QFile::WriteOnly)) - { - qCritical() << "Couldn't open" << orderFile.fileName() - << "for writing:" << orderFile.errorString(); - return false; - } - auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented); - if(orderFile.write(data) != data.size()) - { - qCritical() << "Couldn't write all the data into" << orderFile.fileName() - << "because:" << orderFile.errorString(); - return false; - } - if(!orderFile.commit()) - { - qCritical() << "Couldn't save" << orderFile.fileName() - << "because:" << orderFile.errorString(); - } - return true; -} - bool readOverrideOrders(QString path, PatchOrder &order) { QFile orderFile(path); @@ -154,6 +122,25 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder) return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder); } +bool saveJsonFile(const QJsonDocument doc, const QString & filename) +{ + auto data = doc.toJson(); + QSaveFile jsonFile(filename); + if(!jsonFile.open(QIODevice::WriteOnly)) + { + jsonFile.cancelWriting(); + qWarning() << "Couldn't open" << filename << "for writing"; + return false; + } + jsonFile.write(data); + if(!jsonFile.commit()) + { + qWarning() << "Couldn't save" << filename; + return false; + } + return true; +} + VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo) { QFile file(fileInfo.absoluteFilePath()); diff --git a/api/logic/minecraft/ProfileUtils.h b/api/logic/minecraft/ProfileUtils.h index 267fd42b..351c36cb 100644 --- a/api/logic/minecraft/ProfileUtils.h +++ b/api/logic/minecraft/ProfileUtils.h @@ -16,6 +16,9 @@ bool writeOverrideOrders(QString path, const PatchOrder &order); /// Parse a version file in JSON format VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder); +/// Save a JSON file (in any format) +bool saveJsonFile(const QJsonDocument doc, const QString & filename); + /// Parse a version file in binary JSON format VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo); diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp index 8e2838ee..43d673c2 100644 --- a/api/logic/minecraft/Rule.cpp +++ b/api/logic/minecraft/Rule.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/Rule.h b/api/logic/minecraft/Rule.h index d5fd2492..dc1eee0b 100644 --- a/api/logic/minecraft/Rule.h +++ b/api/logic/minecraft/Rule.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/SkinUpload.cpp b/api/logic/minecraft/SkinUpload.cpp index 1d1e38f3..bd246139 100644 --- a/api/logic/minecraft/SkinUpload.cpp +++ b/api/logic/minecraft/SkinUpload.cpp @@ -6,9 +6,9 @@ QByteArray getModelString(SkinUpload::Model model) { switch (model) { case SkinUpload::STEVE: - return "steve"; + return ""; case SkinUpload::ALEX: - return "alex"; + return "slim"; default: qDebug() << "Unknown skin type!"; return ""; diff --git a/api/logic/minecraft/SkinUpload.h b/api/logic/minecraft/SkinUpload.h index 86944b82..5b331fa9 100644 --- a/api/logic/minecraft/SkinUpload.h +++ b/api/logic/minecraft/SkinUpload.h @@ -9,9 +9,9 @@ typedef std::shared_ptr<class SkinUpload> SkinUploadPtr; -class MULTIMC_LOGIC_EXPORT SkinUpload : public Task\ +class MULTIMC_LOGIC_EXPORT SkinUpload : public Task { -Q_OBJECT + Q_OBJECT public: enum Model { diff --git a/api/logic/minecraft/VersionFile.cpp b/api/logic/minecraft/VersionFile.cpp index 85989549..9f485c55 100644 --- a/api/logic/minecraft/VersionFile.cpp +++ b/api/logic/minecraft/VersionFile.cpp @@ -5,7 +5,7 @@ #include "minecraft/VersionFile.h" #include "minecraft/Library.h" -#include "minecraft/MinecraftProfile.h" +#include "minecraft/ComponentList.h" #include "ParseUtils.h" #include <Version.h> @@ -15,7 +15,7 @@ static bool isMinecraftVersion(const QString &uid) return uid == "net.minecraft"; } -void VersionFile::applyTo(MinecraftProfile *profile) +void VersionFile::applyTo(LaunchProfile *profile) { // Only real Minecraft can set those. Don't let anything override them. if (isMinecraftVersion(uid)) diff --git a/api/logic/minecraft/VersionFile.h b/api/logic/minecraft/VersionFile.h index e3fb46ed..5aea7a7a 100644 --- a/api/logic/minecraft/VersionFile.h +++ b/api/logic/minecraft/VersionFile.h @@ -10,27 +10,26 @@ #include "minecraft/Rule.h" #include "ProblemProvider.h" #include "Library.h" +#include <meta/JsonFormat.h> -class MinecraftProfile; +class ComponentList; class VersionFile; +class LaunchProfile; struct MojangDownloadInfo; struct MojangAssetIndexInfo; -typedef std::shared_ptr<VersionFile> VersionFilePtr; +using VersionFilePtr = std::shared_ptr<VersionFile>; class VersionFile : public ProblemContainer { friend class MojangVersionFormat; friend class OneSixVersionFormat; public: /* methods */ - void applyTo(MinecraftProfile *profile); + void applyTo(LaunchProfile* profile); public: /* data */ /// MultiMC: order hint for this version file if no explicit order is set int order = 0; - /// MultiMC: filename of the file this was loaded from - // QString filename; - /// MultiMC: human readable name of this package QString name; @@ -76,7 +75,7 @@ public: /* data */ /// Mojang: list of libraries to add to the version QList<LibraryPtr> libraries; - // The main jar (Minecraft version library, normally) + /// The main jar (Minecraft version library, normally) LibraryPtr mainJar; /// MultiMC: list of attached traits of this version file - used to enable features @@ -88,6 +87,21 @@ public: /* data */ /// MultiMC: list of mods added to this version QList<LibraryPtr> mods; + /** + * MultiMC: set of packages this depends on + * NOTE: this is shared with the meta format!!! + */ + Meta::RequireSet requires; + + /** + * MultiMC: set of packages this conflicts with + * NOTE: this is shared with the meta format!!! + */ + Meta::RequireSet conflicts; + + /// is volatile -- may be removed as soon as it is no longer needed by something else + bool m_volatile = false; + public: // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads; diff --git a/api/logic/minecraft/World.cpp b/api/logic/minecraft/World.cpp index 81f52ff5..68c0a5cc 100644 --- a/api/logic/minecraft/World.cpp +++ b/api/logic/minecraft/World.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -146,7 +146,7 @@ void World::readFromZip(const QFileInfo &file) { return; } - auto location = MMCZip::findFileInZip(&zip, "level.dat"); + auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); is_valid = !location.isEmpty(); if (!is_valid) { diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h index a87cb8ec..7087bf48 100644 --- a/api/logic/minecraft/World.h +++ b/api/logic/minecraft/World.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp index 47ce2379..4278c2f7 100644 --- a/api/logic/minecraft/WorldList.cpp +++ b/api/logic/minecraft/WorldList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h index 700fe2fd..811393cd 100644 --- a/api/logic/minecraft/WorldList.h +++ b/api/logic/minecraft/WorldList.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp index f4c628a6..edad4344 100644 --- a/api/logic/minecraft/auth/MojangAccount.cpp +++ b/api/logic/minecraft/auth/MojangAccount.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Authors: Orochimarufan <orochimarufan.x3@gmail.com> * @@ -170,8 +170,7 @@ AccountStatus MojangAccount::accountStatus() const return Verified; } -std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, - QString password) +std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, QString password) { Q_ASSERT(m_currentTask.get() == nullptr); @@ -186,18 +185,28 @@ std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, return nullptr; } - if (password.isEmpty()) + if(accountStatus() == Verified && !session->wants_online) { - m_currentTask.reset(new RefreshTask(this)); + session->status = AuthSession::PlayableOffline; + session->auth_server_online = false; + fillSession(session); + return nullptr; } else { - m_currentTask.reset(new AuthenticateTask(this, password)); - } - m_currentTask->assignSession(session); + if (password.isEmpty()) + { + m_currentTask.reset(new RefreshTask(this)); + } + else + { + m_currentTask.reset(new AuthenticateTask(this, password)); + } + m_currentTask->assignSession(session); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); + connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + } return m_currentTask; } diff --git a/api/logic/minecraft/auth/MojangAccount.h b/api/logic/minecraft/auth/MojangAccount.h index c7f15723..b2bbc357 100644 --- a/api/logic/minecraft/auth/MojangAccount.h +++ b/api/logic/minecraft/auth/MojangAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/MojangAccountList.cpp b/api/logic/minecraft/auth/MojangAccountList.cpp index bcda1703..21ae188a 100644 --- a/api/logic/minecraft/auth/MojangAccountList.cpp +++ b/api/logic/minecraft/auth/MojangAccountList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,34 +53,47 @@ const MojangAccountPtr MojangAccountList::at(int i) const void MojangAccountList::addAccount(const MojangAccountPtr account) { - beginResetModel(); + int row = m_accounts.count(); + beginInsertRows(QModelIndex(), row, row); connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); m_accounts.append(account); - endResetModel(); + endInsertRows(); onListChanged(); } void MojangAccountList::removeAccount(const QString &username) { - beginResetModel(); + int idx = 0; for (auto account : m_accounts) { if (account->username() == username) { + beginRemoveRows(QModelIndex(), idx, idx); m_accounts.removeOne(account); + endRemoveRows(); return; } + idx++; } - endResetModel(); onListChanged(); } void MojangAccountList::removeAccount(QModelIndex index) { - beginResetModel(); - m_accounts.removeAt(index.row()); - endResetModel(); - onListChanged(); + int row = index.row(); + if(index.isValid() && row >= 0 && row < m_accounts.size()) + { + auto & account = m_accounts[row]; + if(account == m_activeAccount) + { + m_activeAccount = nullptr; + onActiveChanged(); + } + beginRemoveRows(QModelIndex(), row, row); + m_accounts.removeAt(index.row()); + endRemoveRows(); + onListChanged(); + } } MojangAccountPtr MojangAccountList::activeAccount() const @@ -90,21 +103,49 @@ MojangAccountPtr MojangAccountList::activeAccount() const void MojangAccountList::setActiveAccount(const QString &username) { - beginResetModel(); - if (username.isEmpty()) + if (username.isEmpty() && m_activeAccount) { + int idx = 0; + auto prevActiveAcc = m_activeAccount; m_activeAccount = nullptr; + for (MojangAccountPtr account : m_accounts) + { + if (account == prevActiveAcc) + { + emit dataChanged(index(idx), index(idx)); + } + idx ++; + } + onActiveChanged(); } else { + auto currentActiveAccount = m_activeAccount; + int currentActiveAccountIdx = -1; + auto newActiveAccount = m_activeAccount; + int newActiveAccountIdx = -1; + int idx = 0; for (MojangAccountPtr account : m_accounts) { if (account->username() == username) - m_activeAccount = account; + { + newActiveAccount = account; + newActiveAccountIdx = idx; + } + if(currentActiveAccount == account) + { + currentActiveAccountIdx = idx; + } + idx++; + } + if(currentActiveAccount != newActiveAccount) + { + emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx)); + emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx)); + m_activeAccount = newActiveAccount; + onActiveChanged(); } } - endResetModel(); - onActiveChanged(); } void MojangAccountList::accountChanged() @@ -167,7 +208,7 @@ QVariant MojangAccountList::data(const QModelIndex &index, int role) const switch (index.column()) { case ActiveColumn: - return account == m_activeAccount; + return account == m_activeAccount ? Qt::Checked : Qt::Unchecked; } default: @@ -207,13 +248,13 @@ QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, } } -int MojangAccountList::rowCount(const QModelIndex &parent) const +int MojangAccountList::rowCount(const QModelIndex &) const { // Return count return count(); } -int MojangAccountList::columnCount(const QModelIndex &parent) const +int MojangAccountList::columnCount(const QModelIndex &) const { return 2; } diff --git a/api/logic/minecraft/auth/MojangAccountList.h b/api/logic/minecraft/auth/MojangAccountList.h index 0a7a10d9..dd6c07e8 100644 --- a/api/logic/minecraft/auth/MojangAccountList.h +++ b/api/logic/minecraft/auth/MojangAccountList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/YggdrasilTask.cpp b/api/logic/minecraft/auth/YggdrasilTask.cpp index 425da76a..f17d803f 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.cpp +++ b/api/logic/minecraft/auth/YggdrasilTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -130,6 +130,7 @@ void YggdrasilTask::processReply() "</ul>")); return; // used for invalid credentials and similar errors. Fall through. + case QNetworkReply::ContentAccessDenied: case QNetworkReply::ContentOperationNotPermittedError: break; default: diff --git a/api/logic/minecraft/auth/YggdrasilTask.h b/api/logic/minecraft/auth/YggdrasilTask.h index f21c2733..b165d3e9 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.h +++ b/api/logic/minecraft/auth/YggdrasilTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp index ad892073..ff4e7d47 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.h b/api/logic/minecraft/auth/flows/AuthenticateTask.h index a15c8a9e..ab7a6e64 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.h +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/RefreshTask.cpp b/api/logic/minecraft/auth/flows/RefreshTask.cpp index 76738900..73050907 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.cpp +++ b/api/logic/minecraft/auth/flows/RefreshTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/RefreshTask.h b/api/logic/minecraft/auth/flows/RefreshTask.h index 3c99101e..a97b54e6 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.h +++ b/api/logic/minecraft/auth/flows/RefreshTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/ValidateTask.cpp b/api/logic/minecraft/auth/flows/ValidateTask.cpp index f82e6542..b6d6c823 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.cpp +++ b/api/logic/minecraft/auth/flows/ValidateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/ValidateTask.h b/api/logic/minecraft/auth/flows/ValidateTask.h index a93bc76e..77de24c7 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.h +++ b/api/logic/minecraft/auth/flows/ValidateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/flame/FileResolvingTask.cpp b/api/logic/minecraft/flame/FileResolvingTask.cpp index d55beb63..efc73621 100644 --- a/api/logic/minecraft/flame/FileResolvingTask.cpp +++ b/api/logic/minecraft/flame/FileResolvingTask.cpp @@ -48,7 +48,51 @@ void Flame::FileResolvingTask::netJobFinished() continue; } out.fileName = Json::requireString(obj, "FileNameOnDisk"); - out.url = Json::requireString(obj, "DownloadURL"); + QString rawUrl = Json::requireString(obj, "DownloadURL"); + out.url = QUrl(rawUrl, QUrl::TolerantMode); + if(!out.url.isValid()) + { + throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); + } + // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience + // It is also optional + QJsonObject projObj = Json::ensureObject(obj, "_Project", {}); + if(!projObj.isEmpty()) + { + QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower(); + if(strType == "singlefile") + { + out.type = File::Type::SingleFile; + } + else if(strType == "ctoc") + { + out.type = File::Type::Ctoc; + } + else if(strType == "cmod2") + { + out.type = File::Type::Cmod2; + } + else if(strType == "mod") + { + out.type = File::Type::Mod; + } + else if(strType == "folder") + { + out.type = File::Type::Folder; + } + else if(strType == "modpack") + { + out.type = File::Type::Modpack; + } + else + { + qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType; + out.type = File::Type::Unknown; + failed = true; + continue; + } + out.targetFolder = Json::ensureString(projObj, "Path", "mods"); + } out.resolved = true; } catch(JSONValidationError & e) diff --git a/api/logic/minecraft/flame/PackManifest.cpp b/api/logic/minecraft/flame/PackManifest.cpp index 62921493..6a9324fe 100644 --- a/api/logic/minecraft/flame/PackManifest.cpp +++ b/api/logic/minecraft/flame/PackManifest.cpp @@ -5,7 +5,6 @@ static void loadFileV1(Flame::File & f, QJsonObject & file) { f.projectId = Json::requireInteger(file, "projectID"); f.fileId = Json::requireInteger(file, "fileID"); - // FIXME: what does this mean? f.required = Json::ensureBoolean(file, QString("required"), true); } diff --git a/api/logic/minecraft/flame/PackManifest.h b/api/logic/minecraft/flame/PackManifest.h index ae91bffb..1a5254a8 100644 --- a/api/logic/minecraft/flame/PackManifest.h +++ b/api/logic/minecraft/flame/PackManifest.h @@ -2,6 +2,7 @@ #include <QString> #include <QVector> +#include <QUrl> namespace Flame { @@ -9,12 +10,24 @@ struct File { int projectId = 0; int fileId = 0; + // NOTE: the opposite to 'optional'. This is at the time of writing unused. bool required = true; // our bool resolved = false; QString fileName; - QString url; + QUrl url; + QString targetFolder = QLatin1Literal("mods"); + enum class Type + { + Unknown, + Folder, + Ctoc, + SingleFile, + Cmod2, + Modpack, + Mod + } type = Type::Mod; }; struct Modloader diff --git a/api/logic/minecraft/forge/ForgeXzDownload.cpp b/api/logic/minecraft/forge/ForgeXzDownload.cpp index 593aa24f..a05d0f8d 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.cpp +++ b/api/logic/minecraft/forge/ForgeXzDownload.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/forge/ForgeXzDownload.h b/api/logic/minecraft/forge/ForgeXzDownload.h index ef23809b..cee402ef 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.h +++ b/api/logic/minecraft/forge/ForgeXzDownload.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/ftb/FTBInstanceProvider.cpp b/api/logic/minecraft/ftb/FTBInstanceProvider.cpp deleted file mode 100644 index fe23a84e..00000000 --- a/api/logic/minecraft/ftb/FTBInstanceProvider.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include "FTBInstanceProvider.h" - -#include <QDir> -#include <QDebug> -#include <QXmlStreamReader> -#include <QRegularExpression> - -#include <settings/INISettingsObject.h> -#include <FileSystem.h> - -#include "Env.h" - -#include "LegacyFTBInstance.h" -#include "OneSixFTBInstance.h" - -inline uint qHash(FTBRecord record) -{ - return qHash(record.instanceDir); -} - -FTBInstanceProvider::FTBInstanceProvider(SettingsObjectPtr settings) - : BaseInstanceProvider(settings) -{ - // nil -} - -QList<InstanceId> FTBInstanceProvider::discoverInstances() -{ - // nothing to load when we don't have - if (m_globalSettings->get("TrackFTBInstances").toBool() != true) - { - return {}; - } - m_records.clear(); - discoverFTBEntries(); - return m_records.keys(); -} - -InstancePtr FTBInstanceProvider::loadInstance(const InstanceId& id) -{ - // process the records we acquired. - auto iter = m_records.find(id); - if(iter == m_records.end()) - { - qWarning() << "Cannot load instance" << id << "without a record"; - return nullptr; - } - auto & record = m_records[id]; - qDebug() << "Loading FTB instance from " << record.instanceDir; - QString iconKey = record.iconKey; - auto icons = ENV.icons(); - if(icons) - { - icons->addIcon(iconKey, iconKey, FS::PathCombine(record.templateDir, record.logo), IconType::Transient); - } - auto settingsFilePath = FS::PathCombine(record.instanceDir, "instance.cfg"); - qDebug() << "ICON get!"; - - if (QFileInfo(settingsFilePath).exists()) - { - auto instPtr = loadInstance(record); - if (!instPtr) - { - qWarning() << "Couldn't load instance config:" << settingsFilePath; - if(!QFile::remove(settingsFilePath)) - { - qWarning() << "Couldn't remove broken instance config!"; - return nullptr; - } - // failed to load, but removed the poisonous file - } - else - { - return InstancePtr(instPtr); - } - } - auto instPtr = createInstance(record); - if (!instPtr) - { - qWarning() << "Couldn't create FTB instance!"; - return nullptr; - } - return InstancePtr(instPtr); -} - -void FTBInstanceProvider::discoverFTBEntries() -{ - QDir dir = QDir(m_globalSettings->get("FTBLauncherLocal").toString()); - QDir dataDir = QDir(m_globalSettings->get("FTBRoot").toString()); - if (!dataDir.exists()) - { - qDebug() << "The FTB directory specified does not exist. Please check your settings"; - return; - } - else if (!dir.exists()) - { - qDebug() << "The FTB launcher data directory specified does not exist. Please check " - "your settings"; - return; - } - dir.cd("ModPacks"); - auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name); - for (auto filename : allFiles) - { - if (!filename.endsWith(".xml")) - continue; - auto fpath = dir.absoluteFilePath(filename); - QFile f(fpath); - qDebug() << "Discovering FTB instances -- " << fpath; - if (!f.open(QFile::ReadOnly)) - continue; - - // read the FTB packs XML. - QXmlStreamReader reader(&f); - while (!reader.atEnd()) - { - switch (reader.readNext()) - { - case QXmlStreamReader::StartElement: - { - if (reader.name() == "modpack") - { - QXmlStreamAttributes attrs = reader.attributes(); - FTBRecord record; - record.dirName = attrs.value("dir").toString(); - record.instanceDir = dataDir.absoluteFilePath(record.dirName); - record.templateDir = dir.absoluteFilePath(record.dirName); - QDir test(record.instanceDir); - qDebug() << dataDir.absolutePath() << record.instanceDir << record.dirName; - if (!test.exists()) - continue; - record.name = attrs.value("name").toString(); - record.logo = attrs.value("logo").toString(); - QString logo = record.logo; - record.iconKey = logo.remove(QRegularExpression("\\..*")); - auto customVersions = attrs.value("customMCVersions"); - if (!customVersions.isNull()) - { - QMap<QString, QString> versionMatcher; - QString customVersionsStr = customVersions.toString(); - QStringList list = customVersionsStr.split(';'); - for (auto item : list) - { - auto segment = item.split('^'); - if (segment.size() != 2) - { - qCritical() << "FTB: Segment of size < 2 in " - << customVersionsStr; - continue; - } - versionMatcher[segment[0]] = segment[1]; - } - auto actualVersion = attrs.value("version").toString(); - if (versionMatcher.contains(actualVersion)) - { - record.mcVersion = versionMatcher[actualVersion]; - } - else - { - record.mcVersion = attrs.value("mcVersion").toString(); - } - } - else - { - record.mcVersion = attrs.value("mcVersion").toString(); - } - record.description = attrs.value("description").toString(); - auto id = "FTB/" + record.dirName; - m_records[id] = record; - } - break; - } - case QXmlStreamReader::EndElement: - break; - case QXmlStreamReader::Characters: - break; - default: - break; - } - } - f.close(); - } -} - -InstancePtr FTBInstanceProvider::loadInstance(const FTBRecord & record) const -{ - InstancePtr inst; - - auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg")); - m_settings->registerSetting("InstanceType", "Legacy"); - - qDebug() << "Loading existing " << record.name; - - QString inst_type = m_settings->get("InstanceType").toString(); - if (inst_type == "LegacyFTB") - { - inst.reset(new LegacyFTBInstance(m_globalSettings, m_settings, record.instanceDir)); - } - else if (inst_type == "OneSixFTB") - { - inst.reset(new OneSixFTBInstance(m_globalSettings, m_settings, record.instanceDir)); - } - else - { - return nullptr; - } - qDebug() << "Construction " << record.instanceDir; - - SettingsObject::Lock lock(inst->settings()); - inst->init(); - qDebug() << "Init " << record.instanceDir; - inst->setGroupInitial("FTB"); - /** - * FIXME: this does not respect the user's preferences. BUT, it would work nicely with the planned pack support - * -> instead of changing the user values, change pack values (defaults you can look at and revert to) - */ - /* - inst->setName(record.name); - inst->setIconKey(record.iconKey); - inst->setNotes(record.description); - */ - if (inst->intendedVersionId() != record.mcVersion) - { - inst->setIntendedVersionId(record.mcVersion); - } - qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); - return inst; -} - -InstancePtr FTBInstanceProvider::createInstance(const FTBRecord & record) const -{ - QDir rootDir(record.instanceDir); - - InstancePtr inst; - - qDebug() << "Converting " << record.name << " as new."; - - if (!rootDir.exists() && !rootDir.mkpath(".")) - { - qCritical() << "Can't create instance folder" << record.instanceDir; - return nullptr; - } - - auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg")); - m_settings->registerSetting("InstanceType", "Legacy"); - - // all legacy versions are built in. therefore we can do this even if we don't have ALL the versions Mojang has on their servers. - m_settings->set("InstanceType", "OneSixFTB"); - inst.reset(new OneSixFTBInstance(m_globalSettings, m_settings, record.instanceDir)); - - // initialize - { - SettingsObject::Lock lock(inst->settings()); - inst->setIntendedVersionId(record.mcVersion); - inst->init(); - inst->setGroupInitial("FTB"); - inst->setName(record.name); - inst->setIconKey(record.iconKey); - inst->setNotes(record.description); - } - return inst; -} diff --git a/api/logic/minecraft/ftb/FTBInstanceProvider.h b/api/logic/minecraft/ftb/FTBInstanceProvider.h deleted file mode 100644 index fb3ecb6c..00000000 --- a/api/logic/minecraft/ftb/FTBInstanceProvider.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "BaseInstanceProvider.h" -#include <QMap> - -class QFileSystemWatcher; - -struct MULTIMC_LOGIC_EXPORT FTBRecord -{ - QString dirName; - QString name; - QString logo; - QString iconKey; - QString mcVersion; - QString description; - QString instanceDir; - QString templateDir; - bool operator==(const FTBRecord other) const - { - return instanceDir == other.instanceDir; - } -}; - -class MULTIMC_LOGIC_EXPORT FTBInstanceProvider : public BaseInstanceProvider -{ - Q_OBJECT - -public: - FTBInstanceProvider (SettingsObjectPtr settings); - -public: - QList<InstanceId> discoverInstances() override; - InstancePtr loadInstance(const InstanceId& id) override; - void loadGroupList() override {}; - void saveGroupList() override {}; - -private: /* methods */ - void discoverFTBEntries(); - InstancePtr createInstance(const FTBRecord & record) const; - InstancePtr loadInstance(const FTBRecord & record) const; - - -private: - QMap<InstanceId, FTBRecord> m_records; -}; diff --git a/api/logic/minecraft/ftb/FTBPlugin.cpp b/api/logic/minecraft/ftb/FTBPlugin.cpp deleted file mode 100644 index 541879a1..00000000 --- a/api/logic/minecraft/ftb/FTBPlugin.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "FTBPlugin.h" -#include <Env.h> -#include "LegacyFTBInstance.h" -#include "OneSixFTBInstance.h" -#include <BaseInstance.h> -#include <InstanceList.h> -#include <settings/INISettingsObject.h> -#include <FileSystem.h> - -#include <QDebug> -#include <QRegularExpression> - -#ifdef Q_OS_WIN32 -#include <windows.h> -static const int APPDATA_BUFFER_SIZE = 1024; -#endif - -static QString getLocalCacheStorageLocation() -{ - QString ftbDefault; -#ifdef Q_OS_WIN32 - wchar_t buf[APPDATA_BUFFER_SIZE]; - if (GetEnvironmentVariableW(L"LOCALAPPDATA", buf, APPDATA_BUFFER_SIZE)) // local - { - ftbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher"); - } - else if (GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE)) // roaming - { - ftbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher"); - } - else - { - qCritical() << "Your LOCALAPPDATA and APPDATA folders are missing!" - " If you are on windows, this means your system is broken."; - } -#elif defined(Q_OS_MAC) - ftbDefault = FS::PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher"); -#else - ftbDefault = QDir::home().absoluteFilePath(".ftblauncher"); -#endif - return ftbDefault; -} - - -static QString getRoamingStorageLocation() -{ - QString ftbDefault; -#ifdef Q_OS_WIN32 - wchar_t buf[APPDATA_BUFFER_SIZE]; - QString cacheStorage; - if (GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE)) - { - ftbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher"); - } - else - { - qCritical() << "Your APPDATA folder is missing! If you are on windows, this means your system is broken."; - } -#elif defined(Q_OS_MAC) - ftbDefault = FS::PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher"); -#else - ftbDefault = QDir::home().absoluteFilePath(".ftblauncher"); -#endif - return ftbDefault; -} - -void FTBPlugin::initialize(SettingsObjectPtr globalSettings) -{ - // FTB - globalSettings->registerSetting("TrackFTBInstances", false); - QString ftbRoaming = getRoamingStorageLocation(); - QString ftbLocal = getLocalCacheStorageLocation(); - - globalSettings->registerSetting("FTBLauncherRoaming", ftbRoaming); - globalSettings->registerSetting("FTBLauncherLocal", ftbLocal); - qDebug() << "FTB Launcher paths:" << globalSettings->get("FTBLauncherRoaming").toString() - << "and" << globalSettings->get("FTBLauncherLocal").toString(); - - globalSettings->registerSetting("FTBRoot"); - if (globalSettings->get("FTBRoot").isNull()) - { - QString ftbRoot; - QFile f(QDir(globalSettings->get("FTBLauncherRoaming").toString()).absoluteFilePath("ftblaunch.cfg")); - qDebug() << "Attempting to read" << f.fileName(); - if (f.open(QFile::ReadOnly)) - { - const QString data = QString::fromLatin1(f.readAll()); - QRegularExpression exp("installPath=(.*)"); - ftbRoot = QDir::cleanPath(exp.match(data).captured(1)); -#ifdef Q_OS_WIN32 - if (!ftbRoot.isEmpty()) - { - if (ftbRoot.at(0).isLetter() && ftbRoot.size() > 1 && ftbRoot.at(1) == '/') - { - ftbRoot.remove(1, 1); - } - } -#endif - if (ftbRoot.isEmpty()) - { - qDebug() << "Failed to get FTB root path"; - } - else - { - qDebug() << "FTB is installed at" << ftbRoot; - globalSettings->set("FTBRoot", ftbRoot); - } - } - else - { - qWarning() << "Couldn't open" << f.fileName() << ":" << f.errorString(); - qWarning() << "This is perfectly normal if you don't have FTB installed"; - } - } -} diff --git a/api/logic/minecraft/ftb/FTBPlugin.h b/api/logic/minecraft/ftb/FTBPlugin.h deleted file mode 100644 index e1b56545..00000000 --- a/api/logic/minecraft/ftb/FTBPlugin.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include <BaseInstance.h> - -#include "multimc_logic_export.h" - -// Pseudo-plugin for FTB related things. Super derpy! -class MULTIMC_LOGIC_EXPORT FTBPlugin -{ -public: - static void initialize(SettingsObjectPtr globalSettings); -}; diff --git a/api/logic/minecraft/ftb/FTBProfileStrategy.cpp b/api/logic/minecraft/ftb/FTBProfileStrategy.cpp deleted file mode 100644 index 9fa4e6a1..00000000 --- a/api/logic/minecraft/ftb/FTBProfileStrategy.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include "FTBProfileStrategy.h" -#include "OneSixFTBInstance.h" - -#include <FileSystem.h> - -#include <QDir> -#include <QUuid> -#include <QJsonDocument> -#include <QJsonArray> - -FTBProfileStrategy::FTBProfileStrategy(OneSixFTBInstance* instance) : OneSixProfileStrategy(instance) -{ -} - -void FTBProfileStrategy::loadDefaultBuiltinPatches() -{ - // FIXME: this should be here, but it needs us to be able to deal with multiple libraries paths - // OneSixProfileStrategy::loadDefaultBuiltinPatches(); - auto mcVersion = m_instance->intendedVersionId(); - auto nativeInstance = dynamic_cast<OneSixFTBInstance *>(m_instance); - - ProfilePatchPtr minecraftPatch; - { - std::shared_ptr< VersionFile > file; - auto mcJson = m_instance->versionsPath().absoluteFilePath(mcVersion + "/" + mcVersion + ".json"); - // load up the base minecraft patch - if(QFile::exists(mcJson)) - { - file = ProfileUtils::parseJsonFile(QFileInfo(mcJson), false); - for(auto addLib: file->libraries) - { - addLib->setHint("local"); - addLib->setStoragePrefix(nativeInstance->librariesPath().absolutePath()); - } - } - else - { - file = std::make_shared<VersionFile>(); - file->addProblem(ProblemSeverity::Error, QObject::tr("Minecraft version is missing in the FTB data.")); - } - file->uid = "net.minecraft"; - file->name = QObject::tr("Minecraft (tracked)"); - if(file->version.isEmpty()) - { - file->version = mcVersion; - } - minecraftPatch = std::make_shared<ProfilePatch>(file); - minecraftPatch->setVanilla(true); - minecraftPatch->setOrder(-2); - } - profile->appendPatch(minecraftPatch); - - ProfilePatchPtr packPatch; - { - std::shared_ptr< VersionFile > file; - auto mcJson = m_instance->minecraftRoot() + "/pack.json"; - // load up the base minecraft patch, if it's there... - if(QFile::exists(mcJson)) - { - file = ProfileUtils::parseJsonFile(QFileInfo(mcJson), false); - // adapt the loaded file - the FTB patch file format is different than ours. - file->minecraftVersion.clear(); - file->mainJar = nullptr; - for(auto addLib: file->libraries) - { - addLib->setHint("local"); - addLib->setStoragePrefix(nativeInstance->librariesPath().absolutePath()); - } - } - else - { - file = std::make_shared<VersionFile>(); - file->addProblem(ProblemSeverity::Error, QObject::tr("Modpack version file is missing.")); - } - file->uid = "org.multimc.ftb.pack"; - file->name = QObject::tr("%1 (FTB pack)").arg(m_instance->name()); - if(file->version.isEmpty()) - { - file->version = QObject::tr("Unknown"); - QFile versionFile (FS::PathCombine(m_instance->instanceRoot(), "version")); - if(versionFile.exists()) - { - if(versionFile.open(QIODevice::ReadOnly)) - { - // FIXME: just guessing the encoding/charset here. - auto version = QString::fromUtf8(versionFile.readAll()); - file->version = version; - } - } - } - packPatch = std::make_shared<ProfilePatch>(file); - packPatch->setVanilla(true); - packPatch->setOrder(1); - } - profile->appendPatch(packPatch); -} - -void FTBProfileStrategy::load() -{ - profile->clearPatches(); - - loadDefaultBuiltinPatches(); - loadUserPatches(); -} - -bool FTBProfileStrategy::saveOrder(ProfileUtils::PatchOrder order) -{ - return false; -} - -bool FTBProfileStrategy::resetOrder() -{ - return false; -} - -bool FTBProfileStrategy::installJarMods(QStringList filepaths) -{ - return false; -} - -bool FTBProfileStrategy::customizePatch(ProfilePatchPtr patch) -{ - return false; -} - -bool FTBProfileStrategy::revertPatch(ProfilePatchPtr patch) -{ - return false; -} diff --git a/api/logic/minecraft/ftb/FTBProfileStrategy.h b/api/logic/minecraft/ftb/FTBProfileStrategy.h deleted file mode 100644 index 522af098..00000000 --- a/api/logic/minecraft/ftb/FTBProfileStrategy.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include "minecraft/ProfileStrategy.h" -#include "minecraft/onesix/OneSixProfileStrategy.h" - -class OneSixFTBInstance; - -class FTBProfileStrategy : public OneSixProfileStrategy -{ -public: - FTBProfileStrategy(OneSixFTBInstance * instance); - virtual ~FTBProfileStrategy() {}; - virtual void load() override; - virtual bool resetOrder() override; - virtual bool saveOrder(ProfileUtils::PatchOrder order) override; - virtual bool installJarMods(QStringList filepaths) override; - virtual bool customizePatch (ProfilePatchPtr patch) override; - virtual bool revertPatch (ProfilePatchPtr patch) override; - -protected: - virtual void loadDefaultBuiltinPatches() override; -}; diff --git a/api/logic/minecraft/ftb/LegacyFTBInstance.cpp b/api/logic/minecraft/ftb/LegacyFTBInstance.cpp deleted file mode 100644 index 63412a33..00000000 --- a/api/logic/minecraft/ftb/LegacyFTBInstance.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "LegacyFTBInstance.h" -#include <settings/INISettingsObject.h> -#include <QDir> - -LegacyFTBInstance::LegacyFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) : - LegacyInstance(globalSettings, settings, rootDir) -{ -} - -QString LegacyFTBInstance::id() const -{ - return "FTB/" + BaseInstance::id(); -} - -void LegacyFTBInstance::copy(SettingsObjectPtr newSettings, const QDir& newDir) -{ - // set the target instance to be plain Legacy - newSettings->set("InstanceType", "Legacy"); -} - -QString LegacyFTBInstance::typeName() const -{ - return tr("Legacy FTB"); -} diff --git a/api/logic/minecraft/ftb/LegacyFTBInstance.h b/api/logic/minecraft/ftb/LegacyFTBInstance.h deleted file mode 100644 index 2f2e34a2..00000000 --- a/api/logic/minecraft/ftb/LegacyFTBInstance.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "minecraft/legacy/LegacyInstance.h" - -class LegacyFTBInstance : public LegacyInstance -{ - Q_OBJECT -public: - explicit LegacyFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - QString id() const override; - void copy(SettingsObjectPtr newSettings, const QDir &newDir) override; - QString typeName() const override; - bool canExport() const override - { - return false; - } -}; diff --git a/api/logic/minecraft/ftb/OneSixFTBInstance.cpp b/api/logic/minecraft/ftb/OneSixFTBInstance.cpp deleted file mode 100644 index edf31eb7..00000000 --- a/api/logic/minecraft/ftb/OneSixFTBInstance.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include "OneSixFTBInstance.h" -#include "FTBProfileStrategy.h" - -#include "minecraft/MinecraftProfile.h" -#include "minecraft/GradleSpecifier.h" -#include "tasks/SequentialTask.h" -#include <settings/INISettingsObject.h> -#include <FileSystem.h> - -#include <QJsonArray> - -OneSixFTBInstance::OneSixFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) : - OneSixInstance(globalSettings, settings, rootDir) -{ - m_globalSettings = globalSettings; -} - -void OneSixFTBInstance::copy(SettingsObjectPtr newSettings, const QDir &newDir) -{ - QStringList libraryNames; - // create patch file - { - qDebug()<< "Creating patch file for FTB instance..."; - QFile f(minecraftRoot() + "/pack.json"); - if (!f.open(QFile::ReadOnly)) - { - qCritical() << "Couldn't open" << f.fileName() << ":" << f.errorString(); - return; - } - QJsonObject root = QJsonDocument::fromJson(f.readAll()).object(); - QJsonArray libs = root.value("libraries").toArray(); - QJsonArray outLibs; - for (auto lib : libs) - { - QJsonObject libObj = lib.toObject(); - libObj.insert("MMC-hint", QString("local")); - libObj.insert("insert", QString("prepend")); - libraryNames.append(libObj.value("name").toString()); - outLibs.append(libObj); - } - root.remove("libraries"); - root.remove("id"); - - // HACK HACK HACK HACK - // A workaround for a problem in MultiMC, triggered by a historical problem in FTB, - // triggered by Mojang getting their library versions wrong in 1.7.10 - if(intendedVersionId() == "1.7.10") - { - auto insert = [&outLibs, &libraryNames](QString name) - { - QJsonObject libObj; - libObj.insert("insert", QString("replace")); - libObj.insert("name", name); - libraryNames.push_back(name); - outLibs.prepend(libObj); - }; - insert("com.google.guava:guava:16.0"); - insert("org.apache.commons:commons-lang3:3.2.1"); - } - root.insert("+libraries", outLibs); - root.insert("order", 1); - root.insert("fileId", QString("org.multimc.ftb.pack.json")); - root.insert("name", name()); - root.insert("mcVersion", intendedVersionId()); - root.insert("version", intendedVersionId()); - FS::ensureFilePathExists(newDir.absoluteFilePath("patches/ftb.json")); - QFile out(newDir.absoluteFilePath("patches/ftb.json")); - if (!out.open(QFile::WriteOnly | QFile::Truncate)) - { - qCritical() << "Couldn't open" << out.fileName() << ":" << out.errorString(); - return; - } - out.write(QJsonDocument(root).toJson()); - } - // copy libraries - { - qDebug() << "Copying FTB libraries"; - for (auto library : libraryNames) - { - GradleSpecifier lib(library); - const QString out = QDir::current().absoluteFilePath("libraries/" + lib.toPath()); - if (QFile::exists(out)) - { - continue; - } - if (!FS::ensureFilePathExists(out)) - { - qCritical() << "Couldn't create folder structure for" << out; - } - if (!QFile::copy(librariesPath().absoluteFilePath(lib.toPath()), out)) - { - qCritical() << "Couldn't copy" << QString(lib); - } - } - } - // now set the target instance to be plain OneSix - newSettings->set("InstanceType", "OneSix"); -} - -QString OneSixFTBInstance::id() const -{ - return "FTB/" + BaseInstance::id(); -} - -QDir OneSixFTBInstance::librariesPath() const -{ - return QDir(m_globalSettings->get("FTBRoot").toString() + "/libraries"); -} - -QDir OneSixFTBInstance::versionsPath() const -{ - return QDir(m_globalSettings->get("FTBRoot").toString() + "/versions"); -} - -bool OneSixFTBInstance::providesVersionFile() const -{ - return true; -} - -void OneSixFTBInstance::createProfile() -{ - m_profile.reset(new MinecraftProfile(new FTBProfileStrategy(this))); -} - -shared_qobject_ptr<Task> OneSixFTBInstance::createUpdateTask() -{ - return OneSixInstance::createUpdateTask(); -} - -QString OneSixFTBInstance::typeName() const -{ - return tr("OneSix FTB"); -} - -#include "OneSixFTBInstance.moc" diff --git a/api/logic/minecraft/ftb/OneSixFTBInstance.h b/api/logic/minecraft/ftb/OneSixFTBInstance.h deleted file mode 100644 index 640f609c..00000000 --- a/api/logic/minecraft/ftb/OneSixFTBInstance.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "minecraft/onesix/OneSixInstance.h" - -class OneSixFTBInstance : public OneSixInstance -{ - Q_OBJECT -public: - explicit OneSixFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual ~OneSixFTBInstance(){}; - - void copy(SettingsObjectPtr newSettings, const QDir &newDir) override; - - virtual void createProfile() override; - - virtual shared_qobject_ptr<Task> createUpdateTask() override; - - virtual QString id() const override; - - QDir librariesPath() const override; - QDir versionsPath() const override; - bool providesVersionFile() const override; - virtual QString typeName() const override; - bool canExport() const override - { - return false; - } -private: - SettingsObjectPtr m_globalSettings; -}; diff --git a/api/logic/minecraft/launch/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h index 53f3cee9..de9007d1 100644 --- a/api/logic/minecraft/launch/ClaimAccount.h +++ b/api/logic/minecraft/launch/ClaimAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h index 24daa81e..92026ecb 100644 --- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h +++ b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.cpp b/api/logic/minecraft/launch/DirectJavaLaunch.cpp index 07dbb86c..4ccc5c3c 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp +++ b/api/logic/minecraft/launch/DirectJavaLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.h b/api/logic/minecraft/launch/DirectJavaLaunch.h index 585b5c74..19087b50 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.h +++ b/api/logic/minecraft/launch/DirectJavaLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/ExtractNatives.cpp b/api/logic/minecraft/launch/ExtractNatives.cpp index f30dea89..7ddde374 100644 --- a/api/logic/minecraft/launch/ExtractNatives.cpp +++ b/api/logic/minecraft/launch/ExtractNatives.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/ExtractNatives.h b/api/logic/minecraft/launch/ExtractNatives.h index 0ec021a9..6e1e7cd4 100644 --- a/api/logic/minecraft/launch/ExtractNatives.h +++ b/api/logic/minecraft/launch/ExtractNatives.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.cpp b/api/logic/minecraft/launch/LauncherPartLaunch.cpp index b641968c..1fe9c323 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp +++ b/api/logic/minecraft/launch/LauncherPartLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,32 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state); } +#ifdef Q_OS_WIN +// returns 8.3 file format from long path +#include <windows.h> +QString shortPathName(const QString & file) +{ + auto input = file.toStdWString(); + std::wstring output; + long length = GetShortPathNameW(input.c_str(), NULL, 0); + // NOTE: this resizing might seem weird... + // when GetShortPathNameW fails, it returns length including null character + // when it succeeds, it returns length excluding null character + // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx + output.resize(length); + GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length); + output.resize(length-1); + QString ret = QString::fromStdWString(output); + return ret; +} +#endif + +// if the string survives roundtrip through local 8bit encoding... +bool fitsInLocal8bit(const QString & string) +{ + return string == QString::fromLocal8Bit(string.toLocal8Bit()); +} + void LauncherPartLaunch::executeTask() { auto instance = m_parent->instance(); @@ -45,7 +71,44 @@ void LauncherPartLaunch::executeTask() // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); - args << "-jar" << FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar"); + auto classPath = minecraftInstance->getClassPath(); + classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar")); + + auto natPath = minecraftInstance->getNativePath(); +#ifdef Q_OS_WIN + if (!fitsInLocal8bit(natPath)) + { + args << "-Djava.library.path=" + shortPathName(natPath); + } + else + { + args << "-Djava.library.path=" + natPath; + } +#else + args << "-Djava.library.path=" + natPath; +#endif + + args << "-cp"; +#ifdef Q_OS_WIN + QStringList processed; + for(auto & item: classPath) + { + if (!fitsInLocal8bit(item)) + { + processed << shortPathName(item); + } + else + { + processed << item; + } + } + args << processed.join(';'); +#else + args << classPath.join(':'); +#endif + args << "org.multimc.EntryPoint"; + + qDebug() << args.join(' '); QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); if(!wrapperCommandStr.isEmpty()) diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.h b/api/logic/minecraft/launch/LauncherPartLaunch.h index 01c73895..d384c2d1 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.h +++ b/api/logic/minecraft/launch/LauncherPartLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp index dad41a98..34825b45 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.cpp +++ b/api/logic/minecraft/launch/ModMinecraftJar.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,31 +14,69 @@ */ #include "ModMinecraftJar.h" -#include <launch/LaunchTask.h> -#include <QStandardPaths> +#include "launch/LaunchTask.h" +#include "MMCZip.h" +#include "minecraft/OpSys.h" +#include "FileSystem.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" void ModMinecraftJar::executeTask() { - m_jarModTask = m_parent->instance()->createJarModdingTask(); - if(m_jarModTask) + auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); + + if(!m_inst->getJarMods().size()) { - connect(m_jarModTask.get(), SIGNAL(finished()), this, SLOT(jarModdingFinished())); - m_jarModTask->start(); + emitSucceeded(); return; } + // nuke obsolete stripped jar(s) if needed + if(!FS::ensureFolderPathExists(m_inst->binRoot())) + { + emitFailed(tr("Couldn't create the bin folder for Minecraft.jar")); + } + + auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); + if(!removeJar()) + { + emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); + } + + // create temporary modded jar, if needed + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); + auto jarMods = m_inst->getJarMods(); + if(jarMods.size()) + { + auto mainJar = profile->getMainJar(); + QStringList jars, temp1, temp2, temp3, temp4; + mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); + auto sourceJarPath = jars[0]; + if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) + { + emitFailed(tr("Failed to create the custom Minecraft jar file.")); + return; + } + } emitSucceeded(); } -void ModMinecraftJar::jarModdingFinished() +void ModMinecraftJar::finalize() { - if(m_jarModTask->successful()) - { - emitSucceeded(); - } - else + removeJar(); +} + +bool ModMinecraftJar::removeJar() +{ + auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); + auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); + QFile finalJar(finalJarPath); + if(finalJar.exists()) { - QString reason = tr("jar modding failed because: %1.\n\n").arg(m_jarModTask->failReason()); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); + if(!finalJar.remove()) + { + return false; + } } + return true; } diff --git a/api/logic/minecraft/launch/ModMinecraftJar.h b/api/logic/minecraft/launch/ModMinecraftJar.h index 1196c487..b9a2d35b 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.h +++ b/api/logic/minecraft/launch/ModMinecraftJar.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ #include <launch/LaunchStep.h> #include <memory> -// FIXME: temporary wrapper for existing task. class ModMinecraftJar: public LaunchStep { Q_OBJECT @@ -31,9 +30,7 @@ public: { return false; } -private slots: - void jarModdingFinished(); - + void finalize() override; private: - std::shared_ptr<Task> m_jarModTask; + bool removeJar(); }; diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.cpp b/api/logic/minecraft/launch/PrintInstanceInfo.cpp index a9a87955..83bf584f 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp +++ b/api/logic/minecraft/launch/PrintInstanceInfo.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,73 @@ * limitations under the License. */ +#include <fstream> +#include <string> + #include "PrintInstanceInfo.h" #include <launch/LaunchTask.h> void PrintInstanceInfo::executeTask() { - auto instance = m_parent->instance(); - auto lines = instance->verboseDescription(m_session); - logLines(lines, MessageLevel::MultiMC); - emitSucceeded(); + auto instance = m_parent->instance(); + auto lines = instance->verboseDescription(m_session); + +#ifdef Q_OS_LINUX + std::ifstream cpuin("/proc/cpuinfo"); + for (std::string line; std::getline(cpuin, line);) + { + if (strncmp(line.c_str(), "model name", 10) == 0) + { + QStringList clines = (QStringList() << QString::fromStdString(line.substr(13, std::string::npos))); + logLines(clines, MessageLevel::MultiMC); + break; + } + } + + char buff[512]; + int gpuline = -1; + int cline = 0; + FILE *fp = popen("lspci -k", "r"); + if (fp != NULL) + { + while (fgets(buff, 512, fp) != NULL) + { + std::string str(buff); + if (str.length() < 9) + continue; + if (str.substr(8, 3) == "VGA") + { + gpuline = cline; + QStringList glines = (QStringList() << QString::fromStdString(str.substr(35, std::string::npos))); + logLines(glines, MessageLevel::MultiMC); + } + if (gpuline > -1 && gpuline != cline) + { + if (cline - gpuline < 3) + { + QStringList alines = (QStringList() << QString::fromStdString(str.substr(1, std::string::npos))); + logLines(alines, MessageLevel::MultiMC); + } + } + cline++; + } + } + + FILE *fp2 = popen("glxinfo", "r"); + if (fp2 != NULL) + { + while (fgets(buff, 512, fp2) != NULL) + { + if (strncmp(buff, "OpenGL version string:", 22) == 0) + { + QStringList drlines = (QStringList() << QString::fromUtf8(buff)); + logLines(drlines, MessageLevel::MultiMC); + break; + } + } + } +#endif + + logLines(lines, MessageLevel::MultiMC); + emitSucceeded(); } diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.h b/api/logic/minecraft/launch/PrintInstanceInfo.h index 6aed0865..61615ba1 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.h +++ b/api/logic/minecraft/launch/PrintInstanceInfo.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp index 0987d56f..6e318458 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.cpp +++ b/api/logic/minecraft/legacy/LegacyInstance.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ #include "LegacyInstance.h" -#include "minecraft/legacy/LegacyUpdate.h" #include "minecraft/legacy/LegacyModList.h" #include "minecraft/ModList.h" #include "minecraft/WorldList.h" @@ -28,14 +27,12 @@ #include <FileSystem.h> LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : MinecraftInstance(globalSettings, settings, rootDir) + : BaseInstance(globalSettings, settings, rootDir) { - m_lwjglFolderSetting = globalSettings->getSetting("LWJGLDir"); settings->registerSetting("NeedsRebuild", true); settings->registerSetting("ShouldUpdate", false); - settings->registerSetting("JarVersion", "Unknown"); - settings->registerSetting("LwjglVersion", "2.9.0"); - settings->registerSetting("IntendedJarVersion", ""); + settings->registerSetting("JarVersion", QString()); + settings->registerSetting("IntendedJarVersion", QString()); /* * custom base jar has no default. it is determined in code... see the accessor methods for *it @@ -68,178 +65,102 @@ QString LegacyInstance::customBaseJar() const return value; } -void LegacyInstance::setCustomBaseJar(QString val) -{ - if (val.isNull() || val.isEmpty() || val == defaultCustomBaseJar()) - m_settings->reset("CustomBaseJar"); - else - m_settings->set("CustomBaseJar", val); -} - -void LegacyInstance::setShouldUseCustomBaseJar(bool val) -{ - m_settings->set("UseCustomBaseJar", val); -} - bool LegacyInstance::shouldUseCustomBaseJar() const { return m_settings->get("UseCustomBaseJar").toBool(); } -shared_qobject_ptr<Task> LegacyInstance::createUpdateTask() +shared_qobject_ptr<Task> LegacyInstance::createUpdateTask(Net::Mode) { - // make sure the jar mods list is initialized by asking for it. - auto list = jarModList(); - // create an update task - return shared_qobject_ptr<Task>(new LegacyUpdate(this, this)); + return nullptr; } -std::shared_ptr<Task> LegacyInstance::createJarModdingTask() +/* +class LegacyJarModTask : public Task { - class JarModTask : public Task + //Q_OBJECT +public: + explicit LegacyJarModTask(std::shared_ptr<LegacyInstance> inst) : Task(nullptr), m_inst(inst) + { + } + virtual void executeTask() { - public: - explicit JarModTask(std::shared_ptr<LegacyInstance> inst) : Task(nullptr), m_inst(inst) + if (!m_inst->shouldRebuild()) { + emitSucceeded(); + return; } - virtual void executeTask() - { - if (!m_inst->shouldRebuild()) - { - emitSucceeded(); - return; - } - // Get the mod list - auto modList = m_inst->getJarMods(); + // Get the mod list + auto modList = m_inst->getJarMods(); - QFileInfo runnableJar(m_inst->runnableJar()); - QFileInfo baseJar(m_inst->baseJar()); - bool base_is_custom = m_inst->shouldUseCustomBaseJar(); + QFileInfo runnableJar(m_inst->runnableJar()); + QFileInfo baseJar(m_inst->baseJar()); + bool base_is_custom = m_inst->shouldUseCustomBaseJar(); - // Nothing to do if there are no jar mods to install, no backup and just the mc jar - if (base_is_custom) - { - // yes, this can happen if the instance only has the runnable jar and not the base jar - // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe - // because that's not something mmc4 guarantees - if (runnableJar.isFile() && !baseJar.exists() && modList.empty()) - { - m_inst->setShouldRebuild(false); - emitSucceeded(); - return; - } - - setStatus(tr("Installing mods: Backing up minecraft.jar ...")); - if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) - { - emitFailed("It seems both the active and base jar are gone. A fresh base jar will " - "be used on next run."); - m_inst->setShouldRebuild(true); - m_inst->setShouldUpdate(true); - m_inst->setShouldUseCustomBaseJar(false); - return; - } - } - - if (!baseJar.exists()) + // Nothing to do if there are no jar mods to install, no backup and just the mc jar + if (base_is_custom) + { + // yes, this can happen if the instance only has the runnable jar and not the base jar + // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe + // because that's not something mmc4 guarantees + if (runnableJar.isFile() && !baseJar.exists() && modList.empty()) { - emitFailed("The base jar " + baseJar.filePath() + " does not exist"); + m_inst->setShouldRebuild(false); + emitSucceeded(); return; } - if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) + setStatus(tr("Installing mods: Backing up minecraft.jar ...")); + if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) { - emitFailed("Failed to delete old minecraft.jar"); + emitFailed("It seems both the active and base jar are gone. A fresh base jar will " + "be used on next run."); + m_inst->setShouldRebuild(true); + m_inst->setShouldUpdate(true); + m_inst->setShouldUseCustomBaseJar(false); return; } + } - setStatus(tr("Installing mods: Opening minecraft.jar ...")); - - QString outputJarPath = runnableJar.filePath(); - QString inputJarPath = baseJar.filePath(); - - if(!MMCZip::createModdedJar(inputJarPath, outputJarPath, modList)) - { - emitFailed(tr("Failed to create the custom Minecraft jar file.")); - return; - } - m_inst->setShouldRebuild(false); - // inst->UpdateVersion(true); - emitSucceeded(); + if (!baseJar.exists()) + { + emitFailed("The base jar " + baseJar.filePath() + " does not exist"); return; - } - std::shared_ptr<LegacyInstance> m_inst; - }; - return std::make_shared<JarModTask>(std::dynamic_pointer_cast<LegacyInstance>(shared_from_this())); -} -QString LegacyInstance::createLaunchScript(AuthSessionPtr session) -{ - QString launchScript; - - // window size - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - { - windowParams = "max"; - } - else - { - windowParams = QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt()); - } - - QString lwjgl = QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion()).absolutePath(); - launchScript += "userName " + session->player_name + "\n"; - launchScript += "sessionId " + session->session + "\n"; - launchScript += "windowTitle " + windowTitle() + "\n"; - launchScript += "windowParams " + windowParams + "\n"; - launchScript += "cp bin/minecraft.jar\n"; - launchScript += "cp " + lwjgl + "/lwjgl.jar\n"; - launchScript += "cp " + lwjgl + "/lwjgl_util.jar\n"; - launchScript += "cp " + lwjgl + "/jinput.jar\n"; - launchScript += "natives " + lwjgl + "/natives\n"; - launchScript += "traits legacyLaunch\n"; - launchScript += "launcher onesix\n"; - return launchScript; -} + if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) + { + emitFailed("Failed to delete old minecraft.jar"); + return; + } -std::shared_ptr<LaunchStep> LegacyInstance::createMainLaunchStep(LaunchTask * parent, AuthSessionPtr session) -{ - auto step = std::make_shared<LauncherPartLaunch>(parent); - step->setWorkingDirectory(minecraftRoot()); - step->setAuthSession(session); - return step; -} + setStatus(tr("Installing mods: Opening minecraft.jar ...")); -QString LegacyInstance::launchMethod() -{ - return "Legacy"; -} + QString outputJarPath = runnableJar.filePath(); + QString inputJarPath = baseJar.filePath(); -QStringList LegacyInstance::validLaunchMethods() -{ - return {"Legacy"}; -} + if(!MMCZip::createModdedJar(inputJarPath, outputJarPath, modList)) + { + emitFailed(tr("Failed to create the custom Minecraft jar file.")); + return; + } + m_inst->setShouldRebuild(false); + // inst->UpdateVersion(true); + emitSucceeded(); + return; -std::shared_ptr<ModList> LegacyInstance::coreModList() const -{ - if (!core_mod_list) - { - core_mod_list.reset(new ModList(coreModsDir())); } - core_mod_list->update(); - return core_mod_list; -} + std::shared_ptr<LegacyInstance> m_inst; +}; +*/ std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const { if (!jar_mod_list) { auto list = new LegacyModList(jarModsDir(), modListFile()); - connect(list, SIGNAL(changed()), SLOT(jarModsChanged())); jar_mod_list.reset(list); } jar_mod_list->update(); @@ -251,39 +172,20 @@ QList<Mod> LegacyInstance::getJarMods() const return jarModList()->allMods(); } -void LegacyInstance::jarModsChanged() +QString LegacyInstance::minecraftRoot() const { - qDebug() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt."; - setShouldRebuild(true); -} + QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); -std::shared_ptr<ModList> LegacyInstance::loaderModList() const -{ - if (!loader_mod_list) - { - loader_mod_list.reset(new ModList(loaderModsDir())); - } - loader_mod_list->update(); - return loader_mod_list; + if (mcDir.exists() && !dotMCDir.exists()) + return mcDir.filePath(); + else + return dotMCDir.filePath(); } -std::shared_ptr<ModList> LegacyInstance::texturePackList() const +QString LegacyInstance::binRoot() const { - if (!texture_pack_list) - { - texture_pack_list.reset(new ModList(texturePacksDir())); - } - texture_pack_list->update(); - return texture_pack_list; -} - -std::shared_ptr<WorldList> LegacyInstance::worldList() const -{ - if (!m_world_list) - { - m_world_list.reset(new WorldList(savesDir())); - } - return m_world_list; + return FS::PathCombine(minecraftRoot(), "bin"); } QString LegacyInstance::jarModsDir() const @@ -340,38 +242,16 @@ bool LegacyInstance::shouldRebuild() const return m_settings->get("NeedsRebuild").toBool(); } -void LegacyInstance::setShouldRebuild(bool val) -{ - m_settings->set("NeedsRebuild", val); -} - QString LegacyInstance::currentVersionId() const { return m_settings->get("JarVersion").toString(); } -QString LegacyInstance::lwjglVersion() const -{ - return m_settings->get("LwjglVersion").toString(); -} - -void LegacyInstance::setLWJGLVersion(QString val) -{ - m_settings->set("LwjglVersion", val); -} - QString LegacyInstance::intendedVersionId() const { return m_settings->get("IntendedJarVersion").toString(); } -bool LegacyInstance::setIntendedVersionId(QString version) -{ - settings()->set("IntendedJarVersion", version); - setShouldUpdate(true); - return true; -} - bool LegacyInstance::shouldUpdate() const { QVariant var = settings()->get("ShouldUpdate"); @@ -382,11 +262,6 @@ bool LegacyInstance::shouldUpdate() const return true; } -void LegacyInstance::setShouldUpdate(bool val) -{ - settings()->set("ShouldUpdate", val); -} - QString LegacyInstance::defaultBaseJar() const { return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; @@ -397,9 +272,13 @@ QString LegacyInstance::defaultCustomBaseJar() const return FS::PathCombine(binRoot(), "mcbackup.jar"); } -QString LegacyInstance::lwjglFolder() const +std::shared_ptr<WorldList> LegacyInstance::worldList() const { - return m_lwjglFolderSetting->get().toString(); + if (!m_world_list) + { + m_world_list.reset(new WorldList(savesDir())); + } + return m_world_list; } QString LegacyInstance::typeName() const @@ -407,6 +286,11 @@ QString LegacyInstance::typeName() const return tr("Legacy"); } +QString LegacyInstance::getStatusbarDescription() +{ + return tr("Instance from previous versions."); +} + QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) { QStringList out; @@ -422,48 +306,6 @@ QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) out << ""; } - if(loaderModList()->size()) - { - out << "Mods:"; - for(auto & mod: loaderModList()->allMods()) - { - if(!mod.enabled()) - continue; - if(mod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + mod.filename().completeBaseName(); - } - out << ""; - } - - if(coreModList()->size()) - { - out << "Core Mods:"; - for(auto & coremod: coreModList()->allMods()) - { - if(!coremod.enabled()) - continue; - if(coremod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + coremod.filename().completeBaseName(); - } - out << ""; - } - - if(jarModList()->size()) - { - out << "Jar Mods:"; - for(auto & jarmod: jarModList()->allMods()) - { - out << " " + jarmod.name() + " (" + jarmod.filename().filePath() + ")"; - } - out << ""; - } - QString windowParams; if (settings()->get("LaunchMaximized").toBool()) { @@ -478,40 +320,3 @@ QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) out << ""; return out; } - -QStringList LegacyInstance::getClassPath() const -{ - QString launchScript; - QString lwjgl = getNativePath(); - QStringList out = - { - "bin/minecraft.jar", - lwjgl + "/lwjgl.jar", - lwjgl + "/lwjgl_util.jar", - lwjgl + "/jinput.jar" - }; - return out; -} - -QString LegacyInstance::getMainClass() const -{ - return "net.minecraft.client.Minecraft"; -} - -QString LegacyInstance::getNativePath() const -{ - return QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion()).absolutePath(); -} - -QStringList LegacyInstance::getNativeJars() const -{ - return {}; -} - -QStringList LegacyInstance::processMinecraftArgs(AuthSessionPtr account) const -{ - QStringList out; - out.append(account->player_name); - out.append(account->session); - return out; -} diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h index 15d1383f..4a8bc436 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.h +++ b/api/logic/minecraft/legacy/LegacyInstance.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,22 +15,27 @@ #pragma once -#include "minecraft/MinecraftInstance.h" +#include "BaseInstance.h" +#include "minecraft/Mod.h" #include "multimc_logic_export.h" class ModList; class LegacyModList; +class WorldList; class Task; - -class MULTIMC_LOGIC_EXPORT LegacyInstance : public MinecraftInstance +/* + * WHY: Legacy instances - from MultiMC 3 and 4 - are here only to provide a way to upgrade them to the current format. + */ +class MULTIMC_LOGIC_EXPORT LegacyInstance : public BaseInstance { Q_OBJECT public: explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual void init() override {}; + virtual void init() override {} + virtual void saveNow() override {} /// Path to the instance's minecraft.jar QString runnableJar() const; @@ -38,20 +43,6 @@ 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<LegacyModList> jarModList() const ; - virtual QList< Mod > getJarMods() const override; - std::shared_ptr<ModList> coreModList() const; - std::shared_ptr<ModList> loaderModList() const; - std::shared_ptr<ModList> texturePackList() const override; - std::shared_ptr<WorldList> worldList() const override; - ////// Directories ////// QString libDir() const; QString savesDir() const; @@ -61,8 +52,10 @@ public: QString coreModsDir() const; QString resourceDir() const; virtual QString instanceConfigFolder() const override; + QString minecraftRoot() const; // Path to the instance's minecraft directory. + QString binRoot() const; // Path to the instance's minecraft bin directory. - /// Get the curent base jar of this instance. By default, it's the + /// Get the curent base jar of this instance. By default, it's the /// versions/$version/$version.jar QString baseJar() const; @@ -75,13 +68,15 @@ public: * Whether or not custom base jar is used */ bool shouldUseCustomBaseJar() const; - void setShouldUseCustomBaseJar(bool val); /*! * The value of the custom base jar */ QString customBaseJar() const; - void setCustomBaseJar(QString val); + + std::shared_ptr<LegacyModList> jarModList() const; + QList<Mod> getJarMods() const; + std::shared_ptr<WorldList> worldList() const; /*! * Whether or not the instance's minecraft.jar needs to be rebuilt. @@ -89,67 +84,57 @@ public: * re-added to a fresh minecraft.jar file. */ bool shouldRebuild() const; - void setShouldRebuild(bool val); - - virtual QString currentVersionId() const override; - - //! The version of LWJGL that this instance uses. - QString lwjglVersion() const; - //! Where the lwjgl versions foor this instance can be found... HACK HACK HACK - QString lwjglFolder() const; + QString currentVersionId() const; + QString intendedVersionId() const; - /// st the version of LWJGL libs this instance will use - void setLWJGLVersion(QString val); - - virtual QString intendedVersionId() const override; - virtual bool setIntendedVersionId(QString version) override; - - virtual QSet<QString> traits() override + QSet<QString> traits() const override { return {"legacy-instance", "texturepacks"}; }; - virtual bool shouldUpdate() const override; - virtual void setShouldUpdate(bool val) override; - virtual shared_qobject_ptr<Task> createUpdateTask() override; - virtual std::shared_ptr<Task> createJarModdingTask() override; - virtual QString createLaunchScript(AuthSessionPtr session) override; + virtual bool shouldUpdate() const; + virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override; virtual QString typeName() const override; - bool canExport() const override + bool canLaunch() const override + { + return false; + } + bool canEdit() const override { return true; } - - QStringList getClassPath() const override; - QString getMainClass() const override; - - QStringList getNativeJars() const override; - QString getNativePath() const override; - - QString getLocalLibraryPath() const override + bool canExport() const override + { + return false; + } + std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override + { + return nullptr; + } + IPathMatcher::Ptr getLogFileMatcher() override + { + return nullptr; + } + QString getLogFileRoot() override { - return QString(); + return minecraftRoot(); } - QStringList processMinecraftArgs(AuthSessionPtr account) const override; + QString getStatusbarDescription() override; QStringList verboseDescription(AuthSessionPtr session) override; -protected: - std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) override; - QStringList validLaunchMethods() override; - QString launchMethod() override; - + QProcessEnvironment createEnvironment() override + { + return QProcessEnvironment(); + } + QMap<QString, QString> getVariables() const override + { + return {}; + } protected: mutable std::shared_ptr<LegacyModList> jar_mod_list; - mutable std::shared_ptr<ModList> core_mod_list; - mutable std::shared_ptr<ModList> loader_mod_list; - mutable std::shared_ptr<ModList> texture_pack_list; mutable std::shared_ptr<WorldList> m_world_list; - std::shared_ptr<Setting> m_lwjglFolderSetting; -protected -slots: - virtual void jarModsChanged(); }; diff --git a/api/logic/minecraft/legacy/LegacyModList.cpp b/api/logic/minecraft/legacy/LegacyModList.cpp index 052f75fd..638b2e21 100644 --- a/api/logic/minecraft/legacy/LegacyModList.cpp +++ b/api/logic/minecraft/legacy/LegacyModList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,55 +15,26 @@ #include "LegacyModList.h" #include <FileSystem.h> -#include <QMimeData> -#include <QUrl> -#include <QUuid> #include <QString> -#include <QFileSystemWatcher> #include <QDebug> LegacyModList::LegacyModList(const QString &dir, const QString &list_file) - : QAbstractListModel(), m_dir(dir), m_list_file(list_file) + : m_dir(dir), m_list_file(list_file) { FS::ensureFolderPathExists(m_dir.absolutePath()); m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); - m_list_id = QUuid::createUuid().toString(); - m_watcher = new QFileSystemWatcher(this); - is_watching = false; - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, - SLOT(directoryChanged(QString))); } -void LegacyModList::startWatching() -{ - update(); - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) - { - qDebug() << "Started watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to start watching " << m_dir.absolutePath(); - } -} - -void LegacyModList::stopWatching() -{ - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) + struct OrderItem { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } -} + QString id; + bool enabled = false; + }; + typedef QList<OrderItem> OrderList; -void LegacyModList::internalSort(QList<Mod> &what) +static void internalSort(QList<Mod> &what) { auto predicate = [](const Mod &left, const Mod &right) { @@ -76,9 +47,43 @@ void LegacyModList::internalSort(QList<Mod> &what) std::sort(what.begin(), what.end(), predicate); } +static OrderList readListFile(const QString &m_list_file) +{ + OrderList itemList; + if (m_list_file.isNull() || m_list_file.isEmpty()) + return itemList; + + QFile textFile(m_list_file); + if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) + return OrderList(); + + QTextStream textStream; + textStream.setAutoDetectUnicode(true); + textStream.setDevice(&textFile); + while (true) + { + QString line = textStream.readLine(); + if (line.isNull() || line.isEmpty()) + break; + else + { + OrderItem it; + it.enabled = !line.endsWith(".disabled"); + if (!it.enabled) + { + line.chop(9); + } + it.id = line; + itemList.append(it); + } + } + textFile.close(); + return itemList; +} + bool LegacyModList::update() { - if (!isValid()) + if (!m_dir.exists() || !m_dir.isReadable()) return false; QList<Mod> orderedMods; @@ -88,7 +93,7 @@ bool LegacyModList::update() bool orderOrStateChanged = false; // first, process the ordered items (if any) - OrderList listOrder = readListFile(); + OrderList listOrder = readListFile(m_list_file); for (auto item : listOrder) { QFileInfo infoEnabled(m_dir.filePath(item.id)); @@ -157,460 +162,10 @@ bool LegacyModList::update() } } } - beginResetModel(); mods.swap(orderedMods); - endResetModel(); if (orderOrStateChanged && !m_list_file.isEmpty()) { qDebug() << "Mod list " << m_list_file << " changed!"; - saveListFile(); - emit changed(); - } - return true; -} - -void LegacyModList::directoryChanged(QString path) -{ - update(); -} - -LegacyModList::OrderList LegacyModList::readListFile() -{ - OrderList itemList; - if (m_list_file.isNull() || m_list_file.isEmpty()) - return itemList; - - QFile textFile(m_list_file); - if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) - return OrderList(); - - QTextStream textStream; - textStream.setAutoDetectUnicode(true); - textStream.setDevice(&textFile); - while (true) - { - QString line = textStream.readLine(); - if (line.isNull() || line.isEmpty()) - break; - else - { - OrderItem it; - it.enabled = !line.endsWith(".disabled"); - if (!it.enabled) - { - line.chop(9); - } - it.id = line; - itemList.append(it); - } - } - textFile.close(); - return itemList; -} - -bool LegacyModList::saveListFile() -{ - if (m_list_file.isNull() || m_list_file.isEmpty()) - return false; - QFile textFile(m_list_file); - if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) - return false; - QTextStream textStream; - textStream.setGenerateByteOrderMark(true); - textStream.setCodec("UTF-8"); - textStream.setDevice(&textFile); - for (auto mod : mods) - { - textStream << mod.mmc_id(); - if (!mod.enabled()) - textStream << ".disabled"; - textStream << endl; - } - textFile.close(); - return false; -} - -bool LegacyModList::isValid() -{ - return m_dir.exists() && m_dir.isReadable(); -} - -bool LegacyModList::installMod(const QString &filename, int index) -{ - // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - QFileInfo fileinfo(FS::NormalizePath(filename)); - - qDebug() << "installing: " << fileinfo.absoluteFilePath(); - - if (!fileinfo.exists() || !fileinfo.isReadable() || index < 0) - { - return false; - } - Mod m(fileinfo); - if (!m.valid()) - return false; - - // if it's already there, replace the original mod (in place) - int idx = mods.indexOf(m); - if (idx != -1) - { - int idx2 = mods.indexOf(m, idx + 1); - if (idx2 != -1) - return false; - if (mods[idx].replace(m)) - { - - auto left = this->index(index); - auto right = this->index(index, columnCount(QModelIndex()) - 1); - emit dataChanged(left, right); - saveListFile(); - update(); - return true; - } - return false; - } - - auto type = m.type(); - if (type == Mod::MOD_UNKNOWN) - return false; - if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) - { - QString newpath = FS::PathCombine(m_dir.path(), fileinfo.fileName()); - if (!QFile::copy(fileinfo.filePath(), newpath)) - return false; - m.repath(newpath); - beginInsertRows(QModelIndex(), index, index); - mods.insert(index, m); - endInsertRows(); - saveListFile(); - update(); - return true; - } - else if (type == Mod::MOD_FOLDER) - { - - QString from = fileinfo.filePath(); - QString to = FS::PathCombine(m_dir.path(), fileinfo.fileName()); - if (!FS::copy(from, to)()) - return false; - m.repath(to); - beginInsertRows(QModelIndex(), index, index); - mods.insert(index, m); - endInsertRows(); - saveListFile(); - update(); - return true; } - return false; -} - -bool LegacyModList::deleteMod(int index) -{ - if (index >= mods.size() || index < 0) - return false; - Mod &m = mods[index]; - if (m.destroy()) - { - beginRemoveRows(QModelIndex(), index, index); - mods.removeAt(index); - endRemoveRows(); - saveListFile(); - emit changed(); - return true; - } - return false; -} - -bool LegacyModList::deleteMods(int first, int last) -{ - for (int i = first; i <= last; i++) - { - Mod &m = mods[i]; - m.destroy(); - } - beginRemoveRows(QModelIndex(), first, last); - mods.erase(mods.begin() + first, mods.begin() + last + 1); - endRemoveRows(); - saveListFile(); - emit changed(); - return true; -} - -bool LegacyModList::moveModTo(int from, int to) -{ - if (from < 0 || from >= mods.size()) - return false; - if (to >= rowCount()) - to = rowCount() - 1; - if (to == -1) - to = rowCount() - 1; - if (from == to) - return false; - int togap = to > from ? to + 1 : to; - beginMoveRows(QModelIndex(), from, from, QModelIndex(), togap); - mods.move(from, to); - endMoveRows(); - saveListFile(); - emit changed(); - return true; -} - -bool LegacyModList::moveModUp(int from) -{ - if (from > 0) - return moveModTo(from, from - 1); - return false; -} - -bool LegacyModList::moveModsUp(int first, int last) -{ - if (first == 0) - return false; - - beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1); - mods.move(first - 1, last); - endMoveRows(); - saveListFile(); - emit changed(); return true; } - -bool LegacyModList::moveModDown(int from) -{ - if (from < 0) - return false; - if (from < mods.size() - 1) - return moveModTo(from, from + 1); - return false; -} - -bool LegacyModList::moveModsDown(int first, int last) -{ - if (last == mods.size() - 1) - return false; - - beginMoveRows(QModelIndex(), first, last, QModelIndex(), last + 2); - mods.move(last + 1, first); - endMoveRows(); - saveListFile(); - emit changed(); - return true; -} - -int LegacyModList::columnCount(const QModelIndex &parent) const -{ - return 3; -} - -QVariant LegacyModList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= mods.size()) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - switch (column) - { - case NameColumn: - return mods[row].name(); - case VersionColumn: - return mods[row].version(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return mods[row].mmc_id(); - - case Qt::CheckStateRole: - switch (column) - { - case ActiveColumn: - return mods[row].enabled() ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } - default: - return QVariant(); - } -} - -bool LegacyModList::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return false; - } - - if (role == Qt::CheckStateRole) - { - auto &mod = mods[index.row()]; - if (mod.enable(!mod.enabled())) - { - emit dataChanged(index, index); - return true; - } - } - return false; -} - -QVariant LegacyModList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return QString(); - case NameColumn: - return tr("Name"); - case VersionColumn: - return tr("Version"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case ActiveColumn: - return tr("Is the mod enabled?"); - case NameColumn: - return tr("The name of the mod."); - case VersionColumn: - return tr("The version of the mod."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); -} - -Qt::ItemFlags LegacyModList::flags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - if (index.isValid()) - return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | - defaultFlags; - else - return Qt::ItemIsDropEnabled | defaultFlags; -} - -QStringList LegacyModList::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - types << "text/plain"; - return types; -} - -Qt::DropActions LegacyModList::supportedDropActions() const -{ - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; -} - -Qt::DropActions LegacyModList::supportedDragActions() const -{ - // move to other mod lists or VOID - return Qt::MoveAction; -} - -QMimeData *LegacyModList::mimeData(const QModelIndexList &indexes) const -{ - QMimeData *data = new QMimeData(); - - if (indexes.size() == 0) - return data; - - auto idx = indexes[0]; - int row = idx.row(); - if (row < 0 || row >= mods.size()) - return data; - - QStringList params; - params << m_list_id << QString::number(row); - data->setText(params.join('|')); - return data; -} - -bool LegacyModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, - const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) - return true; - // check if the action is supported - if (!data || !(action & supportedDropActions())) - return false; - if (parent.isValid()) - { - row = parent.row(); - column = parent.column(); - } - - if (row > rowCount()) - row = rowCount(); - if (row == -1) - row = rowCount(); - if (column == -1) - column = 0; - qDebug() << "Drop row: " << row << " column: " << column; - - // files dropped from outside? - if (data->hasUrls()) - { - bool was_watching = is_watching; - if (was_watching) - stopWatching(); - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - continue; - QString filename = url.toLocalFile(); - installMod(filename, row); - // if there is no ordering, re-sort the list - if (m_list_file.isEmpty()) - { - beginResetModel(); - internalSort(mods); - endResetModel(); - } - } - if (was_watching) - startWatching(); - return true; - } - else if (data->hasText()) - { - QString sourcestr = data->text(); - auto list = sourcestr.split('|'); - if (list.size() != 2) - return false; - QString remoteId = list[0]; - int remoteIndex = list[1].toInt(); - qDebug() << "move: " << sourcestr; - // no moving of things between two lists - if (remoteId != m_list_id) - return false; - // no point moving to the same place... - if (row == remoteIndex) - return false; - // otherwise, move the mod :D - moveModTo(remoteIndex, row); - return true; - } - return false; -} diff --git a/api/logic/minecraft/legacy/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h index d1bd0e8b..19b191a7 100644 --- a/api/logic/minecraft/legacy/LegacyModList.h +++ b/api/logic/minecraft/legacy/LegacyModList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ #include <QList> #include <QString> #include <QDir> -#include <QAbstractListModel> #include "minecraft/Mod.h" @@ -26,102 +25,19 @@ class LegacyInstance; class BaseInstance; -class QFileSystemWatcher; /** * A legacy mod list. * Backed by a folder. */ -class MULTIMC_LOGIC_EXPORT LegacyModList : public QAbstractListModel +class MULTIMC_LOGIC_EXPORT LegacyModList { - Q_OBJECT public: - enum Columns - { - ActiveColumn = 0, - NameColumn, - VersionColumn - }; - LegacyModList(const QString &dir, const QString &list_file = QString()); - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole); - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const - { - return size(); - } - ; - virtual QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const; - virtual int columnCount(const QModelIndex &parent) const; - - size_t size() const - { - return mods.size(); - } - ; - bool empty() const - { - return size() == 0; - } - Mod &operator[](size_t index) - { - return mods[index]; - } + LegacyModList(const QString &dir, const QString &list_file = QString()); /// Reloads the mod list and returns true if the list changed. - virtual bool update(); - - /** - * Adds the given mod to the list at the given index - if the list supports custom ordering - */ - virtual bool installMod(const QString & filename, int index = 0); - - /// Deletes the mod at the given index. - virtual bool deleteMod(int index); - - /// Deletes all the selected mods - virtual bool deleteMods(int first, int last); - - /** - * move the mod at index to the position N - * 0 is the beginning of the list, length() is the end of the list. - */ - virtual bool moveModTo(int from, int to); - - /** - * move the mod at index one position upwards - */ - virtual bool moveModUp(int from); - virtual bool moveModsUp(int first, int last); - - /** - * move the mod at index one position downwards - */ - virtual bool moveModDown(int from); - virtual bool moveModsDown(int first, int last); - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - /// get data for drag action - virtual QMimeData *mimeData(const QModelIndexList &indexes) const; - /// get the supported mime types - virtual QStringList mimeTypes() const; - /// process data from drop action - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, - const QModelIndex &parent); - /// what drag actions do we support? - virtual Qt::DropActions supportedDragActions() const; - - /// what drop actions do we support? - virtual Qt::DropActions supportedDropActions() const; - - void startWatching(); - void stopWatching(); - - virtual bool isValid(); + bool update(); QDir dir() { @@ -133,28 +49,8 @@ public: return mods; } -private: - void internalSort(QList<Mod> & what); - struct OrderItem - { - QString id; - bool enabled = false; - }; - typedef QList<OrderItem> OrderList; - OrderList readListFile(); - bool saveListFile(); -private -slots: - void directoryChanged(QString path); - -signals: - void changed(); - protected: - QFileSystemWatcher *m_watcher; - bool is_watching; QDir m_dir; QString m_list_file; - QString m_list_id; QList<Mod> mods; }; diff --git a/api/logic/minecraft/legacy/LegacyUpdate.cpp b/api/logic/minecraft/legacy/LegacyUpdate.cpp deleted file mode 100644 index e263d0de..00000000 --- a/api/logic/minecraft/legacy/LegacyUpdate.cpp +++ /dev/null @@ -1,399 +0,0 @@ -/* Copyright 2013-2017 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <QStringList> -#include <quazip.h> -#include <quazipfile.h> -#include <QDebug> - -#include "Env.h" -#include "BaseInstance.h" -#include "net/URLConstants.h" -#include "MMCZip.h" - -#include "LegacyUpdate.h" -#include "LegacyModList.h" - -#include "LwjglVersionList.h" -#include "LegacyInstance.h" -#include <FileSystem.h> - -LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst) -{ -} - -void LegacyUpdate::executeTask() -{ - fmllibsStart(); -} - -void LegacyUpdate::fmllibsStart() -{ - // Get the mod list - LegacyInstance *inst = (LegacyInstance *)m_inst; - auto modList = inst->jarModList(); - - bool forge_present = false; - - QString version = inst->intendedVersionId(); - auto & fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; - if (!fmlLibsMapping.contains(version)) - { - lwjglStart(); - return; - } - - auto &libList = fmlLibsMapping[version]; - - // determine if we need some libs for FML or forge - setStatus(tr("Checking for FML libraries...")); - for (unsigned i = 0; i < modList->size(); i++) - { - auto &mod = modList->operator[](i); - - // do not use disabled mods. - if (!mod.enabled()) - continue; - - if (mod.type() != Mod::MOD_ZIPFILE) - continue; - - if (mod.mmc_id().contains("forge", Qt::CaseInsensitive)) - { - forge_present = true; - break; - } - if (mod.mmc_id().contains("fml", Qt::CaseInsensitive)) - { - forge_present = true; - break; - } - } - // we don't... - if (!forge_present) - { - lwjglStart(); - return; - } - - // now check the lib folder inside the instance for files. - for (auto &lib : libList) - { - QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); - if (libInfo.exists()) - continue; - fmlLibsToProcess.append(lib); - } - - // if everything is in place, there's nothing to do here... - if (fmlLibsToProcess.isEmpty()) - { - lwjglStart(); - return; - } - - // download missing libs to our place - setStatus(tr("Dowloading FML libraries...")); - auto dljob = new NetJob("FML libraries"); - auto metacache = ENV.metacache(); - for (auto &lib : fmlLibsToProcess) - { - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename - : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename; - dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); - } - - connect(dljob, &NetJob::succeeded, this, &LegacyUpdate::fmllibsFinished); - connect(dljob, &NetJob::failed, this, &LegacyUpdate::fmllibsFailed); - connect(dljob, &NetJob::progress, this, &LegacyUpdate::progress); - legacyDownloadJob.reset(dljob); - legacyDownloadJob->start(); -} - -void LegacyUpdate::fmllibsFinished() -{ - legacyDownloadJob.reset(); - if(!fmlLibsToProcess.isEmpty()) - { - setStatus(tr("Copying FML libraries into the instance...")); - LegacyInstance *inst = (LegacyInstance *)m_inst; - auto metacache = ENV.metacache(); - int index = 0; - for (auto &lib : fmlLibsToProcess) - { - progress(index, fmlLibsToProcess.size()); - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - auto path = FS::PathCombine(inst->libDir(), lib.filename); - if(!FS::ensureFilePathExists(path)) - { - emitFailed(tr("Failed creating FML library folder inside the instance.")); - return; - } - if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) - { - emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); - return; - } - index++; - } - progress(index, fmlLibsToProcess.size()); - } - lwjglStart(); -} - -void LegacyUpdate::fmllibsFailed(QString reason) -{ - emitFailed(tr("Game update failed: it was impossible to fetch the required FML libraries. Reason: %1").arg(reason)); - return; -} - -void LegacyUpdate::lwjglStart() -{ - LegacyInstance *inst = (LegacyInstance *)m_inst; - - auto list = std::dynamic_pointer_cast<LWJGLVersionList>(ENV.getVersionList("org.lwjgl.legacy")); - if (!list->isLoaded()) - { - setStatus(tr("Checking the LWJGL version list...")); - list->loadList(); - auto task = list->getLoadTask(); - connect(task.get(), &Task::succeeded, this, &LegacyUpdate::lwjglStart); - connect(task.get(), &Task::failed, this, [&](const QString & error) - { - emitFailed(tr("Failed to refresh LWJGL list: %1.").arg(error)); - }); - return; - } - - lwjglVersion = inst->lwjglVersion(); - lwjglTargetPath = FS::PathCombine(inst->lwjglFolder(), lwjglVersion); - lwjglNativesPath = FS::PathCombine(lwjglTargetPath, "natives"); - - // if the 'done' file exists, we don't have to download this again - QFileInfo doneFile(FS::PathCombine(lwjglTargetPath, "done")); - if (doneFile.exists()) - { - jarStart(); - return; - } - - setStatus(tr("Downloading new LWJGL...")); - auto version = std::dynamic_pointer_cast<LWJGLVersion>(list->findVersion(lwjglVersion)); - if (!version) - { - emitFailed(QString("Game update failed: the selected LWJGL version is invalid: %1").arg(lwjglVersion)); - return; - } - - QString url = version->url(); - QUrl realUrl(url); - QString hostname = realUrl.host(); - auto worker = &ENV.qnam(); - QNetworkRequest req(realUrl); - req.setRawHeader("Host", hostname.toLatin1()); - req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); - QNetworkReply *rep = worker->get(req); - - m_reply = std::shared_ptr<QNetworkReply>(rep); - connect(rep, &QNetworkReply::downloadProgress, this, &LegacyUpdate::progress); - connect(worker, &QNetworkAccessManager::finished, this, &LegacyUpdate::lwjglFinished); -} - -void LegacyUpdate::lwjglFinished(QNetworkReply *reply) -{ - if (m_reply.get() != reply) - { - return; - } - if (reply->error() != QNetworkReply::NoError) - { - emitFailed("Failed to download: " + reply->errorString() + - "\nSometimes you have to wait a bit if you download many LWJGL versions in " - "a row. YMMV"); - return; - } - auto worker = &ENV.qnam(); - // Here i check if there is a cookie for me in the reply and extract it - QList<QNetworkCookie> cookies = - qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader)); - if (cookies.count() != 0) - { - // you must tell which cookie goes with which url - worker->cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net")); - } - - // here you can check for the 302 or whatever other header i need - QVariant newLoc = reply->header(QNetworkRequest::LocationHeader); - if (newLoc.isValid()) - { - QString redirectedTo = reply->header(QNetworkRequest::LocationHeader).toString(); - QUrl realUrl(redirectedTo); - QString hostname = realUrl.host(); - QNetworkRequest req(redirectedTo); - req.setRawHeader("Host", hostname.toLatin1()); - req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); - QNetworkReply *rep = worker->get(req); - connect(rep, &QNetworkReply::downloadProgress, this, &LegacyUpdate::progress); - m_reply = std::shared_ptr<QNetworkReply>(rep); - return; - } - QFile saveMe("lwjgl.zip"); - saveMe.open(QIODevice::WriteOnly); - saveMe.write(m_reply->readAll()); - saveMe.close(); - setStatus(tr("Installing new LWJGL...")); - extractLwjgl(); - jarStart(); -} -void LegacyUpdate::extractLwjgl() -{ - // make sure the directories are there - - bool success = FS::ensureFolderPathExists(lwjglNativesPath); - - if (!success) - { - emitFailed("Failed to extract the lwjgl libs - error when creating required folders."); - return; - } - - QuaZip zip("lwjgl.zip"); - if (!zip.open(QuaZip::mdUnzip)) - { - emitFailed("Failed to extract the lwjgl libs - not a valid archive."); - return; - } - - // and now we are going to access files inside it - QuaZipFile file(&zip); - const QString jarNames[] = {"jinput.jar", "lwjgl_util.jar", "lwjgl.jar"}; - for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile()) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - emitFailed("Failed to extract the lwjgl libs - error while reading archive."); - return; - } - QuaZipFileInfo info; - QString name = file.getActualFileName(); - if (name.endsWith('/')) - { - file.close(); - continue; - } - QString destFileName; - // Look for the jars - for (int i = 0; i < 3; i++) - { - if (name.endsWith(jarNames[i])) - { - destFileName = FS::PathCombine(lwjglTargetPath, jarNames[i]); - } - } - // Not found? look for the natives - if (destFileName.isEmpty()) - { -#ifdef Q_OS_WIN32 - QString nativesDir = "windows"; -#else -#ifdef Q_OS_MAC - QString nativesDir = "macosx"; -#else - QString nativesDir = "linux"; -#endif -#endif - if (name.contains(nativesDir)) - { - int lastSlash = name.lastIndexOf('/'); - int lastBackSlash = name.lastIndexOf('\\'); - if (lastSlash != -1) - name = name.mid(lastSlash + 1); - else if (lastBackSlash != -1) - name = name.mid(lastBackSlash + 1); - destFileName = FS::PathCombine(lwjglNativesPath, name); - } - } - // Now if destFileName is still empty, go to the next file. - if (!destFileName.isEmpty()) - { - setStatus(tr("Installing new LWJGL - extracting ") + name + "..."); - QFile output(destFileName); - output.open(QIODevice::WriteOnly); - output.write(file.readAll()); - output.close(); - } - file.close(); // do not forget to close! - } - zip.close(); - m_reply.reset(); - QFile doneFile(FS::PathCombine(lwjglTargetPath, "done")); - doneFile.open(QIODevice::WriteOnly); - doneFile.write("done."); - doneFile.close(); -} - -void LegacyUpdate::lwjglFailed(QString reason) -{ - emitFailed(tr("Bad stuff happened while trying to get the lwjgl libs: %1").arg(reason)); -} - -void LegacyUpdate::jarStart() -{ - LegacyInstance *inst = (LegacyInstance *)m_inst; - if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar()) - { - emitSucceeded(); - return; - } - - setStatus(tr("Checking for jar updates...")); - // Make directories - QDir binDir(inst->binRoot()); - if (!binDir.exists() && !binDir.mkpath(".")) - { - emitFailed("Failed to create bin folder."); - return; - } - - // Build a list of URLs that will need to be downloaded. - setStatus(tr("Downloading new minecraft.jar ...")); - - QString version_id = inst->intendedVersionId(); - - auto dljob = new NetJob("Minecraft.jar for version " + version_id); - - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("versions", URLConstants::getJarPath(version_id)); - dljob->addNetAction(Net::Download::makeCached(QUrl(URLConstants::getLegacyJarUrl(version_id)), entry)); - connect(dljob, SIGNAL(succeeded()), SLOT(jarFinished())); - connect(dljob, SIGNAL(failed(QString)), SLOT(jarFailed(QString))); - connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); - legacyDownloadJob.reset(dljob); - legacyDownloadJob->start(); -} - -void LegacyUpdate::jarFinished() -{ - // process the jar - emitSucceeded(); -} - -void LegacyUpdate::jarFailed(QString reason) -{ - // bad, bad - emitFailed(tr("Failed to download the minecraft jar: %1.").arg(reason)); -} diff --git a/api/logic/minecraft/legacy/LegacyUpdate.h b/api/logic/minecraft/legacy/LegacyUpdate.h deleted file mode 100644 index caab978e..00000000 --- a/api/logic/minecraft/legacy/LegacyUpdate.h +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright 2013-2017 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QObject> -#include <QList> -#include <QUrl> - -#include "net/NetJob.h" -#include "tasks/Task.h" -#include "minecraft/VersionFilterData.h" - -class MinecraftVersion; -class BaseInstance; -class QuaZip; -class Mod; - -class LegacyUpdate : public Task -{ - Q_OBJECT -public: - explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0); - virtual void executeTask(); - -private -slots: - void lwjglStart(); - void lwjglFinished(QNetworkReply *); - void lwjglFailed(QString reason); - - void jarStart(); - void jarFinished(); - void jarFailed(QString reason); - - void fmllibsStart(); - void fmllibsFinished(); - void fmllibsFailed(QString reason); - - void extractLwjgl(); - -private: - - std::shared_ptr<QNetworkReply> m_reply; - - // target version, determined during this task - // MinecraftVersion *targetVersion; - QString lwjglURL; - QString lwjglVersion; - - QString lwjglTargetPath; - QString lwjglNativesPath; - -private: - NetJobPtr legacyDownloadJob; - BaseInstance *m_inst = nullptr; - QList<FMLlib> fmlLibsToProcess; -}; diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp new file mode 100644 index 00000000..6cda3e4d --- /dev/null +++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp @@ -0,0 +1,142 @@ +#include "LegacyUpgradeTask.h" +#include "BaseInstanceProvider.h" +#include "settings/INISettingsObject.h" +#include "FileSystem.h" +#include "NullInstance.h" +#include "pathmatcher/RegexpMatcher.h" +#include <QtConcurrentRun> +#include "LegacyInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" +#include "classparser.h" + +LegacyUpgradeTask::LegacyUpgradeTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString & newName) +{ + m_globalSettings = settings; + m_stagingPath = stagingPath; + m_origInstance = origInstance; + m_newName = newName; +} + +void LegacyUpgradeTask::executeTask() +{ + setStatus(tr("Copying instance %1").arg(m_origInstance->name())); + + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); + folderCopy.followSymlinks(true); + + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); + connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &LegacyUpgradeTask::copyFinished); + connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &LegacyUpgradeTask::copyAborted); + m_copyFutureWatcher.setFuture(m_copyFuture); +} + +static QString decideVersion(const QString& currentVersion, const QString& intendedVersion) +{ + if(intendedVersion != currentVersion) + { + if(!intendedVersion.isEmpty()) + { + return intendedVersion; + } + else if(!currentVersion.isEmpty()) + { + return currentVersion; + } + } + else + { + if(!intendedVersion.isEmpty()) + { + return intendedVersion; + } + } + return QString(); +} + +void LegacyUpgradeTask::copyFinished() +{ + auto successful = m_copyFuture.result(); + if(!successful) + { + emitFailed(tr("Instance folder copy failed.")); + return; + } + auto legacyInst = std::dynamic_pointer_cast<LegacyInstance>(m_origInstance); + + auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + // NOTE: this scope ensures the instance is fully saved before we emitSucceeded + { + MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); + inst.setName(m_newName); + inst.init(); + + QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId()); + if(preferredVersionNumber.isNull()) + { + // try to decide version based on the jar(s?) + preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar()); + if(preferredVersionNumber.isNull()) + { + preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar()); + if(preferredVersionNumber.isNull()) + { + emitFailed(tr("Could not decide Minecraft version.")); + return; + } + } + } + auto components = inst.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", preferredVersionNumber, true); + + if(legacyInst->shouldUseCustomBaseJar()) + { + QString jarPath = legacyInst->customBaseJar(); + qDebug() << "Base jar is custom! : " << jarPath; + // FIXME: handle case when the jar is unreadable? + // TODO: check the hash, if it's the same as the upstream jar, do not do this + components->installCustomJar(jarPath); + } + + auto jarMods = legacyInst->getJarMods(); + for(auto & jarMod: jarMods) + { + QString modPath = jarMod.filename().absoluteFilePath(); + qDebug() << "jarMod: " << modPath; + components->installJarMods({modPath}); + } + + // remove all the extra garbage we no longer need + auto removeAll = [&](const QString &root, const QStringList &things) + { + for(auto &thing : things) + { + auto removePath = FS::PathCombine(root, thing); + QFileInfo stat(removePath); + if(stat.isDir()) + { + FS::deletePath(removePath); + } + else + { + QFile::remove(removePath); + } + } + }; + QStringList rootRemovables = {"modlist", "version", "instMods"}; + QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"}; + removeAll(inst.instanceRoot(), rootRemovables); + removeAll(inst.minecraftRoot(), mcRemovables); + } + emitSucceeded(); +} + +void LegacyUpgradeTask::copyAborted() +{ + emitFailed(tr("Instance folder copy has been aborted.")); + return; +} + diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.h b/api/logic/minecraft/legacy/LegacyUpgradeTask.h new file mode 100644 index 00000000..56896385 --- /dev/null +++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.h @@ -0,0 +1,38 @@ +#pragma once + +#include "tasks/Task.h" +#include "multimc_logic_export.h" +#include "net/NetJob.h" +#include <QUrl> +#include <QFuture> +#include <QFutureWatcher> +#include "settings/SettingsObject.h" +#include "BaseVersion.h" +#include "BaseInstance.h" + + +class BaseInstanceProvider; + +class MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public Task +{ + Q_OBJECT +public: + explicit LegacyUpgradeTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString & newName); + +protected: + //! Entry point for tasks. + virtual void executeTask() override; + void copyFinished(); + void copyAborted(); + +private: /* data */ + SettingsObjectPtr m_globalSettings; + InstancePtr m_origInstance; + QString m_stagingPath; + QString m_newName; + QFuture<bool> m_copyFuture; + QFutureWatcher<bool> m_copyFutureWatcher; +}; + + + diff --git a/api/logic/minecraft/legacy/LwjglVersionList.cpp b/api/logic/minecraft/legacy/LwjglVersionList.cpp deleted file mode 100644 index 3d7ad2d4..00000000 --- a/api/logic/minecraft/legacy/LwjglVersionList.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* Copyright 2013-2017 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "LwjglVersionList.h" -#include "Env.h" - -#include <QtNetwork> -#include <QtXml> -#include <QRegExp> - -#include <QDebug> - -#define RSS_URL "https://sourceforge.net/projects/java-game-lib/rss" - -LWJGLVersionList::LWJGLVersionList(QObject *parent) : BaseVersionList(parent) -{ -} - -QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - const PtrLWJGLVersion version = m_vlist.at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - return version->name(); - - case Qt::ToolTipRole: - return version->url(); - - default: - return QVariant(); - } -} - -QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - return tr("Version"); - - case Qt::ToolTipRole: - return tr("LWJGL version name."); - - default: - return QVariant(); - } -} - -int LWJGLVersionList::columnCount(const QModelIndex &parent) const -{ - return 1; -} - -void LWJGLVersionList::loadList() -{ - if(m_loading) - { - return; - } - m_loading = true; - - qDebug() << "Downloading LWJGL RSS..."; - m_rssDLJob.reset(new NetJob("LWJGL RSS")); - m_rssDL = Net::Download::makeByteArray(QUrl(RSS_URL), &m_rssData); - m_rssDLJob->addNetAction(m_rssDL); - connect(m_rssDLJob.get(), &NetJob::failed, this, &LWJGLVersionList::rssFailed); - connect(m_rssDLJob.get(), &NetJob::succeeded, this, &LWJGLVersionList::rssSucceeded); - m_rssDLJob->start(); -} - -inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) -{ - QDomNodeList elementList = parent.elementsByTagName(tagname); - if (elementList.count()) - return elementList.at(0).toElement(); - else - return QDomElement(); -} - -void LWJGLVersionList::rssFailed(const QString& reason) -{ - m_rssDLJob.reset(); - m_loading = false; - qWarning() << "Failed to load LWJGL list. Network error: " + reason; -} - -void LWJGLVersionList::rssSucceeded() -{ - QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip"); - Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", "LWJGL regex is invalid"); - - QDomDocument doc; - - QString xmlErrorMsg; - int errorLine; - - if (!doc.setContent(m_rssData, false, &xmlErrorMsg, &errorLine)) - { - qWarning() << "Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + QString::number(errorLine); - m_rssDLJob.reset(); - m_rssData.clear(); - m_loading = false; - return; - } - m_rssData.clear(); - - QDomNodeList items = doc.elementsByTagName("item"); - - QList<PtrLWJGLVersion> tempList; - - for (int i = 0; i < items.length(); i++) - { - Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list", "XML element isn't an element... wat?"); - - QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link"); - if (linkElement.isNull()) - { - qDebug() << "Link element" << i << "in RSS feed doesn't exist! Skipping."; - continue; - } - - QString link = linkElement.text(); - - // Make sure it's a download link. - if (link.endsWith("/download") && link.contains(lwjglRegex)) - { - QString name = link.mid(lwjglRegex.indexIn(link) + 6); - // Subtract 4 here to remove the .zip file extension. - name = name.left(lwjglRegex.matchedLength() - 10); - - QUrl url(link); - if (!url.isValid()) - { - qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping."; - continue; - } - qDebug() << "Discovered LWGL version" << name << "at" << link; - tempList.append(std::make_shared<LWJGLVersion>(name, link)); - } - } - - beginResetModel(); - m_vlist.swap(tempList); - endResetModel(); - - qDebug() << "Loaded LWJGL list."; - m_rssDLJob.reset(); - m_loading = false; -} diff --git a/api/logic/minecraft/legacy/LwjglVersionList.h b/api/logic/minecraft/legacy/LwjglVersionList.h deleted file mode 100644 index f5312e2c..00000000 --- a/api/logic/minecraft/legacy/LwjglVersionList.h +++ /dev/null @@ -1,116 +0,0 @@ -/* Copyright 2013-2017 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QObject> -#include <QAbstractListModel> -#include <QUrl> -#include <QNetworkReply> -#include <memory> - -#include "BaseVersion.h" -#include "BaseVersionList.h" - -#include "multimc_logic_export.h" -#include <net/NetJob.h> - -class LWJGLVersion; -typedef std::shared_ptr<LWJGLVersion> PtrLWJGLVersion; - -class MULTIMC_LOGIC_EXPORT LWJGLVersion : public BaseVersion -{ -public: - LWJGLVersion(const QString &name, const QString &url) - : m_name(name), m_url(url) - { - } - - virtual QString descriptor() - { - return m_name; - } - - virtual QString name() - { - return m_name; - } - - virtual QString typeString() const - { - return QObject::tr("Upstream"); - } - - QString url() const - { - return m_url; - } - -protected: - QString m_name; - QString m_url; -}; - -class MULTIMC_LOGIC_EXPORT LWJGLVersionList : public BaseVersionList -{ - Q_OBJECT -public: - explicit LWJGLVersionList(QObject *parent = 0); - - bool isLoaded() override - { - return m_vlist.length() > 0; - } - virtual const BaseVersionPtr at(int i) const override - { - return m_vlist[i]; - } - - virtual shared_qobject_ptr<Task> getLoadTask() override - { - return m_rssDLJob; - } - - virtual void sortVersions() override {}; - - virtual void updateListData(QList< BaseVersionPtr > versions) override {}; - - int count() const override - { - return m_vlist.length(); - } - - virtual QVariant data(const QModelIndex &index, int role) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - virtual int rowCount(const QModelIndex &parent) const override - { - return count(); - } - virtual int columnCount(const QModelIndex &parent) const override; - -public slots: - virtual void loadList(); - -private slots: - void rssFailed(const QString & reason); - void rssSucceeded(); - -private: - QList<PtrLWJGLVersion> m_vlist; - Net::Download::Ptr m_rssDL; - NetJobPtr m_rssDLJob; - QByteArray m_rssData; - bool m_loading = false; -}; diff --git a/api/logic/minecraft/onesix/OneSixInstance.cpp b/api/logic/minecraft/onesix/OneSixInstance.cpp deleted file mode 100644 index ecfd0647..00000000 --- a/api/logic/minecraft/onesix/OneSixInstance.cpp +++ /dev/null @@ -1,698 +0,0 @@ -/* Copyright 2013-2017 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 <minecraft/launch/DirectJavaLaunch.h> -#include <minecraft/launch/LauncherPartLaunch.h> -#include <Env.h> - -#include "OneSixInstance.h" -#include "OneSixUpdate.h" -#include "OneSixProfileStrategy.h" - -#include "minecraft/MinecraftProfile.h" -#include "minecraft/launch/ModMinecraftJar.h" -#include "MMCZip.h" - -#include "minecraft/AssetsUtils.h" -#include "minecraft/WorldList.h" -#include <FileSystem.h> - -OneSixInstance::OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : MinecraftInstance(globalSettings, settings, rootDir) -{ - // set explicitly during instance creation - m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); - - // defaults to the version we've been using for years (2.9.1) - m_settings->registerSetting("LWJGLVersion", "2.9.1"); - - // optionals - m_settings->registerSetting("ForgeVersion", ""); - m_settings->registerSetting("LiteloaderVersion", ""); -} - -void OneSixInstance::init() -{ - createProfile(); -} - -void OneSixInstance::createProfile() -{ - m_profile.reset(new MinecraftProfile(new OneSixProfileStrategy(this))); -} - -QSet<QString> OneSixInstance::traits() -{ - auto version = getMinecraftProfile(); - if (!version) - { - return {"version-incomplete"}; - } - else - { - return version->getTraits(); - } -} - -shared_qobject_ptr<Task> OneSixInstance::createUpdateTask() -{ - return shared_qobject_ptr<Task>(new OneSixUpdate(this)); -} - -QString replaceTokensIn(QString text, QMap<QString, QString> with) -{ - QString result; - QRegExp token_regexp("\\$\\{(.+)\\}"); - token_regexp.setMinimal(true); - QStringList list; - int tail = 0; - int head = 0; - while ((head = token_regexp.indexIn(text, head)) != -1) - { - result.append(text.mid(tail, head - tail)); - QString key = token_regexp.cap(1); - auto iter = with.find(key); - if (iter != with.end()) - { - result.append(*iter); - } - head += token_regexp.matchedLength(); - tail = head; - } - result.append(text.mid(tail)); - return result; -} - -QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) const -{ - QString args_pattern = m_profile->getMinecraftArguments(); - for (auto tweaker : m_profile->getTweakers()) - { - args_pattern += " --tweakClass " + tweaker; - } - - QMap<QString, QString> token_mapping; - // yggdrasil! - if(session) - { - token_mapping["auth_username"] = session->username; - token_mapping["auth_session"] = session->session; - token_mapping["auth_access_token"] = session->access_token; - token_mapping["auth_player_name"] = session->player_name; - token_mapping["auth_uuid"] = session->uuid; - token_mapping["user_properties"] = session->serializeUserProperties(); - token_mapping["user_type"] = session->user_type; - } - - // blatant self-promotion. - token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; - if(m_profile->isVanilla()) - { - token_mapping["version_type"] = m_profile->getMinecraftVersionType(); - } - else - { - token_mapping["version_type"] = "custom"; - } - - QString absRootDir = QDir(minecraftRoot()).absolutePath(); - token_mapping["game_directory"] = absRootDir; - QString absAssetsDir = QDir("assets/").absolutePath(); - auto assets = m_profile->getMinecraftAssets(); - token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath(); - - // 1.7.3+ assets tokens - token_mapping["assets_root"] = absAssetsDir; - token_mapping["assets_index_name"] = assets->id; - - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); - for (int i = 0; i < parts.length(); i++) - { - parts[i] = replaceTokensIn(parts[i], token_mapping); - } - return parts; -} - -QString OneSixInstance::getNativePath() const -{ - QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); - return natives_dir.absolutePath(); -} - -QString OneSixInstance::getLocalLibraryPath() const -{ - QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/")); - return libraries_dir.absolutePath(); -} - -QString OneSixInstance::createLaunchScript(AuthSessionPtr session) -{ - QString launchScript; - - if (!m_profile) - return nullptr; - - auto mainClass = getMainClass(); - if (!mainClass.isEmpty()) - { - launchScript += "mainClass " + mainClass + "\n"; - } - auto appletClass = m_profile->getAppletClass(); - if (!appletClass.isEmpty()) - { - launchScript += "appletClass " + appletClass + "\n"; - } - - // generic minecraft params - for (auto param : processMinecraftArgs(session)) - { - launchScript += "param " + param + "\n"; - } - - // window size, title and state, legacy - { - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - windowParams = "max"; - else - windowParams = QString("%1x%2") - .arg(settings()->get("MinecraftWinWidth").toInt()) - .arg(settings()->get("MinecraftWinHeight").toInt()); - launchScript += "windowTitle " + windowTitle() + "\n"; - launchScript += "windowParams " + windowParams + "\n"; - } - - // legacy auth - if(session) - { - launchScript += "userName " + session->player_name + "\n"; - launchScript += "sessionId " + session->session + "\n"; - } - - // libraries and class path. - { - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - for(auto file: jars) - { - launchScript += "cp " + file + "\n"; - } - for(auto file: nativeJars) - { - launchScript += "ext " + file + "\n"; - } - launchScript += "natives " + getNativePath() + "\n"; - } - - for (auto trait : m_profile->getTraits()) - { - launchScript += "traits " + trait + "\n"; - } - launchScript += "launcher onesix\n"; - return launchScript; -} - -QStringList OneSixInstance::verboseDescription(AuthSessionPtr session) -{ - QStringList out; - out << "Main Class:" << " " + getMainClass() << ""; - out << "Native path:" << " " + getNativePath() << ""; - - - auto alltraits = traits(); - if(alltraits.size()) - { - out << "Traits:"; - for (auto trait : alltraits) - { - out << "traits " + trait; - } - out << ""; - } - - // libraries and class path. - { - out << "Libraries:"; - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - auto printLibFile = [&](const QString & path) - { - QFileInfo info(path); - if(info.exists()) - { - out << " " + path; - } - else - { - out << " " + path + " (missing)"; - } - }; - for(auto file: jars) - { - printLibFile(file); - } - out << ""; - out << "Native libraries:"; - for(auto file: nativeJars) - { - printLibFile(file); - } - out << ""; - } - - if(loaderModList()->size()) - { - out << "Mods:"; - for(auto & mod: loaderModList()->allMods()) - { - if(!mod.enabled()) - continue; - if(mod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + mod.filename().completeBaseName(); - } - out << ""; - } - - if(coreModList()->size()) - { - out << "Core Mods:"; - for(auto & coremod: coreModList()->allMods()) - { - if(!coremod.enabled()) - continue; - if(coremod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + coremod.filename().completeBaseName(); - } - out << ""; - } - - auto & jarMods = m_profile->getJarMods(); - if(jarMods.size()) - { - out << "Jar Mods:"; - for(auto & jarmod: jarMods) - { - auto displayname = jarmod->displayName(currentSystem); - auto realname = jarmod->filename(currentSystem); - if(displayname != realname) - { - out << " " + displayname + " (" + realname + ")"; - } - else - { - out << " " + realname; - } - } - out << ""; - } - - auto params = processMinecraftArgs(nullptr); - out << "Params:"; - out << " " + params.join(' '); - out << ""; - - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - { - out << "Window size: max (if available)"; - } - else - { - auto width = settings()->get("MinecraftWinWidth").toInt(); - auto height = settings()->get("MinecraftWinHeight").toInt(); - out << "Window size: " + QString::number(width) + " x " + QString::number(height); - } - out << ""; - return out; -} - - -std::shared_ptr<LaunchStep> OneSixInstance::createMainLaunchStep(LaunchTask * parent, AuthSessionPtr session) -{ - auto method = launchMethod(); - if(method == "LauncherPart") - { - auto step = std::make_shared<LauncherPartLaunch>(parent); - step->setAuthSession(session); - step->setWorkingDirectory(minecraftRoot()); - return step; - } - else if (method == "DirectJava") - { - auto step = std::make_shared<DirectJavaLaunch>(parent); - step->setWorkingDirectory(minecraftRoot()); - step->setAuthSession(session); - return step; - } - return nullptr; -} - - -std::shared_ptr<Task> OneSixInstance::createJarModdingTask() -{ - class JarModTask : public Task - { - public: - explicit JarModTask(std::shared_ptr<OneSixInstance> inst) : Task(nullptr), m_inst(inst) - { - } - virtual void executeTask() - { - auto profile = m_inst->getMinecraftProfile(); - // nuke obsolete stripped jar(s) if needed - QString version_id = profile->getMinecraftVersion(); - if(!FS::ensureFolderPathExists(m_inst->binRoot())) - { - emitFailed(tr("Couldn't create the bin folder for Minecraft.jar")); - } - auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); - QFile finalJar(finalJarPath); - if(finalJar.exists()) - { - if(!finalJar.remove()) - { - emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); - return; - } - } - - // create temporary modded jar, if needed - auto jarMods = m_inst->getJarMods(); - if(jarMods.size()) - { - auto mainJar = profile->getMainJar(); - QStringList jars, temp1, temp2, temp3, temp4; - mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); - auto sourceJarPath = jars[0]; - if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) - { - emitFailed(tr("Failed to create the custom Minecraft jar file.")); - return; - } - } - emitSucceeded(); - } - std::shared_ptr<OneSixInstance> m_inst; - }; - return std::make_shared<JarModTask>(std::dynamic_pointer_cast<OneSixInstance>(shared_from_this())); -} - -std::shared_ptr<ModList> OneSixInstance::loaderModList() const -{ - if (!m_loader_mod_list) - { - m_loader_mod_list.reset(new ModList(loaderModsDir())); - } - m_loader_mod_list->update(); - return m_loader_mod_list; -} - -std::shared_ptr<ModList> OneSixInstance::coreModList() const -{ - if (!m_core_mod_list) - { - m_core_mod_list.reset(new ModList(coreModsDir())); - } - m_core_mod_list->update(); - return m_core_mod_list; -} - -std::shared_ptr<ModList> OneSixInstance::resourcePackList() const -{ - if (!m_resource_pack_list) - { - m_resource_pack_list.reset(new ModList(resourcePacksDir())); - } - m_resource_pack_list->update(); - return m_resource_pack_list; -} - -std::shared_ptr<ModList> OneSixInstance::texturePackList() const -{ - if (!m_texture_pack_list) - { - m_texture_pack_list.reset(new ModList(texturePacksDir())); - } - m_texture_pack_list->update(); - return m_texture_pack_list; -} - -std::shared_ptr<WorldList> OneSixInstance::worldList() const -{ - if (!m_world_list) - { - m_world_list.reset(new WorldList(worldDir())); - } - return m_world_list; -} - -bool OneSixInstance::setIntendedVersionId(QString version) -{ - return setComponentVersion("net.minecraft", version); -} - -QString OneSixInstance::intendedVersionId() const -{ - return getComponentVersion("net.minecraft"); -} - -bool OneSixInstance::setComponentVersion(const QString& uid, const QString& version) -{ - if(uid == "net.minecraft") - { - settings()->set("IntendedVersion", version); - } - else if (uid == "org.lwjgl") - { - settings()->set("LWJGLVersion", version); - } - else if (uid == "net.minecraftforge") - { - settings()->set("ForgeVersion", version); - } - else if (uid == "com.mumfrey.liteloader") - { - settings()->set("LiteloaderVersion", version); - } - if(getMinecraftProfile()) - { - clearProfile(); - } - emit propertiesChanged(this); - return true; -} - -QString OneSixInstance::getComponentVersion(const QString& uid) const -{ - if(uid == "net.minecraft") - { - return settings()->get("IntendedVersion").toString(); - } - else if(uid == "org.lwjgl") - { - return settings()->get("LWJGLVersion").toString(); - } - else if(uid == "net.minecraftforge") - { - return settings()->get("ForgeVersion").toString(); - } - else if(uid == "com.mumfrey.liteloader") - { - return settings()->get("LiteloaderVersion").toString(); - } - return QString(); -} - -QList< Mod > OneSixInstance::getJarMods() const -{ - QList<Mod> mods; - for (auto jarmod : m_profile->getJarMods()) - { - QStringList jar, temp1, temp2, temp3; - jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); - // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); - mods.push_back(Mod(QFileInfo(jar[0]))); - } - return mods; -} - -void OneSixInstance::setShouldUpdate(bool) -{ -} - -bool OneSixInstance::shouldUpdate() const -{ - return true; -} - -QString OneSixInstance::currentVersionId() const -{ - return intendedVersionId(); -} - -void OneSixInstance::reloadProfile() -{ - m_profile->reload(); - setVersionBroken(m_profile->getProblemSeverity() == ProblemSeverity::Error); - emit versionReloaded(); -} - -void OneSixInstance::clearProfile() -{ - m_profile->clear(); - emit versionReloaded(); -} - -std::shared_ptr<MinecraftProfile> OneSixInstance::getMinecraftProfile() const -{ - return m_profile; -} - -QDir OneSixInstance::librariesPath() const -{ - return QDir::current().absoluteFilePath("libraries"); -} - -QDir OneSixInstance::jarmodsPath() const -{ - return QDir(jarModsDir()); -} - -QDir OneSixInstance::versionsPath() const -{ - return QDir::current().absoluteFilePath("versions"); -} - -bool OneSixInstance::providesVersionFile() const -{ - return false; -} - -bool OneSixInstance::reload() -{ - if (BaseInstance::reload()) - { - try - { - reloadProfile(); - return true; - } - catch (...) - { - return false; - } - } - return false; -} - -QString OneSixInstance::loaderModsDir() const -{ - return FS::PathCombine(minecraftRoot(), "mods"); -} - -QString OneSixInstance::coreModsDir() const -{ - return FS::PathCombine(minecraftRoot(), "coremods"); -} - -QString OneSixInstance::resourcePacksDir() const -{ - return FS::PathCombine(minecraftRoot(), "resourcepacks"); -} - -QString OneSixInstance::texturePacksDir() const -{ - return FS::PathCombine(minecraftRoot(), "texturepacks"); -} - -QString OneSixInstance::instanceConfigFolder() const -{ - return FS::PathCombine(minecraftRoot(), "config"); -} - -QString OneSixInstance::jarModsDir() const -{ - return FS::PathCombine(instanceRoot(), "jarmods"); -} - -QString OneSixInstance::libDir() const -{ - return FS::PathCombine(minecraftRoot(), "lib"); -} - -QString OneSixInstance::worldDir() const -{ - return FS::PathCombine(minecraftRoot(), "saves"); -} - -QStringList OneSixInstance::extraArguments() const -{ - auto list = BaseInstance::extraArguments(); - auto version = getMinecraftProfile(); - if (!version) - return list; - auto jarMods = getJarMods(); - if (!jarMods.isEmpty()) - { - list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", - "-Dfml.ignorePatchDiscrepancies=true"}); - } - return list; -} - -std::shared_ptr<OneSixInstance> OneSixInstance::getSharedPtr() -{ - return std::dynamic_pointer_cast<OneSixInstance>(BaseInstance::getSharedPtr()); -} - -QString OneSixInstance::typeName() const -{ - return tr("OneSix"); -} - -QStringList OneSixInstance::validLaunchMethods() -{ - return {"LauncherPart", "DirectJava"}; -} - -QStringList OneSixInstance::getClassPath() const -{ - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return jars; -} - -QString OneSixInstance::getMainClass() const -{ - return m_profile->getMainClass(); -} - -QStringList OneSixInstance::getNativeJars() const -{ - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return nativeJars; -} diff --git a/api/logic/minecraft/onesix/OneSixInstance.h b/api/logic/minecraft/onesix/OneSixInstance.h deleted file mode 100644 index bf12160e..00000000 --- a/api/logic/minecraft/onesix/OneSixInstance.h +++ /dev/null @@ -1,127 +0,0 @@ -/* Copyright 2013-2017 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 "minecraft/MinecraftInstance.h" - -#include "minecraft/MinecraftProfile.h" -#include "minecraft/ModList.h" - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT OneSixInstance : public MinecraftInstance -{ - Q_OBJECT -public: - explicit OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual ~OneSixInstance(){}; - - virtual void init() override; - - ////// Mod Lists ////// - std::shared_ptr<ModList> loaderModList() const; - std::shared_ptr<ModList> coreModList() const; - std::shared_ptr<ModList> resourcePackList() const override; - std::shared_ptr<ModList> texturePackList() const override; - std::shared_ptr<WorldList> worldList() const override; - virtual QList<Mod> getJarMods() const override; - virtual void createProfile(); - - virtual QSet<QString> traits() override; - - ////// Directories and files ////// - QString jarModsDir() const; - QString resourcePacksDir() const; - QString texturePacksDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString libDir() const; - QString worldDir() const; - virtual QString instanceConfigFolder() const override; - - virtual shared_qobject_ptr<Task> createUpdateTask() override; - virtual std::shared_ptr<Task> createJarModdingTask() override; - virtual QString createLaunchScript(AuthSessionPtr session) override; - QStringList verboseDescription(AuthSessionPtr session) override; - - virtual QString intendedVersionId() const override; - virtual bool setIntendedVersionId(QString version) override; - virtual QString currentVersionId() const override; - - QString getComponentVersion(const QString &uid) const; - bool setComponentVersion(const QString &uid, const QString &version); - - virtual bool shouldUpdate() const override; - virtual void setShouldUpdate(bool val) override; - - /** - * reload the profile, including version json files. - * - * throws various exceptions :3 - */ - void reloadProfile(); - - /// clears all version information in preparation for an update - void clearProfile(); - - /// get the current full version info - std::shared_ptr<MinecraftProfile> getMinecraftProfile() const; - - virtual QDir jarmodsPath() const; - virtual QDir librariesPath() const; - virtual QDir versionsPath() const; - virtual bool providesVersionFile() const; - - bool reload() override; - - virtual QStringList extraArguments() const override; - - std::shared_ptr<OneSixInstance> getSharedPtr(); - - virtual QString typeName() const override; - - bool canExport() const override - { - return true; - } - - QStringList getClassPath() const override; - QString getMainClass() const override; - - QStringList getNativeJars() const override; - QString getNativePath() const override; - - QString getLocalLibraryPath() const override; - - QStringList processMinecraftArgs(AuthSessionPtr account) const override; - -protected: - std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) override; - QStringList validLaunchMethods() override; - -signals: - void versionReloaded(); - -protected: - std::shared_ptr<MinecraftProfile> m_profile; - mutable std::shared_ptr<ModList> m_loader_mod_list; - mutable std::shared_ptr<ModList> m_core_mod_list; - mutable std::shared_ptr<ModList> m_resource_pack_list; - mutable std::shared_ptr<ModList> m_texture_pack_list; - mutable std::shared_ptr<WorldList> m_world_list; -}; - -Q_DECLARE_METATYPE(std::shared_ptr<OneSixInstance>) diff --git a/api/logic/minecraft/onesix/OneSixProfileStrategy.cpp b/api/logic/minecraft/onesix/OneSixProfileStrategy.cpp deleted file mode 100644 index ef2a7294..00000000 --- a/api/logic/minecraft/onesix/OneSixProfileStrategy.cpp +++ /dev/null @@ -1,407 +0,0 @@ -#include "OneSixProfileStrategy.h" -#include "OneSixInstance.h" -#include "OneSixVersionFormat.h" - -#include "Env.h" -#include <FileSystem.h> - -#include <QDir> -#include <QUuid> -#include <QJsonDocument> -#include <QJsonArray> -#include <QSaveFile> -#include <QResource> -#include <meta/Index.h> -#include <meta/Version.h> - -#include <tuple> - -OneSixProfileStrategy::OneSixProfileStrategy(OneSixInstance* instance) -{ - m_instance = instance; -} - -void OneSixProfileStrategy::upgradeDeprecatedFiles() -{ - auto versionJsonPath = FS::PathCombine(m_instance->instanceRoot(), "version.json"); - auto customJsonPath = FS::PathCombine(m_instance->instanceRoot(), "custom.json"); - auto mcJson = FS::PathCombine(m_instance->instanceRoot(), "patches" , "net.minecraft.json"); - - QString sourceFile; - QString renameFile; - - // convert old crap. - if(QFile::exists(customJsonPath)) - { - sourceFile = customJsonPath; - renameFile = versionJsonPath; - } - else if(QFile::exists(versionJsonPath)) - { - sourceFile = versionJsonPath; - } - if(!sourceFile.isEmpty() && !QFile::exists(mcJson)) - { - if(!FS::ensureFilePathExists(mcJson)) - { - qWarning() << "Couldn't create patches folder for" << m_instance->name(); - return; - } - if(!renameFile.isEmpty() && QFile::exists(renameFile)) - { - if(!QFile::rename(renameFile, renameFile + ".old")) - { - qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << m_instance->name(); - return; - } - } - auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false); - ProfileUtils::removeLwjglFromPatch(file); - file->uid = "net.minecraft"; - file->version = file->minecraftVersion; - file->name = "Minecraft"; - auto data = OneSixVersionFormat::versionFileToJson(file, false).toJson(); - QSaveFile newPatchFile(mcJson); - if(!newPatchFile.open(QIODevice::WriteOnly)) - { - newPatchFile.cancelWriting(); - qWarning() << "Couldn't open main patch for writing in" << m_instance->name(); - return; - } - newPatchFile.write(data); - if(!newPatchFile.commit()) - { - qWarning() << "Couldn't save main patch in" << m_instance->name(); - return; - } - if(!QFile::rename(sourceFile, sourceFile + ".old")) - { - qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << m_instance->name(); - return; - } - } -} - -void OneSixProfileStrategy::loadDefaultBuiltinPatches() -{ - auto addBuiltinPatch = [&](const QString &uid, const QString intendedVersion, int order) - { - auto jsonFilePath = FS::PathCombine(m_instance->instanceRoot(), "patches" , uid + ".json"); - // load up the base minecraft patch - ProfilePatchPtr profilePatch; - if(QFile::exists(jsonFilePath)) - { - auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false); - if(file->version.isEmpty()) - { - file->version = intendedVersion; - } - profilePatch = std::make_shared<ProfilePatch>(file, jsonFilePath); - profilePatch->setVanilla(false); - profilePatch->setRevertible(true); - } - else - { - auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion); - profilePatch = std::make_shared<ProfilePatch>(metaVersion); - profilePatch->setVanilla(true); - } - profilePatch->setOrder(order); - profile->appendPatch(profilePatch); - }; - addBuiltinPatch("net.minecraft", m_instance->getComponentVersion("net.minecraft"), -2); - addBuiltinPatch("org.lwjgl", m_instance->getComponentVersion("org.lwjgl"), -1); -} - -void OneSixProfileStrategy::loadUserPatches() -{ - // first, collect all patches (that are not builtins of OneSix) and load them - QMap<QString, ProfilePatchPtr> loadedPatches; - QDir patchesDir(FS::PathCombine(m_instance->instanceRoot(),"patches")); - for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - // parse the file - qDebug() << "Reading" << info.fileName(); - auto file = ProfileUtils::parseJsonFile(info, true); - // ignore builtins - if (file->uid == "net.minecraft") - continue; - if (file->uid == "org.lwjgl") - continue; - auto patch = std::make_shared<ProfilePatch>(file, info.filePath()); - patch->setRemovable(true); - patch->setMovable(true); - if(ENV.metadataIndex()->hasUid(file->uid)) - { - // FIXME: requesting a uid/list creates it in the index... this allows reverting to possibly invalid versions... - patch->setRevertible(true); - } - loadedPatches[file->uid] = patch; - } - // these are 'special'... if not already loaded from instance files, grab them from the metadata repo. - auto loadSpecial = [&](const QString & uid, int order) - { - auto patchVersion = m_instance->getComponentVersion(uid); - if(!patchVersion.isEmpty() && !loadedPatches.contains(uid)) - { - auto patch = std::make_shared<ProfilePatch>(ENV.metadataIndex()->get(uid, patchVersion)); - patch->setOrder(order); - patch->setVanilla(true); - patch->setRemovable(true); - patch->setMovable(true); - loadedPatches[uid] = patch; - } - }; - loadSpecial("net.minecraftforge", 5); - loadSpecial("com.mumfrey.liteloader", 10); - - // now add all the patches by user sort order - ProfileUtils::PatchOrder userOrder; - ProfileUtils::readOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), userOrder); - for (auto uid : userOrder) - { - // ignore builtins - if (uid == "net.minecraft") - continue; - if (uid == "org.lwjgl") - continue; - // ordering has a patch that is gone? - if(!loadedPatches.contains(uid)) - { - continue; - } - profile->appendPatch(loadedPatches.take(uid)); - } - - // is there anything left to sort? - if(loadedPatches.isEmpty()) - { - // TODO: save the order here? - return; - } - - // inserting into multimap by order number as key sorts the patches and detects duplicates - QMultiMap<int, ProfilePatchPtr> files; - auto iter = loadedPatches.begin(); - while(iter != loadedPatches.end()) - { - files.insert((*iter)->getOrder(), *iter); - iter++; - } - - // then just extract the patches and put them in the list - for (auto order : files.keys()) - { - const auto &values = files.values(order); - for(auto &value: values) - { - // TODO: put back the insertion of problem messages here, so the user knows about the id duplication - profile->appendPatch(value); - } - } - // TODO: save the order here? -} - - -void OneSixProfileStrategy::load() -{ - profile->clearPatches(); - - upgradeDeprecatedFiles(); - loadDefaultBuiltinPatches(); - loadUserPatches(); -} - -bool OneSixProfileStrategy::saveOrder(ProfileUtils::PatchOrder order) -{ - return ProfileUtils::writeOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), order); -} - -bool OneSixProfileStrategy::resetOrder() -{ - return QDir(m_instance->instanceRoot()).remove("order.json"); -} - -bool OneSixProfileStrategy::removePatch(ProfilePatchPtr patch) -{ - bool ok = true; - // first, remove the patch file. this ensures it's not used anymore - auto fileName = patch->getFilename(); - if(fileName.size()) - { - QFile patchFile(fileName); - if(patchFile.exists() && !patchFile.remove()) - { - qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString(); - return false; - } - } - if(!m_instance->getComponentVersion(patch->getID()).isEmpty()) - { - m_instance->setComponentVersion(patch->getID(), QString()); - } - - // FIXME: we need a generic way of removing local resources, not just jar mods... - auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool - { - if (!jarMod->isLocal()) - { - return true; - } - QStringList jar, temp1, temp2, temp3; - jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, m_instance->jarmodsPath().absolutePath()); - QFileInfo finfo (jar[0]); - if(finfo.exists()) - { - QFile jarModFile(jar[0]); - if(!jarModFile.remove()) - { - qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString(); - return false; - } - return true; - } - return true; - }; - - auto &jarMods = patch->getVersionFile()->jarMods; - for(auto &jarmod: jarMods) - { - ok &= preRemoveJarMod(jarmod); - } - return ok; -} - -bool OneSixProfileStrategy::customizePatch(ProfilePatchPtr patch) -{ - if(patch->isCustom()) - { - return false; - } - - auto filename = FS::PathCombine(m_instance->instanceRoot(), "patches" , patch->getID() + ".json"); - if(!FS::ensureFilePathExists(filename)) - { - return false; - } - // FIXME: get rid of this try-catch. - try - { - QSaveFile jsonFile(filename); - if(!jsonFile.open(QIODevice::WriteOnly)) - { - return false; - } - auto vfile = patch->getVersionFile(); - if(!vfile) - { - return false; - } - auto document = OneSixVersionFormat::versionFileToJson(vfile, true); - jsonFile.write(document.toJson()); - if(!jsonFile.commit()) - { - return false; - } - load(); - } - catch (Exception &error) - { - qWarning() << "Version could not be loaded:" << error.cause(); - } - return true; -} - -bool OneSixProfileStrategy::revertPatch(ProfilePatchPtr patch) -{ - if(!patch->isCustom()) - { - // already not custom - return true; - } - auto filename = patch->getFilename(); - if(!QFile::exists(filename)) - { - // already gone / not custom - return true; - } - // just kill the file and reload - bool result = QFile::remove(filename); - // FIXME: get rid of this try-catch. - try - { - load(); - } - catch (Exception &error) - { - qWarning() << "Version could not be loaded:" << error.cause(); - } - return result; -} - -bool OneSixProfileStrategy::installJarMods(QStringList filepaths) -{ - QString patchDir = FS::PathCombine(m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - - if (!FS::ensureFolderPathExists(m_instance->jarModsDir())) - { - return false; - } - - for(auto filepath:filepaths) - { - 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 = FS::PathCombine(m_instance->jarModsDir(), target_filename); - - QFileInfo targetInfo(finalPath); - if(targetInfo.exists()) - { - return false; - } - - if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) - { - return false; - } - - auto f = std::make_shared<VersionFile>(); - auto jarMod = std::make_shared<Library>(); - jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); - jarMod->setFilename(target_filename); - jarMod->setDisplayName(sourceInfo.completeBaseName()); - jarMod->setHint("local"); - f->jarMods.append(jarMod); - f->name = target_name; - f->uid = target_id; - f->order = profile->getFreeOrderNumber(); - QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f, true).toJson()); - file.close(); - - auto patch = std::make_shared<ProfilePatch>(f, patchFileName); - patch->setMovable(true); - patch->setRemovable(true); - profile->appendPatch(patch); - } - profile->saveCurrentOrder(); - profile->reapplyPatches(); - return true; -} - diff --git a/api/logic/minecraft/onesix/OneSixProfileStrategy.h b/api/logic/minecraft/onesix/OneSixProfileStrategy.h deleted file mode 100644 index 96c1ba7b..00000000 --- a/api/logic/minecraft/onesix/OneSixProfileStrategy.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "minecraft/ProfileStrategy.h" - -class OneSixInstance; - -class OneSixProfileStrategy : public ProfileStrategy -{ -public: - OneSixProfileStrategy(OneSixInstance * instance); - virtual ~OneSixProfileStrategy() {}; - virtual void load() override; - virtual bool resetOrder() override; - virtual bool saveOrder(ProfileUtils::PatchOrder order) override; - virtual bool installJarMods(QStringList filepaths) override; - virtual bool removePatch(ProfilePatchPtr patch) override; - virtual bool customizePatch(ProfilePatchPtr patch) override; - virtual bool revertPatch(ProfilePatchPtr patch) override; - -protected: - virtual void loadDefaultBuiltinPatches(); - virtual void loadUserPatches(); - void upgradeDeprecatedFiles(); - -protected: - OneSixInstance *m_instance; -}; diff --git a/api/logic/minecraft/onesix/update/AssetUpdateTask.cpp b/api/logic/minecraft/update/AssetUpdateTask.cpp index 21600ff0..2ad2b5b2 100644 --- a/api/logic/minecraft/onesix/update/AssetUpdateTask.cpp +++ b/api/logic/minecraft/update/AssetUpdateTask.cpp @@ -1,17 +1,19 @@ #include "Env.h" #include "AssetUpdateTask.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" #include "net/ChecksumValidator.h" #include "minecraft/AssetsUtils.h" -AssetUpdateTask::AssetUpdateTask(OneSixInstance * inst) +AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst) { m_inst = inst; } void AssetUpdateTask::executeTask() { setStatus(tr("Updating assets index...")); - auto profile = m_inst->getMinecraftProfile(); + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); auto assets = profile->getMinecraftAssets(); QUrl indexUrl = assets->url; QString localPath = assets->id + ".json"; @@ -47,7 +49,8 @@ void AssetUpdateTask::assetIndexFinished() AssetsIndex index; qDebug() << m_inst->name() << ": Finished asset index download"; - auto profile = m_inst->getMinecraftProfile(); + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); auto assets = profile->getMinecraftAssets(); QString asset_fname = "assets/indexes/" + assets->id + ".json"; diff --git a/api/logic/minecraft/onesix/update/AssetUpdateTask.h b/api/logic/minecraft/update/AssetUpdateTask.h index dff72571..c666faa6 100644 --- a/api/logic/minecraft/onesix/update/AssetUpdateTask.h +++ b/api/logic/minecraft/update/AssetUpdateTask.h @@ -1,12 +1,13 @@ #pragma once #include "tasks/Task.h" #include "net/NetJob.h" -class OneSixInstance; +class MinecraftInstance; class AssetUpdateTask : public Task { + Q_OBJECT public: - AssetUpdateTask(OneSixInstance * inst); + AssetUpdateTask(MinecraftInstance * inst); void executeTask() override; bool canAbort() const override; @@ -20,6 +21,6 @@ public slots: bool abort() override; private: - OneSixInstance *m_inst; + MinecraftInstance *m_inst; NetJobPtr downloadJob; }; diff --git a/api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp index 1cbee95e..1bd339e4 100644 --- a/api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp +++ b/api/logic/minecraft/update/FMLLibrariesTask.cpp @@ -2,26 +2,27 @@ #include <FileSystem.h> #include <minecraft/VersionFilterData.h> #include "FMLLibrariesTask.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" - -FMLLibrariesTask::FMLLibrariesTask(OneSixInstance * inst) +FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst) { m_inst = inst; } void FMLLibrariesTask::executeTask() { // Get the mod list - OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); - bool forge_present = false; + MinecraftInstance *inst = (MinecraftInstance *)m_inst; + auto components = inst->getComponentList(); + auto profile = components->getProfile(); if (!profile->hasTrait("legacyFML")) { emitSucceeded(); + return; } - QString version = inst->intendedVersionId(); + QString version = components->getComponentVersion("net.minecraft"); auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; if (!fmlLibsMapping.contains(version)) { @@ -33,9 +34,7 @@ void FMLLibrariesTask::executeTask() // determine if we need some libs for FML or forge setStatus(tr("Checking for FML libraries...")); - forge_present = (profile->versionPatch("net.minecraftforge") != nullptr); - // we don't... - if (!forge_present) + if(!components->getComponent("net.minecraftforge")) { emitSucceeded(); return; @@ -87,7 +86,7 @@ void FMLLibrariesTask::fmllibsFinished() if (!fmlLibsToProcess.isEmpty()) { setStatus(tr("Copying FML libraries into the instance...")); - OneSixInstance *inst = (OneSixInstance *)m_inst; + MinecraftInstance *inst = (MinecraftInstance *)m_inst; auto metacache = ENV.metacache(); int index = 0; for (auto &lib : fmlLibsToProcess) diff --git a/api/logic/minecraft/onesix/update/FMLLibrariesTask.h b/api/logic/minecraft/update/FMLLibrariesTask.h index d1c250e4..10f48f5b 100644 --- a/api/logic/minecraft/onesix/update/FMLLibrariesTask.h +++ b/api/logic/minecraft/update/FMLLibrariesTask.h @@ -1,12 +1,13 @@ #pragma once #include "tasks/Task.h" #include "net/NetJob.h" -class OneSixInstance; +class MinecraftInstance; class FMLLibrariesTask : public Task { + Q_OBJECT public: - FMLLibrariesTask(OneSixInstance * inst); + FMLLibrariesTask(MinecraftInstance * inst); void executeTask() override; @@ -20,7 +21,7 @@ public slots: bool abort() override; private: - OneSixInstance *m_inst; + MinecraftInstance *m_inst; NetJobPtr downloadJob; QList<FMLlib> fmlLibsToProcess; }; diff --git a/api/logic/minecraft/onesix/update/FoldersTask.cpp b/api/logic/minecraft/update/FoldersTask.cpp index 239a2675..34e2292a 100644 --- a/api/logic/minecraft/onesix/update/FoldersTask.cpp +++ b/api/logic/minecraft/update/FoldersTask.cpp @@ -1,8 +1,9 @@ #include "FoldersTask.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" #include <QDir> -FoldersTask::FoldersTask(OneSixInstance * inst) +FoldersTask::FoldersTask(MinecraftInstance * inst) + :Task() { m_inst = inst; } diff --git a/api/logic/minecraft/onesix/update/FoldersTask.h b/api/logic/minecraft/update/FoldersTask.h index 552d3098..6e669b1e 100644 --- a/api/logic/minecraft/onesix/update/FoldersTask.h +++ b/api/logic/minecraft/update/FoldersTask.h @@ -2,13 +2,14 @@ #include "tasks/Task.h" -class OneSixInstance; +class MinecraftInstance; class FoldersTask : public Task { + Q_OBJECT public: - FoldersTask(OneSixInstance * inst); + FoldersTask(MinecraftInstance * inst); void executeTask() override; private: - OneSixInstance *m_inst; + MinecraftInstance *m_inst; }; diff --git a/api/logic/minecraft/onesix/update/LibrariesTask.cpp b/api/logic/minecraft/update/LibrariesTask.cpp index 2cd41ded..0bec61c1 100644 --- a/api/logic/minecraft/onesix/update/LibrariesTask.cpp +++ b/api/logic/minecraft/update/LibrariesTask.cpp @@ -1,8 +1,9 @@ #include "Env.h" #include "LibrariesTask.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" -LibrariesTask::LibrariesTask(OneSixInstance * inst) +LibrariesTask::LibrariesTask(MinecraftInstance * inst) { m_inst = inst; } @@ -11,16 +12,11 @@ void LibrariesTask::executeTask() { setStatus(tr("Getting the library files from Mojang...")); qDebug() << m_inst->name() << ": downloading libraries"; - OneSixInstance *inst = (OneSixInstance *)m_inst; - inst->reloadProfile(); - if(inst->hasVersionBroken()) - { - emitFailed(tr("Failed to load the version description files - check the instance for errors.")); - return; - } + MinecraftInstance *inst = (MinecraftInstance *)m_inst; // Build a list of URLs that will need to be downloaded. - std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); + auto components = inst->getComponentList(); + auto profile = components->getProfile(); auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); downloadJob.reset(job); diff --git a/api/logic/minecraft/onesix/update/LibrariesTask.h b/api/logic/minecraft/update/LibrariesTask.h index 80cf0d2a..d06a5037 100644 --- a/api/logic/minecraft/onesix/update/LibrariesTask.h +++ b/api/logic/minecraft/update/LibrariesTask.h @@ -1,12 +1,13 @@ #pragma once #include "tasks/Task.h" #include "net/NetJob.h" -class OneSixInstance; +class MinecraftInstance; class LibrariesTask : public Task { + Q_OBJECT public: - LibrariesTask(OneSixInstance * inst); + LibrariesTask(MinecraftInstance * inst); void executeTask() override; @@ -19,6 +20,6 @@ public slots: bool abort() override; private: - OneSixInstance *m_inst; + MinecraftInstance *m_inst; NetJobPtr downloadJob; }; diff --git a/api/logic/modplatform/FtbPackDownloader.cpp b/api/logic/modplatform/FtbPackDownloader.cpp new file mode 100644 index 00000000..a3951bfd --- /dev/null +++ b/api/logic/modplatform/FtbPackDownloader.cpp @@ -0,0 +1,106 @@ +#include "FtbPackDownloader.h" +#include "PackHelpers.h" +#include "FtbPackFetchTask.h" +#include "Env.h" + +FtbPackDownloader::FtbPackDownloader() { + done = false; + fetching = false; +} + +FtbPackDownloader::~FtbPackDownloader(){ + delete netJobContainer.get(); + netJobContainer.reset(nullptr); +} + +bool FtbPackDownloader::isValidPackSelected(){ + FtbModpack dummy; + dummy.name = "__INVALID__"; + + FtbModpack other = fetchedPacks.value(selected.name, dummy); + if(other.name == "__INVALID__") { + return false; + } + + return other.oldVersions.contains(selectedVersion); +} + +QString FtbPackDownloader::getSuggestedInstanceName() { + return selected.name; +} + +FtbModpackList FtbPackDownloader::getModpacks() { + return static_cast<FtbModpackList>(fetchedPacks.values()); +} + +void FtbPackDownloader::fetchModpacks(bool force = false){ + if(fetching || (!force && done)) { + qDebug() << "Skipping modpack refetch because done or already fetching [done =>" << done << "| fetching =>" << fetching << "]"; + return; + } + + fetching = true; + + fetchTask = new FtbPackFetchTask(); + connect(fetchTask, &FtbPackFetchTask::finished, this, &FtbPackDownloader::fetchSuccess); + connect(fetchTask, &FtbPackFetchTask::failed, this, &FtbPackDownloader::fetchFailed); + fetchTask->fetch(); +} + + +void FtbPackDownloader::fetchSuccess(FtbModpackList modpacks) { + for(int i = 0; i < modpacks.size(); i++) { + fetchedPacks.insert(modpacks.at(i).name, modpacks.at(i)); + } + + fetching = false; + done = true; + emit ready(); + fetchTask->deleteLater(); +} + +void FtbPackDownloader::fetchFailed(QString reason) { + qWarning() << "Failed to fetch FtbData" << reason; + fetching = false; + emit packFetchFailed(); + fetchTask->deleteLater(); +} + +void FtbPackDownloader::selectPack(FtbModpack modpack, QString version) { + selected = modpack; + selectedVersion = version; +} + +FtbModpack FtbPackDownloader::getSelectedPack() { + return selected; +} + +void FtbPackDownloader::downloadSelected(MetaEntryPtr cache) { + NetJob *job = new NetJob("Downlad FTB Pack"); + + cache->setStale(true); + QString url = QString("http://ftb.cursecdn.com/FTB2/modpacks/%1/%2/%3").arg(selected.dir, selectedVersion.replace(".", "_"), selected.file); + job->addNetAction(Net::Download::makeCached(url, cache)); + downloadPath = cache->getFullPath(); + + netJobContainer.reset(job); + + connect(job, &NetJob::succeeded, this, &FtbPackDownloader::_downloadSucceeded); + connect(job, &NetJob::failed, this, &FtbPackDownloader::_downloadFailed); + connect(job, &NetJob::progress, this, &FtbPackDownloader::_downloadProgress); + job->start(); +} + +void FtbPackDownloader::_downloadSucceeded() { + netJobContainer.reset(); + emit downloadSucceded(downloadPath); +} + +void FtbPackDownloader::_downloadProgress(qint64 current, qint64 total) { + emit downloadProgress(current, total); +} + +void FtbPackDownloader::_downloadFailed(QString reason) { + netJobContainer.reset(); + emit downloadFailed(reason); +} diff --git a/api/logic/modplatform/FtbPackDownloader.h b/api/logic/modplatform/FtbPackDownloader.h new file mode 100644 index 00000000..45490afc --- /dev/null +++ b/api/logic/modplatform/FtbPackDownloader.h @@ -0,0 +1,63 @@ +#include <QString> +#include <QUrl> +#include <QList> +#include <QObject> +#include "FtbPackFetchTask.h" +#include "tasks/Task.h" +#include "net/NetJob.h" + +#include "PackHelpers.h" +#include "Env.h" + +#pragma once + +class FtbPackDownloader; +class MULTIMC_LOGIC_EXPORT FtbPackDownloader : public QObject { + + Q_OBJECT + +private: + QMap<QString, FtbModpack> fetchedPacks; + bool fetching; + bool done; + + FtbModpack selected; + QString selectedVersion; + QString downloadPath; + + FtbPackFetchTask *fetchTask = 0; + NetJobPtr netJobContainer; + + void _downloadSucceeded(); + void _downloadFailed(QString reason); + void _downloadProgress(qint64 current, qint64 total); + +private slots: + void fetchSuccess(FtbModpackList modlist); + void fetchFailed(QString reason); + +public: + FtbPackDownloader(); + ~FtbPackDownloader(); + + bool isValidPackSelected(); + void selectPack(FtbModpack modpack, QString version); + + FtbModpack getSelectedPack(); + + void fetchModpacks(bool force); + void downloadSelected(MetaEntryPtr cache); + + QString getSuggestedInstanceName(); + + FtbModpackList getModpacks(); + +signals: + void ready(); + void packFetchFailed(); + + void downloadSucceded(QString archivePath); + void downloadFailed(QString reason); + void downloadProgress(qint64 current, qint64 total); + +}; diff --git a/api/logic/modplatform/FtbPackFetchTask.cpp b/api/logic/modplatform/FtbPackFetchTask.cpp new file mode 100644 index 00000000..6f578e04 --- /dev/null +++ b/api/logic/modplatform/FtbPackFetchTask.cpp @@ -0,0 +1,75 @@ +#include "FtbPackFetchTask.h" +#include <QDomDocument> + +FtbPackFetchTask::FtbPackFetchTask() { + +} + +FtbPackFetchTask::~FtbPackFetchTask() { + +} + +void FtbPackFetchTask::fetch() { + NetJob *netJob = new NetJob("FtbModpackFetch"); + + QUrl url = QUrl("https://ftb.cursecdn.com/FTB2/static/modpacks.xml"); + qDebug() << "Downloading version info from " << url.toString(); + + netJob->addNetAction(downloadPtr = Net::Download::makeByteArray(url, &modpacksXmlFileData)); + + QObject::connect(netJob, &NetJob::succeeded, this, &FtbPackFetchTask::fileDownloadFinished); + QObject::connect(netJob, &NetJob::failed, this, &FtbPackFetchTask::fileDownloadFailed); + + jobPtr.reset(netJob); + netJob->start(); +} + +void FtbPackFetchTask::fileDownloadFinished(){ + + jobPtr.reset(); + + QDomDocument doc; + + QString errorMsg = "Unknown error."; + int errorLine = -1; + int errorCol = -1; + + if(!doc.setContent(modpacksXmlFileData, false, &errorMsg, &errorLine, &errorCol)){ + auto fullErrMsg = QString("Failed to fetch modpack data: %s %d:%d!").arg(errorMsg, errorLine, errorCol); + qWarning() << fullErrMsg; + emit failed(fullErrMsg); + modpacksXmlFileData.clear(); + return; + } + + modpacksXmlFileData.clear(); + + FtbModpackList modpackList; + + QDomNodeList nodes = doc.elementsByTagName("modpack"); + for(int i = 0; i < nodes.length(); i++) { + QDomElement element = nodes.at(i).toElement(); + + FtbModpack modpack; + modpack.name = element.attribute("name"); + modpack.currentVersion = element.attribute("version"); + modpack.mcVersion = element.attribute("mcVersion"); + modpack.description = element.attribute("description"); + modpack.mods = element.attribute("mods"); + modpack.image = element.attribute("image"); + modpack.oldVersions = element.attribute("oldVersions").split(";"); + modpack.author = element.attribute("author"); + + modpack.dir = element.attribute("dir"); + modpack.file = element.attribute("url"); + + modpackList.append(modpack); + } + + emit finished(modpackList); +} + +void FtbPackFetchTask::fileDownloadFailed(QString reason){ + qWarning() << "Fetching FtbPacks failed: " << reason; + emit failed(reason); +} diff --git a/api/logic/modplatform/FtbPackFetchTask.h b/api/logic/modplatform/FtbPackFetchTask.h new file mode 100644 index 00000000..df5a96e6 --- /dev/null +++ b/api/logic/modplatform/FtbPackFetchTask.h @@ -0,0 +1,34 @@ +#pragma once + +#include "multimc_logic_export.h" +#include "net/NetJob.h" +#include <QTemporaryDir> +#include <QByteArray> +#include <QObject> +#include "PackHelpers.h" + +class MULTIMC_LOGIC_EXPORT FtbPackFetchTask : public QObject { + + Q_OBJECT + +public: + FtbPackFetchTask(); + ~FtbPackFetchTask(); + + void fetch(); + +private: + NetJobPtr jobPtr; + Net::Download::Ptr downloadPtr; + + QByteArray modpacksXmlFileData; + +protected slots: + void fileDownloadFinished(); + void fileDownloadFailed(QString reason); + +signals: + void finished(FtbModpackList list); + void failed(QString reason); + +}; diff --git a/api/logic/modplatform/FtbPackInstallTask.cpp b/api/logic/modplatform/FtbPackInstallTask.cpp new file mode 100644 index 00000000..bedf3942 --- /dev/null +++ b/api/logic/modplatform/FtbPackInstallTask.cpp @@ -0,0 +1,65 @@ +#include "FtbPackInstallTask.h" +#include "Env.h" +#include "MMCZip.h" +#include "QtConcurrent" + +FtbPackInstallTask::FtbPackInstallTask(FtbPackDownloader *downloader, SettingsObjectPtr settings, + const QString &stagingPath, const QString &instName, const QString &instIcon, const QString &instGroup) : + m_globalSettings(settings), m_stagingPath(stagingPath), m_instName(instName), m_instIcon(instIcon), m_instGroup(instGroup) +{ + m_downloader = downloader; +} + +void FtbPackInstallTask::executeTask() { + downloadPack(); +} + +void FtbPackInstallTask::downloadPack(){ + FtbModpack toInstall = m_downloader->getSelectedPack(); + setStatus(tr("Installing new FTB Pack %1").arg(toInstall.name)); + + auto entry = ENV.metacache()->resolveEntry("general", "FTBPack/" + toInstall.name); + m_downloader->downloadSelected(entry); + + connect(m_downloader, &FtbPackDownloader::downloadSucceded, this, &FtbPackInstallTask::onDownloadSucceeded); + connect(m_downloader, &FtbPackDownloader::downloadProgress, this, &FtbPackInstallTask::onDownloadProgress); + connect(m_downloader, &FtbPackDownloader::downloadFailed, this,&FtbPackInstallTask::onDownloadFailed); +} + +void FtbPackInstallTask::onDownloadSucceeded(QString archivePath){ + qDebug() << "Download succeeded!"; + unzip(archivePath); +} + +void FtbPackInstallTask::onDownloadFailed(QString reason) { + emitFailed(reason); +} + +void FtbPackInstallTask::onDownloadProgress(qint64 current, qint64 total){ + progress(current, total); +} + +void FtbPackInstallTask::unzip(QString archivePath) { + setStatus(QString("Extracting modpack from %1").arg(archivePath)); + QDir extractDir(m_stagingPath); + + m_packZip.reset(new QuaZip(archivePath)); + if(!m_packZip->open(QuaZip::mdUnzip)) { + emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); + return; + } + + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString("/"), extractDir.absolutePath()); + connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &FtbPackInstallTask::onUnzipFinished); + connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &FtbPackInstallTask::onUnzipCanceled); + m_extractFutureWatcher.setFuture(m_extractFuture); +} + +void FtbPackInstallTask::onUnzipFinished() { + qDebug() << "Unzipped:" << m_stagingPath; + emitSucceeded(); +} + +void FtbPackInstallTask::onUnzipCanceled() { + emitAborted(); +} diff --git a/api/logic/modplatform/FtbPackInstallTask.h b/api/logic/modplatform/FtbPackInstallTask.h new file mode 100644 index 00000000..23ef0811 --- /dev/null +++ b/api/logic/modplatform/FtbPackInstallTask.h @@ -0,0 +1,45 @@ +#pragma once +#include "tasks/Task.h" +#include "modplatform/FtbPackDownloader.h" +#include "BaseInstanceProvider.h" +#include "net/NetJob.h" +#include "quazip.h" +#include "quazipdir.h" + +class MULTIMC_LOGIC_EXPORT FtbPackInstallTask : public Task { + + Q_OBJECT + +public: + explicit FtbPackInstallTask(FtbPackDownloader *downloader, SettingsObjectPtr settings, const QString & stagingPath, const QString &instName, + const QString &instIcon, const QString &instGroup); + +protected: + //! Entry point for tasks. + virtual void executeTask() override; + +private: /* data */ + SettingsObjectPtr m_globalSettings; + QString m_stagingPath; + QString m_instName; + QString m_instIcon; + QString m_instGroup; + NetJobPtr m_netJobPtr; + FtbPackDownloader *m_downloader = nullptr; + + std::unique_ptr<QuaZip> m_packZip; + QFuture<QStringList> m_extractFuture; + QFutureWatcher<QStringList> m_extractFutureWatcher; + + void downloadPack(); + void unzip(QString archivePath); + void install(); + +private slots: + void onDownloadSucceeded(QString archivePath); + void onDownloadFailed(QString reason); + void onDownloadProgress(qint64 current, qint64 total); + + void onUnzipFinished(); + void onUnzipCanceled(); +}; diff --git a/api/logic/modplatform/PackHelpers.h b/api/logic/modplatform/PackHelpers.h new file mode 100644 index 00000000..ba0e5cb0 --- /dev/null +++ b/api/logic/modplatform/PackHelpers.h @@ -0,0 +1,21 @@ +#pragma once +#include <QList> + +//Header for structs etc... + +struct FtbModpack { + QString name; + QString description; + QString author; + QStringList oldVersions; + QString currentVersion; + QString mcVersion; + QString mods; + QString image; + + //Technical data + QString dir; + QString file; //<- Url in the xml, but doesn't make much sense +}; + +typedef QList<FtbModpack> FtbModpackList; diff --git a/api/logic/net/Download.cpp b/api/logic/net/Download.cpp index 12c1b201..631d3076 100644 --- a/api/logic/net/Download.cpp +++ b/api/logic/net/Download.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -102,7 +102,8 @@ void Download::start() connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); + connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); + connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); } void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) @@ -135,31 +136,68 @@ void Download::downloadError(QNetworkReply::NetworkError error) } } -bool Download::handleRedirect() +void Download::sslErrors(const QList<QSslError> & errors) { - QVariant redirect = m_reply->header(QNetworkRequest::LocationHeader); - QString redirectURL; - if(redirect.isValid()) + int i = 1; + for (auto error : errors) { - redirectURL = redirect.toString(); + qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; } - // FIXME: This is a hack for https://bugreports.qt-project.org/browse/QTBUG-41061 - else if(m_reply->hasRawHeader("Location")) +} + +bool Download::handleRedirect() +{ + QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); + if(!redirect.isValid()) { - auto data = m_reply->rawHeader("Location"); - if(data.size() > 2 && data[0] == '/' && data[1] == '/') + if(!m_reply->hasRawHeader("Location")) + { + // no redirect -> it's fine to continue + return false; + } + // there is a Location header, but it's not correct. we need to apply some workarounds... + QByteArray redirectBA = m_reply->rawHeader("Location"); + if(redirectBA.size() == 0) + { + // empty, yet present redirect header? WTF? + return false; + } + QString redirectStr = QString::fromUtf8(redirectBA); + + /* + * IF the URL begins with //, we need to insert the URL scheme. + * See: https://bugreports.qt-project.org/browse/QTBUG-41061 + */ + if(redirectStr.startsWith("//")) + { + redirectStr = m_reply->url().scheme() + ":" + redirectStr; + } + + /* + * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. + * FIXME: report Qt bug for this + */ + redirect = QUrl(redirectStr, QUrl::TolerantMode); + if(!redirect.isValid()) { - redirectURL = m_reply->url().scheme() + ":" + data; + qWarning() << "Failed to parse redirect URL:" << redirectStr; + downloadError(QNetworkReply::ProtocolFailure); + return false; } + qDebug() << "Fixed location header:" << redirect; } - if (!redirectURL.isEmpty()) + else { - m_url = QUrl(redirect.toString()); - qDebug() << "Following redirect to " << m_url.toString(); - start(); - return true; + qDebug() << "Location header:" << redirect; } - return false; + + m_url = QUrl(redirect.toString()); + qDebug() << "Following redirect to " << m_url.toString(); + start(); + return true; } diff --git a/api/logic/net/Download.h b/api/logic/net/Download.h index 3347dc96..00bf108c 100644 --- a/api/logic/net/Download.h +++ b/api/logic/net/Download.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,6 +58,7 @@ private: /* methods */ protected slots: void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; void downloadError(QNetworkReply::NetworkError error) override; + void sslErrors(const QList<QSslError> & errors); void downloadFinished() override; void downloadReadyRead() override; diff --git a/api/logic/net/HttpMetaCache.cpp b/api/logic/net/HttpMetaCache.cpp index 3eec185b..ebcb0a27 100644 --- a/api/logic/net/HttpMetaCache.cpp +++ b/api/logic/net/HttpMetaCache.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/net/HttpMetaCache.h b/api/logic/net/HttpMetaCache.h index 21c9b6c8..7ee5f735 100644 --- a/api/logic/net/HttpMetaCache.h +++ b/api/logic/net/HttpMetaCache.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/net/Mode.h b/api/logic/net/Mode.h new file mode 100644 index 00000000..62e26d92 --- /dev/null +++ b/api/logic/net/Mode.h @@ -0,0 +1,10 @@ +#pragma once + +namespace Net +{ +enum class Mode +{ + Offline, + Online +}; +} diff --git a/api/logic/net/NetAction.h b/api/logic/net/NetAction.h index a533c317..08e40a29 100644 --- a/api/logic/net/NetAction.h +++ b/api/logic/net/NetAction.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,10 @@ enum JobStatus Job_Finished, Job_Failed, Job_Aborted, + /* + * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion. + * Same could be true for aborted task - the presence of pre-existing result is a separate concern + */ Job_Failed_Proceed }; @@ -43,18 +47,26 @@ protected: public: virtual ~NetAction() {}; -public: - virtual qint64 totalProgress() const + bool isRunning() const { - return m_total_progress; + return m_status == Job_InProgress; } - virtual qint64 currentProgress() const + bool isFinished() const { - return m_progress; + return m_status >= Job_Finished; } - virtual qint64 numberOfFailures() const + bool wasSuccessful() const { - return m_failures; + return m_status == Job_Finished || m_status == Job_Failed_Proceed; + } + + qint64 totalProgress() const + { + return m_total_progress; + } + qint64 currentProgress() const + { + return m_progress; } virtual bool abort() { @@ -64,25 +76,10 @@ public: { return false; } - -public: - /// the network reply - unique_qobject_ptr<QNetworkReply> m_reply; - - /// source URL - QUrl m_url; - - /// The file's status - JobStatus m_status = Job_NotStarted; - - /// index within the parent job - int m_index_within_job = 0; - - qint64 m_progress = 0; - qint64 m_total_progress = 1; - - /// number of failures up to this point - int m_failures = 0; + QUrl url() + { + return m_url; + } signals: void started(int index); @@ -91,14 +88,28 @@ signals: void failed(int index); void aborted(int index); -protected -slots: +protected slots: virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; virtual void downloadError(QNetworkReply::NetworkError error) = 0; virtual void downloadFinished() = 0; virtual void downloadReadyRead() = 0; -public -slots: +public slots: virtual void start() = 0; + +public: + /// index within the parent job, FIXME: nuke + int m_index_within_job = 0; + + /// the network reply + unique_qobject_ptr<QNetworkReply> m_reply; + + /// source URL + QUrl m_url; + + qint64 m_progress = 0; + qint64 m_total_progress = 1; + +protected: + JobStatus m_status = Job_NotStarted; }; diff --git a/api/logic/net/NetJob.cpp b/api/logic/net/NetJob.cpp index 275da749..304b5820 100644 --- a/api/logic/net/NetJob.cpp +++ b/api/logic/net/NetJob.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,55 +59,74 @@ void NetJob::partAborted(int index) void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) { auto &slot = parts_progress[index]; - - current_progress -= slot.current_progress; slot.current_progress = bytesReceived; - current_progress += slot.current_progress; - - total_progress -= slot.total_progress; slot.total_progress = bytesTotal; - total_progress += slot.total_progress; - setProgress(current_progress, total_progress); + + int done = m_done.size(); + int doing = m_doing.size(); + int all = parts_progress.size(); + + qint64 bytesAll = 0; + qint64 bytesTotalAll = 0; + for(auto & partIdx: m_doing) + { + auto part = parts_progress[partIdx]; + // do not count parts with unknown/nonsensical total size + if(part.total_progress <= 0) + { + continue; + } + bytesAll += part.current_progress; + bytesTotalAll += part.total_progress; + } + + qint64 inprogress = (bytesTotalAll == 0) ? 0 : (bytesAll * 1000) / bytesTotalAll; + auto current = done * 1000 + doing * inprogress; + auto current_total = all * 1000; + // HACK: make sure it never jumps backwards. + if(m_current_progress > current) + { + current = m_current_progress; + } + m_current_progress = current; + setProgress(current, current_total); } void NetJob::executeTask() { - qDebug() << m_job_name.toLocal8Bit() << " started."; - m_running = true; - for (int i = 0; i < downloads.size(); i++) - { - m_todo.enqueue(i); - } // hack that delays early failures so they can be caught easier QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); } void NetJob::startMoreParts() { - // check for final conditions if there's nothing in the queue + if(!isRunning()) + { + // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later. + return; + } + // OK. We are actively processing tasks, proceed. + // Check for final conditions if there's nothing in the queue. if(!m_todo.size()) { if(!m_doing.size()) { if(!m_failed.size()) { - qDebug() << m_job_name << "succeeded."; emitSucceeded(); } else if(m_aborted) { - qDebug() << m_job_name << "aborted."; - emitFailed(tr("Job '%1' aborted.").arg(m_job_name)); + emitAborted(); } else { - qCritical() << m_job_name << "failed."; - emitFailed(tr("Job '%1' failed to process:\n%2").arg(m_job_name).arg(getFailedFiles().join("\n"))); + emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n"))); } } return; } - // otherwise try to start more parts + // There's work to do, try to start more parts. while (m_doing.size() < 6) { if(!m_todo.size()) @@ -131,7 +150,7 @@ QStringList NetJob::getFailedFiles() QStringList failed; for (auto index: m_failed) { - failed.push_back(downloads[index]->m_url.toString()); + failed.push_back(downloads[index]->url().toString()); } failed.sort(); return failed; @@ -170,3 +189,24 @@ bool NetJob::abort() } return fullyAborted; } + +bool NetJob::addNetAction(NetActionPtr action) +{ + action->m_index_within_job = downloads.size(); + downloads.append(action); + part_info pi; + parts_progress.append(pi); + partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress()); + + if(action->isRunning()) + { + connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64))); + } + else + { + m_todo.append(parts_progress.size() - 1); + } + return true; +} diff --git a/api/logic/net/NetJob.h b/api/logic/net/NetJob.h index ca4f5df1..be58c61a 100644 --- a/api/logic/net/NetJob.h +++ b/api/logic/net/NetJob.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,32 +30,13 @@ class MULTIMC_LOGIC_EXPORT NetJob : public Task { Q_OBJECT public: - explicit NetJob(QString job_name) : Task(), m_job_name(job_name) {} - virtual ~NetJob() {} - bool addNetAction(NetActionPtr action) + explicit NetJob(QString job_name) : Task() { - action->m_index_within_job = downloads.size(); - downloads.append(action); - part_info pi; - { - pi.current_progress = action->currentProgress(); - pi.total_progress = action->totalProgress(); - pi.failures = action->numberOfFailures(); - } - parts_progress.append(pi); - total_progress += pi.total_progress; - // if this is already running, the action needs to be started right away! - if (isRunning()) - { - setProgress(current_progress, total_progress); - connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); - connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); - connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), - SLOT(partProgress(int, qint64, qint64))); - action->start(); - } - return true; + setObjectName(job_name); } + virtual ~NetJob() {} + + bool addNetAction(NetActionPtr action); NetActionPtr operator[](int index) { @@ -75,10 +56,6 @@ public: { return downloads.size(); } - virtual bool isRunning() const override - { - return m_running; - } QStringList getFailedFiles(); bool canAbort() const override; @@ -102,17 +79,13 @@ private: qint64 current_progress = 0; qint64 total_progress = 1; int failures = 0; - bool connected = false; }; - QString m_job_name; QList<NetActionPtr> downloads; QList<part_info> parts_progress; QQueue<int> m_todo; QSet<int> m_doing; QSet<int> m_done; QSet<int> m_failed; - qint64 current_progress = 0; - qint64 total_progress = 0; - bool m_running = false; + qint64 m_current_progress = 0; bool m_aborted = false; }; diff --git a/api/logic/net/PasteUpload.cpp b/api/logic/net/PasteUpload.cpp index 59779b2c..d1ddf39d 100644 --- a/api/logic/net/PasteUpload.cpp +++ b/api/logic/net/PasteUpload.cpp @@ -2,41 +2,45 @@ #include "Env.h" #include <QDebug> #include <QJsonObject> +#include <QJsonArray> #include <QJsonDocument> +#include <QFile> PasteUpload::PasteUpload(QWidget *window, QString text, QString key) : m_window(window) { m_key = key; QByteArray temp; - temp = text.toUtf8(); - temp.replace('\n', "\r\n"); - m_textSize = temp.size(); - m_text = "key=" + m_key.toLatin1() + "&description=MultiMC5+Log+File&language=plain&format=json&expire=2592000&paste=" + temp.toPercentEncoding(); - buf = new QBuffer(&m_text); + QJsonObject topLevelObj; + QJsonObject sectionObject; + sectionObject.insert("contents", text); + QJsonArray sectionArray; + sectionArray.append(sectionObject); + topLevelObj.insert("description", "MultiMC Log Upload"); + topLevelObj.insert("sections", sectionArray); + QJsonDocument docOut; + docOut.setObject(topLevelObj); + m_jsonContent = docOut.toJson(); } PasteUpload::~PasteUpload() { - if(buf) - { - delete buf; - } } bool PasteUpload::validateText() { - return m_textSize <= maxSize(); + return m_jsonContent.size() <= maxSize(); } void PasteUpload::executeTask() { - QNetworkRequest request(QUrl("https://paste.ee/api")); + QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes")); request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); - request.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); - request.setRawHeader("Content-Length", QByteArray::number(m_text.size())); + request.setRawHeader("Content-Type", "application/json"); + request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size())); + request.setRawHeader("X-Auth-Token", m_key.toStdString().c_str()); - QNetworkReply *rep = ENV.qnam().post(request, buf); + QNetworkReply *rep = ENV.qnam().post(request, m_jsonContent); m_reply = std::shared_ptr<QNetworkReply>(rep); setStatus(tr("Uploading to paste.ee")); @@ -54,10 +58,10 @@ void PasteUpload::downloadError(QNetworkReply::NetworkError error) void PasteUpload::downloadFinished() { + QByteArray data = m_reply->readAll(); // if the download succeeded if (m_reply->error() == QNetworkReply::NetworkError::NoError) { - QByteArray data = m_reply->readAll(); m_reply.reset(); QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); @@ -85,14 +89,15 @@ void PasteUpload::downloadFinished() bool PasteUpload::parseResult(QJsonDocument doc) { auto object = doc.object(); - auto status = object.value("status").toString("error"); - if (status == "error") + auto status = object.value("success").toBool(); + if (!status) { qCritical() << "paste.ee reported error:" << QString(object.value("error").toString()); return false; } - m_pasteLink = object.value("paste").toObject().value("link").toString(); - m_pasteID = object.value("paste").toObject().value("id").toString(); + m_pasteLink = object.value("link").toString(); + m_pasteID = object.value("id").toString(); + qDebug() << m_pasteLink; return true; } diff --git a/api/logic/net/PasteUpload.h b/api/logic/net/PasteUpload.h index 78d1da8e..62d2e766 100644 --- a/api/logic/net/PasteUpload.h +++ b/api/logic/net/PasteUpload.h @@ -34,14 +34,12 @@ protected: private: bool parseResult(QJsonDocument doc); - QByteArray m_text; QString m_error; QWidget *m_window; QString m_pasteID; QString m_pasteLink; QString m_key; - int m_textSize = 0; - QBuffer * buf = nullptr; + QByteArray m_jsonContent; std::shared_ptr<QNetworkReply> m_reply; public slots: diff --git a/api/logic/net/URLConstants.h b/api/logic/net/URLConstants.h index dedb1424..22d128b2 100644 --- a/api/logic/net/URLConstants.h +++ b/api/logic/net/URLConstants.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/news/NewsChecker.cpp b/api/logic/news/NewsChecker.cpp index 47cdfc05..0ee3f9da 100644 --- a/api/logic/news/NewsChecker.cpp +++ b/api/logic/news/NewsChecker.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/news/NewsChecker.h b/api/logic/news/NewsChecker.h index ba701a9d..44f2534a 100644 --- a/api/logic/news/NewsChecker.h +++ b/api/logic/news/NewsChecker.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/news/NewsEntry.cpp b/api/logic/news/NewsEntry.cpp index da9f00a7..4377f766 100644 --- a/api/logic/news/NewsEntry.cpp +++ b/api/logic/news/NewsEntry.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/news/NewsEntry.h b/api/logic/news/NewsEntry.h index 8a91399a..16a17f9c 100644 --- a/api/logic/news/NewsEntry.h +++ b/api/logic/news/NewsEntry.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/INIFile.cpp b/api/logic/settings/INIFile.cpp index e2162931..9e97f861 100644 --- a/api/logic/settings/INIFile.cpp +++ b/api/logic/settings/INIFile.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/INIFile.h b/api/logic/settings/INIFile.h index 3295209e..f0c63d3c 100644 --- a/api/logic/settings/INIFile.h +++ b/api/logic/settings/INIFile.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/INISettingsObject.cpp b/api/logic/settings/INISettingsObject.cpp index 0a1628d2..ff2cee31 100644 --- a/api/logic/settings/INISettingsObject.cpp +++ b/api/logic/settings/INISettingsObject.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/INISettingsObject.h b/api/logic/settings/INISettingsObject.h index b776db59..111215e6 100644 --- a/api/logic/settings/INISettingsObject.h +++ b/api/logic/settings/INISettingsObject.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/OverrideSetting.cpp b/api/logic/settings/OverrideSetting.cpp index 1d7b56b5..a3d48e03 100644 --- a/api/logic/settings/OverrideSetting.cpp +++ b/api/logic/settings/OverrideSetting.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/OverrideSetting.h b/api/logic/settings/OverrideSetting.h index 70c0b817..f2cbc5dc 100644 --- a/api/logic/settings/OverrideSetting.h +++ b/api/logic/settings/OverrideSetting.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/PassthroughSetting.cpp b/api/logic/settings/PassthroughSetting.cpp index 2a407260..5da5d11c 100644 --- a/api/logic/settings/PassthroughSetting.cpp +++ b/api/logic/settings/PassthroughSetting.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/PassthroughSetting.h b/api/logic/settings/PassthroughSetting.h index 999efbec..ee844da4 100644 --- a/api/logic/settings/PassthroughSetting.h +++ b/api/logic/settings/PassthroughSetting.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/Setting.cpp b/api/logic/settings/Setting.cpp index c4167436..fa0041e0 100644 --- a/api/logic/settings/Setting.cpp +++ b/api/logic/settings/Setting.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/Setting.h b/api/logic/settings/Setting.h index 79c6f26c..3edea7be 100644 --- a/api/logic/settings/Setting.h +++ b/api/logic/settings/Setting.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/SettingsObject.cpp b/api/logic/settings/SettingsObject.cpp index 545a57d4..87a8c2a8 100644 --- a/api/logic/settings/SettingsObject.cpp +++ b/api/logic/settings/SettingsObject.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/settings/SettingsObject.h b/api/logic/settings/SettingsObject.h index d0b6bea1..8582d8ad 100644 --- a/api/logic/settings/SettingsObject.h +++ b/api/logic/settings/SettingsObject.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/status/StatusChecker.cpp b/api/logic/status/StatusChecker.cpp index 4b9418ee..bff9fda9 100644 --- a/api/logic/status/StatusChecker.cpp +++ b/api/logic/status/StatusChecker.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/status/StatusChecker.h b/api/logic/status/StatusChecker.h index 004381a5..f19aba9a 100644 --- a/api/logic/status/StatusChecker.h +++ b/api/logic/status/StatusChecker.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/tasks/Task.cpp b/api/logic/tasks/Task.cpp index 23ee08e4..2523aeb2 100644 --- a/api/logic/tasks/Task.cpp +++ b/api/logic/tasks/Task.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,31 +41,79 @@ void Task::start() { m_running = true; emit started(); + qDebug() << "Task" << describe() << "started"; executeTask(); } void Task::emitFailed(QString reason) { + // Don't fail twice. + if (!m_running) + { + qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; + return; + } m_running = false; m_finished = true; m_succeeded = false; m_failReason = reason; - qCritical() << "Task failed: " << reason; + qCritical() << "Task" << describe() << "failed: " << reason; emit failed(reason); emit finished(); } +void Task::emitAborted() +{ + // Don't abort twice. + if (!m_running) + { + qCritical() << "Task" << describe() << "aborted while not running!!!!"; + return; + } + m_running = false; + m_finished = true; + m_succeeded = false; + m_failReason = "Aborted."; + qDebug() << "Task" << describe() << "aborted."; + emit failed(m_failReason); + emit finished(); +} + void Task::emitSucceeded() { - if (!m_running) { return; } // Don't succeed twice. + // Don't succeed twice. + if (!m_running) + { + qCritical() << "Task" << describe() << "succeeded while not running!!!!"; + return; + } m_running = false; m_finished = true; m_succeeded = true; - qDebug() << "Task succeeded"; + qDebug() << "Task" << describe() << "succeeded"; emit succeeded(); emit finished(); } +QString Task::describe() +{ + QString outStr; + QTextStream out(&outStr); + out << metaObject()->className() << QChar('('); + auto name = objectName(); + if(name.isEmpty()) + { + out << QString("0x%1").arg((quintptr)this, 0, 16); + } + else + { + out << name; + } + out << QChar(')'); + out.flush(); + return outStr; +} + bool Task::isRunning() const { return m_running; @@ -76,7 +124,7 @@ bool Task::isFinished() const return m_finished; } -bool Task::successful() const +bool Task::wasSuccessful() const { return m_succeeded; } @@ -86,3 +134,13 @@ QString Task::failReason() const return m_failReason; } +void Task::logWarning(const QString& line) +{ + qWarning() << line; + m_Warnings.append(line); +} + +QStringList Task::warnings() const +{ + return m_Warnings; +} diff --git a/api/logic/tasks/Task.h b/api/logic/tasks/Task.h index 47c4a13e..643f8510 100644 --- a/api/logic/tasks/Task.h +++ b/api/logic/tasks/Task.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ #include <QObject> #include <QString> +#include <QStringList> #include "multimc_logic_export.h" @@ -27,21 +28,17 @@ public: explicit Task(QObject *parent = 0); virtual ~Task() {}; - virtual bool isRunning() const; - - virtual bool isFinished() const; - - /*! - * True if this task was successful. - * If the task failed or is still running, returns false. - */ - virtual bool successful() const; + bool isRunning() const; + bool isFinished() const; + bool wasSuccessful() const; /*! * Returns the string that was passed to emitFailed as the error message when the task failed. * If the task hasn't failed, returns an empty string. */ - virtual QString failReason() const; + QString failReason() const; + + virtual QStringList warnings() const; virtual bool canAbort() const { return false; } @@ -60,6 +57,12 @@ public: return m_progressTotal; } +protected: + void logWarning(const QString & line); + +private: + QString describe(); + signals: void started(); void progress(qint64 current, qint64 total); @@ -68,8 +71,7 @@ signals: void failed(QString reason); void status(QString status); -public -slots: +public slots: virtual void start(); virtual bool abort() { return false; }; @@ -78,16 +80,18 @@ protected: protected slots: virtual void emitSucceeded(); + virtual void emitAborted(); virtual void emitFailed(QString reason); public slots: void setStatus(const QString &status); void setProgress(qint64 current, qint64 total); -protected: +private: bool m_running = false; bool m_finished = false; bool m_succeeded = false; + QStringList m_Warnings; QString m_failReason = ""; QString m_status; int m_progress = 0; diff --git a/api/logic/tasks/ThreadTask.cpp b/api/logic/tasks/ThreadTask.cpp deleted file mode 100644 index ddd1dee5..00000000 --- a/api/logic/tasks/ThreadTask.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "ThreadTask.h" -#include <QtConcurrentRun> -ThreadTask::ThreadTask(Task * internal, QObject *parent) : Task(parent), m_internal(internal) -{ -} - -void ThreadTask::start() -{ - connect(m_internal, SIGNAL(failed(QString)), SLOT(iternal_failed(QString))); - connect(m_internal, SIGNAL(progress(qint64,qint64)), SLOT(iternal_progress(qint64,qint64))); - connect(m_internal, SIGNAL(started()), SLOT(iternal_started())); - connect(m_internal, SIGNAL(status(QString)), SLOT(iternal_status(QString))); - connect(m_internal, SIGNAL(succeeded()), SLOT(iternal_succeeded())); - m_running = true; - QtConcurrent::run(m_internal, &Task::start); -} - -void ThreadTask::iternal_failed(QString reason) -{ - emitFailed(reason); -} - -void ThreadTask::iternal_progress(qint64 current, qint64 total) -{ - progress(current, total); -} - -void ThreadTask::iternal_started() -{ - emit started(); -} - -void ThreadTask::iternal_status(QString status) -{ - setStatus(status); -} - -void ThreadTask::iternal_succeeded() -{ - emitSucceeded(); -} diff --git a/api/logic/tasks/ThreadTask.h b/api/logic/tasks/ThreadTask.h deleted file mode 100644 index 46ce3a36..00000000 --- a/api/logic/tasks/ThreadTask.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "Task.h" -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT ThreadTask : public Task -{ - Q_OBJECT -public: - explicit ThreadTask(Task * internal, QObject * parent = nullptr); - -protected: - void executeTask() {}; - -public slots: - virtual void start(); - -private slots: - void iternal_started(); - void iternal_progress(qint64 current, qint64 total); - void iternal_succeeded(); - void iternal_failed(QString reason); - void iternal_status(QString status); -private: - Task * m_internal; -};
\ No newline at end of file diff --git a/api/logic/tools/JVisualVM.cpp b/api/logic/tools/JVisualVM.cpp index 169967d9..8fdb594f 100644 --- a/api/logic/tools/JVisualVM.cpp +++ b/api/logic/tools/JVisualVM.cpp @@ -92,7 +92,8 @@ bool JVisualVMFactory::check(const QString &path, QString *error) *error = QObject::tr("Empty path"); return false; } - if (!QDir::isAbsolutePath(path) || !QFileInfo(path).isExecutable() || !path.contains("visualvm")) + QFileInfo finfo(path); + if (!finfo.isExecutable() || !finfo.fileName().contains("visualvm")) { *error = QObject::tr("Invalid path to JVisualVM"); return false; diff --git a/api/logic/translations/TranslationsModel.h b/api/logic/translations/TranslationsModel.h index 092c775e..bd481134 100644 --- a/api/logic/translations/TranslationsModel.h +++ b/api/logic/translations/TranslationsModel.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/updater/DownloadTask.cpp b/api/logic/updater/DownloadTask.cpp index 0d40f78a..e0adf593 100644 --- a/api/logic/updater/DownloadTask.cpp +++ b/api/logic/updater/DownloadTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,7 +70,7 @@ void DownloadTask::vinfoDownloadFailed() { // Something failed. We really need the second download (current version info), so parse // downloads anyways as long as the first one succeeded. - if (m_newVersionFileListDownload->m_status != Job_Failed) + if (m_newVersionFileListDownload->wasSuccessful()) { processDownloadedVersionInfo(); return; @@ -97,7 +97,7 @@ void DownloadTask::processDownloadedVersionInfo() } // if we have the current version info, use it. - if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->m_status != Job_Failed) + if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->wasSuccessful()) { setStatus(tr("Reading file list for current version...")); qDebug() << "Reading file list for current version..."; diff --git a/api/logic/updater/DownloadTask.h b/api/logic/updater/DownloadTask.h index bcbe9736..186826e2 100644 --- a/api/logic/updater/DownloadTask.h +++ b/api/logic/updater/DownloadTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/updater/UpdateChecker.cpp b/api/logic/updater/UpdateChecker.cpp index a02068d8..d8be2c1a 100644 --- a/api/logic/updater/UpdateChecker.cpp +++ b/api/logic/updater/UpdateChecker.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/updater/UpdateChecker.h b/api/logic/updater/UpdateChecker.h index cbf85fd6..4996da26 100644 --- a/api/logic/updater/UpdateChecker.h +++ b/api/logic/updater/UpdateChecker.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. |