diff options
author | Petr Mrázek <peterix@gmail.com> | 2021-07-25 19:11:59 +0200 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2021-07-25 19:50:44 +0200 |
commit | 20b9f2b42a3b58b6081af271774fbcc34025dccb (patch) | |
tree | 064fa59facb3357139b47bd4e60bfc8edb35ca11 /api/logic/minecraft | |
parent | dd133680858351e3e07690e286882327a4f42ba5 (diff) | |
download | PrismLauncher-20b9f2b42a3b58b6081af271774fbcc34025dccb.tar.gz PrismLauncher-20b9f2b42a3b58b6081af271774fbcc34025dccb.tar.bz2 PrismLauncher-20b9f2b42a3b58b6081af271774fbcc34025dccb.zip |
NOISSUE Flatten gui and logic libraries into MultiMC
Diffstat (limited to 'api/logic/minecraft')
132 files changed, 0 insertions, 18079 deletions
diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp deleted file mode 100644 index c01733b6..00000000 --- a/api/logic/minecraft/AssetsUtils.cpp +++ /dev/null @@ -1,333 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <QFileInfo> -#include <QDir> -#include <QDirIterator> -#include <QCryptographicHash> -#include <QJsonParseError> -#include <QJsonDocument> -#include <QJsonObject> -#include <QVariant> -#include <QDebug> - -#include "AssetsUtils.h" -#include "FileSystem.h" -#include "net/Download.h" -#include "net/ChecksumValidator.h" -#include "BuildConfig.h" - -namespace { -QSet<QString> collectPathsFromDir(QString dirPath) -{ - QFileInfo dirInfo(dirPath); - - if (!dirInfo.exists()) - { - return {}; - } - - QSet<QString> out; - - QDirIterator iter(dirPath, QDirIterator::Subdirectories); - while (iter.hasNext()) - { - QString value = iter.next(); - QFileInfo info(value); - if(info.isFile()) - { - out.insert(value); - qDebug() << value; - } - } - return out; -} -} - - -namespace AssetsUtils -{ - -/* - * Returns true on success, with index populated - * index is undefined otherwise - */ -bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsIndex& index) -{ - /* - { - "objects": { - "icons/icon_16x16.png": { - "hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a", - "size": 3665 - }, - ... - } - } - } - */ - - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::ReadOnly)) - { - qCritical() << "Failed to read assets index file" << path; - return false; - } - index.id = assetsId; - - // Read the file and close it. - QByteArray jsonData = file.readAll(); - file.close(); - - QJsonParseError parseError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); - - // Fail if the JSON is invalid. - if (parseError.error != QJsonParseError::NoError) - { - qCritical() << "Failed to parse assets index file:" << parseError.errorString() - << "at offset " << QString::number(parseError.offset); - return false; - } - - // Make sure the root is an object. - if (!jsonDoc.isObject()) - { - qCritical() << "Invalid assets index JSON: Root should be an array."; - return false; - } - - QJsonObject root = jsonDoc.object(); - - QJsonValue isVirtual = root.value("virtual"); - if (!isVirtual.isUndefined()) - { - index.isVirtual = isVirtual.toBool(false); - } - - QJsonValue mapToResources = root.value("map_to_resources"); - if (!mapToResources.isUndefined()) - { - index.mapToResources = mapToResources.toBool(false); - } - - QJsonValue objects = root.value("objects"); - QVariantMap map = objects.toVariant().toMap(); - - for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) - { - // qDebug() << iter.key(); - - QVariant variant = iter.value(); - QVariantMap nested_objects = variant.toMap(); - - AssetObject object; - - for (QVariantMap::const_iterator nested_iter = nested_objects.begin(); - nested_iter != nested_objects.end(); ++nested_iter) - { - // qDebug() << nested_iter.key() << nested_iter.value().toString(); - QString key = nested_iter.key(); - QVariant value = nested_iter.value(); - - if (key == "hash") - { - object.hash = value.toString(); - } - else if (key == "size") - { - object.size = value.toDouble(); - } - } - - index.objects.insert(iter.key(), object); - } - - return true; -} - -// FIXME: ugly code duplication -QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder) -{ - QDir assetsDir = QDir("assets/"); - QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); - QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); - QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); - - QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); - QFile indexFile(indexPath); - QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); - - if (!indexFile.exists()) - { - qCritical() << "No assets index file" << indexPath << "; can't determine assets path!"; - return virtualRoot; - } - - AssetsIndex index; - if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) - { - qCritical() << "Failed to load asset index file" << indexPath << "; can't determine assets path!"; - return virtualRoot; - } - - QString targetPath; - if(index.isVirtual) - { - return virtualRoot; - } - else if(index.mapToResources) - { - return QDir(resourcesFolder); - } - return virtualRoot; -} - -// FIXME: ugly code duplication -bool reconstructAssets(QString assetsId, QString resourcesFolder) -{ - QDir assetsDir = QDir("assets/"); - QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); - QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); - QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); - - QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); - QFile indexFile(indexPath); - QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); - - if (!indexFile.exists()) - { - qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets!"; - return false; - } - - qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path(); - - AssetsIndex index; - if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) - { - qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!"; - return false; - } - - QString targetPath; - bool removeLeftovers = false; - if(index.isVirtual) - { - targetPath = virtualRoot.path(); - removeLeftovers = true; - qDebug() << "Reconstructing virtual assets folder at" << targetPath; - } - else if(index.mapToResources) - { - targetPath = resourcesFolder; - qDebug() << "Reconstructing resources folder at" << targetPath; - } - - if (!targetPath.isNull()) - { - auto presentFiles = collectPathsFromDir(targetPath); - for (QString map : index.objects.keys()) - { - AssetObject asset_object = index.objects.value(map); - QString target_path = FS::PathCombine(targetPath, map); - QFile target(target_path); - - QString tlk = asset_object.hash.left(2); - - QString original_path = FS::PathCombine(objectDir.path(), tlk, asset_object.hash); - QFile original(original_path); - if (!original.exists()) - continue; - - presentFiles.remove(target_path); - - if (!target.exists()) - { - QFileInfo info(target_path); - QDir target_dir = info.dir(); - - qDebug() << target_dir.path(); - FS::ensureFolderPathExists(target_dir.path()); - - bool couldCopy = original.copy(target_path); - qDebug() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy); - } - } - - // TODO: Write last used time to virtualRoot/.lastused - if(removeLeftovers) - { - for(auto & file: presentFiles) - { - qDebug() << "Would remove" << file; - } - } - } - return true; -} - -} - -NetActionPtr AssetObject::getDownloadAction() -{ - QFileInfo objectFile(getLocalPath()); - if ((!objectFile.isFile()) || (objectFile.size() != size)) - { - auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath()); - if(hash.size()) - { - auto rawHash = QByteArray::fromHex(hash.toLatin1()); - objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); - } - objectDL->m_total_progress = size; - return objectDL; - } - return nullptr; -} - -QString AssetObject::getLocalPath() -{ - return "assets/objects/" + getRelPath(); -} - -QUrl AssetObject::getUrl() -{ - return BuildConfig.RESOURCE_BASE + getRelPath(); -} - -QString AssetObject::getRelPath() -{ - return hash.left(2) + "/" + hash; -} - -NetJobPtr AssetsIndex::getDownloadJob() -{ - auto job = new NetJob(QObject::tr("Assets for %1").arg(id)); - for (auto &object : objects.values()) - { - auto dl = object.getDownloadAction(); - if(dl) - { - job->addNetAction(dl); - } - } - if(job->size()) - return job; - return nullptr; -} diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h deleted file mode 100644 index 32e57060..00000000 --- a/api/logic/minecraft/AssetsUtils.h +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QString> -#include <QMap> -#include "net/NetAction.h" -#include "net/NetJob.h" - -struct AssetObject -{ - QString getRelPath(); - QUrl getUrl(); - QString getLocalPath(); - NetActionPtr getDownloadAction(); - - QString hash; - qint64 size; -}; - -struct AssetsIndex -{ - NetJobPtr getDownloadJob(); - - QString id; - QMap<QString, AssetObject> objects; - bool isVirtual = false; - bool mapToResources = false; -}; - -/// FIXME: this is absolutely horrendous. REDO!!!! -namespace AssetsUtils -{ -bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index); - -QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder); - -/// Reconstruct a virtual assets folder for the given assets ID and return the folder -bool reconstructAssets(QString assetsId, QString resourcesFolder); -} diff --git a/api/logic/minecraft/Component.cpp b/api/logic/minecraft/Component.cpp deleted file mode 100644 index 92821065..00000000 --- a/api/logic/minecraft/Component.cpp +++ /dev/null @@ -1,439 +0,0 @@ -#include <meta/VersionList.h> -#include <meta/Index.h> -#include <Env.h> -#include "Component.h" - -#include "meta/Version.h" -#include "VersionFile.h" -#include "minecraft/PackProfile.h" -#include <FileSystem.h> -#include <QSaveFile> -#include "OneSixVersionFormat.h" -#include <assert.h> - -Component::Component(PackProfile * parent, const QString& uid) -{ - assert(parent); - m_parent = parent; - - m_uid = uid; -} - -Component::Component(PackProfile * 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(PackProfile * 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 (const 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 deleted file mode 100644 index cb202f7f..00000000 --- a/api/logic/minecraft/Component.h +++ /dev/null @@ -1,111 +0,0 @@ -#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 PackProfile; -class LaunchProfile; -namespace Meta -{ - class Version; - class VersionList; -} -class VersionFile; - -class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider -{ -Q_OBJECT -public: - Component(PackProfile * parent, const QString &uid); - - // DEPRECATED: remove these constructors? - Component(PackProfile * parent, std::shared_ptr<Meta::Version> version); - Component(PackProfile * 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 */ - PackProfile * 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/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp deleted file mode 100644 index 241d9a49..00000000 --- a/api/logic/minecraft/ComponentUpdateTask.cpp +++ /dev/null @@ -1,704 +0,0 @@ -#include "ComponentUpdateTask.h" - -#include "PackProfile_p.h" -#include "PackProfile.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 PackProfile. - * See: https://en.wikipedia.org/wiki/Reactor_pattern - */ - -/* - * Or make this operate on a snapshot of the PackProfile state, then merge results in as long as the snapshot and PackProfile didn't change? - * If the component list changes, start over. - */ - -ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* 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 loadPackProfile(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 = loadPackProfile(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.PackProfileIndex = 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->isCustom()) { - decision = Decision::LockedVersionNotSame; - } else { - 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 PackProfile internals. It shouldn't! It needs richer data types than PackProfile 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"; - } - else if (add.uid == "net.fabricmc.intermediary") - { - auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){ - return cmp->getID() == "net.minecraft"; - }); - if(minecraft != components.end()) { - component->m_version = (*minecraft)->getVersion(); - } - } - } -// 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.PackProfileIndex); - 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 deleted file mode 100644 index 4274cabb..00000000 --- a/api/logic/minecraft/ComponentUpdateTask.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "tasks/Task.h" -#include "net/Mode.h" - -#include <memory> -class PackProfile; -struct ComponentUpdateTaskData; - -class ComponentUpdateTask : public Task -{ - Q_OBJECT -public: - enum class Mode - { - Launch, - Resolution - }; - -public: - explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile * 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 deleted file mode 100644 index 5b02431b..00000000 --- a/api/logic/minecraft/ComponentUpdateTask_p.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include <cstddef> -#include <QString> -#include <QList> -#include "net/Mode.h" - -class PackProfile; - -struct RemoteLoadStatus -{ - enum class Type - { - Index, - List, - Version - } type = Type::Version; - size_t PackProfileIndex = 0; - bool finished = false; - bool succeeded = false; - QString error; -}; - -struct ComponentUpdateTaskData -{ - PackProfile * 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/GradleSpecifier.h b/api/logic/minecraft/GradleSpecifier.h deleted file mode 100644 index 60e0a726..00000000 --- a/api/logic/minecraft/GradleSpecifier.h +++ /dev/null @@ -1,151 +0,0 @@ -#pragma once - -#include <QString> -#include <QStringList> -#include "DefaultVariable.h" - -struct GradleSpecifier -{ - GradleSpecifier() - { - m_valid = false; - } - GradleSpecifier(QString value) - { - operator=(value); - } - GradleSpecifier & operator =(const QString & value) - { - /* - org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar - 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar" - 1 "org.gradle.test.classifiers" - 2 "service" - 3 "1.0" - 4 "jdk15" - 5 "jar" - */ - QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?"); - m_valid = matcher.exactMatch(value); - if(!m_valid) { - m_invalidValue = value; - return *this; - } - auto elements = matcher.capturedTexts(); - m_groupId = elements[1]; - m_artifactId = elements[2]; - m_version = elements[3]; - m_classifier = elements[4]; - if(!elements[5].isEmpty()) - { - m_extension = elements[5]; - } - return *this; - } - QString serialize() const - { - if(!m_valid) { - return m_invalidValue; - } - QString retval = m_groupId + ":" + m_artifactId + ":" + m_version; - if(!m_classifier.isEmpty()) - { - retval += ":" + m_classifier; - } - if(m_extension.isExplicit()) - { - retval += "@" + m_extension; - } - return retval; - } - QString getFileName() const - { - if(!m_valid) { - return QString(); - } - QString filename = m_artifactId + '-' + m_version; - if(!m_classifier.isEmpty()) - { - filename += "-" + m_classifier; - } - filename += "." + m_extension; - return filename; - } - QString toPath(const QString & filenameOverride = QString()) const - { - if(!m_valid) { - return QString(); - } - QString filename; - if(filenameOverride.isEmpty()) - { - filename = getFileName(); - } - else - { - filename = filenameOverride; - } - QString path = m_groupId; - path.replace('.', '/'); - path += '/' + m_artifactId + '/' + m_version + '/' + filename; - return path; - } - inline bool valid() const - { - return m_valid; - } - inline QString version() const - { - return m_version; - } - inline QString groupId() const - { - return m_groupId; - } - inline QString artifactId() const - { - return m_artifactId; - } - inline void setClassifier(const QString & classifier) - { - m_classifier = classifier; - } - inline QString classifier() const - { - return m_classifier; - } - inline QString extension() const - { - return m_extension; - } - inline QString artifactPrefix() const - { - return m_groupId + ":" + m_artifactId; - } - bool matchName(const GradleSpecifier & other) const - { - return other.artifactId() == artifactId() && other.groupId() == groupId(); - } - bool operator==(const GradleSpecifier & other) const - { - if(m_groupId != other.m_groupId) - return false; - if(m_artifactId != other.m_artifactId) - return false; - if(m_version != other.m_version) - return false; - if(m_classifier != other.m_classifier) - return false; - if(m_extension != other.m_extension) - return false; - return true; - } -private: - QString m_invalidValue; - QString m_groupId; - QString m_artifactId; - QString m_version; - QString m_classifier; - DefaultVariable<QString> m_extension = DefaultVariable<QString>("jar"); - bool m_valid = false; -}; diff --git a/api/logic/minecraft/GradleSpecifier_test.cpp b/api/logic/minecraft/GradleSpecifier_test.cpp deleted file mode 100644 index 0900c9d8..00000000 --- a/api/logic/minecraft/GradleSpecifier_test.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "minecraft/GradleSpecifier.h" - -class GradleSpecifierTest : public QObject -{ - Q_OBJECT -private -slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void test_Positive_data() - { - QTest::addColumn<QString>("through"); - - QTest::newRow("3 parter") << "org.gradle.test.classifiers:service:1.0"; - QTest::newRow("classifier") << "org.gradle.test.classifiers:service:1.0:jdk15"; - QTest::newRow("jarextension") << "org.gradle.test.classifiers:service:1.0@jar"; - QTest::newRow("jarboth") << "org.gradle.test.classifiers:service:1.0:jdk15@jar"; - QTest::newRow("packxz") << "org.gradle.test.classifiers:service:1.0:jdk15@jar.pack.xz"; - } - void test_Positive() - { - QFETCH(QString, through); - - QString converted = GradleSpecifier(through).serialize(); - - QCOMPARE(converted, through); - } - - void test_Path_data() - { - QTest::addColumn<QString>("spec"); - QTest::addColumn<QString>("expected"); - - QTest::newRow("3 parter") << "group.id:artifact:1.0" << "group/id/artifact/1.0/artifact-1.0.jar"; - QTest::newRow("doom") << "id.software:doom:1.666:demons@wad" << "id/software/doom/1.666/doom-1.666-demons.wad"; - } - void test_Path() - { - QFETCH(QString, spec); - QFETCH(QString, expected); - - QString converted = GradleSpecifier(spec).toPath(); - - QCOMPARE(converted, expected); - } - void test_Negative_data() - { - QTest::addColumn<QString>("input"); - - QTest::newRow("too many :") << "org:gradle.test:class:::ifiers:service:1.0::"; - QTest::newRow("nonsense") << "I like turtles"; - QTest::newRow("empty string") << ""; - QTest::newRow("missing version") << "herp.derp:artifact"; - } - void test_Negative() - { - QFETCH(QString, input); - - GradleSpecifier spec(input); - QVERIFY(!spec.valid()); - QCOMPARE(spec.serialize(), input); - QCOMPARE(spec.toPath(), QString()); - } -}; - -QTEST_GUILESS_MAIN(GradleSpecifierTest) - -#include "GradleSpecifier_test.moc" diff --git a/api/logic/minecraft/LaunchProfile.cpp b/api/logic/minecraft/LaunchProfile.cpp deleted file mode 100644 index 41705187..00000000 --- a/api/logic/minecraft/LaunchProfile.cpp +++ /dev/null @@ -1,319 +0,0 @@ -#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_mavenFiles.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); - } -} - -void LaunchProfile::applyMavenFile(LibraryPtr mavenFile) -{ - if(!mavenFile->isActive()) - { - return; - } - - if(mavenFile->isNative()) - { - return; - } - - // unlike libraries, we do not keep only one version or try to dedupe them - m_mavenFiles.append(Library::limitedCopy(mavenFile)); -} - -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; -} - -const QList<LibraryPtr> & LaunchProfile::getMavenFiles() const -{ - return m_mavenFiles; -} - -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 deleted file mode 100644 index c1752531..00000000 --- a/api/logic/minecraft/LaunchProfile.h +++ /dev/null @@ -1,104 +0,0 @@ -#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 applyMavenFile(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 QList<LibraryPtr> & getMavenFiles() 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 list of maven files to be placed in the libraries folder, but not acted upon - QList<LibraryPtr> m_mavenFiles; - - /// the main jar - LibraryPtr m_mainJar; - - /// the list of native 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 deleted file mode 100644 index f2293679..00000000 --- a/api/logic/minecraft/Library.cpp +++ /dev/null @@ -1,309 +0,0 @@ -#include "Library.h" -#include "MinecraftInstance.h" - -#include <net/Download.h> -#include <net/ChecksumValidator.h> -#include <Env.h> -#include <FileSystem.h> -#include <BuildConfig.h> - - -void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32, - QStringList& native64, const QString &overridePath) const -{ - bool local = isLocal(); - auto actualPath = [&](QString relPath) - { - QFileInfo out(FS::PathCombine(storagePrefix(), relPath)); - if(local && !overridePath.isEmpty()) - { - QString fileName = out.fileName(); - return QFileInfo(FS::PathCombine(overridePath, fileName)).absoluteFilePath(); - } - return out.absoluteFilePath(); - }; - QString raw_storage = storageSuffix(system); - if(isNative()) - { - if (raw_storage.contains("${arch}")) - { - auto nat32Storage = raw_storage; - nat32Storage.replace("${arch}", "32"); - auto nat64Storage = raw_storage; - nat64Storage.replace("${arch}", "64"); - native32 += actualPath(nat32Storage); - native64 += actualPath(nat64Storage); - } - else - { - native += actualPath(raw_storage); - } - } - else - { - jar += actualPath(raw_storage); - } -} - -QList< std::shared_ptr< NetAction > > Library::getDownloads( - OpSys system, - class HttpMetaCache* cache, - QStringList& failedLocalFiles, - const QString & overridePath -) const -{ - QList<NetActionPtr> out; - bool stale = isAlwaysStale(); - bool local = isLocal(); - - auto check_local_file = [&](QString storage) - { - QFileInfo fileinfo(storage); - QString fileName = fileinfo.fileName(); - auto fullPath = FS::PathCombine(overridePath, fileName); - QFileInfo localFileInfo(fullPath); - if(!localFileInfo.exists()) - { - failedLocalFiles.append(localFileInfo.filePath()); - return false; - } - return true; - }; - - auto add_download = [&](QString storage, QString url, QString sha1) - { - if(local) - { - return check_local_file(storage); - } - auto entry = cache->resolveEntry("libraries", storage); - if(stale) - { - entry->setStale(true); - } - if (!entry->isStale()) - return true; - Net::Download::Options options; - if(stale) - { - options |= Net::Download::Option::AcceptLocalFiles; - } - - if(sha1.size()) - { - 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().serialize() << "storage:" << storage << "url:" << url; - out.append(dl); - } - else - { - out.append(Net::Download::makeCached(url, entry, options)); - qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; - } - return true; - }; - - QString raw_storage = storageSuffix(system); - if(m_mojangDownloads) - { - if(isNative()) - { - if(m_nativeClassifiers.contains(system)) - { - auto nativeClassifier = m_nativeClassifiers[system]; - if(nativeClassifier.contains("${arch}")) - { - 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); - } - } - else - { - auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); - if(info) - { - add_download(raw_storage, info->url, info->sha1); - } - } - } - else - { - qDebug() << "Ignoring native library" << m_name.serialize() << "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.serialize() << "because it has no artifact"; - } - } - } - else - { - auto raw_dl = [&]() - { - if (!m_absoluteURL.isEmpty()) - { - return m_absoluteURL; - } - - if (m_repositoryURL.isEmpty()) - { - return BuildConfig.LIBRARY_BASE + raw_storage; - } - - if(m_repositoryURL.endsWith('/')) - { - return m_repositoryURL + raw_storage; - } - else - { - return m_repositoryURL + QChar('/') + raw_storage; - } - }(); - if (raw_storage.contains("${arch}")) - { - QString cooked_storage = raw_storage; - QString cooked_dl = raw_dl; - add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"), QString()); - cooked_storage = raw_storage; - cooked_dl = raw_dl; - add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"), QString()); - } - else - { - add_download(raw_storage, raw_dl, QString()); - } - } - return out; -} - -bool Library::isActive() const -{ - bool result = true; - if (m_rules.empty()) - { - result = true; - } - else - { - RuleAction ruleResult = Disallow; - for (auto rule : m_rules) - { - RuleAction temp = rule->apply(this); - if (temp != Defer) - ruleResult = temp; - } - result = result && (ruleResult == Allow); - } - if (isNative()) - { - result = result && m_nativeClassifiers.contains(currentSystem); - } - return result; -} - -bool Library::isLocal() const -{ - return m_hint == "local"; -} - -bool Library::isAlwaysStale() const -{ - return m_hint == "always-stale"; -} - -void Library::setStoragePrefix(QString prefix) -{ - m_storagePrefix = prefix; -} - -QString Library::defaultStoragePrefix() -{ - return "libraries/"; -} - -QString Library::storagePrefix() const -{ - if(m_storagePrefix.isEmpty()) - { - return defaultStoragePrefix(); - } - return m_storagePrefix; -} - -QString Library::filename(OpSys system) const -{ - if(!m_filename.isEmpty()) - { - return m_filename; - } - // non-native? use only the gradle specifier - if (!isNative()) - { - return m_name.getFileName(); - } - - // otherwise native, override classifiers. Mojang HACK! - GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) - { - nativeSpec.setClassifier(m_nativeClassifiers[system]); - } - else - { - nativeSpec.setClassifier("INVALID"); - } - return nativeSpec.getFileName(); -} - -QString Library::displayName(OpSys system) const -{ - if(!m_displayname.isEmpty()) - return m_displayname; - return filename(system); -} - -QString Library::storageSuffix(OpSys system) const -{ - // non-native? use only the gradle specifier - if (!isNative()) - { - return m_name.toPath(m_filename); - } - - // otherwise native, override classifiers. Mojang HACK! - GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) - { - nativeSpec.setClassifier(m_nativeClassifiers[system]); - } - else - { - nativeSpec.setClassifier("INVALID"); - } - return nativeSpec.toPath(m_filename); -} diff --git a/api/logic/minecraft/Library.h b/api/logic/minecraft/Library.h deleted file mode 100644 index acdd6c9c..00000000 --- a/api/logic/minecraft/Library.h +++ /dev/null @@ -1,219 +0,0 @@ -#pragma once -#include <QString> -#include <net/NetAction.h> -#include <QPair> -#include <QList> -#include <QStringList> -#include <QMap> -#include <QDir> -#include <QUrl> -#include <memory> - -#include "Rule.h" -#include "minecraft/OpSys.h" -#include "GradleSpecifier.h" -#include "MojangDownloadInfo.h" - -#include "multimc_logic_export.h" - -class Library; -class MinecraftInstance; - -typedef std::shared_ptr<Library> LibraryPtr; - -class MULTIMC_LOGIC_EXPORT Library -{ - friend class OneSixVersionFormat; - friend class MojangVersionFormat; - friend class LibraryTest; -public: - Library() - { - } - Library(const QString &name) - { - m_name = name; - } - /// limited copy without some data. TODO: why? - static LibraryPtr limitedCopy(LibraryPtr base) - { - auto newlib = std::make_shared<Library>(); - newlib->m_name = base->m_name; - newlib->m_repositoryURL = base->m_repositoryURL; - newlib->m_hint = base->m_hint; - newlib->m_absoluteURL = base->m_absoluteURL; - newlib->m_extractExcludes = base->m_extractExcludes; - newlib->m_nativeClassifiers = base->m_nativeClassifiers; - newlib->m_rules = base->m_rules; - newlib->m_storagePrefix = base->m_storagePrefix; - newlib->m_mojangDownloads = base->m_mojangDownloads; - newlib->m_filename = base->m_filename; - return newlib; - } - -public: /* methods */ - /// Returns the raw name field - const GradleSpecifier & rawName() const - { - return m_name; - } - - void setRawName(const GradleSpecifier & spec) - { - m_name = spec; - } - - void setClassifier(const QString & spec) - { - m_name.setClassifier(spec); - } - - /// returns the full group and artifact prefix - QString artifactPrefix() const - { - return m_name.artifactPrefix(); - } - - /// get the artifact ID - QString artifactId() const - { - return m_name.artifactId(); - } - - /// get the artifact version - QString version() const - { - return m_name.version(); - } - - /// Returns true if the library is native - bool isNative() const - { - return m_nativeClassifiers.size() != 0; - } - - void setStoragePrefix(QString prefix = QString()); - - /// Set the url base for downloads - void setRepositoryURL(const QString &base_url) - { - m_repositoryURL = base_url; - } - - void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native, - QStringList & native32, QStringList & native64, const QString & overridePath) const; - - void setAbsoluteUrl(const QString &absolute_url) - { - m_absoluteURL = absolute_url; - } - - void setFilename(const QString &filename) - { - m_filename = filename; - } - - /// Get the file name of the library - QString filename(OpSys system) const; - - // DEPRECATED: set a display name, used by jar mods only - void setDisplayName(const QString & displayName) - { - m_displayname = displayName; - } - - /// Get the file name of the library - QString displayName(OpSys system) const; - - void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) - { - m_mojangDownloads = info; - } - - void setHint(const QString &hint) - { - m_hint = hint; - } - - /// Set the load rules - void setRules(QList<std::shared_ptr<Rule>> rules) - { - m_rules = rules; - } - - /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive() const; - - /// Returns true if the library is contained in an instance and false if it is shared - bool isLocal() const; - - /// Returns true if the library is to always be checked for updates - bool isAlwaysStale() const; - - /// Return true if the library requires forge XZ hacks - bool isForge() const; - - // Get a list of downloads for this library - QList<NetActionPtr> getDownloads(OpSys system, class HttpMetaCache * cache, - QStringList & failedLocalFiles, const QString & overridePath) const; - -private: /* methods */ - /// the default storage prefix used by MultiMC - static QString defaultStoragePrefix(); - - /// Get the prefix - root of the storage to be used - QString storagePrefix() const; - - /// Get the relative file path where the library should be saved - QString storageSuffix(OpSys system) const; - - QString hint() const - { - return m_hint; - } - -protected: /* data */ - /// the basic gradle dependency specifier. - GradleSpecifier m_name; - - /// DEPRECATED URL prefix of the maven repo where the file can be downloaded - QString m_repositoryURL; - - /// DEPRECATED: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined - QString m_absoluteURL; - - /// MultiMC extension - filename override - QString m_filename; - - /// DEPRECATED MultiMC extension - display name - QString m_displayname; - - /** - * MultiMC-specific type hint - modifies how the library is treated - */ - QString m_hint; - - /** - * storage - by default the local libraries folder in multimc, but could be elsewhere - * MultiMC specific, because of FTB. - */ - QString m_storagePrefix; - - /// true if the library had an extract/excludes section (even empty) - bool m_hasExcludes = false; - - /// a list of files that shouldn't be extracted from the library - QStringList m_extractExcludes; - - /// native suffixes per OS - QMap<OpSys, QString> m_nativeClassifiers; - - /// true if the library had a rules section (even empty) - bool applyRules = false; - - /// rules associated with the library - QList<std::shared_ptr<Rule>> m_rules; - - /// MOJANG: container with Mojang style download info - MojangLibraryDownloadInfo::Ptr m_mojangDownloads; -}; diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp deleted file mode 100644 index 75bb4db1..00000000 --- a/api/logic/minecraft/Library_test.cpp +++ /dev/null @@ -1,272 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "minecraft/MojangVersionFormat.h" -#include "minecraft/OneSixVersionFormat.h" -#include "minecraft/Library.h" -#include "net/HttpMetaCache.h" -#include "FileSystem.h" - -class LibraryTest : public QObject -{ - Q_OBJECT -private: - LibraryPtr readMojangJson(const char *file) - { - auto path = QFINDTESTDATA(file); - QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); - auto data = jsonFile.readAll(); - jsonFile.close(); - ProblemContainer problems; - return MojangVersionFormat::libraryFromJson(problems, QJsonDocument::fromJson(data).object(), file); - } - // get absolute path to expected storage, assuming default cache prefix - QStringList getStorage(QString relative) - { - return {FS::PathCombine(cache->getBasePath("libraries"), relative)}; - } -private -slots: - void initTestCase() - { - cache.reset(new HttpMetaCache()); - cache->addBase("libraries", QDir("libraries").absolutePath()); - dataDir = QDir("data").absolutePath(); - } - void test_legacy() - { - Library test("test.package:testname:testversion"); - QCOMPARE(test.artifactPrefix(), QString("test.package:testname")); - QCOMPARE(test.isNative(), false); - - QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString()); - QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar")); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - void test_legacy_url() - { - QStringList failedFiles; - Library test("test.package:testname:testversion"); - test.setRepositoryURL("file://foo/bar"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); - QCOMPARE(downloads.size(), 1); - QCOMPARE(failedFiles, {}); - NetActionPtr dl = downloads[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar")); - } - void test_legacy_url_local_broken() - { - Library test("test.package:testname:testversion"); - QCOMPARE(test.isNative(), false); - QStringList failedFiles; - test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); - QCOMPARE(downloads.size(), 0); - QCOMPARE(failedFiles, {"testname-testversion.jar"}); - } - void test_legacy_url_local_override() - { - Library test("com.paulscode:codecwav:20101023"); - QCOMPARE(test.isNative(), false); - QStringList failedFiles; - 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; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - void test_legacy_native() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux"; - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar")); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - auto dl = dls[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar")); - } - } - void test_legacy_native_arch() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; - test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}"; - test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}"; - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); - } - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); - } - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar")); - } - } - void test_legacy_native_arch_local_override() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; - test.setHint("local"); - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()}); - QCOMPARE(native64, {QFileInfo("data/testname-testversion-linux-64.jar").absoluteFilePath()}); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {"data/testname-testversion-linux-64.jar"}); - } - } - void test_onenine() - { - auto test = readMojangJson("data/lib-simple.json"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar")); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); - } - test->setHint("local"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {}); - } - } - void test_onenine_local_override() - { - auto test = readMojangJson("data/lib-simple.json"); - test->setHint("local"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {}); - } - } - void test_onenine_native() - { - auto test = readMojangJson("data/lib-native.json"); - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, QStringList()); - QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - QStringList failedFiles; - auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - 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-natives-osx.jar")); - } - void test_onenine_native_arch() - { - auto test = readMojangJson("data/lib-native-arch.json"); - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); - QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); - QStringList failedFiles; - auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); - } -private: - std::unique_ptr<HttpMetaCache> cache; - QString dataDir; -}; - -QTEST_GUILESS_MAIN(LibraryTest) - -#include "Library_test.moc" diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp deleted file mode 100644 index dbf9f816..00000000 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ /dev/null @@ -1,1054 +0,0 @@ -#include "MinecraftInstance.h" -#include <minecraft/launch/CreateGameFolders.h> -#include <minecraft/launch/ExtractNatives.h> -#include <minecraft/launch/PrintInstanceInfo.h> -#include <settings/Setting.h> -#include "settings/SettingsObject.h" -#include "Env.h" -#include <MMCStrings.h> -#include <pathmatcher/RegexpMatcher.h> -#include <pathmatcher/MultiMatcher.h> -#include <FileSystem.h> -#include <java/JavaVersion.h> - -#include "launch/LaunchTask.h" -#include "launch/steps/LookupServerAddress.h" -#include "launch/steps/PostLaunchCommand.h" -#include "launch/steps/Update.h" -#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 "minecraft/launch/ReconstructAssets.h" -#include "minecraft/launch/ScanModFolders.h" -#include "minecraft/launch/VerifyJavaInstall.h" -#include "java/launch/CheckJava.h" -#include "java/JavaUtils.h" -#include "meta/Index.h" -#include "meta/VersionList.h" - -#include "mod/ModFolderModel.h" -#include "mod/ResourcePackFolderModel.h" -#include "mod/TexturePackFolderModel.h" -#include "WorldList.h" - -#include "icons/IIconList.h" - -#include <QCoreApplication> -#include "PackProfile.h" -#include "AssetsUtils.h" -#include "MinecraftUpdate.h" -#include "MinecraftLoadAndCheck.h" -#include <minecraft/gameoptions/GameOptions.h> -#include <minecraft/update/FoldersTask.h> - -#define IBUS "@im=ibus" - -// all of this because keeping things compatible with deprecated old settings -// if either of the settings {a, b} is true, this also resolves to true -class OrSetting : public Setting -{ - Q_OBJECT -public: - OrSetting(QString id, std::shared_ptr<Setting> a, std::shared_ptr<Setting> b) - :Setting({id}, false), m_a(a), m_b(b) - { - } - virtual QVariant get() const - { - bool a = m_a->get().toBool(); - bool b = m_b->get().toBool(); - return a || b; - } - virtual void reset() {} - virtual void set(QVariant value) {} -private: - std::shared_ptr<Setting> m_a; - std::shared_ptr<Setting> m_b; -}; - -MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : BaseInstance(globalSettings, settings, rootDir) -{ - // Java Settings - auto javaOverride = m_settings->registerSetting("OverrideJava", false); - auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false); - auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false); - - // combinations - auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride); - auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride); - - m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); - m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); - - // special! - m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation); - - // Window Size - auto windowSetting = m_settings->registerSetting("OverrideWindow", false); - m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting); - - // Memory - auto memorySetting = m_settings->registerSetting("OverrideMemory", false); - m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting); - - // Minecraft launch method - auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); - m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); - - // Native library workarounds - auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); - m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); - m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); - - // Game time - auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); - m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); - m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride); - - // Join server on launch, this does not have a global override - m_settings->registerSetting("JoinServerOnLaunch", false); - m_settings->registerSetting("JoinServerOnLaunchAddress", ""); - - // 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 PackProfile(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::saveNow() -{ - m_components->saveNow(); -} - -QString MinecraftInstance::typeName() const -{ - return "Minecraft"; -} - -std::shared_ptr<PackProfile> MinecraftInstance::getPackProfile() const -{ - return m_components; -} - -QSet<QString> MinecraftInstance::traits() const -{ - auto components = getPackProfile(); - if (!components) - { - return {"version-incomplete"}; - } - auto profile = components->getProfile(); - if (!profile) - { - return {"version-incomplete"}; - } - return profile->getTraits(); -} - -QString MinecraftInstance::gameRoot() const -{ - QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - - if (mcDir.exists() && !dotMCDir.exists()) - return mcDir.filePath(); - else - return dotMCDir.filePath(); -} - -QString MinecraftInstance::binRoot() const -{ - return FS::PathCombine(gameRoot(), "bin"); -} - -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::jarModsDir() const -{ - QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/")); - return jarmods_dir.absolutePath(); -} - -QString MinecraftInstance::loaderModsDir() const -{ - return FS::PathCombine(gameRoot(), "mods"); -} - -QString MinecraftInstance::modsCacheLocation() const -{ - return FS::PathCombine(instanceRoot(), "mods.cache"); -} - -QString MinecraftInstance::coreModsDir() const -{ - return FS::PathCombine(gameRoot(), "coremods"); -} - -QString MinecraftInstance::resourcePacksDir() const -{ - return FS::PathCombine(gameRoot(), "resourcepacks"); -} - -QString MinecraftInstance::texturePacksDir() const -{ - return FS::PathCombine(gameRoot(), "texturepacks"); -} - -QString MinecraftInstance::instanceConfigFolder() const -{ - return FS::PathCombine(gameRoot(), "config"); -} - -QString MinecraftInstance::libDir() const -{ - return FS::PathCombine(gameRoot(), "lib"); -} - -QString MinecraftInstance::worldDir() const -{ - return FS::PathCombine(gameRoot(), "saves"); -} - -QString MinecraftInstance::resourcesDir() const -{ - return FS::PathCombine(gameRoot(), "resources"); -} - -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 -{ - 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 = getPackProfile(); - if (!version) - return list; - auto jarMods = getJarMods(); - if (!jarMods.isEmpty()) - { - list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", - "-Dfml.ignorePatchDiscrepancies=true"}); - } - return list; -} - -QStringList MinecraftInstance::javaArguments() const -{ - QStringList args; - - // custom args go first. we want to override them if we have our own here. - args.append(extraArguments()); - - // OSX dock icon and name -#ifdef Q_OS_MAC - 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 - args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" - "minecraft.exe.heapdump"); -#endif - - 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(); - if(javaVersion.requiresPermGen()) - { - auto permgen = settings()->get("PermGen").toInt(); - if (permgen != 64) - { - args << QString("-XX:PermSize=%1m").arg(permgen); - } - } - - args << "-Duser.language=en"; - - return args; -} - -QMap<QString, QString> MinecraftInstance::getVariables() const -{ - QMap<QString, QString> out; - out.insert("INST_NAME", name()); - out.insert("INST_ID", id()); - out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); - out.insert("INST_MC_DIR", QDir(gameRoot()).absolutePath()); - out.insert("INST_JAVA", settings()->get("JavaPath").toString()); - out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); - return out; -} - -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) -{ - 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 MinecraftInstance::processMinecraftArgs( - AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const -{ - auto profile = m_components->getProfile(); - QString args_pattern = profile->getMinecraftArguments(); - for (auto tweaker : profile->getTweakers()) - { - args_pattern += " --tweakClass " + tweaker; - } - - if (serverToJoin && !serverToJoin->address.isEmpty()) - { - args_pattern += " --server " + serverToJoin->address; - args_pattern += " --port " + QString::number(serverToJoin->port); - } - - 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(gameRoot()).absolutePath(); - token_mapping["game_directory"] = absRootDir; - QString absAssetsDir = QDir("assets/").absolutePath(); - auto assets = profile->getMinecraftAssets(); - token_mapping["game_assets"] = AssetsUtils::getAssetsDir(assets->id, resourcesDir()).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 MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) -{ - 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"; - } - - if (serverToJoin && !serverToJoin->address.isEmpty()) - { - launchScript += "serverAddress " + serverToJoin->address + "\n"; - launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n"; - } - - // generic minecraft params - for (auto param : processMinecraftArgs( - session, - nullptr /* When using a launch script, the server parameters are handled by it*/ - )) - { - 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) - { - launchScript += "cp " + file + "\n"; - } - for(auto file: nativeJars) - { - launchScript += "ext " + file + "\n"; - } - 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, MinecraftServerTargetPtr serverToJoin) -{ - 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) - { - out << "traits " + trait; - } - out << ""; - } - - auto settings = this->settings(); - bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); - bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); - if (nativeOpenAL || nativeGLFW) - { - if (nativeOpenAL) - out << "Using system OpenAL."; - if (nativeGLFW) - out << "Using system GLFW."; - 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) - { - printLibFile(file); - } - out << ""; - out << "Native libraries:"; - for(auto file: nativeJars) - { - printLibFile(file); - } - out << ""; - } - - auto printModList = [&](const QString & label, ModFolderModel & model) { - if(model.size()) - { - out << QString("%1:").arg(label); - auto modList = model.allMods(); - std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) { - auto aName = a.filename().completeBaseName(); - auto bName = b.filename().completeBaseName(); - return aName.localeAwareCompare(bName) < 0; - }); - for(auto & mod: modList) - { - if(mod.type() == Mod::MOD_FOLDER) - { - out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)"; - continue; - } - - if(mod.enabled()) { - out << u8" [✔️] " + mod.filename().completeBaseName(); - } - else { - out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)"; - } - - } - out << ""; - } - }; - - printModList("Mods", *(loaderModList().get())); - printModList("Core Mods", *(coreModList().get())); - - auto & jarMods = 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, serverToJoin); - 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) -{ - if(!session) - { - return QMap<QString, QString>(); - } - auto & sessionRef = *session.get(); - QMap<QString, QString> filter; - auto addToFilter = [&filter](QString key, QString value) - { - if(key.trimmed().size()) - { - filter[key] = value; - } - }; - if (sessionRef.session != "-") - { - addToFilter(sessionRef.session, tr("<SESSION ID>")); - } - addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>")); - addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>")); - addToFilter(sessionRef.uuid, tr("<PROFILE ID>")); - - auto i = sessionRef.u.properties.begin(); - while (i != sessionRef.u.properties.end()) - { - if(i.value().length() <= 3) { - ++i; - continue; - } - addToFilter(i.value(), "<" + i.key().toUpper() + ">"); - ++i; - } - return filter; -} - -MessageLevel::Enum MinecraftInstance::guessLevel(const QString &line, MessageLevel::Enum level) -{ - QRegularExpression re("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]"); - auto match = re.match(line); - if(match.hasMatch()) - { - // New style logs from log4j - QString timestamp = match.captured("timestamp"); - QString levelStr = match.captured("level"); - if(levelStr == "INFO") - level = MessageLevel::Message; - if(levelStr == "WARN") - level = MessageLevel::Warning; - if(levelStr == "ERROR") - level = MessageLevel::Error; - if(levelStr == "FATAL") - level = MessageLevel::Fatal; - if(levelStr == "TRACE" || levelStr == "DEBUG") - level = MessageLevel::Debug; - } - else - { - // Old style forge logs - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || - line.contains("[FINER]") || line.contains("[FINEST]")) - level = MessageLevel::Message; - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - if (line.contains("[WARNING]")) - level = MessageLevel::Warning; - if (line.contains("[DEBUG]")) - level = MessageLevel::Debug; - } - if (line.contains("overwriting existing")) - return MessageLevel::Fatal; - //NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; - if (line.contains("Exception in thread") - || line.contains(QRegularExpression("\\s+at " + javaSymbol)) - || line.contains(QRegularExpression("Caused by: " + javaSymbol)) - || line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) - || line.contains(QRegularExpression("... \\d+ more$")) - ) - return MessageLevel::Error; - return level; -} - -IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher() -{ - auto combined = std::make_shared<MultiMatcher>(); - combined->add(std::make_shared<RegexpMatcher>(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); - combined->add(std::make_shared<RegexpMatcher>("crash-.*\\.txt")); - combined->add(std::make_shared<RegexpMatcher>("IDMap dump.*\\.txt$")); - combined->add(std::make_shared<RegexpMatcher>("ModLoader\\.txt(\\..*)?$")); - return combined; -} - -QString MinecraftInstance::getLogFileRoot() -{ - return gameRoot(); -} - -QString MinecraftInstance::prettifyTimeDuration(int64_t duration) -{ - int seconds = (int) (duration % 60); - duration /= 60; - int minutes = (int) (duration % 60); - duration /= 60; - int hours = (int) (duration % 24); - int days = (int) (duration / 24); - if((hours == 0)&&(days == 0)) - { - return tr("%1m %2s").arg(minutes).arg(seconds); - } - if (days == 0) - { - return tr("%1h %2m").arg(hours).arg(minutes); - } - return tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes); -} - -QString MinecraftInstance::getStatusbarDescription() -{ - QStringList traits; - if (hasVersionBroken()) - { - traits.append(tr("broken")); - } - - QString description; - description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); - if(m_settings->get("ShowGameTime").toBool()) - { - if (lastTimePlayed() > 0) { - description.append(tr(", last played for %1").arg(prettifyTimeDuration(lastTimePlayed()))); - } - - if (totalTimePlayed() > 0) { - description.append(tr(", total played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); - } - } - if(hasCrashed()) - { - description.append(tr(", has crashed.")); - } - 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 MinecraftUpdate(this)); - } - } - return nullptr; -} - -shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) -{ - // FIXME: get rid of shared_from_this ... - auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this())); - auto pptr = process.get(); - - ENV.icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG"); - - // print a header - { - process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC)); - } - - // check java - { - process->appendStep(new CheckJava(pptr)); - } - - // check launch method - QStringList validMethods = {"LauncherPart", "DirectJava"}; - QString method = launchMethod(); - if(!validMethods.contains(method)) - { - process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); - return process; - } - - // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) - { - process->appendStep(new CreateGameFolders(pptr)); - } - - if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool()) - { - QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString(); - serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress))); - } - - if(serverToJoin && serverToJoin->port == 25565) - { - // Resolve server address to join on launch - auto *step = new LookupServerAddress(pptr); - step->setLookupAddress(serverToJoin->address); - step->setOutputAddressPtr(serverToJoin); - process->appendStep(step); - } - - // run pre-launch command if that's needed - if(getPreLaunchCommand().size()) - { - auto step = new PreLaunchCommand(pptr); - step->setWorkingDirectory(gameRoot()); - process->appendStep(step); - } - - // if we aren't in offline mode,. - if(session->status != AuthSession::PlayableOffline) - { - process->appendStep(new ClaimAccount(pptr, session)); - process->appendStep(new Update(pptr, Net::Mode::Online)); - } - else - { - process->appendStep(new Update(pptr, Net::Mode::Offline)); - } - - // if there are any jar mods - { - process->appendStep(new ModMinecraftJar(pptr)); - } - - // Scan mods folders for mods - { - process->appendStep(new ScanModFolders(pptr)); - } - - // print some instance info here... - { - process->appendStep(new PrintInstanceInfo(pptr, session, serverToJoin)); - } - - // extract native jars if needed - { - process->appendStep(new ExtractNatives(pptr)); - } - - // reconstruct assets if needed - { - process->appendStep(new ReconstructAssets(pptr)); - } - - // verify that minimum Java requirements are met - { - process->appendStep(new VerifyJavaInstall(pptr)); - } - - { - // actually launch the game - auto method = launchMethod(); - if(method == "LauncherPart") - { - auto step = new LauncherPartLaunch(pptr); - step->setWorkingDirectory(gameRoot()); - step->setAuthSession(session); - step->setServerToJoin(serverToJoin); - process->appendStep(step); - } - else if (method == "DirectJava") - { - auto step = new DirectJavaLaunch(pptr); - step->setWorkingDirectory(gameRoot()); - step->setAuthSession(session); - step->setServerToJoin(serverToJoin); - process->appendStep(step); - } - } - - // run post-exit command if that's needed - if(getPostExitCommand().size()) - { - auto step = new PostLaunchCommand(pptr); - step->setWorkingDirectory(gameRoot()); - process->appendStep(step); - } - if (session) - { - process->setCensorFilter(createCensorFilterFromSession(session)); - } - m_launchProcess = process; - emit launchTaskChanged(m_launchProcess); - return m_launchProcess; -} - -QString MinecraftInstance::launchMethod() -{ - return m_settings->get("MCLaunchMethod").toString(); -} - -JavaVersion MinecraftInstance::getJavaVersion() const -{ - return JavaVersion(settings()->get("JavaVersion").toString()); -} - -std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const -{ - if (!m_loader_mod_list) - { - m_loader_mod_list.reset(new ModFolderModel(loaderModsDir())); - m_loader_mod_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); - } - return m_loader_mod_list; -} - -std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const -{ - if (!m_core_mod_list) - { - m_core_mod_list.reset(new ModFolderModel(coreModsDir())); - m_core_mod_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); - } - return m_core_mod_list; -} - -std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const -{ - if (!m_resource_pack_list) - { - m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir())); - m_resource_pack_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction); - } - return m_resource_pack_list; -} - -std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const -{ - if (!m_texture_pack_list) - { - m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir())); - m_texture_pack_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction); - } - 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; -} - -std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel() const -{ - if (!m_game_options) - { - m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt"))); - } - return m_game_options; -} - -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 deleted file mode 100644 index 05600797..00000000 --- a/api/logic/minecraft/MinecraftInstance.h +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once -#include "BaseInstance.h" -#include <java/JavaVersion.h> -#include "minecraft/mod/Mod.h" -#include <QProcess> -#include <QDir> -#include "multimc_logic_export.h" -#include "minecraft/launch/MinecraftServerTarget.h" - -class ModFolderModel; -class WorldList; -class GameOptions; -class LaunchStep; -class PackProfile; - -class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance -{ - Q_OBJECT -public: - MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual ~MinecraftInstance() {}; - virtual void saveNow() override; - - // FIXME: remove - QString typeName() const override; - // FIXME: remove - QSet<QString> traits() const override; - - bool canEdit() const override - { - return true; - } - - bool canExport() const override - { - return true; - } - - ////// Directories and files ////// - QString jarModsDir() const; - QString resourcePacksDir() const; - QString texturePacksDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString modsCacheLocation() const; - QString libDir() const; - QString worldDir() const; - QString resourcesDir() const; - QDir jarmodsPath() const; - QDir librariesPath() const; - QDir versionsPath() const; - QString instanceConfigFolder() const override; - - // Path to the instance's minecraft directory. - QString gameRoot() const override; - - // Path to the instance's minecraft bin directory. - QString binRoot() const; - - // where to put the natives during/before launch - QString getNativePath() const; - - // where the instance-local libraries should be - QString getLocalLibraryPath() const; - - - ////// Profile management ////// - std::shared_ptr<PackProfile> getPackProfile() const; - - ////// Mod Lists ////// - std::shared_ptr<ModFolderModel> loaderModList() const; - std::shared_ptr<ModFolderModel> coreModList() const; - std::shared_ptr<ModFolderModel> resourcePackList() const; - std::shared_ptr<ModFolderModel> texturePackList() const; - std::shared_ptr<WorldList> worldList() const; - std::shared_ptr<GameOptions> gameOptionsModel() const; - - ////// Launch stuff ////// - shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override; - shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override; - QStringList extraArguments() const override; - QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override; - QList<Mod> getJarMods() const; - QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin); - /// get arguments passed to java - QStringList javaArguments() const; - - /// get variables for launch command variable substitution/environment - QMap<QString, QString> getVariables() const override; - - /// create an environment for launching processes - QProcessEnvironment createEnvironment() override; - - /// guess log level from a line of minecraft log - MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; - - IPathMatcher::Ptr getLogFileMatcher() override; - - QString getLogFileRoot() override; - - QString getStatusbarDescription() override; - - // FIXME: remove - virtual QStringList getClassPath() const; - // FIXME: remove - virtual QStringList getNativeJars() const; - // FIXME: remove - virtual QString getMainClass() const; - - // FIXME: remove - virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const; - - virtual JavaVersion getJavaVersion() const; - -protected: - QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session); - QStringList validLaunchMethods(); - QString launchMethod(); - -private: - QString prettifyTimeDuration(int64_t duration); - -protected: // data - std::shared_ptr<PackProfile> m_components; - mutable std::shared_ptr<ModFolderModel> m_loader_mod_list; - mutable std::shared_ptr<ModFolderModel> m_core_mod_list; - mutable std::shared_ptr<ModFolderModel> m_resource_pack_list; - mutable std::shared_ptr<ModFolderModel> m_texture_pack_list; - mutable std::shared_ptr<WorldList> m_world_list; - mutable std::shared_ptr<GameOptions> m_game_options; -}; - -typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr; diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.cpp b/api/logic/minecraft/MinecraftLoadAndCheck.cpp deleted file mode 100644 index 79b0c484..00000000 --- a/api/logic/minecraft/MinecraftLoadAndCheck.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "MinecraftLoadAndCheck.h" -#include "MinecraftInstance.h" -#include "PackProfile.h" - -MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) -{ -} - -void MinecraftLoadAndCheck::executeTask() -{ - // add offline metadata load task - auto components = m_inst->getPackProfile(); - 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() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; - } - emitSucceeded(); -} - -void MinecraftLoadAndCheck::subtaskFailed(QString error) -{ - if(isFinished()) - { - qCritical() << "MinecraftUpdate: 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 deleted file mode 100644 index 3435b52b..00000000 --- a/api/logic/minecraft/MinecraftLoadAndCheck.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright 2013-2021 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); - virtual ~MinecraftLoadAndCheck() {}; - 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/MinecraftUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp deleted file mode 100644 index 8f1565b0..00000000 --- a/api/logic/minecraft/MinecraftUpdate.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/* Copyright 2013-2021 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 "Env.h" -#include "MinecraftUpdate.h" -#include "MinecraftInstance.h" - -#include <QFile> -#include <QFileInfo> -#include <QTextStream> -#include <QDataStream> - -#include "BaseInstance.h" -#include "minecraft/PackProfile.h" -#include "minecraft/Library.h" -#include <FileSystem.h> - -#include "update/FoldersTask.h" -#include "update/LibrariesTask.h" -#include "update/FMLLibrariesTask.h" -#include "update/AssetUpdateTask.h" - -#include <meta/Index.h> -#include <meta/Version.h> - -MinecraftUpdate::MinecraftUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) -{ -} - -void MinecraftUpdate::executeTask() -{ - m_tasks.clear(); - // create folders - { - m_tasks.append(std::make_shared<FoldersTask>(m_inst)); - } - - // add metadata update task if necessary - { - auto components = m_inst->getPackProfile(); - components->reload(Net::Mode::Online); - auto task = components->getCurrentTask(); - if(task) - { - m_tasks.append(task.unwrap()); - } - } - - // libraries download - { - m_tasks.append(std::make_shared<LibrariesTask>(m_inst)); - } - - // FML libraries download and copy into the instance - { - m_tasks.append(std::make_shared<FMLLibrariesTask>(m_inst)); - } - - // assets update - { - m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst)); - } - - if(!m_preFailure.isEmpty()) - { - emitFailed(m_preFailure); - return; - } - next(); -} - -void MinecraftUpdate::next() -{ - if(m_abort) - { - emitFailed(tr("Aborted by user.")); - return; - } - if(m_failed_out_of_order) - { - emitFailed(m_fail_reason); - return; - } - m_currentTask ++; - if(m_currentTask > 0) - { - auto task = m_tasks[m_currentTask - 1]; - disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); - disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); - disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); - disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); - } - if(m_currentTask == m_tasks.size()) - { - emitSucceeded(); - return; - } - auto task = m_tasks[m_currentTask]; - // if the task is already finished by the time we look at it, skip it - if(task->isFinished()) - { - qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get(); - next(); - } - connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); - connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); - connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); - connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); - // if the task is already running, do not start it again - if(!task->isRunning()) - { - task->start(); - } -} - -void MinecraftUpdate::subtaskSucceeded() -{ - if(isFinished()) - { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if(senderTask != currentTask) - { - qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order."; - return; - } - next(); -} - -void MinecraftUpdate::subtaskFailed(QString error) -{ - if(isFinished()) - { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if(senderTask != currentTask) - { - qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order."; - m_failed_out_of_order = true; - m_fail_reason = error; - return; - } - emitFailed(error); -} - - -bool MinecraftUpdate::abort() -{ - if(!m_abort) - { - m_abort = true; - auto task = m_tasks[m_currentTask]; - if(task->canAbort()) - { - return task->abort(); - } - } - return true; -} - -bool MinecraftUpdate::canAbort() const -{ - return true; -} diff --git a/api/logic/minecraft/MinecraftUpdate.h b/api/logic/minecraft/MinecraftUpdate.h deleted file mode 100644 index fadebff9..00000000 --- a/api/logic/minecraft/MinecraftUpdate.h +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright 2013-2021 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" -#include <quazip.h> - -class MinecraftVersion; -class MinecraftInstance; - -class MinecraftUpdate : public Task -{ - Q_OBJECT -public: - explicit MinecraftUpdate(MinecraftInstance *inst, QObject *parent = 0); - virtual ~MinecraftUpdate() {}; - - void executeTask() override; - bool canAbort() const override; - -private -slots: - bool abort() override; - void subtaskSucceeded(); - void subtaskFailed(QString error); - -private: - void next(); - -private: - 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/MojangDownloadInfo.h b/api/logic/minecraft/MojangDownloadInfo.h deleted file mode 100644 index 88f87287..00000000 --- a/api/logic/minecraft/MojangDownloadInfo.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once -#include <QString> -#include <QMap> -#include <memory> - -struct MojangDownloadInfo -{ - // types - typedef std::shared_ptr<MojangDownloadInfo> Ptr; - - // data - /// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested! - QString path; - /// absolute URL of this file - QString url; - /// sha-1 checksum of the file - QString sha1; - /// size of the file in bytes - int size; -}; - - - -struct MojangLibraryDownloadInfo -{ - MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact): artifact(artifact) {}; - MojangLibraryDownloadInfo() {}; - - // types - typedef std::shared_ptr<MojangLibraryDownloadInfo> Ptr; - - // methods - MojangDownloadInfo *getDownloadInfo(QString classifier) - { - if (classifier.isNull()) - { - return artifact.get(); - } - - return classifiers[classifier].get(); - } - - // data - MojangDownloadInfo::Ptr artifact; - QMap<QString, MojangDownloadInfo::Ptr> classifiers; -}; - - - -struct MojangAssetIndexInfo : public MojangDownloadInfo -{ - // types - typedef std::shared_ptr<MojangAssetIndexInfo> Ptr; - - // methods - MojangAssetIndexInfo() - { - } - - MojangAssetIndexInfo(QString id) - { - this->id = id; - // HACK: ignore assets from other version files than Minecraft - // workaround for stupid assets issue caused by amazon: - // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ - if(id == "legacy") - { - url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json"; - } - // HACK - else - { - url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json"; - } - known = false; - } - - // data - int totalSize; - QString id; - bool known = true; -}; diff --git a/api/logic/minecraft/MojangVersionFormat.cpp b/api/logic/minecraft/MojangVersionFormat.cpp deleted file mode 100644 index f9cb2228..00000000 --- a/api/logic/minecraft/MojangVersionFormat.cpp +++ /dev/null @@ -1,383 +0,0 @@ -#include "MojangVersionFormat.h" -#include "OneSixVersionFormat.h" -#include "MojangDownloadInfo.h" - -#include "Json.h" -using namespace Json; -#include "ParseUtils.h" - -static const int CURRENT_MINIMUM_LAUNCHER_VERSION = 18; - -static MojangAssetIndexInfo::Ptr assetIndexFromJson (const QJsonObject &obj); -static MojangDownloadInfo::Ptr downloadInfoFromJson (const QJsonObject &obj); -static MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson (const QJsonObject &libObj); -static QJsonObject assetIndexToJson (MojangAssetIndexInfo::Ptr assetidxinfo); -static QJsonObject libDownloadInfoToJson (MojangLibraryDownloadInfo::Ptr libinfo); -static QJsonObject downloadInfoToJson (MojangDownloadInfo::Ptr info); - -namespace Bits -{ -static void readString(const QJsonObject &root, const QString &key, QString &variable) -{ - if (root.contains(key)) - { - variable = requireString(root.value(key)); - } -} - -static void readDownloadInfo(MojangDownloadInfo::Ptr out, const QJsonObject &obj) -{ - // optional, not used - readString(obj, "path", out->path); - // required! - out->sha1 = requireString(obj, "sha1"); - out->url = requireString(obj, "url"); - out->size = requireInteger(obj, "size"); -} - -static void readAssetIndex(MojangAssetIndexInfo::Ptr out, const QJsonObject &obj) -{ - out->totalSize = requireInteger(obj, "totalSize"); - out->id = requireString(obj, "id"); - // out->known = true; -} -} - -MojangDownloadInfo::Ptr downloadInfoFromJson(const QJsonObject &obj) -{ - auto out = std::make_shared<MojangDownloadInfo>(); - Bits::readDownloadInfo(out, obj); - return out; -} - -MojangAssetIndexInfo::Ptr assetIndexFromJson(const QJsonObject &obj) -{ - auto out = std::make_shared<MojangAssetIndexInfo>(); - Bits::readDownloadInfo(out, obj); - Bits::readAssetIndex(out, obj); - return out; -} - -QJsonObject downloadInfoToJson(MojangDownloadInfo::Ptr info) -{ - QJsonObject out; - if(!info->path.isNull()) - { - out.insert("path", info->path); - } - out.insert("sha1", info->sha1); - out.insert("size", info->size); - out.insert("url", info->url); - return out; -} - -MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson(const QJsonObject &libObj) -{ - auto out = std::make_shared<MojangLibraryDownloadInfo>(); - auto dlObj = requireObject(libObj.value("downloads")); - if(dlObj.contains("artifact")) - { - out->artifact = downloadInfoFromJson(requireObject(dlObj, "artifact")); - } - if(dlObj.contains("classifiers")) - { - auto classifiersObj = requireObject(dlObj, "classifiers"); - for(auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++) - { - auto classifier = iter.key(); - auto classifierObj = requireObject(iter.value()); - out->classifiers[classifier] = downloadInfoFromJson(classifierObj); - } - } - return out; -} - -QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo) -{ - QJsonObject out; - if(libinfo->artifact) - { - out.insert("artifact", downloadInfoToJson(libinfo->artifact)); - } - if(libinfo->classifiers.size()) - { - QJsonObject classifiersOut; - for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) - { - classifiersOut.insert(iter.key(), downloadInfoToJson(iter.value())); - } - out.insert("classifiers", classifiersOut); - } - return out; -} - -QJsonObject assetIndexToJson(MojangAssetIndexInfo::Ptr info) -{ - QJsonObject out; - if(!info->path.isNull()) - { - out.insert("path", info->path); - } - out.insert("sha1", info->sha1); - out.insert("size", info->size); - out.insert("url", info->url); - out.insert("totalSize", info->totalSize); - out.insert("id", info->id); - return out; -} - -void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFile *out) -{ - Bits::readString(in, "id", out->minecraftVersion); - Bits::readString(in, "mainClass", out->mainClass); - Bits::readString(in, "minecraftArguments", out->minecraftArguments); - if(out->minecraftArguments.isEmpty()) - { - QString processArguments; - Bits::readString(in, "processArguments", processArguments); - QString toCompare = processArguments.toLower(); - if (toCompare == "legacy") - { - out->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; - } - else if (!toCompare.isEmpty()) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments)); - } - } - Bits::readString(in, "type", out->type); - - Bits::readString(in, "assets", out->assets); - if(in.contains("assetIndex")) - { - out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex")); - } - else if (!out->assets.isNull()) - { - out->mojangAssetIndex = std::make_shared<MojangAssetIndexInfo>(out->assets); - } - - out->releaseTime = timeFromS3Time(in.value("releaseTime").toString("")); - out->updateTime = timeFromS3Time(in.value("time").toString("")); - - if (in.contains("minimumLauncherVersion")) - { - out->minimumLauncherVersion = requireInteger(in.value("minimumLauncherVersion")); - if (out->minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) - { - out->addProblem( - ProblemSeverity::Warning, - QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by MultiMC (%2). It might not work properly!") - .arg(out->minimumLauncherVersion) - .arg(CURRENT_MINIMUM_LAUNCHER_VERSION)); - } - } - if(in.contains("downloads")) - { - auto downloadsObj = requireObject(in, "downloads"); - for(auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++) - { - auto classifier = iter.key(); - auto classifierObj = requireObject(iter.value()); - out->mojangDownloads[classifier] = downloadInfoFromJson(classifierObj); - } - } -} - -VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename) -{ - VersionFilePtr out(new VersionFile()); - if (doc.isEmpty() || doc.isNull()) - { - throw JSONValidationError(filename + " is empty or null"); - } - if (!doc.isObject()) - { - throw JSONValidationError(filename + " is not an object"); - } - - QJsonObject root = doc.object(); - - readVersionProperties(root, out.get()); - - out->name = "Minecraft"; - out->uid = "net.minecraft"; - out->version = out->minecraftVersion; - // out->filename = filename; - - - if (root.contains("libraries")) - { - for (auto libVal : requireArray(root.value("libraries"))) - { - auto libObj = requireObject(libVal); - - auto lib = MojangVersionFormat::libraryFromJson(*out, libObj, filename); - out->libraries.append(lib); - } - } - return out; -} - -void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObject& out) -{ - writeString(out, "id", in->minecraftVersion); - writeString(out, "mainClass", in->mainClass); - writeString(out, "minecraftArguments", in->minecraftArguments); - writeString(out, "type", in->type); - if(!in->releaseTime.isNull()) - { - writeString(out, "releaseTime", timeToS3Time(in->releaseTime)); - } - if(!in->updateTime.isNull()) - { - writeString(out, "time", timeToS3Time(in->updateTime)); - } - if(in->minimumLauncherVersion != -1) - { - out.insert("minimumLauncherVersion", in->minimumLauncherVersion); - } - writeString(out, "assets", in->assets); - if(in->mojangAssetIndex && in->mojangAssetIndex->known) - { - out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex)); - } - if(in->mojangDownloads.size()) - { - QJsonObject downloadsOut; - for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) - { - downloadsOut.insert(iter.key(), downloadInfoToJson(iter.value())); - } - out.insert("downloads", downloadsOut); - } -} - -QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch) -{ - QJsonObject root; - writeVersionProperties(patch.get(), root); - if (!patch->libraries.isEmpty()) - { - QJsonArray array; - for (auto value: patch->libraries) - { - array.append(MojangVersionFormat::libraryToJson(value.get())); - } - root.insert("libraries", array); - } - - // write the contents to a json document. - { - QJsonDocument out; - out.setObject(root); - return out; - } -} - -LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename) -{ - LibraryPtr out(new Library()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field"); - } - auto rawName = libObj.value("name").toString(); - out->m_name = rawName; - if(!out->m_name.valid()) { - problems.addProblem(ProblemSeverity::Error, QObject::tr("Library %1 name is broken and cannot be processed.").arg(rawName)); - } - - Bits::readString(libObj, "url", out->m_repositoryURL); - if (libObj.contains("extract")) - { - out->m_hasExcludes = true; - auto extractObj = requireObject(libObj.value("extract")); - for (auto excludeVal : requireArray(extractObj.value("exclude"))) - { - out->m_extractExcludes.append(requireString(excludeVal)); - } - } - if (libObj.contains("natives")) - { - QJsonObject nativesObj = requireObject(libObj.value("natives")); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) - { - if (!it.value().isString()) - { - qWarning() << filename << "contains an invalid native (skipping)"; - } - OpSys opSys = OpSys_fromString(it.key()); - if (opSys != Os_Other) - { - out->m_nativeClassifiers[opSys] = it.value().toString(); - } - } - } - if (libObj.contains("rules")) - { - out->applyRules = true; - out->m_rules = rulesFromJsonV4(libObj); - } - if (libObj.contains("downloads")) - { - out->m_mojangDownloads = libDownloadInfoFromJson(libObj); - } - return out; -} - -QJsonObject MojangVersionFormat::libraryToJson(Library *library) -{ - QJsonObject libRoot; - libRoot.insert("name", library->m_name.serialize()); - if (!library->m_repositoryURL.isEmpty()) - { - libRoot.insert("url", library->m_repositoryURL); - } - if (library->isNative()) - { - QJsonObject nativeList; - auto iter = library->m_nativeClassifiers.begin(); - while (iter != library->m_nativeClassifiers.end()) - { - nativeList.insert(OpSys_toString(iter.key()), iter.value()); - iter++; - } - libRoot.insert("natives", nativeList); - if (library->m_extractExcludes.size()) - { - QJsonArray excludes; - QJsonObject extract; - for (auto exclude : library->m_extractExcludes) - { - excludes.append(exclude); - } - extract.insert("exclude", excludes); - libRoot.insert("extract", extract); - } - } - if (library->m_rules.size()) - { - QJsonArray allRules; - for (auto &rule : library->m_rules) - { - QJsonObject ruleObj = rule->toJson(); - allRules.append(ruleObj); - } - libRoot.insert("rules", allRules); - } - if(library->m_mojangDownloads) - { - auto downloadsObj = libDownloadInfoToJson(library->m_mojangDownloads); - libRoot.insert("downloads", downloadsObj); - } - return libRoot; -} diff --git a/api/logic/minecraft/MojangVersionFormat.h b/api/logic/minecraft/MojangVersionFormat.h deleted file mode 100644 index 2871dae4..00000000 --- a/api/logic/minecraft/MojangVersionFormat.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include <minecraft/VersionFile.h> -#include <minecraft/Library.h> -#include <QJsonDocument> -#include <ProblemProvider.h> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT MojangVersionFormat -{ -friend class OneSixVersionFormat; -protected: - // does not include libraries - static void readVersionProperties(const QJsonObject& in, VersionFile* out); - // does not include libraries - static void writeVersionProperties(const VersionFile* in, QJsonObject& out); -public: - // version files / profile patches - static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename); - static QJsonDocument versionFileToJson(const VersionFilePtr &patch); - - // libraries - static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); - static QJsonObject libraryToJson(Library *library); -}; diff --git a/api/logic/minecraft/MojangVersionFormat_test.cpp b/api/logic/minecraft/MojangVersionFormat_test.cpp deleted file mode 100644 index 9d095340..00000000 --- a/api/logic/minecraft/MojangVersionFormat_test.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include <QTest> -#include <QDebug> -#include "TestUtil.h" - -#include "minecraft/MojangVersionFormat.h" - -class MojangVersionFormatTest : public QObject -{ - Q_OBJECT - - static QJsonDocument readJson(const char *file) - { - auto path = QFINDTESTDATA(file); - QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); - auto data = jsonFile.readAll(); - jsonFile.close(); - return QJsonDocument::fromJson(data); - } - static void writeJson(const char *file, QJsonDocument doc) - { - QFile jsonFile(file); - jsonFile.open(QIODevice::WriteOnly | QIODevice::Text); - auto data = doc.toJson(QJsonDocument::Indented); - qDebug() << QString::fromUtf8(data); - jsonFile.write(data); - jsonFile.close(); - } - -private -slots: - void test_Through_Simple() - { - QJsonDocument doc = readJson("data/1.9-simple.json"); - auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json"); - auto doc2 = MojangVersionFormat::versionFileToJson(vfile); - writeJson("1.9-simple-passthorugh.json", doc2); - - QCOMPARE(doc.toJson(), doc2.toJson()); - } - - void test_Through() - { - QJsonDocument doc = readJson("data/1.9.json"); - auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json"); - auto doc2 = MojangVersionFormat::versionFileToJson(vfile); - writeJson("1.9-passthorugh.json", doc2); - QCOMPARE(doc.toJson(), doc2.toJson()); - } -}; - -QTEST_GUILESS_MAIN(MojangVersionFormatTest) - -#include "MojangVersionFormat_test.moc" - diff --git a/api/logic/minecraft/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp deleted file mode 100644 index 0329d70e..00000000 --- a/api/logic/minecraft/OneSixVersionFormat.cpp +++ /dev/null @@ -1,391 +0,0 @@ -#include "OneSixVersionFormat.h" -#include <Json.h> -#include "minecraft/ParseUtils.h" -#include <minecraft/MojangVersionFormat.h> - -using namespace Json; - -static void readString(const QJsonObject &root, const QString &key, QString &variable) -{ - if (root.contains(key)) - { - variable = requireString(root.value(key)); - } -} - -LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename) -{ - LibraryPtr out = MojangVersionFormat::libraryFromJson(problems, libObj, filename); - readString(libObj, "MMC-hint", out->m_hint); - readString(libObj, "MMC-absulute_url", out->m_absoluteURL); - readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL); - readString(libObj, "MMC-filename", out->m_filename); - readString(libObj, "MMC-displayname", out->m_displayname); - return out; -} - -QJsonObject OneSixVersionFormat::libraryToJson(Library *library) -{ - QJsonObject libRoot = MojangVersionFormat::libraryToJson(library); - if (library->m_absoluteURL.size()) - libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL); - if (library->m_hint.size()) - libRoot.insert("MMC-hint", library->m_hint); - if (library->m_filename.size()) - libRoot.insert("MMC-filename", library->m_filename); - if (library->m_displayname.size()) - libRoot.insert("MMC-displayname", library->m_displayname); - return libRoot; -} - -VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder) -{ - VersionFilePtr out(new VersionFile()); - if (doc.isEmpty() || doc.isNull()) - { - throw JSONValidationError(filename + " is empty or null"); - } - if (!doc.isObject()) - { - throw JSONValidationError(filename + " is not an object"); - } - - 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")) - { - out->order = requireInteger(root.value("order")); - } - else - { - // FIXME: evaluate if we don't want to throw exceptions here instead - qCritical() << filename << "doesn't contain an order field"; - } - } - - out->name = root.value("name").toString(); - - if(root.contains("uid")) - { - out->uid = root.value("uid").toString(); - } - else - { - out->uid = root.value("fileId").toString(); - } - - out->version = root.value("version").toString(); - - MojangVersionFormat::readVersionProperties(root, out.get()); - - // added for legacy Minecraft window embedding, TODO: remove - readString(root, "appletClass", out->appletClass); - - if (root.contains("+tweakers")) - { - for (auto tweakerVal : requireArray(root.value("+tweakers"))) - { - out->addTweakers.append(requireString(tweakerVal)); - } - } - - if (root.contains("+traits")) - { - for (auto tweakerVal : requireArray(root.value("+traits"))) - { - out->traits.insert(requireString(tweakerVal)); - } - } - - - if (root.contains("jarMods")) - { - for (auto libVal : requireArray(root.value("jarMods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::jarModFromJson(*out, libObj, filename); - // and add to jar mods - out->jarMods.append(lib); - } - } - else if (root.contains("+jarMods")) // DEPRECATED: old style '+jarMods' are only here for backwards compatibility - { - for (auto libVal : requireArray(root.value("+jarMods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::plusJarModFromJson(*out, libObj, filename, out->name); - // and add to jar mods - out->jarMods.append(lib); - } - } - - if (root.contains("mods")) - { - for (auto libVal : requireArray(root.value("mods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::modFromJson(*out, libObj, filename); - // and add to jar mods - out->mods.append(lib); - } - } - - auto readLibs = [&](const char * which, QList<LibraryPtr> & outList) - { - for (auto libVal : requireArray(root.value(which))) - { - QJsonObject libObj = requireObject(libVal); - // parse the library - auto lib = libraryFromJson(*out, libObj, filename); - outList.append(lib); - } - }; - bool hasPlusLibs = root.contains("+libraries"); - bool hasLibs = root.contains("libraries"); - if (hasPlusLibs && hasLibs) - { - out->addProblem(ProblemSeverity::Warning, - QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported.")); - readLibs("libraries", out->libraries); - readLibs("+libraries", out->libraries); - } - else if (hasLibs) - { - readLibs("libraries", out->libraries); - } - else if(hasPlusLibs) - { - readLibs("+libraries", out->libraries); - } - - if(root.contains("mavenFiles")) { - readLibs("mavenFiles", out->mavenFiles); - } - - // if we have mainJar, just use it - if(root.contains("mainJar")) - { - QJsonObject libObj = requireObject(root, "mainJar"); - out->mainJar = libraryFromJson(*out, libObj, filename); - } - // else reconstruct it from downloads and id ... if that's available - else if(!out->minecraftVersion.isEmpty()) - { - auto lib = std::make_shared<Library>(); - lib->setRawName(GradleSpecifier(QString("com.mojang:minecraft:%1:client").arg(out->minecraftVersion))); - // we have a reliable client download, use it. - if(out->mojangDownloads.contains("client")) - { - auto LibDLInfo = std::make_shared<MojangLibraryDownloadInfo>(); - LibDLInfo->artifact = out->mojangDownloads["client"]; - lib->setMojangDownloadInfo(LibDLInfo); - } - // we got nothing... - else - { - out->addProblem( - ProblemSeverity::Error, - QObject::tr("URL for the main jar could not be determined - Mojang removed the server that we used as fallback.") - ); - } - 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")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'")); - } - if (root.contains("-libraries")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-libraries'")); - } - if (root.contains("-tweakers")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-tweakers'")); - } - if (root.contains("-minecraftArguments")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-minecraftArguments'")); - } - if (root.contains("+minecraftArguments")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '+minecraftArguments'")); - } - return out; -} - -QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch) -{ - QJsonObject root; - writeString(root, "name", patch->name); - - writeString(root, "uid", patch->uid); - - writeString(root, "version", patch->version); - - Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease); - - MojangVersionFormat::writeVersionProperties(patch.get(), root); - - if(patch->mainJar) - { - root.insert("mainJar", libraryToJson(patch->mainJar.get())); - } - writeString(root, "appletClass", patch->appletClass); - writeStringList(root, "+tweakers", patch->addTweakers); - writeStringList(root, "+traits", patch->traits.toList()); - if (!patch->libraries.isEmpty()) - { - QJsonArray array; - for (auto value: patch->libraries) - { - array.append(OneSixVersionFormat::libraryToJson(value.get())); - } - root.insert("libraries", array); - } - if (!patch->mavenFiles.isEmpty()) - { - QJsonArray array; - for (auto value: patch->mavenFiles) - { - array.append(OneSixVersionFormat::libraryToJson(value.get())); - } - root.insert("mavenFiles", array); - } - if (!patch->jarMods.isEmpty()) - { - QJsonArray array; - for (auto value: patch->jarMods) - { - array.append(OneSixVersionFormat::jarModtoJson(value.get())); - } - root.insert("jarMods", array); - } - if (!patch->mods.isEmpty()) - { - QJsonArray array; - for (auto value: patch->jarMods) - { - array.append(OneSixVersionFormat::modtoJson(value.get())); - } - 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; - out.setObject(root); - return out; - } -} - -LibraryPtr OneSixVersionFormat::plusJarModFromJson( - ProblemContainer & problems, - const QJsonObject &libObj, - const QString &filename, - const QString &originalName -) { - LibraryPtr out(new Library()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + - "contains a jarmod that doesn't have a 'name' field"); - } - - // just make up something unique on the spot for the library name. - auto uuid = QUuid::createUuid(); - QString id = uuid.toString().remove('{').remove('}'); - out->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); - - // filename override is the old name - out->setFilename(libObj.value("name").toString()); - - // it needs to be local, it is stored in the instance jarmods folder - out->setHint("local"); - - // read the original name if present - some versions did not set it - // it is the original jar mod filename before it got renamed at the point of addition - auto displayName = libObj.value("originalName").toString(); - if(displayName.isEmpty()) - { - auto fixed = originalName; - fixed.remove(" (jar mod)"); - out->setDisplayName(fixed); - } - else - { - out->setDisplayName(displayName); - } - return out; -} - -LibraryPtr OneSixVersionFormat::jarModFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename) -{ - return libraryFromJson(problems, libObj, filename); -} - - -QJsonObject OneSixVersionFormat::jarModtoJson(Library *jarmod) -{ - return libraryToJson(jarmod); -} - -LibraryPtr OneSixVersionFormat::modFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename) -{ - return libraryFromJson(problems, libObj, filename); -} - -QJsonObject OneSixVersionFormat::modtoJson(Library *jarmod) -{ - return libraryToJson(jarmod); -} diff --git a/api/logic/minecraft/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h deleted file mode 100644 index 1a091d88..00000000 --- a/api/logic/minecraft/OneSixVersionFormat.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include <minecraft/VersionFile.h> -#include <minecraft/PackProfile.h> -#include <minecraft/Library.h> -#include <QJsonDocument> -#include <ProblemProvider.h> - -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); - - // libraries - static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); - static QJsonObject libraryToJson(Library *library); - - // DEPRECATED: old 'plus' jar mods generated by the application - static LibraryPtr plusJarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename, const QString &originalName); - - // new jar mods derived from libraries - static LibraryPtr jarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); - static QJsonObject jarModtoJson(Library * jarmod); - - // mods, also derived from libraries - static LibraryPtr modFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); - static QJsonObject modtoJson(Library * jarmod); -}; diff --git a/api/logic/minecraft/OpSys.cpp b/api/logic/minecraft/OpSys.cpp deleted file mode 100644 index f6a4ed1c..00000000 --- a/api/logic/minecraft/OpSys.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2013-2021 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 "OpSys.h" - -OpSys OpSys_fromString(QString name) -{ - if (name == "linux") - return Os_Linux; - if (name == "windows") - return Os_Windows; - if (name == "osx") - return Os_OSX; - return Os_Other; -} - -QString OpSys_toString(OpSys name) -{ - switch (name) - { - case Os_Linux: - return "linux"; - case Os_OSX: - return "osx"; - case Os_Windows: - return "windows"; - default: - return "other"; - } -}
\ No newline at end of file diff --git a/api/logic/minecraft/OpSys.h b/api/logic/minecraft/OpSys.h deleted file mode 100644 index 63c750b1..00000000 --- a/api/logic/minecraft/OpSys.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2013-2021 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> -enum OpSys -{ - Os_Windows, - Os_Linux, - Os_OSX, - Os_Other -}; - -OpSys OpSys_fromString(QString); -QString OpSys_toString(OpSys); - -#ifdef Q_OS_WIN32 -#define currentSystem Os_Windows -#else -#ifdef Q_OS_MAC -#define currentSystem Os_OSX -#else -#define currentSystem Os_Linux -#endif -#endif
\ No newline at end of file diff --git a/api/logic/minecraft/PackProfile.cpp b/api/logic/minecraft/PackProfile.cpp deleted file mode 100644 index f6918116..00000000 --- a/api/logic/minecraft/PackProfile.cpp +++ /dev/null @@ -1,1225 +0,0 @@ -/* Copyright 2013-2021 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 "PackProfile.h" -#include "PackProfile_p.h" -#include "ComponentUpdateTask.h" - -PackProfile::PackProfile(MinecraftInstance * instance) - : QAbstractListModel() -{ - d.reset(new PackProfileData); - d->m_instance = instance; - d->m_saveTimer.setSingleShot(true); - d->m_saveTimer.setInterval(5000); - d->interactionDisabled = instance->isRunning(); - connect(d->m_instance, &BaseInstance::runningStatusChanged, this, &PackProfile::disableInteraction); - connect(&d->m_saveTimer, &QTimer::timeout, this, &PackProfile::save_internal); -} - -PackProfile::~PackProfile() -{ - 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(PackProfile * 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 savePackProfile(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 loadPackProfile(PackProfile * 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 (const 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 PackProfile::saveNow() -{ - if(saveIsScheduled()) - { - d->m_saveTimer.stop(); - save_internal(); - } -} - -bool PackProfile::saveIsScheduled() const -{ - return d->dirty; -} - -void PackProfile::buildingFromScratch() -{ - d->loaded = true; - d->dirty = true; -} - -void PackProfile::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 PackProfile::componentsFilePath() const -{ - return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); -} - -QString PackProfile::patchesPattern() const -{ - return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json"); -} - -QString PackProfile::patchFilePathForUid(const QString& uid) const -{ - return patchesPattern().arg(uid); -} - -void PackProfile::save_internal() -{ - qDebug() << "Component list save performed now for" << d->m_instance->name(); - auto filename = componentsFilePath(); - savePackProfile(filename, d->components); - d->dirty = false; -} - -bool PackProfile::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(!loadPackProfile(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, &PackProfile::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, &PackProfile::componentDataChanged); - d->components.append(component); - d->componentIndex[component->m_uid] = component; - } - endResetModel(); - d->loaded = true; - return true; - } -} - -void PackProfile::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> PackProfile::getCurrentTask() -{ - return d->m_updateTask; -} - -void PackProfile::resolve(Net::Mode netmode) -{ - auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this); - d->m_updateTask.reset(updateTask); - connect(updateTask, &ComponentUpdateTask::succeeded, this, &PackProfile::updateSucceeded); - connect(updateTask, &ComponentUpdateTask::failed, this, &PackProfile::updateFailed); - d->m_updateTask->start(); -} - - -void PackProfile::updateSucceeded() -{ - qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name(); - d->m_updateTask.reset(); - invalidateLaunchProfile(); -} - -void PackProfile::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 PackProfile::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 savePackProfile(componentsFilePath(), components); -} - -// END: save/load - -void PackProfile::appendComponent(ComponentPtr component) -{ - insertComponent(d->components.size(), component); -} - -void PackProfile::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, &PackProfile::componentDataChanged); - scheduleSave(); -} - -void PackProfile::componentDataChanged() -{ - auto objPtr = qobject_cast<Component *>(sender()); - if(!objPtr) - { - qWarning() << "PackProfile got dataChenged signal from a non-Component!"; - return; - } - if(objPtr->getID() == "net.minecraft") { - emit minecraftChanged(); - } - // 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() << "PackProfile got dataChenged signal from a Component which does not belong to it!"; -} - -bool PackProfile::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 PackProfile::remove(const QString id) -{ - int i = 0; - for (auto patch : d->components) - { - if (patch->getID() == id) - { - return remove(i); - } - i++; - } - return false; -} - -bool PackProfile::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 PackProfile::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 * PackProfile::getComponent(const QString &id) -{ - auto iter = d->componentIndex.find(id); - if (iter == d->componentIndex.end()) - { - return nullptr; - } - return (*iter).get(); -} - -Component * PackProfile::getComponent(int index) -{ - if(index < 0 || index >= d->components.size()) - { - return nullptr; - } - return d->components[index].get(); -} - -QVariant PackProfile::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 patch->isEnabled() ? Qt::Checked : Qt::Unchecked; - } - default: - return QVariant(); - } - } - case Qt::DisplayRole: - { - switch (column) - { - case NameColumn: - return patch->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 PackProfile::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 PackProfile::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(); -} - -// FIXME: zero precision mess -Qt::ItemFlags PackProfile::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() && !d->interactionDisabled) - { - outFlags |= Qt::ItemIsUserCheckable; - } - return outFlags; -} - -int PackProfile::rowCount(const QModelIndex &parent) const -{ - return d->components.size(); -} - -int PackProfile::columnCount(const QModelIndex &parent) const -{ - return NUM_COLUMNS; -} - -void PackProfile::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 PackProfile::invalidateLaunchProfile() -{ - d->m_profile.reset(); -} - -void PackProfile::installJarMods(QStringList selectedFiles) -{ - installJarMods_internal(selectedFiles); -} - -void PackProfile::installCustomJar(QString selectedFile) -{ - installCustomJar_internal(selectedFile); -} - -bool PackProfile::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 PackProfile::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 PackProfile::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 PackProfile::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> PackProfile::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 (const Exception &error) - { - qWarning() << "Couldn't apply profile patches because: " << error.cause(); - } - } - return d->m_profile; -} - -void PackProfile::setOldConfigVersion(const QString& uid, const QString& version) -{ - if(version.isEmpty()) - { - return; - } - d->m_oldConfigVersions[uid] = version; -} - -bool PackProfile::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 PackProfile::getComponentVersion(const QString& uid) const -{ - const auto iter = d->componentIndex.find(uid); - if (iter != d->componentIndex.end()) - { - return (*iter)->getVersion(); - } - return QString(); -} - -void PackProfile::disableInteraction(bool disable) -{ - if(d->interactionDisabled != disable) { - d->interactionDisabled = disable; - auto size = d->components.size(); - if(size) { - emit dataChanged(index(0), index(size - 1)); - } - } -} diff --git a/api/logic/minecraft/PackProfile.h b/api/logic/minecraft/PackProfile.h deleted file mode 100644 index e55e6a58..00000000 --- a/api/logic/minecraft/PackProfile.h +++ /dev/null @@ -1,152 +0,0 @@ -/* Copyright 2013-2021 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 PackProfileData; -class ComponentUpdateTask; - -class MULTIMC_LOGIC_EXPORT PackProfile : public QAbstractListModel -{ - Q_OBJECT - friend ComponentUpdateTask; -public: - enum Columns - { - NameColumn = 0, - VersionColumn, - NUM_COLUMNS - }; - - explicit PackProfile(MinecraftInstance * instance); - virtual ~PackProfile(); - - 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(); - -signals: - void minecraftChanged(); - -public: - /// get the profile component by id - Component * getComponent(const QString &id); - - /// get the profile component by index - Component * getComponent(int index); - - /// Add the component to the internal list of patches - // todo(merged): is this the best approach - void appendComponent(ComponentPtr component); - -private: - void scheduleSave(); - bool saveIsScheduled() const; - - /// apply the component patches. Catches all the errors and returns true/false for success/failure - void invalidateLaunchProfile(); - - /// 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(); - void disableInteraction(bool disable); - -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<PackProfileData> d; -}; diff --git a/api/logic/minecraft/PackProfile_p.h b/api/logic/minecraft/PackProfile_p.h deleted file mode 100644 index 6cd2a4e5..00000000 --- a/api/logic/minecraft/PackProfile_p.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "Component.h" -#include <map> -#include <QTimer> -#include <QList> -#include <QMap> - -class MinecraftInstance; -using ComponentContainer = QList<ComponentPtr>; -using ComponentIndex = QMap<QString, ComponentPtr>; - -struct PackProfileData -{ - // 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; - bool interactionDisabled = true; -}; - diff --git a/api/logic/minecraft/ParseUtils.cpp b/api/logic/minecraft/ParseUtils.cpp deleted file mode 100644 index c9640e77..00000000 --- a/api/logic/minecraft/ParseUtils.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include <QDateTime> -#include <QString> -#include "ParseUtils.h" -#include <QDebug> -#include <cstdlib> - -QDateTime timeFromS3Time(QString str) -{ - return QDateTime::fromString(str, Qt::ISODate); -} - -QString timeToS3Time(QDateTime time) -{ - // this all because Qt can't format timestamps right. - int offsetRaw = time.offsetFromUtc(); - bool negative = offsetRaw < 0; - int offsetAbs = std::abs(offsetRaw); - - int offsetSeconds = offsetAbs % 60; - offsetAbs -= offsetSeconds; - - int offsetMinutes = offsetAbs % 3600; - offsetAbs -= offsetMinutes; - offsetMinutes /= 60; - - int offsetHours = offsetAbs / 3600; - - QString raw = time.toString("yyyy-MM-ddTHH:mm:ss"); - raw += (negative ? QChar('-') : QChar('+')); - raw += QString("%1").arg(offsetHours, 2, 10, QChar('0')); - raw += ":"; - raw += QString("%1").arg(offsetMinutes, 2, 10, QChar('0')); - return raw; -} diff --git a/api/logic/minecraft/ParseUtils.h b/api/logic/minecraft/ParseUtils.h deleted file mode 100644 index 2b367a10..00000000 --- a/api/logic/minecraft/ParseUtils.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include <QString> -#include <QDateTime> - -#include "multimc_logic_export.h" - -/// take the timestamp used by S3 and turn it into QDateTime -MULTIMC_LOGIC_EXPORT QDateTime timeFromS3Time(QString str); - -/// take a timestamp and convert it into an S3 timestamp -MULTIMC_LOGIC_EXPORT QString timeToS3Time(QDateTime); diff --git a/api/logic/minecraft/ParseUtils_test.cpp b/api/logic/minecraft/ParseUtils_test.cpp deleted file mode 100644 index fcc137e5..00000000 --- a/api/logic/minecraft/ParseUtils_test.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "minecraft/ParseUtils.h" - -class ParseUtilsTest : public QObject -{ - Q_OBJECT -private -slots: - void test_Through_data() - { - QTest::addColumn<QString>("timestamp"); - const char * timestamps[] = - { - "2016-02-29T13:49:54+01:00", - "2016-02-26T15:21:11+00:01", - "2016-02-24T15:52:36+01:13", - "2016-02-18T17:41:00+00:00", - "2016-02-17T15:23:19+00:00", - "2016-02-16T15:22:39+09:22", - "2016-02-10T15:06:41+00:00", - "2016-02-04T15:28:02-05:33" - }; - for(unsigned i = 0; i < (sizeof(timestamps) / sizeof(const char *)); i++) - { - QTest::newRow(timestamps[i]) << QString(timestamps[i]); - } - } - void test_Through() - { - QFETCH(QString, timestamp); - - auto time_parsed = timeFromS3Time(timestamp); - auto time_serialized = timeToS3Time(time_parsed); - - QCOMPARE(time_serialized, timestamp); - } - -}; - -QTEST_GUILESS_MAIN(ParseUtilsTest) - -#include "ParseUtils_test.moc" - diff --git a/api/logic/minecraft/ProfileUtils.cpp b/api/logic/minecraft/ProfileUtils.cpp deleted file mode 100644 index 8ca24cc8..00000000 --- a/api/logic/minecraft/ProfileUtils.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "ProfileUtils.h" -#include "minecraft/VersionFilterData.h" -#include "minecraft/OneSixVersionFormat.h" -#include "Json.h" -#include <QDebug> - -#include <QJsonDocument> -#include <QJsonArray> -#include <QRegularExpression> -#include <QSaveFile> - -namespace ProfileUtils -{ - -static const int currentOrderFileVersion = 1; - -bool readOverrideOrders(QString path, PatchOrder &order) -{ - QFile orderFile(path); - if (!orderFile.exists()) - { - qWarning() << "Order file doesn't exist. Ignoring."; - return false; - } - if (!orderFile.open(QFile::ReadOnly)) - { - qCritical() << "Couldn't open" << orderFile.fileName() - << " for reading:" << orderFile.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } - - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - qCritical() << "Couldn't parse" << orderFile.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("version")); - if (version != currentOrderFileVersion) - { - throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") - .arg(currentOrderFileVersion)); - } - auto orderArray = Json::requireArray(obj.value("order")); - for(auto item: orderArray) - { - order.append(Json::requireString(item)); - } - } - catch (const JSONValidationError &err) - { - qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; - qWarning() << "Ignoring overriden order"; - order.clear(); - return false; - } - return true; -} - -static VersionFilePtr createErrorVersionFile(QString fileId, QString filepath, QString error) -{ - auto outError = std::make_shared<VersionFile>(); - outError->uid = outError->name = fileId; - // outError->filename = filepath; - outError->addProblem(ProblemSeverity::Error, error); - return outError; -} - -static VersionFilePtr guardedParseJson(const QJsonDocument & doc,const QString &fileId,const QString &filepath,const bool &requireOrder) -{ - try - { - return OneSixVersionFormat::versionFileFromJson(doc, filepath, requireOrder); - } - catch (const Exception &e) - { - return createErrorVersionFile(fileId, filepath, e.cause()); - } -} - -VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder) -{ - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - QJsonParseError error; - auto data = file.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - file.close(); - if (error.error != QJsonParseError::NoError) - { - int line = 1; - int column = 0; - for(int i = 0; i < error.offset; i++) - { - if(data[i] == '\n') - { - line++; - column = 0; - continue; - } - column++; - } - auto errorStr = QObject::tr("Unable to process the version file %1: %2 at line %3 column %4.") - .arg(fileInfo.fileName(), error.errorString()) - .arg(line).arg(column); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - 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()); - if (!file.open(QFile::ReadOnly)) - { - auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); - file.close(); - if (doc.isNull()) - { - file.remove(); - throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); - } - return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false); -} - -void removeLwjglFromPatch(VersionFilePtr patch) -{ - auto filter = [](QList<LibraryPtr>& libs) - { - QList<LibraryPtr> filteredLibs; - for (auto lib : libs) - { - if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix())) - { - filteredLibs.append(lib); - } - } - libs = filteredLibs; - }; - filter(patch->libraries); -} -} diff --git a/api/logic/minecraft/ProfileUtils.h b/api/logic/minecraft/ProfileUtils.h deleted file mode 100644 index 351c36cb..00000000 --- a/api/logic/minecraft/ProfileUtils.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "Library.h" -#include "VersionFile.h" - -namespace ProfileUtils -{ -typedef QStringList PatchOrder; - -/// Read and parse a OneSix format order file -bool readOverrideOrders(QString path, PatchOrder &order); - -/// Write a OneSix format order file -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); - -/// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files. -void removeLwjglFromPatch(VersionFilePtr patch); - -} diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp deleted file mode 100644 index af2861e3..00000000 --- a/api/logic/minecraft/Rule.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* Copyright 2013-2021 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 <QJsonObject> -#include <QJsonArray> - -#include "Rule.h" - -RuleAction RuleAction_fromString(QString name) -{ - if (name == "allow") - return Allow; - if (name == "disallow") - return Disallow; - return Defer; -} - -QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules) -{ - QList<std::shared_ptr<Rule>> rules; - auto rulesVal = objectWithRules.value("rules"); - if (!rulesVal.isArray()) - return rules; - - QJsonArray ruleList = rulesVal.toArray(); - for (auto ruleVal : ruleList) - { - std::shared_ptr<Rule> rule; - if (!ruleVal.isObject()) - continue; - auto ruleObj = ruleVal.toObject(); - auto actionVal = ruleObj.value("action"); - if (!actionVal.isString()) - continue; - auto action = RuleAction_fromString(actionVal.toString()); - if (action == Defer) - continue; - - auto osVal = ruleObj.value("os"); - if (!osVal.isObject()) - { - // add a new implicit action rule - rules.append(ImplicitRule::create(action)); - continue; - } - - auto osObj = osVal.toObject(); - auto osNameVal = osObj.value("name"); - if (!osNameVal.isString()) - continue; - OpSys requiredOs = OpSys_fromString(osNameVal.toString()); - QString versionRegex = osObj.value("version").toString(); - // add a new OS rule - rules.append(OsRule::create(action, requiredOs, versionRegex)); - } - return rules; -} - -QJsonObject ImplicitRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - return ruleObj; -} - -QJsonObject OsRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - QJsonObject osObj; - { - osObj.insert("name", OpSys_toString(m_system)); - if(!m_version_regexp.isEmpty()) - { - osObj.insert("version", m_version_regexp); - } - } - ruleObj.insert("os", osObj); - return ruleObj; -} - diff --git a/api/logic/minecraft/Rule.h b/api/logic/minecraft/Rule.h deleted file mode 100644 index 7aa34d96..00000000 --- a/api/logic/minecraft/Rule.h +++ /dev/null @@ -1,101 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QString> -#include <QList> -#include <QJsonObject> -#include <memory> -#include "OpSys.h" - -class Library; -class Rule; - -enum RuleAction -{ - Allow, - Disallow, - Defer -}; - -QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules); - -class Rule -{ -protected: - RuleAction m_result; - virtual bool applies(const Library *parent) = 0; - -public: - Rule(RuleAction result) : m_result(result) - { - } - virtual ~Rule() {}; - virtual QJsonObject toJson() = 0; - RuleAction apply(const Library *parent) - { - if (applies(parent)) - return m_result; - else - return Defer; - } -}; - -class OsRule : public Rule -{ -private: - // the OS - OpSys m_system; - // the OS version regexp - QString m_version_regexp; - -protected: - virtual bool applies(const Library *) - { - return (m_system == currentSystem); - } - OsRule(RuleAction result, OpSys system, QString version_regexp) - : Rule(result), m_system(system), m_version_regexp(version_regexp) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr<OsRule> create(RuleAction result, OpSys system, - QString version_regexp) - { - return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp)); - } -}; - -class ImplicitRule : public Rule -{ -protected: - virtual bool applies(const Library *) - { - return true; - } - ImplicitRule(RuleAction result) : Rule(result) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr<ImplicitRule> create(RuleAction result) - { - return std::shared_ptr<ImplicitRule>(new ImplicitRule(result)); - } -}; diff --git a/api/logic/minecraft/VersionFile.cpp b/api/logic/minecraft/VersionFile.cpp deleted file mode 100644 index d0a1a507..00000000 --- a/api/logic/minecraft/VersionFile.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include <QJsonArray> -#include <QJsonDocument> - -#include <QDebug> - -#include "minecraft/VersionFile.h" -#include "minecraft/Library.h" -#include "minecraft/PackProfile.h" -#include "ParseUtils.h" - -#include <Version.h> - -static bool isMinecraftVersion(const QString &uid) -{ - return uid == "net.minecraft"; -} - -void VersionFile::applyTo(LaunchProfile *profile) -{ - // Only real Minecraft can set those. Don't let anything override them. - if (isMinecraftVersion(uid)) - { - profile->applyMinecraftVersion(minecraftVersion); - profile->applyMinecraftVersionType(type); - // HACK: ignore assets from other version files than Minecraft - // workaround for stupid assets issue caused by amazon: - // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ - profile->applyMinecraftAssets(mojangAssetIndex); - } - - profile->applyMainJar(mainJar); - profile->applyMainClass(mainClass); - profile->applyAppletClass(appletClass); - profile->applyMinecraftArguments(minecraftArguments); - profile->applyTweakers(addTweakers); - profile->applyJarMods(jarMods); - profile->applyMods(mods); - profile->applyTraits(traits); - - for (auto library : libraries) - { - profile->applyLibrary(library); - } - for (auto mavenFile : mavenFiles) - { - profile->applyMavenFile(mavenFile); - } - profile->applyProblemSeverity(getProblemSeverity()); -} - -/* - auto theirVersion = profile->getMinecraftVersion(); - if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull()) - { - if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1) - { - throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion); - } - } -*/ diff --git a/api/logic/minecraft/VersionFile.h b/api/logic/minecraft/VersionFile.h deleted file mode 100644 index b79fcd4f..00000000 --- a/api/logic/minecraft/VersionFile.h +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once - -#include <QString> -#include <QStringList> -#include <QDateTime> -#include <QSet> - -#include <memory> -#include "minecraft/OpSys.h" -#include "minecraft/Rule.h" -#include "ProblemProvider.h" -#include "Library.h" -#include <meta/JsonFormat.h> - -class PackProfile; -class VersionFile; -class LaunchProfile; -struct MojangDownloadInfo; -struct MojangAssetIndexInfo; - -using VersionFilePtr = std::shared_ptr<VersionFile>; -class VersionFile : public ProblemContainer -{ - friend class MojangVersionFormat; - friend class OneSixVersionFormat; -public: /* methods */ - void applyTo(LaunchProfile* profile); - -public: /* data */ - /// MultiMC: order hint for this version file if no explicit order is set - int order = 0; - - /// MultiMC: human readable name of this package - QString name; - - /// MultiMC: package ID of this package - QString uid; - - /// MultiMC: version of this package - QString version; - - /// MultiMC: DEPRECATED dependency on a Minecraft version - QString dependsOnMinecraftVersion; - - /// Mojang: DEPRECATED used to version the Mojang version format - int minimumLauncherVersion = -1; - - /// Mojang: DEPRECATED version of Minecraft this is - QString minecraftVersion; - - /// Mojang: class to launch Minecraft with - QString mainClass; - - /// MultiMC: class to launch legacy Minecraft with (embed in a custom window) - QString appletClass; - - /// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution) - QString minecraftArguments; - - /// Mojang: type of the Minecraft version - QString type; - - /// Mojang: the time this version was actually released by Mojang - QDateTime releaseTime; - - /// Mojang: DEPRECATED the time this version was last updated by Mojang - QDateTime updateTime; - - /// Mojang: DEPRECATED asset group to be used with Minecraft - QString assets; - - /// MultiMC: list of tweaker mod arguments for launchwrapper - QStringList addTweakers; - - /// Mojang: list of libraries to add to the version - QList<LibraryPtr> libraries; - - /// MultiMC: list of maven files to put in the libraries folder, but not in classpath - QList<LibraryPtr> mavenFiles; - - /// The main jar (Minecraft version library, normally) - LibraryPtr mainJar; - - /// MultiMC: list of attached traits of this version file - used to enable features - QSet<QString> traits; - - /// MultiMC: list of jar mods added to this version - QList<LibraryPtr> jarMods; - - /// 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; - - // Mojang: extended asset index download information - std::shared_ptr<MojangAssetIndexInfo> mojangAssetIndex; -}; diff --git a/api/logic/minecraft/VersionFilterData.cpp b/api/logic/minecraft/VersionFilterData.cpp deleted file mode 100644 index 38e7b60c..00000000 --- a/api/logic/minecraft/VersionFilterData.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "VersionFilterData.h" -#include "ParseUtils.h" - -VersionFilterData g_VersionFilterData = VersionFilterData(); - -VersionFilterData::VersionFilterData() -{ - // 1.3.* - auto libs13 = - QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"}}; - - fmlLibsMapping["1.3.2"] = libs13; - - // 1.4.* - auto libs14 = QList<FMLlib>{ - {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"}, - {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb"}}; - - fmlLibsMapping["1.4"] = libs14; - fmlLibsMapping["1.4.1"] = libs14; - fmlLibsMapping["1.4.2"] = libs14; - fmlLibsMapping["1.4.3"] = libs14; - fmlLibsMapping["1.4.4"] = libs14; - fmlLibsMapping["1.4.5"] = libs14; - fmlLibsMapping["1.4.6"] = libs14; - fmlLibsMapping["1.4.7"] = libs14; - - // 1.5 - fmlLibsMapping["1.5"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"}, - {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8"}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}}; - - // 1.5.1 - fmlLibsMapping["1.5.1"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"}, - {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6"}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}}; - - // 1.5.2 - fmlLibsMapping["1.5.2"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"}, - {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9"}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}}; - - // don't use installers for those. - forgeInstallerBlacklist = QSet<QString>({"1.5.2"}); - - // FIXME: remove, used for deciding when core mods should display - legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); - lwjglWhitelist = - QSet<QString>{"net.java.jinput:jinput", "net.java.jinput:jinput-platform", - "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", - "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; - - java8BeginsDate = timeFromS3Time("2017-03-30T09:32:19+00:00"); - java16BeginsDate = timeFromS3Time("2021-05-12T11:19:15+00:00"); -} diff --git a/api/logic/minecraft/VersionFilterData.h b/api/logic/minecraft/VersionFilterData.h deleted file mode 100644 index d100acc3..00000000 --- a/api/logic/minecraft/VersionFilterData.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include <QMap> -#include <QString> -#include <QSet> -#include <QDateTime> - -#include "multimc_logic_export.h" - -struct FMLlib -{ - QString filename; - QString checksum; -}; - -struct VersionFilterData -{ - VersionFilterData(); - // mapping between minecraft versions and FML libraries required - QMap<QString, QList<FMLlib>> fmlLibsMapping; - // set of minecraft versions for which using forge installers is blacklisted - QSet<QString> forgeInstallerBlacklist; - // no new versions below this date will be accepted from Mojang servers - QDateTime legacyCutoffDate; - // Libraries that belong to LWJGL - QSet<QString> lwjglWhitelist; - // release date of first version to require Java 8 (17w13a) - QDateTime java8BeginsDate; - // release data of first version to require Java 16 (21w19a) - QDateTime java16BeginsDate; -}; -extern VersionFilterData MULTIMC_LOGIC_EXPORT g_VersionFilterData; diff --git a/api/logic/minecraft/World.cpp b/api/logic/minecraft/World.cpp deleted file mode 100644 index a2b4dac7..00000000 --- a/api/logic/minecraft/World.cpp +++ /dev/null @@ -1,520 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <QDir> -#include <QString> -#include <QDebug> -#include <QSaveFile> -#include "World.h" - -#include "GZip.h" -#include <MMCZip.h> -#include <FileSystem.h> -#include <sstream> -#include <io/stream_reader.h> -#include <tag_string.h> -#include <tag_primitive.h> -#include <quazip.h> -#include <quazipfile.h> -#include <quazipdir.h> - -#include <QCoreApplication> - -#include <nonstd/optional> - -using nonstd::optional; -using nonstd::nullopt; - -GameType::GameType(nonstd::optional<int> original): - original(original) -{ - if(!original) { - return; - } - switch(*original) { - case 0: - type = GameType::Survival; - break; - case 1: - type = GameType::Creative; - break; - case 2: - type = GameType::Adventure; - break; - case 3: - type = GameType::Spectator; - break; - default: - break; - } -} - -QString GameType::toTranslatedString() const -{ - switch (type) - { - case GameType::Survival: - return QCoreApplication::translate("GameType", "Survival"); - case GameType::Creative: - return QCoreApplication::translate("GameType", "Creative"); - case GameType::Adventure: - return QCoreApplication::translate("GameType", "Adventure"); - case GameType::Spectator: - return QCoreApplication::translate("GameType", "Spectator"); - default: - break; - } - if(original) { - return QCoreApplication::translate("GameType", "Unknown (%1)").arg(*original); - } - return QCoreApplication::translate("GameType", "Undefined"); -} - -QString GameType::toLogString() const -{ - switch (type) - { - case GameType::Survival: - return "Survival"; - case GameType::Creative: - return "Creative"; - case GameType::Adventure: - return "Adventure"; - case GameType::Spectator: - return "Spectator"; - default: - break; - } - if(original) { - return QString("Unknown (%1)").arg(*original); - } - return "Undefined"; -} - -std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data) -{ - QByteArray output; - if(!GZip::unzip(data, output)) - { - return nullptr; - } - std::istringstream foo(std::string(output.constData(), output.size())); - try { - auto pair = nbt::io::read_compound(foo); - - if(pair.first != "") - return nullptr; - - if(pair.second == nullptr) - return nullptr; - - return std::move(pair.second); - } - catch (const nbt::io::input_error &e) - { - qWarning() << "Unable to parse level.dat:" << e.what(); - return nullptr; - } -} - -QByteArray serializeLevelDat(nbt::tag_compound * levelInfo) -{ - std::ostringstream s; - nbt::io::write_tag("", *levelInfo, s); - QByteArray val( s.str().data(), (int) s.str().size() ); - return val; -} - -QString getLevelDatFromFS(const QFileInfo &file) -{ - QDir worldDir(file.filePath()); - if(!file.isDir() || !worldDir.exists("level.dat")) - { - return QString(); - } - return worldDir.absoluteFilePath("level.dat"); -} - -QByteArray getLevelDatDataFromFS(const QFileInfo &file) -{ - auto fullFilePath = getLevelDatFromFS(file); - if(fullFilePath.isNull()) - { - return QByteArray(); - } - QFile f(fullFilePath); - if(!f.open(QIODevice::ReadOnly)) - { - return QByteArray(); - } - return f.readAll(); -} - -bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data) -{ - auto fullFilePath = getLevelDatFromFS(file); - if(fullFilePath.isNull()) - { - return false; - } - QSaveFile f(fullFilePath); - if(!f.open(QIODevice::WriteOnly)) - { - return false; - } - QByteArray compressed; - if(!GZip::zip(data, compressed)) - { - return false; - } - if(f.write(compressed) != compressed.size()) - { - f.cancelWriting(); - return false; - } - return f.commit(); -} - -World::World(const QFileInfo &file) -{ - repath(file); -} - -void World::repath(const QFileInfo &file) -{ - m_containerFile = file; - m_folderName = file.fileName(); - if(file.isFile() && file.suffix() == "zip") - { - m_iconFile = QString(); - readFromZip(file); - } - else if(file.isDir()) - { - QFileInfo assumedIconPath(file.absoluteFilePath() + "/icon.png"); - if(assumedIconPath.exists()) { - m_iconFile = assumedIconPath.absoluteFilePath(); - } - readFromFS(file); - } -} - -bool World::resetIcon() -{ - if(m_iconFile.isNull()) { - return false; - } - if(QFile(m_iconFile).remove()) { - m_iconFile = QString(); - return true; - } - return false; -} - -void World::readFromFS(const QFileInfo &file) -{ - auto bytes = getLevelDatDataFromFS(file); - if(bytes.isEmpty()) - { - is_valid = false; - return; - } - loadFromLevelDat(bytes); - levelDatTime = file.lastModified(); -} - -void World::readFromZip(const QFileInfo &file) -{ - QuaZip zip(file.absoluteFilePath()); - is_valid = zip.open(QuaZip::mdUnzip); - if (!is_valid) - { - return; - } - auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); - is_valid = !location.isEmpty(); - if (!is_valid) - { - return; - } - m_containerOffsetPath = location; - QuaZipFile zippedFile(&zip); - // read the install profile - is_valid = zip.setCurrentFile(location + "level.dat"); - if (!is_valid) - { - return; - } - is_valid = zippedFile.open(QIODevice::ReadOnly); - QuaZipFileInfo64 levelDatInfo; - zippedFile.getFileInfo(&levelDatInfo); - auto modTime = levelDatInfo.getNTFSmTime(); - if(!modTime.isValid()) - { - modTime = levelDatInfo.dateTime; - } - levelDatTime = modTime; - if (!is_valid) - { - return; - } - loadFromLevelDat(zippedFile.readAll()); - zippedFile.close(); -} - -bool World::install(const QString &to, const QString &name) -{ - auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to)); - if(!FS::ensureFolderPathExists(finalPath)) - { - return false; - } - bool ok = false; - if(m_containerFile.isFile()) - { - QuaZip zip(m_containerFile.absoluteFilePath()); - if (!zip.open(QuaZip::mdUnzip)) - { - return false; - } - ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath); - } - else if(m_containerFile.isDir()) - { - QString from = m_containerFile.filePath(); - ok = FS::copy(from, finalPath)(); - } - - if(ok && !name.isEmpty() && m_actualName != name) - { - World newWorld(finalPath); - if(newWorld.isValid()) - { - newWorld.rename(name); - } - } - return ok; -} - -bool World::rename(const QString &newName) -{ - if(m_containerFile.isFile()) - { - return false; - } - - auto data = getLevelDatDataFromFS(m_containerFile); - if(data.isEmpty()) - { - return false; - } - - auto worldData = parseLevelDat(data); - if(!worldData) - { - return false; - } - auto &val = worldData->at("Data"); - if(val.get_type() != nbt::tag_type::Compound) - { - return false; - } - auto &dataCompound = val.as<nbt::tag_compound>(); - dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data())); - data = serializeLevelDat(worldData.get()); - - putLevelDatDataToFS(m_containerFile, data); - - m_actualName = newName; - - QDir parentDir(m_containerFile.absoluteFilePath()); - parentDir.cdUp(); - QFile container(m_containerFile.absoluteFilePath()); - auto dirName = FS::DirNameFromString(m_actualName, parentDir.absolutePath()); - container.rename(parentDir.absoluteFilePath(dirName)); - - return true; -} - -namespace { - -optional<QString> read_string (nbt::value& parent, const char * name) -{ - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::String) - { - return nullopt; - } - auto & tag_str = namedValue.as<nbt::tag_string>(); - return QString::fromStdString(tag_str.get()); - } - catch (const std::out_of_range &e) - { - // fallback for old world formats - qWarning() << "String NBT tag" << name << "could not be found."; - return nullopt; - } - catch (const std::bad_cast &e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to string."; - return nullopt; - } -} - -optional<int64_t> read_long (nbt::value& parent, const char * name) -{ - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::Long) - { - return nullopt; - } - auto & tag_str = namedValue.as<nbt::tag_long>(); - return tag_str.get(); - } - catch (const std::out_of_range &e) - { - // fallback for old world formats - qWarning() << "Long NBT tag" << name << "could not be found."; - return nullopt; - } - catch (const std::bad_cast &e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to long."; - return nullopt; - } -} - -optional<int> read_int (nbt::value& parent, const char * name) -{ - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::Int) - { - return nullopt; - } - auto & tag_str = namedValue.as<nbt::tag_int>(); - return tag_str.get(); - } - catch (const std::out_of_range &e) - { - // fallback for old world formats - qWarning() << "Int NBT tag" << name << "could not be found."; - return nullopt; - } - catch (const std::bad_cast &e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to int."; - return nullopt; - } -} - -GameType read_gametype(nbt::value& parent, const char * name) { - return GameType(read_int(parent, name)); -} - -} - -void World::loadFromLevelDat(QByteArray data) -{ - auto levelData = parseLevelDat(data); - if(!levelData) - { - is_valid = false; - return; - } - - nbt::value * valPtr = nullptr; - try { - valPtr = &levelData->at("Data"); - } - catch (const std::out_of_range &e) { - qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what(); - is_valid = false; - return; - } - nbt::value &val = *valPtr; - - is_valid = val.get_type() == nbt::tag_type::Compound; - if(!is_valid) - return; - - auto name = read_string(val, "LevelName"); - m_actualName = name ? *name : m_folderName; - - auto timestamp = read_long(val, "LastPlayed"); - m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime; - - m_gameType = read_gametype(val, "GameType"); - - optional<int64_t> randomSeed; - try { - auto &WorldGen_val = val.at("WorldGenSettings"); - randomSeed = read_long(WorldGen_val, "seed"); - } - catch (const std::out_of_range &) {} - if(!randomSeed) { - randomSeed = read_long(val, "RandomSeed"); - } - m_randomSeed = randomSeed ? *randomSeed : 0; - - qDebug() << "World Name:" << m_actualName; - qDebug() << "Last Played:" << m_lastPlayed.toString(); - if(randomSeed) { - qDebug() << "Seed:" << *randomSeed; - } - qDebug() << "GameType:" << m_gameType.toLogString(); -} - -bool World::replace(World &with) -{ - if (!destroy()) - return false; - bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())(); - if (success) - { - m_folderName = with.m_folderName; - m_containerFile.refresh(); - } - return success; -} - -bool World::destroy() -{ - if(!is_valid) return false; - if (m_containerFile.isDir()) - { - QDir d(m_containerFile.filePath()); - return d.removeRecursively(); - } - else if(m_containerFile.isFile()) - { - QFile file(m_containerFile.absoluteFilePath()); - return file.remove(); - } - return true; -} - -bool World::operator==(const World &other) const -{ - return is_valid == other.is_valid && folderName() == other.folderName(); -} diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h deleted file mode 100644 index 1d94d54d..00000000 --- a/api/logic/minecraft/World.h +++ /dev/null @@ -1,113 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include <QFileInfo> -#include <QDateTime> -#include <nonstd/optional> - -#include "multimc_logic_export.h" - -struct MULTIMC_LOGIC_EXPORT GameType { - GameType() = default; - GameType (nonstd::optional<int> original); - - QString toTranslatedString() const; - QString toLogString() const; - - enum - { - Unknown = -1, - Survival = 0, - Creative, - Adventure, - Spectator - } type = Unknown; - nonstd::optional<int> original; -}; - -class MULTIMC_LOGIC_EXPORT World -{ -public: - World(const QFileInfo &file); - QString folderName() const - { - return m_folderName; - } - QString name() const - { - return m_actualName; - } - QString iconFile() const - { - return m_iconFile; - } - QDateTime lastPlayed() const - { - return m_lastPlayed; - } - GameType gameType() const - { - return m_gameType; - } - int64_t seed() const - { - return m_randomSeed; - } - bool isValid() const - { - return is_valid; - } - bool isOnFS() const - { - return m_containerFile.isDir(); - } - QFileInfo container() const - { - return m_containerFile; - } - // delete all the files of this world - bool destroy(); - // replace this world with a copy of the other - bool replace(World &with); - // change the world's filesystem path (used by world lists for *MAGIC* purposes) - void repath(const QFileInfo &file); - // remove the icon file, if any - bool resetIcon(); - - bool rename(const QString &to); - bool install(const QString &to, const QString &name= QString()); - - // WEAK compare operator - used for replacing worlds - bool operator==(const World &other) const; - -private: - void readFromZip(const QFileInfo &file); - void readFromFS(const QFileInfo &file); - void loadFromLevelDat(QByteArray data); - -protected: - - QFileInfo m_containerFile; - QString m_containerOffsetPath; - QString m_folderName; - QString m_actualName; - QString m_iconFile; - QDateTime levelDatTime; - QDateTime m_lastPlayed; - int64_t m_randomSeed = 0; - GameType m_gameType; - bool is_valid = false; -}; diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp deleted file mode 100644 index f6309dbd..00000000 --- a/api/logic/minecraft/WorldList.cpp +++ /dev/null @@ -1,387 +0,0 @@ -/* Copyright 2015-2021 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 "WorldList.h" -#include <FileSystem.h> -#include <QMimeData> -#include <QUrl> -#include <QUuid> -#include <QString> -#include <QFileSystemWatcher> -#include <QDebug> - -WorldList::WorldList(const QString &dir) - : QAbstractListModel(), m_dir(dir) -{ - 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_watcher = new QFileSystemWatcher(this); - is_watching = false; - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, - SLOT(directoryChanged(QString))); -} - -void WorldList::startWatching() -{ - if(is_watching) - { - return; - } - 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 WorldList::stopWatching() -{ - if(!is_watching) - { - return; - } - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) - { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } -} - -bool WorldList::update() -{ - if (!isValid()) - return false; - - QList<World> newWorlds; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - // if there are any untracked files... - for (QFileInfo entry : folderContents) - { - if(!entry.isDir()) - continue; - - World w(entry); - if(w.isValid()) - { - newWorlds.append(w); - } - } - beginResetModel(); - worlds.swap(newWorlds); - endResetModel(); - return true; -} - -void WorldList::directoryChanged(QString path) -{ - update(); -} - -bool WorldList::isValid() -{ - return m_dir.exists() && m_dir.isReadable(); -} - -bool WorldList::deleteWorld(int index) -{ - if (index >= worlds.size() || index < 0) - return false; - World &m = worlds[index]; - if (m.destroy()) - { - beginRemoveRows(QModelIndex(), index, index); - worlds.removeAt(index); - endRemoveRows(); - emit changed(); - return true; - } - return false; -} - -bool WorldList::deleteWorlds(int first, int last) -{ - for (int i = first; i <= last; i++) - { - World &m = worlds[i]; - m.destroy(); - } - beginRemoveRows(QModelIndex(), first, last); - worlds.erase(worlds.begin() + first, worlds.begin() + last + 1); - endRemoveRows(); - emit changed(); - return true; -} - -bool WorldList::resetIcon(int row) -{ - if (row >= worlds.size() || row < 0) - return false; - World &m = worlds[row]; - if(m.resetIcon()) { - emit dataChanged(index(row), index(row), {WorldList::IconFileRole}); - return true; - } - return false; -} - - -int WorldList::columnCount(const QModelIndex &parent) const -{ - return 3; -} - -QVariant WorldList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= worlds.size()) - return QVariant(); - - auto & world = worlds[row]; - switch (role) - { - case Qt::DisplayRole: - switch (column) - { - case NameColumn: - return world.name(); - - case GameModeColumn: - return world.gameType().toTranslatedString(); - - case LastPlayedColumn: - return world.lastPlayed(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - { - return world.folderName(); - } - case ObjectRole: - { - return QVariant::fromValue<void *>((void *)&world); - } - case FolderRole: - { - return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName())); - } - case SeedRole: - { - return qVariantFromValue<qlonglong>(world.seed()); - } - case NameRole: - { - return world.name(); - } - case LastPlayedRole: - { - return world.lastPlayed(); - } - case IconFileRole: - { - return world.iconFile(); - } - default: - return QVariant(); - } -} - -QVariant WorldList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case NameColumn: - return tr("Name"); - case GameModeColumn: - return tr("Game Mode"); - case LastPlayedColumn: - return tr("Last Played"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case NameColumn: - return tr("The name of the world."); - case GameModeColumn: - return tr("Game mode of the world."); - case LastPlayedColumn: - return tr("Date and time the world was last played."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); -} - -QStringList WorldList::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - return types; -} - -class WorldMimeData : public QMimeData -{ -Q_OBJECT - -public: - WorldMimeData(QList<World> worlds) - { - m_worlds = worlds; - - } - QStringList formats() const - { - return QMimeData::formats() << "text/uri-list"; - } - -protected: - QVariant retrieveData(const QString &mimetype, QVariant::Type type) const - { - QList<QUrl> urls; - for(auto &world: m_worlds) - { - if(!world.isValid() || !world.isOnFS()) - continue; - QString worldPath = world.container().absoluteFilePath(); - qDebug() << worldPath; - urls.append(QUrl::fromLocalFile(worldPath)); - } - const_cast<WorldMimeData*>(this)->setUrls(urls); - return QMimeData::retrieveData(mimetype, type); - } -private: - QList<World> m_worlds; -}; - -QMimeData *WorldList::mimeData(const QModelIndexList &indexes) const -{ - if (indexes.size() == 0) - return new QMimeData(); - - QList<World> worlds; - for(auto idx : indexes) - { - if(idx.column() != 0) - continue; - int row = idx.row(); - if (row < 0 || row >= this->worlds.size()) - continue; - worlds.append(this->worlds[row]); - } - if(!worlds.size()) - { - return new QMimeData(); - } - return new WorldMimeData(worlds); -} - -Qt::ItemFlags WorldList::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; -} - -Qt::DropActions WorldList::supportedDragActions() const -{ - // move to other mod lists or VOID - return Qt::MoveAction; -} - -Qt::DropActions WorldList::supportedDropActions() const -{ - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; -} - -void WorldList::installWorld(QFileInfo filename) -{ - qDebug() << "installing: " << filename.absoluteFilePath(); - World w(filename); - if(!w.isValid()) - { - return; - } - w.install(m_dir.absolutePath()); -} - -bool WorldList::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; - // 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(); - - QFileInfo worldInfo(filename); - - if(!m_dir.entryInfoList().contains(worldInfo)) - { - installWorld(worldInfo); - } - } - if (was_watching) - startWatching(); - return true; - } - return false; -} - -#include "WorldList.moc" diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h deleted file mode 100644 index 740b1461..00000000 --- a/api/logic/minecraft/WorldList.h +++ /dev/null @@ -1,131 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QList> -#include <QString> -#include <QDir> -#include <QAbstractListModel> -#include <QMimeData> -#include "minecraft/World.h" - -#include "multimc_logic_export.h" - -class QFileSystemWatcher; - -class MULTIMC_LOGIC_EXPORT WorldList : public QAbstractListModel -{ - Q_OBJECT -public: - enum Columns - { - NameColumn, - GameModeColumn, - LastPlayedColumn - }; - - enum Roles - { - ObjectRole = Qt::UserRole + 1, - FolderRole, - SeedRole, - NameRole, - GameModeRole, - LastPlayedRole, - IconFileRole - }; - - WorldList(const QString &dir); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - 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 worlds.size(); - }; - bool empty() const - { - return size() == 0; - } - World &operator[](size_t index) - { - return worlds[index]; - } - - /// Reloads the mod list and returns true if the list changed. - virtual bool update(); - - /// Install a world from location - void installWorld(QFileInfo filename); - - /// Deletes the mod at the given index. - virtual bool deleteWorld(int index); - - /// Removes the world icon, if any - virtual bool resetIcon(int index); - - /// Deletes all the selected mods - virtual bool deleteWorlds(int first, int last); - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - /// get data for drag action - virtual QMimeData *mimeData(const QModelIndexList &indexes) const; - /// get the supported mime types - virtual QStringList mimeTypes() const; - /// process data from drop action - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - /// what drag actions do we support? - virtual Qt::DropActions supportedDragActions() const; - - /// what drop actions do we support? - virtual Qt::DropActions supportedDropActions() const; - - void startWatching(); - void stopWatching(); - - virtual bool isValid(); - - QDir dir() const - { - return m_dir; - } - - const QList<World> &allWorlds() const - { - return worlds; - } - -private slots: - void directoryChanged(QString path); - -signals: - void changed(); - -protected: - QFileSystemWatcher *m_watcher; - bool is_watching; - QDir m_dir; - QList<World> worlds; -}; diff --git a/api/logic/minecraft/auth-msa/BuildConfig.cpp.in b/api/logic/minecraft/auth-msa/BuildConfig.cpp.in deleted file mode 100644 index 8f470e25..00000000 --- a/api/logic/minecraft/auth-msa/BuildConfig.cpp.in +++ /dev/null @@ -1,9 +0,0 @@ -#include "BuildConfig.h" -#include <QObject> - -const Config BuildConfig; - -Config::Config() -{ - CLIENT_ID = "@MOJANGDEMO_CLIENT_ID@"; -} diff --git a/api/logic/minecraft/auth-msa/BuildConfig.h b/api/logic/minecraft/auth-msa/BuildConfig.h deleted file mode 100644 index 7a01d704..00000000 --- a/api/logic/minecraft/auth-msa/BuildConfig.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include <QString> - -class Config -{ -public: - Config(); - QString CLIENT_ID; -}; - -extern const Config BuildConfig; diff --git a/api/logic/minecraft/auth-msa/CMakeLists.txt b/api/logic/minecraft/auth-msa/CMakeLists.txt deleted file mode 100644 index 22777d1b..00000000 --- a/api/logic/minecraft/auth-msa/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -find_package(Qt5 COMPONENTS Core Gui Network Widgets REQUIRED) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_INCLUDE_CURRENT_DIR ON) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") - - -set(MOJANGDEMO_CLIENT_ID "" CACHE STRING "Client ID used for OAuth2 in mojangdemo") - -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/BuildConfig.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp") - -set(mojang_SRCS - main.cpp - context.cpp - context.h - - mainwindow.cpp - mainwindow.h - mainwindow.ui - - ${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp - BuildConfig.h -) - -add_executable( mojangdemo ${mojang_SRCS} ) -target_link_libraries( mojangdemo Katabasis Qt5::Gui Qt5::Widgets ) -target_include_directories(mojangdemo PRIVATE logic) diff --git a/api/logic/minecraft/auth-msa/context.cpp b/api/logic/minecraft/auth-msa/context.cpp deleted file mode 100644 index d7ecda30..00000000 --- a/api/logic/minecraft/auth-msa/context.cpp +++ /dev/null @@ -1,938 +0,0 @@ -#include <QNetworkAccessManager> -#include <QNetworkRequest> -#include <QNetworkReply> -#include <QDesktopServices> -#include <QMetaEnum> -#include <QDebug> - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> - -#include <QUrlQuery> - -#include <QPixmap> -#include <QPainter> - -#include "context.h" -#include "katabasis/Globals.h" -#include "katabasis/StoreQSettings.h" -#include "katabasis/Requestor.h" -#include "BuildConfig.h" - -using OAuth2 = Katabasis::OAuth2; -using Requestor = Katabasis::Requestor; -using Activity = Katabasis::Activity; - -Context::Context(QObject *parent) : - QObject(parent) -{ - mgr = new QNetworkAccessManager(this); - - Katabasis::OAuth2::Options opts; - opts.scope = "XboxLive.signin offline_access"; - opts.clientIdentifier = BuildConfig.CLIENT_ID; - opts.authorizationUrl = "https://login.live.com/oauth20_authorize.srf"; - opts.accessTokenUrl = "https://login.live.com/oauth20_token.srf"; - opts.listenerPorts = {28562, 28563, 28564, 28565, 28566}; - - oauth2 = new OAuth2(opts, m_account.msaToken, this, mgr); - - connect(oauth2, &OAuth2::linkingFailed, this, &Context::onLinkingFailed); - connect(oauth2, &OAuth2::linkingSucceeded, this, &Context::onLinkingSucceeded); - connect(oauth2, &OAuth2::openBrowser, this, &Context::onOpenBrowser); - connect(oauth2, &OAuth2::closeBrowser, this, &Context::onCloseBrowser); - connect(oauth2, &OAuth2::activityChanged, this, &Context::onOAuthActivityChanged); -} - -void Context::beginActivity(Activity activity) { - if(isBusy()) { - throw 0; - } - activity_ = activity; - emit activityChanged(activity_); -} - -void Context::finishActivity() { - if(!isBusy()) { - throw 0; - } - activity_ = Katabasis::Activity::Idle; - m_account.validity_ = m_account.minecraftProfile.validity; - emit activityChanged(activity_); -} - -QString Context::gameToken() { - return m_account.minecraftToken.token; -} - -QString Context::userId() { - return m_account.minecraftProfile.id; -} - -QString Context::userName() { - return m_account.minecraftProfile.name; -} - -bool Context::silentSignIn() { - if(isBusy()) { - return false; - } - beginActivity(Activity::Refreshing); - if(!oauth2->refresh()) { - finishActivity(); - return false; - } - - requestsDone = 0; - xboxProfileSucceeded = false; - mcAuthSucceeded = false; - - return true; -} - -bool Context::signIn() { - if(isBusy()) { - return false; - } - - requestsDone = 0; - xboxProfileSucceeded = false; - mcAuthSucceeded = false; - - beginActivity(Activity::LoggingIn); - oauth2->unlink(); - m_account = AccountData(); - oauth2->link(); - return true; -} - -bool Context::signOut() { - if(isBusy()) { - return false; - } - beginActivity(Activity::LoggingOut); - oauth2->unlink(); - m_account = AccountData(); - finishActivity(); - return true; -} - - -void Context::onOpenBrowser(const QUrl &url) { - QDesktopServices::openUrl(url); -} - -void Context::onCloseBrowser() { - -} - -void Context::onLinkingFailed() { - finishActivity(); -} - -void Context::onLinkingSucceeded() { - auto *o2t = qobject_cast<OAuth2 *>(sender()); - if (!o2t->linked()) { - finishActivity(); - return; - } - QVariantMap extraTokens = o2t->extraTokens(); - if (!extraTokens.isEmpty()) { - qDebug() << "Extra tokens in response:"; - foreach (QString key, extraTokens.keys()) { - qDebug() << "\t" << key << ":" << extraTokens.value(key); - } - } - doUserAuth(); -} - -void Context::onOAuthActivityChanged(Katabasis::Activity activity) { - // respond to activity change here -} - -void Context::doUserAuth() { - QString xbox_auth_template = R"XXX( -{ - "Properties": { - "AuthMethod": "RPS", - "SiteName": "user.auth.xboxlive.com", - "RpsTicket": "d=%1" - }, - "RelyingParty": "http://auth.xboxlive.com", - "TokenType": "JWT" -} -)XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_account.msaToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - auto *requestor = new Katabasis::Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onUserAuthDone); - requestor->post(request, xbox_auth_data.toUtf8()); - qDebug() << "First layer of XBox auth ... commencing."; -} - -namespace { -bool getDateTime(QJsonValue value, QDateTime & out) { - if(!value.isString()) { - return false; - } - out = QDateTime::fromString(value.toString(), Qt::ISODateWithMs); - return out.isValid(); -} - -bool getString(QJsonValue value, QString & out) { - if(!value.isString()) { - return false; - } - out = value.toString(); - return true; -} - -bool getNumber(QJsonValue value, double & out) { - if(!value.isDouble()) { - return false; - } - out = value.toDouble(); - return true; -} - -/* -{ - "IssueInstant":"2020-12-07T19:52:08.4463796Z", - "NotAfter":"2020-12-21T19:52:08.4463796Z", - "Token":"token", - "DisplayClaims":{ - "xui":[ - { - "uhs":"userhash" - } - ] - } - } -*/ -// TODO: handle error responses ... -/* -{ - "Identity":"0", - "XErr":2148916238, - "Message":"", - "Redirect":"https://start.ui.xboxlive.com/AddChildToFamily" -} -// 2148916233 = missing XBox account -// 2148916238 = child account not linked to a family -*/ - -bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output) { - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if(jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); - qDebug() << data; - return false; - } - - auto obj = doc.object(); - if(!getDateTime(obj.value("IssueInstant"), output.issueInstant)) { - qWarning() << "User IssueInstant is not a timestamp"; - qDebug() << data; - return false; - } - if(!getDateTime(obj.value("NotAfter"), output.notAfter)) { - qWarning() << "User NotAfter is not a timestamp"; - qDebug() << data; - return false; - } - if(!getString(obj.value("Token"), output.token)) { - qWarning() << "User Token is not a timestamp"; - qDebug() << data; - return false; - } - auto arrayVal = obj.value("DisplayClaims").toObject().value("xui"); - if(!arrayVal.isArray()) { - qWarning() << "Missing xui claims array"; - qDebug() << data; - return false; - } - bool foundUHS = false; - for(auto item: arrayVal.toArray()) { - if(!item.isObject()) { - continue; - } - auto obj = item.toObject(); - if(obj.contains("uhs")) { - foundUHS = true; - } else { - continue; - } - // consume all 'display claims' ... whatever that means - for(auto iter = obj.begin(); iter != obj.end(); iter++) { - QString claim; - if(!getString(obj.value(iter.key()), claim)) { - qWarning() << "display claim " << iter.key() << " is not a string..."; - qDebug() << data; - return false; - } - output.extra[iter.key()] = claim; - } - - break; - } - if(!foundUHS) { - qWarning() << "Missing uhs"; - qDebug() << data; - return false; - } - output.validity = Katabasis::Validity::Certain; - qDebug() << data; - return true; -} - -} - -void Context::onUserAuthDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - finishActivity(); - return; - } - - Katabasis::Token temp; - if(!parseXTokenResponse(replyData, temp)) { - qWarning() << "Could not parse user authentication response..."; - finishActivity(); - return; - } - m_account.userToken = temp; - - doSTSAuthMinecraft(); - doSTSAuthGeneric(); -} -/* - url = "https://xsts.auth.xboxlive.com/xsts/authorize" - headers = {"x-xbl-contract-version": "1"} - data = { - "RelyingParty": relying_party, - "TokenType": "JWT", - "Properties": { - "UserTokens": [self.user_token.token], - "SandboxId": "RETAIL", - }, - } -*/ -void Context::doSTSAuthMinecraft() { - QString xbox_auth_template = R"XXX( -{ - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [ - "%1" - ] - }, - "RelyingParty": "rp://api.minecraftservices.com/", - "TokenType": "JWT" -} -)XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_account.userToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onSTSAuthMinecraftDone); - requestor->post(request, xbox_auth_data.toUtf8()); - qDebug() << "Second layer of XBox auth ... commencing."; -} - -void Context::onSTSAuthMinecraftDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - finishActivity(); - return; - } - - Katabasis::Token temp; - if(!parseXTokenResponse(replyData, temp)) { - qWarning() << "Could not parse authorization response for access to mojang services..."; - finishActivity(); - return; - } - - if(temp.extra["uhs"] != m_account.userToken.extra["uhs"]) { - qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING"; - qDebug() << replyData; - finishActivity(); - return; - } - m_account.mojangservicesToken = temp; - - doMinecraftAuth(); -} - -void Context::doSTSAuthGeneric() { - QString xbox_auth_template = R"XXX( -{ - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [ - "%1" - ] - }, - "RelyingParty": "http://xboxlive.com", - "TokenType": "JWT" -} -)XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_account.userToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onSTSAuthGenericDone); - requestor->post(request, xbox_auth_data.toUtf8()); - qDebug() << "Second layer of XBox auth ... commencing."; -} - -void Context::onSTSAuthGenericDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - finishActivity(); - return; - } - - Katabasis::Token temp; - if(!parseXTokenResponse(replyData, temp)) { - qWarning() << "Could not parse authorization response for access to xbox API..."; - finishActivity(); - return; - } - - if(temp.extra["uhs"] != m_account.userToken.extra["uhs"]) { - qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING"; - qDebug() << replyData; - finishActivity(); - return; - } - m_account.xboxApiToken = temp; - - doXBoxProfile(); -} - - -void Context::doMinecraftAuth() { - QString mc_auth_template = R"XXX( -{ - "identityToken": "XBL3.0 x=%1;%2" -} -)XXX"; - auto data = mc_auth_template.arg(m_account.mojangservicesToken.extra["uhs"].toString(), m_account.mojangservicesToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://api.minecraftservices.com/authentication/login_with_xbox")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onMinecraftAuthDone); - requestor->post(request, data.toUtf8()); - qDebug() << "Getting Minecraft access token..."; -} - -namespace { -bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) { - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if(jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); - qDebug() << data; - return false; - } - - auto obj = doc.object(); - double expires_in = 0; - if(!getNumber(obj.value("expires_in"), expires_in)) { - qWarning() << "expires_in is not a valid number"; - qDebug() << data; - return false; - } - auto currentTime = QDateTime::currentDateTimeUtc(); - output.issueInstant = currentTime; - output.notAfter = currentTime.addSecs(expires_in); - - QString username; - if(!getString(obj.value("username"), username)) { - qWarning() << "username is not valid"; - qDebug() << data; - return false; - } - - // TODO: it's a JWT... validate it? - if(!getString(obj.value("access_token"), output.token)) { - qWarning() << "access_token is not valid"; - qDebug() << data; - return false; - } - output.validity = Katabasis::Validity::Certain; - qDebug() << data; - return true; -} -} - -void Context::onMinecraftAuthDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - requestsDone++; - - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - qDebug() << replyData; - finishActivity(); - return; - } - - if(!parseMojangResponse(replyData, m_account.minecraftToken)) { - qWarning() << "Could not parse login_with_xbox response..."; - qDebug() << replyData; - finishActivity(); - return; - } - mcAuthSucceeded = true; - - checkResult(); -} - -void Context::doXBoxProfile() { - auto url = QUrl("https://profile.xboxlive.com/users/me/profile/settings"); - QUrlQuery q; - q.addQueryItem( - "settings", - "GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw," - "PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix," - "UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep," - "PreferredColor,Location,Bio,Watermarks," - "RealName,RealNameOverride,IsQuarantined" - ); - url.setQuery(q); - - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - request.setRawHeader("x-xbl-contract-version", "3"); - request.setRawHeader("Authorization", QString("XBL3.0 x=%1;%2").arg(m_account.userToken.extra["uhs"].toString(), m_account.xboxApiToken.token).toUtf8()); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onXBoxProfileDone); - requestor->get(request); - qDebug() << "Getting Xbox profile..."; -} - -void Context::onXBoxProfileDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - requestsDone ++; - - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - qDebug() << replyData; - finishActivity(); - return; - } - - qDebug() << "XBox profile: " << replyData; - - xboxProfileSucceeded = true; - checkResult(); -} - -void Context::checkResult() { - if(requestsDone != 2) { - return; - } - if(mcAuthSucceeded && xboxProfileSucceeded) { - doMinecraftProfile(); - } - else { - finishActivity(); - } -} - -namespace { -bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) { - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if(jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); - qDebug() << data; - return false; - } - - auto obj = doc.object(); - if(!getString(obj.value("id"), output.id)) { - qWarning() << "minecraft profile id is not a string"; - qDebug() << data; - return false; - } - - if(!getString(obj.value("name"), output.name)) { - qWarning() << "minecraft profile name is not a string"; - qDebug() << data; - return false; - } - - auto skinsArray = obj.value("skins").toArray(); - for(auto skin: skinsArray) { - auto skinObj = skin.toObject(); - Skin skinOut; - if(!getString(skinObj.value("id"), skinOut.id)) { - continue; - } - QString state; - if(!getString(skinObj.value("state"), state)) { - continue; - } - if(state != "ACTIVE") { - continue; - } - if(!getString(skinObj.value("url"), skinOut.url)) { - continue; - } - if(!getString(skinObj.value("variant"), skinOut.variant)) { - continue; - } - // we deal with only the active skin - output.skin = skinOut; - break; - } - auto capesArray = obj.value("capes").toArray(); - int i = -1; - int currentCape = -1; - for(auto cape: capesArray) { - i++; - auto capeObj = cape.toObject(); - Cape capeOut; - if(!getString(capeObj.value("id"), capeOut.id)) { - continue; - } - QString state; - if(!getString(capeObj.value("state"), state)) { - continue; - } - if(state == "ACTIVE") { - currentCape = i; - } - if(!getString(capeObj.value("url"), capeOut.url)) { - continue; - } - if(!getString(capeObj.value("alias"), capeOut.alias)) { - continue; - } - - // we deal with only the active skin - output.capes.push_back(capeOut); - } - output.currentCape = currentCape; - output.validity = Katabasis::Validity::Certain; - return true; -} -} - -void Context::doMinecraftProfile() { - auto url = QUrl("https://api.minecraftservices.com/minecraft/profile"); - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - // request.setRawHeader("Accept", "application/json"); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_account.minecraftToken.token).toUtf8()); - - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onMinecraftProfileDone); - requestor->get(request); -} - -void Context::onMinecraftProfileDone(int, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) { - qDebug() << data; - if (error == QNetworkReply::ContentNotFoundError) { - m_account.minecraftProfile = MinecraftProfile(); - finishActivity(); - return; - } - if (error != QNetworkReply::NoError) { - finishActivity(); - return; - } - if(!parseMinecraftProfile(data, m_account.minecraftProfile)) { - m_account.minecraftProfile = MinecraftProfile(); - finishActivity(); - return; - } - doGetSkin(); -} - -void Context::doGetSkin() { - auto url = QUrl(m_account.minecraftProfile.skin.url); - QNetworkRequest request = QNetworkRequest(url); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - connect(requestor, &Requestor::finished, this, &Context::onSkinDone); - requestor->get(request); -} - -void Context::onSkinDone(int, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair>) { - if (error == QNetworkReply::NoError) { - m_account.minecraftProfile.skin.data = data; - } - finishActivity(); -} - -namespace { -void tokenToJSON(QJsonObject &parent, Katabasis::Token t, const char * tokenName) { - if(t.validity == Katabasis::Validity::None || !t.persistent) { - return; - } - QJsonObject out; - if(t.issueInstant.isValid()) { - out["iat"] = QJsonValue(t.issueInstant.toSecsSinceEpoch()); - } - - if(t.notAfter.isValid()) { - out["exp"] = QJsonValue(t.notAfter.toSecsSinceEpoch()); - } - - if(!t.token.isEmpty()) { - out["token"] = QJsonValue(t.token); - } - if(!t.refresh_token.isEmpty()) { - out["refresh_token"] = QJsonValue(t.refresh_token); - } - if(t.extra.size()) { - out["extra"] = QJsonObject::fromVariantMap(t.extra); - } - if(out.size()) { - parent[tokenName] = out; - } -} - -Katabasis::Token tokenFromJSON(const QJsonObject &parent, const char * tokenName) { - Katabasis::Token out; - auto tokenObject = parent.value(tokenName).toObject(); - if(tokenObject.isEmpty()) { - return out; - } - auto issueInstant = tokenObject.value("iat"); - if(issueInstant.isDouble()) { - out.issueInstant = QDateTime::fromSecsSinceEpoch((int64_t) issueInstant.toDouble()); - } - - auto notAfter = tokenObject.value("exp"); - if(notAfter.isDouble()) { - out.notAfter = QDateTime::fromSecsSinceEpoch((int64_t) notAfter.toDouble()); - } - - auto token = tokenObject.value("token"); - if(token.isString()) { - out.token = token.toString(); - out.validity = Katabasis::Validity::Assumed; - } - - auto refresh_token = tokenObject.value("refresh_token"); - if(refresh_token.isString()) { - out.refresh_token = refresh_token.toString(); - } - - auto extra = tokenObject.value("extra"); - if(extra.isObject()) { - out.extra = extra.toObject().toVariantMap(); - } - return out; -} - -void profileToJSON(QJsonObject &parent, MinecraftProfile p, const char * tokenName) { - if(p.id.isEmpty()) { - return; - } - QJsonObject out; - out["id"] = QJsonValue(p.id); - out["name"] = QJsonValue(p.name); - if(p.currentCape != -1) { - out["cape"] = p.capes[p.currentCape].id; - } - - { - QJsonObject skinObj; - skinObj["id"] = p.skin.id; - skinObj["url"] = p.skin.url; - skinObj["variant"] = p.skin.variant; - if(p.skin.data.size()) { - skinObj["data"] = QString::fromLatin1(p.skin.data.toBase64()); - } - out["skin"] = skinObj; - } - - QJsonArray capesArray; - for(auto & cape: p.capes) { - QJsonObject capeObj; - capeObj["id"] = cape.id; - capeObj["url"] = cape.url; - capeObj["alias"] = cape.alias; - if(cape.data.size()) { - capeObj["data"] = QString::fromLatin1(cape.data.toBase64()); - } - capesArray.push_back(capeObj); - } - out["capes"] = capesArray; - parent[tokenName] = out; -} - -MinecraftProfile profileFromJSON(const QJsonObject &parent, const char * tokenName) { - MinecraftProfile out; - auto tokenObject = parent.value(tokenName).toObject(); - if(tokenObject.isEmpty()) { - return out; - } - { - auto idV = tokenObject.value("id"); - auto nameV = tokenObject.value("name"); - if(!idV.isString() || !nameV.isString()) { - qWarning() << "mandatory profile attributes are missing or of unexpected type"; - return MinecraftProfile(); - } - out.name = nameV.toString(); - out.id = idV.toString(); - } - - { - auto skinV = tokenObject.value("skin"); - if(!skinV.isObject()) { - qWarning() << "skin is missing"; - return MinecraftProfile(); - } - auto skinObj = skinV.toObject(); - auto idV = skinObj.value("id"); - auto urlV = skinObj.value("url"); - auto variantV = skinObj.value("variant"); - if(!idV.isString() || !urlV.isString() || !variantV.isString()) { - qWarning() << "mandatory skin attributes are missing or of unexpected type"; - return MinecraftProfile(); - } - out.skin.id = idV.toString(); - out.skin.url = urlV.toString(); - out.skin.variant = variantV.toString(); - - // data for skin is optional - auto dataV = skinObj.value("data"); - if(dataV.isString()) { - // TODO: validate base64 - out.skin.data = QByteArray::fromBase64(dataV.toString().toLatin1()); - } - else if (!dataV.isUndefined()) { - qWarning() << "skin data is something unexpected"; - return MinecraftProfile(); - } - } - - auto capesV = tokenObject.value("capes"); - if(!capesV.isArray()) { - qWarning() << "capes is not an array!"; - return MinecraftProfile(); - } - auto capesArray = capesV.toArray(); - for(auto capeV: capesArray) { - if(!capeV.isObject()) { - qWarning() << "cape is not an object!"; - return MinecraftProfile(); - } - auto capeObj = capeV.toObject(); - auto idV = capeObj.value("id"); - auto urlV = capeObj.value("url"); - auto aliasV = capeObj.value("alias"); - if(!idV.isString() || !urlV.isString() || !aliasV.isString()) { - qWarning() << "mandatory skin attributes are missing or of unexpected type"; - return MinecraftProfile(); - } - Cape cape; - cape.id = idV.toString(); - cape.url = urlV.toString(); - cape.alias = aliasV.toString(); - - // data for cape is optional. - auto dataV = capeObj.value("data"); - if(dataV.isString()) { - // TODO: validate base64 - cape.data = QByteArray::fromBase64(dataV.toString().toLatin1()); - } - else if (!dataV.isUndefined()) { - qWarning() << "cape data is something unexpected"; - return MinecraftProfile(); - } - out.capes.push_back(cape); - } - out.validity = Katabasis::Validity::Assumed; - return out; -} - -} - -bool Context::resumeFromState(QByteArray data) { - QJsonParseError error; - auto doc = QJsonDocument::fromJson(data, &error); - if(error.error != QJsonParseError::NoError) { - qWarning() << "Failed to parse account data as JSON."; - return false; - } - auto docObject = doc.object(); - m_account.msaToken = tokenFromJSON(docObject, "msa"); - m_account.userToken = tokenFromJSON(docObject, "utoken"); - m_account.xboxApiToken = tokenFromJSON(docObject, "xrp-main"); - m_account.mojangservicesToken = tokenFromJSON(docObject, "xrp-mc"); - m_account.minecraftToken = tokenFromJSON(docObject, "ygg"); - - m_account.minecraftProfile = profileFromJSON(docObject, "profile"); - - m_account.validity_ = m_account.minecraftProfile.validity; - - return true; -} - -QByteArray Context::saveState() { - QJsonDocument doc; - QJsonObject output; - tokenToJSON(output, m_account.msaToken, "msa"); - tokenToJSON(output, m_account.userToken, "utoken"); - tokenToJSON(output, m_account.xboxApiToken, "xrp-main"); - tokenToJSON(output, m_account.mojangservicesToken, "xrp-mc"); - tokenToJSON(output, m_account.minecraftToken, "ygg"); - profileToJSON(output, m_account.minecraftProfile, "profile"); - doc.setObject(output); - return doc.toJson(QJsonDocument::Indented); -} diff --git a/api/logic/minecraft/auth-msa/context.h b/api/logic/minecraft/auth-msa/context.h deleted file mode 100644 index f1ac99b8..00000000 --- a/api/logic/minecraft/auth-msa/context.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include <QObject> -#include <QList> -#include <QVector> -#include <QNetworkReply> -#include <QImage> - -#include <katabasis/OAuth2.h> - -struct Skin { - QString id; - QString url; - QString variant; - - QByteArray data; -}; - -struct Cape { - QString id; - QString url; - QString alias; - - QByteArray data; -}; - -struct MinecraftProfile { - QString id; - QString name; - Skin skin; - int currentCape = -1; - QVector<Cape> capes; - Katabasis::Validity validity = Katabasis::Validity::None; -}; - -enum class AccountType { - MSA, - Mojang -}; - -struct AccountData { - AccountType type = AccountType::MSA; - - Katabasis::Token msaToken; - Katabasis::Token userToken; - Katabasis::Token xboxApiToken; - Katabasis::Token mojangservicesToken; - Katabasis::Token minecraftToken; - - MinecraftProfile minecraftProfile; - Katabasis::Validity validity_ = Katabasis::Validity::None; -}; - -class Context : public QObject -{ - Q_OBJECT - -public: - explicit Context(QObject *parent = 0); - - QByteArray saveState(); - bool resumeFromState(QByteArray data); - - bool isBusy() { - return activity_ != Katabasis::Activity::Idle; - }; - Katabasis::Validity validity() { - return m_account.validity_; - }; - - bool signIn(); - bool silentSignIn(); - bool signOut(); - - QString userName(); - QString userId(); - QString gameToken(); -signals: - void succeeded(); - void failed(); - void activityChanged(Katabasis::Activity activity); - -private slots: - void onLinkingSucceeded(); - void onLinkingFailed(); - void onOpenBrowser(const QUrl &url); - void onCloseBrowser(); - void onOAuthActivityChanged(Katabasis::Activity activity); - -private: - void doUserAuth(); - Q_SLOT void onUserAuthDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void doSTSAuthMinecraft(); - Q_SLOT void onSTSAuthMinecraftDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - void doMinecraftAuth(); - Q_SLOT void onMinecraftAuthDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void doSTSAuthGeneric(); - Q_SLOT void onSTSAuthGenericDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - void doXBoxProfile(); - Q_SLOT void onXBoxProfileDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void doMinecraftProfile(); - Q_SLOT void onMinecraftProfileDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void doGetSkin(); - Q_SLOT void onSkinDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void checkResult(); - -private: - void beginActivity(Katabasis::Activity activity); - void finishActivity(); - void clearTokens(); - -private: - Katabasis::OAuth2 *oauth2 = nullptr; - - int requestsDone = 0; - bool xboxProfileSucceeded = false; - bool mcAuthSucceeded = false; - Katabasis::Activity activity_ = Katabasis::Activity::Idle; - - AccountData m_account; - - QNetworkAccessManager *mgr = nullptr; -}; diff --git a/api/logic/minecraft/auth-msa/main.cpp b/api/logic/minecraft/auth-msa/main.cpp deleted file mode 100644 index 481e0126..00000000 --- a/api/logic/minecraft/auth-msa/main.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include <QApplication> -#include <QStringList> -#include <QTimer> -#include <QDebug> -#include <QFile> -#include <QSaveFile> - -#include "context.h" -#include "mainwindow.h" - -void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - QByteArray localMsg = msg.toLocal8Bit(); - const char *file = context.file ? context.file : ""; - const char *function = context.function ? context.function : ""; - switch (type) { - case QtDebugMsg: - fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - case QtInfoMsg: - fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - case QtWarningMsg: - fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - case QtCriticalMsg: - fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - case QtFatalMsg: - fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - } -} - -class Helper : public QObject { - Q_OBJECT - -public: - Helper(Context * context) : QObject(), context_(context), msg_(QString()) { - QFile tokenCache("usercache.dat"); - if(tokenCache.open(QIODevice::ReadOnly)) { - context_->resumeFromState(tokenCache.readAll()); - } - } - -public slots: - void run() { - connect(context_, &Context::activityChanged, this, &Helper::onActivityChanged); - context_->silentSignIn(); - } - - void onFailed() { - qDebug() << "Login failed"; - } - - void onActivityChanged(Katabasis::Activity activity) { - if(activity == Katabasis::Activity::Idle) { - switch(context_->validity()) { - case Katabasis::Validity::None: { - // account is gone, remove it. - QFile::remove("usercache.dat"); - } - break; - case Katabasis::Validity::Assumed: { - // this is basically a soft-failed refresh. do nothing. - } - break; - case Katabasis::Validity::Certain: { - // stuff got refreshed / signed in. Save. - auto data = context_->saveState(); - QSaveFile tokenCache("usercache.dat"); - if(tokenCache.open(QIODevice::WriteOnly)) { - tokenCache.write(context_->saveState()); - tokenCache.commit(); - } - } - break; - } - } - } - -private: - Context *context_; - QString msg_; -}; - -int main(int argc, char *argv[]) { - qInstallMessageHandler(myMessageOutput); - QApplication a(argc, argv); - QCoreApplication::setOrganizationName("MultiMC"); - QCoreApplication::setApplicationName("MultiMC"); - Context c; - Helper helper(&c); - MainWindow window(&c); - window.show(); - QTimer::singleShot(0, &helper, &Helper::run); - return a.exec(); -} - -#include "main.moc" diff --git a/api/logic/minecraft/auth-msa/mainwindow.cpp b/api/logic/minecraft/auth-msa/mainwindow.cpp deleted file mode 100644 index d4e18dc0..00000000 --- a/api/logic/minecraft/auth-msa/mainwindow.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include <QDebug> - -#include <QDesktopServices> - -#include "BuildConfig.h" - -MainWindow::MainWindow(Context * context, QWidget *parent) : - QMainWindow(parent), - m_context(context), - m_ui(new Ui::MainWindow) -{ - m_ui->setupUi(this); - connect(m_ui->signInButton_MSA, &QPushButton::clicked, this, &MainWindow::SignInMSAClicked); - connect(m_ui->signInButton_Mojang, &QPushButton::clicked, this, &MainWindow::SignInMojangClicked); - connect(m_ui->signOutButton, &QPushButton::clicked, this, &MainWindow::SignOutClicked); - connect(m_ui->refreshButton, &QPushButton::clicked, this, &MainWindow::RefreshClicked); - - // connect(m_context, &Context::linkingSucceeded, this, &MainWindow::SignInSucceeded); - // connect(m_context, &Context::linkingFailed, this, &MainWindow::SignInFailed); - connect(m_context, &Context::activityChanged, this, &MainWindow::ActivityChanged); - ActivityChanged(Katabasis::Activity::Idle); -} - -MainWindow::~MainWindow() = default; - -void MainWindow::ActivityChanged(Katabasis::Activity activity) { - switch(activity) { - case Katabasis::Activity::Idle: { - if(m_context->validity() != Katabasis::Validity::None) { - m_ui->signInButton_Mojang->setEnabled(false); - m_ui->signInButton_MSA->setEnabled(false); - m_ui->signOutButton->setEnabled(true); - m_ui->refreshButton->setEnabled(true); - m_ui->statusBar->showMessage(QString("Hello %1!").arg(m_context->userName())); - } - else { - m_ui->signInButton_Mojang->setEnabled(true); - m_ui->signInButton_MSA->setEnabled(true); - m_ui->signOutButton->setEnabled(false); - m_ui->refreshButton->setEnabled(false); - m_ui->statusBar->showMessage("Press the login button to start."); - } - } - break; - case Katabasis::Activity::LoggingIn: { - m_ui->signInButton_Mojang->setEnabled(false); - m_ui->signInButton_MSA->setEnabled(false); - m_ui->signOutButton->setEnabled(false); - m_ui->refreshButton->setEnabled(false); - m_ui->statusBar->showMessage("Logging in..."); - } - break; - case Katabasis::Activity::LoggingOut: { - m_ui->signInButton_Mojang->setEnabled(false); - m_ui->signInButton_MSA->setEnabled(false); - m_ui->signOutButton->setEnabled(false); - m_ui->refreshButton->setEnabled(false); - m_ui->statusBar->showMessage("Logging out..."); - } - break; - case Katabasis::Activity::Refreshing: { - m_ui->signInButton_Mojang->setEnabled(false); - m_ui->signInButton_MSA->setEnabled(false); - m_ui->signOutButton->setEnabled(false); - m_ui->refreshButton->setEnabled(false); - m_ui->statusBar->showMessage("Refreshing login..."); - } - break; - } -} - -void MainWindow::SignInMSAClicked() { - qDebug() << "Sign In MSA"; - // signIn({{"prompt", "select_account"}}) - // FIXME: wrong. very wrong. this should not be operating on the current context - m_context->signIn(); -} - -void MainWindow::SignInMojangClicked() { - qDebug() << "Sign In Mojang"; - // signIn({{"prompt", "select_account"}}) - // FIXME: wrong. very wrong. this should not be operating on the current context - m_context->signIn(); -} - - -void MainWindow::SignOutClicked() { - qDebug() << "Sign Out"; - m_context->signOut(); -} - -void MainWindow::RefreshClicked() { - qDebug() << "Refresh"; - m_context->silentSignIn(); -} diff --git a/api/logic/minecraft/auth-msa/mainwindow.h b/api/logic/minecraft/auth-msa/mainwindow.h deleted file mode 100644 index abde52d8..00000000 --- a/api/logic/minecraft/auth-msa/mainwindow.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include <QMainWindow> -#include <QScopedPointer> -#include <QtNetwork> -#include <katabasis/Bits.h> - -#include "context.h" - -namespace Ui { -class MainWindow; -} - -class MainWindow : public QMainWindow { - Q_OBJECT - -public: - explicit MainWindow(Context * context, QWidget *parent = nullptr); - ~MainWindow() override; - -private slots: - void SignInMojangClicked(); - void SignInMSAClicked(); - - void SignOutClicked(); - void RefreshClicked(); - - void ActivityChanged(Katabasis::Activity activity); - -private: - Context* m_context; - QScopedPointer<Ui::MainWindow> m_ui; -}; - diff --git a/api/logic/minecraft/auth-msa/mainwindow.ui b/api/logic/minecraft/auth-msa/mainwindow.ui deleted file mode 100644 index 32b34128..00000000 --- a/api/logic/minecraft/auth-msa/mainwindow.ui +++ /dev/null @@ -1,72 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>1037</width> - <height>511</height> - </rect> - </property> - <property name="windowTitle"> - <string>SmartMapsClient</string> - </property> - <property name="dockNestingEnabled"> - <bool>true</bool> - </property> - <widget class="QWidget" name="centralWidget"> - <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="3"> - <widget class="QPushButton" name="signInButton_Mojang"> - <property name="text"> - <string>SignIn Mojang</string> - </property> - </widget> - </item> - <item row="3" column="3"> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="1" column="0" rowspan="7" colspan="3"> - <widget class="QTreeView" name="accountView"/> - </item> - <item row="5" column="3"> - <widget class="QPushButton" name="refreshButton"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> - </item> - <item row="2" column="3"> - <widget class="QPushButton" name="signInButton_MSA"> - <property name="text"> - <string>SignIn MSA</string> - </property> - </widget> - </item> - <item row="6" column="3"> - <widget class="QPushButton" name="signOutButton"> - <property name="text"> - <string>SignOut</string> - </property> - </widget> - </item> - <item row="4" column="3"> - <widget class="QPushButton" name="makeActiveButton"> - <property name="text"> - <string>Make Active</string> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QStatusBar" name="statusBar"/> - </widget> - <resources/> - <connections/> -</ui> diff --git a/api/logic/minecraft/auth/AuthSession.cpp b/api/logic/minecraft/auth/AuthSession.cpp deleted file mode 100644 index 4e858796..00000000 --- a/api/logic/minecraft/auth/AuthSession.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "AuthSession.h" -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonDocument> -#include <QStringList> - -QString AuthSession::serializeUserProperties() -{ - QJsonObject userAttrs; - for (auto key : u.properties.keys()) - { - auto array = QJsonArray::fromStringList(u.properties.values(key)); - userAttrs.insert(key, array); - } - QJsonDocument value(userAttrs); - return value.toJson(QJsonDocument::Compact); - -} - -bool AuthSession::MakeOffline(QString offline_playername) -{ - if (status != PlayableOffline && status != PlayableOnline) - { - return false; - } - session = "-"; - player_name = offline_playername; - status = PlayableOffline; - return true; -} diff --git a/api/logic/minecraft/auth/AuthSession.h b/api/logic/minecraft/auth/AuthSession.h deleted file mode 100644 index b397d9a1..00000000 --- a/api/logic/minecraft/auth/AuthSession.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include <QString> -#include <QMultiMap> -#include <memory> - -#include "multimc_logic_export.h" - -class MojangAccount; - -struct User -{ - QString id; - QMultiMap<QString, QString> properties; -}; - -struct MULTIMC_LOGIC_EXPORT AuthSession -{ - bool MakeOffline(QString offline_playername); - - QString serializeUserProperties(); - - enum Status - { - Undetermined, - RequiresPassword, - PlayableOffline, - PlayableOnline - } status = Undetermined; - - User u; - - // client token - QString client_token; - // account user name - QString username; - // combined session ID - QString session; - // volatile auth token - QString access_token; - // profile name - QString player_name; - // profile ID - QString uuid; - // 'legacy' or 'mojang', depending on account type - QString user_type; - // Did the auth server reply? - bool auth_server_online = false; - // Did the user request online mode? - bool wants_online = true; - std::shared_ptr<MojangAccount> m_accountPtr; -}; - -typedef std::shared_ptr<AuthSession> AuthSessionPtr; diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp deleted file mode 100644 index f5853fe3..00000000 --- a/api/logic/minecraft/auth/MojangAccount.cpp +++ /dev/null @@ -1,315 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Authors: Orochimarufan <orochimarufan.x3@gmail.com> - * - * 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 "MojangAccount.h" -#include "flows/RefreshTask.h" -#include "flows/AuthenticateTask.h" - -#include <QUuid> -#include <QJsonObject> -#include <QJsonArray> -#include <QRegExp> -#include <QStringList> -#include <QJsonDocument> - -#include <QDebug> - -MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) -{ - // The JSON object must at least have a username for it to be valid. - if (!object.value("username").isString()) - { - qCritical() << "Can't load Mojang account info from JSON object. Username field is " - "missing or of the wrong type."; - return nullptr; - } - - QString username = object.value("username").toString(""); - QString clientToken = object.value("clientToken").toString(""); - QString accessToken = object.value("accessToken").toString(""); - - QJsonArray profileArray = object.value("profiles").toArray(); - if (profileArray.size() < 1) - { - qCritical() << "Can't load Mojang account with username \"" << username - << "\". No profiles found."; - return nullptr; - } - - QList<AccountProfile> profiles; - for (QJsonValue profileVal : profileArray) - { - QJsonObject profileObject = profileVal.toObject(); - QString id = profileObject.value("id").toString(""); - QString name = profileObject.value("name").toString(""); - bool legacy = profileObject.value("legacy").toBool(false); - if (id.isEmpty() || name.isEmpty()) - { - qWarning() << "Unable to load a profile because it was missing an ID or a name."; - continue; - } - profiles.append({id, name, legacy}); - } - - MojangAccountPtr account(new MojangAccount()); - if (object.value("user").isObject()) - { - User u; - QJsonObject userStructure = object.value("user").toObject(); - u.id = userStructure.value("id").toString(); - /* - QJsonObject propMap = userStructure.value("properties").toObject(); - for(auto key: propMap.keys()) - { - auto values = propMap.operator[](key).toArray(); - for(auto value: values) - u.properties.insert(key, value.toString()); - } - */ - account->m_user = u; - } - account->m_username = username; - account->m_clientToken = clientToken; - account->m_accessToken = accessToken; - account->m_profiles = profiles; - - // Get the currently selected profile. - QString currentProfile = object.value("activeProfile").toString(""); - if (!currentProfile.isEmpty()) - account->setCurrentProfile(currentProfile); - - return account; -} - -MojangAccountPtr MojangAccount::createFromUsername(const QString &username) -{ - MojangAccountPtr account(new MojangAccount()); - account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - account->m_username = username; - return account; -} - -QJsonObject MojangAccount::saveToJson() const -{ - QJsonObject json; - json.insert("username", m_username); - json.insert("clientToken", m_clientToken); - json.insert("accessToken", m_accessToken); - - QJsonArray profileArray; - for (AccountProfile profile : m_profiles) - { - QJsonObject profileObj; - profileObj.insert("id", profile.id); - profileObj.insert("name", profile.name); - profileObj.insert("legacy", profile.legacy); - profileArray.append(profileObj); - } - json.insert("profiles", profileArray); - - QJsonObject userStructure; - { - userStructure.insert("id", m_user.id); - /* - QJsonObject userAttrs; - for(auto key: m_user.properties.keys()) - { - auto array = QJsonArray::fromStringList(m_user.properties.values(key)); - userAttrs.insert(key, array); - } - userStructure.insert("properties", userAttrs); - */ - } - json.insert("user", userStructure); - - if (m_currentProfile != -1) - json.insert("activeProfile", currentProfile()->id); - - return json; -} - -bool MojangAccount::setCurrentProfile(const QString &profileId) -{ - for (int i = 0; i < m_profiles.length(); i++) - { - if (m_profiles[i].id == profileId) - { - m_currentProfile = i; - return true; - } - } - return false; -} - -const AccountProfile *MojangAccount::currentProfile() const -{ - if (m_currentProfile == -1) - return nullptr; - return &m_profiles[m_currentProfile]; -} - -AccountStatus MojangAccount::accountStatus() const -{ - if (m_accessToken.isEmpty()) - return NotVerified; - else - return Verified; -} - -std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, QString password) -{ - Q_ASSERT(m_currentTask.get() == nullptr); - - // take care of the true offline status - if (accountStatus() == NotVerified && password.isEmpty()) - { - if (session) - { - session->status = AuthSession::RequiresPassword; - fillSession(session); - } - return nullptr; - } - - if(accountStatus() == Verified && !session->wants_online) - { - session->status = AuthSession::PlayableOffline; - session->auth_server_online = false; - fillSession(session); - return nullptr; - } - else - { - 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))); - } - return m_currentTask; -} - -void MojangAccount::authSucceeded() -{ - auto session = m_currentTask->getAssignedSession(); - if (session) - { - session->status = - session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline; - fillSession(session); - session->auth_server_online = true; - } - m_currentTask.reset(); - emit changed(); -} - -void MojangAccount::authFailed(QString reason) -{ - auto session = m_currentTask->getAssignedSession(); - // This is emitted when the yggdrasil tasks time out or are cancelled. - // -> we treat the error as no-op - if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT) - { - if (session) - { - session->status = accountStatus() == Verified ? AuthSession::PlayableOffline - : AuthSession::RequiresPassword; - session->auth_server_online = false; - fillSession(session); - } - } - else - { - m_accessToken = QString(); - emit changed(); - if (session) - { - session->status = AuthSession::RequiresPassword; - session->auth_server_online = true; - fillSession(session); - } - } - m_currentTask.reset(); -} - -void MojangAccount::fillSession(AuthSessionPtr session) -{ - // the user name. you have to have an user name - session->username = m_username; - // volatile auth token - session->access_token = m_accessToken; - // the semi-permanent client token - session->client_token = m_clientToken; - if (currentProfile()) - { - // profile name - session->player_name = currentProfile()->name; - // profile ID - session->uuid = currentProfile()->id; - // 'legacy' or 'mojang', depending on account type - session->user_type = currentProfile()->legacy ? "legacy" : "mojang"; - if (!session->access_token.isEmpty()) - { - session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; - } - else - { - session->session = "-"; - } - } - else - { - session->player_name = "Player"; - session->session = "-"; - } - session->u = user(); - session->m_accountPtr = shared_from_this(); -} - -void MojangAccount::decrementUses() -{ - Usable::decrementUses(); - if(!isInUse()) - { - emit changed(); - qWarning() << "Account" << m_username << "is no longer in use."; - } -} - -void MojangAccount::incrementUses() -{ - bool wasInUse = isInUse(); - Usable::incrementUses(); - if(!wasInUse) - { - emit changed(); - qWarning() << "Account" << m_username << "is now in use."; - } -} - -void MojangAccount::invalidateClientToken() -{ - m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - emit changed(); -} diff --git a/api/logic/minecraft/auth/MojangAccount.h b/api/logic/minecraft/auth/MojangAccount.h deleted file mode 100644 index 30a5f2ff..00000000 --- a/api/logic/minecraft/auth/MojangAccount.h +++ /dev/null @@ -1,182 +0,0 @@ -/* Copyright 2013-2021 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 <QString> -#include <QList> -#include <QJsonObject> -#include <QPair> -#include <QMap> - -#include <memory> -#include "AuthSession.h" -#include "Usable.h" - -#include "multimc_logic_export.h" - -class Task; -class YggdrasilTask; -class MojangAccount; - -typedef std::shared_ptr<MojangAccount> MojangAccountPtr; -Q_DECLARE_METATYPE(MojangAccountPtr) - -/** - * A profile within someone's Mojang account. - * - * Currently, the profile system has not been implemented by Mojang yet, - * but we might as well add some things for it in MultiMC right now so - * we don't have to rip the code to pieces to add it later. - */ -struct AccountProfile -{ - QString id; - QString name; - bool legacy; -}; - -enum AccountStatus -{ - NotVerified, - Verified -}; - -/** - * Object that stores information about a certain Mojang account. - * - * Said information may include things such as that account's username, client token, and access - * token if the user chose to stay logged in. - */ -class MULTIMC_LOGIC_EXPORT MojangAccount : - public QObject, - public Usable, - public std::enable_shared_from_this<MojangAccount> -{ - Q_OBJECT -public: /* construction */ - //! Do not copy accounts. ever. - explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; - - //! Default constructor - explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; - - //! Creates an empty account for the specified user name. - static MojangAccountPtr createFromUsername(const QString &username); - - //! Loads a MojangAccount from the given JSON object. - static MojangAccountPtr loadFromJson(const QJsonObject &json); - - //! Saves a MojangAccount to a JSON object and returns it. - QJsonObject saveToJson() const; - -public: /* manipulation */ - /** - * Sets the currently selected profile to the profile with the given ID string. - * If profileId is not in the list of available profiles, the function will simply return - * false. - */ - bool setCurrentProfile(const QString &profileId); - - /** - * Attempt to login. Empty password means we use the token. - * If the attempt fails because we already are performing some task, it returns false. - */ - std::shared_ptr<YggdrasilTask> login(AuthSessionPtr session, QString password = QString()); - void invalidateClientToken(); - -public: /* queries */ - const QString &username() const - { - return m_username; - } - - const QString &clientToken() const - { - return m_clientToken; - } - - const QString &accessToken() const - { - return m_accessToken; - } - - const QList<AccountProfile> &profiles() const - { - return m_profiles; - } - - const User &user() - { - return m_user; - } - - //! Returns the currently selected profile (if none, returns nullptr) - const AccountProfile *currentProfile() const; - - //! Returns whether the account is NotVerified, Verified or Online - AccountStatus accountStatus() const; - -signals: - /** - * This signal is emitted when the account changes - */ - void changed(); - - // TODO: better signalling for the various possible state changes - especially errors - -protected: /* variables */ - QString m_username; - - // Used to identify the client - the user can have multiple clients for the same account - // Think: different launchers, all connecting to the same account/profile - QString m_clientToken; - - // Blank if not logged in. - QString m_accessToken; - - // Index of the selected profile within the list of available - // profiles. -1 if nothing is selected. - int m_currentProfile = -1; - - // List of available profiles. - QList<AccountProfile> m_profiles; - - // the user structure, whatever it is. - User m_user; - - // current task we are executing here - std::shared_ptr<YggdrasilTask> m_currentTask; - -protected: /* methods */ - - void incrementUses() override; - void decrementUses() override; - -private -slots: - void authSucceeded(); - void authFailed(QString reason); - -private: - void fillSession(AuthSessionPtr session); - -public: - friend class YggdrasilTask; - friend class AuthenticateTask; - friend class ValidateTask; - friend class RefreshTask; -}; diff --git a/api/logic/minecraft/auth/MojangAccountList.cpp b/api/logic/minecraft/auth/MojangAccountList.cpp deleted file mode 100644 index e584cb3b..00000000 --- a/api/logic/minecraft/auth/MojangAccountList.cpp +++ /dev/null @@ -1,468 +0,0 @@ -/* Copyright 2013-2021 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 "MojangAccountList.h" -#include "MojangAccount.h" - -#include <QIODevice> -#include <QFile> -#include <QTextStream> -#include <QJsonDocument> -#include <QJsonArray> -#include <QJsonObject> -#include <QJsonParseError> -#include <QDir> - -#include <QDebug> - -#include <FileSystem.h> - -#define ACCOUNT_LIST_FORMAT_VERSION 2 - -MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent) -{ -} - -MojangAccountPtr MojangAccountList::findAccount(const QString &username) const -{ - for (int i = 0; i < count(); i++) - { - MojangAccountPtr account = at(i); - if (account->username() == username) - return account; - } - return nullptr; -} - -const MojangAccountPtr MojangAccountList::at(int i) const -{ - return MojangAccountPtr(m_accounts.at(i)); -} - -void MojangAccountList::addAccount(const MojangAccountPtr account) -{ - int row = m_accounts.count(); - beginInsertRows(QModelIndex(), row, row); - connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); - m_accounts.append(account); - endInsertRows(); - onListChanged(); -} - -void MojangAccountList::removeAccount(const QString &username) -{ - int idx = 0; - for (auto account : m_accounts) - { - if (account->username() == username) - { - beginRemoveRows(QModelIndex(), idx, idx); - m_accounts.removeOne(account); - endRemoveRows(); - return; - } - idx++; - } - onListChanged(); -} - -void MojangAccountList::removeAccount(QModelIndex index) -{ - 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 -{ - return m_activeAccount; -} - -void MojangAccountList::setActiveAccount(const QString &username) -{ - 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) - { - 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(); - } - } -} - -void MojangAccountList::accountChanged() -{ - // the list changed. there is no doubt. - onListChanged(); -} - -void MojangAccountList::onListChanged() -{ - if (m_autosave) - // TODO: Alert the user if this fails. - saveList(); - - emit listChanged(); -} - -void MojangAccountList::onActiveChanged() -{ - if (m_autosave) - saveList(); - - emit activeAccountChanged(); -} - -int MojangAccountList::count() const -{ - return m_accounts.count(); -} - -QVariant MojangAccountList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - MojangAccountPtr account = at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - switch (index.column()) - { - case NameColumn: - return account->username(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return account->username(); - - case PointerRole: - return qVariantFromValue(account); - - case Qt::CheckStateRole: - switch (index.column()) - { - case ActiveColumn: - return account == m_activeAccount ? Qt::Checked : Qt::Unchecked; - } - - default: - return QVariant(); - } -} - -QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return tr("Active?"); - - case NameColumn: - return tr("Name"); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case NameColumn: - return tr("The name of the version."); - - default: - return QVariant(); - } - - default: - return QVariant(); - } -} - -int MojangAccountList::rowCount(const QModelIndex &) const -{ - // Return count - return count(); -} - -int MojangAccountList::columnCount(const QModelIndex &) const -{ - return 2; -} - -Qt::ItemFlags MojangAccountList::flags(const QModelIndex &index) const -{ - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return Qt::NoItemFlags; - } - - return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; -} - -bool MojangAccountList::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) - { - if(value == Qt::Checked) - { - MojangAccountPtr account = this->at(index.row()); - this->setActiveAccount(account->username()); - } - } - - emit dataChanged(index, index); - return true; -} - -void MojangAccountList::updateListData(QList<MojangAccountPtr> versions) -{ - beginResetModel(); - m_accounts = versions; - endResetModel(); -} - -bool MojangAccountList::loadList(const QString &filePath) -{ - QString path = filePath; - if (path.isEmpty()) - path = m_listFilePath; - if (path.isEmpty()) - { - qCritical() << "Can't load Mojang account list. No file path given and no default set."; - return false; - } - - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::ReadOnly)) - { - qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); - return false; - } - - // Read the file and close it. - QByteArray jsonData = file.readAll(); - file.close(); - - QJsonParseError parseError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); - - // Fail if the JSON is invalid. - if (parseError.error != QJsonParseError::NoError) - { - qCritical() << QString("Failed to parse account list file: %1 at offset %2") - .arg(parseError.errorString(), QString::number(parseError.offset)) - .toUtf8(); - return false; - } - - // Make sure the root is an object. - if (!jsonDoc.isObject()) - { - qCritical() << "Invalid account list JSON: Root should be an array."; - return false; - } - - QJsonObject root = jsonDoc.object(); - - // Make sure the format version matches. - if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION) - { - QString newName = "accounts-old.json"; - qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to" - << newName; - - // Attempt to rename the old version. - file.rename(newName); - return false; - } - - // Now, load the accounts array. - beginResetModel(); - QJsonArray accounts = root.value("accounts").toArray(); - for (QJsonValue accountVal : accounts) - { - QJsonObject accountObj = accountVal.toObject(); - MojangAccountPtr account = MojangAccount::loadFromJson(accountObj); - if (account.get() != nullptr) - { - connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); - m_accounts.append(account); - } - else - { - qWarning() << "Failed to load an account."; - } - } - // Load the active account. - m_activeAccount = findAccount(root.value("activeAccount").toString("")); - endResetModel(); - return true; -} - -bool MojangAccountList::saveList(const QString &filePath) -{ - QString path(filePath); - if (path.isEmpty()) - path = m_listFilePath; - if (path.isEmpty()) - { - qCritical() << "Can't save Mojang account list. No file path given and no default set."; - return false; - } - - // make sure the parent folder exists - if(!FS::ensureFilePathExists(path)) - return false; - - // make sure the file wasn't overwritten with a folder before (fixes a bug) - QFileInfo finfo(path); - if(finfo.isDir()) - { - QDir badDir(path); - badDir.removeRecursively(); - } - - qDebug() << "Writing account list to" << path; - - qDebug() << "Building JSON data structure."; - // Build the JSON document to write to the list file. - QJsonObject root; - - root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION); - - // Build a list of accounts. - qDebug() << "Building account array."; - QJsonArray accounts; - for (MojangAccountPtr account : m_accounts) - { - QJsonObject accountObj = account->saveToJson(); - accounts.append(accountObj); - } - - // Insert the account list into the root object. - root.insert("accounts", accounts); - - if(m_activeAccount) - { - // Save the active account. - root.insert("activeAccount", m_activeAccount->username()); - } - - // Create a JSON document object to convert our JSON to bytes. - QJsonDocument doc(root); - - // Now that we're done building the JSON object, we can write it to the file. - qDebug() << "Writing account list to file."; - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::WriteOnly)) - { - qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); - return false; - } - - // Write the JSON to the file. - file.write(doc.toJson()); - file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser); - file.close(); - - qDebug() << "Saved account list to" << path; - - return true; -} - -void MojangAccountList::setListFilePath(QString path, bool autosave) -{ - m_listFilePath = path; - m_autosave = autosave; -} - -bool MojangAccountList::anyAccountIsValid() -{ - for(auto account:m_accounts) - { - if(account->accountStatus() != NotVerified) - return true; - } - return false; -} diff --git a/api/logic/minecraft/auth/MojangAccountList.h b/api/logic/minecraft/auth/MojangAccountList.h deleted file mode 100644 index cc3a61a2..00000000 --- a/api/logic/minecraft/auth/MojangAccountList.h +++ /dev/null @@ -1,201 +0,0 @@ -/* Copyright 2013-2021 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 "MojangAccount.h" - -#include <QObject> -#include <QVariant> -#include <QAbstractListModel> -#include <QSharedPointer> - -#include "multimc_logic_export.h" - -/*! - * \brief List of available Mojang accounts. - * This should be loaded in the background by MultiMC on startup. - * - * This class also inherits from QAbstractListModel. Methods from that - * class determine how this list shows up in a list view. Said methods - * all have a default implementation, but they can be overridden by subclasses to - * change the behavior of the list. - */ -class MULTIMC_LOGIC_EXPORT MojangAccountList : public QAbstractListModel -{ - Q_OBJECT -public: - enum ModelRoles - { - PointerRole = 0x34B1CB48 - }; - - enum VListColumns - { - // TODO: Add icon column. - - // First column - Active? - ActiveColumn = 0, - - // Second column - Name - NameColumn, - }; - - explicit MojangAccountList(QObject *parent = 0); - - //! Gets the account at the given index. - virtual const MojangAccountPtr at(int i) const; - - //! Returns the number of accounts in the list. - virtual int count() const; - - //////// List Model Functions //////// - virtual QVariant data(const QModelIndex &index, int role) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual int rowCount(const QModelIndex &parent) const; - virtual int columnCount(const QModelIndex &parent) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role); - - /*! - * Adds a the given Mojang account to the account list. - */ - virtual void addAccount(const MojangAccountPtr account); - - /*! - * Removes the mojang account with the given username from the account list. - */ - virtual void removeAccount(const QString &username); - - /*! - * Removes the account at the given QModelIndex. - */ - virtual void removeAccount(QModelIndex index); - - /*! - * \brief Finds an account by its username. - * \param The username of the account to find. - * \return A const pointer to the account with the given username. NULL if - * one doesn't exist. - */ - virtual MojangAccountPtr findAccount(const QString &username) const; - - /*! - * Sets the default path to save the list file to. - * If autosave is true, this list will automatically save to the given path whenever it changes. - * THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately - * after calling this function to ensure an autosaved change doesn't overwrite the list you intended - * to load. - */ - virtual void setListFilePath(QString path, bool autosave = false); - - /*! - * \brief Loads the account list from the given file path. - * If the given file is an empty string (default), will load from the default account list file. - * \return True if successful, otherwise false. - */ - virtual bool loadList(const QString &file = ""); - - /*! - * \brief Saves the account list to the given file. - * If the given file is an empty string (default), will save from the default account list file. - * \return True if successful, otherwise false. - */ - virtual bool saveList(const QString &file = ""); - - /*! - * \brief Gets a pointer to the account that the user has selected as their "active" account. - * Which account is active can be overridden on a per-instance basis, but this will return the one that - * is set as active globally. - * \return The currently active MojangAccount. If there isn't an active account, returns a null pointer. - */ - virtual MojangAccountPtr activeAccount() const; - - /*! - * Sets the given account as the current active account. - * If the username given is an empty string, sets the active account to nothing. - */ - virtual void setActiveAccount(const QString &username); - - /*! - * Returns true if any of the account is at least Validated - */ - bool anyAccountIsValid(); - -signals: - /*! - * Signal emitted to indicate that the account list has changed. - * This will also fire if the value of an element in the list changes (will be implemented - * later). - */ - void listChanged(); - - /*! - * Signal emitted to indicate that the active account has changed. - */ - void activeAccountChanged(); - -public -slots: - /** - * This is called when one of the accounts changes and the list needs to be updated - */ - void accountChanged(); - -protected: - /*! - * Called whenever the list changes. - * This emits the listChanged() signal and autosaves the list (if autosave is enabled). - */ - void onListChanged(); - - /*! - * Called whenever the active account changes. - * Emits the activeAccountChanged() signal and autosaves the list if enabled. - */ - void onActiveChanged(); - - QList<MojangAccountPtr> m_accounts; - - /*! - * Account that is currently active. - */ - MojangAccountPtr m_activeAccount; - - //! Path to the account list file. Empty string if there isn't one. - QString m_listFilePath; - - /*! - * If true, the account list will automatically save to the account list path when it changes. - * Ignored if m_listFilePath is blank. - */ - bool m_autosave = false; - -protected -slots: - /*! - * Updates this list with the given list of accounts. - * This is done by copying each account in the given list and inserting it - * into this one. - * We need to do this so that we can set the parents of the accounts are set to this - * account list. This can't be done in the load task, because the accounts the load - * task creates are on the load task's thread and Qt won't allow their parents - * to be set to something created on another thread. - * To get around that problem, we invoke this method on the GUI thread, which - * then copies the accounts and sets their parents correctly. - * \param accounts List of accounts whose parents should be set. - */ - virtual void updateListData(QList<MojangAccountPtr> versions); -}; diff --git a/api/logic/minecraft/auth/YggdrasilTask.cpp b/api/logic/minecraft/auth/YggdrasilTask.cpp deleted file mode 100644 index 0857b46b..00000000 --- a/api/logic/minecraft/auth/YggdrasilTask.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/* Copyright 2013-2021 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 "YggdrasilTask.h" -#include "MojangAccount.h" - -#include <QObject> -#include <QString> -#include <QJsonObject> -#include <QJsonDocument> -#include <QNetworkReply> -#include <QByteArray> - -#include <Env.h> - -#include <BuildConfig.h> - -#include <QDebug> - -YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent) - : Task(parent), m_account(account) -{ - changeState(STATE_CREATED); -} - -void YggdrasilTask::executeTask() -{ - changeState(STATE_SENDING_REQUEST); - - // Get the content of the request we're going to send to the server. - QJsonDocument doc(getRequestContent()); - - QUrl reqUrl(BuildConfig.AUTH_BASE + getEndpoint()); - QNetworkRequest netRequest(reqUrl); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - QByteArray requestData = doc.toJson(); - m_netReply = ENV.qnam().post(netRequest, requestData); - connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply); - connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers); - connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers); - connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors); - timeout_keeper.setSingleShot(true); - timeout_keeper.start(timeout_max); - counter.setSingleShot(false); - counter.start(time_step); - progress(0, timeout_max); - connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout); - connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat); -} - -void YggdrasilTask::refreshTimers(qint64, qint64) -{ - timeout_keeper.stop(); - timeout_keeper.start(timeout_max); - progress(count = 0, timeout_max); -} -void YggdrasilTask::heartbeat() -{ - count += time_step; - progress(count, timeout_max); -} - -bool YggdrasilTask::abort() -{ - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = YggdrasilTask::BY_USER; - m_netReply->abort(); - return true; -} - -void YggdrasilTask::abortByTimeout() -{ - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = YggdrasilTask::BY_TIMEOUT; - m_netReply->abort(); -} - -void YggdrasilTask::sslErrors(QList<QSslError> errors) -{ - int i = 1; - for (auto error : errors) - { - qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -void YggdrasilTask::processReply() -{ - changeState(STATE_PROCESSING_RESPONSE); - - switch (m_netReply->error()) - { - case QNetworkReply::NoError: - break; - case QNetworkReply::TimeoutError: - changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out.")); - return; - case QNetworkReply::OperationCanceledError: - changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled.")); - return; - case QNetworkReply::SslHandshakeFailedError: - changeState( - STATE_FAILED_SOFT, - tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>" - "<ul>" - "<li>You use Windows XP and need to <a " - "href=\"https://www.microsoft.com/en-us/download/details.aspx?id=38918\">update " - "your root certificates</a></li>" - "<li>Some device on your network is interfering with SSL traffic. In that case, " - "you have bigger worries than Minecraft not starting.</li>" - "<li>Possibly something else. Check the MultiMC log file for details</li>" - "</ul>")); - return; - // used for invalid credentials and similar errors. Fall through. - case QNetworkReply::ContentAccessDenied: - case QNetworkReply::ContentOperationNotPermittedError: - break; - default: - changeState(STATE_FAILED_SOFT, - tr("Authentication operation failed due to a network error: %1 (%2)") - .arg(m_netReply->errorString()).arg(m_netReply->error())); - return; - } - - // Try to parse the response regardless of the response code. - // Sometimes the auth server will give more information and an error code. - QJsonParseError jsonError; - QByteArray replyData = m_netReply->readAll(); - QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); - // Check the response code. - int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) - { - // If the response code was 200, then there shouldn't be an error. Make sure - // anyways. - // Also, sometimes an empty reply indicates success. If there was no data received, - // pass an empty json object to the processResponse function. - if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) - { - processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()); - return; - } - else - { - changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response " - "JSON response: %1 at offset %2.") - .arg(jsonError.errorString()) - .arg(jsonError.offset)); - qCritical() << replyData; - } - return; - } - - // If the response code was not 200, then Yggdrasil may have given us information - // about the error. - // If we can parse the response, then get information from it. Otherwise just say - // there was an unknown error. - if (jsonError.error == QJsonParseError::NoError) - { - // We were able to parse the server's response. Woo! - // Call processError. If a subclass has overridden it then they'll handle their - // stuff there. - qDebug() << "The request failed, but the server gave us an error message. " - "Processing error."; - processError(doc.object()); - } - else - { - // The server didn't say anything regarding the error. Give the user an unknown - // error. - qDebug() - << "The request failed and the server gave no error message. Unknown error."; - changeState(STATE_FAILED_SOFT, - tr("An unknown error occurred when trying to communicate with the " - "authentication server: %1").arg(m_netReply->errorString())); - } -} - -void YggdrasilTask::processError(QJsonObject responseData) -{ - QJsonValue errorVal = responseData.value("error"); - QJsonValue errorMessageValue = responseData.value("errorMessage"); - QJsonValue causeVal = responseData.value("cause"); - - if (errorVal.isString() && errorMessageValue.isString()) - { - m_error = std::shared_ptr<Error>(new Error{ - errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); - changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose); - } - else - { - // Error is not in standard format. Don't set m_error and return unknown error. - changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred.")); - } -} - -QString YggdrasilTask::getStateMessage() const -{ - switch (m_state) - { - case STATE_CREATED: - return "Waiting..."; - case STATE_SENDING_REQUEST: - return tr("Sending request to auth servers..."); - case STATE_PROCESSING_RESPONSE: - return tr("Processing response from servers..."); - case STATE_SUCCEEDED: - return tr("Authentication task succeeded."); - case STATE_FAILED_SOFT: - return tr("Failed to contact the authentication server."); - case STATE_FAILED_HARD: - return tr("Failed to authenticate."); - default: - return tr("..."); - } -} - -void YggdrasilTask::changeState(YggdrasilTask::State newState, QString reason) -{ - m_state = newState; - setStatus(getStateMessage()); - if (newState == STATE_SUCCEEDED) - { - emitSucceeded(); - } - else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT) - { - emitFailed(reason); - } -} - -YggdrasilTask::State YggdrasilTask::state() -{ - return m_state; -} diff --git a/api/logic/minecraft/auth/YggdrasilTask.h b/api/logic/minecraft/auth/YggdrasilTask.h deleted file mode 100644 index 8af2e132..00000000 --- a/api/logic/minecraft/auth/YggdrasilTask.h +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright 2013-2021 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 <tasks/Task.h> - -#include <QString> -#include <QJsonObject> -#include <QTimer> -#include <qsslerror.h> - -#include "MojangAccount.h" - -class QNetworkReply; - -/** - * A Yggdrasil task is a task that performs an operation on a given mojang account. - */ -class YggdrasilTask : public Task -{ - Q_OBJECT -public: - explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); - virtual ~YggdrasilTask() {}; - - /** - * assign a session to this task. the session will be filled with required infomration - * upon completion - */ - void assignSession(AuthSessionPtr session) - { - m_session = session; - } - - /// get the assigned session for filling with information. - AuthSessionPtr getAssignedSession() - { - return m_session; - } - - /** - * Class describing a Yggdrasil error response. - */ - struct Error - { - QString m_errorMessageShort; - QString m_errorMessageVerbose; - QString m_cause; - }; - - enum AbortedBy - { - BY_NOTHING, - BY_USER, - BY_TIMEOUT - } m_aborted = BY_NOTHING; - - /** - * Enum for describing the state of the current task. - * Used by the getStateMessage function to determine what the status message should be. - */ - enum State - { - STATE_CREATED, - STATE_SENDING_REQUEST, - STATE_PROCESSING_RESPONSE, - STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated - STATE_FAILED_HARD, //!< hard failure. auth is invalid - STATE_SUCCEEDED - } m_state = STATE_CREATED; - -protected: - - virtual void executeTask() override; - - /** - * Gets the JSON object that will be sent to the authentication server. - * Should be overridden by subclasses. - */ - virtual QJsonObject getRequestContent() const = 0; - - /** - * Gets the endpoint to POST to. - * No leading slash. - */ - virtual QString getEndpoint() const = 0; - - /** - * Processes the response received from the server. - * If an error occurred, this should emit a failed signal and return false. - * If Yggdrasil gave an error response, it should call setError() first, and then return false. - * Otherwise, it should return true. - * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with - * an empty QJsonObject. - */ - virtual void processResponse(QJsonObject responseData) = 0; - - /** - * Processes an error response received from the server. - * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error. - * \returns a QString error message that will be passed to emitFailed. - */ - virtual void processError(QJsonObject responseData); - - /** - * Returns the state message for the given state. - * Used to set the status message for the task. - * Should be overridden by subclasses that want to change messages for a given state. - */ - virtual QString getStateMessage() const; - -protected -slots: - void processReply(); - void refreshTimers(qint64, qint64); - void heartbeat(); - void sslErrors(QList<QSslError>); - - void changeState(State newState, QString reason=QString()); -public -slots: - virtual bool abort() override; - void abortByTimeout(); - State state(); -protected: - // FIXME: segfault disaster waiting to happen - MojangAccount *m_account = nullptr; - QNetworkReply *m_netReply = nullptr; - std::shared_ptr<Error> m_error; - QTimer timeout_keeper; - QTimer counter; - int count = 0; // num msec since time reset - - const int timeout_max = 30000; - const int time_step = 50; - - AuthSessionPtr m_session; -}; diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp deleted file mode 100644 index 2e8dc859..00000000 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp +++ /dev/null @@ -1,202 +0,0 @@ - -/* Copyright 2013-2021 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 "AuthenticateTask.h" -#include "../MojangAccount.h" - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QVariant> - -#include <QDebug> -#include <QUuid> - -AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password, - QObject *parent) - : YggdrasilTask(account, parent), m_password(password) -{ -} - -QJsonObject AuthenticateTask::getRequestContent() const -{ - /* - * { - * "agent": { // optional - * "name": "Minecraft", // So far this is the only encountered value - * "version": 1 // This number might be increased - * // by the vanilla client in the future - * }, - * "username": "mojang account name", // Can be an email address or player name for - // unmigrated accounts - * "password": "mojang account password", - * "clientToken": "client identifier" // optional - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - - { - QJsonObject agent; - // C++ makes string literals void* for some stupid reason, so we have to tell it - // QString... Thanks Obama. - agent.insert("name", QString("Minecraft")); - agent.insert("version", 1); - req.insert("agent", agent); - } - - req.insert("username", m_account->username()); - req.insert("password", m_password); - req.insert("requestUser", true); - - // If we already have a client token, give it to the server. - // Otherwise, let the server give us one. - - if(m_account->m_clientToken.isEmpty()) - { - auto uuid = QUuid::createUuid(); - auto uuidString = uuid.toString().remove('{').remove('-').remove('}'); - m_account->m_clientToken = uuidString; - } - req.insert("clientToken", m_account->m_clientToken); - - return req; -} - -void AuthenticateTask::processResponse(QJsonObject responseData) -{ - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - qDebug() << "Getting client token."; - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) - { - // Fail if the server gave us an empty client token - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) - { - changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } - // Set the client token. - m_account->m_clientToken = clientToken; - - // Now, we set the access token. - qDebug() << "Getting access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) - { - // Fail if the server didn't give us an access token. - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } - // Set the access token. - m_account->m_accessToken = accessToken; - - // Now we load the list of available profiles. - // Mojang hasn't yet implemented the profile system, - // but we might as well support what's there so we - // don't have trouble implementing it later. - qDebug() << "Loading profile list."; - QJsonArray availableProfiles = responseData.value("availableProfiles").toArray(); - QList<AccountProfile> loadedProfiles; - for (auto iter : availableProfiles) - { - QJsonObject profile = iter.toObject(); - // Profiles are easy, we just need their ID and name. - QString id = profile.value("id").toString(""); - QString name = profile.value("name").toString(""); - bool legacy = profile.value("legacy").toBool(false); - - if (id.isEmpty() || name.isEmpty()) - { - // This should never happen, but we might as well - // warn about it if it does so we can debug it easily. - // You never know when Mojang might do something truly derpy. - qWarning() << "Found entry in available profiles list with missing ID or name " - "field. Ignoring it."; - } - - // Now, add a new AccountProfile entry to the list. - loadedProfiles.append({id, name, legacy}); - } - // Put the list of profiles we loaded into the MojangAccount object. - m_account->m_profiles = loadedProfiles; - - // Finally, we set the current profile to the correct value. This is pretty simple. - // We do need to make sure that the current profile that the server gave us - // is actually in the available profiles list. - // If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know). - qDebug() << "Setting current profile."; - QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); - QString currentProfileId = currentProfile.value("id").toString(""); - if (currentProfileId.isEmpty()) - { - changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium.")); - return; - } - if (!m_account->setCurrentProfile(currentProfileId)) - { - changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list.")); - return; - } - - // this is what the vanilla launcher passes to the userProperties launch param - if (responseData.contains("user")) - { - User u; - auto obj = responseData.value("user").toObject(); - u.id = obj.value("id").toString(); - auto propArray = obj.value("properties").toArray(); - for (auto prop : propArray) - { - auto propTuple = prop.toObject(); - auto name = propTuple.value("name").toString(); - auto value = propTuple.value("value").toString(); - u.properties.insert(name, value); - } - m_account->m_user = u; - } - - // We've made it through the minefield of possible errors. Return true to indicate that - // we've succeeded. - qDebug() << "Finished reading authentication response."; - changeState(STATE_SUCCEEDED); -} - -QString AuthenticateTask::getEndpoint() const -{ - return "authenticate"; -} - -QString AuthenticateTask::getStateMessage() const -{ - switch (m_state) - { - case STATE_SENDING_REQUEST: - return tr("Authenticating: Sending request..."); - case STATE_PROCESSING_RESPONSE: - return tr("Authenticating: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } -} diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.h b/api/logic/minecraft/auth/flows/AuthenticateTask.h deleted file mode 100644 index 4c14eec7..00000000 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.h +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright 2013-2021 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 "../YggdrasilTask.h" - -#include <QObject> -#include <QString> -#include <QJsonObject> - -/** - * The authenticate task takes a MojangAccount with no access token and password and attempts to - * authenticate with Mojang's servers. - * If successful, it will set the MojangAccount's access token. - */ -class AuthenticateTask : public YggdrasilTask -{ - Q_OBJECT -public: - AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0); - -protected: - virtual QJsonObject getRequestContent() const override; - - virtual QString getEndpoint() const override; - - virtual void processResponse(QJsonObject responseData) override; - - virtual QString getStateMessage() const override; - -private: - QString m_password; -}; diff --git a/api/logic/minecraft/auth/flows/RefreshTask.cpp b/api/logic/minecraft/auth/flows/RefreshTask.cpp deleted file mode 100644 index ecba178d..00000000 --- a/api/logic/minecraft/auth/flows/RefreshTask.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* Copyright 2013-2021 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 "RefreshTask.h" -#include "../MojangAccount.h" - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QVariant> - -#include <QDebug> - -RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account) -{ -} - -QJsonObject RefreshTask::getRequestContent() const -{ - /* - * { - * "clientToken": "client identifier" - * "accessToken": "current access token to be refreshed" - * "selectedProfile": // specifying this causes errors - * { - * "id": "profile ID" - * "name": "profile name" - * } - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - req.insert("clientToken", m_account->m_clientToken); - req.insert("accessToken", m_account->m_accessToken); - /* - { - auto currentProfile = m_account->currentProfile(); - QJsonObject profile; - profile.insert("id", currentProfile->id()); - profile.insert("name", currentProfile->name()); - req.insert("selectedProfile", profile); - } - */ - req.insert("requestUser", true); - - return req; -} - -void RefreshTask::processResponse(QJsonObject responseData) -{ - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; - - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) - { - // Fail if the server gave us an empty client token - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) - { - changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } - - // Now, we set the access token. - qDebug() << "Getting new access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) - { - // Fail if the server didn't give us an access token. - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } - - // we validate that the server responded right. (our current profile = returned current - // profile) - QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); - QString currentProfileId = currentProfile.value("id").toString(""); - if (m_account->currentProfile()->id != currentProfileId) - { - changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected.")); - return; - } - - // this is what the vanilla launcher passes to the userProperties launch param - if (responseData.contains("user")) - { - User u; - auto obj = responseData.value("user").toObject(); - u.id = obj.value("id").toString(); - auto propArray = obj.value("properties").toArray(); - for (auto prop : propArray) - { - auto propTuple = prop.toObject(); - auto name = propTuple.value("name").toString(); - auto value = propTuple.value("value").toString(); - u.properties.insert(name, value); - } - m_account->m_user = u; - } - - // We've made it through the minefield of possible errors. Return true to indicate that - // we've succeeded. - qDebug() << "Finished reading refresh response."; - // Reset the access token. - m_account->m_accessToken = accessToken; - changeState(STATE_SUCCEEDED); -} - -QString RefreshTask::getEndpoint() const -{ - return "refresh"; -} - -QString RefreshTask::getStateMessage() const -{ - switch (m_state) - { - case STATE_SENDING_REQUEST: - return tr("Refreshing login token..."); - case STATE_PROCESSING_RESPONSE: - return tr("Refreshing login token: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } -} diff --git a/api/logic/minecraft/auth/flows/RefreshTask.h b/api/logic/minecraft/auth/flows/RefreshTask.h deleted file mode 100644 index f0840dda..00000000 --- a/api/logic/minecraft/auth/flows/RefreshTask.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright 2013-2021 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 "../YggdrasilTask.h" - -#include <QObject> -#include <QString> -#include <QJsonObject> - -/** - * The authenticate task takes a MojangAccount with a possibly timed-out access token - * and attempts to authenticate with Mojang's servers. - * If successful, it will set the new access token. The token is considered validated. - */ -class RefreshTask : public YggdrasilTask -{ - Q_OBJECT -public: - RefreshTask(MojangAccount * account); - -protected: - virtual QJsonObject getRequestContent() const override; - - virtual QString getEndpoint() const override; - - virtual void processResponse(QJsonObject responseData) override; - - virtual QString getStateMessage() const override; -}; - diff --git a/api/logic/minecraft/auth/flows/ValidateTask.cpp b/api/logic/minecraft/auth/flows/ValidateTask.cpp deleted file mode 100644 index 6b3f0a65..00000000 --- a/api/logic/minecraft/auth/flows/ValidateTask.cpp +++ /dev/null @@ -1,61 +0,0 @@ - -/* Copyright 2013-2021 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 "ValidateTask.h" -#include "../MojangAccount.h" - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QVariant> - -#include <QDebug> - -ValidateTask::ValidateTask(MojangAccount * account, QObject *parent) - : YggdrasilTask(account, parent) -{ -} - -QJsonObject ValidateTask::getRequestContent() const -{ - QJsonObject req; - req.insert("accessToken", m_account->m_accessToken); - return req; -} - -void ValidateTask::processResponse(QJsonObject responseData) -{ - // Assume that if processError wasn't called, then the request was successful. - changeState(YggdrasilTask::STATE_SUCCEEDED); -} - -QString ValidateTask::getEndpoint() const -{ - return "validate"; -} - -QString ValidateTask::getStateMessage() const -{ - switch (m_state) - { - case YggdrasilTask::STATE_SENDING_REQUEST: - return tr("Validating access token: Sending request..."); - case YggdrasilTask::STATE_PROCESSING_RESPONSE: - return tr("Validating access token: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } -} diff --git a/api/logic/minecraft/auth/flows/ValidateTask.h b/api/logic/minecraft/auth/flows/ValidateTask.h deleted file mode 100644 index 986c2e9f..00000000 --- a/api/logic/minecraft/auth/flows/ValidateTask.h +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright 2013-2021 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. - */ - -/* - * :FIXME: DEAD CODE, DEAD CODE, DEAD CODE! :FIXME: - */ - -#pragma once - -#include "../YggdrasilTask.h" - -#include <QObject> -#include <QString> -#include <QJsonObject> - -/** - * The validate task takes a MojangAccount and checks to make sure its access token is valid. - */ -class ValidateTask : public YggdrasilTask -{ - Q_OBJECT -public: - ValidateTask(MojangAccount *account, QObject *parent = 0); - -protected: - virtual QJsonObject getRequestContent() const override; - - virtual QString getEndpoint() const override; - - virtual void processResponse(QJsonObject responseData) override; - - virtual QString getStateMessage() const override; - -private: -}; diff --git a/api/logic/minecraft/gameoptions/GameOptions.cpp b/api/logic/minecraft/gameoptions/GameOptions.cpp deleted file mode 100644 index e547b32a..00000000 --- a/api/logic/minecraft/gameoptions/GameOptions.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "GameOptions.h" -#include "FileSystem.h" -#include <QDebug> -#include <QSaveFile> - -namespace { -bool load(const QString& path, std::vector<GameOptionItem> &contents, int & version) -{ - contents.clear(); - QFile file(path); - if (!file.open(QFile::ReadOnly)) - { - qWarning() << "Failed to read options file."; - return false; - } - version = 0; - while(!file.atEnd()) - { - auto line = file.readLine(); - if(line.endsWith('\n')) - { - line.chop(1); - } - auto separatorIndex = line.indexOf(':'); - if(separatorIndex == -1) - { - continue; - } - auto key = QString::fromUtf8(line.data(), separatorIndex); - auto value = QString::fromUtf8(line.data() + separatorIndex + 1, line.size() - 1 - separatorIndex); - qDebug() << "!!" << key << "!!"; - if(key == "version") - { - version = value.toInt(); - continue; - } - contents.emplace_back(GameOptionItem{key, value}); - } - qDebug() << "Loaded" << path << "with version:" << version; - return true; -} -bool save(const QString& path, std::vector<GameOptionItem> &mapping, int version) -{ - QSaveFile out(path); - if(!out.open(QIODevice::WriteOnly)) - { - return false; - } - if(version != 0) - { - QString versionLine = QString("version:%1\n").arg(version); - out.write(versionLine.toUtf8()); - } - auto iter = mapping.begin(); - while (iter != mapping.end()) - { - out.write(iter->key.toUtf8()); - out.write(":"); - out.write(iter->value.toUtf8()); - out.write("\n"); - iter++; - } - return out.commit(); -} -} - -GameOptions::GameOptions(const QString& path): - path(path) -{ - reload(); -} - -QVariant GameOptions::headerData(int section, Qt::Orientation orientation, int role) const -{ - if(role != Qt::DisplayRole) - { - return QAbstractListModel::headerData(section, orientation, role); - } - switch(section) - { - case 0: - return tr("Key"); - case 1: - return tr("Value"); - default: - return QVariant(); - } -} - -QVariant GameOptions::data(const QModelIndex& index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= int(contents.size())) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - if(column == 0) - { - return contents[row].key; - } - else - { - return contents[row].value; - } - default: - return QVariant(); - } - return QVariant(); -} - -int GameOptions::rowCount(const QModelIndex&) const -{ - return contents.size(); -} - -int GameOptions::columnCount(const QModelIndex&) const -{ - return 2; -} - -bool GameOptions::isLoaded() const -{ - return loaded; -} - -bool GameOptions::reload() -{ - beginResetModel(); - loaded = load(path, contents, version); - endResetModel(); - return loaded; -} - -bool GameOptions::save() -{ - return ::save(path, contents, version); -} diff --git a/api/logic/minecraft/gameoptions/GameOptions.h b/api/logic/minecraft/gameoptions/GameOptions.h deleted file mode 100644 index c6d25492..00000000 --- a/api/logic/minecraft/gameoptions/GameOptions.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include <map> -#include <QString> -#include <QAbstractListModel> - -struct GameOptionItem -{ - QString key; - QString value; -}; - -class GameOptions : public QAbstractListModel -{ - Q_OBJECT -public: - explicit GameOptions(const QString& path); - virtual ~GameOptions() = default; - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & parent) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - - bool isLoaded() const; - bool reload(); - bool save(); - -private: - std::vector<GameOptionItem> contents; - bool loaded = false; - QString path; - int version = 0; -}; diff --git a/api/logic/minecraft/launch/ClaimAccount.cpp b/api/logic/minecraft/launch/ClaimAccount.cpp deleted file mode 100644 index a1180f0a..00000000 --- a/api/logic/minecraft/launch/ClaimAccount.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "ClaimAccount.h" -#include <launch/LaunchTask.h> - -ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session): LaunchStep(parent) -{ - if(session->status == AuthSession::Status::PlayableOnline) - { - m_account = session->m_accountPtr; - } -} - -void ClaimAccount::executeTask() -{ - if(m_account) - { - lock.reset(new UseLock(m_account)); - emitSucceeded(); - } -} - -void ClaimAccount::finalize() -{ - lock.reset(); -} diff --git a/api/logic/minecraft/launch/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h deleted file mode 100644 index c5bd75f3..00000000 --- a/api/logic/minecraft/launch/ClaimAccount.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2013-2021 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 <launch/LaunchStep.h> -#include <minecraft/auth/MojangAccount.h> - -class ClaimAccount: public LaunchStep -{ - Q_OBJECT -public: - explicit ClaimAccount(LaunchTask *parent, AuthSessionPtr session); - virtual ~ClaimAccount() {}; - - void executeTask() override; - void finalize() override; - bool canAbort() const override - { - return false; - } -private: - std::unique_ptr<UseLock> lock; - MojangAccountPtr m_account; -}; diff --git a/api/logic/minecraft/launch/CreateGameFolders.cpp b/api/logic/minecraft/launch/CreateGameFolders.cpp deleted file mode 100644 index 4081e72e..00000000 --- a/api/logic/minecraft/launch/CreateGameFolders.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "CreateGameFolders.h" -#include "minecraft/MinecraftInstance.h" -#include "launch/LaunchTask.h" -#include "FileSystem.h" - -CreateGameFolders::CreateGameFolders(LaunchTask* parent): LaunchStep(parent) -{ -} - -void CreateGameFolders::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - - if(!FS::ensureFolderPathExists(minecraftInstance->gameRoot())) - { - emit logLine("Couldn't create the main game folder", MessageLevel::Error); - emitFailed(tr("Couldn't create the main game folder")); - return; - } - - // HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created. - if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) - { - emit logLine("Couldn't create the 'server-resource-packs' folder", MessageLevel::Error); - } - emitSucceeded(); -} diff --git a/api/logic/minecraft/launch/CreateGameFolders.h b/api/logic/minecraft/launch/CreateGameFolders.h deleted file mode 100644 index 9c7d3c94..00000000 --- a/api/logic/minecraft/launch/CreateGameFolders.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2013-2021 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 <launch/LaunchStep.h> -#include <LoggedProcess.h> -#include <minecraft/auth/AuthSession.h> - -// Create the main .minecraft for the instance and any other necessary folders -class CreateGameFolders: public LaunchStep -{ - Q_OBJECT -public: - explicit CreateGameFolders(LaunchTask *parent); - virtual ~CreateGameFolders() {}; - - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } -}; - - diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.cpp b/api/logic/minecraft/launch/DirectJavaLaunch.cpp deleted file mode 100644 index 2110384f..00000000 --- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* Copyright 2013-2021 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 "DirectJavaLaunch.h" -#include <launch/LaunchTask.h> -#include <minecraft/MinecraftInstance.h> -#include <FileSystem.h> -#include <Commandline.h> -#include <QStandardPaths> - -DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent) -{ - connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state); -} - -void DirectJavaLaunch::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - QStringList args = minecraftInstance->javaArguments(); - - args.append("-Djava.library.path=" + minecraftInstance->getNativePath()); - - auto classPathEntries = minecraftInstance->getClassPath(); - args.append("-cp"); - QString classpath; -#ifdef Q_OS_WIN32 - classpath = classPathEntries.join(';'); -#else - classpath = classPathEntries.join(':'); -#endif - args.append(classpath); - args.append(minecraftInstance->getMainClass()); - - QString allArgs = args.join(", "); - emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); - - auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - - m_process.setProcessEnvironment(instance->createEnvironment()); - - // make detachable - this will keep the process running even if the object is destroyed - m_process.setDetachable(true); - - auto mcArgs = minecraftInstance->processMinecraftArgs(m_session, m_serverToJoin); - args.append(mcArgs); - - QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); - if(!wrapperCommandStr.isEmpty()) - { - auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); - auto wrapperCommand = wrapperArgs.takeFirst(); - auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); - if (realWrapperCommand.isEmpty()) - { - const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found."); - emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal); - emitFailed(tr(reason).arg(wrapperCommand)); - return; - } - emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); - args.prepend(javaPath); - m_process.start(wrapperCommand, wrapperArgs + args); - } - else - { - m_process.start(javaPath, args); - } -} - -void DirectJavaLaunch::on_state(LoggedProcess::State state) -{ - switch(state) - { - case LoggedProcess::FailedToStart: - { - //: Error message displayed if instance can't start - const char *reason = QT_TR_NOOP("Could not launch minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(tr(reason)); - return; - } - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - { - m_parent->setPid(-1); - emitFailed(tr("Game crashed.")); - return; - } - case LoggedProcess::Finished: - { - m_parent->setPid(-1); - // if the exit code wasn't 0, report this as a crash - auto exitCode = m_process.exitCode(); - if(exitCode != 0) - { - emitFailed(tr("Game crashed.")); - return; - } - //FIXME: make this work again - // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); - // run post-exit - emitSucceeded(); - break; - } - case LoggedProcess::Running: - emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); - m_parent->setPid(m_process.processId()); - m_parent->instance()->setLastLaunch(); - break; - default: - break; - } -} - -void DirectJavaLaunch::setWorkingDirectory(const QString &wd) -{ - m_process.setWorkingDirectory(wd); -} - -void DirectJavaLaunch::proceed() -{ - // nil -} - -bool DirectJavaLaunch::abort() -{ - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; -} - diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.h b/api/logic/minecraft/launch/DirectJavaLaunch.h deleted file mode 100644 index 58b119b8..00000000 --- a/api/logic/minecraft/launch/DirectJavaLaunch.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright 2013-2021 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 <launch/LaunchStep.h> -#include <LoggedProcess.h> -#include <minecraft/auth/AuthSession.h> - -#include "MinecraftServerTarget.h" - -class DirectJavaLaunch: public LaunchStep -{ - Q_OBJECT -public: - explicit DirectJavaLaunch(LaunchTask *parent); - virtual ~DirectJavaLaunch() {}; - - virtual void executeTask(); - virtual bool abort(); - virtual void proceed(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); - void setAuthSession(AuthSessionPtr session) - { - m_session = session; - } - - void setServerToJoin(MinecraftServerTargetPtr serverToJoin) - { - m_serverToJoin = std::move(serverToJoin); - } - -private slots: - void on_state(LoggedProcess::State state); - -private: - LoggedProcess m_process; - QString m_command; - AuthSessionPtr m_session; - MinecraftServerTargetPtr m_serverToJoin; -}; - diff --git a/api/logic/minecraft/launch/ExtractNatives.cpp b/api/logic/minecraft/launch/ExtractNatives.cpp deleted file mode 100644 index d57499aa..00000000 --- a/api/logic/minecraft/launch/ExtractNatives.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* Copyright 2013-2021 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 "ExtractNatives.h" -#include <minecraft/MinecraftInstance.h> -#include <launch/LaunchTask.h> - -#include <quazip.h> -#include <quazipdir.h> -#include "MMCZip.h" -#include "FileSystem.h" -#include <QDir> - -static QString replaceSuffix (QString target, const QString &suffix, const QString &replacement) -{ - if (!target.endsWith(suffix)) - { - return target; - } - target.resize(target.length() - suffix.length()); - return target + replacement; -} - -static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW) -{ - QuaZip zip(source); - if(!zip.open(QuaZip::mdUnzip)) - { - return false; - } - QDir directory(targetFolder); - if (!zip.goToFirstFile()) - { - return false; - } - do - { - QString name = zip.getCurrentFileName(); - auto lowercase = name.toLower(); - if (nativeGLFW && name.contains("glfw")) { - continue; - } - if (nativeOpenAL && name.contains("openal")) { - continue; - } - if(applyJnilibHack) - { - name = replaceSuffix(name, ".jnilib", ".dylib"); - } - QString absFilePath = directory.absoluteFilePath(name); - if (!JlCompress::extractFile(&zip, "", absFilePath)) - { - return false; - } - } while (zip.goToNextFile()); - zip.close(); - if(zip.getZipError()!=0) - { - return false; - } - return true; -} - -void ExtractNatives::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - auto toExtract = minecraftInstance->getNativeJars(); - if(toExtract.isEmpty()) - { - emitSucceeded(); - return; - } - auto settings = minecraftInstance->settings(); - bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); - bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); - - auto outputPath = minecraftInstance->getNativePath(); - auto javaVersion = minecraftInstance->getJavaVersion(); - bool jniHackEnabled = javaVersion.major() >= 8; - for(const auto &source: toExtract) - { - if(!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW)) - { - const char *reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'"); - emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal); - emitFailed(tr(reason).arg(source, outputPath)); - } - } - emitSucceeded(); -} - -void ExtractNatives::finalize() -{ - auto instance = m_parent->instance(); - QString target_dir = FS::PathCombine(instance->instanceRoot(), "natives/"); - QDir dir(target_dir); - dir.removeRecursively(); -} diff --git a/api/logic/minecraft/launch/ExtractNatives.h b/api/logic/minecraft/launch/ExtractNatives.h deleted file mode 100644 index 094fcd6b..00000000 --- a/api/logic/minecraft/launch/ExtractNatives.h +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright 2013-2021 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 <launch/LaunchStep.h> -#include <memory> -#include "minecraft/auth/AuthSession.h" - -// FIXME: temporary wrapper for existing task. -class ExtractNatives: public LaunchStep -{ - Q_OBJECT -public: - explicit ExtractNatives(LaunchTask *parent) : LaunchStep(parent){}; - virtual ~ExtractNatives(){}; - - void executeTask() override; - bool canAbort() const override - { - return false; - } - void finalize() override; -}; - - diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.cpp b/api/logic/minecraft/launch/LauncherPartLaunch.cpp deleted file mode 100644 index ee469770..00000000 --- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/* Copyright 2013-2021 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 "LauncherPartLaunch.h" -#include <QCoreApplication> -#include <launch/LaunchTask.h> -#include <minecraft/MinecraftInstance.h> -#include <FileSystem.h> -#include <Commandline.h> -#include <QStandardPaths> -#include "Env.h" - -LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) -{ - connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines); - 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(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - - m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin); - QStringList args = minecraftInstance->javaArguments(); - QString allArgs = args.join(", "); - emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); - - auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - - m_process.setProcessEnvironment(instance->createEnvironment()); - - // make detachable - this will keep the process running even if the object is destroyed - m_process.setDetachable(true); - - 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()) - { - auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); - auto wrapperCommand = wrapperArgs.takeFirst(); - auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); - if (realWrapperCommand.isEmpty()) - { - const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found."); - emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal); - emitFailed(tr(reason).arg(wrapperCommand)); - return; - } - emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); - args.prepend(javaPath); - m_process.start(wrapperCommand, wrapperArgs + args); - } - else - { - m_process.start(javaPath, args); - } -} - -void LauncherPartLaunch::on_state(LoggedProcess::State state) -{ - switch(state) - { - case LoggedProcess::FailedToStart: - { - //: Error message displayed if instace can't start - const char *reason = QT_TR_NOOP("Could not launch minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(tr(reason)); - return; - } - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - { - m_parent->setPid(-1); - emitFailed(tr("Game crashed.")); - return; - } - case LoggedProcess::Finished: - { - m_parent->setPid(-1); - // if the exit code wasn't 0, report this as a crash - auto exitCode = m_process.exitCode(); - if(exitCode != 0) - { - emitFailed(tr("Game crashed.")); - return; - } - //FIXME: make this work again - // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); - // run post-exit - emitSucceeded(); - break; - } - case LoggedProcess::Running: - emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); - m_parent->setPid(m_process.processId()); - m_parent->instance()->setLastLaunch(); - // send the launch script to the launcher part - m_process.write(m_launchScript.toUtf8()); - - mayProceed = true; - emit readyForLaunch(); - break; - default: - break; - } -} - -void LauncherPartLaunch::setWorkingDirectory(const QString &wd) -{ - m_process.setWorkingDirectory(wd); -} - -void LauncherPartLaunch::proceed() -{ - if(mayProceed) - { - QString launchString("launch\n"); - m_process.write(launchString.toUtf8()); - mayProceed = false; - } -} - -bool LauncherPartLaunch::abort() -{ - if(mayProceed) - { - mayProceed = false; - QString launchString("abort\n"); - m_process.write(launchString.toUtf8()); - } - else - { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - } - return true; -} diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.h b/api/logic/minecraft/launch/LauncherPartLaunch.h deleted file mode 100644 index 6a7ee0e5..00000000 --- a/api/logic/minecraft/launch/LauncherPartLaunch.h +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2013-2021 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 <launch/LaunchStep.h> -#include <LoggedProcess.h> -#include <minecraft/auth/AuthSession.h> - -#include "MinecraftServerTarget.h" - -class LauncherPartLaunch: public LaunchStep -{ - Q_OBJECT -public: - explicit LauncherPartLaunch(LaunchTask *parent); - virtual ~LauncherPartLaunch() {}; - - virtual void executeTask(); - virtual bool abort(); - virtual void proceed(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); - void setAuthSession(AuthSessionPtr session) - { - m_session = session; - } - - void setServerToJoin(MinecraftServerTargetPtr serverToJoin) - { - m_serverToJoin = std::move(serverToJoin); - } - -private slots: - void on_state(LoggedProcess::State state); - -private: - LoggedProcess m_process; - QString m_command; - AuthSessionPtr m_session; - QString m_launchScript; - MinecraftServerTargetPtr m_serverToJoin; - - bool mayProceed = false; -}; diff --git a/api/logic/minecraft/launch/MinecraftServerTarget.cpp b/api/logic/minecraft/launch/MinecraftServerTarget.cpp deleted file mode 100644 index 569273b6..00000000 --- a/api/logic/minecraft/launch/MinecraftServerTarget.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* Copyright 2013-2021 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 "MinecraftServerTarget.h" - -#include <QStringList> - -MinecraftServerTarget MinecraftServerTarget::parse(const QString &fullAddress) { - QStringList split = fullAddress.split(":"); - - // The logic below replicates the exact logic minecraft uses for parsing server addresses. - // While the conversion is not lossless and eats errors, it ensures the same behavior - // within Minecraft and MultiMC when entering server addresses. - if (fullAddress.startsWith("[")) - { - int bracket = fullAddress.indexOf("]"); - if (bracket > 0) - { - QString ipv6 = fullAddress.mid(1, bracket - 1); - QString port = fullAddress.mid(bracket + 1).trimmed(); - - if (port.startsWith(":") && !ipv6.isEmpty()) - { - port = port.mid(1); - split = QStringList({ ipv6, port }); - } - else - { - split = QStringList({ipv6}); - } - } - } - - if (split.size() > 2) - { - split = QStringList({fullAddress}); - } - - QString realAddress = split[0]; - - quint16 realPort = 25565; - if (split.size() > 1) - { - bool ok; - realPort = split[1].toUInt(&ok); - - if (!ok) - { - realPort = 25565; - } - } - - return MinecraftServerTarget { realAddress, realPort }; -} diff --git a/api/logic/minecraft/launch/MinecraftServerTarget.h b/api/logic/minecraft/launch/MinecraftServerTarget.h deleted file mode 100644 index 3c5786f4..00000000 --- a/api/logic/minecraft/launch/MinecraftServerTarget.h +++ /dev/null @@ -1,30 +0,0 @@ -/* Copyright 2013-2021 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> - -#include <QString> -#include <multimc_logic_export.h> - -struct MinecraftServerTarget { - QString address; - quint16 port; - - static MULTIMC_LOGIC_EXPORT MinecraftServerTarget parse(const QString &fullAddress); -}; - -typedef std::shared_ptr<MinecraftServerTarget> MinecraftServerTargetPtr; diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp deleted file mode 100644 index 93de9d59..00000000 --- a/api/logic/minecraft/launch/ModMinecraftJar.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* Copyright 2013-2021 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 "ModMinecraftJar.h" -#include "launch/LaunchTask.h" -#include "MMCZip.h" -#include "minecraft/OpSys.h" -#include "FileSystem.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" - -void ModMinecraftJar::executeTask() -{ - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - - if(!m_inst->getJarMods().size()) - { - 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->getPackProfile(); - 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::finalize() -{ - 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()) - { - if(!finalJar.remove()) - { - return false; - } - } - return true; -} diff --git a/api/logic/minecraft/launch/ModMinecraftJar.h b/api/logic/minecraft/launch/ModMinecraftJar.h deleted file mode 100644 index 081c6a91..00000000 --- a/api/logic/minecraft/launch/ModMinecraftJar.h +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright 2013-2021 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 <launch/LaunchStep.h> -#include <memory> - -class ModMinecraftJar: public LaunchStep -{ - Q_OBJECT -public: - explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {}; - virtual ~ModMinecraftJar(){}; - - virtual void executeTask() override; - virtual bool canAbort() const override - { - return false; - } - void finalize() override; -private: - bool removeJar(); -}; diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.cpp b/api/logic/minecraft/launch/PrintInstanceInfo.cpp deleted file mode 100644 index 0b9611ad..00000000 --- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* Copyright 2013-2021 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 <fstream> -#include <string> - -#include "PrintInstanceInfo.h" -#include <launch/LaunchTask.h> - -#ifdef Q_OS_LINUX -namespace { -void probeProcCpuinfo(QStringList &log) -{ - std::ifstream cpuin("/proc/cpuinfo"); - for (std::string line; std::getline(cpuin, line);) - { - if (strncmp(line.c_str(), "model name", 10) == 0) - { - log << QString::fromStdString(line.substr(13, std::string::npos)); - break; - } - } -} - -void runLspci(QStringList &log) -{ - // FIXME: fixed size buffers... - char buff[512]; - int gpuline = -1; - int cline = 0; - FILE * lspci = popen("lspci -k", "r"); - - if (!lspci) - return; - - while (fgets(buff, 512, lspci) != NULL) - { - std::string str(buff); - if (str.length() < 9) - continue; - if (str.substr(8, 3) == "VGA") - { - gpuline = cline; - log << QString::fromStdString(str.substr(35, std::string::npos)); - } - if (gpuline > -1 && gpuline != cline) - { - if (cline - gpuline < 3) - { - log << QString::fromStdString(str.substr(1, std::string::npos)); - } - } - cline++; - } - pclose(lspci); -} - -void runGlxinfo(QStringList & log) -{ - // FIXME: fixed size buffers... - char buff[512]; - FILE *glxinfo = popen("glxinfo", "r"); - if (!glxinfo) - return; - - while (fgets(buff, 512, glxinfo) != NULL) - { - if (strncmp(buff, "OpenGL version string:", 22) == 0) - { - log << QString::fromUtf8(buff); - break; - } - } - pclose(glxinfo); -} - -} -#endif - -void PrintInstanceInfo::executeTask() -{ - auto instance = m_parent->instance(); - QStringList log; - -#ifdef Q_OS_LINUX - ::probeProcCpuinfo(log); - ::runLspci(log); - ::runGlxinfo(log); -#endif - - logLines(log, MessageLevel::MultiMC); - logLines(instance->verboseDescription(m_session, m_serverToJoin), MessageLevel::MultiMC); - emitSucceeded(); -} diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.h b/api/logic/minecraft/launch/PrintInstanceInfo.h deleted file mode 100644 index fdc30f31..00000000 --- a/api/logic/minecraft/launch/PrintInstanceInfo.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright 2013-2021 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 <launch/LaunchStep.h> -#include <memory> -#include "minecraft/auth/AuthSession.h" -#include "minecraft/launch/MinecraftServerTarget.h" - -// FIXME: temporary wrapper for existing task. -class PrintInstanceInfo: public LaunchStep -{ - Q_OBJECT -public: - explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) : - LaunchStep(parent), m_session(session), m_serverToJoin(serverToJoin) {}; - virtual ~PrintInstanceInfo(){}; - - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } -private: - AuthSessionPtr m_session; - MinecraftServerTargetPtr m_serverToJoin; -}; - diff --git a/api/logic/minecraft/launch/ReconstructAssets.cpp b/api/logic/minecraft/launch/ReconstructAssets.cpp deleted file mode 100644 index 4d206665..00000000 --- a/api/logic/minecraft/launch/ReconstructAssets.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright 2013-2021 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 "ReconstructAssets.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "minecraft/AssetsUtils.h" -#include "launch/LaunchTask.h" - -void ReconstructAssets::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - auto components = minecraftInstance->getPackProfile(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); - - if(!AssetsUtils::reconstructAssets(assets->id, minecraftInstance->resourcesDir())) - { - emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error); - } - - emitSucceeded(); -} diff --git a/api/logic/minecraft/launch/ReconstructAssets.h b/api/logic/minecraft/launch/ReconstructAssets.h deleted file mode 100644 index 58d7febd..00000000 --- a/api/logic/minecraft/launch/ReconstructAssets.h +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright 2013-2021 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 <launch/LaunchStep.h> -#include <memory> - -class ReconstructAssets: public LaunchStep -{ - Q_OBJECT -public: - explicit ReconstructAssets(LaunchTask *parent) : LaunchStep(parent){}; - virtual ~ReconstructAssets(){}; - - void executeTask() override; - bool canAbort() const override - { - return false; - } -}; diff --git a/api/logic/minecraft/launch/ScanModFolders.cpp b/api/logic/minecraft/launch/ScanModFolders.cpp deleted file mode 100644 index 2a0e21b3..00000000 --- a/api/logic/minecraft/launch/ScanModFolders.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright 2013-2021 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 "ScanModFolders.h" -#include "launch/LaunchTask.h" -#include "MMCZip.h" -#include "minecraft/OpSys.h" -#include "FileSystem.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/mod/ModFolderModel.h" - -void ScanModFolders::executeTask() -{ - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - - auto loaders = m_inst->loaderModList(); - connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone); - if(!loaders->update()) { - m_modsDone = true; - } - - auto cores = m_inst->coreModList(); - connect(cores.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone); - if(!cores->update()) { - m_coreModsDone = true; - } - checkDone(); -} - -void ScanModFolders::modsDone() -{ - m_modsDone = true; - checkDone(); -} - -void ScanModFolders::coreModsDone() -{ - m_coreModsDone = true; - checkDone(); -} - -void ScanModFolders::checkDone() -{ - if(m_modsDone && m_coreModsDone) { - emitSucceeded(); - } -} diff --git a/api/logic/minecraft/launch/ScanModFolders.h b/api/logic/minecraft/launch/ScanModFolders.h deleted file mode 100644 index d5989170..00000000 --- a/api/logic/minecraft/launch/ScanModFolders.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2013-2021 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 <launch/LaunchStep.h> -#include <memory> - -class ScanModFolders: public LaunchStep -{ - Q_OBJECT -public: - explicit ScanModFolders(LaunchTask *parent) : LaunchStep(parent) {}; - virtual ~ScanModFolders(){}; - - virtual void executeTask() override; - virtual bool canAbort() const override - { - return false; - } -private slots: - void coreModsDone(); - void modsDone(); -private: - void checkDone(); - -private: // DATA - bool m_modsDone = false; - bool m_coreModsDone = false; -}; diff --git a/api/logic/minecraft/launch/VerifyJavaInstall.cpp b/api/logic/minecraft/launch/VerifyJavaInstall.cpp deleted file mode 100644 index 657669af..00000000 --- a/api/logic/minecraft/launch/VerifyJavaInstall.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "VerifyJavaInstall.h" - -#include <launch/LaunchTask.h> -#include <minecraft/MinecraftInstance.h> -#include <minecraft/PackProfile.h> -#include <minecraft/VersionFilterData.h> - -void VerifyJavaInstall::executeTask() { - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - - auto javaVersion = m_inst->getJavaVersion(); - auto minecraftComponent = m_inst->getPackProfile()->getComponent("net.minecraft"); - - // Java 16 requirement - if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java16BeginsDate) { - if (javaVersion.major() < 16) { - emit logLine("Minecraft 21w19a and above require the use of Java 16", - MessageLevel::Fatal); - emitFailed(tr("Minecraft 21w19a and above require the use of Java 16")); - return; - } - } - // Java 8 requirement - else if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java8BeginsDate) { - if (javaVersion.major() < 8) { - emit logLine("Minecraft 17w13a and above require the use of Java 8", - MessageLevel::Fatal); - emitFailed(tr("Minecraft 17w13a and above require the use of Java 8")); - return; - } - } - - emitSucceeded(); -} diff --git a/api/logic/minecraft/launch/VerifyJavaInstall.h b/api/logic/minecraft/launch/VerifyJavaInstall.h deleted file mode 100644 index a553106d..00000000 --- a/api/logic/minecraft/launch/VerifyJavaInstall.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include <launch/LaunchStep.h> - -class VerifyJavaInstall : public LaunchStep { - Q_OBJECT - -public: - explicit VerifyJavaInstall(LaunchTask *parent) : LaunchStep(parent) { - }; - ~VerifyJavaInstall() override = default; - - void executeTask() override; - bool canAbort() const override { - return false; - } -}; diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp deleted file mode 100644 index 9f9bda5a..00000000 --- a/api/logic/minecraft/legacy/LegacyInstance.cpp +++ /dev/null @@ -1,256 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <QFileInfo> -#include <minecraft/launch/LauncherPartLaunch.h> -#include <QDir> -#include <settings/Setting.h> - -#include "LegacyInstance.h" - -#include "minecraft/legacy/LegacyModList.h" -#include "minecraft/WorldList.h" -#include <MMCZip.h> -#include <FileSystem.h> - -LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : BaseInstance(globalSettings, settings, rootDir) -{ - settings->registerSetting("NeedsRebuild", true); - settings->registerSetting("ShouldUpdate", false); - 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 - * - * for instances that DO NOT have the CustomBaseJar setting (legacy instances), - * [.]minecraft/bin/mcbackup.jar is the default base jar - */ - settings->registerSetting("UseCustomBaseJar", true); - settings->registerSetting("CustomBaseJar", ""); -} - -QString LegacyInstance::mainJarToPreserve() const -{ - bool customJar = m_settings->get("UseCustomBaseJar").toBool(); - if(customJar) - { - auto base = baseJar(); - if(QFile::exists(base)) - { - return base; - } - } - auto runnable = runnableJar(); - if(QFile::exists(runnable)) - { - return runnable; - } - return QString(); -} - - -QString LegacyInstance::baseJar() const -{ - bool customJar = m_settings->get("UseCustomBaseJar").toBool(); - if (customJar) - { - return customBaseJar(); - } - else - return defaultBaseJar(); -} - -QString LegacyInstance::customBaseJar() const -{ - QString value = m_settings->get("CustomBaseJar").toString(); - if (value.isNull() || value.isEmpty()) - { - return defaultCustomBaseJar(); - } - return value; -} - -bool LegacyInstance::shouldUseCustomBaseJar() const -{ - return m_settings->get("UseCustomBaseJar").toBool(); -} - - -shared_qobject_ptr<Task> LegacyInstance::createUpdateTask(Net::Mode) -{ - return nullptr; -} - -std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const -{ - if (!jar_mod_list) - { - auto list = new LegacyModList(jarModsDir(), modListFile()); - jar_mod_list.reset(list); - } - jar_mod_list->update(); - return jar_mod_list; -} - -QString LegacyInstance::gameRoot() const -{ - QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - - if (mcDir.exists() && !dotMCDir.exists()) - return mcDir.filePath(); - else - return dotMCDir.filePath(); -} - -QString LegacyInstance::binRoot() const -{ - return FS::PathCombine(gameRoot(), "bin"); -} - -QString LegacyInstance::jarModsDir() const -{ - return FS::PathCombine(instanceRoot(), "instMods"); -} - -QString LegacyInstance::libDir() const -{ - return FS::PathCombine(gameRoot(), "lib"); -} - -QString LegacyInstance::savesDir() const -{ - return FS::PathCombine(gameRoot(), "saves"); -} - -QString LegacyInstance::loaderModsDir() const -{ - return FS::PathCombine(gameRoot(), "mods"); -} - -QString LegacyInstance::coreModsDir() const -{ - return FS::PathCombine(gameRoot(), "coremods"); -} - -QString LegacyInstance::resourceDir() const -{ - return FS::PathCombine(gameRoot(), "resources"); -} -QString LegacyInstance::texturePacksDir() const -{ - return FS::PathCombine(gameRoot(), "texturepacks"); -} - -QString LegacyInstance::runnableJar() const -{ - return FS::PathCombine(binRoot(), "minecraft.jar"); -} - -QString LegacyInstance::modListFile() const -{ - return FS::PathCombine(instanceRoot(), "modlist"); -} - -QString LegacyInstance::instanceConfigFolder() const -{ - return FS::PathCombine(gameRoot(), "config"); -} - -bool LegacyInstance::shouldRebuild() const -{ - return m_settings->get("NeedsRebuild").toBool(); -} - -QString LegacyInstance::currentVersionId() const -{ - return m_settings->get("JarVersion").toString(); -} - -QString LegacyInstance::intendedVersionId() const -{ - return m_settings->get("IntendedJarVersion").toString(); -} - -bool LegacyInstance::shouldUpdate() const -{ - QVariant var = settings()->get("ShouldUpdate"); - if (!var.isValid() || var.toBool() == false) - { - return intendedVersionId() != currentVersionId(); - } - return true; -} - -QString LegacyInstance::defaultBaseJar() const -{ - return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; -} - -QString LegacyInstance::defaultCustomBaseJar() const -{ - return FS::PathCombine(binRoot(), "mcbackup.jar"); -} - -std::shared_ptr<WorldList> LegacyInstance::worldList() const -{ - if (!m_world_list) - { - m_world_list.reset(new WorldList(savesDir())); - } - return m_world_list; -} - -QString LegacyInstance::typeName() const -{ - return tr("Legacy"); -} - -QString LegacyInstance::getStatusbarDescription() -{ - return tr("Instance from previous versions."); -} - -QStringList LegacyInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) -{ - QStringList out; - - auto alltraits = traits(); - if(alltraits.size()) - { - out << "Traits:"; - for (auto trait : alltraits) - { - out << " " + trait; - } - 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; -} diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h deleted file mode 100644 index 325bac7a..00000000 --- a/api/logic/minecraft/legacy/LegacyInstance.h +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright 2013-2021 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 "BaseInstance.h" -#include "launch/LaunchTask.h" - -#include "multimc_logic_export.h" - -class ModFolderModel; -class LegacyModList; -class WorldList; -class Task; -/* - * 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 saveNow() override {} - - /// Path to the instance's minecraft.jar - QString runnableJar() const; - - //! Path to the instance's modlist file. - QString modListFile() const; - - ////// Directories ////// - QString libDir() const; - QString savesDir() const; - QString texturePacksDir() const; - QString jarModsDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString resourceDir() const; - virtual QString instanceConfigFolder() const override; - QString gameRoot() const override; // 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 - /// versions/$version/$version.jar - QString baseJar() const; - - /// the default base jar of this instance - QString defaultBaseJar() const; - /// the default custom base jar of this instance - QString defaultCustomBaseJar() const; - - // the main jar that we actually want to keep when migrating the instance - QString mainJarToPreserve() const; - - /*! - * Whether or not custom base jar is used - */ - bool shouldUseCustomBaseJar() const; - - /*! - * The value of the custom base jar - */ - QString customBaseJar() const; - - std::shared_ptr<LegacyModList> jarModList() const; - std::shared_ptr<WorldList> worldList() const; - - /*! - * Whether or not the instance's minecraft.jar needs to be rebuilt. - * If this is true, when the instance launches, its jar mods will be - * re-added to a fresh minecraft.jar file. - */ - bool shouldRebuild() const; - - QString currentVersionId() const; - QString intendedVersionId() const; - - QSet<QString> traits() const override - { - return {"legacy-instance", "texturepacks"}; - }; - - virtual bool shouldUpdate() const; - virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override; - - virtual QString typeName() const override; - - bool canLaunch() const override - { - return false; - } - bool canEdit() const override - { - return true; - } - bool canExport() const override - { - return false; - } - shared_qobject_ptr<LaunchTask> createLaunchTask( - AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override - { - return nullptr; - } - IPathMatcher::Ptr getLogFileMatcher() override - { - return nullptr; - } - QString getLogFileRoot() override - { - return gameRoot(); - } - - QString getStatusbarDescription() override; - QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) 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<WorldList> m_world_list; -}; diff --git a/api/logic/minecraft/legacy/LegacyModList.cpp b/api/logic/minecraft/legacy/LegacyModList.cpp deleted file mode 100644 index 7301eb8c..00000000 --- a/api/logic/minecraft/legacy/LegacyModList.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* Copyright 2013-2021 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 "LegacyModList.h" -#include <FileSystem.h> -#include <QString> -#include <QDebug> - -LegacyModList::LegacyModList(const QString &dir, const QString &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); -} - - struct OrderItem - { - QString id; - bool enabled = false; - }; - typedef QList<OrderItem> OrderList; - -static void internalSort(QList<LegacyModList::Mod> &what) -{ - auto predicate = [](const LegacyModList::Mod &left, const LegacyModList::Mod &right) - { - return left.fileName().localeAwareCompare(right.fileName()) < 0; - }; - 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 (!m_dir.exists() || !m_dir.isReadable()) - return false; - - QList<Mod> orderedMods; - QList<Mod> newMods; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - - // first, process the ordered items (if any) - OrderList listOrder = readListFile(m_list_file); - for (auto item : listOrder) - { - QFileInfo infoEnabled(m_dir.filePath(item.id)); - QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled")); - int idxEnabled = folderContents.indexOf(infoEnabled); - int idxDisabled = folderContents.indexOf(infoDisabled); - bool isEnabled; - // if both enabled and disabled versions are present, it's a special case... - if (idxEnabled >= 0 && idxDisabled >= 0) - { - // we only process the one we actually have in the order file. - // and exactly as we have it. - // THIS IS A CORNER CASE - isEnabled = item.enabled; - } - else - { - // only one is present. - // we pick the one that we found. - // we assume the mod was enabled/disabled by external means - isEnabled = idxEnabled >= 0; - } - int idx = isEnabled ? idxEnabled : idxDisabled; - QFileInfo &info = isEnabled ? infoEnabled : infoDisabled; - // if the file from the index file exists - if (idx != -1) - { - // remove from the actual folder contents list - folderContents.takeAt(idx); - // append the new mod - orderedMods.append(info); - } - } - // if there are any untracked files... append them sorted at the end - if (folderContents.size()) - { - for (auto entry : folderContents) - { - newMods.append(entry); - } - internalSort(newMods); - orderedMods.append(newMods); - } - mods.swap(orderedMods); - return true; -} diff --git a/api/logic/minecraft/legacy/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h deleted file mode 100644 index 8881d471..00000000 --- a/api/logic/minecraft/legacy/LegacyModList.h +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QList> -#include <QString> -#include <QDir> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT LegacyModList -{ -public: - - using Mod = QFileInfo; - - LegacyModList(const QString &dir, const QString &list_file = QString()); - - /// Reloads the mod list and returns true if the list changed. - bool update(); - - QDir dir() - { - return m_dir; - } - - const QList<Mod> & allMods() - { - return mods; - } - -protected: - QDir m_dir; - QString m_list_file; - QList<Mod> mods; -}; diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp deleted file mode 100644 index a4ea60cd..00000000 --- a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include "LegacyUpgradeTask.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/PackProfile.h" -#include "LegacyModList.h" -#include "classparser.h" - -LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance) -{ - m_origInstance = origInstance; -} - -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_instName); - - 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.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", preferredVersionNumber, true); - - QString jarPath = legacyInst->mainJarToPreserve(); - if(!jarPath.isNull()) - { - qDebug() << "Preserving base jar! : " << 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->jarModList()->allMods(); - for(auto & jarMod: jarMods) - { - QString modPath = jarMod.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.gameRoot(), 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 deleted file mode 100644 index e35e43b7..00000000 --- a/api/logic/minecraft/legacy/LegacyUpgradeTask.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "InstanceTask.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 MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public InstanceTask -{ - Q_OBJECT -public: - explicit LegacyUpgradeTask(InstancePtr origInstance); - -protected: - //! Entry point for tasks. - virtual void executeTask() override; - void copyFinished(); - void copyAborted(); - -private: /* data */ - InstancePtr m_origInstance; - QFuture<bool> m_copyFuture; - QFutureWatcher<bool> m_copyFutureWatcher; -}; diff --git a/api/logic/minecraft/mod/LocalModParseTask.cpp b/api/logic/minecraft/mod/LocalModParseTask.cpp deleted file mode 100644 index 0d6972fb..00000000 --- a/api/logic/minecraft/mod/LocalModParseTask.cpp +++ /dev/null @@ -1,467 +0,0 @@ -#include "LocalModParseTask.h" - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> -#include <quazip.h> -#include <quazipfile.h> -#include <toml.h> - -#include "settings/INIFile.h" -#include "FileSystem.h" - -namespace { - -// NEW format -// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3 - -// OLD format: -// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc -std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents) -{ - auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr<ModDetails> - { - if (!arr.at(0).isObject()) { - return nullptr; - } - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - auto firstObj = arr.at(0).toObject(); - details->mod_id = firstObj.value("modid").toString(); - auto name = firstObj.value("name").toString(); - // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name - if(name != "Example Mod") { - details->name = name; - } - details->version = firstObj.value("version").toString(); - details->updateurl = firstObj.value("updateUrl").toString(); - auto homeurl = firstObj.value("url").toString().trimmed(); - if(!homeurl.isEmpty()) - { - // fix up url. - if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) - { - homeurl.prepend("http://"); - } - } - details->homeurl = homeurl; - details->description = firstObj.value("description").toString(); - QJsonArray authors = firstObj.value("authorList").toArray(); - if (authors.size() == 0) { - // FIXME: what is the format of this? is there any? - authors = firstObj.value("authors").toArray(); - } - - for (auto author: authors) - { - details->authors.append(author.toString()); - } - details->credits = firstObj.value("credits").toString(); - return details; - }; - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - // this is the very old format that had just the array - if (jsonDoc.isArray()) - { - return getInfoFromArray(jsonDoc.array()); - } - else if (jsonDoc.isObject()) - { - auto val = jsonDoc.object().value("modinfoversion"); - if(val.isUndefined()) { - val = jsonDoc.object().value("modListVersion"); - } - int version = val.toDouble(); - if (version != 2) - { - qCritical() << "BAD stuff happened to mod json:"; - qCritical() << contents; - return nullptr; - } - auto arrVal = jsonDoc.object().value("modlist"); - if(arrVal.isUndefined()) { - arrVal = jsonDoc.object().value("modList"); - } - if (arrVal.isArray()) - { - return getInfoFromArray(arrVal.toArray()); - } - } - return nullptr; -} - -// https://github.com/MinecraftForge/Documentation/blob/5ab4ba6cf9abc0ac4c0abd96ad187461aefd72af/docs/gettingstarted/structuring.md -std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents) -{ - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - - char errbuf[200]; - // top-level table - toml_table_t* tomlData = toml_parse(contents.data(), errbuf, sizeof(errbuf)); - - if(!tomlData) - { - return nullptr; - } - - // array defined by [[mods]] - toml_array_t* tomlModsArr = toml_array_in(tomlData, "mods"); - // we only really care about the first element, since multiple mods in one file is not supported by us at the moment - toml_table_t* tomlModsTable0 = toml_table_at(tomlModsArr, 0); - - // mandatory properties - always in [[mods]] - toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId"); - if(modIdDatum.ok) - { - details->mod_id = modIdDatum.u.s; - // library says this is required for strings - free(modIdDatum.u.s); - } - toml_datum_t versionDatum = toml_string_in(tomlModsTable0, "version"); - if(versionDatum.ok) - { - details->version = versionDatum.u.s; - free(versionDatum.u.s); - } - toml_datum_t displayNameDatum = toml_string_in(tomlModsTable0, "displayName"); - if(displayNameDatum.ok) - { - details->name = displayNameDatum.u.s; - free(displayNameDatum.u.s); - } - toml_datum_t descriptionDatum = toml_string_in(tomlModsTable0, "description"); - if(descriptionDatum.ok) - { - details->description = descriptionDatum.u.s; - free(descriptionDatum.u.s); - } - - // optional properties - can be in the root table or [[mods]] - toml_datum_t authorsDatum = toml_string_in(tomlData, "authors"); - QString authors = ""; - if(authorsDatum.ok) - { - authors = authorsDatum.u.s; - free(authorsDatum.u.s); - } - else - { - authorsDatum = toml_string_in(tomlModsTable0, "authors"); - if(authorsDatum.ok) - { - authors = authorsDatum.u.s; - free(authorsDatum.u.s); - } - } - if(!authors.isEmpty()) - { - // author information is stored as a string now, not a list - details->authors.append(authors); - } - // is credits even used anywhere? including this for completion/parity with old data version - toml_datum_t creditsDatum = toml_string_in(tomlData, "credits"); - QString credits = ""; - if(creditsDatum.ok) - { - authors = creditsDatum.u.s; - free(creditsDatum.u.s); - } - else - { - creditsDatum = toml_string_in(tomlModsTable0, "credits"); - if(creditsDatum.ok) - { - credits = creditsDatum.u.s; - free(creditsDatum.u.s); - } - } - details->credits = credits; - toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL"); - QString homeurl = ""; - if(homeurlDatum.ok) - { - homeurl = homeurlDatum.u.s; - free(homeurlDatum.u.s); - } - else - { - homeurlDatum = toml_string_in(tomlModsTable0, "displayURL"); - if(homeurlDatum.ok) - { - homeurl = homeurlDatum.u.s; - free(homeurlDatum.u.s); - } - } - if(!homeurl.isEmpty()) - { - // fix up url. - if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) - { - homeurl.prepend("http://"); - } - } - details->homeurl = homeurl; - - // this seems to be recursive, so it should free everything - toml_free(tomlData); - - return details; -} - -// https://fabricmc.net/wiki/documentation:fabric_mod_json -std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents) -{ - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - auto object = jsonDoc.object(); - auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0; - - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - - details->mod_id = object.value("id").toString(); - details->version = object.value("version").toString(); - - details->name = object.contains("name") ? object.value("name").toString() : details->mod_id; - details->description = object.value("description").toString(); - - if (schemaVersion >= 1) - { - QJsonArray authors = object.value("authors").toArray(); - for (auto author: authors) - { - if(author.isObject()) { - details->authors.append(author.toObject().value("name").toString()); - } - else { - details->authors.append(author.toString()); - } - } - - if (object.contains("contact")) - { - QJsonObject contact = object.value("contact").toObject(); - - if (contact.contains("homepage")) - { - details->homeurl = contact.value("homepage").toString(); - } - } - } - return details; -} - -std::shared_ptr<ModDetails> ReadForgeInfo(QByteArray contents) -{ - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - // Read the data - details->name = "Minecraft Forge"; - details->mod_id = "Forge"; - details->homeurl = "http://www.minecraftforge.net/forum/"; - INIFile ini; - if (!ini.loadFile(contents)) - return details; - - QString major = ini.get("forge.major.number", "0").toString(); - QString minor = ini.get("forge.minor.number", "0").toString(); - QString revision = ini.get("forge.revision.number", "0").toString(); - QString build = ini.get("forge.build.number", "0").toString(); - - details->version = major + "." + minor + "." + revision + "." + build; - return details; -} - -std::shared_ptr<ModDetails> ReadLiteModInfo(QByteArray contents) -{ - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - auto object = jsonDoc.object(); - if (object.contains("name")) - { - details->mod_id = details->name = object.value("name").toString(); - } - if (object.contains("version")) - { - details->version = object.value("version").toString(""); - } - else - { - details->version = object.value("revision").toString(""); - } - details->mcversion = object.value("mcversion").toString(); - auto author = object.value("author").toString(); - if(!author.isEmpty()) { - details->authors.append(author); - } - details->description = object.value("description").toString(); - details->homeurl = object.value("url").toString(); - return details; -} - -} - -LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile): - m_token(token), - m_type(type), - m_modFile(modFile), - m_result(new Result()) -{ -} - -void LocalModParseTask::processAsZip() -{ - QuaZip zip(m_modFile.filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return; - - QuaZipFile file(&zip); - - if (zip.setCurrentFile("META-INF/mods.toml")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadMCModTOML(file.readAll()); - file.close(); - - // to replace ${file.jarVersion} with the actual version, as needed - if (m_result->details && m_result->details->version == "${file.jarVersion}") - { - if (zip.setCurrentFile("META-INF/MANIFEST.MF")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - // quick and dirty line-by-line parser - auto manifestLines = file.readAll().split('\n'); - QString manifestVersion = ""; - for (auto &line : manifestLines) - { - if (QString(line).startsWith("Implementation-Version: ")) - { - manifestVersion = QString(line).remove("Implementation-Version: "); - break; - } - } - - // some mods use ${projectversion} in their build.gradle, causing this mess to show up in MANIFEST.MF - // also keep with forge's behavior of setting the version to "NONE" if none is found - if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") - { - manifestVersion = "NONE"; - } - - m_result->details->version = manifestVersion; - - file.close(); - } - } - - zip.close(); - return; - } - else if (zip.setCurrentFile("mcmod.info")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadMCModInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - else if (zip.setCurrentFile("fabric.mod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadFabricModInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - else if (zip.setCurrentFile("forgeversion.properties")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadForgeInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - - zip.close(); -} - -void LocalModParseTask::processAsFolder() -{ - QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info")); - if (mcmod_info.isFile()) - { - QFile mcmod(mcmod_info.filePath()); - if (!mcmod.open(QIODevice::ReadOnly)) - return; - auto data = mcmod.readAll(); - if (data.isEmpty() || data.isNull()) - return; - m_result->details = ReadMCModInfo(data); - } -} - -void LocalModParseTask::processAsLitemod() -{ - QuaZip zip(m_modFile.filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return; - - QuaZipFile file(&zip); - - if (zip.setCurrentFile("litemod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadLiteModInfo(file.readAll()); - file.close(); - } - zip.close(); -} - -void LocalModParseTask::run() -{ - switch(m_type) - { - case Mod::MOD_ZIPFILE: - processAsZip(); - break; - case Mod::MOD_FOLDER: - processAsFolder(); - break; - case Mod::MOD_LITEMOD: - processAsLitemod(); - break; - default: - break; - } - emit finished(m_token); -} diff --git a/api/logic/minecraft/mod/LocalModParseTask.h b/api/logic/minecraft/mod/LocalModParseTask.h deleted file mode 100644 index 0f119ba6..00000000 --- a/api/logic/minecraft/mod/LocalModParseTask.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once -#include <QRunnable> -#include <QDebug> -#include <QObject> -#include "Mod.h" -#include "ModDetails.h" - -class LocalModParseTask : public QObject, public QRunnable -{ - Q_OBJECT -public: - struct Result { - QString id; - std::shared_ptr<ModDetails> details; - }; - using ResultPtr = std::shared_ptr<Result>; - ResultPtr result() const { - return m_result; - } - - LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile); - void run(); - -signals: - void finished(int token); - -private: - void processAsZip(); - void processAsFolder(); - void processAsLitemod(); - -private: - int m_token; - Mod::ModType m_type; - QFileInfo m_modFile; - ResultPtr m_result; -}; diff --git a/api/logic/minecraft/mod/Mod.cpp b/api/logic/minecraft/mod/Mod.cpp deleted file mode 100644 index b6bff29b..00000000 --- a/api/logic/minecraft/mod/Mod.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <QDir> -#include <QString> - -#include "Mod.h" -#include <QDebug> -#include <FileSystem.h> - -namespace { - -ModDetails invalidDetails; - -} - - -Mod::Mod(const QFileInfo &file) -{ - repath(file); - m_changedDateTime = file.lastModified(); -} - -void Mod::repath(const QFileInfo &file) -{ - m_file = file; - QString name_base = file.fileName(); - - m_type = Mod::MOD_UNKNOWN; - - m_mmc_id = name_base; - - if (m_file.isDir()) - { - m_type = MOD_FOLDER; - m_name = name_base; - } - else if (m_file.isFile()) - { - if (name_base.endsWith(".disabled")) - { - m_enabled = false; - name_base.chop(9); - } - else - { - m_enabled = true; - } - if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) - { - m_type = MOD_ZIPFILE; - name_base.chop(4); - } - else if (name_base.endsWith(".litemod")) - { - m_type = MOD_LITEMOD; - name_base.chop(8); - } - else - { - m_type = MOD_SINGLEFILE; - } - m_name = name_base; - } -} - -bool Mod::enable(bool value) -{ - if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER) - return false; - - if (m_enabled == value) - return false; - - QString path = m_file.absoluteFilePath(); - if (value) - { - QFile foo(path); - if (!path.endsWith(".disabled")) - return false; - path.chop(9); - if (!foo.rename(path)) - return false; - } - else - { - QFile foo(path); - path += ".disabled"; - if (!foo.rename(path)) - return false; - } - repath(QFileInfo(path)); - m_enabled = value; - return true; -} - -bool Mod::destroy() -{ - m_type = MOD_UNKNOWN; - return FS::deletePath(m_file.filePath()); -} - - -const ModDetails & Mod::details() const -{ - if(!m_localDetails) - return invalidDetails; - return *m_localDetails; -} - - -QString Mod::version() const -{ - return details().version; -} - -QString Mod::name() const -{ - auto & d = details(); - if(!d.name.isEmpty()) { - return d.name; - } - return m_name; -} - -QString Mod::homeurl() const -{ - return details().homeurl; -} - -QString Mod::description() const -{ - return details().description; -} - -QStringList Mod::authors() const -{ - return details().authors; -} diff --git a/api/logic/minecraft/mod/Mod.h b/api/logic/minecraft/mod/Mod.h deleted file mode 100644 index f77ffd41..00000000 --- a/api/logic/minecraft/mod/Mod.h +++ /dev/null @@ -1,117 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include <QFileInfo> -#include <QDateTime> -#include <QList> -#include <memory> - -#include "multimc_logic_export.h" - -#include "ModDetails.h" - - - -class MULTIMC_LOGIC_EXPORT Mod -{ -public: - enum ModType - { - MOD_UNKNOWN, //!< Indicates an unspecified mod type. - MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. - MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). - MOD_FOLDER, //!< The mod is in a folder on the filesystem. - MOD_LITEMOD, //!< The mod is a litemod - }; - - Mod() = default; - Mod(const QFileInfo &file); - - QFileInfo filename() const - { - return m_file; - } - QString mmc_id() const - { - return m_mmc_id; - } - ModType type() const - { - return m_type; - } - bool valid() - { - return m_type != MOD_UNKNOWN; - } - - QDateTime dateTimeChanged() const - { - return m_changedDateTime; - } - - bool enabled() const - { - return m_enabled; - } - - const ModDetails &details() const; - - QString name() const; - QString version() const; - QString homeurl() const; - QString description() const; - QStringList authors() const; - - bool enable(bool value); - - // delete all the files of this mod - bool destroy(); - - // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) - void repath(const QFileInfo &file); - - bool shouldResolve() { - return !m_resolving && !m_resolved; - } - bool isResolving() { - return m_resolving; - } - int resolutionTicket() - { - return m_resolutionTicket; - } - void setResolving(bool resolving, int resolutionTicket) { - m_resolving = resolving; - m_resolutionTicket = resolutionTicket; - } - void finishResolvingWithDetails(std::shared_ptr<ModDetails> details){ - m_resolving = false; - m_resolved = true; - m_localDetails = details; - } - -protected: - QFileInfo m_file; - QDateTime m_changedDateTime; - QString m_mmc_id; - QString m_name; - bool m_enabled = true; - bool m_resolving = false; - bool m_resolved = false; - int m_resolutionTicket = 0; - ModType m_type = MOD_UNKNOWN; - std::shared_ptr<ModDetails> m_localDetails; -}; diff --git a/api/logic/minecraft/mod/ModDetails.h b/api/logic/minecraft/mod/ModDetails.h deleted file mode 100644 index 6ab4aee7..00000000 --- a/api/logic/minecraft/mod/ModDetails.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include <QString> -#include <QStringList> - -struct ModDetails -{ - QString mod_id; - QString name; - QString version; - QString mcversion; - QString homeurl; - QString updateurl; - QString description; - QStringList authors; - QString credits; -}; diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.cpp b/api/logic/minecraft/mod/ModFolderLoadTask.cpp deleted file mode 100644 index 88349877..00000000 --- a/api/logic/minecraft/mod/ModFolderLoadTask.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "ModFolderLoadTask.h" -#include <QDebug> - -ModFolderLoadTask::ModFolderLoadTask(QDir dir) : - m_dir(dir), m_result(new Result()) -{ -} - -void ModFolderLoadTask::run() -{ - m_dir.refresh(); - for (auto entry : m_dir.entryInfoList()) - { - Mod m(entry); - m_result->mods[m.mmc_id()] = m; - } - emit succeeded(); -} diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.h b/api/logic/minecraft/mod/ModFolderLoadTask.h deleted file mode 100644 index 8d720e65..00000000 --- a/api/logic/minecraft/mod/ModFolderLoadTask.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include <QRunnable> -#include <QObject> -#include <QDir> -#include <QMap> -#include "Mod.h" -#include <memory> - -class ModFolderLoadTask : public QObject, public QRunnable -{ - Q_OBJECT -public: - struct Result { - QMap<QString, Mod> mods; - }; - using ResultPtr = std::shared_ptr<Result>; - ResultPtr result() const { - return m_result; - } - -public: - ModFolderLoadTask(QDir dir); - void run(); -signals: - void succeeded(); -private: - QDir m_dir; - ResultPtr m_result; -}; diff --git a/api/logic/minecraft/mod/ModFolderModel.cpp b/api/logic/minecraft/mod/ModFolderModel.cpp deleted file mode 100644 index 031eebe5..00000000 --- a/api/logic/minecraft/mod/ModFolderModel.cpp +++ /dev/null @@ -1,554 +0,0 @@ -/* Copyright 2013-2021 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 "ModFolderModel.h" -#include <FileSystem.h> -#include <QMimeData> -#include <QUrl> -#include <QUuid> -#include <QString> -#include <QFileSystemWatcher> -#include <QDebug> -#include "ModFolderLoadTask.h" -#include <QThreadPool> -#include <algorithm> -#include "LocalModParseTask.h" - -ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir) -{ - 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_watcher = new QFileSystemWatcher(this); - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); -} - -void ModFolderModel::startWatching() -{ - if(is_watching) - return; - - 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 ModFolderModel::stopWatching() -{ - if(!is_watching) - return; - - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) - { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } -} - -bool ModFolderModel::update() -{ - if (!isValid()) { - return false; - } - if(m_update) { - scheduled_update = true; - return true; - } - - auto task = new ModFolderLoadTask(m_dir); - m_update = task->result(); - QThreadPool *threadPool = QThreadPool::globalInstance(); - connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate); - threadPool->start(task); - return true; -} - -void ModFolderModel::finishUpdate() -{ - QSet<QString> currentSet = modsIndex.keys().toSet(); - auto & newMods = m_update->mods; - QSet<QString> newSet = newMods.keys().toSet(); - - // see if the kept mods changed in some way - { - QSet<QString> kept = currentSet; - kept.intersect(newSet); - for(auto & keptMod: kept) { - auto & newMod = newMods[keptMod]; - auto row = modsIndex[keptMod]; - auto & currentMod = mods[row]; - if(newMod.dateTimeChanged() == currentMod.dateTimeChanged()) { - // no significant change, ignore... - continue; - } - auto & oldMod = mods[row]; - if(oldMod.isResolving()) { - activeTickets.remove(oldMod.resolutionTicket()); - } - oldMod = newMod; - resolveMod(mods[row]); - emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); - } - } - - // remove mods no longer present - { - QSet<QString> removed = currentSet; - QList<int> removedRows; - removed.subtract(newSet); - for(auto & removedMod: removed) { - removedRows.append(modsIndex[removedMod]); - } - std::sort(removedRows.begin(), removedRows.end(), std::greater<int>()); - for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) { - int removedIndex = *iter; - beginRemoveRows(QModelIndex(), removedIndex, removedIndex); - auto removedIter = mods.begin() + removedIndex; - if(removedIter->isResolving()) { - activeTickets.remove(removedIter->resolutionTicket()); - } - mods.erase(removedIter); - endRemoveRows(); - } - } - - // add new mods to the end - { - QSet<QString> added = newSet; - added.subtract(currentSet); - beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1); - for(auto & addedMod: added) { - mods.append(newMods[addedMod]); - resolveMod(mods.last()); - } - endInsertRows(); - } - - // update index - { - modsIndex.clear(); - int idx = 0; - for(auto & mod: mods) { - modsIndex[mod.mmc_id()] = idx; - idx++; - } - } - - m_update.reset(); - - emit updateFinished(); - - if(scheduled_update) { - scheduled_update = false; - update(); - } -} - -void ModFolderModel::resolveMod(Mod& m) -{ - if(!m.shouldResolve()) { - return; - } - - auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename()); - auto result = task->result(); - result->id = m.mmc_id(); - activeTickets.insert(nextResolutionTicket, result); - m.setResolving(true, nextResolutionTicket); - nextResolutionTicket++; - QThreadPool *threadPool = QThreadPool::globalInstance(); - connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse); - threadPool->start(task); -} - -void ModFolderModel::finishModParse(int token) -{ - auto iter = activeTickets.find(token); - if(iter == activeTickets.end()) { - return; - } - auto result = *iter; - activeTickets.remove(token); - int row = modsIndex[result->id]; - auto & mod = mods[row]; - mod.finishResolvingWithDetails(result->details); - emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); -} - -void ModFolderModel::disableInteraction(bool disabled) -{ - if (interaction_disabled == disabled) { - return; - } - interaction_disabled = disabled; - if(size()) { - emit dataChanged(index(0), index(size() - 1)); - } -} - -void ModFolderModel::directoryChanged(QString path) -{ - update(); -} - -bool ModFolderModel::isValid() -{ - return m_dir.exists() && m_dir.isReadable(); -} - -// FIXME: this does not take disabled mod (with extra .disable extension) into account... -bool ModFolderModel::installMod(const QString &filename) -{ - if(interaction_disabled) { - return false; - } - - // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - auto originalPath = FS::NormalizePath(filename); - QFileInfo fileinfo(originalPath); - - if (!fileinfo.exists() || !fileinfo.isReadable()) - { - qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath; - return false; - } - qDebug() << "installing: " << fileinfo.absoluteFilePath(); - - Mod installedMod(fileinfo); - if (!installedMod.valid()) - { - qDebug() << originalPath << "is not a valid mod. Ignoring it."; - return false; - } - - auto type = installedMod.type(); - if (type == Mod::MOD_UNKNOWN) - { - qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it."; - return false; - } - - auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName())); - if(originalPath == newpath) - { - qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense..."; - return false; - } - - if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) - { - if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled"))) - { - if(!QFile::remove(newpath)) - { - // FIXME: report error in a user-visible way - qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; - return false; - } - qDebug() << newpath << "has been deleted."; - } - if (!QFile::copy(fileinfo.filePath(), newpath)) - { - qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; - // FIXME: report error in a user-visible way - return false; - } - FS::updateTimestamp(newpath); - installedMod.repath(newpath); - update(); - return true; - } - else if (type == Mod::MOD_FOLDER) - { - QString from = fileinfo.filePath(); - if(QFile::exists(newpath)) - { - qDebug() << "Ignoring folder " << from << ", it would merge with " << newpath; - return false; - } - - if (!FS::copy(from, newpath)()) - { - qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed."; - return false; - } - installedMod.repath(newpath); - update(); - return true; - } - return false; -} - -bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable) -{ - if(interaction_disabled) { - return false; - } - - if(indexes.isEmpty()) - return true; - - for (auto index: indexes) - { - if(index.column() != 0) { - continue; - } - setModStatus(index.row(), enable); - } - return true; -} - -bool ModFolderModel::deleteMods(const QModelIndexList& indexes) -{ - if(interaction_disabled) { - return false; - } - - if(indexes.isEmpty()) - return true; - - for (auto i: indexes) - { - Mod &m = mods[i.row()]; - m.destroy(); - } - return true; -} - -int ModFolderModel::columnCount(const QModelIndex &parent) const -{ - return NUM_COLUMNS; -} - -QVariant ModFolderModel::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: { - switch(mods[row].type()) { - case Mod::MOD_FOLDER: - return tr("Folder"); - case Mod::MOD_SINGLEFILE: - return tr("File"); - default: - break; - } - return mods[row].version(); - } - case DateColumn: - return mods[row].dateTimeChanged(); - - 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 ModFolderModel::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) - { - return setModStatus(index.row(), Toggle); - } - return false; -} - -bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action) -{ - if(row < 0 || row >= mods.size()) { - return false; - } - - auto &mod = mods[row]; - bool desiredStatus; - switch(action) { - case Enable: - desiredStatus = true; - break; - case Disable: - desiredStatus = false; - break; - case Toggle: - default: - desiredStatus = !mod.enabled(); - break; - } - - if(desiredStatus == mod.enabled()) { - return true; - } - - // preserve the row, but change its ID - auto oldId = mod.mmc_id(); - if(!mod.enable(!mod.enabled())) { - return false; - } - auto newId = mod.mmc_id(); - if(modsIndex.contains(newId)) { - // NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled - // But is it necessary? - } - modsIndex.remove(oldId); - modsIndex[newId] = row; - emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); - return true; -} - -QVariant ModFolderModel::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"); - case DateColumn: - return tr("Last changed"); - 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."); - case DateColumn: - return tr("The date and time this mod was last changed (or added)."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); -} - -Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - auto flags = defaultFlags; - if(interaction_disabled) { - flags &= ~Qt::ItemIsDropEnabled; - } - else - { - flags |= Qt::ItemIsDropEnabled; - if(index.isValid()) { - flags |= Qt::ItemIsUserCheckable; - } - } - return flags; -} - -Qt::DropActions ModFolderModel::supportedDropActions() const -{ - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; -} - -QStringList ModFolderModel::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - return types; -} - -bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) -{ - if (action == Qt::IgnoreAction) - { - return true; - } - - // check if the action is supported - if (!data || !(action & supportedDropActions())) - { - return false; - } - - // files dropped from outside? - if (data->hasUrls()) - { - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - { - continue; - } - // TODO: implement not only copy, but also move - // FIXME: handle errors here - installMod(url.toLocalFile()); - } - return true; - } - return false; -} diff --git a/api/logic/minecraft/mod/ModFolderModel.h b/api/logic/minecraft/mod/ModFolderModel.h deleted file mode 100644 index b0a76121..00000000 --- a/api/logic/minecraft/mod/ModFolderModel.h +++ /dev/null @@ -1,149 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QList> -#include <QMap> -#include <QSet> -#include <QString> -#include <QDir> -#include <QAbstractListModel> - -#include "Mod.h" - -#include "multimc_logic_export.h" -#include "ModFolderLoadTask.h" -#include "LocalModParseTask.h" - -class LegacyInstance; -class BaseInstance; -class QFileSystemWatcher; - -/** - * A legacy mod list. - * Backed by a folder. - */ -class MULTIMC_LOGIC_EXPORT ModFolderModel : public QAbstractListModel -{ - Q_OBJECT -public: - enum Columns - { - ActiveColumn = 0, - NameColumn, - VersionColumn, - DateColumn, - NUM_COLUMNS - }; - enum ModStatusAction { - Disable, - Enable, - Toggle - }; - ModFolderModel(const QString &dir); - - 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; - Qt::DropActions supportedDropActions() const override; - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - QStringList mimeTypes() const override; - bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; - - virtual int rowCount(const QModelIndex &) const override - { - return size(); - } - - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - virtual int columnCount(const QModelIndex &parent) const override; - - size_t size() const - { - return mods.size(); - } - ; - bool empty() const - { - return size() == 0; - } - Mod &operator[](size_t index) - { - return mods[index]; - } - const Mod &at(size_t index) const - { - return mods.at(index); - } - - /// Reloads the mod list and returns true if the list changed. - bool update(); - - /** - * Adds the given mod to the list at the given index - if the list supports custom ordering - */ - bool installMod(const QString& filename); - - /// Deletes all the selected mods - bool deleteMods(const QModelIndexList &indexes); - - /// Enable or disable listed mods - bool setModStatus(const QModelIndexList &indexes, ModStatusAction action); - - void startWatching(); - void stopWatching(); - - bool isValid(); - - QDir dir() - { - return m_dir; - } - - const QList<Mod> & allMods() - { - return mods; - } - -public slots: - void disableInteraction(bool disabled); - -private -slots: - void directoryChanged(QString path); - void finishUpdate(); - void finishModParse(int token); - -signals: - void updateFinished(); - -private: - void resolveMod(Mod& m); - bool setModStatus(int index, ModStatusAction action); - -protected: - QFileSystemWatcher *m_watcher; - bool is_watching = false; - ModFolderLoadTask::ResultPtr m_update; - bool scheduled_update = false; - bool interaction_disabled = false; - QDir m_dir; - QMap<QString, int> modsIndex; - QMap<int, LocalModParseTask::ResultPtr> activeTickets; - int nextResolutionTicket = 0; - QList<Mod> mods; -}; diff --git a/api/logic/minecraft/mod/ModFolderModel_test.cpp b/api/logic/minecraft/mod/ModFolderModel_test.cpp deleted file mode 100644 index 76f16ed5..00000000 --- a/api/logic/minecraft/mod/ModFolderModel_test.cpp +++ /dev/null @@ -1,53 +0,0 @@ - -#include <QTest> -#include <QTemporaryDir> -#include "TestUtil.h" - -#include "FileSystem.h" -#include "minecraft/mod/ModFolderModel.h" - -class ModFolderModelTest : public QObject -{ - Q_OBJECT - -private -slots: - // test for GH-1178 - install a folder with files to a mod list - void test_1178() - { - // source - QString source = QFINDTESTDATA("data/test_folder"); - - // sanity check - QVERIFY(!source.endsWith('/')); - - auto verify = [](QString path) - { - QDir target_dir(FS::PathCombine(path, "test_folder")); - QVERIFY(target_dir.entryList().contains("pack.mcmeta")); - QVERIFY(target_dir.entryList().contains("assets")); - }; - - // 1. test with no trailing / - { - QString folder = source; - QTemporaryDir tempDir; - ModFolderModel m(tempDir.path()); - m.installMod(folder); - verify(tempDir.path()); - } - - // 2. test with trailing / - { - QString folder = source + '/'; - QTemporaryDir tempDir; - ModFolderModel m(tempDir.path()); - m.installMod(folder); - verify(tempDir.path()); - } - } -}; - -QTEST_GUILESS_MAIN(ModFolderModelTest) - -#include "ModFolderModel_test.moc" diff --git a/api/logic/minecraft/mod/ResourcePackFolderModel.cpp b/api/logic/minecraft/mod/ResourcePackFolderModel.cpp deleted file mode 100644 index f3d7f566..00000000 --- a/api/logic/minecraft/mod/ResourcePackFolderModel.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "ResourcePackFolderModel.h" - -ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) { -} - -QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::ToolTipRole) { - switch (section) { - case ActiveColumn: - return tr("Is the resource pack enabled?"); - case NameColumn: - return tr("The name of the resource pack."); - case VersionColumn: - return tr("The version of the resource pack."); - case DateColumn: - return tr("The date and time this resource pack was last changed (or added)."); - default: - return QVariant(); - } - } - - return ModFolderModel::headerData(section, orientation, role); -} diff --git a/api/logic/minecraft/mod/ResourcePackFolderModel.h b/api/logic/minecraft/mod/ResourcePackFolderModel.h deleted file mode 100644 index 47eb4bb2..00000000 --- a/api/logic/minecraft/mod/ResourcePackFolderModel.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "ModFolderModel.h" - -class MULTIMC_LOGIC_EXPORT ResourcePackFolderModel : public ModFolderModel -{ - Q_OBJECT - -public: - explicit ResourcePackFolderModel(const QString &dir); - - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; -}; diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.cpp b/api/logic/minecraft/mod/TexturePackFolderModel.cpp deleted file mode 100644 index d5956da1..00000000 --- a/api/logic/minecraft/mod/TexturePackFolderModel.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "TexturePackFolderModel.h" - -TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) { -} - -QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::ToolTipRole) { - switch (section) { - case ActiveColumn: - return tr("Is the texture pack enabled?"); - case NameColumn: - return tr("The name of the texture pack."); - case VersionColumn: - return tr("The version of the texture pack."); - case DateColumn: - return tr("The date and time this texture pack was last changed (or added)."); - default: - return QVariant(); - } - } - - return ModFolderModel::headerData(section, orientation, role); -} diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.h b/api/logic/minecraft/mod/TexturePackFolderModel.h deleted file mode 100644 index d773b17b..00000000 --- a/api/logic/minecraft/mod/TexturePackFolderModel.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "ModFolderModel.h" - -class MULTIMC_LOGIC_EXPORT TexturePackFolderModel : public ModFolderModel -{ - Q_OBJECT - -public: - explicit TexturePackFolderModel(const QString &dir); - - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; -}; diff --git a/api/logic/minecraft/services/SkinDelete.cpp b/api/logic/minecraft/services/SkinDelete.cpp deleted file mode 100644 index 34977257..00000000 --- a/api/logic/minecraft/services/SkinDelete.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "SkinDelete.h" -#include <QNetworkRequest> -#include <QHttpMultiPart> -#include <Env.h> - -SkinDelete::SkinDelete(QObject *parent, AuthSessionPtr session) - : Task(parent), m_session(session) -{ -} - -void SkinDelete::executeTask() -{ - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active")); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_session->access_token).toLocal8Bit()); - QNetworkReply *rep = ENV.qnam().deleteResource(request); - m_reply = std::shared_ptr<QNetworkReply>(rep); - - setStatus(tr("Deleting skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); -} - -void SkinDelete::downloadError(QNetworkReply::NetworkError error) -{ - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); -} - -void SkinDelete::downloadFinished() -{ - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); -} - diff --git a/api/logic/minecraft/services/SkinDelete.h b/api/logic/minecraft/services/SkinDelete.h deleted file mode 100644 index 705ce8ef..00000000 --- a/api/logic/minecraft/services/SkinDelete.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include <QFile> -#include <QtNetwork/QtNetwork> -#include <memory> -#include <minecraft/auth/AuthSession.h> -#include "tasks/Task.h" -#include "multimc_logic_export.h" - -typedef std::shared_ptr<class SkinDelete> SkinDeletePtr; - -class MULTIMC_LOGIC_EXPORT SkinDelete : public Task -{ - Q_OBJECT -public: - SkinDelete(QObject *parent, AuthSessionPtr session); - virtual ~SkinDelete() = default; - -private: - AuthSessionPtr m_session; - std::shared_ptr<QNetworkReply> m_reply; - -protected: - virtual void executeTask(); - -public slots: - void downloadError(QNetworkReply::NetworkError); - void downloadFinished(); -}; - diff --git a/api/logic/minecraft/services/SkinUpload.cpp b/api/logic/minecraft/services/SkinUpload.cpp deleted file mode 100644 index 4e5a1698..00000000 --- a/api/logic/minecraft/services/SkinUpload.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "SkinUpload.h" -#include <QNetworkRequest> -#include <QHttpMultiPart> -#include <Env.h> - -QByteArray getVariant(SkinUpload::Model model) { - switch (model) { - default: - qDebug() << "Unknown skin type!"; - case SkinUpload::STEVE: - return "CLASSIC"; - case SkinUpload::ALEX: - return "SLIM"; - } -} - -SkinUpload::SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, SkinUpload::Model model) - : Task(parent), m_model(model), m_skin(skin), m_session(session) -{ -} - -void SkinUpload::executeTask() -{ - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins")); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_session->access_token).toLocal8Bit()); - QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - - QHttpPart skin; - skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); - skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); - skin.setBody(m_skin); - - QHttpPart model; - model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\"")); - model.setBody(getVariant(m_model)); - - multiPart->append(skin); - multiPart->append(model); - - QNetworkReply *rep = ENV.qnam().post(request, multiPart); - m_reply = std::shared_ptr<QNetworkReply>(rep); - - setStatus(tr("Uploading skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); -} - -void SkinUpload::downloadError(QNetworkReply::NetworkError error) -{ - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); -} - -void SkinUpload::downloadFinished() -{ - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); -} diff --git a/api/logic/minecraft/services/SkinUpload.h b/api/logic/minecraft/services/SkinUpload.h deleted file mode 100644 index c77abb03..00000000 --- a/api/logic/minecraft/services/SkinUpload.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include <QFile> -#include <QtNetwork/QtNetwork> -#include <memory> -#include <minecraft/auth/AuthSession.h> -#include "tasks/Task.h" -#include "multimc_logic_export.h" - -typedef std::shared_ptr<class SkinUpload> SkinUploadPtr; - -class MULTIMC_LOGIC_EXPORT SkinUpload : public Task -{ - Q_OBJECT -public: - enum Model - { - STEVE, - ALEX - }; - - // Note this class takes ownership of the file. - SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, Model model = STEVE); - virtual ~SkinUpload() {} - -private: - Model m_model; - QByteArray m_skin; - AuthSessionPtr m_session; - std::shared_ptr<QNetworkReply> m_reply; -protected: - virtual void executeTask(); - -public slots: - - void downloadError(QNetworkReply::NetworkError); - - void downloadFinished(); -}; diff --git a/api/logic/minecraft/testdata/1.9-simple.json b/api/logic/minecraft/testdata/1.9-simple.json deleted file mode 100644 index 574c5b06..00000000 --- a/api/logic/minecraft/testdata/1.9-simple.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "assets": "1.9", - "id": "1.9", - "libraries": [ - { - "name": "oshi-project:oshi-core:1.1" - }, - { - "name": "net.java.dev.jna:jna:3.4.0" - }, - { - "name": "net.java.dev.jna:platform:3.4.0" - }, - { - "name": "com.ibm.icu:icu4j-core-mojang:51.2" - }, - { - "name": "net.sf.jopt-simple:jopt-simple:4.6" - }, - { - "name": "com.paulscode:codecjorbis:20101023" - }, - { - "name": "com.paulscode:codecwav:20101023" - }, - { - "name": "com.paulscode:libraryjavasound:20101123" - }, - { - "name": "com.paulscode:librarylwjglopenal:20100824" - }, - { - "name": "com.paulscode:soundsystem:20120107" - }, - { - "name": "io.netty:netty-all:4.0.23.Final" - }, - { - "name": "com.google.guava:guava:17.0" - }, - { - "name": "org.apache.commons:commons-lang3:3.3.2" - }, - { - "name": "commons-io:commons-io:2.4" - }, - { - "name": "commons-codec:commons-codec:1.9" - }, - { - "name": "net.java.jinput:jinput:2.0.5" - }, - { - "name": "net.java.jutils:jutils:1.0.0" - }, - { - "name": "com.google.code.gson:gson:2.2.4" - }, - { - "name": "com.mojang:authlib:1.5.22" - }, - { - "name": "com.mojang:realms:1.8.4" - }, - { - "name": "org.apache.commons:commons-compress:1.8.1" - }, - { - "name": "org.apache.httpcomponents:httpclient:4.3.3" - }, - { - "name": "commons-logging:commons-logging:1.1.3" - }, - { - "name": "org.apache.httpcomponents:httpcore:4.3.2" - }, - { - "name": "org.apache.logging.log4j:log4j-api:2.0-beta9" - }, - { - "name": "org.apache.logging.log4j:log4j-core:2.0-beta9" - }, - { - "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209", - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209", - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "name": "org.lwjgl.lwjgl:lwjgl:2.9.2-nightly-20140822", - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.2-nightly-20140822", - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.2-nightly-20140822", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "net.java.jinput:jinput-platform:2.0.5", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - } - } - ], - "mainClass": "net.minecraft.client.main.Main", - "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userType ${user_type} --versionType ${version_type}", - "minimumLauncherVersion": 18, - "releaseTime": "2016-02-29T13:49:54+00:00", - "time": "2016-03-01T13:14:53+00:00", - "type": "release" -} diff --git a/api/logic/minecraft/testdata/1.9.json b/api/logic/minecraft/testdata/1.9.json deleted file mode 100644 index 697c6059..00000000 --- a/api/logic/minecraft/testdata/1.9.json +++ /dev/null @@ -1,529 +0,0 @@ -{ - "assetIndex": { - "id": "1.9", - "sha1": "cde65b47a43f638653ab1da3848b53f8a7477b16", - "size": 136916, - "totalSize": 119917473, - "url": "https://launchermeta.mojang.com/mc-staging/assets/1.9/cde65b47a43f638653ab1da3848b53f8a7477b16/1.9.json" - }, - "assets": "1.9", - "downloads": { - "client": { - "sha1": "2f67dfe8953299440d1902f9124f0f2c3a2c940f", - "size": 8697592, - "url": "https://launcher.mojang.com/mc/game/1.9/client/2f67dfe8953299440d1902f9124f0f2c3a2c940f/client.jar" - }, - "server": { - "sha1": "b4d449cf2918e0f3bd8aa18954b916a4d1880f0d", - "size": 8848015, - "url": "https://launcher.mojang.com/mc/game/1.9/server/b4d449cf2918e0f3bd8aa18954b916a4d1880f0d/server.jar" - } - }, - "id": "1.9", - "libraries": [ - { - "downloads": { - "artifact": { - "path": "oshi-project/oshi-core/1.1/oshi-core-1.1.jar", - "sha1": "9ddf7b048a8d701be231c0f4f95fd986198fd2d8", - "size": 30973, - "url": "https://libraries.minecraft.net/oshi-project/oshi-core/1.1/oshi-core-1.1.jar" - } - }, - "name": "oshi-project:oshi-core:1.1" - }, - { - "downloads": { - "artifact": { - "path": "net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar", - "sha1": "803ff252fedbd395baffd43b37341dc4a150a554", - "size": 1008730, - "url": "https://libraries.minecraft.net/net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar" - } - }, - "name": "net.java.dev.jna:jna:3.4.0" - }, - { - "downloads": { - "artifact": { - "path": "net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar", - "sha1": "e3f70017be8100d3d6923f50b3d2ee17714e9c13", - "size": 913436, - "url": "https://libraries.minecraft.net/net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar" - } - }, - "name": "net.java.dev.jna:platform:3.4.0" - }, - { - "downloads": { - "artifact": { - "path": "com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar", - "sha1": "63d216a9311cca6be337c1e458e587f99d382b84", - "size": 1634692, - "url": "https://libraries.minecraft.net/com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar" - } - }, - "name": "com.ibm.icu:icu4j-core-mojang:51.2" - }, - { - "downloads": { - "artifact": { - "path": "net/sf/jopt-simple/jopt-simple/4.6/jopt-simple-4.6.jar", - "sha1": "306816fb57cf94f108a43c95731b08934dcae15c", - "size": 62477, - "url": "https://libraries.minecraft.net/net/sf/jopt-simple/jopt-simple/4.6/jopt-simple-4.6.jar" - } - }, - "name": "net.sf.jopt-simple:jopt-simple:4.6" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar", - "sha1": "c73b5636faf089d9f00e8732a829577de25237ee", - "size": 103871, - "url": "https://libraries.minecraft.net/com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar" - } - }, - "name": "com.paulscode:codecjorbis:20101023" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar", - "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da", - "size": 5618, - "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar" - } - }, - "name": "com.paulscode:codecwav:20101023" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar", - "sha1": "5c5e304366f75f9eaa2e8cca546a1fb6109348b3", - "size": 21679, - "url": "https://libraries.minecraft.net/com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar" - } - }, - "name": "com.paulscode:libraryjavasound:20101123" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar", - "sha1": "73e80d0794c39665aec3f62eee88ca91676674ef", - "size": 18981, - "url": "https://libraries.minecraft.net/com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar" - } - }, - "name": "com.paulscode:librarylwjglopenal:20100824" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/soundsystem/20120107/soundsystem-20120107.jar", - "sha1": "419c05fe9be71f792b2d76cfc9b67f1ed0fec7f6", - "size": 65020, - "url": "https://libraries.minecraft.net/com/paulscode/soundsystem/20120107/soundsystem-20120107.jar" - } - }, - "name": "com.paulscode:soundsystem:20120107" - }, - { - "downloads": { - "artifact": { - "path": "io/netty/netty-all/4.0.23.Final/netty-all-4.0.23.Final.jar", - "sha1": "0294104aaf1781d6a56a07d561e792c5d0c95f45", - "size": 1779991, - "url": "https://libraries.minecraft.net/io/netty/netty-all/4.0.23.Final/netty-all-4.0.23.Final.jar" - } - }, - "name": "io.netty:netty-all:4.0.23.Final" - }, - { - "downloads": { - "artifact": { - "path": "com/google/guava/guava/17.0/guava-17.0.jar", - "sha1": "9c6ef172e8de35fd8d4d8783e4821e57cdef7445", - "size": 2243036, - "url": "https://libraries.minecraft.net/com/google/guava/guava/17.0/guava-17.0.jar" - } - }, - "name": "com.google.guava:guava:17.0" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/commons/commons-lang3/3.3.2/commons-lang3-3.3.2.jar", - "sha1": "90a3822c38ec8c996e84c16a3477ef632cbc87a3", - "size": 412739, - "url": "https://libraries.minecraft.net/org/apache/commons/commons-lang3/3.3.2/commons-lang3-3.3.2.jar" - } - }, - "name": "org.apache.commons:commons-lang3:3.3.2" - }, - { - "downloads": { - "artifact": { - "path": "commons-io/commons-io/2.4/commons-io-2.4.jar", - "sha1": "b1b6ea3b7e4aa4f492509a4952029cd8e48019ad", - "size": 185140, - "url": "https://libraries.minecraft.net/commons-io/commons-io/2.4/commons-io-2.4.jar" - } - }, - "name": "commons-io:commons-io:2.4" - }, - { - "downloads": { - "artifact": { - "path": "commons-codec/commons-codec/1.9/commons-codec-1.9.jar", - "sha1": "9ce04e34240f674bc72680f8b843b1457383161a", - "size": 263965, - "url": "https://libraries.minecraft.net/commons-codec/commons-codec/1.9/commons-codec-1.9.jar" - } - }, - "name": "commons-codec:commons-codec:1.9" - }, - { - "downloads": { - "artifact": { - "path": "net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar", - "sha1": "39c7796b469a600f72380316f6b1f11db6c2c7c4", - "size": 208338, - "url": "https://libraries.minecraft.net/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar" - } - }, - "name": "net.java.jinput:jinput:2.0.5" - }, - { - "downloads": { - "artifact": { - "path": "net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar", - "sha1": "e12fe1fda814bd348c1579329c86943d2cd3c6a6", - "size": 7508, - "url": "https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar" - } - }, - "name": "net.java.jutils:jutils:1.0.0" - }, - { - "downloads": { - "artifact": { - "path": "com/google/code/gson/gson/2.2.4/gson-2.2.4.jar", - "sha1": "a60a5e993c98c864010053cb901b7eab25306568", - "size": 190432, - "url": "https://libraries.minecraft.net/com/google/code/gson/gson/2.2.4/gson-2.2.4.jar" - } - }, - "name": "com.google.code.gson:gson:2.2.4" - }, - { - "downloads": { - "artifact": { - "path": "com/mojang/authlib/1.5.22/authlib-1.5.22.jar", - "sha1": "afaa8f6df976fcb5520e76ef1d5798c9e6b5c0b2", - "size": 64539, - "url": "https://libraries.minecraft.net/com/mojang/authlib/1.5.22/authlib-1.5.22.jar" - } - }, - "name": "com.mojang:authlib:1.5.22" - }, - { - "downloads": { - "artifact": { - "path": "com/mojang/realms/1.8.4/realms-1.8.4.jar", - "sha1": "15f8dc326c97a96dee6e65392e145ad6d1cb46cb", - "size": 1131574, - "url": "https://libraries.minecraft.net/com/mojang/realms/1.8.4/realms-1.8.4.jar" - } - }, - "name": "com.mojang:realms:1.8.4" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar", - "sha1": "a698750c16740fd5b3871425f4cb3bbaa87f529d", - "size": 365552, - "url": "https://libraries.minecraft.net/org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar" - } - }, - "name": "org.apache.commons:commons-compress:1.8.1" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar", - "sha1": "18f4247ff4572a074444572cee34647c43e7c9c7", - "size": 589512, - "url": "https://libraries.minecraft.net/org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar" - } - }, - "name": "org.apache.httpcomponents:httpclient:4.3.3" - }, - { - "downloads": { - "artifact": { - "path": "commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar", - "sha1": "f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f", - "size": 62050, - "url": "https://libraries.minecraft.net/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar" - } - }, - "name": "commons-logging:commons-logging:1.1.3" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar", - "sha1": "31fbbff1ddbf98f3aa7377c94d33b0447c646b6e", - "size": 282269, - "url": "https://libraries.minecraft.net/org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar" - } - }, - "name": "org.apache.httpcomponents:httpcore:4.3.2" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar", - "sha1": "1dd66e68cccd907880229f9e2de1314bd13ff785", - "size": 108161, - "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar" - } - }, - "name": "org.apache.logging.log4j:log4j-api:2.0-beta9" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar", - "sha1": "678861ba1b2e1fccb594bb0ca03114bb05da9695", - "size": 681134, - "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar" - } - }, - "name": "org.apache.logging.log4j:log4j-core:2.0-beta9" - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar", - "sha1": "697517568c68e78ae0b4544145af031c81082dfe", - "size": 1047168, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar" - } - }, - "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209", - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar", - "sha1": "d51a7c040a721d13efdfbd34f8b257b2df882ad0", - "size": 173887, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar" - } - }, - "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209", - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", - "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", - "size": 22, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" - }, - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", - "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", - "size": 578680, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", - "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", - "size": 426822, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", - "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", - "size": 613748, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl/2.9.2-nightly-20140822/lwjgl-2.9.2-nightly-20140822.jar", - "sha1": "7707204c9ffa5d91662de95f0a224e2f721b22af", - "size": 1045632, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.2-nightly-20140822/lwjgl-2.9.2-nightly-20140822.jar" - } - }, - "name": "org.lwjgl.lwjgl:lwjgl:2.9.2-nightly-20140822", - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl_util/2.9.2-nightly-20140822/lwjgl_util-2.9.2-nightly-20140822.jar", - "sha1": "f0e612c840a7639c1f77f68d72a28dae2f0c8490", - "size": 173887, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.2-nightly-20140822/lwjgl_util-2.9.2-nightly-20140822.jar" - } - }, - "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.2-nightly-20140822", - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-linux.jar", - "sha1": "d898a33b5d0a6ef3fed3a4ead506566dce6720a5", - "size": 578539, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-osx.jar", - "sha1": "79f5ce2fea02e77fe47a3c745219167a542121d7", - "size": 468116, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-windows.jar", - "sha1": "78b2a55ce4dc29c6b3ec4df8ca165eba05f9b341", - "size": 613680, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.2-nightly-20140822", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "classifiers": { - "natives-linux": { - "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar", - "sha1": "7ff832a6eb9ab6a767f1ade2b548092d0fa64795", - "size": 10362, - "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar" - }, - "natives-osx": { - "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar", - "sha1": "53f9c919f34d2ca9de8c51fc4e1e8282029a9232", - "size": 12186, - "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar" - }, - "natives-windows": { - "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-windows.jar", - "sha1": "385ee093e01f587f30ee1c8a2ee7d408fd732e16", - "size": 155179, - "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "net.java.jinput:jinput-platform:2.0.5", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - } - } - ], - "mainClass": "net.minecraft.client.main.Main", - "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userType ${user_type} --versionType ${version_type}", - "minimumLauncherVersion": 18, - "releaseTime": "2016-02-29T13:49:54+00:00", - "time": "2016-03-01T13:14:53+00:00", - "type": "release" -} diff --git a/api/logic/minecraft/testdata/codecwav-20101023.jar b/api/logic/minecraft/testdata/codecwav-20101023.jar deleted file mode 100644 index f5236083..00000000 --- a/api/logic/minecraft/testdata/codecwav-20101023.jar +++ /dev/null @@ -1 +0,0 @@ -dummy test file. diff --git a/api/logic/minecraft/testdata/lib-native-arch.json b/api/logic/minecraft/testdata/lib-native-arch.json deleted file mode 100644 index 501826ae..00000000 --- a/api/logic/minecraft/testdata/lib-native-arch.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "downloads": { - "classifiers": { - "natives-osx": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar", - "sha1": "62503ee712766cf77f97252e5902786fd834b8c5", - "size": 418331, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar" - }, - "natives-windows-32": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", - "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23", - "size": 386792, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar" - }, - "natives-windows-64": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar", - "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9", - "size": 463390, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "tv.twitch:twitch-platform:5.16", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows-${arch}" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "linux" - } - } - ] -} diff --git a/api/logic/minecraft/testdata/lib-native.json b/api/logic/minecraft/testdata/lib-native.json deleted file mode 100644 index 5b9f3b55..00000000 --- a/api/logic/minecraft/testdata/lib-native.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", - "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", - "size": 22, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" - }, - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", - "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", - "size": 578680, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", - "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", - "size": 426822, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", - "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", - "size": 613748, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] -} diff --git a/api/logic/minecraft/testdata/lib-simple.json b/api/logic/minecraft/testdata/lib-simple.json deleted file mode 100644 index 90bbff07..00000000 --- a/api/logic/minecraft/testdata/lib-simple.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "downloads": { - "artifact": { - "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar", - "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da", - "size": 5618, - "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar" - } - }, - "name": "com.paulscode:codecwav:20101023" -} diff --git a/api/logic/minecraft/testdata/testname-testversion-linux-32.jar b/api/logic/minecraft/testdata/testname-testversion-linux-32.jar deleted file mode 100644 index f5236083..00000000 --- a/api/logic/minecraft/testdata/testname-testversion-linux-32.jar +++ /dev/null @@ -1 +0,0 @@ -dummy test file. diff --git a/api/logic/minecraft/update/AssetUpdateTask.cpp b/api/logic/minecraft/update/AssetUpdateTask.cpp deleted file mode 100644 index e26ab4ef..00000000 --- a/api/logic/minecraft/update/AssetUpdateTask.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "Env.h" -#include "AssetUpdateTask.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "net/ChecksumValidator.h" -#include "minecraft/AssetsUtils.h" - -AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst) -{ - m_inst = inst; -} - -AssetUpdateTask::~AssetUpdateTask() -{ -} - -void AssetUpdateTask::executeTask() -{ - setStatus(tr("Updating assets index...")); - auto components = m_inst->getPackProfile(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); - QUrl indexUrl = assets->url; - QString localPath = assets->id + ".json"; - auto job = new NetJob(tr("Asset index for %1").arg(m_inst->name())); - - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", localPath); - entry->setStale(true); - auto hexSha1 = assets->sha1.toLatin1(); - qDebug() << "Asset index SHA1:" << hexSha1; - auto dl = Net::Download::makeCached(indexUrl, entry); - auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - job->addNetAction(dl); - - downloadJob.reset(job); - - connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished); - connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); - connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); - - qDebug() << m_inst->name() << ": Starting asset index download"; - downloadJob->start(); -} - -bool AssetUpdateTask::canAbort() const -{ - return true; -} - -void AssetUpdateTask::assetIndexFinished() -{ - AssetsIndex index; - qDebug() << m_inst->name() << ": Finished asset index download"; - - auto components = m_inst->getPackProfile(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); - - QString asset_fname = "assets/indexes/" + assets->id + ".json"; - // FIXME: this looks like a job for a generic validator based on json schema? - if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, index)) - { - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); - metacache->evictEntry(entry); - emitFailed(tr("Failed to read the assets index!")); - } - - auto job = index.getDownloadJob(); - if(job) - { - setStatus(tr("Getting the assets files from Mojang...")); - downloadJob = job; - connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); - connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); - connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); - downloadJob->start(); - return; - } - emitSucceeded(); -} - -void AssetUpdateTask::assetIndexFailed(QString reason) -{ - qDebug() << m_inst->name() << ": Failed asset index download"; - emitFailed(tr("Failed to download the assets index:\n%1").arg(reason)); -} - -void AssetUpdateTask::assetsFailed(QString reason) -{ - emitFailed(tr("Failed to download assets:\n%1").arg(reason)); -} - -bool AssetUpdateTask::abort() -{ - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted AssetUpdateTask"; - } - return true; -} diff --git a/api/logic/minecraft/update/AssetUpdateTask.h b/api/logic/minecraft/update/AssetUpdateTask.h deleted file mode 100644 index fdfa8f1c..00000000 --- a/api/logic/minecraft/update/AssetUpdateTask.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -class MinecraftInstance; - -class AssetUpdateTask : public Task -{ - Q_OBJECT -public: - AssetUpdateTask(MinecraftInstance * inst); - virtual ~AssetUpdateTask(); - - void executeTask() override; - - bool canAbort() const override; - -private slots: - void assetIndexFinished(); - void assetIndexFailed(QString reason); - void assetsFailed(QString reason); - -public slots: - bool abort() override; - -private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; -}; diff --git a/api/logic/minecraft/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp deleted file mode 100644 index a05a7c2a..00000000 --- a/api/logic/minecraft/update/FMLLibrariesTask.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "Env.h" -#include <FileSystem.h> -#include <minecraft/VersionFilterData.h> -#include "FMLLibrariesTask.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "BuildConfig.h" - -FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst) -{ - m_inst = inst; -} -void FMLLibrariesTask::executeTask() -{ - // Get the mod list - MinecraftInstance *inst = (MinecraftInstance *)m_inst; - auto components = inst->getPackProfile(); - auto profile = components->getProfile(); - - if (!profile->hasTrait("legacyFML")) - { - emitSucceeded(); - return; - } - - QString version = components->getComponentVersion("net.minecraft"); - auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; - if (!fmlLibsMapping.contains(version)) - { - emitSucceeded(); - return; - } - - auto &libList = fmlLibsMapping[version]; - - // determine if we need some libs for FML or forge - setStatus(tr("Checking for FML libraries...")); - if(!components->getComponent("net.minecraftforge")) - { - emitSucceeded(); - 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()) - { - emitSucceeded(); - 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 = BuildConfig.FMLLIBS_BASE_URL + lib.filename; - dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); - } - - connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); - connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); - connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); - downloadJob.reset(dljob); - downloadJob->start(); -} - -bool FMLLibrariesTask::canAbort() const -{ - return true; -} - -void FMLLibrariesTask::fmllibsFinished() -{ - downloadJob.reset(); - if (!fmlLibsToProcess.isEmpty()) - { - setStatus(tr("Copying FML libraries into the instance...")); - MinecraftInstance *inst = (MinecraftInstance *)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()); - } - emitSucceeded(); -} -void FMLLibrariesTask::fmllibsFailed(QString reason) -{ - QStringList failed = downloadJob->getFailedFiles(); - QString failed_all = failed.join("\n"); - emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); -} - -bool FMLLibrariesTask::abort() -{ - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted FMLLibrariesTask"; - } - return true; -} diff --git a/api/logic/minecraft/update/FMLLibrariesTask.h b/api/logic/minecraft/update/FMLLibrariesTask.h deleted file mode 100644 index a1e70ed4..00000000 --- a/api/logic/minecraft/update/FMLLibrariesTask.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -#include "minecraft/VersionFilterData.h" - -class MinecraftInstance; - -class FMLLibrariesTask : public Task -{ - Q_OBJECT -public: - FMLLibrariesTask(MinecraftInstance * inst); - virtual ~FMLLibrariesTask() {}; - - void executeTask() override; - - bool canAbort() const override; - -private slots: - void fmllibsFinished(); - void fmllibsFailed(QString reason); - -public slots: - bool abort() override; - -private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; - QList<FMLlib> fmlLibsToProcess; -}; - diff --git a/api/logic/minecraft/update/FoldersTask.cpp b/api/logic/minecraft/update/FoldersTask.cpp deleted file mode 100644 index e2b1bb48..00000000 --- a/api/logic/minecraft/update/FoldersTask.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "FoldersTask.h" -#include "minecraft/MinecraftInstance.h" -#include <QDir> - -FoldersTask::FoldersTask(MinecraftInstance * inst) - :Task() -{ - m_inst = inst; -} - -void FoldersTask::executeTask() -{ - // Make directories - QDir mcDir(m_inst->gameRoot()); - if (!mcDir.exists() && !mcDir.mkpath(".")) - { - emitFailed(tr("Failed to create folder for minecraft binaries.")); - return; - } - emitSucceeded(); -} diff --git a/api/logic/minecraft/update/FoldersTask.h b/api/logic/minecraft/update/FoldersTask.h deleted file mode 100644 index f6ed5e6d..00000000 --- a/api/logic/minecraft/update/FoldersTask.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "tasks/Task.h" - -class MinecraftInstance; -class FoldersTask : public Task -{ - Q_OBJECT -public: - FoldersTask(MinecraftInstance * inst); - virtual ~FoldersTask() {}; - - void executeTask() override; -private: - MinecraftInstance *m_inst; -}; - diff --git a/api/logic/minecraft/update/LibrariesTask.cpp b/api/logic/minecraft/update/LibrariesTask.cpp deleted file mode 100644 index 7f66a651..00000000 --- a/api/logic/minecraft/update/LibrariesTask.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "Env.h" -#include "LibrariesTask.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" - -LibrariesTask::LibrariesTask(MinecraftInstance * inst) -{ - m_inst = inst; -} - -void LibrariesTask::executeTask() -{ - setStatus(tr("Getting the library files from Mojang...")); - qDebug() << m_inst->name() << ": downloading libraries"; - MinecraftInstance *inst = (MinecraftInstance *)m_inst; - - // Build a list of URLs that will need to be downloaded. - auto components = inst->getPackProfile(); - auto profile = components->getProfile(); - - auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); - downloadJob.reset(job); - - auto metacache = ENV.metacache(); - - auto processArtifactPool = [&](const QList<LibraryPtr> & pool, QStringList & errors, const QString & localPath) - { - for (auto lib : pool) - { - if(!lib) - { - emitFailed(tr("Null jar is specified in the metadata, aborting.")); - return false; - } - auto dls = lib->getDownloads(currentSystem, metacache.get(), errors, localPath); - for(auto dl : dls) - { - downloadJob->addNetAction(dl); - } - } - return true; - }; - - QStringList failedLocalLibraries; - QList<LibraryPtr> libArtifactPool; - libArtifactPool.append(profile->getLibraries()); - libArtifactPool.append(profile->getNativeLibraries()); - libArtifactPool.append(profile->getMavenFiles()); - libArtifactPool.append(profile->getMainJar()); - processArtifactPool(libArtifactPool, failedLocalLibraries, inst->getLocalLibraryPath()); - - QStringList failedLocalJarMods; - processArtifactPool(profile->getJarMods(), failedLocalJarMods, inst->jarModsDir()); - - if (!failedLocalJarMods.empty() || !failedLocalLibraries.empty()) - { - downloadJob.reset(); - QString failed_all = (failedLocalLibraries + failedLocalJarMods).join("\n"); - emitFailed(tr("Some artifacts marked as 'local' are missing their files:\n%1\n\nYou need to either add the files, or removed the packages that require them.\nYou'll have to correct this problem manually.").arg(failed_all)); - return; - } - - connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded); - connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); - connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); - downloadJob->start(); -} - -bool LibrariesTask::canAbort() const -{ - return true; -} - -void LibrariesTask::jarlibFailed(QString reason) -{ - emitFailed(tr("Game update failed: it was impossible to fetch the required libraries.\nReason:\n%1").arg(reason)); -} - -bool LibrariesTask::abort() -{ - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted LibrariesTask"; - } - return true; -} diff --git a/api/logic/minecraft/update/LibrariesTask.h b/api/logic/minecraft/update/LibrariesTask.h deleted file mode 100644 index 49f76932..00000000 --- a/api/logic/minecraft/update/LibrariesTask.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -class MinecraftInstance; - -class LibrariesTask : public Task -{ - Q_OBJECT -public: - LibrariesTask(MinecraftInstance * inst); - virtual ~LibrariesTask() {}; - - void executeTask() override; - - bool canAbort() const override; - -private slots: - void jarlibFailed(QString reason); - -public slots: - bool abort() override; - -private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; -}; |