diff options
Diffstat (limited to 'api/logic/minecraft')
79 files changed, 1032 insertions, 299 deletions
diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp index 68089080..c01733b6 100644 --- a/api/logic/minecraft/AssetsUtils.cpp +++ b/api/logic/minecraft/AssetsUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h index 356b8c8a..32e57060 100644 --- a/api/logic/minecraft/AssetsUtils.h +++ b/api/logic/minecraft/AssetsUtils.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/GradleSpecifier.h b/api/logic/minecraft/GradleSpecifier.h index 959325c6..60e0a726 100644 --- a/api/logic/minecraft/GradleSpecifier.h +++ b/api/logic/minecraft/GradleSpecifier.h @@ -18,32 +18,35 @@ struct GradleSpecifier { /* org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar - DEBUG 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar" - DEBUG 1 "org.gradle.test.classifiers" - DEBUG 2 "service" - DEBUG 3 "1.0" - DEBUG 4 ":jdk15" - DEBUG 5 "jdk15" - DEBUG 6 "@jar" - DEBUG 7 "jar" + 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar" + 1 "org.gradle.test.classifiers" + 2 "service" + 3 "1.0" + 4 "jdk15" + 5 "jar" */ - QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(:([^:@]+))?" "(@([^:@]+))?"); + QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?"); m_valid = matcher.exactMatch(value); + if(!m_valid) { + m_invalidValue = value; + return *this; + } auto elements = matcher.capturedTexts(); m_groupId = elements[1]; m_artifactId = elements[2]; m_version = elements[3]; - m_classifier = elements[5]; - if(!elements[7].isEmpty()) + m_classifier = elements[4]; + if(!elements[5].isEmpty()) { - m_extension = elements[7]; + m_extension = elements[5]; } return *this; } - operator QString() const + QString serialize() const { - if(!m_valid) - return "INVALID"; + if(!m_valid) { + return m_invalidValue; + } QString retval = m_groupId + ":" + m_artifactId + ":" + m_version; if(!m_classifier.isEmpty()) { @@ -57,6 +60,9 @@ struct GradleSpecifier } QString getFileName() const { + if(!m_valid) { + return QString(); + } QString filename = m_artifactId + '-' + m_version; if(!m_classifier.isEmpty()) { @@ -67,8 +73,9 @@ struct GradleSpecifier } QString toPath(const QString & filenameOverride = QString()) const { - if(!m_valid) - return "INVALID"; + if(!m_valid) { + return QString(); + } QString filename; if(filenameOverride.isEmpty()) { @@ -134,6 +141,7 @@ struct GradleSpecifier return true; } private: + QString m_invalidValue; QString m_groupId; QString m_artifactId; QString m_version; diff --git a/api/logic/minecraft/GradleSpecifier_test.cpp b/api/logic/minecraft/GradleSpecifier_test.cpp index f49ec718..0900c9d8 100644 --- a/api/logic/minecraft/GradleSpecifier_test.cpp +++ b/api/logic/minecraft/GradleSpecifier_test.cpp @@ -31,7 +31,7 @@ slots: { QFETCH(QString, through); - QString converted = GradleSpecifier(through); + QString converted = GradleSpecifier(through).serialize(); QCOMPARE(converted, through); } @@ -68,7 +68,8 @@ slots: GradleSpecifier spec(input); QVERIFY(!spec.valid()); - QCOMPARE(spec.operator QString(), QString("INVALID")); + QCOMPARE(spec.serialize(), input); + QCOMPARE(spec.toPath(), QString()); } }; diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp index b3c7657c..f2293679 100644 --- a/api/logic/minecraft/Library.cpp +++ b/api/logic/minecraft/Library.cpp @@ -94,13 +94,13 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads( auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); auto dl = Net::Download::makeCached(url, entry, options); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url; + qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; out.append(dl); } else { out.append(Net::Download::makeCached(url, entry, options)); - qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url; + qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; } return true; }; @@ -145,7 +145,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads( } else { - qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS"; + qDebug() << "Ignoring native library" << m_name.serialize() << "because it has no classifier for current OS"; } } else @@ -157,7 +157,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads( } else { - qDebug() << "Ignoring java library" << m_name << "because it has no artifact"; + qDebug() << "Ignoring java library" << m_name.serialize() << "because it has no artifact"; } } } diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp index c3d6150d..75bb4db1 100644 --- a/api/logic/minecraft/Library_test.cpp +++ b/api/logic/minecraft/Library_test.cpp @@ -18,7 +18,8 @@ private: jsonFile.open(QIODevice::ReadOnly); auto data = jsonFile.readAll(); jsonFile.close(); - return MojangVersionFormat::libraryFromJson(QJsonDocument::fromJson(data).object(), file); + ProblemContainer problems; + return MojangVersionFormat::libraryFromJson(problems, QJsonDocument::fromJson(data).object(), file); } // get absolute path to expected storage, assuming default cache prefix QStringList getStorage(QString relative) diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp index db259395..dbf9f816 100644 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ b/api/logic/minecraft/MinecraftInstance.cpp @@ -1,5 +1,5 @@ #include "MinecraftInstance.h" -#include <minecraft/launch/CreateServerResourcePacksFolder.h> +#include <minecraft/launch/CreateGameFolders.h> #include <minecraft/launch/ExtractNatives.h> #include <minecraft/launch/PrintInstanceInfo.h> #include <settings/Setting.h> @@ -12,6 +12,7 @@ #include <java/JavaVersion.h> #include "launch/LaunchTask.h" +#include "launch/steps/LookupServerAddress.h" #include "launch/steps/PostLaunchCommand.h" #include "launch/steps/Update.h" #include "launch/steps/PreLaunchCommand.h" @@ -22,12 +23,15 @@ #include "minecraft/launch/ClaimAccount.h" #include "minecraft/launch/ReconstructAssets.h" #include "minecraft/launch/ScanModFolders.h" +#include "minecraft/launch/VerifyJavaInstall.h" #include "java/launch/CheckJava.h" #include "java/JavaUtils.h" #include "meta/Index.h" #include "meta/VersionList.h" #include "mod/ModFolderModel.h" +#include "mod/ResourcePackFolderModel.h" +#include "mod/TexturePackFolderModel.h" #include "WorldList.h" #include "icons/IIconList.h" @@ -38,6 +42,7 @@ #include "MinecraftUpdate.h" #include "MinecraftLoadAndCheck.h" #include <minecraft/gameoptions/GameOptions.h> +#include <minecraft/update/FoldersTask.h> #define IBUS "@im=ibus" @@ -100,6 +105,20 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); + // Native library workarounds + auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); + m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); + m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); + + // Game time + auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); + m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); + m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride); + + // Join server on launch, this does not have a global override + m_settings->registerSetting("JoinServerOnLaunch", false); + m_settings->registerSetting("JoinServerOnLaunchAddress", ""); + // DEPRECATED: Read what versions the user configuration thinks should be used m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); m_settings->registerSetting("LWJGLVersion", ""); @@ -384,7 +403,8 @@ static QString replaceTokensIn(QString text, QMap<QString, QString> with) return result; } -QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) const +QStringList MinecraftInstance::processMinecraftArgs( + AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const { auto profile = m_components->getProfile(); QString args_pattern = profile->getMinecraftArguments(); @@ -393,6 +413,12 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) cons args_pattern += " --tweakClass " + tweaker; } + if (serverToJoin && !serverToJoin->address.isEmpty()) + { + args_pattern += " --server " + serverToJoin->address; + args_pattern += " --port " + QString::number(serverToJoin->port); + } + QMap<QString, QString> token_mapping; // yggdrasil! if(session) @@ -429,7 +455,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) cons return parts; } -QString MinecraftInstance::createLaunchScript(AuthSessionPtr session) +QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) { QString launchScript; @@ -450,8 +476,17 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session) launchScript += "appletClass " + appletClass + "\n"; } + if (serverToJoin && !serverToJoin->address.isEmpty()) + { + launchScript += "serverAddress " + serverToJoin->address + "\n"; + launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n"; + } + // generic minecraft params - for (auto param : processMinecraftArgs(session)) + for (auto param : processMinecraftArgs( + session, + nullptr /* When using a launch script, the server parameters are handled by it*/ + )) { launchScript += "param " + param + "\n"; } @@ -501,7 +536,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session) return launchScript; } -QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session) +QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) { QStringList out; out << "Main Class:" << " " + getMainClass() << ""; @@ -520,11 +555,23 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session) out << ""; } + auto settings = this->settings(); + bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); + bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); + if (nativeOpenAL || nativeGLFW) + { + if (nativeOpenAL) + out << "Using system OpenAL."; + if (nativeGLFW) + out << "Using system GLFW."; + out << ""; + } + // libraries and class path. { out << "Libraries:"; QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + auto javaArchitecture = settings->get("JavaArchitecture").toString(); profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); auto printLibFile = [&](const QString & path) { @@ -604,20 +651,20 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session) out << ""; } - auto params = processMinecraftArgs(nullptr); + auto params = processMinecraftArgs(nullptr, serverToJoin); out << "Params:"; out << " " + params.join(' '); out << ""; QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) + if (settings->get("LaunchMaximized").toBool()) { out << "Window size: max (if available)"; } else { - auto width = settings()->get("MinecraftWinWidth").toInt(); - auto height = settings()->get("MinecraftWinHeight").toInt(); + auto width = settings->get("MinecraftWinWidth").toInt(); + auto height = settings->get("MinecraftWinHeight").toInt(); out << "Window size: " + QString::number(width) + " x " + QString::number(height); } out << ""; @@ -751,9 +798,15 @@ QString MinecraftInstance::getStatusbarDescription() QString description; description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); - if(totalTimePlayed() > 0) + if(m_settings->get("ShowGameTime").toBool()) { - description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); + if (lastTimePlayed() > 0) { + description.append(tr(", last played for %1").arg(prettifyTimeDuration(lastTimePlayed()))); + } + + if (totalTimePlayed() > 0) { + description.append(tr(", total played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); + } } if(hasCrashed()) { @@ -778,7 +831,7 @@ shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode) return nullptr; } -shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session) +shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) { // FIXME: get rid of shared_from_this ... auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this())); @@ -805,6 +858,26 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt return process; } + // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) + { + process->appendStep(new CreateGameFolders(pptr)); + } + + if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool()) + { + QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString(); + serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress))); + } + + if(serverToJoin && serverToJoin->port == 25565) + { + // Resolve server address to join on launch + auto *step = new LookupServerAddress(pptr); + step->setLookupAddress(serverToJoin->address); + step->setOutputAddressPtr(serverToJoin); + process->appendStep(step); + } + // run pre-launch command if that's needed if(getPreLaunchCommand().size()) { @@ -829,19 +902,14 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(new ModMinecraftJar(pptr)); } - // if there are any jar mods + // Scan mods folders for mods { process->appendStep(new ScanModFolders(pptr)); } // print some instance info here... { - process->appendStep(new PrintInstanceInfo(pptr, session)); - } - - // create the server-resource-packs folder (workaround for Minecraft bug MCL-3732) - { - process->appendStep(new CreateServerResourcePacksFolder(pptr)); + process->appendStep(new PrintInstanceInfo(pptr, session, serverToJoin)); } // extract native jars if needed @@ -854,6 +922,11 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(new ReconstructAssets(pptr)); } + // verify that minimum Java requirements are met + { + process->appendStep(new VerifyJavaInstall(pptr)); + } + { // actually launch the game auto method = launchMethod(); @@ -862,6 +935,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt auto step = new LauncherPartLaunch(pptr); step->setWorkingDirectory(gameRoot()); step->setAuthSession(session); + step->setServerToJoin(serverToJoin); process->appendStep(step); } else if (method == "DirectJava") @@ -869,6 +943,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt auto step = new DirectJavaLaunch(pptr); step->setWorkingDirectory(gameRoot()); step->setAuthSession(session); + step->setServerToJoin(serverToJoin); process->appendStep(step); } } @@ -925,7 +1000,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const { if (!m_resource_pack_list) { - m_resource_pack_list.reset(new ModFolderModel(resourcePacksDir())); + 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); } @@ -936,7 +1011,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const { if (!m_texture_pack_list) { - m_texture_pack_list.reset(new ModFolderModel(texturePacksDir())); + m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir())); m_texture_pack_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction); } diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h index 985a8a76..05600797 100644 --- a/api/logic/minecraft/MinecraftInstance.h +++ b/api/logic/minecraft/MinecraftInstance.h @@ -5,6 +5,7 @@ #include <QProcess> #include <QDir> #include "multimc_logic_export.h" +#include "minecraft/launch/MinecraftServerTarget.h" class ModFolderModel; class WorldList; @@ -76,11 +77,11 @@ public: ////// Launch stuff ////// shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override; - shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override; + shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override; QStringList extraArguments() const override; - QStringList verboseDescription(AuthSessionPtr session) override; + QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override; QList<Mod> getJarMods() const; - QString createLaunchScript(AuthSessionPtr session); + QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin); /// get arguments passed to java QStringList javaArguments() const; @@ -107,7 +108,7 @@ public: virtual QString getMainClass() const; // FIXME: remove - virtual QStringList processMinecraftArgs(AuthSessionPtr account) const; + virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const; virtual JavaVersion getJavaVersion() const; diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h index 6924ca2d..3435b52b 100644 --- a/api/logic/minecraft/MinecraftLoadAndCheck.h +++ b/api/logic/minecraft/MinecraftLoadAndCheck.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/MinecraftUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp index 68dd437d..8f1565b0 100644 --- a/api/logic/minecraft/MinecraftUpdate.cpp +++ b/api/logic/minecraft/MinecraftUpdate.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/MinecraftUpdate.h b/api/logic/minecraft/MinecraftUpdate.h index eb8e74db..fadebff9 100644 --- a/api/logic/minecraft/MinecraftUpdate.h +++ b/api/logic/minecraft/MinecraftUpdate.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/MojangVersionFormat.cpp b/api/logic/minecraft/MojangVersionFormat.cpp index 33d3c54c..f9cb2228 100644 --- a/api/logic/minecraft/MojangVersionFormat.cpp +++ b/api/logic/minecraft/MojangVersionFormat.cpp @@ -220,7 +220,7 @@ VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument &doc { auto libObj = requireObject(libVal); - auto lib = MojangVersionFormat::libraryFromJson(libObj, filename); + auto lib = MojangVersionFormat::libraryFromJson(*out, libObj, filename); out->libraries.append(lib); } } @@ -283,14 +283,18 @@ QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch } } -LibraryPtr MojangVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename) +LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename) { LibraryPtr out(new Library()); if (!libObj.contains("name")) { throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field"); } - out->m_name = libObj.value("name").toString(); + auto rawName = libObj.value("name").toString(); + out->m_name = rawName; + if(!out->m_name.valid()) { + problems.addProblem(ProblemSeverity::Error, QObject::tr("Library %1 name is broken and cannot be processed.").arg(rawName)); + } Bits::readString(libObj, "url", out->m_repositoryURL); if (libObj.contains("extract")) @@ -333,7 +337,7 @@ LibraryPtr MojangVersionFormat::libraryFromJson(const QJsonObject &libObj, const QJsonObject MojangVersionFormat::libraryToJson(Library *library) { QJsonObject libRoot; - libRoot.insert("name", (QString)library->m_name); + libRoot.insert("name", library->m_name.serialize()); if (!library->m_repositoryURL.isEmpty()) { libRoot.insert("url", library->m_repositoryURL); diff --git a/api/logic/minecraft/MojangVersionFormat.h b/api/logic/minecraft/MojangVersionFormat.h index 76c529e9..2871dae4 100644 --- a/api/logic/minecraft/MojangVersionFormat.h +++ b/api/logic/minecraft/MojangVersionFormat.h @@ -3,6 +3,7 @@ #include <minecraft/VersionFile.h> #include <minecraft/Library.h> #include <QJsonDocument> +#include <ProblemProvider.h> #include "multimc_logic_export.h" @@ -20,6 +21,6 @@ public: static QJsonDocument versionFileToJson(const VersionFilePtr &patch); // libraries - static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); + static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); static QJsonObject libraryToJson(Library *library); }; diff --git a/api/logic/minecraft/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp index 7ac9e2db..d6aaa790 100644 --- a/api/logic/minecraft/OneSixVersionFormat.cpp +++ b/api/logic/minecraft/OneSixVersionFormat.cpp @@ -13,9 +13,9 @@ static void readString(const QJsonObject &root, const QString &key, QString &var } } -LibraryPtr OneSixVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename) +LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename) { - LibraryPtr out = MojangVersionFormat::libraryFromJson(libObj, filename); + LibraryPtr out = MojangVersionFormat::libraryFromJson(problems, libObj, filename); readString(libObj, "MMC-hint", out->m_hint); readString(libObj, "MMC-absulute_url", out->m_absoluteURL); readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL); @@ -115,7 +115,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc { QJsonObject libObj = requireObject(libVal); // parse the jarmod - auto lib = OneSixVersionFormat::jarModFromJson(libObj, filename); + auto lib = OneSixVersionFormat::jarModFromJson(*out, libObj, filename); // and add to jar mods out->jarMods.append(lib); } @@ -126,7 +126,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc { QJsonObject libObj = requireObject(libVal); // parse the jarmod - auto lib = OneSixVersionFormat::plusJarModFromJson(libObj, filename, out->name); + auto lib = OneSixVersionFormat::plusJarModFromJson(*out, libObj, filename, out->name); // and add to jar mods out->jarMods.append(lib); } @@ -138,20 +138,20 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc { QJsonObject libObj = requireObject(libVal); // parse the jarmod - auto lib = OneSixVersionFormat::modFromJson(libObj, filename); + auto lib = OneSixVersionFormat::modFromJson(*out, libObj, filename); // and add to jar mods out->mods.append(lib); } } - auto readLibs = [&](const char * which, QList<LibraryPtr> & out) + auto readLibs = [&](const char * which, QList<LibraryPtr> & outList) { for (auto libVal : requireArray(root.value(which))) { QJsonObject libObj = requireObject(libVal); // parse the library - auto lib = libraryFromJson(libObj, filename); - out.append(lib); + auto lib = libraryFromJson(*out, libObj, filename); + outList.append(lib); } }; bool hasPlusLibs = root.contains("+libraries"); @@ -180,7 +180,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc if(root.contains("mainJar")) { QJsonObject libObj = requireObject(root, "mainJar"); - out->mainJar = libraryFromJson(libObj, filename); + out->mainJar = libraryFromJson(*out, libObj, filename); } // else reconstruct it from downloads and id ... if that's available else if(!out->minecraftVersion.isEmpty()) @@ -330,8 +330,12 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch } } -LibraryPtr OneSixVersionFormat::plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName) -{ +LibraryPtr OneSixVersionFormat::plusJarModFromJson( + ProblemContainer & problems, + const QJsonObject &libObj, + const QString &filename, + const QString &originalName +) { LibraryPtr out(new Library()); if (!libObj.contains("name")) { @@ -366,9 +370,9 @@ LibraryPtr OneSixVersionFormat::plusJarModFromJson(const QJsonObject &libObj, co return out; } -LibraryPtr OneSixVersionFormat::jarModFromJson(const QJsonObject& libObj, const QString& filename) +LibraryPtr OneSixVersionFormat::jarModFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename) { - return libraryFromJson(libObj, filename); + return libraryFromJson(problems, libObj, filename); } @@ -377,9 +381,9 @@ QJsonObject OneSixVersionFormat::jarModtoJson(Library *jarmod) return libraryToJson(jarmod); } -LibraryPtr OneSixVersionFormat::modFromJson(const QJsonObject& libObj, const QString& filename) +LibraryPtr OneSixVersionFormat::modFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename) { - return libraryFromJson(libObj, filename); + return libraryFromJson(problems, libObj, filename); } QJsonObject OneSixVersionFormat::modtoJson(Library *jarmod) diff --git a/api/logic/minecraft/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h index 14ae385c..1a091d88 100644 --- a/api/logic/minecraft/OneSixVersionFormat.h +++ b/api/logic/minecraft/OneSixVersionFormat.h @@ -4,6 +4,7 @@ #include <minecraft/PackProfile.h> #include <minecraft/Library.h> #include <QJsonDocument> +#include <ProblemProvider.h> class OneSixVersionFormat { @@ -13,17 +14,17 @@ public: static QJsonDocument versionFileToJson(const VersionFilePtr &patch); // libraries - static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); + static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); static QJsonObject libraryToJson(Library *library); // DEPRECATED: old 'plus' jar mods generated by the application - static LibraryPtr plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName); + static LibraryPtr plusJarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename, const QString &originalName); // new jar mods derived from libraries - static LibraryPtr jarModFromJson(const QJsonObject &libObj, const QString &filename); + static LibraryPtr jarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); static QJsonObject jarModtoJson(Library * jarmod); // mods, also derived from libraries - static LibraryPtr modFromJson(const QJsonObject &libObj, const QString &filename); + static LibraryPtr modFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); static QJsonObject modtoJson(Library * jarmod); }; diff --git a/api/logic/minecraft/OpSys.cpp b/api/logic/minecraft/OpSys.cpp index 5e1e6c2b..f6a4ed1c 100644 --- a/api/logic/minecraft/OpSys.cpp +++ b/api/logic/minecraft/OpSys.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/OpSys.h b/api/logic/minecraft/OpSys.h index 58852acb..63c750b1 100644 --- a/api/logic/minecraft/OpSys.h +++ b/api/logic/minecraft/OpSys.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/PackProfile.cpp b/api/logic/minecraft/PackProfile.cpp index 15f2f55d..f6918116 100644 --- a/api/logic/minecraft/PackProfile.cpp +++ b/api/logic/minecraft/PackProfile.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/PackProfile.h b/api/logic/minecraft/PackProfile.h index 66cd5e3e..e55e6a58 100644 --- a/api/logic/minecraft/PackProfile.h +++ b/api/logic/minecraft/PackProfile.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -114,6 +114,10 @@ public: /// get the profile component by index Component * getComponent(int index); + /// Add the component to the internal list of patches + // todo(merged): is this the best approach + void appendComponent(ComponentPtr component); + private: void scheduleSave(); bool saveIsScheduled() const; @@ -121,8 +125,6 @@ private: /// apply the component patches. Catches all the errors and returns true/false for success/failure void invalidateLaunchProfile(); - /// Add the component to the internal list of patches - void appendComponent(ComponentPtr component); /// insert component so that its index is ideally the specified one (returns real index) void insertComponent(size_t index, ComponentPtr component); diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp index cd10504f..af2861e3 100644 --- a/api/logic/minecraft/Rule.cpp +++ b/api/logic/minecraft/Rule.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/Rule.h b/api/logic/minecraft/Rule.h index ef10a6c3..7aa34d96 100644 --- a/api/logic/minecraft/Rule.h +++ b/api/logic/minecraft/Rule.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/VersionFilterData.cpp b/api/logic/minecraft/VersionFilterData.cpp index 11f7eea9..38e7b60c 100644 --- a/api/logic/minecraft/VersionFilterData.cpp +++ b/api/logic/minecraft/VersionFilterData.cpp @@ -7,18 +7,18 @@ VersionFilterData::VersionFilterData() { // 1.3.* auto libs13 = - QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}}; + QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"}}; fmlLibsMapping["1.3.2"] = libs13; // 1.4.* auto libs14 = QList<FMLlib>{ - {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}, - {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}}; + {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"}, + {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb"}}; fmlLibsMapping["1.4"] = libs14; fmlLibsMapping["1.4.1"] = libs14; @@ -31,30 +31,30 @@ VersionFilterData::VersionFilterData() // 1.5 fmlLibsMapping["1.5"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"}, + {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8"}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}}; // 1.5.1 fmlLibsMapping["1.5.1"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"}, + {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6"}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}}; // 1.5.2 fmlLibsMapping["1.5.2"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"}, + {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9"}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}}; // don't use installers for those. forgeInstallerBlacklist = QSet<QString>({"1.5.2"}); @@ -65,4 +65,7 @@ VersionFilterData::VersionFilterData() QSet<QString>{"net.java.jinput:jinput", "net.java.jinput:jinput-platform", "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; + + java8BeginsDate = timeFromS3Time("2017-03-30T09:32:19+00:00"); + java16BeginsDate = timeFromS3Time("2021-05-12T11:19:15+00:00"); } diff --git a/api/logic/minecraft/VersionFilterData.h b/api/logic/minecraft/VersionFilterData.h index 88e91f11..d100acc3 100644 --- a/api/logic/minecraft/VersionFilterData.h +++ b/api/logic/minecraft/VersionFilterData.h @@ -10,7 +10,6 @@ struct FMLlib { QString filename; QString checksum; - bool ours; }; struct VersionFilterData @@ -24,5 +23,9 @@ struct VersionFilterData QDateTime legacyCutoffDate; // Libraries that belong to LWJGL QSet<QString> lwjglWhitelist; + // release date of first version to require Java 8 (17w13a) + QDateTime java8BeginsDate; + // release data of first version to require Java 16 (21w19a) + QDateTime java16BeginsDate; }; extern VersionFilterData MULTIMC_LOGIC_EXPORT g_VersionFilterData; diff --git a/api/logic/minecraft/World.cpp b/api/logic/minecraft/World.cpp index 17dbf4ae..a2b4dac7 100644 --- a/api/logic/minecraft/World.cpp +++ b/api/logic/minecraft/World.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2019 MultiMC Contributors +/* Copyright 2015-2021 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,36 @@ #include <QCoreApplication> -QString gameTypeToString(GameType type) +#include <nonstd/optional> + +using nonstd::optional; +using nonstd::nullopt; + +GameType::GameType(nonstd::optional<int> original): + original(original) +{ + if(!original) { + return; + } + switch(*original) { + case 0: + type = GameType::Survival; + break; + case 1: + type = GameType::Creative; + break; + case 2: + type = GameType::Adventure; + break; + case 3: + type = GameType::Spectator; + break; + default: + break; + } +} + +QString GameType::toTranslatedString() const { switch (type) { @@ -47,7 +76,31 @@ QString gameTypeToString(GameType type) default: break; } - return QObject::tr("Unknown"); + if(original) { + return QCoreApplication::translate("GameType", "Unknown (%1)").arg(*original); + } + return QCoreApplication::translate("GameType", "Undefined"); +} + +QString GameType::toLogString() const +{ + switch (type) + { + case GameType::Survival: + return "Survival"; + case GameType::Creative: + return "Creative"; + case GameType::Adventure: + return "Adventure"; + case GameType::Spectator: + return "Spectator"; + default: + break; + } + if(original) { + return QString("Unknown (%1)").arg(*original); + } + return "Undefined"; } std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data) @@ -58,15 +111,22 @@ std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data) return nullptr; } std::istringstream foo(std::string(output.constData(), output.size())); - auto pair = nbt::io::read_compound(foo); + try { + auto pair = nbt::io::read_compound(foo); - if(pair.first != "") - return nullptr; + if(pair.first != "") + return nullptr; - if(pair.second == nullptr) - return nullptr; + if(pair.second == nullptr) + return nullptr; - return std::move(pair.second); + return std::move(pair.second); + } + catch (const nbt::io::input_error &e) + { + qWarning() << "Unable to parse level.dat:" << e.what(); + return nullptr; + } } QByteArray serializeLevelDat(nbt::tag_compound * levelInfo) @@ -138,14 +198,31 @@ void World::repath(const QFileInfo &file) m_folderName = file.fileName(); if(file.isFile() && file.suffix() == "zip") { + m_iconFile = QString(); readFromZip(file); } else if(file.isDir()) { + QFileInfo assumedIconPath(file.absoluteFilePath() + "/icon.png"); + if(assumedIconPath.exists()) { + m_iconFile = assumedIconPath.absoluteFilePath(); + } readFromFS(file); } } +bool World::resetIcon() +{ + if(m_iconFile.isNull()) { + return false; + } + if(QFile(m_iconFile).remove()) { + m_iconFile = QString(); + return true; + } + return false; +} + void World::readFromFS(const QFileInfo &file) { auto bytes = getLevelDatDataFromFS(file); @@ -212,7 +289,7 @@ bool World::install(const QString &to, const QString &name) { return false; } - ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty(); + ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath); } else if(m_containerFile.isDir()) { @@ -271,14 +348,16 @@ bool World::rename(const QString &newName) return true; } -static QString read_string (nbt::value& parent, const char * name, const QString & fallback = QString()) +namespace { + +optional<QString> read_string (nbt::value& parent, const char * name) { try { auto &namedValue = parent.at(name); if(namedValue.get_type() != nbt::tag_type::String) { - return fallback; + return nullopt; } auto & tag_str = namedValue.as<nbt::tag_string>(); return QString::fromStdString(tag_str.get()); @@ -286,25 +365,25 @@ static QString read_string (nbt::value& parent, const char * name, const QString catch (const std::out_of_range &e) { // fallback for old world formats - qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback; - return fallback; + qWarning() << "String NBT tag" << name << "could not be found."; + return nullopt; } catch (const std::bad_cast &e) { // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback; - return fallback; + qWarning() << "NBT tag" << name << "could not be converted to string."; + return nullopt; } } -static int64_t read_long (nbt::value& parent, const char * name, const int64_t & fallback = 0) +optional<int64_t> read_long (nbt::value& parent, const char * name) { try { auto &namedValue = parent.at(name); if(namedValue.get_type() != nbt::tag_type::Long) { - return fallback; + return nullopt; } auto & tag_str = namedValue.as<nbt::tag_long>(); return tag_str.get(); @@ -312,25 +391,25 @@ static int64_t read_long (nbt::value& parent, const char * name, const int64_t & catch (const std::out_of_range &e) { // fallback for old world formats - qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback; - return fallback; + qWarning() << "Long NBT tag" << name << "could not be found."; + return nullopt; } catch (const std::bad_cast &e) { // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback; - return fallback; + qWarning() << "NBT tag" << name << "could not be converted to long."; + return nullopt; } } -static int read_int (nbt::value& parent, const char * name, const int & fallback = 0) +optional<int> read_int (nbt::value& parent, const char * name) { try { auto &namedValue = parent.at(name); if(namedValue.get_type() != nbt::tag_type::Int) { - return fallback; + return nullopt; } auto & tag_str = namedValue.as<nbt::tag_int>(); return tag_str.get(); @@ -338,62 +417,72 @@ static int read_int (nbt::value& parent, const char * name, const int & fallback catch (const std::out_of_range &e) { // fallback for old world formats - qWarning() << "Int NBT tag" << name << "could not be found. Defaulting to" << fallback; - return fallback; + qWarning() << "Int NBT tag" << name << "could not be found."; + return nullopt; } catch (const std::bad_cast &e) { // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to int. Defaulting to" << fallback; - return fallback; + qWarning() << "NBT tag" << name << "could not be converted to int."; + return nullopt; } } +GameType read_gametype(nbt::value& parent, const char * name) { + return GameType(read_int(parent, name)); +} + +} + void World::loadFromLevelDat(QByteArray data) { - try + auto levelData = parseLevelDat(data); + if(!levelData) { - auto levelData = parseLevelDat(data); - if(!levelData) - { - is_valid = false; - return; - } - - auto &val = levelData->at("Data"); - is_valid = val.get_type() == nbt::tag_type::Compound; - if(!is_valid) - return; + is_valid = false; + return; + } - m_actualName = read_string(val, "LevelName", m_folderName); + nbt::value * valPtr = nullptr; + try { + valPtr = &levelData->at("Data"); + } + catch (const std::out_of_range &e) { + qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what(); + is_valid = false; + return; + } + nbt::value &val = *valPtr; + is_valid = val.get_type() == nbt::tag_type::Compound; + if(!is_valid) + return; - int64_t temp = read_long(val, "LastPlayed", 0); - if(temp == 0) - { - m_lastPlayed = levelDatTime; - } - else - { - m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp); - } + auto name = read_string(val, "LevelName"); + m_actualName = name ? *name : m_folderName; - int GameType_val = read_int(val, "GameType", 0); - m_gameType = (GameType) GameType_val; + auto timestamp = read_long(val, "LastPlayed"); + m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime; - m_randomSeed = read_long(val, "RandomSeed", 0); + m_gameType = read_gametype(val, "GameType"); - qDebug() << "World Name:" << m_actualName; - qDebug() << "Last Played:" << m_lastPlayed.toString(); - qDebug() << "Seed:" << m_randomSeed; - qDebug() << "GameMode:" << GameType_val; + optional<int64_t> randomSeed; + try { + auto &WorldGen_val = val.at("WorldGenSettings"); + randomSeed = read_long(WorldGen_val, "seed"); } - catch (const nbt::io::input_error &e) - { - qWarning() << "Unable to load" << m_folderName << ":" << e.what(); - is_valid = false; - return; + catch (const std::out_of_range &) {} + if(!randomSeed) { + randomSeed = read_long(val, "RandomSeed"); + } + m_randomSeed = randomSeed ? *randomSeed : 0; + + qDebug() << "World Name:" << m_actualName; + qDebug() << "Last Played:" << m_lastPlayed.toString(); + if(randomSeed) { + qDebug() << "Seed:" << *randomSeed; } + qDebug() << "GameType:" << m_gameType.toLogString(); } bool World::replace(World &with) diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h index 818701fa..1d94d54d 100644 --- a/api/logic/minecraft/World.h +++ b/api/logic/minecraft/World.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2019 MultiMC Contributors +/* Copyright 2015-2021 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,27 @@ #pragma once #include <QFileInfo> #include <QDateTime> +#include <nonstd/optional> #include "multimc_logic_export.h" -enum class GameType -{ - Survival, - Creative, - Adventure, - Spectator +struct MULTIMC_LOGIC_EXPORT GameType { + GameType() = default; + GameType (nonstd::optional<int> original); + + QString toTranslatedString() const; + QString toLogString() const; + + enum + { + Unknown = -1, + Survival = 0, + Creative, + Adventure, + Spectator + } type = Unknown; + nonstd::optional<int> original; }; -QString MULTIMC_LOGIC_EXPORT gameTypeToString(GameType type); class MULTIMC_LOGIC_EXPORT World { @@ -40,6 +50,10 @@ public: { return m_actualName; } + QString iconFile() const + { + return m_iconFile; + } QDateTime lastPlayed() const { return m_lastPlayed; @@ -70,6 +84,8 @@ public: bool replace(World &with); // change the world's filesystem path (used by world lists for *MAGIC* purposes) void repath(const QFileInfo &file); + // remove the icon file, if any + bool resetIcon(); bool rename(const QString &to); bool install(const QString &to, const QString &name= QString()); @@ -88,9 +104,10 @@ protected: QString m_containerOffsetPath; QString m_folderName; QString m_actualName; + QString m_iconFile; QDateTime levelDatTime; QDateTime m_lastPlayed; int64_t m_randomSeed = 0; - GameType m_gameType = GameType::Survival; + GameType m_gameType; bool is_valid = false; }; diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp index b7a24434..f6309dbd 100644 --- a/api/logic/minecraft/WorldList.cpp +++ b/api/logic/minecraft/WorldList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2019 MultiMC Contributors +/* Copyright 2015-2021 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -136,6 +136,19 @@ bool WorldList::deleteWorlds(int first, int last) return true; } +bool WorldList::resetIcon(int row) +{ + if (row >= worlds.size() || row < 0) + return false; + World &m = worlds[row]; + if(m.resetIcon()) { + emit dataChanged(index(row), index(row), {WorldList::IconFileRole}); + return true; + } + return false; +} + + int WorldList::columnCount(const QModelIndex &parent) const { return 3; @@ -162,7 +175,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const return world.name(); case GameModeColumn: - return gameTypeToString(world.gameType()); + return world.gameType().toTranslatedString(); case LastPlayedColumn: return world.lastPlayed(); @@ -195,6 +208,10 @@ QVariant WorldList::data(const QModelIndex &index, int role) const { return world.lastPlayed(); } + case IconFileRole: + { + return world.iconFile(); + } default: return QVariant(); } diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h index 37ad330a..740b1461 100644 --- a/api/logic/minecraft/WorldList.h +++ b/api/logic/minecraft/WorldList.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2019 MultiMC Contributors +/* Copyright 2015-2021 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,8 @@ public: SeedRole, NameRole, GameModeRole, - LastPlayedRole + LastPlayedRole, + IconFileRole }; WorldList(const QString &dir); @@ -81,6 +82,9 @@ public: /// Deletes the mod at the given index. virtual bool deleteWorld(int index); + /// Removes the world icon, if any + virtual bool resetIcon(int index); + /// Deletes all the selected mods virtual bool deleteWorlds(int first, int last); diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp index 657e0009..f5853fe3 100644 --- a/api/logic/minecraft/auth/MojangAccount.cpp +++ b/api/logic/minecraft/auth/MojangAccount.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* Copyright 2013-2021 MultiMC Contributors * * Authors: Orochimarufan <orochimarufan.x3@gmail.com> * diff --git a/api/logic/minecraft/auth/MojangAccount.h b/api/logic/minecraft/auth/MojangAccount.h index 7006435e..30a5f2ff 100644 --- a/api/logic/minecraft/auth/MojangAccount.h +++ b/api/logic/minecraft/auth/MojangAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/MojangAccountList.cpp b/api/logic/minecraft/auth/MojangAccountList.cpp index b594c652..e584cb3b 100644 --- a/api/logic/minecraft/auth/MojangAccountList.cpp +++ b/api/logic/minecraft/auth/MojangAccountList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/MojangAccountList.h b/api/logic/minecraft/auth/MojangAccountList.h index 7760b32f..cc3a61a2 100644 --- a/api/logic/minecraft/auth/MojangAccountList.h +++ b/api/logic/minecraft/auth/MojangAccountList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/YggdrasilTask.cpp b/api/logic/minecraft/auth/YggdrasilTask.cpp index 570fe1ea..0857b46b 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.cpp +++ b/api/logic/minecraft/auth/YggdrasilTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/YggdrasilTask.h b/api/logic/minecraft/auth/YggdrasilTask.h index c5352386..8af2e132 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.h +++ b/api/logic/minecraft/auth/YggdrasilTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp index 8286c7a4..2e8dc859 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.h b/api/logic/minecraft/auth/flows/AuthenticateTask.h index e95837a9..4c14eec7 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.h +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/flows/RefreshTask.cpp b/api/logic/minecraft/auth/flows/RefreshTask.cpp index 7a789f42..ecba178d 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.cpp +++ b/api/logic/minecraft/auth/flows/RefreshTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/flows/RefreshTask.h b/api/logic/minecraft/auth/flows/RefreshTask.h index b2dfd261..f0840dda 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.h +++ b/api/logic/minecraft/auth/flows/RefreshTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/flows/ValidateTask.cpp b/api/logic/minecraft/auth/flows/ValidateTask.cpp index 01f0f819..6b3f0a65 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.cpp +++ b/api/logic/minecraft/auth/flows/ValidateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/auth/flows/ValidateTask.h b/api/logic/minecraft/auth/flows/ValidateTask.h index dcb76b2b..986c2e9f 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.h +++ b/api/logic/minecraft/auth/flows/ValidateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/launch/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h index d0892981..c5bd75f3 100644 --- a/api/logic/minecraft/launch/ClaimAccount.h +++ b/api/logic/minecraft/launch/ClaimAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/launch/CreateGameFolders.cpp b/api/logic/minecraft/launch/CreateGameFolders.cpp new file mode 100644 index 00000000..4081e72e --- /dev/null +++ b/api/logic/minecraft/launch/CreateGameFolders.cpp @@ -0,0 +1,28 @@ +#include "CreateGameFolders.h" +#include "minecraft/MinecraftInstance.h" +#include "launch/LaunchTask.h" +#include "FileSystem.h" + +CreateGameFolders::CreateGameFolders(LaunchTask* parent): LaunchStep(parent) +{ +} + +void CreateGameFolders::executeTask() +{ + auto instance = m_parent->instance(); + std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); + + if(!FS::ensureFolderPathExists(minecraftInstance->gameRoot())) + { + emit logLine("Couldn't create the main game folder", MessageLevel::Error); + emitFailed(tr("Couldn't create the main game folder")); + return; + } + + // HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created. + if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) + { + emit logLine("Couldn't create the 'server-resource-packs' folder", MessageLevel::Error); + } + emitSucceeded(); +} diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h b/api/logic/minecraft/launch/CreateGameFolders.h index 00ab2f2d..9c7d3c94 100644 --- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h +++ b/api/logic/minecraft/launch/CreateGameFolders.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -19,13 +19,13 @@ #include <LoggedProcess.h> #include <minecraft/auth/AuthSession.h> -// HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created. -class CreateServerResourcePacksFolder: public LaunchStep +// Create the main .minecraft for the instance and any other necessary folders +class CreateGameFolders: public LaunchStep { Q_OBJECT public: - explicit CreateServerResourcePacksFolder(LaunchTask *parent); - virtual ~CreateServerResourcePacksFolder() {}; + explicit CreateGameFolders(LaunchTask *parent); + virtual ~CreateGameFolders() {}; virtual void executeTask(); virtual bool canAbort() const diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp deleted file mode 100644 index ae426e31..00000000 --- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "CreateServerResourcePacksFolder.h" -#include "minecraft/MinecraftInstance.h" -#include "launch/LaunchTask.h" -#include "FileSystem.h" - -CreateServerResourcePacksFolder::CreateServerResourcePacksFolder(LaunchTask* parent): LaunchStep(parent) -{ -} - -void CreateServerResourcePacksFolder::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) - { - emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error); - } - emitSucceeded(); -} diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.cpp b/api/logic/minecraft/launch/DirectJavaLaunch.cpp index 5c2bb91e..2110384f 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp +++ b/api/logic/minecraft/launch/DirectJavaLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -55,7 +55,7 @@ void DirectJavaLaunch::executeTask() // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); - auto mcArgs = minecraftInstance->processMinecraftArgs(m_session); + auto mcArgs = minecraftInstance->processMinecraftArgs(m_session, m_serverToJoin); args.append(mcArgs); QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); @@ -66,9 +66,9 @@ void DirectJavaLaunch::executeTask() auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); if (realWrapperCommand.isEmpty()) { - QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); + const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found."); + emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal); + emitFailed(tr(reason).arg(wrapperCommand)); return; } emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); @@ -87,18 +87,17 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state) { case LoggedProcess::FailedToStart: { - //: Error message displayed if instace can't start - QString reason = tr("Could not launch minecraft!"); + //: Error message displayed if instance can't start + const char *reason = QT_TR_NOOP("Could not launch minecraft!"); emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); + emitFailed(tr(reason)); return; } case LoggedProcess::Aborted: case LoggedProcess::Crashed: - { m_parent->setPid(-1); - emitFailed("Game crashed."); + emitFailed(tr("Game crashed.")); return; } case LoggedProcess::Finished: @@ -108,7 +107,7 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state) auto exitCode = m_process.exitCode(); if(exitCode != 0) { - emitFailed("Game crashed."); + emitFailed(tr("Game crashed.")); return; } //FIXME: make this work again @@ -118,7 +117,7 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state) break; } case LoggedProcess::Running: - emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); + emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); m_parent->setPid(m_process.processId()); m_parent->instance()->setLastLaunch(); break; diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.h b/api/logic/minecraft/launch/DirectJavaLaunch.h index 9f57272c..58b119b8 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.h +++ b/api/logic/minecraft/launch/DirectJavaLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -19,6 +19,8 @@ #include <LoggedProcess.h> #include <minecraft/auth/AuthSession.h> +#include "MinecraftServerTarget.h" + class DirectJavaLaunch: public LaunchStep { Q_OBJECT @@ -38,6 +40,12 @@ public: { m_session = session; } + + void setServerToJoin(MinecraftServerTargetPtr serverToJoin) + { + m_serverToJoin = std::move(serverToJoin); + } + private slots: void on_state(LoggedProcess::State state); @@ -45,5 +53,6 @@ private: LoggedProcess m_process; QString m_command; AuthSessionPtr m_session; + MinecraftServerTargetPtr m_serverToJoin; }; diff --git a/api/logic/minecraft/launch/ExtractNatives.cpp b/api/logic/minecraft/launch/ExtractNatives.cpp index 253d13bc..d57499aa 100644 --- a/api/logic/minecraft/launch/ExtractNatives.cpp +++ b/api/logic/minecraft/launch/ExtractNatives.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -33,7 +33,7 @@ static QString replaceSuffix (QString target, const QString &suffix, const QStri return target + replacement; } -static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack) +static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW) { QuaZip zip(source); if(!zip.open(QuaZip::mdUnzip)) @@ -48,6 +48,13 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH do { QString name = zip.getCurrentFileName(); + auto lowercase = name.toLower(); + if (nativeGLFW && name.contains("glfw")) { + continue; + } + if (nativeOpenAL && name.contains("openal")) { + continue; + } if(applyJnilibHack) { name = replaceSuffix(name, ".jnilib", ".dylib"); @@ -76,16 +83,20 @@ void ExtractNatives::executeTask() emitSucceeded(); return; } + auto settings = minecraftInstance->settings(); + bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); + bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); + auto outputPath = minecraftInstance->getNativePath(); auto javaVersion = minecraftInstance->getJavaVersion(); bool jniHackEnabled = javaVersion.major() >= 8; for(const auto &source: toExtract) { - if(!unzipNatives(source, outputPath, jniHackEnabled)) + if(!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW)) { - auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); + const char *reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'"); + emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal); + emitFailed(tr(reason).arg(source, outputPath)); } } emitSucceeded(); diff --git a/api/logic/minecraft/launch/ExtractNatives.h b/api/logic/minecraft/launch/ExtractNatives.h index abb3bd7a..094fcd6b 100644 --- a/api/logic/minecraft/launch/ExtractNatives.h +++ b/api/logic/minecraft/launch/ExtractNatives.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.cpp b/api/logic/minecraft/launch/LauncherPartLaunch.cpp index e854a3a6..ee469770 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp +++ b/api/logic/minecraft/launch/LauncherPartLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -59,7 +59,7 @@ void LauncherPartLaunch::executeTask() auto instance = m_parent->instance(); std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - m_launchScript = minecraftInstance->createLaunchScript(m_session); + m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin); QStringList args = minecraftInstance->javaArguments(); QString allArgs = args.join(", "); emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); @@ -118,9 +118,9 @@ void LauncherPartLaunch::executeTask() auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); if (realWrapperCommand.isEmpty()) { - QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); + const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found."); + emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal); + emitFailed(tr(reason).arg(wrapperCommand)); return; } emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); @@ -140,17 +140,16 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) case LoggedProcess::FailedToStart: { //: Error message displayed if instace can't start - QString reason = tr("Could not launch minecraft!"); + const char *reason = QT_TR_NOOP("Could not launch minecraft!"); emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); + emitFailed(tr(reason)); return; } case LoggedProcess::Aborted: case LoggedProcess::Crashed: - { m_parent->setPid(-1); - emitFailed("Game crashed."); + emitFailed(tr("Game crashed.")); return; } case LoggedProcess::Finished: @@ -160,7 +159,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) auto exitCode = m_process.exitCode(); if(exitCode != 0) { - emitFailed("Game crashed."); + emitFailed(tr("Game crashed.")); return; } //FIXME: make this work again @@ -170,7 +169,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) break; } case LoggedProcess::Running: - emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); + emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); m_parent->setPid(m_process.processId()); m_parent->instance()->setLastLaunch(); // send the launch script to the launcher part diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.h b/api/logic/minecraft/launch/LauncherPartLaunch.h index 1bc4a5f8..6a7ee0e5 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.h +++ b/api/logic/minecraft/launch/LauncherPartLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -19,6 +19,8 @@ #include <LoggedProcess.h> #include <minecraft/auth/AuthSession.h> +#include "MinecraftServerTarget.h" + class LauncherPartLaunch: public LaunchStep { Q_OBJECT @@ -39,6 +41,11 @@ public: m_session = session; } + void setServerToJoin(MinecraftServerTargetPtr serverToJoin) + { + m_serverToJoin = std::move(serverToJoin); + } + private slots: void on_state(LoggedProcess::State state); @@ -47,5 +54,7 @@ private: QString m_command; AuthSessionPtr m_session; QString m_launchScript; + MinecraftServerTargetPtr m_serverToJoin; + bool mayProceed = false; }; diff --git a/api/logic/minecraft/launch/MinecraftServerTarget.cpp b/api/logic/minecraft/launch/MinecraftServerTarget.cpp new file mode 100644 index 00000000..569273b6 --- /dev/null +++ b/api/logic/minecraft/launch/MinecraftServerTarget.cpp @@ -0,0 +1,66 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MinecraftServerTarget.h" + +#include <QStringList> + +MinecraftServerTarget MinecraftServerTarget::parse(const QString &fullAddress) { + QStringList split = fullAddress.split(":"); + + // The logic below replicates the exact logic minecraft uses for parsing server addresses. + // While the conversion is not lossless and eats errors, it ensures the same behavior + // within Minecraft and MultiMC when entering server addresses. + if (fullAddress.startsWith("[")) + { + int bracket = fullAddress.indexOf("]"); + if (bracket > 0) + { + QString ipv6 = fullAddress.mid(1, bracket - 1); + QString port = fullAddress.mid(bracket + 1).trimmed(); + + if (port.startsWith(":") && !ipv6.isEmpty()) + { + port = port.mid(1); + split = QStringList({ ipv6, port }); + } + else + { + split = QStringList({ipv6}); + } + } + } + + if (split.size() > 2) + { + split = QStringList({fullAddress}); + } + + QString realAddress = split[0]; + + quint16 realPort = 25565; + if (split.size() > 1) + { + bool ok; + realPort = split[1].toUInt(&ok); + + if (!ok) + { + realPort = 25565; + } + } + + return MinecraftServerTarget { realAddress, realPort }; +} diff --git a/api/logic/minecraft/launch/MinecraftServerTarget.h b/api/logic/minecraft/launch/MinecraftServerTarget.h new file mode 100644 index 00000000..3c5786f4 --- /dev/null +++ b/api/logic/minecraft/launch/MinecraftServerTarget.h @@ -0,0 +1,30 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <memory> + +#include <QString> +#include <multimc_logic_export.h> + +struct MinecraftServerTarget { + QString address; + quint16 port; + + static MULTIMC_LOGIC_EXPORT MinecraftServerTarget parse(const QString &fullAddress); +}; + +typedef std::shared_ptr<MinecraftServerTarget> MinecraftServerTargetPtr; diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp index ba43a68d..93de9d59 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.cpp +++ b/api/logic/minecraft/launch/ModMinecraftJar.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/launch/ModMinecraftJar.h b/api/logic/minecraft/launch/ModMinecraftJar.h index bed5079f..081c6a91 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.h +++ b/api/logic/minecraft/launch/ModMinecraftJar.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.cpp b/api/logic/minecraft/launch/PrintInstanceInfo.cpp index 4cc180fd..0b9611ad 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp +++ b/api/logic/minecraft/launch/PrintInstanceInfo.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -101,6 +101,6 @@ void PrintInstanceInfo::executeTask() #endif logLines(log, MessageLevel::MultiMC); - logLines(instance->verboseDescription(m_session), MessageLevel::MultiMC); + logLines(instance->verboseDescription(m_session, m_serverToJoin), MessageLevel::MultiMC); emitSucceeded(); } diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.h b/api/logic/minecraft/launch/PrintInstanceInfo.h index 1d081f3e..fdc30f31 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.h +++ b/api/logic/minecraft/launch/PrintInstanceInfo.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -18,13 +18,15 @@ #include <launch/LaunchStep.h> #include <memory> #include "minecraft/auth/AuthSession.h" +#include "minecraft/launch/MinecraftServerTarget.h" // FIXME: temporary wrapper for existing task. class PrintInstanceInfo: public LaunchStep { Q_OBJECT public: - explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session) : LaunchStep(parent), m_session(session) {}; + explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) : + LaunchStep(parent), m_session(session), m_serverToJoin(serverToJoin) {}; virtual ~PrintInstanceInfo(){}; virtual void executeTask(); @@ -34,5 +36,6 @@ public: } private: AuthSessionPtr m_session; + MinecraftServerTargetPtr m_serverToJoin; }; diff --git a/api/logic/minecraft/launch/ReconstructAssets.cpp b/api/logic/minecraft/launch/ReconstructAssets.cpp index 3ac320bd..4d206665 100644 --- a/api/logic/minecraft/launch/ReconstructAssets.cpp +++ b/api/logic/minecraft/launch/ReconstructAssets.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/launch/ReconstructAssets.h b/api/logic/minecraft/launch/ReconstructAssets.h index 228fa083..58d7febd 100644 --- a/api/logic/minecraft/launch/ReconstructAssets.h +++ b/api/logic/minecraft/launch/ReconstructAssets.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/launch/ScanModFolders.cpp b/api/logic/minecraft/launch/ScanModFolders.cpp index 485c3dc4..2a0e21b3 100644 --- a/api/logic/minecraft/launch/ScanModFolders.cpp +++ b/api/logic/minecraft/launch/ScanModFolders.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -27,11 +27,16 @@ void ScanModFolders::executeTask() auto loaders = m_inst->loaderModList(); connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone); - loaders->update(); + if(!loaders->update()) { + m_modsDone = true; + } auto cores = m_inst->coreModList(); connect(cores.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone); - cores->update(); + if(!cores->update()) { + m_coreModsDone = true; + } + checkDone(); } void ScanModFolders::modsDone() diff --git a/api/logic/minecraft/launch/ScanModFolders.h b/api/logic/minecraft/launch/ScanModFolders.h index 360df98d..d5989170 100644 --- a/api/logic/minecraft/launch/ScanModFolders.h +++ b/api/logic/minecraft/launch/ScanModFolders.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/launch/VerifyJavaInstall.cpp b/api/logic/minecraft/launch/VerifyJavaInstall.cpp new file mode 100644 index 00000000..657669af --- /dev/null +++ b/api/logic/minecraft/launch/VerifyJavaInstall.cpp @@ -0,0 +1,34 @@ +#include "VerifyJavaInstall.h" + +#include <launch/LaunchTask.h> +#include <minecraft/MinecraftInstance.h> +#include <minecraft/PackProfile.h> +#include <minecraft/VersionFilterData.h> + +void VerifyJavaInstall::executeTask() { + auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); + + auto javaVersion = m_inst->getJavaVersion(); + auto minecraftComponent = m_inst->getPackProfile()->getComponent("net.minecraft"); + + // Java 16 requirement + if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java16BeginsDate) { + if (javaVersion.major() < 16) { + emit logLine("Minecraft 21w19a and above require the use of Java 16", + MessageLevel::Fatal); + emitFailed(tr("Minecraft 21w19a and above require the use of Java 16")); + return; + } + } + // Java 8 requirement + else if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java8BeginsDate) { + if (javaVersion.major() < 8) { + emit logLine("Minecraft 17w13a and above require the use of Java 8", + MessageLevel::Fatal); + emitFailed(tr("Minecraft 17w13a and above require the use of Java 8")); + return; + } + } + + emitSucceeded(); +} diff --git a/api/logic/minecraft/launch/VerifyJavaInstall.h b/api/logic/minecraft/launch/VerifyJavaInstall.h new file mode 100644 index 00000000..a553106d --- /dev/null +++ b/api/logic/minecraft/launch/VerifyJavaInstall.h @@ -0,0 +1,17 @@ +#pragma once + +#include <launch/LaunchStep.h> + +class VerifyJavaInstall : public LaunchStep { + Q_OBJECT + +public: + explicit VerifyJavaInstall(LaunchTask *parent) : LaunchStep(parent) { + }; + ~VerifyJavaInstall() override = default; + + void executeTask() override; + bool canAbort() const override { + return false; + } +}; diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp index b9d68a9c..9f9bda5a 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.cpp +++ b/api/logic/minecraft/legacy/LegacyInstance.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -225,7 +225,7 @@ QString LegacyInstance::getStatusbarDescription() return tr("Instance from previous versions."); } -QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) +QStringList LegacyInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) { QStringList out; diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h index 7c0b94e8..325bac7a 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.h +++ b/api/logic/minecraft/legacy/LegacyInstance.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -111,7 +111,8 @@ public: { return false; } - shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override + shared_qobject_ptr<LaunchTask> createLaunchTask( + AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override { return nullptr; } @@ -125,7 +126,7 @@ public: } QString getStatusbarDescription() override; - QStringList verboseDescription(AuthSessionPtr session) override; + QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override; QProcessEnvironment createEnvironment() override { diff --git a/api/logic/minecraft/legacy/LegacyModList.cpp b/api/logic/minecraft/legacy/LegacyModList.cpp index 23b837c1..7301eb8c 100644 --- a/api/logic/minecraft/legacy/LegacyModList.cpp +++ b/api/logic/minecraft/legacy/LegacyModList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/legacy/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h index 9a7bea50..8881d471 100644 --- a/api/logic/minecraft/legacy/LegacyModList.h +++ b/api/logic/minecraft/legacy/LegacyModList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/mod/LocalModParseTask.cpp b/api/logic/minecraft/mod/LocalModParseTask.cpp index 22ebd7d4..0d6972fb 100644 --- a/api/logic/minecraft/mod/LocalModParseTask.cpp +++ b/api/logic/minecraft/mod/LocalModParseTask.cpp @@ -6,6 +6,7 @@ #include <QJsonValue> #include <quazip.h> #include <quazipfile.h> +#include <toml.h> #include "settings/INIFile.h" #include "FileSystem.h" @@ -90,6 +91,124 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents) return nullptr; } +// https://github.com/MinecraftForge/Documentation/blob/5ab4ba6cf9abc0ac4c0abd96ad187461aefd72af/docs/gettingstarted/structuring.md +std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents) +{ + std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); + + char errbuf[200]; + // top-level table + toml_table_t* tomlData = toml_parse(contents.data(), errbuf, sizeof(errbuf)); + + if(!tomlData) + { + return nullptr; + } + + // array defined by [[mods]] + toml_array_t* tomlModsArr = toml_array_in(tomlData, "mods"); + // we only really care about the first element, since multiple mods in one file is not supported by us at the moment + toml_table_t* tomlModsTable0 = toml_table_at(tomlModsArr, 0); + + // mandatory properties - always in [[mods]] + toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId"); + if(modIdDatum.ok) + { + details->mod_id = modIdDatum.u.s; + // library says this is required for strings + free(modIdDatum.u.s); + } + toml_datum_t versionDatum = toml_string_in(tomlModsTable0, "version"); + if(versionDatum.ok) + { + details->version = versionDatum.u.s; + free(versionDatum.u.s); + } + toml_datum_t displayNameDatum = toml_string_in(tomlModsTable0, "displayName"); + if(displayNameDatum.ok) + { + details->name = displayNameDatum.u.s; + free(displayNameDatum.u.s); + } + toml_datum_t descriptionDatum = toml_string_in(tomlModsTable0, "description"); + if(descriptionDatum.ok) + { + details->description = descriptionDatum.u.s; + free(descriptionDatum.u.s); + } + + // optional properties - can be in the root table or [[mods]] + toml_datum_t authorsDatum = toml_string_in(tomlData, "authors"); + QString authors = ""; + if(authorsDatum.ok) + { + authors = authorsDatum.u.s; + free(authorsDatum.u.s); + } + else + { + authorsDatum = toml_string_in(tomlModsTable0, "authors"); + if(authorsDatum.ok) + { + authors = authorsDatum.u.s; + free(authorsDatum.u.s); + } + } + if(!authors.isEmpty()) + { + // author information is stored as a string now, not a list + details->authors.append(authors); + } + // is credits even used anywhere? including this for completion/parity with old data version + toml_datum_t creditsDatum = toml_string_in(tomlData, "credits"); + QString credits = ""; + if(creditsDatum.ok) + { + authors = creditsDatum.u.s; + free(creditsDatum.u.s); + } + else + { + creditsDatum = toml_string_in(tomlModsTable0, "credits"); + if(creditsDatum.ok) + { + credits = creditsDatum.u.s; + free(creditsDatum.u.s); + } + } + details->credits = credits; + toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL"); + QString homeurl = ""; + if(homeurlDatum.ok) + { + homeurl = homeurlDatum.u.s; + free(homeurlDatum.u.s); + } + else + { + homeurlDatum = toml_string_in(tomlModsTable0, "displayURL"); + if(homeurlDatum.ok) + { + homeurl = homeurlDatum.u.s; + free(homeurlDatum.u.s); + } + } + if(!homeurl.isEmpty()) + { + // fix up url. + if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) + { + homeurl.prepend("http://"); + } + } + details->homeurl = homeurl; + + // this seems to be recursive, so it should free everything + toml_free(tomlData); + + return details; +} + // https://fabricmc.net/wiki/documentation:fabric_mod_json std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents) { @@ -198,7 +317,57 @@ void LocalModParseTask::processAsZip() QuaZipFile file(&zip); - if (zip.setCurrentFile("mcmod.info")) + if (zip.setCurrentFile("META-INF/mods.toml")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadMCModTOML(file.readAll()); + file.close(); + + // to replace ${file.jarVersion} with the actual version, as needed + if (m_result->details && m_result->details->version == "${file.jarVersion}") + { + if (zip.setCurrentFile("META-INF/MANIFEST.MF")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + // quick and dirty line-by-line parser + auto manifestLines = file.readAll().split('\n'); + QString manifestVersion = ""; + for (auto &line : manifestLines) + { + if (QString(line).startsWith("Implementation-Version: ")) + { + manifestVersion = QString(line).remove("Implementation-Version: "); + break; + } + } + + // some mods use ${projectversion} in their build.gradle, causing this mess to show up in MANIFEST.MF + // also keep with forge's behavior of setting the version to "NONE" if none is found + if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") + { + manifestVersion = "NONE"; + } + + m_result->details->version = manifestVersion; + + file.close(); + } + } + + zip.close(); + return; + } + else if (zip.setCurrentFile("mcmod.info")) { if (!file.open(QIODevice::ReadOnly)) { diff --git a/api/logic/minecraft/mod/Mod.cpp b/api/logic/minecraft/mod/Mod.cpp index df8b406d..b6bff29b 100644 --- a/api/logic/minecraft/mod/Mod.cpp +++ b/api/logic/minecraft/mod/Mod.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/mod/Mod.h b/api/logic/minecraft/mod/Mod.h index d787fb48..f77ffd41 100644 --- a/api/logic/minecraft/mod/Mod.h +++ b/api/logic/minecraft/mod/Mod.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/mod/ModFolderModel.cpp b/api/logic/minecraft/mod/ModFolderModel.cpp index 59ccaaba..031eebe5 100644 --- a/api/logic/minecraft/mod/ModFolderModel.cpp +++ b/api/logic/minecraft/mod/ModFolderModel.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/mod/ModFolderModel.h b/api/logic/minecraft/mod/ModFolderModel.h index 03c584a9..b0a76121 100644 --- a/api/logic/minecraft/mod/ModFolderModel.h +++ b/api/logic/minecraft/mod/ModFolderModel.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/api/logic/minecraft/mod/ResourcePackFolderModel.cpp b/api/logic/minecraft/mod/ResourcePackFolderModel.cpp new file mode 100644 index 00000000..f3d7f566 --- /dev/null +++ b/api/logic/minecraft/mod/ResourcePackFolderModel.cpp @@ -0,0 +1,23 @@ +#include "ResourcePackFolderModel.h" + +ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) { +} + +QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role == Qt::ToolTipRole) { + switch (section) { + case ActiveColumn: + return tr("Is the resource pack enabled?"); + case NameColumn: + return tr("The name of the resource pack."); + case VersionColumn: + return tr("The version of the resource pack."); + case DateColumn: + return tr("The date and time this resource pack was last changed (or added)."); + default: + return QVariant(); + } + } + + return ModFolderModel::headerData(section, orientation, role); +} diff --git a/api/logic/minecraft/mod/ResourcePackFolderModel.h b/api/logic/minecraft/mod/ResourcePackFolderModel.h new file mode 100644 index 00000000..47eb4bb2 --- /dev/null +++ b/api/logic/minecraft/mod/ResourcePackFolderModel.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ModFolderModel.h" + +class MULTIMC_LOGIC_EXPORT ResourcePackFolderModel : public ModFolderModel +{ + Q_OBJECT + +public: + explicit ResourcePackFolderModel(const QString &dir); + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; +}; diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.cpp b/api/logic/minecraft/mod/TexturePackFolderModel.cpp new file mode 100644 index 00000000..d5956da1 --- /dev/null +++ b/api/logic/minecraft/mod/TexturePackFolderModel.cpp @@ -0,0 +1,23 @@ +#include "TexturePackFolderModel.h" + +TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) { +} + +QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role == Qt::ToolTipRole) { + switch (section) { + case ActiveColumn: + return tr("Is the texture pack enabled?"); + case NameColumn: + return tr("The name of the texture pack."); + case VersionColumn: + return tr("The version of the texture pack."); + case DateColumn: + return tr("The date and time this texture pack was last changed (or added)."); + default: + return QVariant(); + } + } + + return ModFolderModel::headerData(section, orientation, role); +} diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.h b/api/logic/minecraft/mod/TexturePackFolderModel.h new file mode 100644 index 00000000..d773b17b --- /dev/null +++ b/api/logic/minecraft/mod/TexturePackFolderModel.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ModFolderModel.h" + +class MULTIMC_LOGIC_EXPORT TexturePackFolderModel : public ModFolderModel +{ + Q_OBJECT + +public: + explicit TexturePackFolderModel(const QString &dir); + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; +}; diff --git a/api/logic/minecraft/services/SkinDelete.cpp b/api/logic/minecraft/services/SkinDelete.cpp new file mode 100644 index 00000000..34977257 --- /dev/null +++ b/api/logic/minecraft/services/SkinDelete.cpp @@ -0,0 +1,42 @@ +#include "SkinDelete.h" +#include <QNetworkRequest> +#include <QHttpMultiPart> +#include <Env.h> + +SkinDelete::SkinDelete(QObject *parent, AuthSessionPtr session) + : Task(parent), m_session(session) +{ +} + +void SkinDelete::executeTask() +{ + QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active")); + request.setRawHeader("Authorization", QString("Bearer %1").arg(m_session->access_token).toLocal8Bit()); + QNetworkReply *rep = ENV.qnam().deleteResource(request); + m_reply = std::shared_ptr<QNetworkReply>(rep); + + setStatus(tr("Deleting skin")); + connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); +} + +void SkinDelete::downloadError(QNetworkReply::NetworkError error) +{ + // error happened during download. + qCritical() << "Network error: " << error; + emitFailed(m_reply->errorString()); +} + +void SkinDelete::downloadFinished() +{ + // if the download failed + if (m_reply->error() != QNetworkReply::NetworkError::NoError) + { + emitFailed(QString("Network error: %1").arg(m_reply->errorString())); + m_reply.reset(); + return; + } + emitSucceeded(); +} + diff --git a/api/logic/minecraft/services/SkinDelete.h b/api/logic/minecraft/services/SkinDelete.h new file mode 100644 index 00000000..705ce8ef --- /dev/null +++ b/api/logic/minecraft/services/SkinDelete.h @@ -0,0 +1,30 @@ +#pragma once + +#include <QFile> +#include <QtNetwork/QtNetwork> +#include <memory> +#include <minecraft/auth/AuthSession.h> +#include "tasks/Task.h" +#include "multimc_logic_export.h" + +typedef std::shared_ptr<class SkinDelete> SkinDeletePtr; + +class MULTIMC_LOGIC_EXPORT SkinDelete : public Task +{ + Q_OBJECT +public: + SkinDelete(QObject *parent, AuthSessionPtr session); + virtual ~SkinDelete() = default; + +private: + AuthSessionPtr m_session; + std::shared_ptr<QNetworkReply> m_reply; + +protected: + virtual void executeTask(); + +public slots: + void downloadError(QNetworkReply::NetworkError); + void downloadFinished(); +}; + diff --git a/api/logic/minecraft/SkinUpload.cpp b/api/logic/minecraft/services/SkinUpload.cpp index 83bdf592..4e5a1698 100644 --- a/api/logic/minecraft/SkinUpload.cpp +++ b/api/logic/minecraft/services/SkinUpload.cpp @@ -3,15 +3,14 @@ #include <QHttpMultiPart> #include <Env.h> -QByteArray getModelString(SkinUpload::Model model) { +QByteArray getVariant(SkinUpload::Model model) { switch (model) { - case SkinUpload::STEVE: - return ""; - case SkinUpload::ALEX: - return "slim"; default: qDebug() << "Unknown skin type!"; - return ""; + case SkinUpload::STEVE: + return "CLASSIC"; + case SkinUpload::ALEX: + return "SLIM"; } } @@ -22,25 +21,23 @@ SkinUpload::SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, void SkinUpload::executeTask() { - QNetworkRequest request(QUrl(QString("https://api.mojang.com/user/profile/%1/skin").arg(m_session->uuid))); - request.setRawHeader("Authorization", QString("Bearer: %1").arg(m_session->access_token).toLocal8Bit()); - + QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins")); + request.setRawHeader("Authorization", QString("Bearer %1").arg(m_session->access_token).toLocal8Bit()); QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - QHttpPart model; - model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\"")); - model.setBody(getModelString(m_model)); - QHttpPart skin; skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); - skin.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); + skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); skin.setBody(m_skin); - multiPart->append(model); + QHttpPart model; + model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\"")); + model.setBody(getVariant(m_model)); + multiPart->append(skin); + multiPart->append(model); - QNetworkReply *rep = ENV.qnam().put(request, multiPart); + QNetworkReply *rep = ENV.qnam().post(request, multiPart); m_reply = std::shared_ptr<QNetworkReply>(rep); setStatus(tr("Uploading skin")); diff --git a/api/logic/minecraft/SkinUpload.h b/api/logic/minecraft/services/SkinUpload.h index c77abb03..c77abb03 100644 --- a/api/logic/minecraft/SkinUpload.h +++ b/api/logic/minecraft/services/SkinUpload.h diff --git a/api/logic/minecraft/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp index 85993e81..a05a7c2a 100644 --- a/api/logic/minecraft/update/FMLLibrariesTask.cpp +++ b/api/logic/minecraft/update/FMLLibrariesTask.cpp @@ -64,7 +64,7 @@ void FMLLibrariesTask::executeTask() for (auto &lib : fmlLibsToProcess) { auto entry = metacache->resolveEntry("fmllibs", lib.filename); - QString urlString = (lib.ours ? BuildConfig.FMLLIBS_OUR_BASE_URL : BuildConfig.FMLLIBS_FORGE_BASE_URL) + lib.filename; + QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename; dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); } |