aboutsummaryrefslogtreecommitdiff
path: root/api/logic/minecraft/mod
diff options
context:
space:
mode:
Diffstat (limited to 'api/logic/minecraft/mod')
-rw-r--r--api/logic/minecraft/mod/LocalModParseTask.cpp467
-rw-r--r--api/logic/minecraft/mod/LocalModParseTask.h37
-rw-r--r--api/logic/minecraft/mod/Mod.cpp151
-rw-r--r--api/logic/minecraft/mod/Mod.h117
-rw-r--r--api/logic/minecraft/mod/ModDetails.h17
-rw-r--r--api/logic/minecraft/mod/ModFolderLoadTask.cpp18
-rw-r--r--api/logic/minecraft/mod/ModFolderLoadTask.h29
-rw-r--r--api/logic/minecraft/mod/ModFolderModel.cpp554
-rw-r--r--api/logic/minecraft/mod/ModFolderModel.h149
-rw-r--r--api/logic/minecraft/mod/ModFolderModel_test.cpp53
-rw-r--r--api/logic/minecraft/mod/ResourcePackFolderModel.cpp23
-rw-r--r--api/logic/minecraft/mod/ResourcePackFolderModel.h13
-rw-r--r--api/logic/minecraft/mod/TexturePackFolderModel.cpp23
-rw-r--r--api/logic/minecraft/mod/TexturePackFolderModel.h13
14 files changed, 0 insertions, 1664 deletions
diff --git a/api/logic/minecraft/mod/LocalModParseTask.cpp b/api/logic/minecraft/mod/LocalModParseTask.cpp
deleted file mode 100644
index 0d6972fb..00000000
--- a/api/logic/minecraft/mod/LocalModParseTask.cpp
+++ /dev/null
@@ -1,467 +0,0 @@
-#include "LocalModParseTask.h"
-
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonArray>
-#include <QJsonValue>
-#include <quazip.h>
-#include <quazipfile.h>
-#include <toml.h>
-
-#include "settings/INIFile.h"
-#include "FileSystem.h"
-
-namespace {
-
-// NEW format
-// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
-
-// OLD format:
-// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
-std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
-{
- auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr<ModDetails>
- {
- if (!arr.at(0).isObject()) {
- return nullptr;
- }
- std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
- auto firstObj = arr.at(0).toObject();
- details->mod_id = firstObj.value("modid").toString();
- auto name = firstObj.value("name").toString();
- // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name
- if(name != "Example Mod") {
- details->name = name;
- }
- details->version = firstObj.value("version").toString();
- details->updateurl = firstObj.value("updateUrl").toString();
- auto homeurl = firstObj.value("url").toString().trimmed();
- if(!homeurl.isEmpty())
- {
- // fix up url.
- if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://"))
- {
- homeurl.prepend("http://");
- }
- }
- details->homeurl = homeurl;
- details->description = firstObj.value("description").toString();
- QJsonArray authors = firstObj.value("authorList").toArray();
- if (authors.size() == 0) {
- // FIXME: what is the format of this? is there any?
- authors = firstObj.value("authors").toArray();
- }
-
- for (auto author: authors)
- {
- details->authors.append(author.toString());
- }
- details->credits = firstObj.value("credits").toString();
- return details;
- };
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
- // this is the very old format that had just the array
- if (jsonDoc.isArray())
- {
- return getInfoFromArray(jsonDoc.array());
- }
- else if (jsonDoc.isObject())
- {
- auto val = jsonDoc.object().value("modinfoversion");
- if(val.isUndefined()) {
- val = jsonDoc.object().value("modListVersion");
- }
- int version = val.toDouble();
- if (version != 2)
- {
- qCritical() << "BAD stuff happened to mod json:";
- qCritical() << contents;
- return nullptr;
- }
- auto arrVal = jsonDoc.object().value("modlist");
- if(arrVal.isUndefined()) {
- arrVal = jsonDoc.object().value("modList");
- }
- if (arrVal.isArray())
- {
- return getInfoFromArray(arrVal.toArray());
- }
- }
- return nullptr;
-}
-
-// https://github.com/MinecraftForge/Documentation/blob/5ab4ba6cf9abc0ac4c0abd96ad187461aefd72af/docs/gettingstarted/structuring.md
-std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
-{
- std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
-
- char errbuf[200];
- // top-level table
- toml_table_t* tomlData = toml_parse(contents.data(), errbuf, sizeof(errbuf));
-
- if(!tomlData)
- {
- return nullptr;
- }
-
- // array defined by [[mods]]
- toml_array_t* tomlModsArr = toml_array_in(tomlData, "mods");
- // we only really care about the first element, since multiple mods in one file is not supported by us at the moment
- toml_table_t* tomlModsTable0 = toml_table_at(tomlModsArr, 0);
-
- // mandatory properties - always in [[mods]]
- toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId");
- if(modIdDatum.ok)
- {
- details->mod_id = modIdDatum.u.s;
- // library says this is required for strings
- free(modIdDatum.u.s);
- }
- toml_datum_t versionDatum = toml_string_in(tomlModsTable0, "version");
- if(versionDatum.ok)
- {
- details->version = versionDatum.u.s;
- free(versionDatum.u.s);
- }
- toml_datum_t displayNameDatum = toml_string_in(tomlModsTable0, "displayName");
- if(displayNameDatum.ok)
- {
- details->name = displayNameDatum.u.s;
- free(displayNameDatum.u.s);
- }
- toml_datum_t descriptionDatum = toml_string_in(tomlModsTable0, "description");
- if(descriptionDatum.ok)
- {
- details->description = descriptionDatum.u.s;
- free(descriptionDatum.u.s);
- }
-
- // optional properties - can be in the root table or [[mods]]
- toml_datum_t authorsDatum = toml_string_in(tomlData, "authors");
- QString authors = "";
- if(authorsDatum.ok)
- {
- authors = authorsDatum.u.s;
- free(authorsDatum.u.s);
- }
- else
- {
- authorsDatum = toml_string_in(tomlModsTable0, "authors");
- if(authorsDatum.ok)
- {
- authors = authorsDatum.u.s;
- free(authorsDatum.u.s);
- }
- }
- if(!authors.isEmpty())
- {
- // author information is stored as a string now, not a list
- details->authors.append(authors);
- }
- // is credits even used anywhere? including this for completion/parity with old data version
- toml_datum_t creditsDatum = toml_string_in(tomlData, "credits");
- QString credits = "";
- if(creditsDatum.ok)
- {
- authors = creditsDatum.u.s;
- free(creditsDatum.u.s);
- }
- else
- {
- creditsDatum = toml_string_in(tomlModsTable0, "credits");
- if(creditsDatum.ok)
- {
- credits = creditsDatum.u.s;
- free(creditsDatum.u.s);
- }
- }
- details->credits = credits;
- toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL");
- QString homeurl = "";
- if(homeurlDatum.ok)
- {
- homeurl = homeurlDatum.u.s;
- free(homeurlDatum.u.s);
- }
- else
- {
- homeurlDatum = toml_string_in(tomlModsTable0, "displayURL");
- if(homeurlDatum.ok)
- {
- homeurl = homeurlDatum.u.s;
- free(homeurlDatum.u.s);
- }
- }
- if(!homeurl.isEmpty())
- {
- // fix up url.
- if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://"))
- {
- homeurl.prepend("http://");
- }
- }
- details->homeurl = homeurl;
-
- // this seems to be recursive, so it should free everything
- toml_free(tomlData);
-
- return details;
-}
-
-// https://fabricmc.net/wiki/documentation:fabric_mod_json
-std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
-{
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
- auto object = jsonDoc.object();
- auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0;
-
- std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
-
- details->mod_id = object.value("id").toString();
- details->version = object.value("version").toString();
-
- details->name = object.contains("name") ? object.value("name").toString() : details->mod_id;
- details->description = object.value("description").toString();
-
- if (schemaVersion >= 1)
- {
- QJsonArray authors = object.value("authors").toArray();
- for (auto author: authors)
- {
- if(author.isObject()) {
- details->authors.append(author.toObject().value("name").toString());
- }
- else {
- details->authors.append(author.toString());
- }
- }
-
- if (object.contains("contact"))
- {
- QJsonObject contact = object.value("contact").toObject();
-
- if (contact.contains("homepage"))
- {
- details->homeurl = contact.value("homepage").toString();
- }
- }
- }
- return details;
-}
-
-std::shared_ptr<ModDetails> ReadForgeInfo(QByteArray contents)
-{
- std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
- // Read the data
- details->name = "Minecraft Forge";
- details->mod_id = "Forge";
- details->homeurl = "http://www.minecraftforge.net/forum/";
- INIFile ini;
- if (!ini.loadFile(contents))
- return details;
-
- QString major = ini.get("forge.major.number", "0").toString();
- QString minor = ini.get("forge.minor.number", "0").toString();
- QString revision = ini.get("forge.revision.number", "0").toString();
- QString build = ini.get("forge.build.number", "0").toString();
-
- details->version = major + "." + minor + "." + revision + "." + build;
- return details;
-}
-
-std::shared_ptr<ModDetails> ReadLiteModInfo(QByteArray contents)
-{
- std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
- auto object = jsonDoc.object();
- if (object.contains("name"))
- {
- details->mod_id = details->name = object.value("name").toString();
- }
- if (object.contains("version"))
- {
- details->version = object.value("version").toString("");
- }
- else
- {
- details->version = object.value("revision").toString("");
- }
- details->mcversion = object.value("mcversion").toString();
- auto author = object.value("author").toString();
- if(!author.isEmpty()) {
- details->authors.append(author);
- }
- details->description = object.value("description").toString();
- details->homeurl = object.value("url").toString();
- return details;
-}
-
-}
-
-LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile):
- m_token(token),
- m_type(type),
- m_modFile(modFile),
- m_result(new Result())
-{
-}
-
-void LocalModParseTask::processAsZip()
-{
- QuaZip zip(m_modFile.filePath());
- if (!zip.open(QuaZip::mdUnzip))
- return;
-
- QuaZipFile file(&zip);
-
- if (zip.setCurrentFile("META-INF/mods.toml"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- m_result->details = ReadMCModTOML(file.readAll());
- file.close();
-
- // to replace ${file.jarVersion} with the actual version, as needed
- if (m_result->details && m_result->details->version == "${file.jarVersion}")
- {
- if (zip.setCurrentFile("META-INF/MANIFEST.MF"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- // quick and dirty line-by-line parser
- auto manifestLines = file.readAll().split('\n');
- QString manifestVersion = "";
- for (auto &line : manifestLines)
- {
- if (QString(line).startsWith("Implementation-Version: "))
- {
- manifestVersion = QString(line).remove("Implementation-Version: ");
- break;
- }
- }
-
- // some mods use ${projectversion} in their build.gradle, causing this mess to show up in MANIFEST.MF
- // also keep with forge's behavior of setting the version to "NONE" if none is found
- if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "")
- {
- manifestVersion = "NONE";
- }
-
- m_result->details->version = manifestVersion;
-
- file.close();
- }
- }
-
- zip.close();
- return;
- }
- else if (zip.setCurrentFile("mcmod.info"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- m_result->details = ReadMCModInfo(file.readAll());
- file.close();
- zip.close();
- return;
- }
- else if (zip.setCurrentFile("fabric.mod.json"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- m_result->details = ReadFabricModInfo(file.readAll());
- file.close();
- zip.close();
- return;
- }
- else if (zip.setCurrentFile("forgeversion.properties"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- m_result->details = ReadForgeInfo(file.readAll());
- file.close();
- zip.close();
- return;
- }
-
- zip.close();
-}
-
-void LocalModParseTask::processAsFolder()
-{
- QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info"));
- if (mcmod_info.isFile())
- {
- QFile mcmod(mcmod_info.filePath());
- if (!mcmod.open(QIODevice::ReadOnly))
- return;
- auto data = mcmod.readAll();
- if (data.isEmpty() || data.isNull())
- return;
- m_result->details = ReadMCModInfo(data);
- }
-}
-
-void LocalModParseTask::processAsLitemod()
-{
- QuaZip zip(m_modFile.filePath());
- if (!zip.open(QuaZip::mdUnzip))
- return;
-
- QuaZipFile file(&zip);
-
- if (zip.setCurrentFile("litemod.json"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- m_result->details = ReadLiteModInfo(file.readAll());
- file.close();
- }
- zip.close();
-}
-
-void LocalModParseTask::run()
-{
- switch(m_type)
- {
- case Mod::MOD_ZIPFILE:
- processAsZip();
- break;
- case Mod::MOD_FOLDER:
- processAsFolder();
- break;
- case Mod::MOD_LITEMOD:
- processAsLitemod();
- break;
- default:
- break;
- }
- emit finished(m_token);
-}
diff --git a/api/logic/minecraft/mod/LocalModParseTask.h b/api/logic/minecraft/mod/LocalModParseTask.h
deleted file mode 100644
index 0f119ba6..00000000
--- a/api/logic/minecraft/mod/LocalModParseTask.h
+++ /dev/null
@@ -1,37 +0,0 @@
-#pragma once
-#include <QRunnable>
-#include <QDebug>
-#include <QObject>
-#include "Mod.h"
-#include "ModDetails.h"
-
-class LocalModParseTask : public QObject, public QRunnable
-{
- Q_OBJECT
-public:
- struct Result {
- QString id;
- std::shared_ptr<ModDetails> details;
- };
- using ResultPtr = std::shared_ptr<Result>;
- ResultPtr result() const {
- return m_result;
- }
-
- LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile);
- void run();
-
-signals:
- void finished(int token);
-
-private:
- void processAsZip();
- void processAsFolder();
- void processAsLitemod();
-
-private:
- int m_token;
- Mod::ModType m_type;
- QFileInfo m_modFile;
- ResultPtr m_result;
-};
diff --git a/api/logic/minecraft/mod/Mod.cpp b/api/logic/minecraft/mod/Mod.cpp
deleted file mode 100644
index b6bff29b..00000000
--- a/api/logic/minecraft/mod/Mod.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <QDir>
-#include <QString>
-
-#include "Mod.h"
-#include <QDebug>
-#include <FileSystem.h>
-
-namespace {
-
-ModDetails invalidDetails;
-
-}
-
-
-Mod::Mod(const QFileInfo &file)
-{
- repath(file);
- m_changedDateTime = file.lastModified();
-}
-
-void Mod::repath(const QFileInfo &file)
-{
- m_file = file;
- QString name_base = file.fileName();
-
- m_type = Mod::MOD_UNKNOWN;
-
- m_mmc_id = name_base;
-
- if (m_file.isDir())
- {
- m_type = MOD_FOLDER;
- m_name = name_base;
- }
- else if (m_file.isFile())
- {
- if (name_base.endsWith(".disabled"))
- {
- m_enabled = false;
- name_base.chop(9);
- }
- else
- {
- m_enabled = true;
- }
- if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
- {
- m_type = MOD_ZIPFILE;
- name_base.chop(4);
- }
- else if (name_base.endsWith(".litemod"))
- {
- m_type = MOD_LITEMOD;
- name_base.chop(8);
- }
- else
- {
- m_type = MOD_SINGLEFILE;
- }
- m_name = name_base;
- }
-}
-
-bool Mod::enable(bool value)
-{
- if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
- return false;
-
- if (m_enabled == value)
- return false;
-
- QString path = m_file.absoluteFilePath();
- if (value)
- {
- QFile foo(path);
- if (!path.endsWith(".disabled"))
- return false;
- path.chop(9);
- if (!foo.rename(path))
- return false;
- }
- else
- {
- QFile foo(path);
- path += ".disabled";
- if (!foo.rename(path))
- return false;
- }
- repath(QFileInfo(path));
- m_enabled = value;
- return true;
-}
-
-bool Mod::destroy()
-{
- m_type = MOD_UNKNOWN;
- return FS::deletePath(m_file.filePath());
-}
-
-
-const ModDetails & Mod::details() const
-{
- if(!m_localDetails)
- return invalidDetails;
- return *m_localDetails;
-}
-
-
-QString Mod::version() const
-{
- return details().version;
-}
-
-QString Mod::name() const
-{
- auto & d = details();
- if(!d.name.isEmpty()) {
- return d.name;
- }
- return m_name;
-}
-
-QString Mod::homeurl() const
-{
- return details().homeurl;
-}
-
-QString Mod::description() const
-{
- return details().description;
-}
-
-QStringList Mod::authors() const
-{
- return details().authors;
-}
diff --git a/api/logic/minecraft/mod/Mod.h b/api/logic/minecraft/mod/Mod.h
deleted file mode 100644
index f77ffd41..00000000
--- a/api/logic/minecraft/mod/Mod.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-#include <QFileInfo>
-#include <QDateTime>
-#include <QList>
-#include <memory>
-
-#include "multimc_logic_export.h"
-
-#include "ModDetails.h"
-
-
-
-class MULTIMC_LOGIC_EXPORT Mod
-{
-public:
- enum ModType
- {
- MOD_UNKNOWN, //!< Indicates an unspecified mod type.
- MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
- MOD_SINGLEFILE, //!< The mod is a single file (not a zip file).
- MOD_FOLDER, //!< The mod is in a folder on the filesystem.
- MOD_LITEMOD, //!< The mod is a litemod
- };
-
- Mod() = default;
- Mod(const QFileInfo &file);
-
- QFileInfo filename() const
- {
- return m_file;
- }
- QString mmc_id() const
- {
- return m_mmc_id;
- }
- ModType type() const
- {
- return m_type;
- }
- bool valid()
- {
- return m_type != MOD_UNKNOWN;
- }
-
- QDateTime dateTimeChanged() const
- {
- return m_changedDateTime;
- }
-
- bool enabled() const
- {
- return m_enabled;
- }
-
- const ModDetails &details() const;
-
- QString name() const;
- QString version() const;
- QString homeurl() const;
- QString description() const;
- QStringList authors() const;
-
- bool enable(bool value);
-
- // delete all the files of this mod
- bool destroy();
-
- // change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
- void repath(const QFileInfo &file);
-
- bool shouldResolve() {
- return !m_resolving && !m_resolved;
- }
- bool isResolving() {
- return m_resolving;
- }
- int resolutionTicket()
- {
- return m_resolutionTicket;
- }
- void setResolving(bool resolving, int resolutionTicket) {
- m_resolving = resolving;
- m_resolutionTicket = resolutionTicket;
- }
- void finishResolvingWithDetails(std::shared_ptr<ModDetails> details){
- m_resolving = false;
- m_resolved = true;
- m_localDetails = details;
- }
-
-protected:
- QFileInfo m_file;
- QDateTime m_changedDateTime;
- QString m_mmc_id;
- QString m_name;
- bool m_enabled = true;
- bool m_resolving = false;
- bool m_resolved = false;
- int m_resolutionTicket = 0;
- ModType m_type = MOD_UNKNOWN;
- std::shared_ptr<ModDetails> m_localDetails;
-};
diff --git a/api/logic/minecraft/mod/ModDetails.h b/api/logic/minecraft/mod/ModDetails.h
deleted file mode 100644
index 6ab4aee7..00000000
--- a/api/logic/minecraft/mod/ModDetails.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include <QString>
-#include <QStringList>
-
-struct ModDetails
-{
- QString mod_id;
- QString name;
- QString version;
- QString mcversion;
- QString homeurl;
- QString updateurl;
- QString description;
- QStringList authors;
- QString credits;
-};
diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.cpp b/api/logic/minecraft/mod/ModFolderLoadTask.cpp
deleted file mode 100644
index 88349877..00000000
--- a/api/logic/minecraft/mod/ModFolderLoadTask.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "ModFolderLoadTask.h"
-#include <QDebug>
-
-ModFolderLoadTask::ModFolderLoadTask(QDir dir) :
- m_dir(dir), m_result(new Result())
-{
-}
-
-void ModFolderLoadTask::run()
-{
- m_dir.refresh();
- for (auto entry : m_dir.entryInfoList())
- {
- Mod m(entry);
- m_result->mods[m.mmc_id()] = m;
- }
- emit succeeded();
-}
diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.h b/api/logic/minecraft/mod/ModFolderLoadTask.h
deleted file mode 100644
index 8d720e65..00000000
--- a/api/logic/minecraft/mod/ModFolderLoadTask.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-#include <QRunnable>
-#include <QObject>
-#include <QDir>
-#include <QMap>
-#include "Mod.h"
-#include <memory>
-
-class ModFolderLoadTask : public QObject, public QRunnable
-{
- Q_OBJECT
-public:
- struct Result {
- QMap<QString, Mod> mods;
- };
- using ResultPtr = std::shared_ptr<Result>;
- ResultPtr result() const {
- return m_result;
- }
-
-public:
- ModFolderLoadTask(QDir dir);
- void run();
-signals:
- void succeeded();
-private:
- QDir m_dir;
- ResultPtr m_result;
-};
diff --git a/api/logic/minecraft/mod/ModFolderModel.cpp b/api/logic/minecraft/mod/ModFolderModel.cpp
deleted file mode 100644
index 031eebe5..00000000
--- a/api/logic/minecraft/mod/ModFolderModel.cpp
+++ /dev/null
@@ -1,554 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ModFolderModel.h"
-#include <FileSystem.h>
-#include <QMimeData>
-#include <QUrl>
-#include <QUuid>
-#include <QString>
-#include <QFileSystemWatcher>
-#include <QDebug>
-#include "ModFolderLoadTask.h"
-#include <QThreadPool>
-#include <algorithm>
-#include "LocalModParseTask.h"
-
-ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir)
-{
- FS::ensureFolderPathExists(m_dir.absolutePath());
- m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks);
- m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
- m_watcher = new QFileSystemWatcher(this);
- connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
-}
-
-void ModFolderModel::startWatching()
-{
- if(is_watching)
- return;
-
- update();
-
- is_watching = m_watcher->addPath(m_dir.absolutePath());
- if (is_watching)
- {
- qDebug() << "Started watching " << m_dir.absolutePath();
- }
- else
- {
- qDebug() << "Failed to start watching " << m_dir.absolutePath();
- }
-}
-
-void ModFolderModel::stopWatching()
-{
- if(!is_watching)
- return;
-
- is_watching = !m_watcher->removePath(m_dir.absolutePath());
- if (!is_watching)
- {
- qDebug() << "Stopped watching " << m_dir.absolutePath();
- }
- else
- {
- qDebug() << "Failed to stop watching " << m_dir.absolutePath();
- }
-}
-
-bool ModFolderModel::update()
-{
- if (!isValid()) {
- return false;
- }
- if(m_update) {
- scheduled_update = true;
- return true;
- }
-
- auto task = new ModFolderLoadTask(m_dir);
- m_update = task->result();
- QThreadPool *threadPool = QThreadPool::globalInstance();
- connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
- threadPool->start(task);
- return true;
-}
-
-void ModFolderModel::finishUpdate()
-{
- QSet<QString> currentSet = modsIndex.keys().toSet();
- auto & newMods = m_update->mods;
- QSet<QString> newSet = newMods.keys().toSet();
-
- // see if the kept mods changed in some way
- {
- QSet<QString> kept = currentSet;
- kept.intersect(newSet);
- for(auto & keptMod: kept) {
- auto & newMod = newMods[keptMod];
- auto row = modsIndex[keptMod];
- auto & currentMod = mods[row];
- if(newMod.dateTimeChanged() == currentMod.dateTimeChanged()) {
- // no significant change, ignore...
- continue;
- }
- auto & oldMod = mods[row];
- if(oldMod.isResolving()) {
- activeTickets.remove(oldMod.resolutionTicket());
- }
- oldMod = newMod;
- resolveMod(mods[row]);
- emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
- }
- }
-
- // remove mods no longer present
- {
- QSet<QString> removed = currentSet;
- QList<int> removedRows;
- removed.subtract(newSet);
- for(auto & removedMod: removed) {
- removedRows.append(modsIndex[removedMod]);
- }
- std::sort(removedRows.begin(), removedRows.end(), std::greater<int>());
- for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) {
- int removedIndex = *iter;
- beginRemoveRows(QModelIndex(), removedIndex, removedIndex);
- auto removedIter = mods.begin() + removedIndex;
- if(removedIter->isResolving()) {
- activeTickets.remove(removedIter->resolutionTicket());
- }
- mods.erase(removedIter);
- endRemoveRows();
- }
- }
-
- // add new mods to the end
- {
- QSet<QString> added = newSet;
- added.subtract(currentSet);
- beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1);
- for(auto & addedMod: added) {
- mods.append(newMods[addedMod]);
- resolveMod(mods.last());
- }
- endInsertRows();
- }
-
- // update index
- {
- modsIndex.clear();
- int idx = 0;
- for(auto & mod: mods) {
- modsIndex[mod.mmc_id()] = idx;
- idx++;
- }
- }
-
- m_update.reset();
-
- emit updateFinished();
-
- if(scheduled_update) {
- scheduled_update = false;
- update();
- }
-}
-
-void ModFolderModel::resolveMod(Mod& m)
-{
- if(!m.shouldResolve()) {
- return;
- }
-
- auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename());
- auto result = task->result();
- result->id = m.mmc_id();
- activeTickets.insert(nextResolutionTicket, result);
- m.setResolving(true, nextResolutionTicket);
- nextResolutionTicket++;
- QThreadPool *threadPool = QThreadPool::globalInstance();
- connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse);
- threadPool->start(task);
-}
-
-void ModFolderModel::finishModParse(int token)
-{
- auto iter = activeTickets.find(token);
- if(iter == activeTickets.end()) {
- return;
- }
- auto result = *iter;
- activeTickets.remove(token);
- int row = modsIndex[result->id];
- auto & mod = mods[row];
- mod.finishResolvingWithDetails(result->details);
- emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
-}
-
-void ModFolderModel::disableInteraction(bool disabled)
-{
- if (interaction_disabled == disabled) {
- return;
- }
- interaction_disabled = disabled;
- if(size()) {
- emit dataChanged(index(0), index(size() - 1));
- }
-}
-
-void ModFolderModel::directoryChanged(QString path)
-{
- update();
-}
-
-bool ModFolderModel::isValid()
-{
- return m_dir.exists() && m_dir.isReadable();
-}
-
-// FIXME: this does not take disabled mod (with extra .disable extension) into account...
-bool ModFolderModel::installMod(const QString &filename)
-{
- if(interaction_disabled) {
- return false;
- }
-
- // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
- auto originalPath = FS::NormalizePath(filename);
- QFileInfo fileinfo(originalPath);
-
- if (!fileinfo.exists() || !fileinfo.isReadable())
- {
- qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath;
- return false;
- }
- qDebug() << "installing: " << fileinfo.absoluteFilePath();
-
- Mod installedMod(fileinfo);
- if (!installedMod.valid())
- {
- qDebug() << originalPath << "is not a valid mod. Ignoring it.";
- return false;
- }
-
- auto type = installedMod.type();
- if (type == Mod::MOD_UNKNOWN)
- {
- qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it.";
- return false;
- }
-
- auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName()));
- if(originalPath == newpath)
- {
- qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense...";
- return false;
- }
-
- if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
- {
- if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled")))
- {
- if(!QFile::remove(newpath))
- {
- // FIXME: report error in a user-visible way
- qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed.";
- return false;
- }
- qDebug() << newpath << "has been deleted.";
- }
- if (!QFile::copy(fileinfo.filePath(), newpath))
- {
- qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed.";
- // FIXME: report error in a user-visible way
- return false;
- }
- FS::updateTimestamp(newpath);
- installedMod.repath(newpath);
- update();
- return true;
- }
- else if (type == Mod::MOD_FOLDER)
- {
- QString from = fileinfo.filePath();
- if(QFile::exists(newpath))
- {
- qDebug() << "Ignoring folder " << from << ", it would merge with " << newpath;
- return false;
- }
-
- if (!FS::copy(from, newpath)())
- {
- qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed.";
- return false;
- }
- installedMod.repath(newpath);
- update();
- return true;
- }
- return false;
-}
-
-bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable)
-{
- if(interaction_disabled) {
- return false;
- }
-
- if(indexes.isEmpty())
- return true;
-
- for (auto index: indexes)
- {
- if(index.column() != 0) {
- continue;
- }
- setModStatus(index.row(), enable);
- }
- return true;
-}
-
-bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
-{
- if(interaction_disabled) {
- return false;
- }
-
- if(indexes.isEmpty())
- return true;
-
- for (auto i: indexes)
- {
- Mod &m = mods[i.row()];
- m.destroy();
- }
- return true;
-}
-
-int ModFolderModel::columnCount(const QModelIndex &parent) const
-{
- return NUM_COLUMNS;
-}
-
-QVariant ModFolderModel::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- int row = index.row();
- int column = index.column();
-
- if (row < 0 || row >= mods.size())
- return QVariant();
-
- switch (role)
- {
- case Qt::DisplayRole:
- switch (column)
- {
- case NameColumn:
- return mods[row].name();
- case VersionColumn: {
- switch(mods[row].type()) {
- case Mod::MOD_FOLDER:
- return tr("Folder");
- case Mod::MOD_SINGLEFILE:
- return tr("File");
- default:
- break;
- }
- return mods[row].version();
- }
- case DateColumn:
- return mods[row].dateTimeChanged();
-
- default:
- return QVariant();
- }
-
- case Qt::ToolTipRole:
- return mods[row].mmc_id();
-
- case Qt::CheckStateRole:
- switch (column)
- {
- case ActiveColumn:
- return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
- default:
- return QVariant();
- }
- default:
- return QVariant();
- }
-}
-
-bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role)
-{
- if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
- {
- return false;
- }
-
- if (role == Qt::CheckStateRole)
- {
- return setModStatus(index.row(), Toggle);
- }
- return false;
-}
-
-bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action)
-{
- if(row < 0 || row >= mods.size()) {
- return false;
- }
-
- auto &mod = mods[row];
- bool desiredStatus;
- switch(action) {
- case Enable:
- desiredStatus = true;
- break;
- case Disable:
- desiredStatus = false;
- break;
- case Toggle:
- default:
- desiredStatus = !mod.enabled();
- break;
- }
-
- if(desiredStatus == mod.enabled()) {
- return true;
- }
-
- // preserve the row, but change its ID
- auto oldId = mod.mmc_id();
- if(!mod.enable(!mod.enabled())) {
- return false;
- }
- auto newId = mod.mmc_id();
- if(modsIndex.contains(newId)) {
- // NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled
- // But is it necessary?
- }
- modsIndex.remove(oldId);
- modsIndex[newId] = row;
- emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
- return true;
-}
-
-QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
-{
- switch (role)
- {
- case Qt::DisplayRole:
- switch (section)
- {
- case ActiveColumn:
- return QString();
- case NameColumn:
- return tr("Name");
- case VersionColumn:
- return tr("Version");
- case DateColumn:
- return tr("Last changed");
- default:
- return QVariant();
- }
-
- case Qt::ToolTipRole:
- switch (section)
- {
- case ActiveColumn:
- return tr("Is the mod enabled?");
- case NameColumn:
- return tr("The name of the mod.");
- case VersionColumn:
- return tr("The version of the mod.");
- case DateColumn:
- return tr("The date and time this mod was last changed (or added).");
- default:
- return QVariant();
- }
- default:
- return QVariant();
- }
- return QVariant();
-}
-
-Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const
-{
- Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
- auto flags = defaultFlags;
- if(interaction_disabled) {
- flags &= ~Qt::ItemIsDropEnabled;
- }
- else
- {
- flags |= Qt::ItemIsDropEnabled;
- if(index.isValid()) {
- flags |= Qt::ItemIsUserCheckable;
- }
- }
- return flags;
-}
-
-Qt::DropActions ModFolderModel::supportedDropActions() const
-{
- // copy from outside, move from within and other mod lists
- return Qt::CopyAction | Qt::MoveAction;
-}
-
-QStringList ModFolderModel::mimeTypes() const
-{
- QStringList types;
- types << "text/uri-list";
- return types;
-}
-
-bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
-{
- if (action == Qt::IgnoreAction)
- {
- return true;
- }
-
- // check if the action is supported
- if (!data || !(action & supportedDropActions()))
- {
- return false;
- }
-
- // files dropped from outside?
- if (data->hasUrls())
- {
- auto urls = data->urls();
- for (auto url : urls)
- {
- // only local files may be dropped...
- if (!url.isLocalFile())
- {
- continue;
- }
- // TODO: implement not only copy, but also move
- // FIXME: handle errors here
- installMod(url.toLocalFile());
- }
- return true;
- }
- return false;
-}
diff --git a/api/logic/minecraft/mod/ModFolderModel.h b/api/logic/minecraft/mod/ModFolderModel.h
deleted file mode 100644
index b0a76121..00000000
--- a/api/logic/minecraft/mod/ModFolderModel.h
+++ /dev/null
@@ -1,149 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QList>
-#include <QMap>
-#include <QSet>
-#include <QString>
-#include <QDir>
-#include <QAbstractListModel>
-
-#include "Mod.h"
-
-#include "multimc_logic_export.h"
-#include "ModFolderLoadTask.h"
-#include "LocalModParseTask.h"
-
-class LegacyInstance;
-class BaseInstance;
-class QFileSystemWatcher;
-
-/**
- * A legacy mod list.
- * Backed by a folder.
- */
-class MULTIMC_LOGIC_EXPORT ModFolderModel : public QAbstractListModel
-{
- Q_OBJECT
-public:
- enum Columns
- {
- ActiveColumn = 0,
- NameColumn,
- VersionColumn,
- DateColumn,
- NUM_COLUMNS
- };
- enum ModStatusAction {
- Disable,
- Enable,
- Toggle
- };
- ModFolderModel(const QString &dir);
-
- virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
- Qt::DropActions supportedDropActions() const override;
-
- /// flags, mostly to support drag&drop
- virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
- QStringList mimeTypes() const override;
- bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
-
- virtual int rowCount(const QModelIndex &) const override
- {
- return size();
- }
-
- virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
- virtual int columnCount(const QModelIndex &parent) const override;
-
- size_t size() const
- {
- return mods.size();
- }
- ;
- bool empty() const
- {
- return size() == 0;
- }
- Mod &operator[](size_t index)
- {
- return mods[index];
- }
- const Mod &at(size_t index) const
- {
- return mods.at(index);
- }
-
- /// Reloads the mod list and returns true if the list changed.
- bool update();
-
- /**
- * Adds the given mod to the list at the given index - if the list supports custom ordering
- */
- bool installMod(const QString& filename);
-
- /// Deletes all the selected mods
- bool deleteMods(const QModelIndexList &indexes);
-
- /// Enable or disable listed mods
- bool setModStatus(const QModelIndexList &indexes, ModStatusAction action);
-
- void startWatching();
- void stopWatching();
-
- bool isValid();
-
- QDir dir()
- {
- return m_dir;
- }
-
- const QList<Mod> & allMods()
- {
- return mods;
- }
-
-public slots:
- void disableInteraction(bool disabled);
-
-private
-slots:
- void directoryChanged(QString path);
- void finishUpdate();
- void finishModParse(int token);
-
-signals:
- void updateFinished();
-
-private:
- void resolveMod(Mod& m);
- bool setModStatus(int index, ModStatusAction action);
-
-protected:
- QFileSystemWatcher *m_watcher;
- bool is_watching = false;
- ModFolderLoadTask::ResultPtr m_update;
- bool scheduled_update = false;
- bool interaction_disabled = false;
- QDir m_dir;
- QMap<QString, int> modsIndex;
- QMap<int, LocalModParseTask::ResultPtr> activeTickets;
- int nextResolutionTicket = 0;
- QList<Mod> mods;
-};
diff --git a/api/logic/minecraft/mod/ModFolderModel_test.cpp b/api/logic/minecraft/mod/ModFolderModel_test.cpp
deleted file mode 100644
index 76f16ed5..00000000
--- a/api/logic/minecraft/mod/ModFolderModel_test.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-
-#include <QTest>
-#include <QTemporaryDir>
-#include "TestUtil.h"
-
-#include "FileSystem.h"
-#include "minecraft/mod/ModFolderModel.h"
-
-class ModFolderModelTest : public QObject
-{
- Q_OBJECT
-
-private
-slots:
- // test for GH-1178 - install a folder with files to a mod list
- void test_1178()
- {
- // source
- QString source = QFINDTESTDATA("data/test_folder");
-
- // sanity check
- QVERIFY(!source.endsWith('/'));
-
- auto verify = [](QString path)
- {
- QDir target_dir(FS::PathCombine(path, "test_folder"));
- QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
- QVERIFY(target_dir.entryList().contains("assets"));
- };
-
- // 1. test with no trailing /
- {
- QString folder = source;
- QTemporaryDir tempDir;
- ModFolderModel m(tempDir.path());
- m.installMod(folder);
- verify(tempDir.path());
- }
-
- // 2. test with trailing /
- {
- QString folder = source + '/';
- QTemporaryDir tempDir;
- ModFolderModel m(tempDir.path());
- m.installMod(folder);
- verify(tempDir.path());
- }
- }
-};
-
-QTEST_GUILESS_MAIN(ModFolderModelTest)
-
-#include "ModFolderModel_test.moc"
diff --git a/api/logic/minecraft/mod/ResourcePackFolderModel.cpp b/api/logic/minecraft/mod/ResourcePackFolderModel.cpp
deleted file mode 100644
index f3d7f566..00000000
--- a/api/logic/minecraft/mod/ResourcePackFolderModel.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#include "ResourcePackFolderModel.h"
-
-ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) {
-}
-
-QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const {
- if (role == Qt::ToolTipRole) {
- switch (section) {
- case ActiveColumn:
- return tr("Is the resource pack enabled?");
- case NameColumn:
- return tr("The name of the resource pack.");
- case VersionColumn:
- return tr("The version of the resource pack.");
- case DateColumn:
- return tr("The date and time this resource pack was last changed (or added).");
- default:
- return QVariant();
- }
- }
-
- return ModFolderModel::headerData(section, orientation, role);
-}
diff --git a/api/logic/minecraft/mod/ResourcePackFolderModel.h b/api/logic/minecraft/mod/ResourcePackFolderModel.h
deleted file mode 100644
index 47eb4bb2..00000000
--- a/api/logic/minecraft/mod/ResourcePackFolderModel.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include "ModFolderModel.h"
-
-class MULTIMC_LOGIC_EXPORT ResourcePackFolderModel : public ModFolderModel
-{
- Q_OBJECT
-
-public:
- explicit ResourcePackFolderModel(const QString &dir);
-
- QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
-};
diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.cpp b/api/logic/minecraft/mod/TexturePackFolderModel.cpp
deleted file mode 100644
index d5956da1..00000000
--- a/api/logic/minecraft/mod/TexturePackFolderModel.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#include "TexturePackFolderModel.h"
-
-TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) {
-}
-
-QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const {
- if (role == Qt::ToolTipRole) {
- switch (section) {
- case ActiveColumn:
- return tr("Is the texture pack enabled?");
- case NameColumn:
- return tr("The name of the texture pack.");
- case VersionColumn:
- return tr("The version of the texture pack.");
- case DateColumn:
- return tr("The date and time this texture pack was last changed (or added).");
- default:
- return QVariant();
- }
- }
-
- return ModFolderModel::headerData(section, orientation, role);
-}
diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.h b/api/logic/minecraft/mod/TexturePackFolderModel.h
deleted file mode 100644
index d773b17b..00000000
--- a/api/logic/minecraft/mod/TexturePackFolderModel.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include "ModFolderModel.h"
-
-class MULTIMC_LOGIC_EXPORT TexturePackFolderModel : public ModFolderModel
-{
- Q_OBJECT
-
-public:
- explicit TexturePackFolderModel(const QString &dir);
-
- QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
-};