aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft')
-rw-r--r--launcher/minecraft/GradleSpecifier_test.cpp77
-rw-r--r--launcher/minecraft/Library.cpp3
-rw-r--r--launcher/minecraft/Library_test.cpp270
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp148
-rw-r--r--launcher/minecraft/MinecraftInstance.h30
-rw-r--r--launcher/minecraft/MinecraftUpdate.cpp10
-rw-r--r--launcher/minecraft/MinecraftUpdate.h2
-rw-r--r--launcher/minecraft/MojangVersionFormat_test.cpp53
-rw-r--r--launcher/minecraft/ParseUtils_test.cpp43
-rw-r--r--launcher/minecraft/World.cpp8
-rw-r--r--launcher/minecraft/World.h6
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.cpp2
-rw-r--r--launcher/minecraft/launch/DirectJavaLaunch.cpp4
-rw-r--r--launcher/minecraft/launch/LauncherPartLaunch.cpp2
-rw-r--r--launcher/minecraft/mod/Mod.cpp180
-rw-r--r--launcher/minecraft/mod/Mod.h70
-rw-r--r--launcher/minecraft/mod/ModDetails.h61
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp617
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h100
-rw-r--r--launcher/minecraft/mod/ModFolderModel_test.cpp92
-rw-r--r--launcher/minecraft/mod/Resource.cpp147
-rw-r--r--launcher/minecraft/mod/Resource.h117
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp524
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.h332
-rw-r--r--launcher/minecraft/mod/ResourcePack.cpp116
-rw-r--r--launcher/minecraft/mod/ResourcePack.h69
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.cpp187
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.h26
-rw-r--r--launcher/minecraft/mod/ShaderPackFolderModel.h10
-rw-r--r--launcher/minecraft/mod/TexturePackFolderModel.cpp22
-rw-r--r--launcher/minecraft/mod/TexturePackFolderModel.h6
-rw-r--r--launcher/minecraft/mod/tasks/BasicFolderLoadTask.h64
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.cpp140
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.h22
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp164
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h56
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp26
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.h23
-rw-r--r--launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt1
-rw-r--r--launcher/minecraft/mod/testdata/test_folder/pack.mcmeta6
-rw-r--r--launcher/minecraft/mod/testdata/test_folder/pack.nfo1
-rw-r--r--launcher/minecraft/testdata/1.9-simple.json198
-rw-r--r--launcher/minecraft/testdata/1.9.json529
-rw-r--r--launcher/minecraft/testdata/codecwav-20101023.jar1
-rw-r--r--launcher/minecraft/testdata/lib-native-arch.json46
-rw-r--r--launcher/minecraft/testdata/lib-native.json52
-rw-r--r--launcher/minecraft/testdata/lib-simple.json11
-rw-r--r--launcher/minecraft/testdata/testname-testversion-linux-32.jar1
-rw-r--r--launcher/minecraft/update/FMLLibrariesTask.cpp3
49 files changed, 2282 insertions, 2396 deletions
diff --git a/launcher/minecraft/GradleSpecifier_test.cpp b/launcher/minecraft/GradleSpecifier_test.cpp
deleted file mode 100644
index a062dfac..00000000
--- a/launcher/minecraft/GradleSpecifier_test.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-#include <QTest>
-
-#include "minecraft/GradleSpecifier.h"
-
-class GradleSpecifierTest : public QObject
-{
- Q_OBJECT
-private
-slots:
- void initTestCase()
- {
-
- }
- void cleanupTestCase()
- {
-
- }
-
- void test_Positive_data()
- {
- QTest::addColumn<QString>("through");
-
- QTest::newRow("3 parter") << "org.gradle.test.classifiers:service:1.0";
- QTest::newRow("classifier") << "org.gradle.test.classifiers:service:1.0:jdk15";
- QTest::newRow("jarextension") << "org.gradle.test.classifiers:service:1.0@jar";
- QTest::newRow("jarboth") << "org.gradle.test.classifiers:service:1.0:jdk15@jar";
- QTest::newRow("packxz") << "org.gradle.test.classifiers:service:1.0:jdk15@jar.pack.xz";
- }
- void test_Positive()
- {
- QFETCH(QString, through);
-
- QString converted = GradleSpecifier(through).serialize();
-
- QCOMPARE(converted, through);
- }
-
- void test_Path_data()
- {
- QTest::addColumn<QString>("spec");
- QTest::addColumn<QString>("expected");
-
- QTest::newRow("3 parter") << "group.id:artifact:1.0" << "group/id/artifact/1.0/artifact-1.0.jar";
- QTest::newRow("doom") << "id.software:doom:1.666:demons@wad" << "id/software/doom/1.666/doom-1.666-demons.wad";
- }
- void test_Path()
- {
- QFETCH(QString, spec);
- QFETCH(QString, expected);
-
- QString converted = GradleSpecifier(spec).toPath();
-
- QCOMPARE(converted, expected);
- }
- void test_Negative_data()
- {
- QTest::addColumn<QString>("input");
-
- QTest::newRow("too many :") << "org:gradle.test:class:::ifiers:service:1.0::";
- QTest::newRow("nonsense") << "I like turtles";
- QTest::newRow("empty string") << "";
- QTest::newRow("missing version") << "herp.derp:artifact";
- }
- void test_Negative()
- {
- QFETCH(QString, input);
-
- GradleSpecifier spec(input);
- QVERIFY(!spec.valid());
- QCOMPARE(spec.serialize(), input);
- QCOMPARE(spec.toPath(), QString());
- }
-};
-
-QTEST_GUILESS_MAIN(GradleSpecifierTest)
-
-#include "GradleSpecifier_test.moc"
diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp
index c7982705..ba7aed4b 100644
--- a/launcher/minecraft/Library.cpp
+++ b/launcher/minecraft/Library.cpp
@@ -88,6 +88,9 @@ QList<NetAction::Ptr> Library::getDownloads(
options |= Net::Download::Option::AcceptLocalFiles;
}
+ // Don't add a time limit for the libraries cache entry validity
+ options |= Net::Download::Option::MakeEternal;
+
if(sha1.size())
{
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
diff --git a/launcher/minecraft/Library_test.cpp b/launcher/minecraft/Library_test.cpp
deleted file mode 100644
index 834dd558..00000000
--- a/launcher/minecraft/Library_test.cpp
+++ /dev/null
@@ -1,270 +0,0 @@
-#include <QTest>
-
-#include "minecraft/MojangVersionFormat.h"
-#include "minecraft/OneSixVersionFormat.h"
-#include "minecraft/Library.h"
-#include "net/HttpMetaCache.h"
-#include "FileSystem.h"
-
-class LibraryTest : public QObject
-{
- Q_OBJECT
-private:
- LibraryPtr readMojangJson(const QString path)
- {
- QFile jsonFile(path);
- jsonFile.open(QIODevice::ReadOnly);
- auto data = jsonFile.readAll();
- jsonFile.close();
- ProblemContainer problems;
- return MojangVersionFormat::libraryFromJson(problems, QJsonDocument::fromJson(data).object(), path);
- }
- // get absolute path to expected storage, assuming default cache prefix
- QStringList getStorage(QString relative)
- {
- return {FS::PathCombine(cache->getBasePath("libraries"), relative)};
- }
-private
-slots:
- void initTestCase()
- {
- cache.reset(new HttpMetaCache());
- cache->addBase("libraries", QDir("libraries").absolutePath());
- dataDir = QDir(QFINDTESTDATA("testdata")).absolutePath();
- }
- void test_legacy()
- {
- Library test("test.package:testname:testversion");
- QCOMPARE(test.artifactPrefix(), QString("test.package:testname"));
- QCOMPARE(test.isNative(), false);
-
- QStringList jar, native, native32, native64;
- test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString());
- QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar"));
- QCOMPARE(native, {});
- QCOMPARE(native32, {});
- QCOMPARE(native64, {});
- }
- void test_legacy_url()
- {
- QStringList failedFiles;
- Library test("test.package:testname:testversion");
- test.setRepositoryURL("file://foo/bar");
- auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
- QCOMPARE(downloads.size(), 1);
- QCOMPARE(failedFiles, {});
- NetAction::Ptr dl = downloads[0];
- QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar"));
- }
- void test_legacy_url_local_broken()
- {
- Library test("test.package:testname:testversion");
- QCOMPARE(test.isNative(), false);
- QStringList failedFiles;
- test.setHint("local");
- auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
- QCOMPARE(downloads.size(), 0);
- QCOMPARE(failedFiles, {"testname-testversion.jar"});
- }
- void test_legacy_url_local_override()
- {
- Library test("com.paulscode:codecwav:20101023");
- QCOMPARE(test.isNative(), false);
- QStringList failedFiles;
- test.setHint("local");
- auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
- QCOMPARE(downloads.size(), 0);
- qDebug() << failedFiles;
- QCOMPARE(failedFiles.size(), 0);
-
- QStringList jar, native, native32, native64;
- test.getApplicableFiles(currentSystem, jar, native, native32, native64, QFINDTESTDATA("testdata"));
- QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()});
- QCOMPARE(native, {});
- QCOMPARE(native32, {});
- QCOMPARE(native64, {});
- }
- void test_legacy_native()
- {
- Library test("test.package:testname:testversion");
- test.m_nativeClassifiers[OpSys::Os_Linux]="linux";
- QCOMPARE(test.isNative(), true);
- test.setRepositoryURL("file://foo/bar");
- {
- QStringList jar, native, native32, native64;
- test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
- QCOMPARE(jar, {});
- QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar"));
- QCOMPARE(native32, {});
- QCOMPARE(native64, {});
- QStringList failedFiles;
- auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
- QCOMPARE(dls.size(), 1);
- QCOMPARE(failedFiles, {});
- auto dl = dls[0];
- QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar"));
- }
- }
- void test_legacy_native_arch()
- {
- Library test("test.package:testname:testversion");
- test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
- test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}";
- test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}";
- QCOMPARE(test.isNative(), true);
- test.setRepositoryURL("file://foo/bar");
- {
- QStringList jar, native, native32, native64;
- test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
- QCOMPARE(jar, {});
- QCOMPARE(native, {});
- QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar"));
- QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
- QStringList failedFiles;
- auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
- QCOMPARE(dls.size(), 2);
- QCOMPARE(failedFiles, {});
- QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar"));
- QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar"));
- }
- {
- QStringList jar, native, native32, native64;
- test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
- QCOMPARE(jar, {});
- QCOMPARE(native, {});
- QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar"));
- QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar"));
- QStringList failedFiles;
- auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString());
- QCOMPARE(dls.size(), 2);
- QCOMPARE(failedFiles, {});
- QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar"));
- QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar"));
- }
- {
- QStringList jar, native, native32, native64;
- test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
- QCOMPARE(jar, {});
- QCOMPARE(native, {});
- QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar"));
- QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar"));
- QStringList failedFiles;
- auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString());
- QCOMPARE(dls.size(), 2);
- QCOMPARE(failedFiles, {});
- QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar"));
- QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar"));
- }
- }
- void test_legacy_native_arch_local_override()
- {
- Library test("test.package:testname:testversion");
- test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
- test.setHint("local");
- QCOMPARE(test.isNative(), true);
- test.setRepositoryURL("file://foo/bar");
- {
- QStringList jar, native, native32, native64;
- test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QFINDTESTDATA("testdata"));
- QCOMPARE(jar, {});
- QCOMPARE(native, {});
- QCOMPARE(native32, {QFileInfo(QFINDTESTDATA("testdata/testname-testversion-linux-32.jar")).absoluteFilePath()});
- QCOMPARE(native64, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()});
- QStringList failedFiles;
- auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
- QCOMPARE(dls.size(), 0);
- QCOMPARE(failedFiles, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()});
- }
- }
- void test_onenine()
- {
- auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json"));
- {
- QStringList jar, native, native32, native64;
- test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
- QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
- QCOMPARE(native, {});
- QCOMPARE(native32, {});
- QCOMPARE(native64, {});
- }
- {
- QStringList failedFiles;
- auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString());
- QCOMPARE(dls.size(), 1);
- QCOMPARE(failedFiles, {});
- QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
- }
- test->setHint("local");
- {
- QStringList jar, native, native32, native64;
- test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata"));
- QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()});
- QCOMPARE(native, {});
- QCOMPARE(native32, {});
- QCOMPARE(native64, {});
- }
- {
- QStringList failedFiles;
- auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
- QCOMPARE(dls.size(), 0);
- QCOMPARE(failedFiles, {});
- }
- }
- void test_onenine_local_override()
- {
- auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json"));
- test->setHint("local");
- {
- QStringList jar, native, native32, native64;
- test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata"));
- QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()});
- QCOMPARE(native, {});
- QCOMPARE(native32, {});
- QCOMPARE(native64, {});
- }
- {
- QStringList failedFiles;
- auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
- QCOMPARE(dls.size(), 0);
- QCOMPARE(failedFiles, {});
- }
- }
- void test_onenine_native()
- {
- auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native.json"));
- QStringList jar, native, native32, native64;
- test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
- QCOMPARE(jar, QStringList());
- QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
- QCOMPARE(native32, {});
- QCOMPARE(native64, {});
- QStringList failedFiles;
- auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString());
- QCOMPARE(dls.size(), 1);
- QCOMPARE(failedFiles, {});
- QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
- }
- void test_onenine_native_arch()
- {
- auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native-arch.json"));
- QStringList jar, native, native32, native64;
- test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
- QCOMPARE(jar, {});
- QCOMPARE(native, {});
- QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
- QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
- QStringList failedFiles;
- auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString());
- QCOMPARE(dls.size(), 2);
- QCOMPARE(failedFiles, {});
- QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
- QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
- }
-private:
- std::unique_ptr<HttpMetaCache> cache;
- QString dataDir;
-};
-
-QTEST_GUILESS_MAIN(LibraryTest)
-
-#include "Library_test.moc"
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 5a6f8de0..9478b1b8 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -76,6 +76,7 @@
#include "mod/ModFolderModel.h"
#include "mod/ResourcePackFolderModel.h"
+#include "mod/ShaderPackFolderModel.h"
#include "mod/TexturePackFolderModel.h"
#include "WorldList.h"
@@ -115,6 +116,19 @@ private:
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: BaseInstance(globalSettings, settings, rootDir)
{
+ m_components.reset(new PackProfile(this));
+}
+
+void MinecraftInstance::saveNow()
+{
+ m_components->saveNow();
+}
+
+void MinecraftInstance::loadSpecificSettings()
+{
+ if (isSpecificSettingsLoaded())
+ return;
+
// Java Settings
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
@@ -124,64 +138,58 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
- m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation);
- m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs);
- m_settings->registerOverride(globalSettings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
-
- // special!
- m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation);
- m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation);
- m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation);
-
- // Window Size
- auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
- m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting);
- m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting);
- m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting);
-
- // Memory
- auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
- m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting);
- m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting);
- m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting);
-
- // Minecraft launch method
- auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
- m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
-
- // Native library workarounds
- auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
- m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
- m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
-
- // Peformance related options
- auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
- m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride);
- m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride);
- m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride);
-
- // Game time
- auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
- m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
- m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride);
+ if (auto global_settings = globalSettings()) {
+ m_settings->registerOverride(global_settings->getSetting("JavaPath"), javaOrLocation);
+ m_settings->registerOverride(global_settings->getSetting("JvmArgs"), javaOrArgs);
+ m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
+
+ // special!
+ m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation);
+ m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
+ m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
+
+ // Window Size
+ auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
+ m_settings->registerOverride(global_settings->getSetting("LaunchMaximized"), windowSetting);
+ m_settings->registerOverride(global_settings->getSetting("MinecraftWinWidth"), windowSetting);
+ m_settings->registerOverride(global_settings->getSetting("MinecraftWinHeight"), windowSetting);
+
+ // Memory
+ auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
+ m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting);
+ m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
+ m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
+
+ // Minecraft launch method
+ auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
+ m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride);
+
+ // Native library workarounds
+ auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
+ m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
+ m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
+
+ // Peformance related options
+ auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
+ m_settings->registerOverride(global_settings->getSetting("EnableFeralGamemode"), performanceOverride);
+ m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride);
+ m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride);
+
+ // Miscellaneous
+ auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
+ m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
+ m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
+
+ m_settings->set("InstanceType", "OneSix");
+ }
// Join server on launch, this does not have a global override
m_settings->registerSetting("JoinServerOnLaunch", false);
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
- // Miscellaneous
- auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
- m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
- m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
-
- m_settings->set("InstanceType", "OneSix");
+ qDebug() << "Instance-type specific settings were loaded!";
- m_components.reset(new PackProfile(this));
-}
-
-void MinecraftInstance::saveNow()
-{
- m_components->saveNow();
+ setSpecificSettingsLoaded(true);
}
QString MinecraftInstance::typeName() const
@@ -308,7 +316,7 @@ QDir MinecraftInstance::versionsPath() const
return QDir::current().absoluteFilePath("versions");
}
-QStringList MinecraftInstance::getClassPath() const
+QStringList MinecraftInstance::getClassPath()
{
QStringList jars, nativeJars;
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
@@ -323,7 +331,7 @@ QString MinecraftInstance::getMainClass() const
return profile->getMainClass();
}
-QStringList MinecraftInstance::getNativeJars() const
+QStringList MinecraftInstance::getNativeJars()
{
QStringList jars, nativeJars;
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
@@ -332,7 +340,7 @@ QStringList MinecraftInstance::getNativeJars() const
return nativeJars;
}
-QStringList MinecraftInstance::extraArguments() const
+QStringList MinecraftInstance::extraArguments()
{
auto list = BaseInstance::extraArguments();
auto version = getPackProfile();
@@ -358,7 +366,7 @@ QStringList MinecraftInstance::extraArguments() const
return list;
}
-QStringList MinecraftInstance::javaArguments() const
+QStringList MinecraftInstance::javaArguments()
{
QStringList args;
@@ -415,7 +423,7 @@ QStringList MinecraftInstance::javaArguments() const
return args;
}
-QMap<QString, QString> MinecraftInstance::getVariables() const
+QMap<QString, QString> MinecraftInstance::getVariables()
{
QMap<QString, QString> out;
out.insert("INST_NAME", name());
@@ -447,13 +455,11 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
QProcessEnvironment env = createEnvironment();
#ifdef Q_OS_LINUX
- if (settings()->get("EnableMangoHud").toBool())
+ if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud)
{
auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so";
- auto lib_path = env.value("LD_LIBRARY_PATH", "") + ":/usr/local/$LIB/mangohud/:/usr/$LIB/mangohud/";
env.insert("LD_PRELOAD", preload);
- env.insert("LD_LIBRARY_PATH", lib_path);
env.insert("MANGOHUD", "1");
}
@@ -707,7 +713,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
});
for(auto mod: modList)
{
- if(mod->type() == Mod::MOD_FOLDER)
+ if(mod->type() == ResourceType::FOLDER)
{
out << u8" [🖿] " + mod->fileinfo().completeBaseName() + " (folder)";
continue;
@@ -943,9 +949,9 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(new CreateGameFolders(pptr));
}
- if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool())
+ if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool())
{
- QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString();
+ QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
}
@@ -1053,10 +1059,10 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
QString MinecraftInstance::launchMethod()
{
- return m_settings->get("MCLaunchMethod").toString();
+ return settings()->get("MCLaunchMethod").toString();
}
-JavaVersion MinecraftInstance::getJavaVersion() const
+JavaVersion MinecraftInstance::getJavaVersion()
{
return JavaVersion(settings()->get("JavaVersion").toString());
}
@@ -1085,18 +1091,18 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
return m_core_mod_list;
}
-std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
+std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() const
{
if (!m_resource_pack_list)
{
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
- m_resource_pack_list->disableInteraction(isRunning());
- connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction);
+ m_resource_pack_list->enableInteraction(!isRunning());
+ connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ResourcePackFolderModel::disableInteraction);
}
return m_resource_pack_list;
}
-std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
+std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() const
{
if (!m_texture_pack_list)
{
@@ -1107,11 +1113,11 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
return m_texture_pack_list;
}
-std::shared_ptr<ModFolderModel> MinecraftInstance::shaderPackList() const
+std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() const
{
if (!m_shader_pack_list)
{
- m_shader_pack_list.reset(new ResourcePackFolderModel(shaderPacksDir()));
+ m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir()));
m_shader_pack_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_shader_pack_list.get(), &ModFolderModel::disableInteraction);
}
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index 8e1c67f2..d62ac655 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -7,6 +7,10 @@
#include "minecraft/launch/MinecraftServerTarget.h"
class ModFolderModel;
+class ResourceFolderModel;
+class ResourcePackFolderModel;
+class ShaderPackFolderModel;
+class TexturePackFolderModel;
class WorldList;
class GameOptions;
class LaunchStep;
@@ -20,6 +24,8 @@ public:
virtual ~MinecraftInstance() {};
virtual void saveNow() override;
+ void loadSpecificSettings() override;
+
// FIXME: remove
QString typeName() const override;
// FIXME: remove
@@ -70,24 +76,24 @@ public:
////// Mod Lists //////
std::shared_ptr<ModFolderModel> loaderModList() const;
std::shared_ptr<ModFolderModel> coreModList() const;
- std::shared_ptr<ModFolderModel> resourcePackList() const;
- std::shared_ptr<ModFolderModel> texturePackList() const;
- std::shared_ptr<ModFolderModel> shaderPackList() const;
+ std::shared_ptr<ResourcePackFolderModel> resourcePackList() const;
+ std::shared_ptr<TexturePackFolderModel> texturePackList() const;
+ std::shared_ptr<ShaderPackFolderModel> shaderPackList() const;
std::shared_ptr<WorldList> worldList() const;
std::shared_ptr<GameOptions> gameOptionsModel() const;
////// Launch stuff //////
Task::Ptr createUpdateTask(Net::Mode mode) override;
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override;
- QStringList extraArguments() const override;
+ QStringList extraArguments() override;
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
QList<Mod*> getJarMods() const;
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
/// get arguments passed to java
- QStringList javaArguments() const;
+ QStringList javaArguments();
/// get variables for launch command variable substitution/environment
- QMap<QString, QString> getVariables() const override;
+ QMap<QString, QString> getVariables() override;
/// create an environment for launching processes
QProcessEnvironment createEnvironment() override;
@@ -103,16 +109,16 @@ public:
QString getStatusbarDescription() override;
// FIXME: remove
- virtual QStringList getClassPath() const;
+ virtual QStringList getClassPath();
// FIXME: remove
- virtual QStringList getNativeJars() const;
+ virtual QStringList getNativeJars();
// FIXME: remove
virtual QString getMainClass() const;
// FIXME: remove
virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const;
- virtual JavaVersion getJavaVersion() const;
+ virtual JavaVersion getJavaVersion();
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
@@ -123,9 +129,9 @@ protected: // data
std::shared_ptr<PackProfile> m_components;
mutable std::shared_ptr<ModFolderModel> m_loader_mod_list;
mutable std::shared_ptr<ModFolderModel> m_core_mod_list;
- mutable std::shared_ptr<ModFolderModel> m_resource_pack_list;
- mutable std::shared_ptr<ModFolderModel> m_shader_pack_list;
- mutable std::shared_ptr<ModFolderModel> m_texture_pack_list;
+ mutable std::shared_ptr<ResourcePackFolderModel> m_resource_pack_list;
+ mutable std::shared_ptr<ShaderPackFolderModel> m_shader_pack_list;
+ mutable std::shared_ptr<TexturePackFolderModel> m_texture_pack_list;
mutable std::shared_ptr<WorldList> m_world_list;
mutable std::shared_ptr<GameOptions> m_game_options;
};
diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp
index 0ce0c347..3a3aa864 100644
--- a/launcher/minecraft/MinecraftUpdate.cpp
+++ b/launcher/minecraft/MinecraftUpdate.cpp
@@ -43,7 +43,7 @@ void MinecraftUpdate::executeTask()
m_tasks.clear();
// create folders
{
- m_tasks.append(std::make_shared<FoldersTask>(m_inst));
+ m_tasks.append(new FoldersTask(m_inst));
}
// add metadata update task if necessary
@@ -53,23 +53,23 @@ void MinecraftUpdate::executeTask()
auto task = components->getCurrentTask();
if(task)
{
- m_tasks.append(task.unwrap());
+ m_tasks.append(task);
}
}
// libraries download
{
- m_tasks.append(std::make_shared<LibrariesTask>(m_inst));
+ m_tasks.append(new LibrariesTask(m_inst));
}
// FML libraries download and copy into the instance
{
- m_tasks.append(std::make_shared<FMLLibrariesTask>(m_inst));
+ m_tasks.append(new FMLLibrariesTask(m_inst));
}
// assets update
{
- m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst));
+ m_tasks.append(new AssetUpdateTask(m_inst));
}
if(!m_preFailure.isEmpty())
diff --git a/launcher/minecraft/MinecraftUpdate.h b/launcher/minecraft/MinecraftUpdate.h
index acf2eb86..c9cf8624 100644
--- a/launcher/minecraft/MinecraftUpdate.h
+++ b/launcher/minecraft/MinecraftUpdate.h
@@ -50,7 +50,7 @@ private:
private:
MinecraftInstance *m_inst = nullptr;
- QList<std::shared_ptr<Task>> m_tasks;
+ QList<Task::Ptr> m_tasks;
QString m_preFailure;
int m_currentTask = -1;
bool m_abort = false;
diff --git a/launcher/minecraft/MojangVersionFormat_test.cpp b/launcher/minecraft/MojangVersionFormat_test.cpp
deleted file mode 100644
index 71df784b..00000000
--- a/launcher/minecraft/MojangVersionFormat_test.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include <QTest>
-#include <QDebug>
-
-#include "minecraft/MojangVersionFormat.h"
-
-class MojangVersionFormatTest : public QObject
-{
- Q_OBJECT
-
- static QJsonDocument readJson(const QString path)
- {
- QFile jsonFile(path);
- jsonFile.open(QIODevice::ReadOnly);
- auto data = jsonFile.readAll();
- jsonFile.close();
- return QJsonDocument::fromJson(data);
- }
- static void writeJson(const char *file, QJsonDocument doc)
- {
- QFile jsonFile(file);
- jsonFile.open(QIODevice::WriteOnly | QIODevice::Text);
- auto data = doc.toJson(QJsonDocument::Indented);
- qDebug() << QString::fromUtf8(data);
- jsonFile.write(data);
- jsonFile.close();
- }
-
-private
-slots:
- void test_Through_Simple()
- {
- QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9-simple.json"));
- auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json");
- auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
- writeJson("1.9-simple-passthorugh.json", doc2);
-
- QCOMPARE(doc.toJson(), doc2.toJson());
- }
-
- void test_Through()
- {
- QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9.json"));
- auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json");
- auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
- writeJson("1.9-passthorugh.json", doc2);
- QCOMPARE(doc.toJson(), doc2.toJson());
- }
-};
-
-QTEST_GUILESS_MAIN(MojangVersionFormatTest)
-
-#include "MojangVersionFormat_test.moc"
-
diff --git a/launcher/minecraft/ParseUtils_test.cpp b/launcher/minecraft/ParseUtils_test.cpp
deleted file mode 100644
index 7721a46d..00000000
--- a/launcher/minecraft/ParseUtils_test.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#include <QTest>
-
-#include "minecraft/ParseUtils.h"
-
-class ParseUtilsTest : public QObject
-{
- Q_OBJECT
-private
-slots:
- void test_Through_data()
- {
- QTest::addColumn<QString>("timestamp");
- const char * timestamps[] =
- {
- "2016-02-29T13:49:54+01:00",
- "2016-02-26T15:21:11+00:01",
- "2016-02-24T15:52:36+01:13",
- "2016-02-18T17:41:00+00:00",
- "2016-02-17T15:23:19+00:00",
- "2016-02-16T15:22:39+09:22",
- "2016-02-10T15:06:41+00:00",
- "2016-02-04T15:28:02-05:33"
- };
- for(unsigned i = 0; i < (sizeof(timestamps) / sizeof(const char *)); i++)
- {
- QTest::newRow(timestamps[i]) << QString(timestamps[i]);
- }
- }
- void test_Through()
- {
- QFETCH(QString, timestamp);
-
- auto time_parsed = timeFromS3Time(timestamp);
- auto time_serialized = timeToS3Time(time_parsed);
-
- QCOMPARE(time_serialized, timestamp);
- }
-
-};
-
-QTEST_GUILESS_MAIN(ParseUtilsTest)
-
-#include "ParseUtils_test.moc"
diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp
index dfcb43d8..90fcf337 100644
--- a/launcher/minecraft/World.cpp
+++ b/launcher/minecraft/World.cpp
@@ -53,12 +53,12 @@
#include <QCoreApplication>
-#include <nonstd/optional>
+#include <optional>
-using nonstd::optional;
-using nonstd::nullopt;
+using std::optional;
+using std::nullopt;
-GameType::GameType(nonstd::optional<int> original):
+GameType::GameType(std::optional<int> original):
original(original)
{
if(!original) {
diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h
index 0f587620..8327253a 100644
--- a/launcher/minecraft/World.h
+++ b/launcher/minecraft/World.h
@@ -16,11 +16,11 @@
#pragma once
#include <QFileInfo>
#include <QDateTime>
-#include <nonstd/optional>
+#include <optional>
struct GameType {
GameType() = default;
- GameType (nonstd::optional<int> original);
+ GameType (std::optional<int> original);
QString toTranslatedString() const;
QString toLogString() const;
@@ -33,7 +33,7 @@ struct GameType {
Adventure,
Spectator
} type = Unknown;
- nonstd::optional<int> original;
+ std::optional<int> original;
};
class World
diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp
index a5c6f542..73d570f1 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -238,7 +238,7 @@ void MinecraftAccount::authFailed(QString reason)
}
bool MinecraftAccount::isActive() const {
- return m_currentTask;
+ return !m_currentTask.isNull();
}
bool MinecraftAccount::shouldRefresh() const {
diff --git a/launcher/minecraft/launch/DirectJavaLaunch.cpp b/launcher/minecraft/launch/DirectJavaLaunch.cpp
index 152485b3..ca55cd2e 100644
--- a/launcher/minecraft/launch/DirectJavaLaunch.cpp
+++ b/launcher/minecraft/launch/DirectJavaLaunch.cpp
@@ -21,6 +21,8 @@
#include <FileSystem.h>
#include <Commandline.h>
+#include "Application.h"
+
#ifdef Q_OS_LINUX
#include "gamemode_client.h"
#endif
@@ -86,7 +88,7 @@ void DirectJavaLaunch::executeTask()
}
#ifdef Q_OS_LINUX
- if (instance->settings()->get("EnableFeralGamemode").toBool())
+ if (instance->settings()->get("EnableFeralGamemode").toBool() && APPLICATION->capabilities() & Application::SupportsGameMode)
{
auto pid = m_process.processId();
if (pid)
diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp
index 63e4d90f..ce477ad7 100644
--- a/launcher/minecraft/launch/LauncherPartLaunch.cpp
+++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp
@@ -181,7 +181,7 @@ void LauncherPartLaunch::executeTask()
}
#ifdef Q_OS_LINUX
- if (instance->settings()->get("EnableFeralGamemode").toBool())
+ if (instance->settings()->get("EnableFeralGamemode").toBool() && APPLICATION->capabilities() & Application::SupportsGameMode)
{
auto pid = m_process.processId();
if (pid)
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index 588d76e3..39023f69 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -36,130 +36,77 @@
#include "Mod.h"
+#include <QDebug>
#include <QDir>
#include <QString>
+#include <QRegularExpression>
-#include <FileSystem.h>
-#include <QDebug>
-
-#include "Application.h"
#include "MetadataHandler.h"
+#include "Version.h"
-namespace {
-
-ModDetails invalidDetails;
-
-}
-
-Mod::Mod(const QFileInfo& file)
+Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
{
- repath(file);
- m_changedDateTime = file.lastModified();
+ m_enabled = (file.suffix() != "disabled");
}
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)
- : m_file(mods_dir.absoluteFilePath(metadata.filename))
- , m_internal_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_enabled = true;
- m_changedDateTime = m_file.lastModified();
-
- m_temp_metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
+ : Mod(mods_dir.absoluteFilePath(metadata.filename))
+{
+ m_name = metadata.name;
+ m_local_details.metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
}
-void Mod::repath(const QFileInfo& file)
+void Mod::setStatus(ModStatus status)
{
- m_file = file;
- QString name_base = file.fileName();
-
- m_type = Mod::MOD_UNKNOWN;
-
- m_internal_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;
- }
+ m_local_details.status = status;
}
-
-auto Mod::enable(bool value) -> bool
+void Mod::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
{
- if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
- return false;
-
- if (m_enabled == value)
- return false;
-
- QString path = m_file.absoluteFilePath();
- QFile file(path);
- if (value) {
- if (!path.endsWith(".disabled"))
- return false;
- path.chop(9);
-
- if (!file.rename(path))
- return false;
- } else {
- path += ".disabled";
-
- if (!file.rename(path))
- return false;
- }
-
if (status() == ModStatus::NoMetadata)
- repath(QFileInfo(path));
+ setStatus(ModStatus::Installed);
- m_enabled = value;
- return true;
+ m_local_details.metadata = metadata;
}
-void Mod::setStatus(ModStatus status)
+std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
{
- if (m_localDetails) {
- m_localDetails->status = status;
- } else {
- m_temp_status = status;
+ auto cast_other = dynamic_cast<Mod const*>(&other);
+ if (!cast_other)
+ return Resource::compare(other, type);
+
+ switch (type) {
+ default:
+ case SortType::ENABLED:
+ case SortType::NAME:
+ case SortType::DATE: {
+ auto res = Resource::compare(other, type);
+ if (res.first != 0)
+ return res;
+ }
+ case SortType::VERSION: {
+ auto this_ver = Version(version());
+ auto other_ver = Version(cast_other->version());
+ if (this_ver > other_ver)
+ return { 1, type == SortType::VERSION };
+ if (this_ver < other_ver)
+ return { -1, type == SortType::VERSION };
+ }
}
+ return { 0, false };
}
-void Mod::setMetadata(const Metadata::ModStruct& metadata)
+
+bool Mod::applyFilter(QRegularExpression filter) const
{
- if (status() == ModStatus::NoMetadata)
- setStatus(ModStatus::Installed);
+ if (filter.match(description()).hasMatch())
+ return true;
- if (m_localDetails) {
- m_localDetails->metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
- } else {
- m_temp_metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
+ for (auto& author : authors()) {
+ if (filter.match(author).hasMatch()) {
+ return true;
+ }
}
+
+ return Resource::applyFilter(filter);
}
auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
@@ -175,13 +122,12 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
}
}
- m_type = MOD_UNKNOWN;
- return FS::deletePath(m_file.filePath());
+ return Resource::destroy();
}
auto Mod::details() const -> const ModDetails&
{
- return m_localDetails ? *m_localDetails : invalidDetails;
+ return m_local_details;
}
auto Mod::name() const -> QString
@@ -218,35 +164,29 @@ auto Mod::authors() const -> QStringList
auto Mod::status() const -> ModStatus
{
- if (!m_localDetails)
- return m_temp_status;
return details().status;
}
auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
{
- if (m_localDetails)
- return m_localDetails->metadata;
- return m_temp_metadata;
+ return m_local_details.metadata;
}
auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
{
- if (m_localDetails)
- return m_localDetails->metadata;
- return m_temp_metadata;
+ return m_local_details.metadata;
}
-void Mod::finishResolvingWithDetails(std::shared_ptr<ModDetails> details)
+void Mod::finishResolvingWithDetails(ModDetails&& details)
{
- m_resolving = false;
- m_resolved = true;
- m_localDetails = details;
+ m_is_resolving = false;
+ m_is_resolved = true;
- setStatus(m_temp_status);
+ std::shared_ptr<Metadata::ModStruct> metadata = details.metadata;
+ if (details.status == ModStatus::Unknown)
+ details.status = m_local_details.status;
- if (m_localDetails && m_temp_metadata && m_temp_metadata->isValid()) {
- setMetadata(*m_temp_metadata);
- m_temp_metadata.reset();
- }
+ m_local_details = std::move(details);
+ if (metadata)
+ setMetadata(std::move(metadata));
}
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index 7a13e44b..f336bec4 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -39,38 +39,23 @@
#include <QFileInfo>
#include <QList>
-#include "QObjectPtr.h"
+#include "Resource.h"
#include "ModDetails.h"
-class Mod : public QObject
+class Mod : public Resource
{
Q_OBJECT
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
- };
-
using Ptr = shared_qobject_ptr<Mod>;
+ using WeakPtr = QPointer<Mod>;
Mod() = default;
Mod(const QFileInfo &file);
- explicit Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
-
- auto fileinfo() const -> QFileInfo { return m_file; }
- auto dateTimeChanged() const -> QDateTime { return m_changedDateTime; }
- auto internal_id() const -> QString { return m_internal_id; }
- auto type() const -> ModType { return m_type; }
- auto enabled() const -> bool { return m_enabled; }
-
- auto valid() const -> bool { return m_type != MOD_UNKNOWN; }
+ Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
+ Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
auto details() const -> const ModDetails&;
- auto name() const -> QString;
+ auto name() const -> QString override;
auto version() const -> QString;
auto homeurl() const -> QString;
auto description() const -> QString;
@@ -81,46 +66,17 @@ public:
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
void setStatus(ModStatus status);
- void setMetadata(const Metadata::ModStruct& metadata);
+ void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
+ void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
- auto enable(bool value) -> bool;
+ [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
+ [[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
- // delete all the files of this mod
+ // Delete all the files of this mod
auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool;
- // change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
- void repath(const QFileInfo &file);
-
- auto shouldResolve() const -> bool { return !m_resolving && !m_resolved; }
- auto isResolving() const -> bool { return m_resolving; }
- auto resolutionTicket() const -> int { return m_resolutionTicket; }
-
- void setResolving(bool resolving, int resolutionTicket) {
- m_resolving = resolving;
- m_resolutionTicket = resolutionTicket;
- }
- void finishResolvingWithDetails(std::shared_ptr<ModDetails> details);
+ void finishResolvingWithDetails(ModDetails&& details);
protected:
- QFileInfo m_file;
- QDateTime m_changedDateTime;
-
- QString m_internal_id;
- /* Name as reported via the file name */
- QString m_name;
- ModType m_type = MOD_UNKNOWN;
-
- /* If the mod has metadata, this will be filled in the constructor, and passed to
- * the ModDetails when calling finishResolvingWithDetails */
- std::shared_ptr<Metadata::ModStruct> m_temp_metadata;
-
- /* Set the mod status while it doesn't have local details just yet */
- ModStatus m_temp_status = ModStatus::NoMetadata;
-
- std::shared_ptr<ModDetails> m_localDetails;
-
- bool m_enabled = true;
- bool m_resolving = false;
- bool m_resolved = false;
- int m_resolutionTicket = 0;
+ ModDetails m_local_details;
};
diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h
index 3e0a7ab0..dd84b0a3 100644
--- a/launcher/minecraft/mod/ModDetails.h
+++ b/launcher/minecraft/mod/ModDetails.h
@@ -46,34 +46,77 @@ enum class ModStatus {
Installed, // Both JAR and Metadata are present
NotInstalled, // Only the Metadata is present
NoMetadata, // Only the JAR is present
+ Unknown, // Default status
};
struct ModDetails
{
/* Mod ID as defined in the ModLoader-specific metadata */
- QString mod_id;
+ QString mod_id = {};
/* Human-readable name */
- QString name;
+ QString name = {};
/* Human-readable mod version */
- QString version;
+ QString version = {};
/* Human-readable minecraft version */
- QString mcversion;
+ QString mcversion = {};
/* URL for mod's home page */
- QString homeurl;
+ QString homeurl = {};
/* Human-readable description */
- QString description;
+ QString description = {};
/* List of the author's names */
- QStringList authors;
+ QStringList authors = {};
/* Installation status of the mod */
- ModStatus status;
+ ModStatus status = ModStatus::Unknown;
/* Metadata information, if any */
- std::shared_ptr<Metadata::ModStruct> metadata;
+ std::shared_ptr<Metadata::ModStruct> metadata = nullptr;
+
+ ModDetails() = default;
+
+ /** Metadata should be handled manually to properly set the mod status. */
+ ModDetails(ModDetails& other)
+ : mod_id(other.mod_id)
+ , name(other.name)
+ , version(other.version)
+ , mcversion(other.mcversion)
+ , homeurl(other.homeurl)
+ , description(other.description)
+ , authors(other.authors)
+ , status(other.status)
+ {}
+
+ ModDetails& operator=(ModDetails& other)
+ {
+ this->mod_id = other.mod_id;
+ this->name = other.name;
+ this->version = other.version;
+ this->mcversion = other.mcversion;
+ this->homeurl = other.homeurl;
+ this->description = other.description;
+ this->authors = other.authors;
+ this->status = other.status;
+
+ return *this;
+ }
+
+ ModDetails& operator=(ModDetails&& other)
+ {
+ this->mod_id = other.mod_id;
+ this->name = other.name;
+ this->version = other.version;
+ this->mcversion = other.mcversion;
+ this->homeurl = other.homeurl;
+ this->description = other.description;
+ this->authors = other.authors;
+ this->status = other.status;
+
+ return *this;
+ }
};
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 112d219e..13fed1c9 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -49,428 +49,53 @@
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
-ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : QAbstractListModel(), m_dir(dir), m_is_indexed(is_indexed)
+ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
- m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
- 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();
-
- // Watch the mods folder
- 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();
- }
-
- // Watch the mods index folder
- is_watching = m_watcher->addPath(indexDir().absolutePath());
- if (is_watching) {
- qDebug() << "Started watching " << indexDir().absolutePath();
- } else {
- qDebug() << "Failed to start watching " << indexDir().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();
- }
-
- is_watching = !m_watcher->removePath(indexDir().absolutePath());
- if (!is_watching) {
- qDebug() << "Stopped watching " << indexDir().absolutePath();
- } else {
- qDebug() << "Failed to stop watching " << indexDir().absolutePath();
- }
-}
-
-bool ModFolderModel::update()
-{
- if (!isValid()) {
- return false;
- }
- if(m_update) {
- scheduled_update = true;
- return true;
- }
-
- auto index_dir = indexDir();
- auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed);
-
- m_update = task->result();
-
- QThreadPool *threadPool = QThreadPool::globalInstance();
- connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
-
- threadPool->start(task);
- return true;
-}
-
-void ModFolderModel::finishUpdate()
-{
-#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
- auto currentList = modsIndex.keys();
- QSet<QString> currentSet(currentList.begin(), currentList.end());
- auto & newMods = m_update->mods;
- auto newList = newMods.keys();
- QSet<QString> newSet(newList.begin(), newList.end());
-#else
- QSet<QString> currentSet = modsIndex.keys().toSet();
- auto& newMods = m_update->mods;
- QSet<QString> newSet = newMods.keys().toSet();
-#endif
-
- // 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());
- }
-
- mods[row] = 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);
-
- // When you have a Qt build with assertions turned on, proceeding here will abort the application
- if (added.size() > 0) {
- 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->internal_id()] = idx;
- idx++;
- }
- }
-
- m_update.reset();
-
- emit updateFinished();
-
- if(scheduled_update) {
- scheduled_update = false;
- update();
- }
-}
-
-void ModFolderModel::resolveMod(Mod::Ptr m)
-{
- if(!m->shouldResolve()) {
- return;
- }
-
- auto task = new LocalModParseTask(nextResolutionTicket, m->type(), m->fileinfo());
- auto result = task->result();
- result->id = m->internal_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();
-}
-
-auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList<Mod::Ptr>
-{
- QList<Mod::Ptr> selected_mods;
- for (auto i : indexes) {
- if(i.column() != 0)
- continue;
-
- selected_mods.push_back(mods[i.row()]);
- }
- return selected_mods;
-}
-
-// 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);
- QFileInfo newpathInfo(newpath);
- installedMod.repath(newpathInfo);
- 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;
- }
- QFileInfo newpathInfo(newpath);
- installedMod.repath(newpathInfo);
- update();
- return true;
- }
- return false;
-}
-
-bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
-{
-
- for(auto mod : allMods()){
- if(mod->fileinfo().fileName() == filename){
- auto index_dir = indexDir();
- mod->destroy(index_dir, preserve_metadata);
- 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)
- {
- if(i.column() != 0) {
- continue;
- }
- auto m = mods[i.row()];
- auto index_dir = indexDir();
- m->destroy(index_dir);
- }
- return true;
-}
-
-int ModFolderModel::columnCount(const QModelIndex &parent) const
-{
- return NUM_COLUMNS;
+ m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE };
}
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
{
- if (!index.isValid())
- return QVariant();
+ if (!validateIndex(index))
+ return {};
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();
+ return m_resources[row]->name();
case VersionColumn: {
- switch(mods[row]->type()) {
- case Mod::MOD_FOLDER:
+ switch(m_resources[row]->type()) {
+ case ResourceType::FOLDER:
return tr("Folder");
- case Mod::MOD_SINGLEFILE:
+ case ResourceType::SINGLEFILE:
return tr("File");
default:
break;
}
- return mods[row]->version();
+ return at(row)->version();
}
case DateColumn:
- return mods[row]->dateTimeChanged();
+ return m_resources[row]->dateTimeChanged();
default:
return QVariant();
}
case Qt::ToolTipRole:
- return mods[row]->internal_id();
+ return m_resources[row]->internal_id();
case Qt::CheckStateRole:
switch (column)
{
case ActiveColumn:
- return mods[row]->enabled() ? Qt::Checked : Qt::Unchecked;
+ return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
@@ -479,61 +104,6 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
}
}
-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->internal_id();
- if(!mod->enable(!mod->enabled())) {
- return false;
- }
- auto newId = mod->internal_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)
@@ -573,65 +143,142 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
return QVariant();
}
-Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const
+int ModFolderModel::columnCount(const QModelIndex &parent) 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;
+ return NUM_COLUMNS;
}
-Qt::DropActions ModFolderModel::supportedDropActions() const
+Task* ModFolderModel::createUpdateTask()
{
- // copy from outside, move from within and other mod lists
- return Qt::CopyAction | Qt::MoveAction;
+ auto index_dir = indexDir();
+ auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
+ m_first_folder_load = false;
+ return task;
}
-QStringList ModFolderModel::mimeTypes() const
+Task* ModFolderModel::createParseTask(Resource& resource)
{
- QStringList types;
- types << "text/uri-list";
- return types;
+ return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
}
-bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
+bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
{
- if (action == Qt::IgnoreAction)
- {
- return true;
+ for(auto mod : allMods()){
+ if(mod->fileinfo().fileName() == filename){
+ auto index_dir = indexDir();
+ mod->destroy(index_dir, preserve_metadata);
+
+ update();
+
+ return true;
+ }
}
- // check if the action is supported
- if (!data || !(action & supportedDropActions()))
- {
+ return false;
+}
+
+bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
+{
+ if(!m_can_interact) {
return false;
}
- // files dropped from outside?
- if (data->hasUrls())
+ if(indexes.isEmpty())
+ return true;
+
+ for (auto i: indexes)
{
- 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());
+ if(i.column() != 0) {
+ continue;
}
- return true;
+ auto m = at(i.row());
+ auto index_dir = indexDir();
+ m->destroy(index_dir);
}
- return false;
+
+ update();
+
+ return true;
+}
+
+bool ModFolderModel::isValid()
+{
+ return m_dir.exists() && m_dir.isReadable();
+}
+
+bool ModFolderModel::startWatching()
+{
+ // Remove orphaned metadata next time
+ m_first_folder_load = true;
+ return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
+}
+
+bool ModFolderModel::stopWatching()
+{
+ return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
+}
+
+auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList<Mod*>
+{
+ QList<Mod*> selected_resources;
+ for (auto i : indexes) {
+ if(i.column() != 0)
+ continue;
+
+ selected_resources.push_back(at(i.row()));
+ }
+ return selected_resources;
+}
+
+auto ModFolderModel::allMods() -> QList<Mod*>
+{
+ QList<Mod*> mods;
+
+ for (auto& res : m_resources) {
+ mods.append(static_cast<Mod*>(res.get()));
+ }
+
+ return mods;
+}
+
+void ModFolderModel::onUpdateSucceeded()
+{
+ auto update_results = static_cast<ModFolderLoadTask*>(m_current_update_task.get())->result();
+
+ auto& new_mods = update_results->mods;
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ auto current_list = m_resources_index.keys();
+ QSet<QString> current_set(current_list.begin(), current_list.end());
+
+ auto new_list = new_mods.keys();
+ QSet<QString> new_set(new_list.begin(), new_list.end());
+#else
+ QSet<QString> current_set(m_resources_index.keys().toSet());
+ QSet<QString> new_set(new_mods.keys().toSet());
+#endif
+
+ applyUpdates(current_set, new_set, new_mods);
+}
+
+void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
+{
+ auto iter = m_active_parse_tasks.constFind(ticket);
+ if (iter == m_active_parse_tasks.constEnd())
+ return;
+
+ int row = m_resources_index[mod_id];
+
+ auto parse_task = *iter;
+ auto cast_task = static_cast<LocalModParseTask*>(parse_task.get());
+
+ Q_ASSERT(cast_task->token() == ticket);
+
+ auto resource = find(mod_id);
+
+ auto result = cast_task->result();
+ if (result && resource)
+ resource->finishResolvingWithDetails(std::move(result->details));
+
+ emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index a7d3ece0..93980319 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -44,6 +44,7 @@
#include <QAbstractListModel>
#include "Mod.h"
+#include "ResourceFolderModel.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
@@ -56,7 +57,7 @@ class QFileSystemWatcher;
* A legacy mod list.
* Backed by a folder.
*/
-class ModFolderModel : public QAbstractListModel
+class ModFolderModel : public ResourceFolderModel
{
Q_OBJECT
public:
@@ -75,105 +76,38 @@ public:
};
ModFolderModel(const QString &dir, bool is_indexed = false);
- 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;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) 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;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+ int columnCount(const QModelIndex &parent) const 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);
+ [[nodiscard]] Task* createUpdateTask() override;
+ [[nodiscard]] Task* createParseTask(Resource&) override;
+ bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
/// 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;
- }
-
- QDir indexDir()
- {
- return { QString("%1/.index").arg(dir().absolutePath()) };
- }
+ bool startWatching() override;
+ bool stopWatching() override;
- const QList<Mod::Ptr>& allMods()
- {
- return mods;
- }
+ QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
- auto selectedMods(QModelIndexList& indexes) -> QList<Mod::Ptr>;
+ auto selectedMods(QModelIndexList& indexes) -> QList<Mod*>;
+ auto allMods() -> QList<Mod*>;
-public slots:
- void disableInteraction(bool disabled);
+ RESOURCE_HELPERS(Mod)
private
slots:
- void directoryChanged(QString path);
- void finishUpdate();
- void finishModParse(int token);
-
-signals:
- void updateFinished();
-
-private:
- void resolveMod(Mod::Ptr m);
- bool setModStatus(int index, ModStatusAction action);
+ void onUpdateSucceeded() override;
+ void onParseSucceeded(int ticket, QString resource_id) override;
protected:
- QFileSystemWatcher *m_watcher;
- bool is_watching = false;
- ModFolderLoadTask::ResultPtr m_update;
- bool scheduled_update = false;
- bool interaction_disabled = false;
- QDir m_dir;
bool m_is_indexed;
- QMap<QString, int> modsIndex;
- QMap<int, LocalModParseTask::ResultPtr> activeTickets;
- int nextResolutionTicket = 0;
- QList<Mod::Ptr> mods;
+ bool m_first_folder_load = true;
};
diff --git a/launcher/minecraft/mod/ModFolderModel_test.cpp b/launcher/minecraft/mod/ModFolderModel_test.cpp
deleted file mode 100644
index b4d37ce5..00000000
--- a/launcher/minecraft/mod/ModFolderModel_test.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
-* PolyMC - Minecraft Launcher
-* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
-*
-* 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 <https://www.gnu.org/licenses/>.
-*
-* 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
-*
-* 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 <QTest>
-#include <QTemporaryDir>
-
-#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("testdata/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;
- QEventLoop loop;
- ModFolderModel m(tempDir.path(), true);
- connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
- m.installMod(folder);
- loop.exec();
- verify(tempDir.path());
- }
-
- // 2. test with trailing /
- {
- QString folder = source + '/';
- QTemporaryDir tempDir;
- QEventLoop loop;
- ModFolderModel m(tempDir.path(), true);
- connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
- m.installMod(folder);
- loop.exec();
- verify(tempDir.path());
- }
- }
-};
-
-QTEST_GUILESS_MAIN(ModFolderModelTest)
-
-#include "ModFolderModel_test.moc"
diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp
new file mode 100644
index 00000000..0fbcfd7c
--- /dev/null
+++ b/launcher/minecraft/mod/Resource.cpp
@@ -0,0 +1,147 @@
+#include "Resource.h"
+
+#include <QRegularExpression>
+
+#include "FileSystem.h"
+
+Resource::Resource(QObject* parent) : QObject(parent) {}
+
+Resource::Resource(QFileInfo file_info) : QObject()
+{
+ setFile(file_info);
+}
+
+void Resource::setFile(QFileInfo file_info)
+{
+ m_file_info = file_info;
+ parseFile();
+}
+
+void Resource::parseFile()
+{
+ QString file_name{ m_file_info.fileName() };
+
+ m_type = ResourceType::UNKNOWN;
+
+ m_internal_id = file_name;
+
+ if (m_file_info.isDir()) {
+ m_type = ResourceType::FOLDER;
+ m_name = file_name;
+ } else if (m_file_info.isFile()) {
+ if (file_name.endsWith(".disabled")) {
+ file_name.chop(9);
+ m_enabled = false;
+ }
+
+ if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) {
+ m_type = ResourceType::ZIPFILE;
+ file_name.chop(4);
+ } else if (file_name.endsWith(".litemod")) {
+ m_type = ResourceType::LITEMOD;
+ file_name.chop(8);
+ } else {
+ m_type = ResourceType::SINGLEFILE;
+ }
+
+ m_name = file_name;
+ }
+
+ m_changed_date_time = m_file_info.lastModified();
+}
+
+static void removeThePrefix(QString& string)
+{
+ QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
+ string.remove(regex);
+ string = string.trimmed();
+}
+
+std::pair<int, bool> Resource::compare(const Resource& other, SortType type) const
+{
+ switch (type) {
+ default:
+ case SortType::ENABLED:
+ if (enabled() && !other.enabled())
+ return { 1, type == SortType::ENABLED };
+ if (!enabled() && other.enabled())
+ return { -1, type == SortType::ENABLED };
+ case SortType::NAME: {
+ QString this_name{ name() };
+ QString other_name{ other.name() };
+
+ removeThePrefix(this_name);
+ removeThePrefix(other_name);
+
+ auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive);
+ if (compare_result != 0)
+ return { compare_result, type == SortType::NAME };
+ }
+ case SortType::DATE:
+ if (dateTimeChanged() > other.dateTimeChanged())
+ return { 1, type == SortType::DATE };
+ if (dateTimeChanged() < other.dateTimeChanged())
+ return { -1, type == SortType::DATE };
+ }
+
+ return { 0, false };
+}
+
+bool Resource::applyFilter(QRegularExpression filter) const
+{
+ return filter.match(name()).hasMatch();
+}
+
+bool Resource::enable(EnableAction action)
+{
+ if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER)
+ return false;
+
+
+ QString path = m_file_info.absoluteFilePath();
+ QFile file(path);
+
+ bool enable = true;
+ switch (action) {
+ case EnableAction::ENABLE:
+ enable = true;
+ break;
+ case EnableAction::DISABLE:
+ enable = false;
+ break;
+ case EnableAction::TOGGLE:
+ default:
+ enable = !enabled();
+ break;
+ }
+
+ if (m_enabled == enable)
+ return false;
+
+ if (enable) {
+ // m_enabled is false, but there's no '.disabled' suffix.
+ // TODO: Report error?
+ if (!path.endsWith(".disabled"))
+ return false;
+ path.chop(9);
+
+ if (!file.rename(path))
+ return false;
+ } else {
+ path += ".disabled";
+
+ if (!file.rename(path))
+ return false;
+ }
+
+ setFile(QFileInfo(path));
+
+ m_enabled = enable;
+ return true;
+}
+
+bool Resource::destroy()
+{
+ m_type = ResourceType::UNKNOWN;
+ return FS::deletePath(m_file_info.filePath());
+}
diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h
new file mode 100644
index 00000000..f9bd811e
--- /dev/null
+++ b/launcher/minecraft/mod/Resource.h
@@ -0,0 +1,117 @@
+#pragma once
+
+#include <QDateTime>
+#include <QFileInfo>
+#include <QObject>
+#include <QPointer>
+
+#include "QObjectPtr.h"
+
+enum class ResourceType {
+ UNKNOWN, //!< Indicates an unspecified resource type.
+ ZIPFILE, //!< The resource is a zip file containing the resource's class files.
+ SINGLEFILE, //!< The resource is a single file (not a zip file).
+ FOLDER, //!< The resource is in a folder on the filesystem.
+ LITEMOD, //!< The resource is a litemod
+};
+
+enum class SortType {
+ NAME,
+ DATE,
+ VERSION,
+ ENABLED,
+ PACK_FORMAT
+};
+
+enum class EnableAction {
+ ENABLE,
+ DISABLE,
+ TOGGLE
+};
+
+/** General class for managed resources. It mirrors a file in disk, with some more info
+ * for display and house-keeping purposes.
+ *
+ * Subclass it to add additional data / behavior, such as Mods or Resource packs.
+ */
+class Resource : public QObject {
+ Q_OBJECT
+ Q_DISABLE_COPY(Resource)
+ public:
+ using Ptr = shared_qobject_ptr<Resource>;
+ using WeakPtr = QPointer<Resource>;
+
+ Resource(QObject* parent = nullptr);
+ Resource(QFileInfo file_info);
+ Resource(QString file_path) : Resource(QFileInfo(file_path)) {}
+
+ ~Resource() override = default;
+
+ void setFile(QFileInfo file_info);
+ void parseFile();
+
+ [[nodiscard]] auto fileinfo() const -> QFileInfo { return m_file_info; }
+ [[nodiscard]] auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; }
+ [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; }
+ [[nodiscard]] auto type() const -> ResourceType { return m_type; }
+ [[nodiscard]] bool enabled() const { return m_enabled; }
+
+ [[nodiscard]] virtual auto name() const -> QString { return m_name; }
+ [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; }
+
+ /** Compares two Resources, for sorting purposes, considering a ascending order, returning:
+ * > 0: 'this' comes after 'other'
+ * = 0: 'this' is equal to 'other'
+ * < 0: 'this' comes before 'other'
+ *
+ * The second argument in the pair is true if the sorting type that decided which one is greater was 'type'.
+ */
+ [[nodiscard]] virtual auto compare(Resource const& other, SortType type = SortType::NAME) const -> std::pair<int, bool>;
+
+ /** Returns whether the given filter should filter out 'this' (false),
+ * or if such filter includes the Resource (true).
+ */
+ [[nodiscard]] virtual bool applyFilter(QRegularExpression filter) const;
+
+ /** Changes the enabled property, according to 'action'.
+ *
+ * Returns whether a change was applied to the Resource's properties.
+ */
+ bool enable(EnableAction action);
+
+ [[nodiscard]] auto shouldResolve() const -> bool { return !m_is_resolving && !m_is_resolved; }
+ [[nodiscard]] auto isResolving() const -> bool { return m_is_resolving; }
+ [[nodiscard]] auto isResolved() const -> bool { return m_is_resolved; }
+ [[nodiscard]] auto resolutionTicket() const -> int { return m_resolution_ticket; }
+
+ void setResolving(bool resolving, int resolutionTicket)
+ {
+ m_is_resolving = resolving;
+ m_resolution_ticket = resolutionTicket;
+ }
+
+ // Delete all files of this resource.
+ bool destroy();
+
+ protected:
+ /* The file corresponding to this resource. */
+ QFileInfo m_file_info;
+ /* The cached date when this file was last changed. */
+ QDateTime m_changed_date_time;
+
+ /* Internal ID for internal purposes. Properties such as human-readability should not be assumed. */
+ QString m_internal_id;
+ /* Name as reported via the file name. In the absence of a better name, this is shown to the user. */
+ QString m_name;
+
+ /* The type of file we're dealing with. */
+ ResourceType m_type = ResourceType::UNKNOWN;
+
+ /* Whether the resource is enabled (e.g. shows up in the game) or not. */
+ bool m_enabled = true;
+
+ /* Used to keep trach of pending / concluded actions on the resource. */
+ bool m_is_resolving = false;
+ bool m_is_resolved = false;
+ int m_resolution_ticket = 0;
+};
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
new file mode 100644
index 00000000..45d1db59
--- /dev/null
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -0,0 +1,524 @@
+#include "ResourceFolderModel.h"
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QMimeData>
+#include <QThreadPool>
+#include <QUrl>
+
+#include "FileSystem.h"
+
+#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
+
+#include "tasks/Task.h"
+
+ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractListModel(parent), m_dir(dir), m_watcher(this)
+{
+ m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
+ m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
+
+ connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
+}
+
+ResourceFolderModel::~ResourceFolderModel()
+{
+ while (!QThreadPool::globalInstance()->waitForDone(100))
+ QCoreApplication::processEvents();
+}
+
+bool ResourceFolderModel::startWatching(const QStringList paths)
+{
+ if (m_is_watching)
+ return false;
+
+ auto couldnt_be_watched = m_watcher.addPaths(paths);
+ for (auto path : paths) {
+ if (couldnt_be_watched.contains(path))
+ qDebug() << "Failed to start watching " << path;
+ else
+ qDebug() << "Started watching " << path;
+ }
+
+ update();
+
+ m_is_watching = !m_is_watching;
+ return m_is_watching;
+}
+
+bool ResourceFolderModel::stopWatching(const QStringList paths)
+{
+ if (!m_is_watching)
+ return false;
+
+ auto couldnt_be_stopped = m_watcher.removePaths(paths);
+ for (auto path : paths) {
+ if (couldnt_be_stopped.contains(path))
+ qDebug() << "Failed to stop watching " << path;
+ else
+ qDebug() << "Stopped watching " << path;
+ }
+
+ m_is_watching = !m_is_watching;
+ return !m_is_watching;
+}
+
+bool ResourceFolderModel::installResource(QString original_path)
+{
+ if (!m_can_interact) {
+ return false;
+ }
+
+ // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
+ original_path = FS::NormalizePath(original_path);
+ QFileInfo file_info(original_path);
+
+ if (!file_info.exists() || !file_info.isReadable()) {
+ qWarning() << "Caught attempt to install non-existing file or file-like object:" << original_path;
+ return false;
+ }
+ qDebug() << "Installing: " << file_info.absoluteFilePath();
+
+ Resource resource(file_info);
+ if (!resource.valid()) {
+ qWarning() << original_path << "is not a valid resource. Ignoring it.";
+ return false;
+ }
+
+ auto new_path = FS::NormalizePath(m_dir.filePath(file_info.fileName()));
+ if (original_path == new_path) {
+ qWarning() << "Overwriting the mod (" << original_path << ") with itself makes no sense...";
+ return false;
+ }
+
+ switch (resource.type()) {
+ case ResourceType::SINGLEFILE:
+ case ResourceType::ZIPFILE:
+ case ResourceType::LITEMOD: {
+ if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) {
+ if (!QFile::remove(new_path)) {
+ qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!";
+ return false;
+ }
+ qDebug() << new_path << "has been deleted.";
+ }
+
+ if (!QFile::copy(original_path, new_path)) {
+ qCritical() << "Copy from" << original_path << "to" << new_path << "has failed.";
+ return false;
+ }
+
+ FS::updateTimestamp(new_path);
+
+ QFileInfo new_path_file_info(new_path);
+ resource.setFile(new_path_file_info);
+
+ if (!m_is_watching)
+ return update();
+
+ return true;
+ }
+ case ResourceType::FOLDER: {
+ if (QFile::exists(new_path)) {
+ qDebug() << "Ignoring folder '" << original_path << "', it would merge with" << new_path;
+ return false;
+ }
+
+ if (!FS::copy(original_path, new_path)()) {
+ qWarning() << "Copy of folder from" << original_path << "to" << new_path << "has (potentially partially) failed.";
+ return false;
+ }
+
+ QFileInfo newpathInfo(new_path);
+ resource.setFile(newpathInfo);
+
+ if (!m_is_watching)
+ return update();
+
+ return true;
+ }
+ default:
+ break;
+ }
+ return false;
+}
+
+bool ResourceFolderModel::uninstallResource(QString file_name)
+{
+ for (auto& resource : m_resources) {
+ if (resource->fileinfo().fileName() == file_name) {
+ auto res = resource->destroy();
+
+ update();
+
+ return res;
+ }
+ }
+ return false;
+}
+
+bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
+{
+ if (!m_can_interact)
+ return false;
+
+ if (indexes.isEmpty())
+ return true;
+
+ for (auto i : indexes) {
+ if (i.column() != 0) {
+ continue;
+ }
+
+ auto& resource = m_resources.at(i.row());
+
+ resource->destroy();
+ }
+
+ update();
+
+ return true;
+}
+
+bool ResourceFolderModel::setResourceEnabled(const QModelIndexList &indexes, EnableAction action)
+{
+ if (!m_can_interact)
+ return false;
+
+ if (indexes.isEmpty())
+ return true;
+
+ bool succeeded = true;
+ for (auto const& idx : indexes) {
+ if (!validateIndex(idx) || idx.column() != 0)
+ continue;
+
+ int row = idx.row();
+
+ auto& resource = m_resources[row];
+
+ // Preserve the row, but change its ID
+ auto old_id = resource->internal_id();
+ if (!resource->enable(action)) {
+ succeeded = false;
+ continue;
+ }
+
+ auto new_id = resource->internal_id();
+ if (m_resources_index.contains(new_id)) {
+ // FIXME: https://github.com/PolyMC/PolyMC/issues/550
+ }
+
+ m_resources_index.remove(old_id);
+ m_resources_index[new_id] = row;
+
+ emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
+ }
+
+ return succeeded;
+}
+
+static QMutex s_update_task_mutex;
+bool ResourceFolderModel::update()
+{
+ // We hold a lock here to prevent race conditions on the m_current_update_task reset.
+ QMutexLocker lock(&s_update_task_mutex);
+
+ // Already updating, so we schedule a future update and return.
+ if (m_current_update_task) {
+ m_scheduled_update = true;
+ return false;
+ }
+
+ m_current_update_task.reset(createUpdateTask());
+ if (!m_current_update_task)
+ return false;
+
+ connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded,
+ Qt::ConnectionType::QueuedConnection);
+ connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
+ connect(m_current_update_task.get(), &Task::finished, this, [=] {
+ m_current_update_task.reset();
+ if (m_scheduled_update) {
+ m_scheduled_update = false;
+ update();
+ } else {
+ emit updateFinished();
+ }
+ }, Qt::ConnectionType::QueuedConnection);
+
+ QThreadPool::globalInstance()->start(m_current_update_task.get());
+
+ return true;
+}
+
+void ResourceFolderModel::resolveResource(Resource::Ptr res)
+{
+ if (!res->shouldResolve()) {
+ return;
+ }
+
+ auto task = createParseTask(*res);
+ if (!task)
+ return;
+
+ int ticket = m_next_resolution_ticket.fetch_add(1);
+
+ res->setResolving(true, ticket);
+ m_active_parse_tasks.insert(ticket, task);
+
+ connect(
+ task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
+ connect(
+ task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
+ connect(
+ task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
+
+ QThreadPool::globalInstance()->start(task);
+}
+
+void ResourceFolderModel::onUpdateSucceeded()
+{
+ auto update_results = static_cast<BasicFolderLoadTask*>(m_current_update_task.get())->result();
+
+ auto& new_resources = update_results->resources;
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ auto current_list = m_resources_index.keys();
+ QSet<QString> current_set(current_list.begin(), current_list.end());
+
+ auto new_list = new_resources.keys();
+ QSet<QString> new_set(new_list.begin(), new_list.end());
+#else
+ QSet<QString> current_set(m_resources_index.keys().toSet());
+ QSet<QString> new_set(new_resources.keys().toSet());
+#endif
+
+ applyUpdates(current_set, new_set, new_resources);
+}
+
+void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
+{
+ auto iter = m_active_parse_tasks.constFind(ticket);
+ if (iter == m_active_parse_tasks.constEnd())
+ return;
+
+ int row = m_resources_index[resource_id];
+ emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
+}
+
+Task* ResourceFolderModel::createUpdateTask()
+{
+ return new BasicFolderLoadTask(m_dir);
+}
+
+bool ResourceFolderModel::hasPendingParseTasks() const
+{
+ return !m_active_parse_tasks.isEmpty();
+}
+
+void ResourceFolderModel::directoryChanged(QString path)
+{
+ update();
+}
+
+Qt::DropActions ResourceFolderModel::supportedDropActions() const
+{
+ // copy from outside, move from within and other resource lists
+ return Qt::CopyAction | Qt::MoveAction;
+}
+
+Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const
+{
+ Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
+ auto flags = defaultFlags;
+ if (!m_can_interact) {
+ flags &= ~Qt::ItemIsDropEnabled;
+ } else {
+ flags |= Qt::ItemIsDropEnabled;
+ if (index.isValid()) {
+ flags |= Qt::ItemIsUserCheckable;
+ }
+ }
+ return flags;
+}
+
+QStringList ResourceFolderModel::mimeTypes() const
+{
+ QStringList types;
+ types << "text/uri-list";
+ return types;
+}
+
+bool ResourceFolderModel::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
+ installResource(url.toLocalFile());
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ResourceFolderModel::validateIndex(const QModelIndex& index) const
+{
+ if (!index.isValid())
+ return false;
+
+ int row = index.row();
+ if (row < 0 || row >= m_resources.size())
+ return false;
+
+ return true;
+}
+
+QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
+{
+ if (!validateIndex(index))
+ return {};
+
+ int row = index.row();
+ int column = index.column();
+
+ switch (role) {
+ case Qt::DisplayRole:
+ switch (column) {
+ case NAME_COLUMN:
+ return m_resources[row]->name();
+ case DATE_COLUMN:
+ return m_resources[row]->dateTimeChanged();
+ default:
+ return {};
+ }
+ case Qt::ToolTipRole:
+ return m_resources[row]->internal_id();
+ case Qt::CheckStateRole:
+ switch (column) {
+ case ACTIVE_COLUMN:
+ return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
+ default:
+ return {};
+ }
+ default:
+ return {};
+ }
+}
+
+bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+ int row = index.row();
+ if (row < 0 || row >= rowCount(index) || !index.isValid())
+ return false;
+
+ if (role == Qt::CheckStateRole)
+ return setResourceEnabled({ index }, EnableAction::TOGGLE);
+
+ return false;
+}
+
+QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ switch (role) {
+ case Qt::DisplayRole:
+ switch (section) {
+ case NAME_COLUMN:
+ return tr("Name");
+ case DATE_COLUMN:
+ return tr("Last modified");
+ default:
+ return {};
+ }
+ case Qt::ToolTipRole: {
+ switch (section) {
+ case ACTIVE_COLUMN:
+ //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
+ return tr("Is the resource enabled?");
+ case NAME_COLUMN:
+ //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
+ return tr("The name of the resource.");
+ case DATE_COLUMN:
+ //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
+ return tr("The date and time this resource was last changed (or added).");
+ default:
+ return {};
+ }
+ }
+ default:
+ break;
+ }
+
+ return {};
+}
+
+QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent)
+{
+ return new ProxyModel(parent);
+}
+
+SortType ResourceFolderModel::columnToSortKey(size_t column) const
+{
+ Q_ASSERT(m_column_sort_keys.size() == columnCount());
+ return m_column_sort_keys.at(column);
+}
+
+void ResourceFolderModel::enableInteraction(bool enabled)
+{
+ if (m_can_interact == enabled)
+ return;
+
+ m_can_interact = enabled;
+ if (size())
+ emit dataChanged(index(0), index(size() - 1));
+}
+
+/* Standard Proxy Model for createFilterProxyModel */
+[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
+{
+ auto* model = qobject_cast<ResourceFolderModel*>(sourceModel());
+ if (!model)
+ return true;
+
+ const auto& resource = model->at(source_row);
+
+ return resource.applyFilter(filterRegularExpression());
+}
+
+[[nodiscard]] bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
+{
+ auto* model = qobject_cast<ResourceFolderModel*>(sourceModel());
+ if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) {
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+ }
+
+ // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and
+ // proceed.
+
+ auto column_sort_key = model->columnToSortKey(source_left.column());
+ auto const& resource_left = model->at(source_left.row());
+ auto const& resource_right = model->at(source_right.row());
+
+ auto compare_result = resource_left.compare(resource_right, column_sort_key);
+ if (compare_result.first == 0)
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+
+ if (compare_result.second || sortOrder() != Qt::DescendingOrder)
+ return (compare_result.first < 0);
+ return (compare_result.first > 0);
+}
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
new file mode 100644
index 00000000..5652c156
--- /dev/null
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -0,0 +1,332 @@
+#pragma once
+
+#include <QAbstractListModel>
+#include <QDir>
+#include <QFileSystemWatcher>
+#include <QMutex>
+#include <QSet>
+#include <QSortFilterProxyModel>
+
+#include "Resource.h"
+
+#include "tasks/Task.h"
+
+class QSortFilterProxyModel;
+
+/** A basic model for external resources.
+ *
+ * This model manages a list of resources. As such, external users of such resources do not own them,
+ * and the resource's lifetime is contingent on the model's lifetime.
+ *
+ * TODO: Make the resources unique pointers accessible through weak pointers.
+ */
+class ResourceFolderModel : public QAbstractListModel {
+ Q_OBJECT
+ public:
+ ResourceFolderModel(QDir, QObject* parent = nullptr);
+ ~ResourceFolderModel() override;
+
+ /** Starts watching the paths for changes.
+ *
+ * Returns whether starting to watch all the paths was successful.
+ * If one or more fails, it returns false.
+ */
+ bool startWatching(const QStringList paths);
+
+ /** Stops watching the paths for changes.
+ *
+ * Returns whether stopping to watch all the paths was successful.
+ * If one or more fails, it returns false.
+ */
+ bool stopWatching(const QStringList paths);
+
+ /* Helper methods for subclasses, using a predetermined list of paths. */
+ virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); };
+ virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); };
+
+ /** Given a path in the system, install that resource, moving it to its place in the
+ * instance file hierarchy.
+ *
+ * Returns whether the installation was succcessful.
+ */
+ virtual bool installResource(QString path);
+
+ /** Uninstall (i.e. remove all data about it) a resource, given its file name.
+ *
+ * Returns whether the removal was successful.
+ */
+ virtual bool uninstallResource(QString file_name);
+ virtual bool deleteResources(const QModelIndexList&);
+
+ /** Applies the given 'action' to the resources in 'indexes'.
+ *
+ * Returns whether the action was successfully applied to all resources.
+ */
+ virtual bool setResourceEnabled(const QModelIndexList& indexes, EnableAction action);
+
+ /** Creates a new update task and start it. Returns false if no update was done, like when an update is already underway. */
+ virtual bool update();
+
+ /** Creates a new parse task, if needed, for 'res' and start it.*/
+ virtual void resolveResource(Resource::Ptr res);
+
+ [[nodiscard]] size_t size() const { return m_resources.size(); };
+ [[nodiscard]] bool empty() const { return size() == 0; }
+ [[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
+ [[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
+ [[nodiscard]] QList<Resource::Ptr> const& all() const { return m_resources; }
+
+ [[nodiscard]] QDir const& dir() const { return m_dir; }
+
+ /** Checks whether there's any parse tasks being done.
+ *
+ * Since they can be quite expensive, and are usually done in a separate thread, if we were to destroy the model while having
+ * such tasks would introduce an undefined behavior, most likely resulting in a crash.
+ */
+ [[nodiscard]] bool hasPendingParseTasks() const;
+
+ /* Qt behavior */
+
+ /* Basic columns */
+ enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
+
+ [[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); }
+ [[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; };
+
+ [[nodiscard]] Qt::DropActions supportedDropActions() const override;
+
+ /// flags, mostly to support drag&drop
+ [[nodiscard]] Qt::ItemFlags flags(const QModelIndex& index) const override;
+ [[nodiscard]] QStringList mimeTypes() const override;
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
+
+ [[nodiscard]] bool validateIndex(const QModelIndex& index) const;
+
+ [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+ bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
+
+ [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+ /** This creates a proxy model to filter / sort the model for a UI.
+ *
+ * The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead!
+ */
+ QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr);
+
+ [[nodiscard]] SortType columnToSortKey(size_t column) const;
+
+ class ProxyModel : public QSortFilterProxyModel {
+ public:
+ explicit ProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {}
+
+ protected:
+ [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
+ [[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override;
+ };
+
+ public slots:
+ void enableInteraction(bool enabled);
+ void disableInteraction(bool disabled) { enableInteraction(!disabled); }
+
+ signals:
+ void updateFinished();
+
+ protected:
+ /** This creates a new update task to be executed by update().
+ *
+ * The task should load and parse all resources necessary, and provide a way of accessing such results.
+ *
+ * This Task is normally executed when opening a page, so it shouldn't contain much heavy work.
+ * If such work is needed, try using it in the Task create by createParseTask() instead!
+ */
+ [[nodiscard]] virtual Task* createUpdateTask();
+
+ /** This creates a new parse task to be executed by onUpdateSucceeded().
+ *
+ * This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed
+ * in the background, so it slowly updates the UI as tasks get done.
+ */
+ [[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; };
+
+ /** Standard implementation of the model update logic.
+ *
+ * It uses set operations to find differences between the current state and the updated state,
+ * to act only on those disparities.
+ *
+ * The implementation is at the end of this header.
+ */
+ template <typename T>
+ void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources);
+
+ protected slots:
+ void directoryChanged(QString);
+
+ /** Called when the update task is successful.
+ *
+ * This usually calls static_cast on the specific Task type returned by createUpdateTask,
+ * so care must be taken in such cases.
+ * TODO: Figure out a way to express this relationship better without templated classes (Q_OBJECT macro disallows that).
+ */
+ virtual void onUpdateSucceeded();
+ virtual void onUpdateFailed() {}
+
+ /** Called when the parse task with the given ticket is successful.
+ *
+ * This is just a simple reference implementation. You probably want to override it with your own logic in a subclass
+ * if the resource is complex and has more stuff to parse.
+ */
+ virtual void onParseSucceeded(int ticket, QString resource_id);
+ virtual void onParseFailed(int ticket, QString resource_id) {}
+
+ protected:
+ // Represents the relationship between a column's index (represented by the list index), and it's sorting key.
+ // As such, the order in with they appear is very important!
+ QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
+
+ bool m_can_interact = true;
+
+ QDir m_dir;
+ QFileSystemWatcher m_watcher;
+ bool m_is_watching = false;
+
+ Task::Ptr m_current_update_task = nullptr;
+ bool m_scheduled_update = false;
+
+ QList<Resource::Ptr> m_resources;
+
+ // Represents the relationship between a resource's internal ID and it's row position on the model.
+ QMap<QString, int> m_resources_index;
+
+ QMap<int, Task::Ptr> m_active_parse_tasks;
+ std::atomic<int> m_next_resolution_ticket = 0;
+};
+
+/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
+#define RESOURCE_HELPERS(T) \
+ [[nodiscard]] T* operator[](size_t index) \
+ { \
+ return static_cast<T*>(m_resources[index].get()); \
+ } \
+ [[nodiscard]] T* at(size_t index) \
+ { \
+ return static_cast<T*>(m_resources[index].get()); \
+ } \
+ [[nodiscard]] const T* at(size_t index) const \
+ { \
+ return static_cast<const T*>(m_resources.at(index).get()); \
+ } \
+ [[nodiscard]] T* first() \
+ { \
+ return static_cast<T*>(m_resources.first().get()); \
+ } \
+ [[nodiscard]] T* last() \
+ { \
+ return static_cast<T*>(m_resources.last().get()); \
+ } \
+ [[nodiscard]] T* find(QString id) \
+ { \
+ auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \
+ [&](Resource::Ptr const& r) { return r->internal_id() == id; }); \
+ if (iter == m_resources.constEnd()) \
+ return nullptr; \
+ return static_cast<T*>((*iter).get()); \
+ }
+
+/* Template definition to avoid some code duplication */
+template <typename T>
+void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources)
+{
+ // see if the kept resources changed in some way
+ {
+ QSet<QString> kept_set = current_set;
+ kept_set.intersect(new_set);
+
+ for (auto const& kept : kept_set) {
+ auto row_it = m_resources_index.constFind(kept);
+ Q_ASSERT(row_it != m_resources_index.constEnd());
+ auto row = row_it.value();
+
+ auto& new_resource = new_resources[kept];
+ auto const& current_resource = m_resources[row];
+
+ if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
+ // no significant change, ignore...
+ continue;
+ }
+
+ // If the resource is resolving, but something about it changed, we don't want to
+ // continue the resolving.
+ if (current_resource->isResolving()) {
+ auto ticket = current_resource->resolutionTicket();
+ if (m_active_parse_tasks.contains(ticket)) {
+ auto task = (*m_active_parse_tasks.find(ticket)).get();
+ task->abort();
+ }
+ }
+
+ m_resources[row].reset(new_resource);
+ resolveResource(m_resources.at(row));
+ emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
+ }
+ }
+
+ // remove resources no longer present
+ {
+ QSet<QString> removed_set = current_set;
+ removed_set.subtract(new_set);
+
+ QList<int> removed_rows;
+ for (auto& removed : removed_set)
+ removed_rows.append(m_resources_index[removed]);
+
+ std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
+
+ for (auto& removed_index : removed_rows) {
+ auto removed_it = m_resources.begin() + removed_index;
+
+ Q_ASSERT(removed_it != m_resources.end());
+ Q_ASSERT(removed_set.contains(removed_it->get()->internal_id()));
+
+ if ((*removed_it)->isResolving()) {
+ auto ticket = (*removed_it)->resolutionTicket();
+ if (m_active_parse_tasks.contains(ticket)) {
+ auto task = (*m_active_parse_tasks.find(ticket)).get();
+ task->abort();
+ }
+ }
+
+ beginRemoveRows(QModelIndex(), removed_index, removed_index);
+ m_resources.erase(removed_it);
+ endRemoveRows();
+ }
+ }
+
+ // add new resources to the end
+ {
+ QSet<QString> added_set = new_set;
+ added_set.subtract(current_set);
+
+ // When you have a Qt build with assertions turned on, proceeding here will abort the application
+ if (added_set.size() > 0) {
+ beginInsertRows(QModelIndex(), m_resources.size(), m_resources.size() + added_set.size() - 1);
+
+ for (auto& added : added_set) {
+ auto res = new_resources[added];
+ m_resources.append(res);
+ resolveResource(res);
+ }
+
+ endInsertRows();
+ }
+ }
+
+ // update index
+ {
+ m_resources_index.clear();
+ int idx = 0;
+ for (auto const& mod : m_resources) {
+ m_resources_index[mod->internal_id()] = idx;
+ idx++;
+ }
+ }
+}
diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp
new file mode 100644
index 00000000..3fc10a2f
--- /dev/null
+++ b/launcher/minecraft/mod/ResourcePack.cpp
@@ -0,0 +1,116 @@
+#include "ResourcePack.h"
+
+#include <QDebug>
+#include <QMap>
+#include <QRegularExpression>
+
+#include "Version.h"
+
+#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
+
+// Values taken from:
+// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
+ { 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
+ { 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
+ { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
+ { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
+ { 9, { Version("1.19"), Version("1.19.2") } },
+};
+
+void ResourcePack::setPackFormat(int new_format_id)
+{
+ QMutexLocker locker(&m_data_lock);
+
+ if (!s_pack_format_versions.contains(new_format_id)) {
+ qWarning() << "Pack format '%1' is not a recognized resource pack id!";
+ }
+
+ m_pack_format = new_format_id;
+}
+
+void ResourcePack::setDescription(QString new_description)
+{
+ QMutexLocker locker(&m_data_lock);
+
+ m_description = new_description;
+}
+
+void ResourcePack::setImage(QImage new_image)
+{
+ QMutexLocker locker(&m_data_lock);
+
+ Q_ASSERT(!new_image.isNull());
+
+ if (m_pack_image_cache_key.key.isValid())
+ QPixmapCache::remove(m_pack_image_cache_key.key);
+
+ m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image));
+ m_pack_image_cache_key.was_ever_used = true;
+}
+
+QPixmap ResourcePack::image(QSize size)
+{
+ QPixmap cached_image;
+ if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
+ if (size.isNull())
+ return cached_image;
+ return cached_image.scaled(size);
+ }
+
+ // No valid image we can get
+ if (!m_pack_image_cache_key.was_ever_used)
+ return {};
+
+ // Imaged got evicted from the cache. Re-process it and retry.
+ ResourcePackUtils::process(*this);
+ return image(size);
+}
+
+std::pair<Version, Version> ResourcePack::compatibleVersions() const
+{
+ if (!s_pack_format_versions.contains(m_pack_format)) {
+ return { {}, {} };
+ }
+
+ return s_pack_format_versions.constFind(m_pack_format).value();
+}
+
+std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type) const
+{
+ auto const& cast_other = static_cast<ResourcePack const&>(other);
+
+ switch (type) {
+ default: {
+ auto res = Resource::compare(other, type);
+ if (res.first != 0)
+ return res;
+ }
+ case SortType::PACK_FORMAT: {
+ auto this_ver = packFormat();
+ auto other_ver = cast_other.packFormat();
+
+ if (this_ver > other_ver)
+ return { 1, type == SortType::PACK_FORMAT };
+ if (this_ver < other_ver)
+ return { -1, type == SortType::PACK_FORMAT };
+ }
+ }
+ return { 0, false };
+}
+
+bool ResourcePack::applyFilter(QRegularExpression filter) const
+{
+ if (filter.match(description()).hasMatch())
+ return true;
+
+ if (filter.match(QString::number(packFormat())).hasMatch())
+ return true;
+
+ if (filter.match(compatibleVersions().first.toString()).hasMatch())
+ return true;
+ if (filter.match(compatibleVersions().second.toString()).hasMatch())
+ return true;
+
+ return Resource::applyFilter(filter);
+}
diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h
new file mode 100644
index 00000000..03121908
--- /dev/null
+++ b/launcher/minecraft/mod/ResourcePack.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "Resource.h"
+
+#include <QImage>
+#include <QMutex>
+#include <QPixmap>
+#include <QPixmapCache>
+
+class Version;
+
+/* TODO:
+ *
+ * Store localized descriptions
+ * */
+
+class ResourcePack : public Resource {
+ Q_OBJECT
+ public:
+ using Ptr = shared_qobject_ptr<Resource>;
+
+ ResourcePack(QObject* parent = nullptr) : Resource(parent) {}
+ ResourcePack(QFileInfo file_info) : Resource(file_info) {}
+
+ /** Gets the numerical ID of the pack format. */
+ [[nodiscard]] int packFormat() const { return m_pack_format; }
+ /** Gets, respectively, the lower and upper versions supported by the set pack format. */
+ [[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
+
+ /** Gets the description of the resource pack. */
+ [[nodiscard]] QString description() const { return m_description; }
+
+ /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
+ [[nodiscard]] QPixmap image(QSize size);
+
+ /** Thread-safe. */
+ void setPackFormat(int new_format_id);
+
+ /** Thread-safe. */
+ void setDescription(QString new_description);
+
+ /** Thread-safe. */
+ void setImage(QImage new_image);
+
+ [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
+ [[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
+
+ protected:
+ mutable QMutex m_data_lock;
+
+ /* The 'version' of a resource pack, as defined in the pack.mcmeta file.
+ * See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+ */
+ int m_pack_format = 0;
+
+ /** The resource pack's description, as defined in the pack.mcmeta file.
+ */
+ QString m_description;
+
+ /** The resource pack's image file cache key, for access in the QPixmapCache global instance.
+ *
+ * The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true),
+ * so as to tell whether a cache entry is inexistent or if it was just evicted from the cache.
+ */
+ struct {
+ QPixmapCache::Key key;
+ bool was_ever_used = false;
+ } m_pack_image_cache_key;
+};
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index 276804ed..f8a6c1cf 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -1,58 +1,151 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
-* PolyMC - Minecraft Launcher
-* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
-*
-* 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 <https://www.gnu.org/licenses/>.
-*
-* 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
-*
-* 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.
-*/
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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
+ *
+ * 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 "ResourcePackFolderModel.h"
-ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) {
+#include "Version.h"
+
+#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
+#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
+
+ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir))
+{
+ m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
}
-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();
+QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
+{
+ if (!validateIndex(index))
+ return {};
+
+ int row = index.row();
+ int column = index.column();
+
+ switch (role) {
+ case Qt::DisplayRole:
+ switch (column) {
+ case NameColumn:
+ return m_resources[row]->name();
+ case PackFormatColumn: {
+ auto resource = at(row);
+ auto pack_format = resource->packFormat();
+ if (pack_format == 0)
+ return tr("Unrecognized");
+
+ auto version_bounds = resource->compatibleVersions();
+ if (version_bounds.first.toString().isEmpty())
+ return QString::number(pack_format);
+
+ return QString("%1 (%2 - %3)")
+ .arg(QString::number(pack_format), version_bounds.first.toString(), version_bounds.second.toString());
+ }
+ case DateColumn:
+ return m_resources[row]->dateTimeChanged();
+
+ default:
+ return {};
+ }
+
+ case Qt::ToolTipRole: {
+ if (column == PackFormatColumn) {
+ //: The string being explained by this is in the format: ID (Lower version - Upper version)
+ return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
+ }
+ return m_resources[row]->internal_id();
}
+ case Qt::CheckStateRole:
+ switch (column) {
+ case ActiveColumn:
+ return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
+ default:
+ return {};
+ }
+ default:
+ return {};
}
+}
+
+QVariant ResourcePackFolderModel::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 PackFormatColumn:
+ return tr("Pack Format");
+ case DateColumn:
+ return tr("Last changed");
+ default:
+ return {};
+ }
+
+ case Qt::ToolTipRole:
+ switch (section) {
+ case ActiveColumn:
+ return tr("Is the resource pack enabled? (Only valid for ZIPs)");
+ case NameColumn:
+ return tr("The name of the resource pack.");
+ case PackFormatColumn:
+ //: The string being explained by this is in the format: ID (Lower version - Upper version)
+ return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
+ case DateColumn:
+ return tr("The date and time this resource pack was last changed (or added).");
+ default:
+ return {};
+ }
+ default:
+ return {};
+ }
+ return {};
+}
+
+int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
+{
+ return NUM_COLUMNS;
+}
+
+Task* ResourcePackFolderModel::createUpdateTask()
+{
+ return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new ResourcePack(entry); });
+}
- return ModFolderModel::headerData(section, orientation, role);
+Task* ResourcePackFolderModel::createParseTask(Resource& resource)
+{
+ return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));
}
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h
index 0cd6214b..cb620ce2 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.h
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.h
@@ -1,13 +1,31 @@
#pragma once
-#include "ModFolderModel.h"
+#include "ResourceFolderModel.h"
-class ResourcePackFolderModel : public ModFolderModel
+#include "ResourcePack.h"
+
+class ResourcePackFolderModel : public ResourceFolderModel
{
Q_OBJECT
-
public:
+ enum Columns
+ {
+ ActiveColumn = 0,
+ NameColumn,
+ PackFormatColumn,
+ DateColumn,
+ NUM_COLUMNS
+ };
+
explicit ResourcePackFolderModel(const QString &dir);
- QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+ [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+ [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+ [[nodiscard]] int columnCount(const QModelIndex &parent) const override;
+
+ [[nodiscard]] Task* createUpdateTask() override;
+ [[nodiscard]] Task* createParseTask(Resource&) override;
+
+ RESOURCE_HELPERS(ResourcePack)
};
diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h
new file mode 100644
index 00000000..a3aa958f
--- /dev/null
+++ b/launcher/minecraft/mod/ShaderPackFolderModel.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "ResourceFolderModel.h"
+
+class ShaderPackFolderModel : public ResourceFolderModel {
+ Q_OBJECT
+
+ public:
+ explicit ShaderPackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir)) {}
+};
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp
index e3a22219..2c7c945b 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.cpp
+++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp
@@ -35,24 +35,4 @@
#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);
-}
+TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {}
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h
index a59d5119..69e98661 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.h
+++ b/launcher/minecraft/mod/TexturePackFolderModel.h
@@ -1,13 +1,11 @@
#pragma once
-#include "ModFolderModel.h"
+#include "ResourceFolderModel.h"
-class TexturePackFolderModel : public ModFolderModel
+class TexturePackFolderModel : public ResourceFolderModel
{
Q_OBJECT
public:
explicit TexturePackFolderModel(const QString &dir);
-
- QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
};
diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h
new file mode 100644
index 00000000..be0e752d
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <QDir>
+#include <QMap>
+#include <QObject>
+
+#include <memory>
+
+#include "minecraft/mod/Resource.h"
+
+#include "tasks/Task.h"
+
+/** Very simple task that just loads a folder's contents directly.
+ */
+class BasicFolderLoadTask : public Task {
+ Q_OBJECT
+ public:
+ struct Result {
+ QMap<QString, Resource::Ptr> resources;
+ };
+ using ResultPtr = std::shared_ptr<Result>;
+
+ [[nodiscard]] ResultPtr result() const { return m_result; }
+
+ public:
+ BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result)
+ {
+ m_create_func = [](QFileInfo const& entry) -> Resource* {
+ return new Resource(entry);
+ };
+ }
+ BasicFolderLoadTask(QDir dir, std::function<Resource*(QFileInfo const&)> create_function)
+ : Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function))
+ {}
+
+ [[nodiscard]] bool canAbort() const override { return true; }
+ bool abort() override
+ {
+ m_aborted.store(true);
+ return true;
+ }
+
+ void executeTask() override
+ {
+ m_dir.refresh();
+ for (auto entry : m_dir.entryInfoList()) {
+ auto resource = m_create_func(entry);
+ m_result->resources.insert(resource->internal_id(), resource);
+ }
+
+ if (m_aborted)
+ emit finished();
+ else
+ emitSucceeded();
+ }
+
+private:
+ QDir m_dir;
+ ResultPtr m_result;
+
+ std::atomic<bool> m_aborted = false;
+
+ std::function<Resource*(QFileInfo const&)> m_create_func;
+};
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index 1519f49d..8a6e54d8 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -20,22 +20,22 @@ namespace {
// OLD format:
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
-std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
+ModDetails ReadMCModInfo(QByteArray contents)
{
- auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr<ModDetails>
+ auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails
{
if (!arr.at(0).isObject()) {
- return nullptr;
+ return {};
}
- std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ ModDetails details;
auto firstObj = arr.at(0).toObject();
- details->mod_id = firstObj.value("modid").toString();
+ 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.name = name;
}
- details->version = firstObj.value("version").toString();
+ details.version = firstObj.value("version").toString();
auto homeurl = firstObj.value("url").toString().trimmed();
if(!homeurl.isEmpty())
{
@@ -45,8 +45,8 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
homeurl.prepend("http://");
}
}
- details->homeurl = homeurl;
- details->description = firstObj.value("description").toString();
+ 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?
@@ -55,7 +55,7 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
for (auto author: authors)
{
- details->authors.append(author.toString());
+ details.authors.append(author.toString());
}
return details;
};
@@ -83,7 +83,7 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
{
qCritical() << "BAD stuff happened to mod json:";
qCritical() << contents;
- return nullptr;
+ return {};
}
auto arrVal = jsonDoc.object().value("modlist");
if(arrVal.isUndefined()) {
@@ -94,13 +94,13 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
return getInfoFromArray(arrVal.toArray());
}
}
- return nullptr;
+ return {};
}
// https://github.com/MinecraftForge/Documentation/blob/5ab4ba6cf9abc0ac4c0abd96ad187461aefd72af/docs/gettingstarted/structuring.md
-std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
+ModDetails ReadMCModTOML(QByteArray contents)
{
- std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ ModDetails details;
char errbuf[200];
// top-level table
@@ -108,7 +108,7 @@ std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
if(!tomlData)
{
- return nullptr;
+ return {};
}
// array defined by [[mods]]
@@ -116,7 +116,7 @@ std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
if(!tomlModsArr)
{
qWarning() << "Corrupted mods.toml? Couldn't find [[mods]] array!";
- return nullptr;
+ return {};
}
// we only really care about the first element, since multiple mods in one file is not supported by us at the moment
@@ -124,33 +124,33 @@ std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
if(!tomlModsTable0)
{
qWarning() << "Corrupted mods.toml? [[mods]] didn't have an element at index 0!";
- return nullptr;
+ return {};
}
// mandatory properties - always in [[mods]]
toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId");
if(modIdDatum.ok)
{
- details->mod_id = modIdDatum.u.s;
+ 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;
+ 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;
+ 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;
+ details.description = descriptionDatum.u.s;
free(descriptionDatum.u.s);
}
@@ -173,7 +173,7 @@ std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
}
if(!authors.isEmpty())
{
- details->authors.append(authors);
+ details.authors.append(authors);
}
toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL");
@@ -200,7 +200,7 @@ std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
homeurl.prepend("http://");
}
}
- details->homeurl = homeurl;
+ details.homeurl = homeurl;
// this seems to be recursive, so it should free everything
toml_free(tomlData);
@@ -209,20 +209,20 @@ std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
}
// https://fabricmc.net/wiki/documentation:fabric_mod_json
-std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
+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>();
+ ModDetails details;
- details->mod_id = object.value("id").toString();
- details->version = object.value("version").toString();
+ 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();
+ details.name = object.contains("name") ? object.value("name").toString() : details.mod_id;
+ details.description = object.value("description").toString();
if (schemaVersion >= 1)
{
@@ -230,10 +230,10 @@ std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
for (auto author: authors)
{
if(author.isObject()) {
- details->authors.append(author.toObject().value("name").toString());
+ details.authors.append(author.toObject().value("name").toString());
}
else {
- details->authors.append(author.toString());
+ details.authors.append(author.toString());
}
}
@@ -243,7 +243,7 @@ std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
if (contact.contains("homepage"))
{
- details->homeurl = contact.value("homepage").toString();
+ details.homeurl = contact.value("homepage").toString();
}
}
}
@@ -251,50 +251,50 @@ std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
}
// https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md
-std::shared_ptr<ModDetails> ReadQuiltModInfo(QByteArray contents)
+ModDetails 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<ModDetails> details = std::make_shared<ModDetails>();
+ ModDetails details;
// 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");
+ 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"));
+ 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();
+ details.authors += modContributors.keys();
auto modContact = Json::ensureObject(modMetadata.value("contact"));
if (modContact.contains("homepage"))
{
- details->homeurl = Json::requireString(modContact.value("homepage"));
+ details.homeurl = Json::requireString(modContact.value("homepage"));
}
}
return details;
}
-std::shared_ptr<ModDetails> ReadForgeInfo(QByteArray contents)
+ModDetails ReadForgeInfo(QByteArray contents)
{
- std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ ModDetails details;
// Read the data
- details->name = "Minecraft Forge";
- details->mod_id = "Forge";
- details->homeurl = "http://www.minecraftforge.net/forum/";
+ details.name = "Minecraft Forge";
+ details.mod_id = "Forge";
+ details.homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini;
if (!ini.loadFile(contents))
return details;
@@ -304,47 +304,47 @@ std::shared_ptr<ModDetails> ReadForgeInfo(QByteArray contents)
QString revision = ini.get("forge.revision.number", "0").toString();
QString build = ini.get("forge.build.number", "0").toString();
- details->version = major + "." + minor + "." + revision + "." + build;
+ details.version = major + "." + minor + "." + revision + "." + build;
return details;
}
-std::shared_ptr<ModDetails> ReadLiteModInfo(QByteArray contents)
+ModDetails ReadLiteModInfo(QByteArray contents)
{
- std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ ModDetails details;
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();
+ details.mod_id = details.name = object.value("name").toString();
}
if (object.contains("version"))
{
- details->version = object.value("version").toString("");
+ details.version = object.value("version").toString("");
}
else
{
- details->version = object.value("revision").toString("");
+ details.version = object.value("revision").toString("");
}
- details->mcversion = object.value("mcversion").toString();
+ details.mcversion = object.value("mcversion").toString();
auto author = object.value("author").toString();
if(!author.isEmpty()) {
- details->authors.append(author);
+ details.authors.append(author);
}
- details->description = object.value("description").toString();
- details->homeurl = object.value("url").toString();
+ details.description = object.value("description").toString();
+ details.homeurl = object.value("url").toString();
return details;
}
}
-LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile):
+LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile):
+ Task(nullptr, false),
m_token(token),
m_type(type),
m_modFile(modFile),
m_result(new Result())
-{
-}
+{}
void LocalModParseTask::processAsZip()
{
@@ -366,7 +366,7 @@ void LocalModParseTask::processAsZip()
file.close();
// to replace ${file.jarVersion} with the actual version, as needed
- if (m_result->details && m_result->details->version == "${file.jarVersion}")
+ if (m_result->details.version == "${file.jarVersion}")
{
if (zip.setCurrentFile("META-INF/MANIFEST.MF"))
{
@@ -395,7 +395,7 @@ void LocalModParseTask::processAsZip()
manifestVersion = "NONE";
}
- m_result->details->version = manifestVersion;
+ m_result->details.version = manifestVersion;
file.close();
}
@@ -497,21 +497,31 @@ void LocalModParseTask::processAsLitemod()
zip.close();
}
-void LocalModParseTask::run()
+bool LocalModParseTask::abort()
+{
+ m_aborted.store(true);
+ return true;
+}
+
+void LocalModParseTask::executeTask()
{
switch(m_type)
{
- case Mod::MOD_ZIPFILE:
+ case ResourceType::ZIPFILE:
processAsZip();
break;
- case Mod::MOD_FOLDER:
+ case ResourceType::FOLDER:
processAsFolder();
break;
- case Mod::MOD_LITEMOD:
+ case ResourceType::LITEMOD:
processAsLitemod();
break;
default:
break;
}
- emit finished(m_token);
+
+ if (m_aborted)
+ emit finished();
+ else
+ emitSucceeded();
}
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h
index ed92394c..413eb2d1 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h
@@ -2,29 +2,31 @@
#include <QDebug>
#include <QObject>
-#include <QRunnable>
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/ModDetails.h"
-class LocalModParseTask : public QObject, public QRunnable
+#include "tasks/Task.h"
+
+class LocalModParseTask : public Task
{
Q_OBJECT
public:
struct Result {
- QString id;
- std::shared_ptr<ModDetails> details;
+ 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();
+ [[nodiscard]] bool canAbort() const override { return true; }
+ bool abort() override;
+
+ LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile);
+ void executeTask() override;
-signals:
- void finished(int token);
+ [[nodiscard]] int token() const { return m_token; }
private:
void processAsZip();
@@ -33,7 +35,9 @@ private:
private:
int m_token;
- Mod::ModType m_type;
+ ResourceType m_type;
QFileInfo m_modFile;
ResultPtr m_result;
+
+ std::atomic<bool> m_aborted = false;
};
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
new file mode 100644
index 00000000..4f87bc13
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "LocalResourcePackParseTask.h"
+
+#include "FileSystem.h"
+#include "Json.h"
+
+#include <quazip/quazip.h>
+#include <quazip/quazipfile.h>
+
+#include <QCryptographicHash>
+
+namespace ResourcePackUtils {
+
+bool process(ResourcePack& pack)
+{
+ switch (pack.type()) {
+ case ResourceType::FOLDER:
+ ResourcePackUtils::processFolder(pack);
+ return true;
+ case ResourceType::ZIPFILE:
+ ResourcePackUtils::processZIP(pack);
+ return true;
+ default:
+ qWarning() << "Invalid type for resource pack parse task!";
+ return false;
+ }
+}
+
+void processFolder(ResourcePack& pack)
+{
+ Q_ASSERT(pack.type() == ResourceType::FOLDER);
+
+ QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
+ if (mcmeta_file_info.isFile()) {
+ QFile mcmeta_file(mcmeta_file_info.filePath());
+ if (!mcmeta_file.open(QIODevice::ReadOnly))
+ return;
+
+ auto data = mcmeta_file.readAll();
+
+ ResourcePackUtils::processMCMeta(pack, std::move(data));
+
+ mcmeta_file.close();
+ }
+
+ QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
+ if (image_file_info.isFile()) {
+ QFile mcmeta_file(image_file_info.filePath());
+ if (!mcmeta_file.open(QIODevice::ReadOnly))
+ return;
+
+ auto data = mcmeta_file.readAll();
+
+ ResourcePackUtils::processPackPNG(pack, std::move(data));
+
+ mcmeta_file.close();
+ }
+}
+
+void processZIP(ResourcePack& pack)
+{
+ Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
+
+ QuaZip zip(pack.fileinfo().filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return;
+
+ QuaZipFile file(&zip);
+
+ if (zip.setCurrentFile("pack.mcmeta")) {
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCritical() << "Failed to open file in zip.";
+ zip.close();
+ return;
+ }
+
+ auto data = file.readAll();
+
+ ResourcePackUtils::processMCMeta(pack, std::move(data));
+
+ file.close();
+ }
+
+ if (zip.setCurrentFile("pack.png")) {
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCritical() << "Failed to open file in zip.";
+ zip.close();
+ return;
+ }
+
+ auto data = file.readAll();
+
+ ResourcePackUtils::processPackPNG(pack, std::move(data));
+
+ file.close();
+ }
+
+ zip.close();
+}
+
+// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+void processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
+{
+ try {
+ auto json_doc = QJsonDocument::fromJson(raw_data);
+ auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
+
+ pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
+ pack.setDescription(Json::ensureString(pack_obj, "description", ""));
+ } catch (Json::JsonException& e) {
+ qWarning() << "JsonException: " << e.what() << e.cause();
+ }
+}
+
+void processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
+{
+ auto img = QImage::fromData(raw_data);
+ if (!img.isNull()) {
+ pack.setImage(img);
+ } else {
+ qWarning() << "Failed to parse pack.png.";
+ }
+}
+} // namespace ResourcePackUtils
+
+LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp)
+ : Task(nullptr, false), m_token(token), m_resource_pack(rp)
+{}
+
+bool LocalResourcePackParseTask::abort()
+{
+ m_aborted = true;
+ return true;
+}
+
+void LocalResourcePackParseTask::executeTask()
+{
+ Q_ASSERT(m_resource_pack.valid());
+
+ if (!ResourcePackUtils::process(m_resource_pack))
+ return;
+
+ if (m_aborted)
+ emitAborted();
+ else
+ emitSucceeded();
+}
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
new file mode 100644
index 00000000..d3c25464
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QDebug>
+#include <QObject>
+
+#include "minecraft/mod/ResourcePack.h"
+
+#include "tasks/Task.h"
+
+namespace ResourcePackUtils {
+bool process(ResourcePack& pack);
+
+void processZIP(ResourcePack& pack);
+void processFolder(ResourcePack& pack);
+
+void processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
+void processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
+} // namespace ResourcePackUtils
+
+class LocalResourcePackParseTask : public Task {
+ Q_OBJECT
+ public:
+ LocalResourcePackParseTask(int token, ResourcePack& rp);
+
+ [[nodiscard]] bool canAbort() const override { return true; }
+ bool abort() override;
+
+ void executeTask() override;
+
+ [[nodiscard]] int token() const { return m_token; }
+
+ private:
+ int m_token;
+
+ ResourcePack& m_resource_pack;
+
+ bool m_aborted = false;
+};
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
index a2e055ba..3a857740 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
@@ -38,11 +38,11 @@
#include "minecraft/mod/MetadataHandler.h"
-ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed)
- : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result())
+ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan)
+ : Task(nullptr, false), m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result())
{}
-void ModFolderLoadTask::run()
+void ModFolderLoadTask::executeTask()
{
if (m_is_indexed) {
// Read metadata first
@@ -52,7 +52,7 @@ void ModFolderLoadTask::run()
// Read JAR files that don't have metadata
m_mods_dir.refresh();
for (auto entry : m_mods_dir.entryInfoList()) {
- Mod::Ptr mod(new Mod(entry));
+ Mod* mod(new Mod(entry));
if (mod->enabled()) {
if (m_result->mods.contains(mod->internal_id())) {
@@ -83,7 +83,23 @@ void ModFolderLoadTask::run()
}
}
- emit succeeded();
+ // Remove orphan metadata to prevent issues
+ // See https://github.com/PolyMC/PolyMC/issues/996
+ if (m_clean_orphan) {
+ QMutableMapIterator<QString, Mod::Ptr> iter(m_result->mods);
+ while (iter.hasNext()) {
+ auto mod = iter.next().value();
+ if (mod->status() == ModStatus::NotInstalled) {
+ mod->destroy(m_index_dir, false);
+ iter.remove();
+ }
+ }
+ }
+
+ if (m_aborted)
+ emit finished();
+ else
+ emitSucceeded();
}
void ModFolderLoadTask::getFromMetadata()
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
index 0b6bb6cc..0f18b8b9 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
@@ -42,8 +42,9 @@
#include <QRunnable>
#include <memory>
#include "minecraft/mod/Mod.h"
+#include "tasks/Task.h"
-class ModFolderLoadTask : public QObject, public QRunnable
+class ModFolderLoadTask : public Task
{
Q_OBJECT
public:
@@ -56,16 +57,26 @@ public:
}
public:
- ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed);
- void run();
-signals:
- void succeeded();
+ ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false);
+
+ [[nodiscard]] bool canAbort() const override { return true; }
+ bool abort() override
+ {
+ m_aborted.store(true);
+ return true;
+ }
+
+
+ void executeTask() override;
private:
void getFromMetadata();
private:
- QDir& m_mods_dir, m_index_dir;
+ QDir m_mods_dir, m_index_dir;
bool m_is_indexed;
+ bool m_clean_orphan;
ResultPtr m_result;
+
+ std::atomic<bool> m_aborted = false;
};
diff --git a/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt b/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt
deleted file mode 100644
index 8d1c8b69..00000000
--- a/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta b/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta
deleted file mode 100644
index 67ee0434..00000000
--- a/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "pack": {
- "pack_format": 1,
- "description": "Some resource pack maybe"
- }
-}
diff --git a/launcher/minecraft/mod/testdata/test_folder/pack.nfo b/launcher/minecraft/mod/testdata/test_folder/pack.nfo
deleted file mode 100644
index 8d1c8b69..00000000
--- a/launcher/minecraft/mod/testdata/test_folder/pack.nfo
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/launcher/minecraft/testdata/1.9-simple.json b/launcher/minecraft/testdata/1.9-simple.json
deleted file mode 100644
index 574c5b06..00000000
--- a/launcher/minecraft/testdata/1.9-simple.json
+++ /dev/null
@@ -1,198 +0,0 @@
-{
- "assets": "1.9",
- "id": "1.9",
- "libraries": [
- {
- "name": "oshi-project:oshi-core:1.1"
- },
- {
- "name": "net.java.dev.jna:jna:3.4.0"
- },
- {
- "name": "net.java.dev.jna:platform:3.4.0"
- },
- {
- "name": "com.ibm.icu:icu4j-core-mojang:51.2"
- },
- {
- "name": "net.sf.jopt-simple:jopt-simple:4.6"
- },
- {
- "name": "com.paulscode:codecjorbis:20101023"
- },
- {
- "name": "com.paulscode:codecwav:20101023"
- },
- {
- "name": "com.paulscode:libraryjavasound:20101123"
- },
- {
- "name": "com.paulscode:librarylwjglopenal:20100824"
- },
- {
- "name": "com.paulscode:soundsystem:20120107"
- },
- {
- "name": "io.netty:netty-all:4.0.23.Final"
- },
- {
- "name": "com.google.guava:guava:17.0"
- },
- {
- "name": "org.apache.commons:commons-lang3:3.3.2"
- },
- {
- "name": "commons-io:commons-io:2.4"
- },
- {
- "name": "commons-codec:commons-codec:1.9"
- },
- {
- "name": "net.java.jinput:jinput:2.0.5"
- },
- {
- "name": "net.java.jutils:jutils:1.0.0"
- },
- {
- "name": "com.google.code.gson:gson:2.2.4"
- },
- {
- "name": "com.mojang:authlib:1.5.22"
- },
- {
- "name": "com.mojang:realms:1.8.4"
- },
- {
- "name": "org.apache.commons:commons-compress:1.8.1"
- },
- {
- "name": "org.apache.httpcomponents:httpclient:4.3.3"
- },
- {
- "name": "commons-logging:commons-logging:1.1.3"
- },
- {
- "name": "org.apache.httpcomponents:httpcore:4.3.2"
- },
- {
- "name": "org.apache.logging.log4j:log4j-api:2.0-beta9"
- },
- {
- "name": "org.apache.logging.log4j:log4j-core:2.0-beta9"
- },
- {
- "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209",
- "rules": [
- {
- "action": "allow"
- },
- {
- "action": "disallow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209",
- "rules": [
- {
- "action": "allow"
- },
- {
- "action": "disallow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "extract": {
- "exclude": [
- "META-INF/"
- ]
- },
- "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209",
- "natives": {
- "linux": "natives-linux",
- "osx": "natives-osx",
- "windows": "natives-windows"
- },
- "rules": [
- {
- "action": "allow"
- },
- {
- "action": "disallow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "name": "org.lwjgl.lwjgl:lwjgl:2.9.2-nightly-20140822",
- "rules": [
- {
- "action": "allow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.2-nightly-20140822",
- "rules": [
- {
- "action": "allow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "extract": {
- "exclude": [
- "META-INF/"
- ]
- },
- "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.2-nightly-20140822",
- "natives": {
- "linux": "natives-linux",
- "osx": "natives-osx",
- "windows": "natives-windows"
- },
- "rules": [
- {
- "action": "allow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "extract": {
- "exclude": [
- "META-INF/"
- ]
- },
- "name": "net.java.jinput:jinput-platform:2.0.5",
- "natives": {
- "linux": "natives-linux",
- "osx": "natives-osx",
- "windows": "natives-windows"
- }
- }
- ],
- "mainClass": "net.minecraft.client.main.Main",
- "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userType ${user_type} --versionType ${version_type}",
- "minimumLauncherVersion": 18,
- "releaseTime": "2016-02-29T13:49:54+00:00",
- "time": "2016-03-01T13:14:53+00:00",
- "type": "release"
-}
diff --git a/launcher/minecraft/testdata/1.9.json b/launcher/minecraft/testdata/1.9.json
deleted file mode 100644
index 697c6059..00000000
--- a/launcher/minecraft/testdata/1.9.json
+++ /dev/null
@@ -1,529 +0,0 @@
-{
- "assetIndex": {
- "id": "1.9",
- "sha1": "cde65b47a43f638653ab1da3848b53f8a7477b16",
- "size": 136916,
- "totalSize": 119917473,
- "url": "https://launchermeta.mojang.com/mc-staging/assets/1.9/cde65b47a43f638653ab1da3848b53f8a7477b16/1.9.json"
- },
- "assets": "1.9",
- "downloads": {
- "client": {
- "sha1": "2f67dfe8953299440d1902f9124f0f2c3a2c940f",
- "size": 8697592,
- "url": "https://launcher.mojang.com/mc/game/1.9/client/2f67dfe8953299440d1902f9124f0f2c3a2c940f/client.jar"
- },
- "server": {
- "sha1": "b4d449cf2918e0f3bd8aa18954b916a4d1880f0d",
- "size": 8848015,
- "url": "https://launcher.mojang.com/mc/game/1.9/server/b4d449cf2918e0f3bd8aa18954b916a4d1880f0d/server.jar"
- }
- },
- "id": "1.9",
- "libraries": [
- {
- "downloads": {
- "artifact": {
- "path": "oshi-project/oshi-core/1.1/oshi-core-1.1.jar",
- "sha1": "9ddf7b048a8d701be231c0f4f95fd986198fd2d8",
- "size": 30973,
- "url": "https://libraries.minecraft.net/oshi-project/oshi-core/1.1/oshi-core-1.1.jar"
- }
- },
- "name": "oshi-project:oshi-core:1.1"
- },
- {
- "downloads": {
- "artifact": {
- "path": "net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar",
- "sha1": "803ff252fedbd395baffd43b37341dc4a150a554",
- "size": 1008730,
- "url": "https://libraries.minecraft.net/net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar"
- }
- },
- "name": "net.java.dev.jna:jna:3.4.0"
- },
- {
- "downloads": {
- "artifact": {
- "path": "net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar",
- "sha1": "e3f70017be8100d3d6923f50b3d2ee17714e9c13",
- "size": 913436,
- "url": "https://libraries.minecraft.net/net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar"
- }
- },
- "name": "net.java.dev.jna:platform:3.4.0"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar",
- "sha1": "63d216a9311cca6be337c1e458e587f99d382b84",
- "size": 1634692,
- "url": "https://libraries.minecraft.net/com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar"
- }
- },
- "name": "com.ibm.icu:icu4j-core-mojang:51.2"
- },
- {
- "downloads": {
- "artifact": {
- "path": "net/sf/jopt-simple/jopt-simple/4.6/jopt-simple-4.6.jar",
- "sha1": "306816fb57cf94f108a43c95731b08934dcae15c",
- "size": 62477,
- "url": "https://libraries.minecraft.net/net/sf/jopt-simple/jopt-simple/4.6/jopt-simple-4.6.jar"
- }
- },
- "name": "net.sf.jopt-simple:jopt-simple:4.6"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar",
- "sha1": "c73b5636faf089d9f00e8732a829577de25237ee",
- "size": 103871,
- "url": "https://libraries.minecraft.net/com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar"
- }
- },
- "name": "com.paulscode:codecjorbis:20101023"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar",
- "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da",
- "size": 5618,
- "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"
- }
- },
- "name": "com.paulscode:codecwav:20101023"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar",
- "sha1": "5c5e304366f75f9eaa2e8cca546a1fb6109348b3",
- "size": 21679,
- "url": "https://libraries.minecraft.net/com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar"
- }
- },
- "name": "com.paulscode:libraryjavasound:20101123"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar",
- "sha1": "73e80d0794c39665aec3f62eee88ca91676674ef",
- "size": 18981,
- "url": "https://libraries.minecraft.net/com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar"
- }
- },
- "name": "com.paulscode:librarylwjglopenal:20100824"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/paulscode/soundsystem/20120107/soundsystem-20120107.jar",
- "sha1": "419c05fe9be71f792b2d76cfc9b67f1ed0fec7f6",
- "size": 65020,
- "url": "https://libraries.minecraft.net/com/paulscode/soundsystem/20120107/soundsystem-20120107.jar"
- }
- },
- "name": "com.paulscode:soundsystem:20120107"
- },
- {
- "downloads": {
- "artifact": {
- "path": "io/netty/netty-all/4.0.23.Final/netty-all-4.0.23.Final.jar",
- "sha1": "0294104aaf1781d6a56a07d561e792c5d0c95f45",
- "size": 1779991,
- "url": "https://libraries.minecraft.net/io/netty/netty-all/4.0.23.Final/netty-all-4.0.23.Final.jar"
- }
- },
- "name": "io.netty:netty-all:4.0.23.Final"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/google/guava/guava/17.0/guava-17.0.jar",
- "sha1": "9c6ef172e8de35fd8d4d8783e4821e57cdef7445",
- "size": 2243036,
- "url": "https://libraries.minecraft.net/com/google/guava/guava/17.0/guava-17.0.jar"
- }
- },
- "name": "com.google.guava:guava:17.0"
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/apache/commons/commons-lang3/3.3.2/commons-lang3-3.3.2.jar",
- "sha1": "90a3822c38ec8c996e84c16a3477ef632cbc87a3",
- "size": 412739,
- "url": "https://libraries.minecraft.net/org/apache/commons/commons-lang3/3.3.2/commons-lang3-3.3.2.jar"
- }
- },
- "name": "org.apache.commons:commons-lang3:3.3.2"
- },
- {
- "downloads": {
- "artifact": {
- "path": "commons-io/commons-io/2.4/commons-io-2.4.jar",
- "sha1": "b1b6ea3b7e4aa4f492509a4952029cd8e48019ad",
- "size": 185140,
- "url": "https://libraries.minecraft.net/commons-io/commons-io/2.4/commons-io-2.4.jar"
- }
- },
- "name": "commons-io:commons-io:2.4"
- },
- {
- "downloads": {
- "artifact": {
- "path": "commons-codec/commons-codec/1.9/commons-codec-1.9.jar",
- "sha1": "9ce04e34240f674bc72680f8b843b1457383161a",
- "size": 263965,
- "url": "https://libraries.minecraft.net/commons-codec/commons-codec/1.9/commons-codec-1.9.jar"
- }
- },
- "name": "commons-codec:commons-codec:1.9"
- },
- {
- "downloads": {
- "artifact": {
- "path": "net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar",
- "sha1": "39c7796b469a600f72380316f6b1f11db6c2c7c4",
- "size": 208338,
- "url": "https://libraries.minecraft.net/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar"
- }
- },
- "name": "net.java.jinput:jinput:2.0.5"
- },
- {
- "downloads": {
- "artifact": {
- "path": "net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar",
- "sha1": "e12fe1fda814bd348c1579329c86943d2cd3c6a6",
- "size": 7508,
- "url": "https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar"
- }
- },
- "name": "net.java.jutils:jutils:1.0.0"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/google/code/gson/gson/2.2.4/gson-2.2.4.jar",
- "sha1": "a60a5e993c98c864010053cb901b7eab25306568",
- "size": 190432,
- "url": "https://libraries.minecraft.net/com/google/code/gson/gson/2.2.4/gson-2.2.4.jar"
- }
- },
- "name": "com.google.code.gson:gson:2.2.4"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/mojang/authlib/1.5.22/authlib-1.5.22.jar",
- "sha1": "afaa8f6df976fcb5520e76ef1d5798c9e6b5c0b2",
- "size": 64539,
- "url": "https://libraries.minecraft.net/com/mojang/authlib/1.5.22/authlib-1.5.22.jar"
- }
- },
- "name": "com.mojang:authlib:1.5.22"
- },
- {
- "downloads": {
- "artifact": {
- "path": "com/mojang/realms/1.8.4/realms-1.8.4.jar",
- "sha1": "15f8dc326c97a96dee6e65392e145ad6d1cb46cb",
- "size": 1131574,
- "url": "https://libraries.minecraft.net/com/mojang/realms/1.8.4/realms-1.8.4.jar"
- }
- },
- "name": "com.mojang:realms:1.8.4"
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar",
- "sha1": "a698750c16740fd5b3871425f4cb3bbaa87f529d",
- "size": 365552,
- "url": "https://libraries.minecraft.net/org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar"
- }
- },
- "name": "org.apache.commons:commons-compress:1.8.1"
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar",
- "sha1": "18f4247ff4572a074444572cee34647c43e7c9c7",
- "size": 589512,
- "url": "https://libraries.minecraft.net/org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar"
- }
- },
- "name": "org.apache.httpcomponents:httpclient:4.3.3"
- },
- {
- "downloads": {
- "artifact": {
- "path": "commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar",
- "sha1": "f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f",
- "size": 62050,
- "url": "https://libraries.minecraft.net/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar"
- }
- },
- "name": "commons-logging:commons-logging:1.1.3"
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar",
- "sha1": "31fbbff1ddbf98f3aa7377c94d33b0447c646b6e",
- "size": 282269,
- "url": "https://libraries.minecraft.net/org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar"
- }
- },
- "name": "org.apache.httpcomponents:httpcore:4.3.2"
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar",
- "sha1": "1dd66e68cccd907880229f9e2de1314bd13ff785",
- "size": 108161,
- "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar"
- }
- },
- "name": "org.apache.logging.log4j:log4j-api:2.0-beta9"
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar",
- "sha1": "678861ba1b2e1fccb594bb0ca03114bb05da9695",
- "size": 681134,
- "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar"
- }
- },
- "name": "org.apache.logging.log4j:log4j-core:2.0-beta9"
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar",
- "sha1": "697517568c68e78ae0b4544145af031c81082dfe",
- "size": 1047168,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar"
- }
- },
- "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209",
- "rules": [
- {
- "action": "allow"
- },
- {
- "action": "disallow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar",
- "sha1": "d51a7c040a721d13efdfbd34f8b257b2df882ad0",
- "size": 173887,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar"
- }
- },
- "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209",
- "rules": [
- {
- "action": "allow"
- },
- {
- "action": "disallow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar",
- "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33",
- "size": 22,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar"
- },
- "classifiers": {
- "natives-linux": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar",
- "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b",
- "size": 578680,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar"
- },
- "natives-osx": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar",
- "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed",
- "size": 426822,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"
- },
- "natives-windows": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar",
- "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0",
- "size": 613748,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar"
- }
- }
- },
- "extract": {
- "exclude": [
- "META-INF/"
- ]
- },
- "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209",
- "natives": {
- "linux": "natives-linux",
- "osx": "natives-osx",
- "windows": "natives-windows"
- },
- "rules": [
- {
- "action": "allow"
- },
- {
- "action": "disallow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/lwjgl/lwjgl/lwjgl/2.9.2-nightly-20140822/lwjgl-2.9.2-nightly-20140822.jar",
- "sha1": "7707204c9ffa5d91662de95f0a224e2f721b22af",
- "size": 1045632,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.2-nightly-20140822/lwjgl-2.9.2-nightly-20140822.jar"
- }
- },
- "name": "org.lwjgl.lwjgl:lwjgl:2.9.2-nightly-20140822",
- "rules": [
- {
- "action": "allow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "downloads": {
- "artifact": {
- "path": "org/lwjgl/lwjgl/lwjgl_util/2.9.2-nightly-20140822/lwjgl_util-2.9.2-nightly-20140822.jar",
- "sha1": "f0e612c840a7639c1f77f68d72a28dae2f0c8490",
- "size": 173887,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.2-nightly-20140822/lwjgl_util-2.9.2-nightly-20140822.jar"
- }
- },
- "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.2-nightly-20140822",
- "rules": [
- {
- "action": "allow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "downloads": {
- "classifiers": {
- "natives-linux": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-linux.jar",
- "sha1": "d898a33b5d0a6ef3fed3a4ead506566dce6720a5",
- "size": 578539,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-linux.jar"
- },
- "natives-osx": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-osx.jar",
- "sha1": "79f5ce2fea02e77fe47a3c745219167a542121d7",
- "size": 468116,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-osx.jar"
- },
- "natives-windows": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-windows.jar",
- "sha1": "78b2a55ce4dc29c6b3ec4df8ca165eba05f9b341",
- "size": 613680,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-windows.jar"
- }
- }
- },
- "extract": {
- "exclude": [
- "META-INF/"
- ]
- },
- "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.2-nightly-20140822",
- "natives": {
- "linux": "natives-linux",
- "osx": "natives-osx",
- "windows": "natives-windows"
- },
- "rules": [
- {
- "action": "allow",
- "os": {
- "name": "osx"
- }
- }
- ]
- },
- {
- "downloads": {
- "classifiers": {
- "natives-linux": {
- "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar",
- "sha1": "7ff832a6eb9ab6a767f1ade2b548092d0fa64795",
- "size": 10362,
- "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar"
- },
- "natives-osx": {
- "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar",
- "sha1": "53f9c919f34d2ca9de8c51fc4e1e8282029a9232",
- "size": 12186,
- "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar"
- },
- "natives-windows": {
- "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-windows.jar",
- "sha1": "385ee093e01f587f30ee1c8a2ee7d408fd732e16",
- "size": 155179,
- "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-windows.jar"
- }
- }
- },
- "extract": {
- "exclude": [
- "META-INF/"
- ]
- },
- "name": "net.java.jinput:jinput-platform:2.0.5",
- "natives": {
- "linux": "natives-linux",
- "osx": "natives-osx",
- "windows": "natives-windows"
- }
- }
- ],
- "mainClass": "net.minecraft.client.main.Main",
- "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userType ${user_type} --versionType ${version_type}",
- "minimumLauncherVersion": 18,
- "releaseTime": "2016-02-29T13:49:54+00:00",
- "time": "2016-03-01T13:14:53+00:00",
- "type": "release"
-}
diff --git a/launcher/minecraft/testdata/codecwav-20101023.jar b/launcher/minecraft/testdata/codecwav-20101023.jar
deleted file mode 100644
index f5236083..00000000
--- a/launcher/minecraft/testdata/codecwav-20101023.jar
+++ /dev/null
@@ -1 +0,0 @@
-dummy test file.
diff --git a/launcher/minecraft/testdata/lib-native-arch.json b/launcher/minecraft/testdata/lib-native-arch.json
deleted file mode 100644
index 501826ae..00000000
--- a/launcher/minecraft/testdata/lib-native-arch.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "downloads": {
- "classifiers": {
- "natives-osx": {
- "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar",
- "sha1": "62503ee712766cf77f97252e5902786fd834b8c5",
- "size": 418331,
- "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar"
- },
- "natives-windows-32": {
- "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar",
- "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23",
- "size": 386792,
- "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"
- },
- "natives-windows-64": {
- "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar",
- "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9",
- "size": 463390,
- "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"
- }
- }
- },
- "extract": {
- "exclude": [
- "META-INF/"
- ]
- },
- "name": "tv.twitch:twitch-platform:5.16",
- "natives": {
- "linux": "natives-linux",
- "osx": "natives-osx",
- "windows": "natives-windows-${arch}"
- },
- "rules": [
- {
- "action": "allow"
- },
- {
- "action": "disallow",
- "os": {
- "name": "linux"
- }
- }
- ]
-}
diff --git a/launcher/minecraft/testdata/lib-native.json b/launcher/minecraft/testdata/lib-native.json
deleted file mode 100644
index 5b9f3b55..00000000
--- a/launcher/minecraft/testdata/lib-native.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- "downloads": {
- "artifact": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar",
- "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33",
- "size": 22,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar"
- },
- "classifiers": {
- "natives-linux": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar",
- "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b",
- "size": 578680,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar"
- },
- "natives-osx": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar",
- "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed",
- "size": 426822,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"
- },
- "natives-windows": {
- "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar",
- "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0",
- "size": 613748,
- "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar"
- }
- }
- },
- "extract": {
- "exclude": [
- "META-INF/"
- ]
- },
- "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209",
- "natives": {
- "linux": "natives-linux",
- "osx": "natives-osx",
- "windows": "natives-windows"
- },
- "rules": [
- {
- "action": "allow"
- },
- {
- "action": "disallow",
- "os": {
- "name": "osx"
- }
- }
- ]
-}
diff --git a/launcher/minecraft/testdata/lib-simple.json b/launcher/minecraft/testdata/lib-simple.json
deleted file mode 100644
index 90bbff07..00000000
--- a/launcher/minecraft/testdata/lib-simple.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "downloads": {
- "artifact": {
- "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar",
- "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da",
- "size": 5618,
- "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"
- }
- },
- "name": "com.paulscode:codecwav:20101023"
-}
diff --git a/launcher/minecraft/testdata/testname-testversion-linux-32.jar b/launcher/minecraft/testdata/testname-testversion-linux-32.jar
deleted file mode 100644
index f5236083..00000000
--- a/launcher/minecraft/testdata/testname-testversion-linux-32.jar
+++ /dev/null
@@ -1 +0,0 @@
-dummy test file.
diff --git a/launcher/minecraft/update/FMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp
index b6238ce9..7a0bd2f3 100644
--- a/launcher/minecraft/update/FMLLibrariesTask.cpp
+++ b/launcher/minecraft/update/FMLLibrariesTask.cpp
@@ -63,11 +63,12 @@ void FMLLibrariesTask::executeTask()
setStatus(tr("Downloading FML libraries..."));
auto dljob = new NetJob("FML libraries", APPLICATION->network());
auto metacache = APPLICATION->metacache();
+ Net::Download::Options options = Net::Download::Option::MakeEternal;
for (auto &lib : fmlLibsToProcess)
{
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
- dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry));
+ dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options));
}
connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);