From b6d455a02bd338e9dc0faa09d4d8177ecd8d569a Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 10 Apr 2016 15:53:05 +0200 Subject: NOISSUE reorganize and document libraries --- api/logic/minecraft/AssetsUtils.cpp | 230 ++++++++ api/logic/minecraft/AssetsUtils.h | 48 ++ api/logic/minecraft/GradleSpecifier.h | 129 +++++ api/logic/minecraft/JarMod.h | 12 + api/logic/minecraft/Library.cpp | 239 ++++++++ api/logic/minecraft/Library.h | 184 ++++++ api/logic/minecraft/MinecraftInstance.cpp | 369 ++++++++++++ api/logic/minecraft/MinecraftInstance.h | 69 +++ api/logic/minecraft/MinecraftProfile.cpp | 610 ++++++++++++++++++++ api/logic/minecraft/MinecraftProfile.h | 200 +++++++ api/logic/minecraft/MinecraftVersion.cpp | 215 +++++++ api/logic/minecraft/MinecraftVersion.h | 119 ++++ api/logic/minecraft/MinecraftVersionList.cpp | 591 ++++++++++++++++++++ api/logic/minecraft/MinecraftVersionList.h | 72 +++ api/logic/minecraft/Mod.cpp | 377 +++++++++++++ api/logic/minecraft/Mod.h | 134 +++++ api/logic/minecraft/ModList.cpp | 616 +++++++++++++++++++++ api/logic/minecraft/ModList.h | 160 ++++++ api/logic/minecraft/MojangDownloadInfo.h | 71 +++ api/logic/minecraft/MojangVersionFormat.cpp | 381 +++++++++++++ api/logic/minecraft/MojangVersionFormat.h | 25 + api/logic/minecraft/OpSys.cpp | 42 ++ api/logic/minecraft/OpSys.h | 37 ++ api/logic/minecraft/ParseUtils.cpp | 34 ++ api/logic/minecraft/ParseUtils.h | 11 + api/logic/minecraft/ProfilePatch.h | 104 ++++ api/logic/minecraft/ProfileStrategy.h | 35 ++ api/logic/minecraft/ProfileUtils.cpp | 191 +++++++ api/logic/minecraft/ProfileUtils.h | 25 + api/logic/minecraft/Rule.cpp | 93 ++++ api/logic/minecraft/Rule.h | 101 ++++ api/logic/minecraft/VersionBuildError.h | 58 ++ api/logic/minecraft/VersionFile.cpp | 60 ++ api/logic/minecraft/VersionFile.h | 195 +++++++ api/logic/minecraft/VersionFilterData.cpp | 75 +++ api/logic/minecraft/VersionFilterData.h | 32 ++ api/logic/minecraft/World.cpp | 385 +++++++++++++ api/logic/minecraft/World.h | 83 +++ api/logic/minecraft/WorldList.cpp | 355 ++++++++++++ api/logic/minecraft/WorldList.h | 125 +++++ api/logic/minecraft/auth/AuthSession.cpp | 30 + api/logic/minecraft/auth/AuthSession.h | 51 ++ api/logic/minecraft/auth/MojangAccount.cpp | 278 ++++++++++ api/logic/minecraft/auth/MojangAccount.h | 173 ++++++ api/logic/minecraft/auth/MojangAccountList.cpp | 427 ++++++++++++++ api/logic/minecraft/auth/MojangAccountList.h | 201 +++++++ api/logic/minecraft/auth/YggdrasilTask.cpp | 255 +++++++++ api/logic/minecraft/auth/YggdrasilTask.h | 150 +++++ .../minecraft/auth/flows/AuthenticateTask.cpp | 202 +++++++ api/logic/minecraft/auth/flows/AuthenticateTask.h | 46 ++ api/logic/minecraft/auth/flows/RefreshTask.cpp | 144 +++++ api/logic/minecraft/auth/flows/RefreshTask.h | 44 ++ api/logic/minecraft/auth/flows/ValidateTask.cpp | 61 ++ api/logic/minecraft/auth/flows/ValidateTask.h | 47 ++ api/logic/minecraft/forge/ForgeInstaller.cpp | 458 +++++++++++++++ api/logic/minecraft/forge/ForgeInstaller.h | 52 ++ api/logic/minecraft/forge/ForgeVersion.cpp | 55 ++ api/logic/minecraft/forge/ForgeVersion.h | 42 ++ api/logic/minecraft/forge/ForgeVersionList.cpp | 450 +++++++++++++++ api/logic/minecraft/forge/ForgeVersionList.h | 90 +++ api/logic/minecraft/forge/ForgeXzDownload.cpp | 358 ++++++++++++ api/logic/minecraft/forge/ForgeXzDownload.h | 59 ++ api/logic/minecraft/forge/LegacyForge.cpp | 56 ++ api/logic/minecraft/forge/LegacyForge.h | 25 + api/logic/minecraft/ftb/FTBPlugin.cpp | 395 +++++++++++++ api/logic/minecraft/ftb/FTBPlugin.h | 13 + api/logic/minecraft/ftb/FTBProfileStrategy.cpp | 128 +++++ api/logic/minecraft/ftb/FTBProfileStrategy.h | 21 + api/logic/minecraft/ftb/FTBVersion.h | 32 ++ api/logic/minecraft/ftb/LegacyFTBInstance.cpp | 27 + api/logic/minecraft/ftb/LegacyFTBInstance.h | 17 + api/logic/minecraft/ftb/OneSixFTBInstance.cpp | 138 +++++ api/logic/minecraft/ftb/OneSixFTBInstance.h | 30 + api/logic/minecraft/legacy/LegacyInstance.cpp | 453 +++++++++++++++ api/logic/minecraft/legacy/LegacyInstance.h | 142 +++++ api/logic/minecraft/legacy/LegacyUpdate.cpp | 393 +++++++++++++ api/logic/minecraft/legacy/LegacyUpdate.h | 70 +++ api/logic/minecraft/legacy/LwjglVersionList.cpp | 189 +++++++ api/logic/minecraft/legacy/LwjglVersionList.h | 156 ++++++ .../minecraft/liteloader/LiteLoaderInstaller.cpp | 142 +++++ .../minecraft/liteloader/LiteLoaderInstaller.h | 39 ++ .../minecraft/liteloader/LiteLoaderVersionList.cpp | 276 +++++++++ .../minecraft/liteloader/LiteLoaderVersionList.h | 119 ++++ api/logic/minecraft/onesix/OneSixInstance.cpp | 597 ++++++++++++++++++++ api/logic/minecraft/onesix/OneSixInstance.h | 117 ++++ .../minecraft/onesix/OneSixProfileStrategy.cpp | 418 ++++++++++++++ api/logic/minecraft/onesix/OneSixProfileStrategy.h | 26 + api/logic/minecraft/onesix/OneSixUpdate.cpp | 342 ++++++++++++ api/logic/minecraft/onesix/OneSixUpdate.h | 67 +++ api/logic/minecraft/onesix/OneSixVersionFormat.cpp | 225 ++++++++ api/logic/minecraft/onesix/OneSixVersionFormat.h | 22 + 91 files changed, 15240 insertions(+) create mode 100644 api/logic/minecraft/AssetsUtils.cpp create mode 100644 api/logic/minecraft/AssetsUtils.h create mode 100644 api/logic/minecraft/GradleSpecifier.h create mode 100644 api/logic/minecraft/JarMod.h create mode 100644 api/logic/minecraft/Library.cpp create mode 100644 api/logic/minecraft/Library.h create mode 100644 api/logic/minecraft/MinecraftInstance.cpp create mode 100644 api/logic/minecraft/MinecraftInstance.h create mode 100644 api/logic/minecraft/MinecraftProfile.cpp create mode 100644 api/logic/minecraft/MinecraftProfile.h create mode 100644 api/logic/minecraft/MinecraftVersion.cpp create mode 100644 api/logic/minecraft/MinecraftVersion.h create mode 100644 api/logic/minecraft/MinecraftVersionList.cpp create mode 100644 api/logic/minecraft/MinecraftVersionList.h create mode 100644 api/logic/minecraft/Mod.cpp create mode 100644 api/logic/minecraft/Mod.h create mode 100644 api/logic/minecraft/ModList.cpp create mode 100644 api/logic/minecraft/ModList.h create mode 100644 api/logic/minecraft/MojangDownloadInfo.h create mode 100644 api/logic/minecraft/MojangVersionFormat.cpp create mode 100644 api/logic/minecraft/MojangVersionFormat.h create mode 100644 api/logic/minecraft/OpSys.cpp create mode 100644 api/logic/minecraft/OpSys.h create mode 100644 api/logic/minecraft/ParseUtils.cpp create mode 100644 api/logic/minecraft/ParseUtils.h create mode 100644 api/logic/minecraft/ProfilePatch.h create mode 100644 api/logic/minecraft/ProfileStrategy.h create mode 100644 api/logic/minecraft/ProfileUtils.cpp create mode 100644 api/logic/minecraft/ProfileUtils.h create mode 100644 api/logic/minecraft/Rule.cpp create mode 100644 api/logic/minecraft/Rule.h create mode 100644 api/logic/minecraft/VersionBuildError.h create mode 100644 api/logic/minecraft/VersionFile.cpp create mode 100644 api/logic/minecraft/VersionFile.h create mode 100644 api/logic/minecraft/VersionFilterData.cpp create mode 100644 api/logic/minecraft/VersionFilterData.h create mode 100644 api/logic/minecraft/World.cpp create mode 100644 api/logic/minecraft/World.h create mode 100644 api/logic/minecraft/WorldList.cpp create mode 100644 api/logic/minecraft/WorldList.h create mode 100644 api/logic/minecraft/auth/AuthSession.cpp create mode 100644 api/logic/minecraft/auth/AuthSession.h create mode 100644 api/logic/minecraft/auth/MojangAccount.cpp create mode 100644 api/logic/minecraft/auth/MojangAccount.h create mode 100644 api/logic/minecraft/auth/MojangAccountList.cpp create mode 100644 api/logic/minecraft/auth/MojangAccountList.h create mode 100644 api/logic/minecraft/auth/YggdrasilTask.cpp create mode 100644 api/logic/minecraft/auth/YggdrasilTask.h create mode 100644 api/logic/minecraft/auth/flows/AuthenticateTask.cpp create mode 100644 api/logic/minecraft/auth/flows/AuthenticateTask.h create mode 100644 api/logic/minecraft/auth/flows/RefreshTask.cpp create mode 100644 api/logic/minecraft/auth/flows/RefreshTask.h create mode 100644 api/logic/minecraft/auth/flows/ValidateTask.cpp create mode 100644 api/logic/minecraft/auth/flows/ValidateTask.h create mode 100644 api/logic/minecraft/forge/ForgeInstaller.cpp create mode 100644 api/logic/minecraft/forge/ForgeInstaller.h create mode 100644 api/logic/minecraft/forge/ForgeVersion.cpp create mode 100644 api/logic/minecraft/forge/ForgeVersion.h create mode 100644 api/logic/minecraft/forge/ForgeVersionList.cpp create mode 100644 api/logic/minecraft/forge/ForgeVersionList.h create mode 100644 api/logic/minecraft/forge/ForgeXzDownload.cpp create mode 100644 api/logic/minecraft/forge/ForgeXzDownload.h create mode 100644 api/logic/minecraft/forge/LegacyForge.cpp create mode 100644 api/logic/minecraft/forge/LegacyForge.h create mode 100644 api/logic/minecraft/ftb/FTBPlugin.cpp create mode 100644 api/logic/minecraft/ftb/FTBPlugin.h create mode 100644 api/logic/minecraft/ftb/FTBProfileStrategy.cpp create mode 100644 api/logic/minecraft/ftb/FTBProfileStrategy.h create mode 100644 api/logic/minecraft/ftb/FTBVersion.h create mode 100644 api/logic/minecraft/ftb/LegacyFTBInstance.cpp create mode 100644 api/logic/minecraft/ftb/LegacyFTBInstance.h create mode 100644 api/logic/minecraft/ftb/OneSixFTBInstance.cpp create mode 100644 api/logic/minecraft/ftb/OneSixFTBInstance.h create mode 100644 api/logic/minecraft/legacy/LegacyInstance.cpp create mode 100644 api/logic/minecraft/legacy/LegacyInstance.h create mode 100644 api/logic/minecraft/legacy/LegacyUpdate.cpp create mode 100644 api/logic/minecraft/legacy/LegacyUpdate.h create mode 100644 api/logic/minecraft/legacy/LwjglVersionList.cpp create mode 100644 api/logic/minecraft/legacy/LwjglVersionList.h create mode 100644 api/logic/minecraft/liteloader/LiteLoaderInstaller.cpp create mode 100644 api/logic/minecraft/liteloader/LiteLoaderInstaller.h create mode 100644 api/logic/minecraft/liteloader/LiteLoaderVersionList.cpp create mode 100644 api/logic/minecraft/liteloader/LiteLoaderVersionList.h create mode 100644 api/logic/minecraft/onesix/OneSixInstance.cpp create mode 100644 api/logic/minecraft/onesix/OneSixInstance.h create mode 100644 api/logic/minecraft/onesix/OneSixProfileStrategy.cpp create mode 100644 api/logic/minecraft/onesix/OneSixProfileStrategy.h create mode 100644 api/logic/minecraft/onesix/OneSixUpdate.cpp create mode 100644 api/logic/minecraft/onesix/OneSixUpdate.h create mode 100644 api/logic/minecraft/onesix/OneSixVersionFormat.cpp create mode 100644 api/logic/minecraft/onesix/OneSixVersionFormat.h (limited to 'api/logic/minecraft') diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp new file mode 100644 index 00000000..7a525abe --- /dev/null +++ b/api/logic/minecraft/AssetsUtils.cpp @@ -0,0 +1,230 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AssetsUtils.h" +#include "FileSystem.h" +#include "net/MD5EtagDownload.h" + +namespace AssetsUtils +{ + +/* + * Returns true on success, with index populated + * index is undefined otherwise + */ +bool loadAssetsIndexJson(QString assetsId, 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 objects = root.value("objects"); + QVariantMap map = objects.toVariant().toMap(); + + for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) + { + // qDebug() << iter.key(); + + QVariant variant = iter.value(); + QVariantMap nested_objects = variant.toMap(); + + AssetObject object; + + for (QVariantMap::const_iterator nested_iter = nested_objects.begin(); + nested_iter != nested_objects.end(); ++nested_iter) + { + // qDebug() << nested_iter.key() << nested_iter.value().toString(); + QString key = nested_iter.key(); + QVariant value = nested_iter.value(); + + if (key == "hash") + { + object.hash = value.toString(); + } + else if (key == "size") + { + object.size = value.toDouble(); + } + } + + index->objects.insert(iter.key(), object); + } + + return true; +} + +QDir reconstructAssets(QString assetsId) +{ + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(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 virtualRoot; + } + + qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() + << objectDir.path() << virtualDir.path() << virtualRoot.path(); + + AssetsIndex index; + bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index); + + if (loadAssetsIndex && index.isVirtual) + { + qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path(); + + for (QString map : index.objects.keys()) + { + AssetObject asset_object = index.objects.value(map); + QString target_path = FS::PathCombine(virtualRoot.path(), 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; + if (!target.exists()) + { + QFileInfo info(target_path); + QDir target_dir = info.dir(); + // qDebug() << target_dir; + if (!target_dir.exists()) + QDir("").mkpath(target_dir.path()); + + bool couldCopy = original.copy(target_path); + qDebug() << " Copying" << original_path << "to" << target_path + << QString::number(couldCopy); // << original.errorString(); + } + } + + // TODO: Write last used time to virtualRoot/.lastused + } + + return virtualRoot; +} + +} + +NetActionPtr AssetObject::getDownloadAction() +{ + QFileInfo objectFile(getLocalPath()); + if ((!objectFile.isFile()) || (objectFile.size() != size)) + { + auto objectDL = MD5EtagDownload::make(getUrl(), objectFile.filePath()); + objectDL->m_total_progress = size; + return objectDL; + } + return nullptr; +} + +QString AssetObject::getLocalPath() +{ + return "assets/objects/" + getRelPath(); +} + +QUrl AssetObject::getUrl() +{ + return QUrl("http://resources.download.minecraft.net/" + 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 new file mode 100644 index 00000000..90251c2d --- /dev/null +++ b/api/logic/minecraft/AssetsUtils.h @@ -0,0 +1,48 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include "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 objects; + bool isVirtual = false; +}; + +namespace AssetsUtils +{ +bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index); +/// Reconstruct a virtual assets folder for the given assets ID and return the folder +QDir reconstructAssets(QString assetsId); +} diff --git a/api/logic/minecraft/GradleSpecifier.h b/api/logic/minecraft/GradleSpecifier.h new file mode 100644 index 00000000..18308537 --- /dev/null +++ b/api/logic/minecraft/GradleSpecifier.h @@ -0,0 +1,129 @@ +#pragma once + +#include +#include +#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 + DEBUG 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar" + DEBUG 1 "org.gradle.test.classifiers" + DEBUG 2 "service" + DEBUG 3 "1.0" + DEBUG 4 ":jdk15" + DEBUG 5 "jdk15" + DEBUG 6 "@jar" + DEBUG 7 "jar" + */ + QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(:([^:@]+))?" "(@([^:@]+))?"); + m_valid = matcher.exactMatch(value); + auto elements = matcher.capturedTexts(); + m_groupId = elements[1]; + m_artifactId = elements[2]; + m_version = elements[3]; + m_classifier = elements[5]; + if(!elements[7].isEmpty()) + { + m_extension = elements[7]; + } + return *this; + } + operator QString() const + { + if(!m_valid) + return "INVALID"; + 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 toPath() const + { + if(!m_valid) + return "INVALID"; + QString path = m_groupId; + path.replace('.', '/'); + path += '/' + m_artifactId + '/' + m_version + '/' + m_artifactId + '-' + m_version; + if(!m_classifier.isEmpty()) + { + path += "-" + m_classifier; + } + path += "." + m_extension; + 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_groupId; + QString m_artifactId; + QString m_version; + QString m_classifier; + DefaultVariable m_extension = DefaultVariable("jar"); + bool m_valid = false; +}; diff --git a/api/logic/minecraft/JarMod.h b/api/logic/minecraft/JarMod.h new file mode 100644 index 00000000..42d05da9 --- /dev/null +++ b/api/logic/minecraft/JarMod.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include +#include +class Jarmod; +typedef std::shared_ptr JarmodPtr; +class Jarmod +{ +public: /* data */ + QString name; + QString originalName; +}; diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp new file mode 100644 index 00000000..922db84e --- /dev/null +++ b/api/logic/minecraft/Library.cpp @@ -0,0 +1,239 @@ +#include "Library.h" +#include +#include +#include +#include + +void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32, QStringList& native64) const +{ + auto actualPath = [&](QString relPath) + { + QFileInfo out(FS::PathCombine(storagePrefix(), relPath)); + return out.absoluteFilePath(); + }; + if(m_mojangDownloads) + { + if(m_mojangDownloads->artifact) + { + auto artifact = m_mojangDownloads->artifact; + jar += actualPath(artifact->path); + } + if(!isNative()) + return; + 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) + native32 += actualPath(nat32info->path); + auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); + if(nat64info) + native64 += actualPath(nat64info->path); + } + else + { + native += actualPath(m_mojangDownloads->getDownloadInfo(nativeClassifier)->path); + } + } + } + else + { + 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 Library::getDownloads(OpSys system, HttpMetaCache * cache, QStringList &failedFiles) const +{ + QList out; + bool isLocal = (hint() == "local"); + bool isForge = (hint() == "forge-pack-xz"); + + auto add_download = [&](QString storage, QString dl) + { + auto entry = cache->resolveEntry("libraries", storage); + if (!entry->isStale()) + return true; + if(isLocal) + { + QFileInfo fileinfo(entry->getFullPath()); + if(!fileinfo.exists()) + { + failedFiles.append(entry->getFullPath()); + return false; + } + return true; + } + if (isForge) + { + out.append(ForgeXzDownload::make(storage, entry)); + } + else + { + out.append(CacheDownload::make(dl, entry)); + } + return true; + }; + + if(m_mojangDownloads) + { + if(m_mojangDownloads->artifact) + { + auto artifact = m_mojangDownloads->artifact; + add_download(artifact->path, artifact->url); + } + 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) + add_download(nat32info->path, nat32info->url); + auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); + if(nat64info) + add_download(nat64info->path, nat64info->url); + } + else + { + auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); + if(info) + { + add_download(info->path, info->url); + } + } + } + } + else + { + QString raw_storage = storageSuffix(system); + auto raw_dl = [&](){ + if (!m_absoluteURL.isEmpty()) + { + return m_absoluteURL; + } + + if (m_repositoryURL.isEmpty()) + { + return QString("https://" + URLConstants::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")); + cooked_storage = raw_storage; + cooked_dl = raw_dl; + add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64")); + } + else + { + add_download(raw_storage, raw_dl); + } + } + 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; +} + +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::storageSuffix(OpSys system) const +{ + // non-native? use only the gradle specifier + if (!isNative()) + { + return m_name.toPath(); + } + + // 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(); +} diff --git a/api/logic/minecraft/Library.h b/api/logic/minecraft/Library.h new file mode 100644 index 00000000..fdce93f3 --- /dev/null +++ b/api/logic/minecraft/Library.h @@ -0,0 +1,184 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Rule.h" +#include "minecraft/OpSys.h" +#include "GradleSpecifier.h" +#include "net/URLConstants.h" +#include "MojangDownloadInfo.h" + +#include "multimc_logic_export.h" + +class Library; + +typedef std::shared_ptr 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(); + 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; + 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; + + void setAbsoluteUrl(const QString &absolute_url) + { + m_absoluteURL = absolute_url; + } + + void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) + { + m_mojangDownloads = info; + } + + void setHint(const QString &hint) + { + m_hint = hint; + } + + /// Set the load rules + void setRules(QList> rules) + { + m_rules = rules; + } + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive() const; + + // Get a list of downloads for this library + QList getDownloads(OpSys system, class HttpMetaCache * cache, QStringList &failedFiles) 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 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-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 m_nativeClassifiers; + + /// true if the library had a rules section (even empty) + bool applyRules = false; + + /// rules associated with the library + QList> m_rules; + + /// MOJANG: container with Mojang style download info + MojangLibraryDownloadInfo::Ptr m_mojangDownloads; +}; diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp new file mode 100644 index 00000000..405ccd26 --- /dev/null +++ b/api/logic/minecraft/MinecraftInstance.cpp @@ -0,0 +1,369 @@ +#include "MinecraftInstance.h" +#include +#include "settings/SettingsObject.h" +#include "Env.h" +#include "minecraft/MinecraftVersionList.h" +#include +#include +#include +#include +#include + +#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 a, std::shared_ptr 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 m_a; + std::shared_ptr 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("JavaOrLocationOverride", javaOverride, locationOverride); + auto javaOrArgs = std::make_shared("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); + + // 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); +} + +QString MinecraftInstance::minecraftRoot() const +{ + QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); + + if (dotMCDir.exists() && !mcDir.exists()) + return dotMCDir.filePath(); + else + return mcDir.filePath(); +} + +std::shared_ptr< BaseVersionList > MinecraftInstance::versionList() const +{ + return ENV.getVersionList("net.minecraft"); +} + +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 + + // 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 + + args << QString("-Xms%1m").arg(settings()->get("MinMemAlloc").toInt()); + args << QString("-Xmx%1m").arg(settings()->get("MaxMemAlloc").toInt()); + + // No PermGen in newer java. + JavaVersion javaVersion(settings()->get("JavaVersion").toString()); + if(javaVersion.requiresPermGen()) + { + auto permgen = settings()->get("PermGen").toInt(); + if (permgen != 64) + { + args << QString("-XX:PermSize=%1m").arg(permgen); + } + } + + args << "-Duser.language=en"; + args << "-jar" << FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "NewLaunch.jar"); + + return args; +} + +QMap MinecraftInstance::getVariables() const +{ + QMap out; + out.insert("INST_NAME", name()); + out.insert("INST_ID", id()); + out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); + out.insert("INST_MC_DIR", QDir(minecraftRoot()).absolutePath()); + out.insert("INST_JAVA", settings()->get("JavaPath").toString()); + out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); + return out; +} + +static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH) +{ + QDir mmcBin(QCoreApplication::applicationDirPath()); + auto items = LD_LIBRARY_PATH.split(':'); + QStringList final; + for(auto & item: items) + { + QDir test(item); + if(test == mmcBin) + { + qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; + continue; + } + final.append(item); + } + return final.join(':'); +} + +QProcessEnvironment MinecraftInstance::createEnvironment() +{ + // prepare the process environment + QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); + QProcessEnvironment env; + + QStringList ignored = + { + "JAVA_ARGS", + "CLASSPATH", + "CONFIGPATH", + "JAVA_HOME", + "JRE_HOME", + "_JAVA_OPTIONS", + "JAVA_OPTIONS", + "JAVA_TOOL_OPTIONS" + }; + for(auto key: rawenv.keys()) + { + auto value = rawenv.value(key); + // filter out dangerous java crap + if(ignored.contains(key)) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // filter MultiMC-related things + if(key.startsWith("QT_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } +#ifdef Q_OS_LINUX + // Do not pass LD_* variables to java. They were intended for MultiMC + if(key.startsWith("LD_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // Strip IBus + // IBus is a Linux IME framework. For some reason, it breaks MC? + if (key == "XMODIFIERS" && value.contains(IBUS)) + { + QString save = value; + value.replace(IBUS, ""); + qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; + } + if(key == "GAME_PRELOAD") + { + env.insert("LD_PRELOAD", value); + continue; + } + if(key == "GAME_LIBRARY_PATH") + { + env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); + continue; + } +#endif + qDebug() << "Env: " << key << value; + env.insert(key, value); + } +#ifdef Q_OS_LINUX + // HACK: Workaround for QTBUG42500 + if(!env.contains("LD_LIBRARY_PATH")) + { + env.insert("LD_LIBRARY_PATH", ""); + } +#endif + + // export some infos + auto variables = getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + env.insert(it.key(), it.value()); + } + return env; +} + +QMap MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session) +{ + if(!session) + { + return QMap(); + } + auto & sessionRef = *session.get(); + QMap filter; + auto addToFilter = [&filter](QString key, QString value) + { + if(key.trimmed().size()) + { + filter[key] = value; + } + }; + if (sessionRef.session != "-") + { + addToFilter(sessionRef.session, tr("")); + } + addToFilter(sessionRef.access_token, tr("")); + addToFilter(sessionRef.client_token, tr("")); + addToFilter(sessionRef.uuid, tr("")); + addToFilter(sessionRef.player_name, tr("")); + + auto i = sessionRef.u.properties.begin(); + while (i != sessionRef.u.properties.end()) + { + addToFilter(i.value(), "<" + i.key().toUpper() + ">"); + ++i; + } + return filter; +} + +MessageLevel::Enum MinecraftInstance::guessLevel(const QString &line, MessageLevel::Enum level) +{ + QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); + 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(); + combined->add(std::make_shared(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); + combined->add(std::make_shared("crash-.*\\.txt")); + combined->add(std::make_shared("IDMap dump.*\\.txt$")); + combined->add(std::make_shared("ModLoader\\.txt(\\..*)?$")); + return combined; +} + +QString MinecraftInstance::getLogFileRoot() +{ + return minecraftRoot(); +} + +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 (flags() & VersionBrokenFlag) + { + traits.append(tr("broken")); + } + + QString description; + description.append(tr("Minecraft %1 (%2)").arg(intendedVersionId()).arg(typeName())); + if(totalTimePlayed() > 0) + { + description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); + } + /* + if(traits.size()) + { + description.append(QString(" (%1)").arg(traits.join(", "))); + } + */ + return description; +} + +#include "MinecraftInstance.moc" diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h new file mode 100644 index 00000000..cd3a8d90 --- /dev/null +++ b/api/logic/minecraft/MinecraftInstance.h @@ -0,0 +1,69 @@ +#pragma once +#include "BaseInstance.h" +#include "minecraft/Mod.h" +#include + +#include "multimc_logic_export.h" + +class ModList; +class WorldList; + +class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance +{ +public: + MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); + virtual ~MinecraftInstance() {}; + + /// Path to the instance's minecraft directory. + QString minecraftRoot() const; + + ////// Mod Lists ////// + virtual std::shared_ptr resourcePackList() const + { + return nullptr; + } + virtual std::shared_ptr texturePackList() const + { + return nullptr; + } + virtual std::shared_ptr worldList() const + { + return nullptr; + } + /// get all jar mods applicable to this instance's jar + virtual QList getJarMods() const + { + return QList(); + } + + /// get the launch script to be used with this + virtual QString createLaunchScript(AuthSessionPtr session) = 0; + + //FIXME: nuke? + virtual std::shared_ptr versionList() const override; + + /// get arguments passed to java + QStringList javaArguments() const; + + /// get variables for launch command variable substitution/environment + virtual QMap getVariables() const override; + + /// create an environment for launching processes + virtual QProcessEnvironment createEnvironment() override; + + /// guess log level from a line of minecraft log + virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; + + virtual IPathMatcher::Ptr getLogFileMatcher() override; + + virtual QString getLogFileRoot() override; + + virtual QString getStatusbarDescription() override; + +protected: + QMap createCensorFilterFromSession(AuthSessionPtr session); +private: + QString prettifyTimeDuration(int64_t duration); +}; + +typedef std::shared_ptr MinecraftInstancePtr; diff --git a/api/logic/minecraft/MinecraftProfile.cpp b/api/logic/minecraft/MinecraftProfile.cpp new file mode 100644 index 00000000..70d0cee4 --- /dev/null +++ b/api/logic/minecraft/MinecraftProfile.cpp @@ -0,0 +1,610 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "minecraft/MinecraftProfile.h" +#include "ProfileUtils.h" +#include "ProfileStrategy.h" +#include "Exception.h" + +MinecraftProfile::MinecraftProfile(ProfileStrategy *strategy) + : QAbstractListModel() +{ + setStrategy(strategy); + clear(); +} + +void MinecraftProfile::setStrategy(ProfileStrategy* strategy) +{ + Q_ASSERT(strategy != nullptr); + + if(m_strategy != nullptr) + { + delete m_strategy; + m_strategy = nullptr; + } + m_strategy = strategy; + m_strategy->profile = this; +} + +ProfileStrategy* MinecraftProfile::strategy() +{ + return m_strategy; +} + +void MinecraftProfile::reload() +{ + beginResetModel(); + m_strategy->load(); + reapplyPatches(); + endResetModel(); +} + +void MinecraftProfile::clear() +{ + m_minecraftVersion.clear(); + m_minecraftVersionType.clear(); + m_minecraftAssets.reset(); + m_minecraftArguments.clear(); + m_tweakers.clear(); + m_mainClass.clear(); + m_appletClass.clear(); + m_libraries.clear(); + m_traits.clear(); + m_jarMods.clear(); + mojangDownloads.clear(); + m_problemSeverity = ProblemSeverity::PROBLEM_NONE; +} + +void MinecraftProfile::clearPatches() +{ + beginResetModel(); + m_patches.clear(); + endResetModel(); +} + +void MinecraftProfile::appendPatch(ProfilePatchPtr patch) +{ + int index = m_patches.size(); + beginInsertRows(QModelIndex(), index, index); + m_patches.append(patch); + endInsertRows(); +} + +bool MinecraftProfile::remove(const int index) +{ + auto patch = versionPatch(index); + if (!patch->isRemovable()) + { + qDebug() << "Patch" << patch->getID() << "is non-removable"; + return false; + } + + if(!m_strategy->removePatch(patch)) + { + qCritical() << "Patch" << patch->getID() << "could not be removed"; + return false; + } + + beginRemoveRows(QModelIndex(), index, index); + m_patches.removeAt(index); + endRemoveRows(); + reapplyPatches(); + saveCurrentOrder(); + return true; +} + +bool MinecraftProfile::remove(const QString id) +{ + int i = 0; + for (auto patch : m_patches) + { + if (patch->getID() == id) + { + return remove(i); + } + i++; + } + return false; +} + +bool MinecraftProfile::customize(int index) +{ + auto patch = versionPatch(index); + if (!patch->isCustomizable()) + { + qDebug() << "Patch" << patch->getID() << "is not customizable"; + return false; + } + if(!m_strategy->customizePatch(patch)) + { + qCritical() << "Patch" << patch->getID() << "could not be customized"; + return false; + } + reapplyPatches(); + saveCurrentOrder(); + // FIXME: maybe later in unstable + // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); + return true; +} + +bool MinecraftProfile::revertToBase(int index) +{ + auto patch = versionPatch(index); + if (!patch->isRevertible()) + { + qDebug() << "Patch" << patch->getID() << "is not revertible"; + return false; + } + if(!m_strategy->revertPatch(patch)) + { + qCritical() << "Patch" << patch->getID() << "could not be reverted"; + return false; + } + reapplyPatches(); + saveCurrentOrder(); + // FIXME: maybe later in unstable + // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); + return true; +} + +ProfilePatchPtr MinecraftProfile::versionPatch(const QString &id) +{ + for (auto file : m_patches) + { + if (file->getID() == id) + { + return file; + } + } + return nullptr; +} + +ProfilePatchPtr MinecraftProfile::versionPatch(int index) +{ + if(index < 0 || index >= m_patches.size()) + return nullptr; + return m_patches[index]; +} + +bool MinecraftProfile::isVanilla() +{ + for(auto patchptr: m_patches) + { + if(patchptr->isCustom()) + return false; + } + return true; +} + +bool MinecraftProfile::revertToVanilla() +{ + // remove patches, if present + auto VersionPatchesCopy = m_patches; + for(auto & it: VersionPatchesCopy) + { + if (!it->isCustom()) + { + continue; + } + if(it->isRevertible() || it->isRemovable()) + { + if(!remove(it->getID())) + { + qWarning() << "Couldn't remove" << it->getID() << "from profile!"; + reapplyPatches(); + saveCurrentOrder(); + return false; + } + } + } + reapplyPatches(); + saveCurrentOrder(); + return true; +} + +QVariant MinecraftProfile::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= m_patches.size()) + return QVariant(); + + auto patch = m_patches.at(row); + + if (role == Qt::DisplayRole) + { + switch (column) + { + case 0: + return m_patches.at(row)->getName(); + case 1: + { + if(patch->isCustom()) + { + return QString("%1 (Custom)").arg(patch->getVersion()); + } + else + { + return patch->getVersion(); + } + } + default: + return QVariant(); + } + } + if(role == Qt::DecorationRole) + { + switch(column) + { + case 0: + { + auto severity = patch->getProblemSeverity(); + switch (severity) + { + case PROBLEM_WARNING: + return "warning"; + case PROBLEM_ERROR: + return "error"; + default: + return QVariant(); + } + } + default: + { + return QVariant(); + } + } + } + return QVariant(); +} +QVariant MinecraftProfile::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) + { + switch (section) + { + case 0: + return tr("Name"); + case 1: + return tr("Version"); + default: + return QVariant(); + } + } + } + return QVariant(); +} +Qt::ItemFlags MinecraftProfile::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +int MinecraftProfile::rowCount(const QModelIndex &parent) const +{ + return m_patches.size(); +} + +int MinecraftProfile::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +void MinecraftProfile::saveCurrentOrder() const +{ + ProfileUtils::PatchOrder order; + for(auto item: m_patches) + { + if(!item->isMoveable()) + continue; + order.append(item->getID()); + } + m_strategy->saveOrder(order); +} + +void MinecraftProfile::move(const int index, const MoveDirection direction) +{ + int theirIndex; + if (direction == MoveUp) + { + theirIndex = index - 1; + } + else + { + theirIndex = index + 1; + } + + if (index < 0 || index >= m_patches.size()) + return; + if (theirIndex >= rowCount()) + theirIndex = rowCount() - 1; + if (theirIndex == -1) + theirIndex = rowCount() - 1; + if (index == theirIndex) + return; + int togap = theirIndex > index ? theirIndex + 1 : theirIndex; + + auto from = versionPatch(index); + auto to = versionPatch(theirIndex); + + if (!from || !to || !to->isMoveable() || !from->isMoveable()) + { + return; + } + beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); + m_patches.swap(index, theirIndex); + endMoveRows(); + reapplyPatches(); + saveCurrentOrder(); +} +void MinecraftProfile::resetOrder() +{ + m_strategy->resetOrder(); + reload(); +} + +bool MinecraftProfile::reapplyPatches() +{ + try + { + clear(); + for(auto file: m_patches) + { + file->applyTo(this); + } + } + catch (Exception & error) + { + clear(); + qWarning() << "Couldn't apply profile patches because: " << error.cause(); + return false; + } + return true; +} + +static void applyString(const QString & from, QString & to) +{ + if(from.isEmpty()) + return; + to = from; +} + +void MinecraftProfile::applyMinecraftVersion(const QString& id) +{ + applyString(id, this->m_minecraftVersion); +} + +void MinecraftProfile::applyAppletClass(const QString& appletClass) +{ + applyString(appletClass, this->m_appletClass); +} + +void MinecraftProfile::applyMainClass(const QString& mainClass) +{ + applyString(mainClass, this->m_mainClass); +} + +void MinecraftProfile::applyMinecraftArguments(const QString& minecraftArguments) +{ + applyString(minecraftArguments, this->m_minecraftArguments); +} + +void MinecraftProfile::applyMinecraftVersionType(const QString& type) +{ + applyString(type, this->m_minecraftVersionType); +} + +void MinecraftProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets) +{ + if(assets) + { + m_minecraftAssets = assets; + } +} + +void MinecraftProfile::applyMojangDownload(const QString &key, MojangDownloadInfo::Ptr download) +{ + if(download) + { + mojangDownloads[key] = download; + } + else + { + mojangDownloads.remove(key); + } +} + +void MinecraftProfile::applyTraits(const QSet& traits) +{ + this->m_traits.unite(traits); +} + +void MinecraftProfile::applyTweakers(const QStringList& tweakers) +{ + // FIXME: check for dupes? + // FIXME: does order matter? + for (auto tweaker : tweakers) + { + this->m_tweakers += tweaker; + } +} + +void MinecraftProfile::applyJarMods(const QList& jarMods) +{ + this->m_jarMods.append(jarMods); +} + +static int findLibraryByName(QList haystack, const GradleSpecifier &needle) +{ + int retval = -1; + for (int i = 0; i < haystack.size(); ++i) + { + if (haystack.at(i)->rawName().matchName(needle)) + { + // only one is allowed. + if (retval != -1) + return -1; + retval = i; + } + } + return retval; +} + +void MinecraftProfile::applyLibrary(LibraryPtr library) +{ + if(!library->isActive()) + { + return; + } + // find the library by name. + const int index = findLibraryByName(m_libraries, library->rawName()); + // library not found? just add it. + if (index < 0) + { + m_libraries.append(Library::limitedCopy(library)); + return; + } + auto existingLibrary = m_libraries.at(index); + // if we are higher it means we should update + if (Version(library->version()) > Version(existingLibrary->version())) + { + auto libraryCopy = Library::limitedCopy(library); + m_libraries.replace(index, libraryCopy); + } +} + +void MinecraftProfile::applyProblemSeverity(ProblemSeverity severity) +{ + if (m_problemSeverity < severity) + { + m_problemSeverity = severity; + } +} + + +QString MinecraftProfile::getMinecraftVersion() const +{ + return m_minecraftVersion; +} + +QString MinecraftProfile::getAppletClass() const +{ + return m_appletClass; +} + +QString MinecraftProfile::getMainClass() const +{ + return m_mainClass; +} + +const QSet &MinecraftProfile::getTraits() const +{ + return m_traits; +} + +const QStringList & MinecraftProfile::getTweakers() const +{ + return m_tweakers; +} + +bool MinecraftProfile::hasTrait(const QString& trait) const +{ + return m_traits.contains(trait); +} + +ProblemSeverity MinecraftProfile::getProblemSeverity() const +{ + return m_problemSeverity; +} + +QString MinecraftProfile::getMinecraftVersionType() const +{ + return m_minecraftVersionType; +} + +std::shared_ptr MinecraftProfile::getMinecraftAssets() const +{ + if(!m_minecraftAssets) + { + return std::make_shared("legacy"); + } + return m_minecraftAssets; +} + +QString MinecraftProfile::getMinecraftArguments() const +{ + return m_minecraftArguments; +} + +const QList & MinecraftProfile::getJarMods() const +{ + return m_jarMods; +} + +const QList & MinecraftProfile::getLibraries() const +{ + return m_libraries; +} + +QString MinecraftProfile::getMainJarUrl() const +{ + auto iter = mojangDownloads.find("client"); + if(iter != mojangDownloads.end()) + { + // current + return iter.value()->url; + } + else + { + // legacy fallback + return URLConstants::getLegacyJarUrl(getMinecraftVersion()); + } +} + +void MinecraftProfile::installJarMods(QStringList selectedFiles) +{ + m_strategy->installJarMods(selectedFiles); +} + +/* + * TODO: get rid of this. Get rid of all order numbers. + */ +int MinecraftProfile::getFreeOrderNumber() +{ + int largest = 100; + // yes, I do realize this is dumb. The order thing itself is dumb. and to be removed next. + for(auto thing: m_patches) + { + int order = thing->getOrder(); + if(order > largest) + largest = order; + } + return largest + 1; +} diff --git a/api/logic/minecraft/MinecraftProfile.h b/api/logic/minecraft/MinecraftProfile.h new file mode 100644 index 00000000..ca9288ad --- /dev/null +++ b/api/logic/minecraft/MinecraftProfile.h @@ -0,0 +1,200 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +#include "Library.h" +#include "VersionFile.h" +#include "JarMod.h" +#include "MojangDownloadInfo.h" + +#include "multimc_logic_export.h" + +class ProfileStrategy; +class OneSixInstance; + + +class MULTIMC_LOGIC_EXPORT MinecraftProfile : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit MinecraftProfile(ProfileStrategy *strategy); + + void setStrategy(ProfileStrategy * strategy); + ProfileStrategy *strategy(); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex &parent) const override; + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + + /// is this version unchanged by the user? + bool isVanilla(); + + /// remove any customizations on top of whatever 'vanilla' means + bool revertToVanilla(); + + /// install more jar mods + void installJarMods(QStringList selectedFiles); + + /// DEPRECATED, remove ASAP + int getFreeOrderNumber(); + + enum MoveDirection { MoveUp, MoveDown }; + /// move patch file # up or down the list + void move(const int index, const MoveDirection direction); + + /// remove patch file # - including files/records + bool remove(const int index); + + /// remove patch file by id - including files/records + bool remove(const QString id); + + bool customize(int index); + + bool revertToBase(int index); + + void resetOrder(); + + /// reload all profile patches from storage, clear the profile and apply the patches + void reload(); + + /// clear the profile + void clear(); + + /// apply the patches. Catches all the errors and returns true/false for success/failure + bool reapplyPatches(); + +public: /* application of profile variables from patches */ + void applyMinecraftVersion(const QString& id); + void applyMainClass(const QString& mainClass); + void applyAppletClass(const QString& appletClass); + void applyMinecraftArguments(const QString& minecraftArguments); + void applyMinecraftVersionType(const QString& type); + void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); + void applyTraits(const QSet &traits); + void applyTweakers(const QStringList &tweakers); + void applyJarMods(const QList &jarMods); + void applyLibrary(LibraryPtr library); + void applyProblemSeverity(ProblemSeverity severity); + void applyMojangDownload(const QString & key, MojangDownloadInfo::Ptr download); + +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 & getTraits() const; + const QStringList & getTweakers() const; + const QList & getJarMods() const; + const QList & getLibraries() const; + QString getMainJarUrl() const; + bool hasTrait(const QString & trait) const; + ProblemSeverity getProblemSeverity() const; + +public: + /// get the profile patch by id + ProfilePatchPtr versionPatch(const QString &id); + + /// get the profile patch by index + ProfilePatchPtr versionPatch(int index); + + /// save the current patch order + void saveCurrentOrder() const; + + /// Remove all the patches + void clearPatches(); + + /// Add the patch object to the internal list of patches + void appendPatch(ProfilePatchPtr patch); + +private: /* data */ + /// the version of Minecraft - jar to use + QString m_minecraftVersion; + + /// Release type - "release" or "snapshot" + QString m_minecraftVersionType; + + /// Assets type - "legacy" or a version ID + MojangAssetIndexInfo::Ptr m_minecraftAssets; + + // Mojang: list of 'downloads' - client jar, server jar, windows server exe, maybe more. + QMap > mojangDownloads; + + /** + * arguments t