diff options
Diffstat (limited to 'logic')
-rw-r--r-- | logic/BaseInstaller.cpp | 66 | ||||
-rw-r--r-- | logic/BaseInstaller.h | 39 | ||||
-rw-r--r-- | logic/ForgeInstaller.cpp | 91 | ||||
-rw-r--r-- | logic/ForgeInstaller.h | 10 | ||||
-rw-r--r-- | logic/InstanceFactory.cpp | 10 | ||||
-rw-r--r-- | logic/LiteLoaderInstaller.cpp | 100 | ||||
-rw-r--r-- | logic/LiteLoaderInstaller.h | 20 | ||||
-rw-r--r-- | logic/OneSixFTBInstance.cpp | 6 | ||||
-rw-r--r-- | logic/OneSixInstance.cpp | 118 | ||||
-rw-r--r-- | logic/OneSixInstance.h | 27 | ||||
-rw-r--r-- | logic/OneSixInstance_p.h | 12 | ||||
-rw-r--r-- | logic/OneSixLibrary.cpp | 36 | ||||
-rw-r--r-- | logic/OneSixLibrary.h | 32 | ||||
-rw-r--r-- | logic/OneSixRule.cpp | 4 | ||||
-rw-r--r-- | logic/OneSixRule.h | 2 | ||||
-rw-r--r-- | logic/OneSixUpdate.cpp | 4 | ||||
-rw-r--r-- | logic/OneSixVersion.cpp | 352 | ||||
-rw-r--r-- | logic/OneSixVersion.h | 60 | ||||
-rw-r--r-- | logic/OneSixVersionBuilder.cpp | 919 | ||||
-rw-r--r-- | logic/OneSixVersionBuilder.h | 43 |
20 files changed, 1486 insertions, 465 deletions
diff --git a/logic/BaseInstaller.cpp b/logic/BaseInstaller.cpp new file mode 100644 index 00000000..92aa0c92 --- /dev/null +++ b/logic/BaseInstaller.cpp @@ -0,0 +1,66 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "BaseInstaller.h" + +#include <QFile> + +#include "OneSixVersion.h" +#include "OneSixLibrary.h" +#include "OneSixInstance.h" + +#include "cmdutils.h" + +BaseInstaller::BaseInstaller() +{ + +} + +bool BaseInstaller::isApplied(OneSixInstance *on) +{ + return QFile::exists(filename(on->instanceRoot())); +} + +bool BaseInstaller::add(OneSixInstance *to) +{ + if (!patchesDir(to->instanceRoot()).exists()) + { + QDir(to->instanceRoot()).mkdir("patches"); + } + + if (isApplied(to)) + { + if (!remove(to)) + { + return false; + } + } + + return true; +} + +bool BaseInstaller::remove(OneSixInstance *from) +{ + return QFile::remove(filename(from->instanceRoot())); +} + +QString BaseInstaller::filename(const QString &root) const +{ + return patchesDir(root).absoluteFilePath(id() + ".json"); +} +QDir BaseInstaller::patchesDir(const QString &root) const +{ + return QDir(root + "/patches/"); +} diff --git a/logic/BaseInstaller.h b/logic/BaseInstaller.h new file mode 100644 index 00000000..df7eab89 --- /dev/null +++ b/logic/BaseInstaller.h @@ -0,0 +1,39 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <memory> + +class OneSixInstance; +class QDir; +class QString; + +class BaseInstaller +{ +public: + BaseInstaller(); + + virtual bool canApply(OneSixInstance *instance) const { return true; } + bool isApplied(OneSixInstance *on); + + virtual bool add(OneSixInstance *to); + virtual bool remove(OneSixInstance *from); + +protected: + virtual QString id() const = 0; + QString filename(const QString &root) const; + QDir patchesDir(const QString &root) const; +}; diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp index 8d4c5b41..47c42694 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/ForgeInstaller.cpp @@ -21,7 +21,15 @@ #include <quazipfile.h> #include <pathutils.h> #include <QStringList> +#include <QRegularExpression> +#include <QRegularExpressionMatch> #include "MultiMC.h" +#include "OneSixInstance.h" + +#include <QJsonDocument> +#include <QJsonArray> +#include <QSaveFile> +#include <QCryptographicHash> ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) { @@ -66,6 +74,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) QJsonObject installObj = installVal.toObject(); QString libraryName = installObj.value("path").toString(); internalPath = installObj.value("filePath").toString(); + m_forgeVersionString = installObj.value("version").toString().remove("Forge").trimmed(); // where do we put the library? decode the mojang path OneSixLibrary lib(libraryName); @@ -103,13 +112,22 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) realVersionId = m_forge_version->id = installObj.value("minecraft").toString(); } -bool ForgeInstaller::apply(std::shared_ptr<OneSixVersion> to) +bool ForgeInstaller::add(OneSixInstance *to) { + if (!BaseInstaller::add(to)) + { + return false; + } + + QJsonObject obj; + obj.insert("order", 5); + if (!m_forge_version) return false; - to->externalUpdateStart(); int sliding_insert_window = 0; { + QJsonArray librariesPlus; + // for each library in the version we are adding (except for the blacklisted) QSet<QString> blacklist{"lwjgl", "lwjgl_util", "lwjgl-platform"}; for (auto lib : m_forge_version->libraries) @@ -128,28 +146,79 @@ bool ForgeInstaller::apply(std::shared_ptr<OneSixVersion> to) if (blacklist.contains(libName)) continue; - // find an entry that matches this one + QJsonObject libObj = lib->toJson(); + bool found = false; - for (auto tolib : to->libraries) + bool equals = false; + // find an entry that matches this one + for (auto tolib : to->getNonCustomVersion()->libraries) { if (tolib->name() != libName) continue; found = true; + if (tolib->toJson() == libObj) + { + equals = true; + } // replace lib - tolib = lib; + libObj.insert("insert", QString("replace")); break; } + if (equals) + { + continue; + } if (!found) { // add lib - to->libraries.insert(sliding_insert_window, lib); + libObj.insert("insert", QString("prepend-if-not-exists")); sliding_insert_window++; } + librariesPlus.prepend(libObj); + } + obj.insert("+libraries", librariesPlus); + obj.insert("mainClass", m_forge_version->mainClass); + QString args = m_forge_version->minecraftArguments; + QStringList tweakers; + { + QRegularExpression expression("--tweakClass ([a-zA-Z0-9\\.]*)"); + QRegularExpressionMatch match = expression.match(args); + while (match.hasMatch()) + { + tweakers.append(match.captured(1)); + args.remove(match.capturedStart(), match.capturedLength()); + match = expression.match(args); + } + } + if (!args.isEmpty() && args != to->getNonCustomVersion()->minecraftArguments) + { + obj.insert("minecraftArguments", args); + } + if (!tweakers.isEmpty()) + { + obj.insert("+tweakers", QJsonArray::fromStringList(tweakers)); + } + if (!m_forge_version->processArguments.isEmpty() && + m_forge_version->processArguments != to->getNonCustomVersion()->processArguments) + { + obj.insert("processArguments", m_forge_version->processArguments); } - to->mainClass = m_forge_version->mainClass; - to->minecraftArguments = m_forge_version->minecraftArguments; - to->processArguments = m_forge_version->processArguments; } - to->externalUpdateFinish(); - return to->toOriginalFile(); + + obj.insert("name", QString("Forge")); + obj.insert("fileId", id()); + obj.insert("version", m_forgeVersionString); + obj.insert("mcVersion", to->intendedVersionId()); + + QFile file(filename(to->instanceRoot())); + if (!file.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(QJsonDocument(obj).toJson()); + file.close(); + + return true; } diff --git a/logic/ForgeInstaller.h b/logic/ForgeInstaller.h index 0b9f9c77..c5052092 100644 --- a/logic/ForgeInstaller.h +++ b/logic/ForgeInstaller.h @@ -14,17 +14,22 @@ */ #pragma once + +#include "BaseInstaller.h" + #include <QString> #include <memory> class OneSixVersion; -class ForgeInstaller +class ForgeInstaller : public BaseInstaller { public: ForgeInstaller(QString filename, QString universal_url); - bool apply(std::shared_ptr<OneSixVersion> to); + bool add(OneSixInstance *to) override; + + QString id() const override { return "net.minecraftforge"; } private: // the version, read from the installer @@ -32,5 +37,6 @@ private: QString internalPath; QString finalPath; QString realVersionId; + QString m_forgeVersionString; QString m_universal_url; }; diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index 1f1a5879..807bccd0 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -24,6 +24,7 @@ #include "OneSixInstance.h" #include "OneSixFTBInstance.h" #include "NostalgiaInstance.h" +#include "OneSixInstance.h" #include "BaseVersion.h" #include "MinecraftVersion.h" @@ -50,13 +51,13 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst QString inst_type = m_settings->get("InstanceType").toString(); // FIXME: replace with a map lookup, where instance classes register their types - if (inst_type == "Legacy") + if (inst_type == "OneSix") { - inst = new LegacyInstance(instDir, m_settings, this); + inst = new OneSixInstance(instDir, m_settings, this); } - else if (inst_type == "OneSix") + else if (inst_type == "Legacy") { - inst = new OneSixInstance(instDir, m_settings, this); + inst = new LegacyInstance(instDir, m_settings, this); } else if (inst_type == "Nostalgia") { @@ -101,6 +102,7 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *& switch (mcVer->type) { case MinecraftVersion::Legacy: + // TODO new instance type m_settings->set("InstanceType", "Legacy"); inst = new LegacyInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor()); diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp index 07fffff3..60a43d49 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/LiteLoaderInstaller.cpp @@ -15,12 +15,19 @@ #include "LiteLoaderInstaller.h" +#include <QJsonArray> +#include <QJsonDocument> + +#include "logger/QsLog.h" + #include "OneSixVersion.h" #include "OneSixLibrary.h" +#include "OneSixInstance.h" QMap<QString, QString> LiteLoaderInstaller::m_launcherWrapperVersionMapping; -LiteLoaderInstaller::LiteLoaderInstaller(const QString &mcVersion) : m_mcVersion(mcVersion) +LiteLoaderInstaller::LiteLoaderInstaller() + : BaseInstaller() { if (m_launcherWrapperVersionMapping.isEmpty()) { @@ -31,72 +38,59 @@ LiteLoaderInstaller::LiteLoaderInstaller(const QString &mcVersion) : m_mcVersion } } -bool LiteLoaderInstaller::canApply() const +bool LiteLoaderInstaller::canApply(OneSixInstance *instance) const { - return m_launcherWrapperVersionMapping.contains(m_mcVersion); + return m_launcherWrapperVersionMapping.contains(instance->intendedVersionId()); } -bool LiteLoaderInstaller::apply(std::shared_ptr<OneSixVersion> to) +bool LiteLoaderInstaller::add(OneSixInstance *to) { - to->externalUpdateStart(); - - applyLaunchwrapper(to); - applyLiteLoader(to); - - to->mainClass = "net.minecraft.launchwrapper.Launch"; - if (!to->minecraftArguments.contains( - " --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker")) + if (!BaseInstaller::add(to)) { - to->minecraftArguments.append( - " --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker"); + return false; } - to->externalUpdateFinish(); - return to->toOriginalFile(); -} + QJsonObject obj; -void LiteLoaderInstaller::applyLaunchwrapper(std::shared_ptr<OneSixVersion> to) -{ - const QString intendedVersion = m_launcherWrapperVersionMapping[m_mcVersion]; + obj.insert("mainClass", QString("net.minecraft.launchwrapper.Launch")); + obj.insert("+tweakers", QJsonArray::fromStringList(QStringList() << "com.mumfrey.liteloader.launch.LiteLoaderTweaker")); + obj.insert("order", 10); - QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries); - while (it.hasNext()) + QJsonArray libraries; + + // launchwrapper { - it.next(); - if (it.value()->rawName().startsWith("net.minecraft:launchwrapper:")) - { - if (it.value()->version() >= intendedVersion) - { - return; - } - else - { - it.remove(); - } - } + OneSixLibrary launchwrapperLib("net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[to->intendedVersionId()]); + launchwrapperLib.finalize(); + QJsonObject lwLibObj = launchwrapperLib.toJson(); + lwLibObj.insert("insert", QString("prepend-if-not-exists")); + libraries.append(lwLibObj); } - std::shared_ptr<OneSixLibrary> lib(new OneSixLibrary( - "net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[m_mcVersion])); - lib->finalize(); - to->libraries.prepend(lib); -} + // liteloader + { + OneSixLibrary liteloaderLib("com.mumfrey:liteloader:" + to->intendedVersionId()); + liteloaderLib.setBaseUrl("http://dl.liteloader.com/versions/"); + liteloaderLib.finalize(); + QJsonObject llLibObj = liteloaderLib.toJson(); + llLibObj.insert("insert", QString("prepend")); + libraries.append(llLibObj); + } -void LiteLoaderInstaller::applyLiteLoader(std::shared_ptr<OneSixVersion> to) -{ - QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries); - while (it.hasNext()) + obj.insert("+libraries", libraries); + obj.insert("name", QString("LiteLoader")); + obj.insert("fileId", id()); + obj.insert("version", to->intendedVersionId()); + obj.insert("mcVersion", to->intendedVersionId()); + + QFile file(filename(to->instanceRoot())); + if (!file.open(QFile::WriteOnly)) { - it.next(); - if (it.value()->rawName().startsWith("com.mumfrey:liteloader:")) - { - it.remove(); - } + QLOG_ERROR() << "Error opening" << file.fileName() << "for reading:" << file.errorString(); + return false; } + file.write(QJsonDocument(obj).toJson()); + file.close(); - std::shared_ptr<OneSixLibrary> lib( - new OneSixLibrary("com.mumfrey:liteloader:" + m_mcVersion)); - lib->setBaseUrl("http://dl.liteloader.com/versions/"); - lib->finalize(); - to->libraries.prepend(lib); + return true; } diff --git a/logic/LiteLoaderInstaller.h b/logic/LiteLoaderInstaller.h index 44b306d6..5e01b16c 100644 --- a/logic/LiteLoaderInstaller.h +++ b/logic/LiteLoaderInstaller.h @@ -14,26 +14,22 @@ */ #pragma once + +#include "BaseInstaller.h" + #include <QString> #include <QMap> -#include <memory> - -class OneSixVersion; -class LiteLoaderInstaller +class LiteLoaderInstaller : public BaseInstaller { public: - LiteLoaderInstaller(const QString &mcVersion); + LiteLoaderInstaller(); - bool canApply() const; - - bool apply(std::shared_ptr<OneSixVersion> to); + bool canApply(OneSixInstance *instance) const override; + bool add(OneSixInstance *to) override; private: - QString m_mcVersion; - - void applyLaunchwrapper(std::shared_ptr<OneSixVersion> to); - void applyLiteLoader(std::shared_ptr<OneSixVersion> to); + virtual QString id() const override { return "com.mumfrey.liteloader"; } static QMap<QString, QString> m_launcherWrapperVersionMapping; }; diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp index f8e695b9..ca88142a 100644 --- a/logic/OneSixFTBInstance.cpp +++ b/logic/OneSixFTBInstance.cpp @@ -55,15 +55,13 @@ slots: setStatus(tr("Installing Forge...")); QString forgePath = entry->getFullPath(); ForgeInstaller forge(forgePath, forgeVersion->universal_url); - if (!instance->reloadFullVersion()) + if (!instance->reloadVersion()) { emitFailed(tr("Couldn't load the version config")); return; } - instance->revertCustomVersion(); - instance->customizeVersion(); auto version = instance->getFullVersion(); - if (!forge.apply(version)) + if (!forge.add(instance)) { emitFailed(tr("Couldn't install Forge")); return; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 67649f77..8ecbe7a0 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -13,32 +13,37 @@ * limitations under the License. */ -#include "MultiMC.h" #include "OneSixInstance.h" + +#include <QIcon> + #include "OneSixInstance_p.h" #include "OneSixUpdate.h" -#include "MinecraftProcess.h" #include "OneSixVersion.h" -#include "JavaChecker.h" -#include "logic/icons/IconList.h" - -#include <setting.h> -#include <pathutils.h> -#include <cmdutils.h> -#include <JlCompress.h> -#include "gui/dialogs/OneSixModEditDialog.h" +#include "pathutils.h" #include "logger/QsLog.h" -#include "logic/assets/AssetsUtils.h" -#include <QIcon> +#include "assets/AssetsUtils.h" +#include "MultiMC.h" +#include "icons/IconList.h" +#include "MinecraftProcess.h" +#include "gui/dialogs/OneSixModEditDialog.h" -OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_obj, - QObject *parent) - : BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent) +OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) + : BaseInstance(new OneSixInstancePrivate(), rootDir, settings, parent) { I_D(OneSixInstance); d->m_settings->registerSetting("IntendedVersion", ""); d->m_settings->registerSetting("ShouldUpdate", false); - reloadFullVersion(); + d->version.reset(new OneSixVersion(this, this)); + d->nonCustomVersion.reset(new OneSixVersion(this, this)); + if (QDir(instanceRoot()).exists("version.json")) + { + reloadVersion(); + } + else + { + clearVersion(); + } } std::shared_ptr<Task> OneSixInstance::doUpdate() @@ -135,6 +140,10 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) I_D(OneSixInstance); auto version = d->version; QString args_pattern = version->minecraftArguments; + for (auto tweaker : version->tweakers) + { + args_pattern += " --tweakClass " + tweaker; + } QMap<QString, QString> token_mapping; // yggdrasil! @@ -267,11 +276,8 @@ bool OneSixInstance::setIntendedVersionId(QString version) { settings().set("IntendedVersion", version); setShouldUpdate(true); - auto pathCustom = PathCombine(instanceRoot(), "custom.json"); - auto pathOrig = PathCombine(instanceRoot(), "version.json"); - QFile::remove(pathCustom); - QFile::remove(pathOrig); - reloadFullVersion(); + QFile::remove(PathCombine(instanceRoot(), "version.json")); + clearVersion(); return true; } @@ -297,9 +303,10 @@ bool OneSixInstance::shouldUpdate() const bool OneSixInstance::versionIsCustom() { - QString verpath_custom = PathCombine(instanceRoot(), "custom.json"); - QFile versionfile(verpath_custom); - return versionfile.exists(); + QDir patches(PathCombine(instanceRoot(), "patches/")); + return (patches.exists() && patches.count() >= 0) + || QFile::exists(PathCombine(instanceRoot(), "custom.json")) + || QFile::exists(PathCombine(instanceRoot(), "user.json")); } QString OneSixInstance::currentVersionId() const @@ -307,62 +314,39 @@ QString OneSixInstance::currentVersionId() const return intendedVersionId(); } -bool OneSixInstance::customizeVersion() +bool OneSixInstance::reloadVersion(QWidget *widgetParent) { - if (!versionIsCustom()) - { - auto pathCustom = PathCombine(instanceRoot(), "custom.json"); - auto pathOrig = PathCombine(instanceRoot(), "version.json"); - QFile::copy(pathOrig, pathCustom); - return reloadFullVersion(); - } - else - return true; -} + I_D(OneSixInstance); -bool OneSixInstance::revertCustomVersion() -{ - if (versionIsCustom()) + bool ret = d->version->reload(widgetParent); + if (ret) { - auto path = PathCombine(instanceRoot(), "custom.json"); - QFile::remove(path); - return reloadFullVersion(); + ret = d->nonCustomVersion->reload(widgetParent, true); } - else - return true; + emit versionReloaded(); + return ret; } -bool OneSixInstance::reloadFullVersion() +void OneSixInstance::clearVersion() { I_D(OneSixInstance); - - QString verpath = PathCombine(instanceRoot(), "version.json"); - { - QString verpath_custom = PathCombine(instanceRoot(), "custom.json"); - QFile versionfile(verpath_custom); - if (versionfile.exists()) - verpath = verpath_custom; - } - - auto version = OneSixVersion::fromFile(verpath); - if (version) - { - d->version = version; - return true; - } - else - { - d->version.reset(); - return false; - } + d->version->clear(); + d->nonCustomVersion->clear(); + emit versionReloaded(); } -std::shared_ptr<OneSixVersion> OneSixInstance::getFullVersion() +std::shared_ptr<OneSixVersion> OneSixInstance::getFullVersion() const { - I_D(OneSixInstance); + I_D(const OneSixInstance); return d->version; } +std::shared_ptr<OneSixVersion> OneSixInstance::getNonCustomVersion() const +{ + I_D(const OneSixInstance); + return d->nonCustomVersion; +} + QString OneSixInstance::defaultBaseJar() const { return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; @@ -382,7 +366,7 @@ bool OneSixInstance::menuActionEnabled(QString action_name) const QString OneSixInstance::getStatusbarDescription() { - QString descr = "One Six : " + intendedVersionId(); + QString descr = "OneSix : " + intendedVersionId(); if (versionIsCustom()) { descr + " (custom)"; diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index c159723b..5aa12e44 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -15,21 +15,17 @@ #pragma once -#include <QStringList> -#include <QDir> - #include "BaseInstance.h" -class OneSixVersion; -class Task; -class ModList; +#include "OneSixVersion.h" +#include "ModList.h" class OneSixInstance : public BaseInstance { Q_OBJECT public: explicit OneSixInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent = 0); + QObject *parent = 0); ////// Mod Lists ////// std::shared_ptr<ModList> loaderModList(); @@ -55,14 +51,14 @@ public: virtual QDialog *createModEditDialog(QWidget *parent) override; - /// reload the full version json file. return true on success! - bool reloadFullVersion(); + /// reload the full version json files. return true on success! + bool reloadVersion(QWidget *widgetParent = 0); + /// clears all version information in preparation for an update + void clearVersion(); /// get the current full version info - std::shared_ptr<OneSixVersion> getFullVersion(); - /// revert the current custom version back to base - bool revertCustomVersion(); - /// customize the current base version - bool customizeVersion(); + std::shared_ptr<OneSixVersion> getFullVersion() const; + /// gets the current version info, excluding custom.json + std::shared_ptr<OneSixVersion> getNonCustomVersion() const; /// is the current version original, or custom? virtual bool versionIsCustom() override; @@ -72,6 +68,9 @@ public: virtual bool menuActionEnabled(QString action_name) const override; virtual QString getStatusbarDescription() override; +signals: + void versionReloaded(); + private: QStringList processMinecraftArgs(AuthSessionPtr account); QDir reconstructAssets(std::shared_ptr<OneSixVersion> version); diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h index 6b7ea431..6dc74e50 100644 --- a/logic/OneSixInstance_p.h +++ b/logic/OneSixInstance_p.h @@ -15,16 +15,14 @@ #pragma once -#include <memory> - -#include "logic/BaseInstance_p.h" -#include "logic/OneSixVersion.h" -#include "logic/OneSixLibrary.h" -#include "logic/ModList.h" +#include "BaseInstance_p.h" +#include "OneSixVersion.h" +#include "ModList.h" struct OneSixInstancePrivate : public BaseInstancePrivate { std::shared_ptr<OneSixVersion> version; + std::shared_ptr<OneSixVersion> nonCustomVersion; std::shared_ptr<ModList> loader_mod_list; std::shared_ptr<ModList> resource_pack_list; -};
\ No newline at end of file +}; diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp index 7b80d5e7..2b1f0600 100644 --- a/logic/OneSixLibrary.cpp +++ b/logic/OneSixLibrary.cpp @@ -76,11 +76,11 @@ void OneSixLibrary::finalize() } } -void OneSixLibrary::setName(QString name) +void OneSixLibrary::setName(const QString &name) { m_name = name; } -void OneSixLibrary::setBaseUrl(QString base_url) +void OneSixLibrary::setBaseUrl(const QString &base_url) { m_base_url = base_url; } @@ -88,50 +88,54 @@ void OneSixLibrary::setIsNative() { m_is_native = true; } -void OneSixLibrary::addNative(OpSys os, QString suffix) +void OneSixLibrary::addNative(OpSys os, const QString &suffix) { m_is_native = true; m_native_suffixes[os] = suffix; } +void OneSixLibrary::clearSuffixes() +{ + m_native_suffixes.clear(); +} void OneSixLibrary::setRules(QList<std::shared_ptr<Rule>> rules) { m_rules = rules; } -bool OneSixLibrary::isActive() +bool OneSixLibrary::isActive() const { return m_is_active; } -bool OneSixLibrary::isNative() +bool OneSixLibrary::isNative() const { return m_is_native; } -QString OneSixLibrary::downloadUrl() +QString OneSixLibrary::downloadUrl() const { if (m_absolute_url.size()) return m_absolute_url; return m_download_url; } -QString OneSixLibrary::storagePath() +QString OneSixLibrary::storagePath() const { return m_storage_path; } -void OneSixLibrary::setAbsoluteUrl(QString absolute_url) +void OneSixLibrary::setAbsoluteUrl(const QString &absolute_url) { m_absolute_url = absolute_url; } -QString OneSixLibrary::absoluteUrl() +QString OneSixLibrary::absoluteUrl() const { return m_absolute_url; } -void OneSixLibrary::setHint(QString hint) +void OneSixLibrary::setHint(const QString &hint) { m_hint = hint; } -QString OneSixLibrary::hint() +QString OneSixLibrary::hint() const { return m_hint; } @@ -176,7 +180,7 @@ bool OneSixLibrary::extractTo(QString target_dir) cooked_storage.replace("${arch}", "32"); QString origin = PathCombine("libraries", cooked_storage); QString target_dir_cooked = PathCombine(target_dir, "32"); - if(!ensureFolderPathExists(target_dir_cooked)) + if (!ensureFolderPathExists(target_dir_cooked)) { QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; return false; @@ -191,7 +195,7 @@ bool OneSixLibrary::extractTo(QString target_dir) cooked_storage.replace("${arch}", "64"); origin = PathCombine("libraries", cooked_storage); target_dir_cooked = PathCombine(target_dir, "64"); - if(!ensureFolderPathExists(target_dir_cooked)) + if (!ensureFolderPathExists(target_dir_cooked)) { QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; return false; @@ -205,7 +209,7 @@ bool OneSixLibrary::extractTo(QString target_dir) } else { - if(!ensureFolderPathExists(target_dir)) + if (!ensureFolderPathExists(target_dir)) { QLOG_ERROR() << "Couldn't create folder " + target_dir; return false; @@ -230,8 +234,10 @@ QJsonObject OneSixLibrary::toJson() libRoot.insert("MMC-hint", m_hint); if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::LIBRARY_BASE) + m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty()) + { libRoot.insert("url", m_base_url); + } if (isNative() && m_native_suffixes.size()) { QJsonObject nativeList; diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h index 227cdbef..5384015a 100644 --- a/logic/OneSixLibrary.h +++ b/logic/OneSixLibrary.h @@ -63,7 +63,7 @@ public: public: /// Constructor - OneSixLibrary(QString name) + OneSixLibrary(const QString &name) { m_name = name; } @@ -84,48 +84,50 @@ public: void finalize(); /// Set the library composite name - void setName(QString name); + void setName(const QString &name); /// get a decent-looking name - QString name() + QString name() const { return m_decentname; } /// get a decent-looking version - QString version() + QString version() const { return m_decentversion; } /// what kind of library is it? (for display) - QString type() + QString type() const { return m_decenttype; } /// Set the url base for downloads - void setBaseUrl(QString base_url); + void setBaseUrl(const QString &base_url); /// Call this to mark the library as 'native' (it's a zip archive with DLLs) void setIsNative(); /// Attach a name suffix to the specified OS native - void addNative(OpSys os, QString suffix); + void addNative(OpSys os, const QString &suffix); + /// Clears all suffixes + void clearSuffixes(); /// Set the load rules void setRules(QList<std::shared_ptr<Rule>> rules); /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive(); + bool isActive() const; /// Returns true if the library is native - bool isNative(); + bool isNative() const; /// Get the URL to download the library from - QString downloadUrl(); + QString downloadUrl() const; /// Get the relative path where the library should be saved - QString storagePath(); + QString storagePath() const; /// set an absolute URL for the library. This is an MMC extension. - void setAbsoluteUrl(QString absolute_url); - QString absoluteUrl(); + void setAbsoluteUrl(const QString &absolute_url); + QString absoluteUrl() const; /// set a hint about how to treat the library. This is an MMC extension. - void setHint(QString hint); - QString hint(); + void setHint(const QString &hint); + QString hint() const; bool extractTo(QString target_dir); bool filesExist(); diff --git a/logic/OneSixRule.cpp b/logic/OneSixRule.cpp index 392b1dd1..d8d13b50 100644 --- a/logic/OneSixRule.cpp +++ b/logic/OneSixRule.cpp @@ -18,7 +18,7 @@ #include "OneSixRule.h" -QList<std::shared_ptr<Rule>> rulesFromJsonV4(QJsonObject &objectWithRules) +QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules) { QList<std::shared_ptr<Rule>> rules; auto rulesVal = objectWithRules.value("rules"); @@ -86,4 +86,4 @@ RuleAction RuleAction_fromString(QString name) if (name == "disallow") return Disallow; return Defer; -}
\ No newline at end of file +} diff --git a/logic/OneSixRule.h b/logic/OneSixRule.h index 5a13cbd9..426e2886 100644 --- a/logic/OneSixRule.h +++ b/logic/OneSixRule.h @@ -27,7 +27,7 @@ enum RuleAction }; RuleAction RuleAction_fromString(QString); -QList<std::shared_ptr<Rule>> rulesFromJsonV4(QJsonObject &objectWithRules); +QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules); class Rule { diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index 7685952c..d3ac80c2 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -131,7 +131,7 @@ void OneSixUpdate::versionFileFinished() { finfo.remove(); } - inst->reloadFullVersion(); + inst->reloadVersion(); jarlibStart(); } @@ -229,7 +229,7 @@ void OneSixUpdate::jarlibStart() setStatus(tr("Getting the library files from Mojang...")); QLOG_INFO() << m_inst->name() << ": downloading libraries"; OneSixInstance *inst = (OneSixInstance *)m_inst; - bool successful = inst->reloadFullVersion(); + bool successful = inst->reloadVersion(); if (!successful) { emitFailed("Failed to load the version description file. It might be " diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index 8ae685f0..0b6d1986 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -13,228 +13,86 @@ * limitations under the License. */ -#include "logic/OneSixVersion.h" -#include "logic/OneSixLibrary.h" -#include "logic/OneSixRule.h" +#include "OneSixVersion.h" -#include "logger/QsLog.h" +#include <QDebug> +#include <QFile> -std::shared_ptr<OneSixVersion> fromJsonV4(QJsonObject root, - std::shared_ptr<OneSixVersion> fullVersion) -{ - fullVersion->id = root.value("id").toString(); - - fullVersion->mainClass = root.value("mainClass").toString(); - auto procArgsValue = root.value("processArguments"); - if (procArgsValue.isString()) - { - fullVersion->processArguments = procArgsValue.toString(); - QString toCompare = fullVersion->processArguments.toLower(); - if (toCompare == "legacy") - { - fullVersion->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - fullVersion->minecraftArguments = - "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - fullVersion->minecraftArguments = "--username ${auth_player_name} " - "--session ${auth_session} " - "--version ${profile_name}"; - } - } - - auto minecraftArgsValue = root.value("minecraftArguments"); - if (minecraftArgsValue.isString()) - { - fullVersion->minecraftArguments = minecraftArgsValue.toString(); - } +#include "OneSixVersionBuilder.h" - auto minecraftTypeValue = root.value("type"); - if (minecraftTypeValue.isString()) - { - fullVersion->type = minecraftTypeValue.toString(); - } - - fullVersion->releaseTime = root.value("releaseTime").toString(); - fullVersion->time = root.value("time").toString(); - - auto assetsID = root.value("assets"); - if (assetsID.isString()) - { - fullVersion->assets = assetsID.toString(); - } - else - { - fullVersion->assets = "legacy"; - } - - QLOG_DEBUG() << "Assets version:" << fullVersion->assets; - - // Iterate through the list, if it's a list. - auto librariesValue = root.value("libraries"); - if (!librariesValue.isArray()) - return fullVersion; - - QJsonArray libList = root.value("libraries").toArray(); - for (auto libVal : libList) - { - if (!libVal.isObject()) - { - continue; - } - - QJsonObject libObj = libVal.toObject(); - - // Library name - auto nameVal = libObj.value("name"); - if (!nameVal.isString()) - continue; - std::shared_ptr<OneSixLibrary> library(new OneSixLibrary(nameVal.toString())); - - auto urlVal = libObj.value("url"); - if (urlVal.isString()) - { - library->setBaseUrl(urlVal.toString()); - } - auto hintVal = libObj.value("MMC-hint"); - if (hintVal.isString()) - { - library->setHint(hintVal.toString()); - } - auto urlAbsVal = libObj.value("MMC-absoluteUrl"); - auto urlAbsuVal = libObj.value("MMC-absulute_url"); // compatibility - if (urlAbsVal.isString()) - { - library->setAbsoluteUrl(urlAbsVal.toString()); - } - else if (urlAbsuVal.isString()) - { - library->setAbsoluteUrl(urlAbsuVal.toString()); - } - // Extract excludes (if any) - auto extractVal = libObj.value("extract"); - if (extractVal.isObject()) - { - QStringList excludes; - auto extractObj = extractVal.toObject(); - auto excludesVal = extractObj.value("exclude"); - if (excludesVal.isArray()) - { - auto excludesList = excludesVal.toArray(); - for (auto excludeVal : excludesList) - { - if (excludeVal.isString()) - excludes.append(excludeVal.toString()); - } - library->extract_excludes = excludes; - } - } - - auto nativesVal = libObj.value("natives"); - if (nativesVal.isObject()) - { - library->setIsNative(); - auto nativesObj = nativesVal.toObject(); - auto iter = nativesObj.begin(); - while (iter != nativesObj.end()) - { - auto osType = OpSys_fromString(iter.key()); - if (osType == Os_Other) - continue; - if (!iter.value().isString()) - continue; - library->addNative(osType, iter.value().toString()); - iter++; - } - } - library->setRules(rulesFromJsonV4(libObj)); - library->finalize(); - fullVersion->libraries.append(library); - } - return fullVersion; +OneSixVersion::OneSixVersion(OneSixInstance *instance, QObject *parent) + : QAbstractListModel(parent), m_instance(instance) +{ + clear(); } -std::shared_ptr<OneSixVersion> OneSixVersion::fromJson(QJsonObject root) +bool OneSixVersion::reload(QWidget *widgetParent, const bool excludeCustom) { - std::shared_ptr<OneSixVersion> readVersion(new OneSixVersion()); - int launcher_ver = readVersion->minimumLauncherVersion = - root.value("minimumLauncherVersion").toDouble(); - - // ADD MORE HERE :D - if (launcher_ver > 0 && launcher_ver <= 13) - return fromJsonV4(root, readVersion); - else - { - return std::shared_ptr<OneSixVersion>(); - } + beginResetModel(); + bool ret = OneSixVersionBuilder::build(this, m_instance, widgetParent, excludeCustom); + endResetModel(); + return ret; } -std::shared_ptr<OneSixVersion> OneSixVersion::fromFile(QString filepath) +void OneSixVersion::clear() { - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) - { - return std::shared_ptr<OneSixVersion>(); - } - - auto data = file.readAll(); - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + beginResetModel(); + id.clear(); + time.clear(); + releaseTime.clear(); + type.clear(); + assets.clear(); + processArguments.clear(); + minecraftArguments.clear(); + minimumLauncherVersion = 0xDEADBEAF; + mainClass.clear(); + libraries.clear(); + tweakers.clear(); + versionFiles.clear(); + endResetModel(); +} - if (jsonError.error != QJsonParseError::NoError) +void OneSixVersion::dump() const +{ + qDebug().nospace() << "OneSixVersion(" + << "\n\tid=" << id + << "\n\ttime=" << time + << "\n\treleaseTime=" << releaseTime + << "\n\ttype=" << type + << "\n\tassets=" << assets + << "\n\tprocessArguments=" << processArguments + << "\n\tminecraftArguments=" << minecraftArguments + << "\n\tminimumLauncherVersion=" << minimumLauncherVersion + << "\n\tmainClass=" << mainClass + << "\n\tlibraries="; + for (auto lib : libraries) { - return std::shared_ptr<OneSixVersion>(); + qDebug().nospace() << "\n\t\t" << lib.get(); } + qDebug().nospace() << "\n)"; +} - if (!jsonDoc.isObject()) +bool OneSixVersion::canRemove(const int index) const +{ + if (index < versionFiles.size()) { - return std::shared_ptr<OneSixVersion>(); + return versionFiles.at(index).id != "org.multimc.version.json"; } - QJsonObject root = jsonDoc.object(); - auto version = fromJson(root); - if (version) - version->original_file = filepath; - return version; + return false; } -bool OneSixVersion::toOriginalFile() +bool OneSixVersion::remove(const int index) { - if (original_file.isEmpty()) - return false; - QSaveFile file(original_file); - if (!file.open(QIODevice::WriteOnly)) + if (canRemove(index)) { - return false; + return QFile::remove(versionFiles.at(index).filename); } - // serialize base attributes (those we care about anyway) - QJsonObject root; - root.insert("minecraftArguments", minecraftArguments); - root.insert("mainClass", mainClass); - root.insert("minimumLauncherVersion", minimumLauncherVersion); - root.insert("time", time); - root.insert("id", id); - root.insert("type", type); - // screw processArguments - root.insert("releaseTime", releaseTime); - QJsonArray libarray; - for (const auto &lib : libraries) - { - libarray.append(lib->toJson()); - } - if (libarray.count()) - root.insert("libraries", libarray); - QJsonDocument doc(root); - file.write(doc.toJson()); - return file.commit(); + return false; } -QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNormalLibs() +QList<std::shared_ptr<OneSixLibrary> > OneSixVersion::getActiveNormalLibs() { - QList<std::shared_ptr<OneSixLibrary>> output; + QList<std::shared_ptr<OneSixLibrary> > output; for (auto lib : libraries) { if (lib->isActive() && !lib->isNative()) @@ -245,9 +103,9 @@ QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNormalLibs() return output; } -QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNativeLibs() +QList<std::shared_ptr<OneSixLibrary> > OneSixVersion::getActiveNativeLibs() { - QList<std::shared_ptr<OneSixLibrary>> output; + QList<std::shared_ptr<OneSixLibrary> > output; for (auto lib : libraries) { if (lib->isActive() && lib->isNative()) @@ -258,14 +116,14 @@ QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNativeLibs() return output; } -void OneSixVersion::externalUpdateStart() -{ - beginResetModel(); -} - -void OneSixVersion::externalUpdateFinish() +std::shared_ptr<OneSixVersion> OneSixVersion::fromJson(const QJsonObject &obj) { - endResetModel(); + std::shared_ptr<OneSixVersion> version(new OneSixVersion(0)); + if (OneSixVersionBuilder::read(version.get(), obj)) + { + return version; + } + return 0; } QVariant OneSixVersion::data(const QModelIndex &index, int role) const @@ -276,7 +134,7 @@ QVariant OneSixVersion::data(const QModelIndex &index, int role) const int row = index.row(); int column = index.column(); - if (row < 0 || row >= libraries.size()) + if (row < 0 || row >= versionFiles.size()) return QVariant(); if (role == Qt::DisplayRole) @@ -284,11 +142,9 @@ QVariant OneSixVersion::data(const QModelIndex &index, int role) const switch (column) { case 0: - return libraries[row]->name(); + return versionFiles.at(row).name; case 1: - return libraries[row]->type(); - case 2: - return libraries[row]->version(); + return versionFiles.at(row).version; default: return QVariant(); } @@ -296,45 +152,61 @@ QVariant OneSixVersion::data(const QModelIndex &index, int role) const return QVariant(); } -Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const +QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const { - if (!index.isValid()) - return Qt::NoItemFlags; - int row = index.row(); - if (libraries[row]->isActive()) - { - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; - } - else + if (orientation == Qt::Horizontal) { - return Qt::ItemNeverHasChildren; + if (role == Qt::DisplayRole) + { + switch (section) + { + case 0: + return tr("Name"); + case 1: + return tr("Version"); + default: + return QVariant(); + } + } } - // return QAbstractListModel::flags(index); + return QVariant(); } -QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const +Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const { - if (role != Qt::DisplayRole || orientation != Qt::Horizontal) - return QVariant(); - switch (section) - { - case 0: - return QString("Name"); - case 1: - return QString("Type"); - case 2: - return QString("Version"); - default: - return QString(); - } + if (!index.isValid()) + return Qt::NoItemFlags; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } int OneSixVersion::rowCount(const QModelIndex &parent) const { - return libraries.size(); + return versionFiles.size(); } int OneSixVersion::columnCount(const QModelIndex &parent) const { - return 3; + return 2; +} + +QDebug operator<<(QDebug &dbg, const OneSixVersion *version) +{ + version->dump(); + return dbg.maybeSpace(); +} +QDebug operator<<(QDebug &dbg, const OneSixLibrary *library) +{ + dbg.nospace() << "OneSixLibrary(" + << "\n\t\t\trawName=" << library->rawName() + << "\n\t\t\tname=" << library->name() + << "\n\t\t\tversion=" << library->version() + << "\n\t\t\ttype=" << library->type() + << "\n\t\t\tisActive=" << library->isActive() + << "\n\t\t\tisNative=" << library->isNative() + << "\n\t\t\tdownloadUrl=" << library->downloadUrl() + << "\n\t\t\tstoragePath=" << library->storagePath() + << "\n\t\t\tabsolutePath=" << library->absoluteUrl() + << "\n\t\t\thint=" << library->hint(); + dbg.nospace() << "\n\t\t)"; + return dbg.maybeSpace(); } diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index 036f3d53..d7a6684d 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -14,40 +14,48 @@ */ #pragma once -#include <QtCore> + +#include <QAbstractListModel> + +#include <QString> +#include <QList> #include <memory> -class OneSixLibrary; +#include "OneSixLibrary.h" + +class OneSixInstance; class OneSixVersion : public QAbstractListModel { - // Things required to implement the Qt list model + Q_OBJECT public: + explicit OneSixVersion(OneSixInstance *instance, QObject *parent = 0); + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const; virtual int columnCount(const QModelIndex &parent) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; - // serialization/deserialization -public: - bool toOriginalFile(); - static std::shared_ptr<OneSixVersion> fromJson(QJsonObject root); - static std::shared_ptr<OneSixVersion> fromFile(QString filepath); + bool reload(QWidget *widgetParent, const bool excludeCustom = false); + void clear(); + + void dump() const; + + bool canRemove(const int index) const; + +public +slots: + bool remove(const int index); public: QList<std::shared_ptr<OneSixLibrary>> getActiveNormalLibs(); QList<std::shared_ptr<OneSixLibrary>> getActiveNativeLibs(); - // called when something starts/stops messing with the object - // FIXME: these are ugly in every possible way. - void externalUpdateStart(); - void externalUpdateFinish(); + + static std::shared_ptr<OneSixVersion> fromJson(const QJsonObject &obj); // data members public: - /// file this was read from. blank, if none - QString original_file; /// the ID - determines which jar to use! ACTUALLY IMPORTANT! QString id; /// Last updated time - as a string @@ -76,6 +84,10 @@ public: */ int minimumLauncherVersion = 0xDEADBEEF; /** + * A list of all tweaker classes + */ + QStringList tweakers; + /** * The main class to load first */ QString mainClass; @@ -103,4 +115,20 @@ public: } */ // QList<Rule> rules; + + struct VersionFile + { + QString name; + QString id; + QString version; + QString mcVersion; + QString filename; + }; + QList<VersionFile> versionFiles; + +private: + OneSixInstance *m_instance; }; + +QDebug operator<<(QDebug &dbg, const OneSixVersion *version); +QDebug operator<<(QDebug &dbg, const OneSixLibrary *library); diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp new file mode 100644 index 00000000..a541766b --- /dev/null +++ b/logic/OneSixVersionBuilder.cpp @@ -0,0 +1,919 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "OneSixVersionBuilder.h" + +#include <QList> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonDocument> +#include <QFile> +#include <QFileInfo> +#include <QMessageBox> +#include <QObject> +#include <QDir> +#include <QDebug> + +#include "OneSixVersion.h" +#include "OneSixInstance.h" +#include "OneSixRule.h" +#include "logger/QsLog.h" + +struct VersionFile +{ + int order; + QString name; + QString fileId; + QString version; + // TODO use the mcVersion to determine if a version file should be removed on update + QString mcVersion; + QString filename; + // TODO requirements + // QMap<QString, QString> requirements; + QString id; + QString mainClass; + QString overwriteMinecraftArguments; + QString addMinecraftArguments; + QString removeMinecraftArguments; + QString processArguments; + QString type; + QString releaseTime; + QString time; + QString assets; + int minimumLauncherVersion = -1; + + bool shouldOverwriteTweakers = false; + QStringList overwriteTweakers; + QStringList addTweakers; + QStringList removeTweakers; + + struct Library + { + QString name; + QString url; + QString hint; + QString absoluteUrl; + bool applyExcludes = false; + QStringList excludes; + bool applyNatives = false; + QList<QPair<OpSys, QString>> natives; + bool applyRules = false; + QList<std::shared_ptr<Rule>> rules; + + // user for '+' libraries + enum InsertType + { + Apply, + Append, + Prepend, + AppendIfNotExists, + PrependIfNotExists, + Replace + }; + InsertType insertType; + QString insertData; + }; + bool shouldOverwriteLibs = false; + QList<Library> overwriteLibs; + QList<Library> addLibs; + QList<QString> removeLibs; + + static Library fromLibraryJson(const QJsonObject &libObj, const QString &filename, + bool &isError) + { + isError = true; + Library out; + if (!libObj.contains("name")) + { + QLOG_ERROR() << filename << "contains a library that doesn't have a 'name' field"; + return out; + } + out.name = libObj.value("name").toString(); + + auto readString = [libObj, filename](const QString &key, QString &variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("url", out.url); + readString("MMC-hint", out.hint); + readString("MMC-absulute_url", out.absoluteUrl); + readString("MMC-absoluteUrl", out.absoluteUrl); + if (libObj.contains("extract")) + { + if (!libObj.value("extract").isObject()) + { + QLOG_ERROR() + << filename + << "contains a library with an 'extract' field that's not an object"; + return out; + } + QJsonObject extractObj = libObj.value("extract").toObject(); + if (!extractObj.contains("exclude") || !extractObj.value("exclude").isArray()) + { + QLOG_ERROR() << filename + << "contains a library with an invalid 'extract' field"; + return out; + } + out.applyExcludes = true; + QJsonArray excludeArray = extractObj.value("exclude").toArray(); + for (auto excludeVal : excludeArray) + { + if (!excludeVal.isString()) + { + QLOG_WARN() << filename << "contains a library that contains an 'extract' " + "field that contains an invalid 'exclude' entry " + "(skipping)"; + } + else + { + out.excludes.append(excludeVal.toString()); + } + } + } + if (libObj.contains("natives")) + { + if (!libObj.value("natives").isObject()) + { + QLOG_ERROR() + << filename + << "contains a library with a 'natives' field that's not an object"; + return out; + } + out.applyNatives = true; + QJsonObject nativesObj = libObj.value("natives").toObject(); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + if (!it.value().isString()) + { + QLOG_WARN() << filename << "contains an invalid native (skipping)"; + } + OpSys opSys = OpSys_fromString(it.key()); + if (opSys != Os_Other) + { + out.natives.append(qMakePair(opSys, it.value().toString())); + } + } + } + if (libObj.contains("rules")) + { + out.applyRules = true; + out.rules = rulesFromJsonV4(libObj); + } + isError = false; + return out; + } + static VersionFile fromJson(const QJsonDocument &doc, const QString &filename, + const bool requireOrder, bool &isError) + { + VersionFile out; + isError = true; + if (doc.isEmpty() || doc.isNull()) + { + QLOG_ERROR() << filename << "is empty or null"; + return out; + } + if (!doc.isObject()) + { + QLOG_ERROR() << "The root of" << filename << "is not an object"; + return out; + } + + QJsonObject root = doc.object(); + + if (requireOrder) + { + if (root.contains("order")) + { + if (root.value("order").isDouble()) + { + out.order = root.value("order").toDouble(); + } + else + { + QLOG_ERROR() << "'order' field contains an invalid value in" << filename; + return out; + } + } + else + { + QLOG_ERROR() << filename << "doesn't contain an order field"; + } + } + + out.name = root.value("name").toString(); + out.fileId = root.value("fileId").toString(); + out.version = root.value("version").toString(); + out.mcVersion = root.value("mcVersion").toString(); + out.filename = filename; + + auto readString = [root, filename](const QString &key, QString &variable) + { + if (root.contains(key)) + { + QJsonValue val = root.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("id", out.id); + readString("mainClass", out.mainClass); + readString("processArguments", out.processArguments); + readString("minecraftArguments", out.overwriteMinecraftArguments); + readString("+minecraftArguments", out.addMinecraftArguments); + readString("-minecraftArguments", out.removeMinecraftArguments); + readString("type", out.type); + readString("releaseTime", out.releaseTime); + readString("time", out.time); + readString("assets", out.assets); + if (root.contains("minimumLauncherVersion")) + { + QJsonValue val = root.value("minimumLauncherVersion"); + if (!val.isDouble()) + { + QLOG_WARN() << "minimumLauncherVersion is not an int in" << filename + << "(skipping)"; + } + else + { + out.minimumLauncherVersion = val.toDouble(); + } + } + + if (root.contains("tweakers")) + { + QJsonValue tweakersVal = root.value("tweakers"); + if (!tweakersVal.isArray()) + { + QLOG_ERROR() << filename << "contains a 'tweakers' field, but it's not an array"; + return out; + } + out.shouldOverwriteTweakers = true; + QJsonArray tweakers = root.value("tweakers").toArray(); + for (auto tweakerVal : tweakers) + { + if (!tweakerVal.isString()) + { + QLOG_ERROR() << filename << "contains a 'tweakers' field entry that's not a string"; + return out; + } + out.overwriteTweakers.append(tweakerVal.toString()); + } + } + if (root.contains("+tweakers")) + { + QJsonValue tweakersVal = root.value("+tweakers"); + if (!tweakersVal.isArray()) + { + QLOG_ERROR() << filename << "contains a '+tweakers' field, but it's not an array"; + return out; + } + QJsonArray tweakers = root.value("+tweakers").toArray(); + for (auto tweakerVal : tweakers) + { + if (!tweakerVal.isString()) + { + QLOG_ERROR() << filename << "contains a '+tweakers' field entry that's not a string"; + return out; + } + out.addTweakers.append(tweakerVal.toString()); + } + } + if (root.contains("-tweakers")) + { + QJsonValue tweakersVal = root.value("-tweakers"); + if (!tweakersVal.isArray()) + { + QLOG_ERROR() << filename << "contains a '-tweakers' field, but it's not an array"; + return out; + } + out.shouldOverwriteTweakers = true; + QJsonArray tweakers = root.value("-tweakers").toArray(); + for (auto tweakerVal : tweakers) + { + if (!tweakerVal.isString()) + { + QLOG_ERROR() << filename << "contains a '-tweakers' field entry that's not a string"; + return out; + } + out.removeTweakers.append(tweakerVal.toString()); + } + } + + if (root.contains("libraries")) + { + out.shouldOverwriteLibs = true; + QJsonValue librariesVal = root.value("libraries"); + if (!librariesVal.isArray()) + { + QLOG_ERROR() << filename + << "contains a 'libraries' field, but its not an array"; + return out; + } + QJsonArray librariesArray = librariesVal.toArray(); + for (auto libVal : librariesArray) + { + if (!libVal.isObject()) + { + QLOG_ERROR() << filename << "contains a library that's not an object"; + return out; + } + QJsonObject libObj = libVal.toObject(); + bool error; + Library lib = fromLibraryJson(libObj, filename, error); + if (error) + { + QLOG_ERROR() << "Error while reading a library entry in" << filename; + return out; + } + out.overwriteLibs.append(lib); + } + } + if (root.contains("+libraries")) + { + QJsonValue librariesVal = root.value("+libraries"); + if (!librariesVal.isArray()) + { + QLOG_ERROR() << filename + << "contains a '+libraries' field, but its not an array"; + return out; + } + QJsonArray librariesArray = librariesVal.toArray(); + for (auto libVal : librariesArray) + { + if (!libVal.isObject()) + { + QLOG_ERROR() << filename << "contains a library that's not an object"; + return out; + } + QJsonObject libObj = libVal.toObject(); + bool error; + Library lib = fromLibraryJson(libObj, filename, error); + if (error) + { + QLOG_ERROR() << "Error while reading a library entry in" << filename; + return out; + } + if (!libObj.contains("insert")) + { + QLOG_ERROR() << "Missing 'insert' field in '+libraries' field in" + << filename; + return out; + } + QJsonValue insertVal = libObj.value("insert"); + QString insertString; + { + if (insertVal.isString()) + { + insertString = insertVal.toString(); + } + else if (insertVal.isObject()) + { + QJsonObject insertObj = insertVal.toObject(); + if (insertObj.isEmpty()) + { + QLOG_ERROR() << "One library has an empty insert object in" + << filename; + return out; + } + insertString = insertObj.keys().first(); + lib.insertData = insertObj.value(insertString).toString(); + } + } + if (insertString == "apply") + { + lib.insertType = Library::Apply; + } + else if (insertString == "append") + { + lib.insertType = Library::Append; + } + else if (insertString == "prepend") + { + lib.insertType = Library::Prepend; + } + else if (insertString == "prepend-if-not-exists") + { + lib.insertType = Library::PrependIfNotExists; + } + else if (insertString == "append-if-not-exists") + { + lib.insertType = Library::PrependIfNotExists; + } + else if (insertString == "replace") + { + lib.insertType = Library::Replace; + } + else + { + QLOG_ERROR() << "A '+' library in" << filename + << "contains an invalid insert type"; + return out; + } + out.addLibs.append(lib); + } + } + if (root.contains("-libraries")) + { + QJsonValue librariesVal = root.value("-libraries"); + if (!librariesVal.isArray()) + { + QLOG_ERROR() << filename + << "contains a '-libraries' field, but its not an array"; + return out; + } + QJsonArray librariesArray = librariesVal.toArray(); + for (auto libVal : librariesArray) + { + if (!libVal.isObject()) + { + QLOG_ERROR() << filename << "contains a library that's not an object"; + return out; + } + QJsonObject libObj = libVal.toObject(); + if (!libObj.contains("name")) + { + QLOG_ERROR() << filename << "contains a library without a name"; + return out; + } + if (!libObj.value("name").isString()) + { + QLOG_ERROR() << filename + << "contains a library without a valid 'name' field"; + return out; + } + out.removeLibs.append(libObj.value("name").toString()); + } + } + + isError = false; + return out; + } + + static std::shared_ptr<OneSixLibrary> createLibrary(const Library &lib) + { + std::shared_ptr<OneSixLibrary> out(new OneSixLibrary(lib.name)); + out->setBaseUrl(lib.url); + out->setHint(lib.hint); + out->setAbsoluteUrl(lib.absoluteUrl); + out->extract_excludes = lib.excludes; + for (auto native : lib.natives) + { + out->addNative(native.first, native.second); + } + out->setRules(lib.rules); + out->finalize(); + return out; + } + int findLibrary(QList<std::shared_ptr<OneSixLibrary>> haystack, const QString &needle) + { + for (int i = 0; i < haystack.size(); ++i) + { + if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix) + .indexIn(haystack.at(i)->rawName()) != -1) + { + return i; + } + } + return -1; + } + void applyTo(OneSixVersion *version, bool &isError) + { + isError = true; + if (!id.isNull()) + { + version->id = id; + } + if (!mainClass.isNull()) + { + version->mainClass = mainClass; + } + if (!processArguments.isNull()) + { + version->processArguments = processArguments; + } + if (!type.isNull()) + { + version->type = type; + } + if (!releaseTime.isNull()) + { + version->releaseTime = releaseTime; + } + if (!time.isNull()) + { + version->time = time; + } + if (!assets.isNull()) + { + version->assets = assets; + } + if (minimumLauncherVersion >= 0) + { + version->minimumLauncherVersion = minimumLauncherVersion; + } + if (!overwriteMinecraftArguments.isNull()) + { + version->minecraftArguments = overwriteMinecraftArguments; + } + if (!addMinecraftArguments.isNull()) + { + version->minecraftArguments += addMinecraftArguments; + } + if (!removeMinecraftArguments.isNull()) + { + version->minecraftArguments.remove(removeMinecraftArguments); + } + if (shouldOverwriteTweakers) + { + version->tweakers = overwriteTweakers; + } + for (auto tweaker : addTweakers) + { + version->tweakers += tweaker; + } + for (auto tweaker : removeTweakers) + { + version->tweakers.removeAll(tweaker); + } + if (shouldOverwriteLibs) + { + version->libraries.clear(); + for (auto lib : overwriteLibs) + { + version->libraries.append(createLibrary(lib)); + } + } + for (auto lib : addLibs) + { + switch (lib.insertType) + { + case Library::Apply: + { + + int index = findLibrary(version->libraries, lib.name); + if (index >= 0) + { + auto library = version->libraries[index]; + if (!lib.url.isNull()) + { + library->setBaseUrl(lib.url); + } + if (!lib.hint.isNull()) + { + library->setHint(lib.hint); + } + if (!lib.absoluteUrl.isNull()) + { + library->setAbsoluteUrl(lib.absoluteUrl); + } + if (lib.applyExcludes) + { + library->extract_excludes = lib.excludes; + } + if (lib.applyNatives) + { + library->clearSuffixes(); + for (auto native : lib.natives) + { + library->addNative(native.first, native.second); + } + } + if (lib.applyRules) + { + library->setRules(lib.rules); + } + library->finalize(); + } + else + { + QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)"; + } + break; + } + case Library::Append: + version->libraries.append(createLibrary(lib)); + break; + case Library::Prepend: + version->libraries.prepend(createLibrary(lib)); + break; + case Library::AppendIfNotExists: + { + + int index = findLibrary(version->libraries, lib.name); + if (index < 0) + { + version->libraries.append(createLibrary(lib)); + } + break; + } + case Library::PrependIfNotExists: + { + + int index = findLibrary(version->libraries, lib.name); + if (index < 0) + { + version->libraries.prepend(createLibrary(lib)); + } + break; + } + case Library::Replace: + { + int index = findLibrary(version->libraries, lib.insertData); + if (index >= 0) + { + version->libraries.replace(index, createLibrary(lib)); + } + else + { + QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)"; + } + break; + } + } + } + for (auto lib : removeLibs) + { + int index = findLibrary(version->libraries, lib); + if (index >= 0) + { + version->libraries.removeAt(index); + } + else + { + QLOG_WARN() << "Couldn't find" << lib << "(skipping)"; + } + } + + OneSixVersion::VersionFile versionFile; + versionFile.name = name; + versionFile.id = fileId; + versionFile.version = this->version; + versionFile.mcVersion = mcVersion; + versionFile.filename = filename; + version->versionFiles.append(versionFile); + + isError = false; + } +}; + +OneSixVersionBuilder::OneSixVersionBuilder() +{ +} + +bool OneSixVersionBuilder::build(OneSixVersion *version, OneSixInstance *instance, + QWidget *widgetParent, const bool excludeCustom) +{ + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = instance; + builder.m_widgetParent = widgetParent; + return builder.build(excludeCustom); +} + +bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj) +{ + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = 0; + builder.m_widgetParent = 0; + return builder.read(obj); +} + +bool OneSixVersionBuilder::build(const bool excludeCustom) +{ + m_version->clear(); + + QDir root(m_instance->instanceRoot()); + QDir patches(root.absoluteFilePath("patches/")); + + if (QFile::exists(root.absoluteFilePath("custom.json"))) + { + QLOG_INFO() << "Reading custom.json"; + VersionFile file; + if (!read(QFileInfo(root.absoluteFilePath("custom.json")), false, &file)) + { + return false; + } + bool isError = false; + file.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("custom.json"))); + return false; + } + } + else + { + // version.json -> patches/*.json -> instance.json + + // version.json + { + QLOG_INFO() << "Reading version.json"; + VersionFile file; + if (!read(QFileInfo(root.absoluteFilePath("version.json")), false, &file)) + { + return false; + } + file.name = "version.json"; + file.fileId = "org.multimc.version.json"; + file.version = m_instance->intendedVersionId(); + file.mcVersion = m_instance->intendedVersionId(); + bool isError = false; + file.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("version.json"))); + return false; + } + } + + // patches/ + { + // load all, put into map for ordering, apply in the right order + + QMap<int, QPair<QString, VersionFile>> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + QLOG_INFO() << "Reading" << info.fileName(); + VersionFile file; + if (!read(info, true, &file)) + { + return false; + } + files.insert(file.order, qMakePair(info.fileName(), file)); + } + for (auto order : files.keys()) + { + QLOG_DEBUG() << "Applying file with order" << order; + auto filePair = files[order]; + bool isError = false; + filePair.second.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(filePair.first)); + return false; + } + } + } + + // user.json + if (!excludeCustom) + { + if (QFile::exists(root.absoluteFilePath("user.json"))) + { + QLOG_INFO() << "Reading user.json"; + VersionFile file; + if (!read(QFileInfo(root.absoluteFilePath("user.json")), false, &file)) + { + return false; + } + file.name = "user.json"; + file.fileId = "org.multimc.user.json"; + file.version = QString(); + file.mcVersion = QString(); + bool isError = false; + file.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr( + "Error while applying %1. Please check MultiMC-0.log for more info.") + .arg(root.absoluteFilePath("user.json"))); + return false; + } + } + } + } + + // some final touches + { + if (m_version->assets.isEmpty()) + { + m_version->assets = "legacy"; + } + if (m_version->minecraftArguments.isEmpty()) + { + QString toCompare = m_version->processArguments.toLower(); + if (toCompare == "legacy") + { + m_version->minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + m_version->minecraftArguments = + "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + m_version->minecraftArguments = "--username ${auth_player_name} " + "--session ${auth_session} " + "--version ${profile_name}"; + } + } + } + + return true; +} + +bool OneSixVersionBuilder::read(const QJsonObject &obj) +{ + m_version->clear(); + + bool isError = false; + VersionFile file = VersionFile::fromJson(QJsonDocument(obj), QString(), false, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); + return false; + } + file.applyTo(m_version, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr("Error while applying. Please check MultiMC-0.log for more info.")); + return false; + } + + return true; +} + +bool OneSixVersionBuilder::read(const QFileInfo &fileInfo, const bool requireOrder, + VersionFile *out) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr("Unable to open %1: %2").arg(file.fileName(), file.errorString())); + return false; + } + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), + QObject::tr("Unable to parse %1: %2 at %3") + .arg(file.fileName(), error.errorString()) + .arg(error.offset)); + return false; + } + bool isError = false; + *out = VersionFile::fromJson(doc, file.fileName(), requireOrder, isError); + if (isError) + { + QMessageBox::critical( + m_widgetParent, QObject::tr("Error"), + QObject::tr("Error while reading %1. Please check MultiMC-0.log for more info.") + .arg(file.fileName())); + ; + } + return true; +} diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h new file mode 100644 index 00000000..ac8c13bf --- /dev/null +++ b/logic/OneSixVersionBuilder.h @@ -0,0 +1,43 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QString> + +class OneSixVersion; +class OneSixInstance; +class QWidget; +class QJsonObject; +class QFileInfo; +class VersionFile; + +class OneSixVersionBuilder +{ + OneSixVersionBuilder(); +public: + static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool excludeCustom); + static bool read(OneSixVersion *version, const QJsonObject &obj); + +private: + OneSixVersion *m_version; + OneSixInstance *m_instance; + QWidget *m_widgetParent; + + bool build(const bool excludeCustom); + bool read(const QJsonObject &obj); + + bool read(const QFileInfo &fileInfo, const bool requireOrder, VersionFile *out); +}; |