From 8c8eabf7ac1920b47792b26790f3646cb6693ec0 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Apr 2022 22:12:14 -0300 Subject: refactor: organize a little more the code in launcher/net/ This also reduces some code duplication by using some Task logic in NetAction. --- launcher/minecraft/AssetsUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp index 7290aeb4..281f730f 100644 --- a/launcher/minecraft/AssetsUtils.cpp +++ b/launcher/minecraft/AssetsUtils.cpp @@ -297,7 +297,7 @@ NetAction::Ptr AssetObject::getDownloadAction() auto rawHash = QByteArray::fromHex(hash.toLatin1()); objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); } - objectDL->m_total_progress = size; + objectDL->setProgress(objectDL->getProgress(), size); return objectDL; } return nullptr; -- cgit From dd2b324d8f7081f52decd90210ce11ef37625315 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 2 May 2022 14:33:21 -0300 Subject: chore: add license header to remaining files Also remove some unused imports --- launcher/minecraft/AssetsUtils.cpp | 40 ++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp index 281f730f..15062c2b 100644 --- a/launcher/minecraft/AssetsUtils.cpp +++ b/launcher/minecraft/AssetsUtils.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 + * 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 + * 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. + * 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 -- cgit From 77caaca50dab7ba8e455d641ac6b448052bc6799 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 19 May 2022 08:09:18 +0200 Subject: fix: only consider enabled mod loaders --- launcher/minecraft/PackProfile.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index d53f41e1..87d11c4c 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -36,6 +36,13 @@ #include "ComponentUpdateTask.h" #include "Application.h" +#include "modplatform/ModAPI.h" + +static const QMap modloaderMapping{ + {"net.minecraftforge", ModAPI::Forge}, + {"net.fabricmc.fabric-loader", ModAPI::Fabric}, + {"org.quiltmc.quilt-loader", ModAPI::Quilt} +}; PackProfile::PackProfile(MinecraftInstance * instance) : QAbstractListModel() @@ -973,17 +980,15 @@ void PackProfile::disableInteraction(bool disable) ModAPI::ModLoaderType PackProfile::getModLoader() { - if (!getComponentVersion("net.minecraftforge").isEmpty()) - { - return ModAPI::Forge; - } - else if (!getComponentVersion("net.fabricmc.fabric-loader").isEmpty()) - { - return ModAPI::Fabric; - } - else if (!getComponentVersion("org.quiltmc.quilt-loader").isEmpty()) + QMapIterator i(modloaderMapping); + + while (i.hasNext()) { - return ModAPI::Quilt; + i.next(); + Component* c = getComponent(i.key()); + if (c != nullptr && c->isEnabled()) { + return i.value(); + } } return ModAPI::Unspecified; } -- cgit From 943090db98dbbe969afed8a4fb59f4bbb43449cc Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 19 May 2022 08:40:28 +0200 Subject: refactor: allow tracking multiple mod loaders --- launcher/minecraft/PackProfile.cpp | 8 +++++--- launcher/minecraft/PackProfile.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 87d11c4c..125048f0 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -978,8 +978,10 @@ void PackProfile::disableInteraction(bool disable) } } -ModAPI::ModLoaderType PackProfile::getModLoader() +ModAPI::ModLoaderTypes PackProfile::getModLoaders() { + ModAPI::ModLoaderTypes result = ModAPI::Unspecified; + QMapIterator i(modloaderMapping); while (i.hasNext()) @@ -987,8 +989,8 @@ ModAPI::ModLoaderType PackProfile::getModLoader() i.next(); Component* c = getComponent(i.key()); if (c != nullptr && c->isEnabled()) { - return i.value(); + result |= i.value(); } } - return ModAPI::Unspecified; + return result; } diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index ab4cd5c8..918e7f7a 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -118,7 +118,7 @@ public: // todo(merged): is this the best approach void appendComponent(ComponentPtr component); - ModAPI::ModLoaderType getModLoader(); + ModAPI::ModLoaderTypes getModLoaders(); private: void scheduleSave(); -- cgit From 188c5aaa356323392be1100d74f62d70ab298695 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Tue, 17 May 2022 18:43:35 +0100 Subject: Launch: Match Vanilla launcher version string behaviour This removes a means of profiling users. --- launcher/minecraft/MinecraftInstance.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index e20dc24c..61326fac 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Jamie Mansfield * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -487,9 +488,8 @@ QStringList MinecraftInstance::processMinecraftArgs( } } - // blatant self-promotion. - token_mapping["profile_name"] = token_mapping["version_name"] = BuildConfig.LAUNCHER_NAME; - + token_mapping["profile_name"] = name(); + token_mapping["version_name"] = profile->getMinecraftVersion(); token_mapping["version_type"] = profile->getMinecraftVersionType(); QString absRootDir = QDir(gameRoot()).absolutePath(); -- cgit From 96f16069a93afa320de174e740bc6b915e9a1103 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Tue, 17 May 2022 21:03:15 +0100 Subject: Launch: Apply the Minecraft version correctly It was previously using a deprecated field. --- launcher/minecraft/VersionFile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/VersionFile.cpp b/launcher/minecraft/VersionFile.cpp index 9db30ba2..f242fbe7 100644 --- a/launcher/minecraft/VersionFile.cpp +++ b/launcher/minecraft/VersionFile.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Jamie Mansfield * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,7 +56,7 @@ void VersionFile::applyTo(LaunchProfile *profile) // Only real Minecraft can set those. Don't let anything override them. if (isMinecraftVersion(uid)) { - profile->applyMinecraftVersion(minecraftVersion); + profile->applyMinecraftVersion(version); profile->applyMinecraftVersionType(type); // HACK: ignore assets from other version files than Minecraft // workaround for stupid assets issue caused by amazon: -- cgit From b30b88716e67de93ea1c97d9dfd02a41af5428f3 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 13 Apr 2022 19:16:36 -0300 Subject: feat: add very early mod.toml packwiz support Also use it as a on-disk format for storing mod metadata. This will be used later on to make better mod managment. --- launcher/minecraft/mod/LocalModUpdateTask.cpp | 32 +++++++++++++++++++++++++++ launcher/minecraft/mod/LocalModUpdateTask.h | 26 ++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 launcher/minecraft/mod/LocalModUpdateTask.cpp create mode 100644 launcher/minecraft/mod/LocalModUpdateTask.h (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/mod/LocalModUpdateTask.cpp b/launcher/minecraft/mod/LocalModUpdateTask.cpp new file mode 100644 index 00000000..0f48217b --- /dev/null +++ b/launcher/minecraft/mod/LocalModUpdateTask.cpp @@ -0,0 +1,32 @@ +#include "LocalModUpdateTask.h" + +#include + +#include "FileSystem.h" +#include "modplatform/packwiz/Packwiz.h" + +LocalModUpdateTask::LocalModUpdateTask(QDir mods_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) + : m_mod(mod), m_mod_version(mod_version) +{ + // Ensure a '.index' folder exists in the mods folder, and create it if it does not + m_index_dir = { QString("%1/.index").arg(mods_dir.absolutePath()) }; + if (!FS::ensureFolderPathExists(m_index_dir.path())) { + emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); + } +} + +void LocalModUpdateTask::executeTask() +{ + setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); + + auto pw_mod = Packwiz::createModFormat(m_index_dir, m_mod, m_mod_version); + Packwiz::updateModIndex(m_index_dir, pw_mod); + + emitSucceeded(); +} + +bool LocalModUpdateTask::abort() +{ + emitAborted(); + return true; +} diff --git a/launcher/minecraft/mod/LocalModUpdateTask.h b/launcher/minecraft/mod/LocalModUpdateTask.h new file mode 100644 index 00000000..866089e9 --- /dev/null +++ b/launcher/minecraft/mod/LocalModUpdateTask.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "tasks/Task.h" +#include "modplatform/ModIndex.h" + +class LocalModUpdateTask : public Task { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + + explicit LocalModUpdateTask(QDir mods_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version); + + bool canAbort() const override { return true; } + bool abort() override; + + protected slots: + //! Entry point for tasks. + void executeTask() override; + + private: + QDir m_index_dir; + ModPlatform::IndexedPack& m_mod; + ModPlatform::IndexedVersion& m_mod_version; +}; -- cgit From e93b9560b5137a5ee7acdc34c0f74992aa02aad6 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 14 Apr 2022 22:02:41 -0300 Subject: feat: add method to delete mod metadata Also moves indexDir setting from LocalModUpdateTask -> ModFolderModel --- launcher/minecraft/mod/LocalModUpdateTask.cpp | 7 +++---- launcher/minecraft/mod/ModFolderModel.h | 7 ++++++- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/mod/LocalModUpdateTask.cpp b/launcher/minecraft/mod/LocalModUpdateTask.cpp index 0f48217b..63f5cf9a 100644 --- a/launcher/minecraft/mod/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/LocalModUpdateTask.cpp @@ -5,12 +5,11 @@ #include "FileSystem.h" #include "modplatform/packwiz/Packwiz.h" -LocalModUpdateTask::LocalModUpdateTask(QDir mods_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) - : m_mod(mod), m_mod_version(mod_version) +LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) + : m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version) { // Ensure a '.index' folder exists in the mods folder, and create it if it does not - m_index_dir = { QString("%1/.index").arg(mods_dir.absolutePath()) }; - if (!FS::ensureFolderPathExists(m_index_dir.path())) { + if (!FS::ensureFolderPathExists(index_dir.path())) { emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); } } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 62c504df..f8ad4ca8 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -108,11 +108,16 @@ public: bool isValid(); - QDir dir() + QDir& dir() { return m_dir; } + QDir indexDir() + { + return { QString("%1/.index").arg(dir().absolutePath()) }; + } + const QList & allMods() { return mods; -- cgit From fcfb2cfc3da9a8f897063db05fdf3aebc41a59ae Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Apr 2022 00:24:57 -0300 Subject: feat: use mod metadata for getting mod information For now this doesn't mean much, but it will help when we need data exclusive from the metadata, such as addon id and mod provider. Also removes the metadata when the mod is deleted, and make the Mod.h file a little more pleasing to look at :) --- launcher/minecraft/mod/Mod.cpp | 33 ++++++++++++- launcher/minecraft/mod/Mod.h | 73 ++++++++++------------------ launcher/minecraft/mod/ModFolderLoadTask.cpp | 29 ++++++++--- launcher/minecraft/mod/ModFolderLoadTask.h | 4 +- launcher/minecraft/mod/ModFolderModel.cpp | 9 +++- 5 files changed, 89 insertions(+), 59 deletions(-) (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index b6bff29b..59f4d83b 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -33,6 +33,30 @@ Mod::Mod(const QFileInfo &file) m_changedDateTime = file.lastModified(); } +Mod::Mod(const QDir& mods_dir, const Packwiz::Mod& metadata) + : m_file(mods_dir.absoluteFilePath(metadata.filename)) + // It is weird, but name is not reliable for comparing with the JAR files name + // FIXME: Maybe use hash when implemented? + , m_mmc_id(metadata.filename) + , m_name(metadata.name) +{ + if(m_file.isDir()){ + m_type = MOD_FOLDER; + } + else{ + if (metadata.filename.endsWith(".zip") || metadata.filename.endsWith(".jar")) + m_type = MOD_ZIPFILE; + else if (metadata.filename.endsWith(".litemod")) + m_type = MOD_LITEMOD; + else + m_type = MOD_SINGLEFILE; + } + + m_from_metadata = true; + m_enabled = true; + m_changedDateTime = m_file.lastModified(); +} + void Mod::repath(const QFileInfo &file) { m_file = file; @@ -101,13 +125,18 @@ bool Mod::enable(bool value) if (!foo.rename(path)) return false; } - repath(QFileInfo(path)); + if(!fromMetadata()) + repath(QFileInfo(path)); + m_enabled = value; return true; } -bool Mod::destroy() +bool Mod::destroy(QDir& index_dir) { + // Delete metadata + Packwiz::deleteModIndex(index_dir, m_name); + m_type = MOD_UNKNOWN; return FS::deletePath(m_file.filePath()); } diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 921faeb1..c9fd5813 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -14,14 +14,14 @@ */ #pragma once -#include + #include +#include #include #include #include "ModDetails.h" - - +#include "modplatform/packwiz/Packwiz.h" class Mod { @@ -32,65 +32,41 @@ public: 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_LITEMOD, //!< The mod is a litemod }; Mod() = default; Mod(const QFileInfo &file); + explicit Mod(const QDir& mods_dir, const Packwiz::Mod& metadata); - 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; - } + QFileInfo filename() const { return m_file; } + QDateTime dateTimeChanged() const { return m_changedDateTime; } + QString mmc_id() const { return m_mmc_id; } + ModType type() const { return m_type; } + bool fromMetadata() const { return m_from_metadata; } + bool enabled() const { return m_enabled; } - const ModDetails &details() const; + bool valid() const { return m_type != MOD_UNKNOWN; } - QString name() const; - QString version() const; - QString homeurl() const; + 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(); + bool destroy(QDir& index_dir); // 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; - } + bool shouldResolve() const { return !m_resolving && !m_resolved; } + bool isResolving() const { return m_resolving; } + int resolutionTicket() const { return m_resolutionTicket; } + void setResolving(bool resolving, int resolutionTicket) { m_resolving = resolving; m_resolutionTicket = resolutionTicket; @@ -104,12 +80,15 @@ public: protected: QFileInfo m_file; QDateTime m_changedDateTime; + QString m_mmc_id; QString m_name; + ModType m_type = MOD_UNKNOWN; + bool m_from_metadata = false; + std::shared_ptr m_localDetails; + bool m_enabled = true; bool m_resolving = false; bool m_resolved = false; int m_resolutionTicket = 0; - ModType m_type = MOD_UNKNOWN; - std::shared_ptr m_localDetails; }; diff --git a/launcher/minecraft/mod/ModFolderLoadTask.cpp b/launcher/minecraft/mod/ModFolderLoadTask.cpp index 88349877..fd4d6008 100644 --- a/launcher/minecraft/mod/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/ModFolderLoadTask.cpp @@ -1,18 +1,35 @@ #include "ModFolderLoadTask.h" #include -ModFolderLoadTask::ModFolderLoadTask(QDir dir) : - m_dir(dir), m_result(new Result()) +#include "modplatform/packwiz/Packwiz.h" + +ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) : + m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result()) { } void ModFolderLoadTask::run() { - m_dir.refresh(); - for (auto entry : m_dir.entryInfoList()) + // Read metadata first + m_index_dir.refresh(); + for(auto entry : m_index_dir.entryList()){ + // QDir::Filter::NoDotAndDotDot seems to exclude all files for some reason... + if(entry == "." || entry == "..") + continue; + + entry.chop(5); // Remove .toml at the end + Mod mod(m_mods_dir, Packwiz::getIndexForMod(m_index_dir, entry)); + m_result->mods[mod.mmc_id()] = mod; + } + + // Read JAR files that don't have metadata + m_mods_dir.refresh(); + for (auto entry : m_mods_dir.entryInfoList()) { - Mod m(entry); - m_result->mods[m.mmc_id()] = m; + Mod mod(entry); + if(!m_result->mods.contains(mod.mmc_id())) + m_result->mods[mod.mmc_id()] = mod; } + emit succeeded(); } diff --git a/launcher/minecraft/mod/ModFolderLoadTask.h b/launcher/minecraft/mod/ModFolderLoadTask.h index 8d720e65..c869f083 100644 --- a/launcher/minecraft/mod/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/ModFolderLoadTask.h @@ -19,11 +19,11 @@ public: } public: - ModFolderLoadTask(QDir dir); + ModFolderLoadTask(QDir& mods_dir, QDir& index_dir); void run(); signals: void succeeded(); private: - QDir m_dir; + QDir& m_mods_dir, m_index_dir; ResultPtr m_result; }; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index f0c53c39..615cfc0c 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -79,10 +79,14 @@ bool ModFolderModel::update() return true; } - auto task = new ModFolderLoadTask(m_dir); + auto index_dir = indexDir(); + auto task = new ModFolderLoadTask(dir(), index_dir); + m_update = task->result(); + QThreadPool *threadPool = QThreadPool::globalInstance(); connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate); + threadPool->start(task); return true; } @@ -334,7 +338,8 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes) for (auto i: indexes) { Mod &m = mods[i.row()]; - m.destroy(); + auto index_dir = indexDir(); + m.destroy(index_dir); } return true; } -- cgit From 5a34e8fd7c913bc138e1606baf9df2cd1a64baed Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Apr 2022 20:35:17 -0300 Subject: refactor: move mod tasks to their own subfolder Makes the launcher/minecraft/mod/ folder a little more organized. --- launcher/minecraft/mod/LocalModParseTask.cpp | 530 --------------------- launcher/minecraft/mod/LocalModParseTask.h | 37 -- launcher/minecraft/mod/LocalModUpdateTask.cpp | 31 -- launcher/minecraft/mod/LocalModUpdateTask.h | 26 - launcher/minecraft/mod/ModFolderLoadTask.cpp | 35 -- launcher/minecraft/mod/ModFolderLoadTask.h | 29 -- launcher/minecraft/mod/ModFolderModel.cpp | 14 +- launcher/minecraft/mod/ModFolderModel.h | 4 +- launcher/minecraft/mod/tasks/LocalModParseTask.cpp | 530 +++++++++++++++++++++ launcher/minecraft/mod/tasks/LocalModParseTask.h | 39 ++ .../minecraft/mod/tasks/LocalModUpdateTask.cpp | 31 ++ launcher/minecraft/mod/tasks/LocalModUpdateTask.h | 26 + launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 35 ++ launcher/minecraft/mod/tasks/ModFolderLoadTask.h | 30 ++ 14 files changed, 701 insertions(+), 696 deletions(-) delete mode 100644 launcher/minecraft/mod/LocalModParseTask.cpp delete mode 100644 launcher/minecraft/mod/LocalModParseTask.h delete mode 100644 launcher/minecraft/mod/LocalModUpdateTask.cpp delete mode 100644 launcher/minecraft/mod/LocalModUpdateTask.h delete mode 100644 launcher/minecraft/mod/ModFolderLoadTask.cpp delete mode 100644 launcher/minecraft/mod/ModFolderLoadTask.h create mode 100644 launcher/minecraft/mod/tasks/LocalModParseTask.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalModParseTask.h create mode 100644 launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalModUpdateTask.h create mode 100644 launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp create mode 100644 launcher/minecraft/mod/tasks/ModFolderLoadTask.h (limited to 'launcher/minecraft') diff --git a/launcher/minecraft/mod/LocalModParseTask.cpp b/launcher/minecraft/mod/LocalModParseTask.cpp deleted file mode 100644 index a7bec5ae..00000000 --- a/launcher/minecraft/mod/LocalModParseTask.cpp +++ /dev/null @@ -1,530 +0,0 @@ -#include "LocalModParseTask.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "Json.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 ReadMCModInfo(QByteArray contents) -{ - auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr - { - if (!arr.at(0).isObject()) { - return nullptr; - } - std::shared_ptr details = std::make_shared(); - 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 ReadMCModTOML(QByteArray contents) -{ - std::shared_ptr details = std::make_shared(); - - 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"); - if(!tomlModsArr) - { - qWarning() << "Corrupted mods.toml? Couldn't find [[mods]] array!"; - return nullptr; - } - - // 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); - if(!tomlModsTable0) - { - qWarning() << "Corrupted mods.toml? [[mods]] didn't have an element at index 0!"; - return nullptr; - } - - // 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 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 details = std::make_shared(); - - 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; -} - -// https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md -std::shared_ptr ReadQuiltModInfo(QByteArray contents) -{ - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - auto object = Json::requireObject(jsonDoc, "quilt.mod.json"); - auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version"); - - std::shared_ptr details = std::make_shared(); - - // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md - if (schemaVersion == 1) - { - auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info"); - - details->mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); - details->version = Json::requireString(modInfo.value("version"), "Mod version"); - - auto modMetadata = Json::ensureObject(modInfo.value("metadata")); - - details->name = Json::ensureString(modMetadata.value("name"), details->mod_id); - details->description = Json::ensureString(modMetadata.value("description")); - - auto modContributors = Json::ensureObject(modMetadata.value("contributors")); - - // We don't really care about the role of a contributor here - details->authors += modContributors.keys(); - - auto modContact = Json::ensureObject(modMetadata.value("contact")); - - if (modContact.contains("homepage")) - { - details->homeurl = Json::requireString(modContact.value("homepage")); - } - } - return details; -} - -std::shared_ptr ReadForgeInfo(QByteArray contents) -{ - std::shared_ptr details = std::make_shared(); - // 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 ReadLiteModInfo(QByteArray contents) -{ - std::shared_ptr details = std::make_shared(); - 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("quilt.mod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadQuiltModInfo(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/launcher/minecraft/mod/LocalModParseTask.h b/launcher/minecraft/mod/LocalModParseTask.h deleted file mode 100644 index 0f119ba6..00000000 --- a/launcher/minecraft/mod/LocalModParseTask.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once -#include -#include -#include -#include "Mod.h" -#include "ModDetails.h" - -class LocalModParseTask : public QObject, public QRunnable -{ - Q_OBJECT -public: - struct Result { - QString id; - std::shared_ptr details; - }; - using ResultPtr = std::shared_ptr; - 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/launcher/minecraft/mod/LocalModUpdateTask.cpp b/launcher/minecraft/mod/LocalModUpdateTask.cpp deleted file mode 100644 index 63f5cf9a..00000000 --- a/launcher/minecraft/mod/LocalModUpdateTask.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "LocalModUpdateTask.h" - -#include - -#include "FileSystem.h" -#include "modplatform/packwiz/Packwiz.h" - -LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) - : m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version) -{ - // Ensure a '.index' folder exists in the mods folder, and create it if it does not - if (!FS::ensureFolderPathExists(index_dir.path())) { - emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); - } -} - -void LocalModUpdateTask::executeTask() -{ - setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); - - auto pw_mod = Packwiz::createModFormat(m_index_dir, m_mod, m_mod_version); - Packwiz::updateModIndex(m_index_dir, pw_mod); - - emitSucceeded(); -} - -bool LocalModUpdateTask::abort() -{ - emitAborted(); - return true; -} diff --git a/launcher/minecraft/mod/LocalModUpdateTask.h b/launcher/minecraft/mod/LocalModUpdateTask.h deleted file mode 100644 index 866089e9..00000000 --- a/launcher/minecraft/mod/LocalModUpdateTask.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include - -#include "tasks/Task.h" -#include "modplatform/ModIndex.h" - -class LocalModUpdateTask : public Task { - Q_OBJECT - public: - using Ptr = shared_qobject_ptr; - - explicit LocalModUpdateTask(QDir mods_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version); - - bool canAbort() const override { return true; } - bool abort() override; - - protected slots: - //! Entry point for tasks. - void executeTask() override; - - private: - QDir m_index_dir; - ModPlatform::IndexedPack& m_mod; - ModPlatform::IndexedVersion& m_mod_version; -}; diff --git a/launcher/minecraft/mod/ModFolderLoadTask.cpp b/launcher/minecraft/mod/ModFolderLoadTask.cpp deleted file mode 100644 index fd4d6008..00000000 --- a/launcher/minecraft/mod/ModFolderLoadTask.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "ModFolderLoadTask.h" -#include - -#include "modplatform/packwiz/Packwiz.h" - -ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) : - m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result()) -{ -} - -void ModFolderLoadTask::run() -{ - // Read metadata first - m_index_dir.refresh(); - for(auto entry : m_index_dir.entryList()){ - // QDir::Filter::NoDotAndDotDot seems to exclude all files for some reason... - if(entry == "." || entry == "..") - continue; - - entry.chop(5); // Remove .toml at the end - Mod mod(m_mods_dir, Packwiz::getIndexForMod(m_index_dir, entry)); - m_result->mods[mod.mmc_id()] = mod; - } - - // Read JAR files that don't have metadata - m_mods_dir.refresh(); - for (auto entry : m_mods_dir.entryInfoList()) - { - Mod mod(entry); - if(!m_result->mods.contains(mod.mmc_id())) - m_result->mods[mod.mmc_id()] = mod; - } - - emit succeeded(); -} diff --git a/launcher/minecraft/mod/ModFolderLoadTask.h b/launcher/minecraft/mod/ModFolderLoadTask.h deleted file mode 100644 index c869f083..00000000 --- a/launcher/minecraft/mod/ModFolderLoadTask.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include "Mod.h" -#include - -class ModFolderLoadTask : public QObject, public QRunnable -{ - Q_OBJECT -public: - struct Result { - QMap mods; - }; - using ResultPtr = std::shared_ptr; - ResultPtr result() const { - return m_result; - } - -public: - ModFolderLoadTask(QDir& mods_dir, QDir& index_dir); - void run(); -signals: - void succeeded(); -private: - QDir& m_mods_dir, m_index_dir; - ResultPtr m_result; -}; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 615cfc0c..936b68d3 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -14,17 +14,19 @@ */ #include "ModFolderModel.h" + #include +#include +#include #include -#include -#include #include -#include -#include -#include "ModFolderLoadTask.h" #include +#include +#include #include -#include "LocalModParseTask.h" + +#include "minecraft/mod/tasks/LocalModParseTask.h" +#include "minecraft/mod/tasks/ModFolderLoadTask.h" ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir) { diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index f8ad4ca8..10a72691 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -24,8 +24,8 @@ #include "Mod.h" -#include "ModFolderLoadTask.h" -#include "LocalModParseTask.h" +#include "minecraft/mod/tasks/ModFolderLoadTask.h" +#include "minecraft/mod/tasks/LocalModParseTask.h" class LegacyInstance; class BaseInstance; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp new file mode 100644 index 00000000..a7bec5ae --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -0,0 +1,530 @@ +#include "LocalModParseTask.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Json.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 ReadMCModInfo(QByteArray contents) +{ + auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr + { + if (!arr.at(0).isObject()) { + return nullptr; + } + std::shared_ptr details = std::make_shared(); + 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 ReadMCModTOML(QByteArray contents) +{ + std::shared_ptr details = std::make_shared(); + + 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"); + if(!tomlModsArr) + { + qWarning() << "Corrupted mods.toml? Couldn't find [[mods]] array!"; + return nullptr; + } + + // 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); + if(!tomlModsTable0) + { + qWarning() << "Corrupted mods.toml? [[mods]] didn't have an element at index 0!"; + return nullptr; + } + + // 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 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 details = std::make_shared(); + + 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; +} + +// https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md +std::shared_ptr ReadQuiltModInfo(QByteArray contents) +{ + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); + auto object = Json::requireObject(jsonDoc, "quilt.mod.json"); + auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version"); + + std::shared_ptr details = std::make_shared(); + + // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md + if (schemaVersion == 1) + { + auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info"); + + details->mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); + details->version = Json::requireString(modInfo.value("version"), "Mod version"); + + auto modMetadata = Json::ensureObject(modInfo.value("metadata")); + + details->name = Json::ensureString(modMetadata.value("name"), details->mod_id); + details->description = Json::ensureString(modMetadata.value("description")); + + auto modContributors = Json::ensureObject(modMetadata.value("contributors")); + + // We don't really care about the role of a contributor here + details->authors += modContributors.keys(); + + auto modContact = Json::ensureObject(modMetadata.value("contact")); + + if (modContact.contains("homepage")) + { + details->homeurl = Json::requireString(modContact.value("homepage")); + } + } + return details; +} + +std::shared_ptr ReadForgeInfo(QByteArray contents) +{ + std::shared_ptr details = std::make_shared(); + // 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 ReadLiteModInfo(QByteArray contents) +{ + std::shared_ptr details = std::make_shared(); + 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("quilt.mod.json")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadQuiltModInfo(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() +{ +