diff options
Diffstat (limited to 'launcher')
46 files changed, 1283 insertions, 371 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 91b7b82c..690a7ee4 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -682,6 +682,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("JavaVendor", ""); m_settings->registerSetting("LastHostname", ""); m_settings->registerSetting("JvmArgs", ""); + m_settings->registerSetting("IgnoreJavaCompatibility", false); // Native library workarounds m_settings->registerSetting("UseNativeOpenAL", false); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 692aebe5..05af3503 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -348,7 +348,7 @@ set(MINECRAFT_SOURCES mojang/PackageManifest.h mojang/PackageManifest.cpp - ) + minecraft/Agent.h) add_unit_test(GradleSpecifier SOURCES minecraft/GradleSpecifier_test.cpp @@ -539,6 +539,8 @@ set(TECHNIC_SOURCES modplatform/technic/SingleZipPackInstallTask.cpp modplatform/technic/SolderPackInstallTask.h modplatform/technic/SolderPackInstallTask.cpp + modplatform/technic/SolderPackManifest.h + modplatform/technic/SolderPackManifest.cpp modplatform/technic/TechnicPackProcessor.h modplatform/technic/TechnicPackProcessor.cpp ) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 35ddc35c..946599c5 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -129,7 +129,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) auto os_arch = results["os.arch"]; auto java_version = results["java.version"]; auto java_vendor = results["java.vendor"]; - bool is_64 = os_arch == "x86_64" || os_arch == "amd64"; + bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64"; result.validity = JavaCheckResult::Validity::Valid; diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index a0a60871..9b745095 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -183,7 +183,7 @@ void JavaListLoadTask::javaCheckerFinished() JavaInstallPtr javaVersion(new JavaInstall()); javaVersion->id = result.javaVersion; - javaVersion->arch = result.mojangPlatform; + javaVersion->arch = result.realPlatform; javaVersion->path = result.path; candidates.append(javaVersion); diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index c2ebb334..3226fae7 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -124,7 +124,8 @@ void CheckJava::checkJavaFinished(JavaCheckResult result) case JavaCheckResult::Validity::Valid: { auto instance = m_parent->instance(); - printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.javaVendor); + printJavaInfo(result.javaVersion.toString(), result.realPlatform, result.javaVendor); + printSystemInfo(true, result.is_64bit); instance->settings()->set("JavaVersion", result.javaVersion.toString()); instance->settings()->set("JavaArchitecture", result.mojangPlatform); instance->settings()->set("JavaVendor", result.javaVendor); @@ -137,8 +138,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result) void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString & vendor) { - emit logLine(QString("Java is version %1, using %2-bit architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::Launcher); - printSystemInfo(true, architecture == "64"); + emit logLine(QString("Java is version %1, using %2 architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::Launcher); } void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit) diff --git a/launcher/minecraft/Agent.h b/launcher/minecraft/Agent.h new file mode 100644 index 00000000..01109daf --- /dev/null +++ b/launcher/minecraft/Agent.h @@ -0,0 +1,36 @@ +#pragma once + +#include <QString> + +#include "Library.h" + +class Agent; + +typedef std::shared_ptr<Agent> AgentPtr; + +class Agent { +public: + Agent(LibraryPtr library, QString &argument) + { + m_library = library; + m_argument = argument; + } + +public: /* methods */ + + LibraryPtr library() { + return m_library; + } + QString argument() { + return m_argument; + } + +protected: /* data */ + + /// The library pointing to the jar this Java agent is contained within + LibraryPtr m_library; + + /// The argument to the Java agent, passed after an = if present + QString m_argument; + +}; diff --git a/launcher/minecraft/LaunchProfile.cpp b/launcher/minecraft/LaunchProfile.cpp index 41705187..39a342ca 100644 --- a/launcher/minecraft/LaunchProfile.cpp +++ b/launcher/minecraft/LaunchProfile.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "LaunchProfile.h" #include <Version.h> @@ -7,11 +42,13 @@ void LaunchProfile::clear() m_minecraftVersionType.clear(); m_minecraftAssets.reset(); m_minecraftArguments.clear(); + m_addnJvmArguments.clear(); m_tweakers.clear(); m_mainClass.clear(); m_appletClass.clear(); m_libraries.clear(); m_mavenFiles.clear(); + m_agents.clear(); m_traits.clear(); m_jarMods.clear(); m_mainJar.reset(); @@ -45,6 +82,11 @@ void LaunchProfile::applyMinecraftArguments(const QString& minecraftArguments) applyString(minecraftArguments, this->m_minecraftArguments); } +void LaunchProfile::applyAddnJvmArguments(const QStringList& addnJvmArguments) +{ + this->m_addnJvmArguments.append(addnJvmArguments); +} + void LaunchProfile::applyMinecraftVersionType(const QString& type) { applyString(type, this->m_minecraftVersionType); @@ -126,6 +168,11 @@ void LaunchProfile::applyMods(const QList<LibraryPtr>& mods) } } +void LaunchProfile::applyCompatibleJavaMajors(QList<int>& javaMajor) +{ + m_compatibleJavaMajors.append(javaMajor); +} + void LaunchProfile::applyLibrary(LibraryPtr library) { if(!library->isActive()) @@ -174,6 +221,22 @@ void LaunchProfile::applyMavenFile(LibraryPtr mavenFile) m_mavenFiles.append(Library::limitedCopy(mavenFile)); } +void LaunchProfile::applyAgent(AgentPtr agent) +{ + auto lib = agent->library(); + if(!lib->isActive()) + { + return; + } + + if(lib->isNative()) + { + return; + } + + m_agents.append(agent); +} + const LibraryPtr LaunchProfile::getMainJar() const { return m_mainJar; @@ -255,6 +318,11 @@ QString LaunchProfile::getMinecraftArguments() const return m_minecraftArguments; } +const QStringList & LaunchProfile::getAddnJvmArguments() const +{ + return m_addnJvmArguments; +} + const QList<LibraryPtr> & LaunchProfile::getJarMods() const { return m_jarMods; @@ -275,6 +343,16 @@ const QList<LibraryPtr> & LaunchProfile::getMavenFiles() const return m_mavenFiles; } +const QList<AgentPtr> & LaunchProfile::getAgents() const +{ + return m_agents; +} + +const QList<int> & LaunchProfile::getCompatibleJavaMajors() const +{ + return m_compatibleJavaMajors; +} + void LaunchProfile::getLibraryFiles( const QString& architecture, QStringList& jars, diff --git a/launcher/minecraft/LaunchProfile.h b/launcher/minecraft/LaunchProfile.h index c1752531..b55cf661 100644 --- a/launcher/minecraft/LaunchProfile.h +++ b/launcher/minecraft/LaunchProfile.h @@ -1,6 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include <QString> #include "Library.h" +#include "Agent.h" #include <ProblemProvider.h> class LaunchProfile: public ProblemProvider @@ -13,6 +49,7 @@ public: /* application of profile variables from patches */ void applyMainClass(const QString& mainClass); void applyAppletClass(const QString& appletClass); void applyMinecraftArguments(const QString& minecraftArguments); + void applyAddnJvmArguments(const QStringList& minecraftArguments); void applyMinecraftVersionType(const QString& type); void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); void applyTraits(const QSet<QString> &traits); @@ -21,6 +58,8 @@ public: /* application of profile variables from patches */ void applyMods(const QList<LibraryPtr> &jarMods); void applyLibrary(LibraryPtr library); void applyMavenFile(LibraryPtr library); + void applyAgent(AgentPtr agent); + void applyCompatibleJavaMajors(QList<int>& javaMajor); void applyMainJar(LibraryPtr jar); void applyProblemSeverity(ProblemSeverity severity); /// clear the profile @@ -33,12 +72,15 @@ public: /* getters for profile variables */ QString getMinecraftVersionType() const; MojangAssetIndexInfo::Ptr getMinecraftAssets() const; QString getMinecraftArguments() const; + const QStringList & getAddnJvmArguments() const; const QSet<QString> & getTraits() const; const QStringList & getTweakers() const; const QList<LibraryPtr> & getJarMods() const; const QList<LibraryPtr> & getLibraries() const; const QList<LibraryPtr> & getNativeLibraries() const; const QList<LibraryPtr> & getMavenFiles() const; + const QList<AgentPtr> & getAgents() const; + const QList<int> & getCompatibleJavaMajors() const; const LibraryPtr getMainJar() const; void getLibraryFiles( const QString & architecture, @@ -69,6 +111,12 @@ private: */ QString m_minecraftArguments; + /** + * Additional arguments to pass to the JVM in addition to those the user has configured, + * memory settings, etc. + */ + QStringList m_addnJvmArguments; + /// A list of all tweaker classes QStringList m_tweakers; @@ -84,6 +132,9 @@ private: /// the list of maven files to be placed in the libraries folder, but not acted upon QList<LibraryPtr> m_mavenFiles; + /// the list of java agents to add to JVM arguments + QList<AgentPtr> m_agents; + /// the main jar LibraryPtr m_mainJar; @@ -99,6 +150,9 @@ private: /// the list of mods QList<LibraryPtr> m_mods; + /// compatible java major versions + QList<int> m_compatibleJavaMajors; + ProblemSeverity m_problemSeverity = ProblemSeverity::None; }; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index c7e60fda..3ba79178 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -125,6 +125,7 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); + m_settings->registerOverride(globalSettings->getSetting("IgnoreJavaCompatibility"), javaOrLocation); // special! m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); @@ -329,6 +330,17 @@ QStringList MinecraftInstance::extraArguments() const list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", "-Dfml.ignorePatchDiscrepancies=true"}); } + auto addn = m_components->getProfile()->getAddnJvmArguments(); + if (!addn.isEmpty()) { + list.append(addn); + } + auto agents = m_components->getProfile()->getAgents(); + for (auto agent : agents) + { + QStringList jar, temp1, temp2, temp3; + agent->library()->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, getLocalLibraryPath()); + list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument())); + } return list; } diff --git a/launcher/minecraft/MojangVersionFormat.cpp b/launcher/minecraft/MojangVersionFormat.cpp index ff5409fd..94c58676 100644 --- a/launcher/minecraft/MojangVersionFormat.cpp +++ b/launcher/minecraft/MojangVersionFormat.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "MojangVersionFormat.h" #include "OneSixVersionFormat.h" #include "MojangDownloadInfo.h" @@ -183,6 +218,15 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFi ); } } + + if (in.contains("compatibleJavaMajors")) + { + for (auto compatible : requireArray(in.value("compatibleJavaMajors"))) + { + out->compatibleJavaMajors.append(requireInteger(compatible)); + } + } + if(in.contains("downloads")) { auto downloadsObj = requireObject(in, "downloads"); diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 0329d70e..879f18c1 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -1,5 +1,6 @@ #include "OneSixVersionFormat.h" #include <Json.h> +#include "minecraft/Agent.h" #include "minecraft/ParseUtils.h" #include <minecraft/MojangVersionFormat.h> @@ -108,6 +109,14 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc } } + if (root.contains("+jvmArgs")) + { + for (auto arg : requireArray(root.value("+jvmArgs"))) + { + out->addnJvmArguments.append(requireString(arg)); + } + } + if (root.contains("jarMods")) { @@ -176,6 +185,21 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc readLibs("mavenFiles", out->mavenFiles); } + if(root.contains("+agents")) { + for (auto agentVal : requireArray(root.value("+agents"))) + { + QJsonObject agentObj = requireObject(agentVal); + auto lib = libraryFromJson(*out, agentObj, filename); + QString arg = ""; + if (agentObj.contains("argument")) + { + readString(agentObj, "argument", arg); + } + AgentPtr agent(new Agent(lib, arg)); + out->agents.append(agent); + } + } + // if we have mainJar, just use it if(root.contains("mainJar")) { diff --git a/launcher/minecraft/VersionFile.cpp b/launcher/minecraft/VersionFile.cpp index d0a1a507..9db30ba2 100644 --- a/launcher/minecraft/VersionFile.cpp +++ b/launcher/minecraft/VersionFile.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include <QJsonArray> #include <QJsonDocument> @@ -32,10 +67,12 @@ void VersionFile::applyTo(LaunchProfile *profile) profile->applyMainClass(mainClass); profile->applyAppletClass(appletClass); profile->applyMinecraftArguments(minecraftArguments); + profile->applyAddnJvmArguments(addnJvmArguments); profile->applyTweakers(addTweakers); profile->applyJarMods(jarMods); profile->applyMods(mods); profile->applyTraits(traits); + profile->applyCompatibleJavaMajors(compatibleJavaMajors); for (auto library : libraries) { @@ -45,6 +82,10 @@ void VersionFile::applyTo(LaunchProfile *profile) { profile->applyMavenFile(mavenFile); } + for (auto agent : agents) + { + profile->applyAgent(agent); + } profile->applyProblemSeverity(getProblemSeverity()); } diff --git a/launcher/minecraft/VersionFile.h b/launcher/minecraft/VersionFile.h index 239a4069..d4b29719 100644 --- a/launcher/minecraft/VersionFile.h +++ b/launcher/minecraft/VersionFile.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include <QString> @@ -10,6 +45,7 @@ #include "minecraft/Rule.h" #include "ProblemProvider.h" #include "Library.h" +#include "Agent.h" #include <meta/JsonFormat.h> class PackProfile; @@ -57,6 +93,12 @@ public: /* data */ /// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution) QString minecraftArguments; + /// PolyMC: Additional JVM launch arguments + QStringList addnJvmArguments; + + /// Mojang: list of compatible java majors + QList<int> compatibleJavaMajors; + /// Mojang: type of the Minecraft version QString type; @@ -78,6 +120,9 @@ public: /* data */ /// PolyMC: list of maven files to put in the libraries folder, but not in classpath QList<LibraryPtr> mavenFiles; + /// PolyMC: list of agents to add to JVM arguments + QList<AgentPtr> agents; + /// The main jar (Minecraft version library, normally) LibraryPtr mainJar; diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 2937c116..dc756e06 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -17,6 +17,7 @@ #include <QString> #include <QDebug> #include <QSaveFile> +#include <QDirIterator> #include "World.h" #include "GZip.h" @@ -187,6 +188,26 @@ bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data) return f.commit(); } +int64_t calculateWorldSize(const QFileInfo &file) +{ + if (file.isFile() && file.suffix() == "zip") + { + return file.size(); + } + else if(file.isDir()) + { + QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); + int64_t total = 0; + while (it.hasNext()) + { + total += it.fileInfo().size(); + it.next(); + } + return total; + } + return -1; +} + World::World(const QFileInfo &file) { repath(file); @@ -196,6 +217,7 @@ void World::repath(const QFileInfo &file) { m_containerFile = file; m_folderName = file.fileName(); + m_size = calculateWorldSize(file); if(file.isFile() && file.suffix() == "zip") { m_iconFile = QString(); @@ -482,6 +504,7 @@ void World::loadFromLevelDat(QByteArray data) if(randomSeed) { qDebug() << "Seed:" << *randomSeed; } + qDebug() << "Size:" << m_size; qDebug() << "GameType:" << m_gameType.toLogString(); } diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index 35e32788..0f587620 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -52,6 +52,10 @@ public: { return m_iconFile; } + int64_t bytes() const + { + return m_size; + } QDateTime lastPlayed() const { return m_lastPlayed; @@ -105,6 +109,7 @@ protected: QString m_iconFile; QDateTime levelDatTime; QDateTime m_lastPlayed; + int64_t m_size; int64_t m_randomSeed = 0; GameType m_gameType; bool is_valid = false; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index dcdbc321..344bea63 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -14,6 +14,8 @@ */ #include "WorldList.h" + +#include "Application.h" #include <FileSystem.h> #include <QMimeData> #include <QUrl> @@ -150,7 +152,7 @@ bool WorldList::resetIcon(int row) int WorldList::columnCount(const QModelIndex &parent) const { - return 3; + return 4; } QVariant WorldList::data(const QModelIndex &index, int role) const @@ -164,6 +166,8 @@ QVariant WorldList::data(const QModelIndex &index, int role) const if (row < 0 || row >= worlds.size()) return QVariant(); + QLocale locale; + auto & world = worlds[row]; switch (role) { @@ -179,6 +183,9 @@ QVariant WorldList::data(const QModelIndex &index, int role) const case LastPlayedColumn: return world.lastPlayed(); + case SizeColumn: + return locale.formattedDataSize(world.bytes()); + default: return QVariant(); } @@ -207,6 +214,10 @@ QVariant WorldList::data(const QModelIndex &index, int role) const { return world.lastPlayed(); } + case SizeRole: + { + return locale.formattedDataSize(world.bytes()); + } case IconFileRole: { return world.iconFile(); @@ -229,6 +240,9 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol return tr("Game Mode"); case LastPlayedColumn: return tr("Last Played"); + case SizeColumn: + //: World size on disk + return tr("Size"); default: return QVariant(); } @@ -242,6 +256,8 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol return tr("Game mode of the world."); case LastPlayedColumn: return tr("Date and time the world was last played."); + case SizeColumn: + return tr("Size of the world on disk."); default: return QVariant(); } diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 8e238ee3..5138e583 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -32,7 +32,8 @@ public: { NameColumn, GameModeColumn, - LastPlayedColumn + LastPlayedColumn, + SizeColumn }; enum Roles @@ -43,6 +44,7 @@ public: NameRole, GameModeRole, LastPlayedRole, + SizeRole, IconFileRole }; diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 15acf678..99809f82 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -1,50 +1,75 @@ -#include "VerifyJavaInstall.h" +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -#include <launch/LaunchTask.h> -#include <minecraft/MinecraftInstance.h> -#include <minecraft/PackProfile.h> -#include <minecraft/VersionFilterData.h> +#include "VerifyJavaInstall.h" -#ifdef major - #undef major -#endif -#ifdef minor - #undef minor -#endif +#include "java/JavaVersion.h" +#include "minecraft/PackProfile.h" +#include "minecraft/MinecraftInstance.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 17 requirement - if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java17BeginsDate) { - if (javaVersion.major() < 17) { - emit logLine("Minecraft 1.18 Pre Release 2 and above require the use of Java 17", - MessageLevel::Fatal); - emitFailed(tr("Minecraft 1.18 Pre Release 2 and above require the use of Java 17")); - return; - } - } - // Java 16 requirement - else 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; - } + auto instance = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); + auto packProfile = instance->getPackProfile(); + auto settings = instance->settings(); + auto storedVersion = settings->get("JavaVersion").toString(); + auto ignoreCompatibility = settings->get("IgnoreJavaCompatibility").toBool(); + + auto compatibleMajors = packProfile->getProfile()->getCompatibleJavaMajors(); + + JavaVersion javaVersion(storedVersion); + + if (compatibleMajors.isEmpty() || compatibleMajors.contains(javaVersion.major())) + { + emitSucceeded(); + 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; - } + + + if (ignoreCompatibility) + { + emit logLine(tr("Java major version is incompatible. Things might break."), MessageLevel::Warning); + emitSucceeded(); + return; } - emitSucceeded(); + emit logLine(tr("This instance is not compatible with Java version %1.\n" + "Please switch to one of the following Java versions for this instance:").arg(javaVersion.major()), + MessageLevel::Error); + for (auto major : compatibleMajors) + { + emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); + } + emitFailed(QString("Incompatible Java major version")); } diff --git a/launcher/minecraft/launch/VerifyJavaInstall.h b/launcher/minecraft/launch/VerifyJavaInstall.h index a553106d..9139c0fa 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.h +++ b/launcher/minecraft/launch/VerifyJavaInstall.h @@ -1,6 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include <launch/LaunchStep.h> +#include <launch/LaunchTask.h> class VerifyJavaInstall : public LaunchStep { Q_OBJECT diff --git a/launcher/minecraft/update/LibrariesTask.cpp b/launcher/minecraft/update/LibrariesTask.cpp index 667dd5d9..26679110 100644 --- a/launcher/minecraft/update/LibrariesTask.cpp +++ b/launcher/minecraft/update/LibrariesTask.cpp @@ -48,6 +48,10 @@ void LibrariesTask::executeTask() libArtifactPool.append(profile->getLibraries()); libArtifactPool.append(profile->getNativeLibraries()); libArtifactPool.append(profile->getMavenFiles()); + for (auto agent : profile->getAgents()) + { + libArtifactPool.append(agent->library()); + } libArtifactPool.append(profile->getMainJar()); processArtifactPool(libArtifactPool, failedLocalLibraries, inst->getLocalLibraryPath()); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 5b75f034..a3c2f166 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -12,7 +12,13 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireString(obj, "project_id"); pack.name = Json::requireString(obj, "title"); - pack.websiteUrl = "https://modrinth.com/mod/" + Json::ensureString(obj, "slug", ""); + + QString slug = Json::ensureString(obj, "slug", ""); + if (!slug.isEmpty()) + pack.websiteUrl = "https://modrinth.com/mod/" + Json::ensureString(obj, "slug", ""); + else + pack.websiteUrl = ""; + pack.description = Json::ensureString(obj, "description", ""); pack.logoUrl = Json::requireString(obj, "icon_url"); diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index b5c91582..89dbf4ca 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org> * - * 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 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "SolderPackInstallTask.h" @@ -19,16 +39,23 @@ #include <Json.h> #include <QtConcurrentRun> #include <MMCZip.h> + #include "TechnicPackProcessor.h" +#include "SolderPackManifest.h" +#include "net/ChecksumValidator.h" Technic::SolderPackInstallTask::SolderPackInstallTask( shared_qobject_ptr<QNetworkAccessManager> network, - const QUrl &sourceUrl, + const QUrl &solderUrl, + const QString &pack, + const QString &version, const QString &minecraftVersion ) { - m_sourceUrl = sourceUrl; - m_minecraftVersion = minecraftVersion; + m_solderUrl = solderUrl; + m_pack = pack; + m_version = version; m_network = network; + m_minecraftVersion = minecraftVersion; } bool Technic::SolderPackInstallTask::abort() { @@ -41,34 +68,12 @@ bool Technic::SolderPackInstallTask::abort() { void Technic::SolderPackInstallTask::executeTask() { - setStatus(tr("Finding recommended version:\n%1").arg(m_sourceUrl.toString())); - m_filesNetJob = new NetJob(tr("Finding recommended version"), m_network); - m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response)); - auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::versionSucceeded); - connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); - m_filesNetJob->start(); -} - -void Technic::SolderPackInstallTask::versionSucceeded() -{ - try - { - QJsonDocument doc = Json::requireDocument(m_response); - QJsonObject obj = Json::requireObject(doc); - QString version = Json::requireString(obj, "recommended", "__placeholder__"); - m_sourceUrl = m_sourceUrl.toString() + '/' + version; - } - catch (const JSONValidationError &e) - { - emitFailed(e.cause()); - m_filesNetJob.reset(); - return; - } + setStatus(tr("Resolving modpack files")); - setStatus(tr("Resolving modpack files:\n%1").arg(m_sourceUrl.toString())); m_filesNetJob = new NetJob(tr("Resolving modpack files"), m_network); - m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response)); + auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version); + m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, &m_response)); + auto job = m_filesNetJob.get(); connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); @@ -77,38 +82,47 @@ void Technic::SolderPackInstallTask::versionSucceeded() void Technic::SolderPackInstallTask::fileListSucceeded() { - setStatus(tr("Downloading modpack:")); - QStringList modUrls; - try - { - QJsonDocument doc = Json::requireDocument(m_response); - QJsonObject obj = Json::requireObject(doc); - QString minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__"); - if (!minecraftVersion.isEmpty()) - m_minecraftVersion = minecraftVersion; - QJsonArray mods = Json::requireArray(obj, "mods", "'mods'"); - for (auto mod: mods) - { - QJsonObject modObject = Json::requireObject(mod); - modUrls.append(Json::requireString(modObject, "url", "'url'")); - } + setStatus(tr("Downloading modpack")); + + QJsonParseError parse_error {}; + QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << m_response; + return; } - catch (const JSONValidationError &e) - { - emitFailed(e.cause()); + auto obj = doc.object(); + + TechnicSolder::PackBuild build; + try { + TechnicSolder::loadPackBuild(build, obj); + } + catch (const JSONValidationError& e) { + emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); m_filesNetJob.reset(); return; } + + if (!build.minecraft.isEmpty()) + m_minecraftVersion = build.minecraft; + m_filesNetJob = new NetJob(tr("Downloading modpack"), m_network); + int i = 0; - for (auto &modUrl: modUrls) - { + for (const auto &mod : build.mods) { auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i)); - m_filesNetJob->addNetAction(Net::Download::makeFile(modUrl, path)); + + auto dl = Net::Download::makeFile(mod.url, path); + if (!mod.md5.isEmpty()) { + auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); + } + m_filesNetJob->addNetAction(dl); + i++; } - m_modCount = modUrls.size(); + m_modCount = build.mods.size(); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); @@ -206,6 +220,5 @@ void Technic::SolderPackInstallTask::extractFinished() void Technic::SolderPackInstallTask::extractAborted() { emitFailed(tr("Instance import has been aborted.")); - return; } diff --git a/launcher/modplatform/technic/SolderPackInstallTask.h b/launcher/modplatform/technic/SolderPackInstallTask.h index 9b2058d8..117a7bd6 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.h +++ b/launcher/modplatform/technic/SolderPackInstallTask.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org> * - * 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 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once @@ -27,7 +47,7 @@ namespace Technic { Q_OBJECT public: - explicit SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, const QUrl &sourceUrl, const QString &minecraftVersion); + explicit SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, const QUrl &solderUrl, const QString& pack, const QString& version, const QString &minecraftVersion); bool canAbort() const override { return true; } bool abort() override; @@ -37,7 +57,6 @@ namespace Technic virtual void executeTask() override; private slots: - void versionSucceeded(); void fileListSucceeded(); void downloadSucceeded(); void downloadFailed(QString reason); @@ -51,7 +70,9 @@ namespace Technic shared_qobject_ptr<QNetworkAccessManager> m_network; NetJob::Ptr m_filesNetJob; - QUrl m_sourceUrl; + QUrl m_solderUrl; + QString m_pack; + QString m_version; QString m_minecraftVersion; QByteArray m_response; QTemporaryDir m_outputDir; diff --git a/launcher/modplatform/technic/SolderPackManifest.cpp b/launcher/modplatform/technic/SolderPackManifest.cpp new file mode 100644 index 00000000..16fe0b0e --- /dev/null +++ b/launcher/modplatform/technic/SolderPackManifest.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "SolderPackManifest.h" + +#include "Json.h" + +namespace TechnicSolder { + +void loadPack(Pack& v, QJsonObject& obj) +{ + v.recommended = Json::requireString(obj, "recommended"); + v.latest = Json::requireString(obj, "latest"); + + auto builds = Json::requireArray(obj, "builds"); + for (const auto buildRaw : builds) { + auto build = Json::requireString(buildRaw); + v.builds.append(build); + } +} + +static void loadPackBuildMod(PackBuildMod& b, QJsonObject& obj) +{ + b.name = Json::requireString(obj, "name"); + b.version = Json::requireString(obj, "version"); + b.md5 = Json::requireString(obj, "md5"); + b.url = Json::requireString(obj, "url"); +} + +void loadPackBuild(PackBuild& v, QJsonObject& obj) +{ + v.minecraft = Json::requireString(obj, "minecraft"); + + auto mods = Json::requireArray(obj, "mods"); + for (const auto modRaw : mods) { + auto modObj = Json::requireObject(modRaw); + PackBuildMod mod; + loadPackBuildMod(mod, modObj); + v.mods.append(mod); + } +} + +} diff --git a/launcher/modplatform/technic/SolderPackManifest.h b/launcher/modplatform/technic/SolderPackManifest.h new file mode 100644 index 00000000..09f18df0 --- /dev/null +++ b/launcher/modplatform/technic/SolderPackManifest.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <QString> +#include <QVector> +#include <QJsonObject> + +namespace TechnicSolder { + +struct Pack { + QString recommended; + QString latest; + QVector<QString> builds; +}; + +void loadPack(Pack& v, QJsonObject& obj); + +struct PackBuildMod { + QString name; + QString version; + QString md5; + QString url; +}; + +struct PackBuild { + QString minecraft; + QVector<PackBuildMod> mods; +}; + +void loadPackBuild(PackBuild& v, QJsonObject& obj); + +} diff --git a/launcher/tasks/SequentialTask.cpp b/launcher/tasks/SequentialTask.cpp index a66b9d78..1573e476 100644 --- a/launcher/tasks/SequentialTask.cpp +++ b/launcher/tasks/SequentialTask.cpp @@ -1,7 +1,23 @@ #include "SequentialTask.h" -SequentialTask::SequentialTask(QObject *parent) : Task(parent), m_currentIndex(-1) +SequentialTask::SequentialTask(QObject* parent, const QString& task_name) : Task(parent), m_name(task_name), m_currentIndex(-1) {} + +SequentialTask::~SequentialTask() +{ + for(auto task : m_queue){ + if(task) + task->deleteLater(); + } +} + +auto SequentialTask::getStepProgress() const -> qint64 +{ + return m_stepProgress; +} + +auto SequentialTask::getStepTotalProgress() const -> qint64 { + return m_stepTotalProgress; } void SequentialTask::addTask(Task::Ptr task) @@ -15,16 +31,24 @@ void SequentialTask::executeTask() startNext(); } +bool SequentialTask::abort() +{ + bool succeeded = true; + for (auto& task : m_queue) { + if (!task->abort()) succeeded = false; + } + + return succeeded; +} + void SequentialTask::startNext() { - if (m_currentIndex != -1) - { + if (m_currentIndex != -1) { Task::Ptr previous = m_queue[m_currentIndex]; disconnect(previous.get(), 0, this, 0); } m_currentIndex++; - if (m_queue.isEmpty() || m_currentIndex >= m_queue.size()) - { + if (m_queue.isEmpty() || m_currentIndex >= m_queue.size()) { emitSucceeded(); return; } @@ -33,23 +57,27 @@ void SequentialTask::startNext() connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext())); + + setStatus(tr("Executing task %1 out of %2").arg(m_currentIndex + 1).arg(m_queue.size())); next->start(); } -void SequentialTask::subTaskFailed(const QString &msg) +void SequentialTask::subTaskFailed(const QString& msg) { emitFailed(msg); } -void SequentialTask::subTaskStatus(const QString &msg) +void SequentialTask::subTaskStatus(const QString& msg) { - setStatus(msg); + setStepStatus(m_queue[m_currentIndex]->getStatus()); } void SequentialTask::subTaskProgress(qint64 current, qint64 total) { - if(total == 0) - { + if (total == 0) { setProgress(0, 100); return; } - setProgress(current, total); + setProgress(m_currentIndex, m_queue.count()); + + m_stepProgress = current; + m_stepTotalProgress = total; } diff --git a/launcher/tasks/SequentialTask.h b/launcher/tasks/SequentialTask.h index 027744f3..5b3c0111 100644 --- a/launcher/tasks/SequentialTask.h +++ b/launcher/tasks/SequentialTask.h @@ -9,13 +9,21 @@ class SequentialTask : public Task { Q_OBJECT public: - explicit SequentialTask(QObject *parent = 0); - virtual ~SequentialTask() {}; + explicit SequentialTask(QObject *parent = nullptr, const QString& task_name = ""); + virtual ~SequentialTask(); + + inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; + auto getStepProgress() const -> qint64 override; + auto getStepTotalProgress() const -> qint64 override; + + inline auto getStepStatus() const -> QString override { return m_step_status; } void addTask(Task::Ptr task); -protected: - void executeTask(); +protected slots: + void executeTask() override; +public slots: + bool abort() override; private slots: @@ -24,7 +32,19 @@ slots: void subTaskStatus(const QString &msg); void subTaskProgress(qint64 current, qint64 total); +signals: + void stepStatus(QString status); + private: + void setStepStatus(QString status) { m_step_status = status; }; + +private: + QString m_name; + QString m_step_status; + QQueue<Task::Ptr > m_queue; int m_currentIndex; + + qint64 m_stepProgress = 0; + qint64 m_stepTotalProgress = 100; }; diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 9cf08dbd..47c249b3 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -21,29 +21,27 @@ #include "QObjectPtr.h" -class Task : public QObject -{ +class Task : public QObject { Q_OBJECT -public: + public: using Ptr = shared_qobject_ptr<Task>; - enum class State - { - Inactive, - Running, - Succeeded, - Failed, - AbortedByUser - }; + enum class State { Inactive, Running, Succeeded, Failed, AbortedByUser }; -public: - explicit Task(QObject *parent = 0); - virtual ~Task() {}; + public: + explicit Task(QObject* parent = 0); + virtual ~Task() = default; bool isRunning() const; bool isFinished() const; bool wasSuccessful() const; + /*! + * MultiStep tasks are combinations of multiple tasks into a single logical task. + * The main usage of this is in SequencialTask. + */ + virtual auto isMultiStep() const -> bool { return false; } + /*! * Returns the string that was passed to emitFailed as the error message when the task failed. * If the task hasn't failed, returns an empty string. @@ -54,52 +52,45 @@ public: virtual bool canAbort() const { return false; } - QString getStatus() - { - return m_status; - } + QString getStatus() { return m_status; } + virtual auto getStepStatus() const -> QString { return {}; } - qint64 getProgress() - { - return m_progress; - } + qint64 getProgress() { return m_progress; } + qint64 getTotalProgress() { return m_progressTotal; } + virtual auto getStepProgress() const -> qint64 { return 0; } + virtual auto getStepTotalProgress() const -> qint64 { return 100; } - qint64 getTotalProgress() - { - return m_progressTotal; - } + protected: + void logWarning(const QString& line); -protected: - void logWarning(const QString & line); - -private: + private: QString describe(); -signals: + signals: void started(); - void progress(qint64 current, qint64 total); + virtual void progress(qint64 current, qint64 total); void finished(); void succeeded(); void failed(QString reason); void status(QString status); -public slots: + public slots: virtual void start(); virtual bool abort() { return false; }; -protected: + protected: virtual void executeTask() = 0; -protected slots: + protected slots: virtual void emitSucceeded(); virtual void emitAborted(); virtual void emitFailed(QString reason); -public slots: - void setStatus(const QString &status); + public slots: + void setStatus(const QString& status); void setProgress(qint64 current, qint64 total); -private: + private: State m_state = State::Inactive; QStringList m_Warnings; QString m_failReason = ""; @@ -107,4 +98,3 @@ private: int m_progress = 0; int m_progressTotal = 100; }; - diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui index f9665c30..70c5009d 100644 --- a/launcher/ui/dialogs/AboutDialog.ui +++ b/launcher/ui/dialogs/AboutDialog.ui @@ -80,7 +80,7 @@ </font> </property> <property name="text"> - <string notr="true">PolyMC</string> + <string notr="true">Launcher</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 4b092859..648bd88b 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -81,6 +81,12 @@ int ProgressDialog::execWithTask(Task *task) connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &))); connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64))); + m_is_multi_step = task->isMultiStep(); + if(!m_is_multi_step){ + ui->globalStatusLabel->setHidden(true); + ui->globalProgressBar->setHidden(true); + } + // if this didn't connect to an already running task, invoke start if(!task->isRunning()) { @@ -152,14 +158,24 @@ void ProgressDialog::onTaskSucceeded() void ProgressDialog::changeStatus(const QString &status) { - ui->statusLabel->setText(status); + ui->statusLabel->setText(task->getStepStatus()); + ui->globalStatusLabel->setText(status); updateSize(); } void ProgressDialog::changeProgress(qint64 current, qint64 total) { - ui->taskProgressBar->setMaximum(total); - ui->taskProgressBar->setValue(current); + ui->globalProgressBar->setMaximum(total); + ui->globalProgressBar->setValue(current); + + if(!m_is_multi_step){ + ui->taskProgressBar->setMaximum(total); + ui->taskProgressBar->setValue(current); + } + else{ + ui->taskProgressBar->setMaximum(task->getStepProgress()); + ui->taskProgressBar->setValue(task->getStepTotalProgress()); + } } void ProgressDialog::keyPressEvent(QKeyEvent *e) diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index b28ad4fa..0b4b78a4 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -19,6 +19,7 @@ #include <memory> class Task; +class SequentialTask; namespace Ui { @@ -35,7 +36,7 @@ public: void updateSize(); - int execWithTask(Task *task); + int execWithTask(Task* task); int execWithTask(std::unique_ptr<Task> &&task); int execWithTask(std::unique_ptr<Task> &task); @@ -68,4 +69,6 @@ private: Ui::ProgressDialog *ui; Task *task; + + bool m_is_multi_step = false; }; diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui index 04b8fef3..bf119a78 100644 --- a/launcher/ui/dialogs/ProgressDialog.ui +++ b/launcher/ui/dialogs/ProgressDialog.ui @@ -2,14 +2,6 @@ <ui version="4.0"> <class>ProgressDialog</class> <widget class="QDialog" name="ProgressDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>100</height> - </rect> - </property> <property name="minimumSize"> <size> <width>400</width> @@ -26,7 +18,27 @@ <string>Please wait...</string> </property> <layout class="QGridLayout" name="gridLayout"> + <item row="4" column="0"> + <widget class="QPushButton" name="skipButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Skip</string> + </property> + </widget> + </item> <item row="0" column="0"> + <widget class="QLabel" name="globalStatusLabel"> + <property name="text"> + <string>Global Task Status...</string> + </property> + </widget> + </item> + <item row="2" column="0"> <widget class="QLabel" name="statusLabel"> <property name="text"> <string>Task Status...</string> @@ -36,7 +48,7 @@ </property> </widget> </item> - <item row="1" column="0"> + <item row="3" column="0"> <widget class="QProgressBar" name="taskProgressBar"> <property name="value"> <number>24</number> @@ -46,16 +58,13 @@ </property> </widget> </item> - <item row="2" column="0"> - <widget class="QPushButton" name="skipButton"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <item row="1" column="0"> + <widget class="QProgressBar" name="globalProgressBar"> + <property name="enabled"> + <bool>true</bool> </property> - <property name="text"> - <string>Skip</string> + <property name="value"> + <number>24</number> </property> </widget> </item> diff --git a/launcher/ui/dialogs/UpdateDialog.ui b/launcher/ui/dialogs/UpdateDialog.ui index bd94a554..5eb9d88a 100644 --- a/launcher/ui/dialogs/UpdateDialog.ui +++ b/launcher/ui/dialogs/UpdateDialog.ui @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>PolyMC Update</string> + <string>Launcher Update</string> </property> <property name="windowIcon"> <iconset> diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 3eb4bd59..f0616db1 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -95,6 +96,7 @@ void JavaPage::applySettings() // Java Settings s->set("JavaPath", ui->javaPathTextBox->text()); s->set("JvmArgs", ui->jvmArgsTextBox->text()); + s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); } void JavaPage::loadSettings() @@ -118,6 +120,7 @@ void JavaPage::loadSettings() // Java Settings ui->javaPathTextBox->setText(s->get("JavaPath").toString()); ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString()); + ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool()); } void JavaPage::on_javaDetectBtn_clicked() diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index b67e9994..d27b200f 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -222,6 +222,22 @@ </property> </widget> </item> + <item row="4" column="1"> + <widget class="QCheckBox" name="skipCompatibilityCheckbox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string> + </property> + <property name="text"> + <string>Skip Java compatibility checks</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index decc9b8b..c18ab34b 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -173,20 +173,20 @@ <item> <widget class="QCheckBox" name="closeAfterLaunchCheck"> <property name="toolTip"> - <string><html><head/><body><p>PolyMC will automatically reopen when the game crashes or exits.</p></body></html></string> + <string><html><head/><body><p>The launcher will automatically reopen when the game crashes or exits.</p></body></html></string> </property> <property name="text"> - <string>Close PolyMC after game window opens</string> + <string>Close the launcher after game window opens</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="quitAfterGameStopCheck"> <property name="toolTip"> - <string><html><head/><body><p>PolyMC will automatically exit if the game crashes or exists.</p></body></html></string> + <string><html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html></string> </property> <property name="text"> - <string>Quit PolyMC after game window stops</string> + <string>Quit the launcher after game window closes</string> </property> </widget> </item> diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index e68a7124..a5985741 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -165,10 +165,12 @@ void InstanceSettingsPage::applySettings() if (javaInstall) { m_settings->set("JavaPath", ui->javaPathTextBox->text()); + m_settings->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); } else { m_settings->reset("JavaPath"); + m_settings->reset("IgnoreJavaCompatibility"); } // Java arguments @@ -286,6 +288,7 @@ void InstanceSettingsPage::loadSettings() ui->javaSettingsGroupBox->setChecked(overrideLocation); ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); + ui->skipCompatibilityCheckbox->setChecked(m_settings->get("IgnoreJavaCompatibility").toBool()); ui->javaArgumentsGroupBox->setChecked(overrideArgs); ui->jvmArgsTextBox->setPlainText(m_settings->get("JvmArgs").toString()); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 729f8e2a..5db2d147 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -85,6 +85,16 @@ </property> </widget> </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="skipCompatibilityCheckbox"> + <property name="toolTip"> + <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string> + </property> + <property name="text"> + <string>Skip Java compatibility checks</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 599f0e11..fcb6022d 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -58,6 +58,7 @@ #include "Version.h" #include "ui/dialogs/ProgressDialog.h" +#include "tasks/SequentialTask.h" namespace { // FIXME: wasteful @@ -394,25 +395,25 @@ void ModFolderPage::on_actionInstall_mods_triggered() return; } ModDownloadDialog mdownload(m_mods, this, m_inst); - if(mdownload.exec()) { - for(auto task : mdownload.getTasks()){ - connect(task, &Task::failed, [this, task](QString reason) { - task->deleteLater(); - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - }); - connect(task, &Task::succeeded, [this, task]() { - QStringList warnings = task->warnings(); - if (warnings.count()) { - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), - QMessageBox::Warning)->show(); - } - task->deleteLater(); - }); - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(task); - m_mods->update(); + if (mdownload.exec()) { + SequentialTask* tasks = new SequentialTask(this); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); } + tasks->deleteLater(); + }); + + for (auto task : mdownload.getTasks()) { + tasks->addTask(task); } + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + m_mods->update(); } } diff --git a/launcher/ui/pages/modplatform/technic/TechnicData.h b/launcher/ui/pages/modplatform/technic/TechnicData.h index 50fd75e8..cd2ea8e1 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicData.h +++ b/launcher/ui/pages/modplatform/technic/TechnicData.h @@ -1,22 +1,43 @@ -/* Copyright 2020-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org> * - * 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 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once #include <QList> #include <QString> +#include <QVector> namespace Technic { struct Modpack { @@ -36,6 +57,11 @@ struct Modpack { QString websiteUrl; QString author; QString description; + QString currentVersion; + + bool versionsLoaded = false; + QString recommended; + QVector<QString> versions; }; } diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index 0167f746..9c9d1e75 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -1,20 +1,41 @@ -/* Copyright 2020-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2021 Jamie Mansfield <jmansfield@cadixdev.org> * - * 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 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-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 "TechnicModel.h" #include "Application.h" +#include "BuildConfig.h" #include "Json.h" #include <QIcon> @@ -94,13 +115,24 @@ void Technic::ListModel::performSearch() NetJob *netJob = new NetJob("Technic::Search", APPLICATION->network()); QString searchUrl = ""; if (currentSearchTerm.isEmpty()) { - searchUrl = "https://api.technicpack.net/trending?build=multimc"; + searchUrl = QString("%1trending?build=%2") + .arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD); + searchMode = List; } - else - { + else if (currentSearchTerm.startsWith("http://api.technicpack.net/modpack/")) { + searchUrl = QString("https://%1?build=%2") + .arg(currentSearchTerm.mid(7), BuildConfig.TECHNIC_API_BUILD); + searchMode = Single; + } + else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) { + searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD); + searchMode = Single; + } + else { searchUrl = QString( - "https://api.technicpack.net/search?build=multimc&q=%1" - ).arg(currentSearchTerm); + "%1search?build=%2&q=%3" + ).arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm); + searchMode = List; } netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; @@ -125,26 +157,58 @@ void Technic::ListModel::searchRequestFinished() QList<Modpack> newList; try { auto root = Json::requireObject(doc); - auto objs = Json::requireArray(root, "modpacks"); - for (auto technicPack: objs) { - Modpack pack; - auto technicPackObject = Json::requireObject(technicPack); - pack.name = Json::requireString(technicPackObject, "name"); - pack.slug = Json::requireString(technicPackObject, "slug"); - if (pack.slug == "vanilla") - continue; - - auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null"); - if(rawURL == "null") { - pack.logoUrl = "null"; - pack.logoName = "null"; + + switch (searchMode) { + case List: { + auto objs = Json::requireArray(root, "modpacks"); + for (auto technicPack: objs) { + Modpack pack; + auto technicPackObject = Json::requireObject(technicPack); + pack.name = Json::requireString(technicPackObject, "name"); + pack.slug = Json::requireString(technicPackObject, "slug"); + if (pack.slug == "vanilla") + continue; + + auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null"); + if(rawURL == "null") { + pack.logoUrl = "null"; + pack.logoName = "null"; + } + else { + pack.logoUrl = rawURL; + pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + } + pack.broken = false; + newList.append(pack); + } + break; } - else { - pack.logoUrl = rawURL; - pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + case Single: { + if (root.contains("error")) { + // Invalid API url + break; + } + + Modpack pack; + pack.name = Json::requireString(root, "displayName"); + pack.slug = Json::requireString(root, "name"); + + if (root.contains("icon")) { + auto iconObj = Json::requireObject(root, "icon"); + auto iconUrl = Json::requireString(iconObj, "url"); + + pack.logoUrl = iconUrl; + pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + } + else { + pack.logoUrl = "null"; + pack.logoName = "null"; + } + + pack.broken = false; + newList.append(pack); + break; } - pack.broken = false; - newList.append(pack); } } catch (const JSONValidationError &err) diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h index e80e6e7c..5eea124c 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.h +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h @@ -1,16 +1,36 @@ -/* Copyright 2020-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2021 Jamie Mansfield <jmansfield@cadixdev.org> * - * 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 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-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 @@ -63,6 +83,10 @@ private: ResetRequested, Finished } searchState = None; + enum SearchMode { + List, + Single, + } searchMode = List; NetJob::Ptr jobPtr; QByteArray response; }; diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index c3807269..b8c1e00a 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,12 +40,14 @@ #include "ui/dialogs/NewInstanceDialog.h" +#include "BuildConfig.h" #include "TechnicModel.h" #include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h" #include "Json.h" #include "Application.h" +#include "modplatform/technic/SolderPackManifest.h" TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) @@ -55,7 +57,9 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) ui->searchEdit->installEventFilter(this); model = new Technic::ListModel(this); ui->packView->setModel(model); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged); } bool TechnicPage::eventFilter(QObject* watched, QEvent* event) @@ -98,13 +102,14 @@ void TechnicPage::triggerSearch() { void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second) { + ui->versionSelectionBox->clear(); + if(!first.isValid()) { if(isOpened) { dialog->setSuggestedPack(); } - //ui->frame->clear(); return; } @@ -137,17 +142,19 @@ void TechnicPage::suggestCurrent() } NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); - std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); QString slug = current.slug; - netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), response.get())); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, slug] + netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), &response)); + QObject::connect(netJob, &NetJob::succeeded, this, [this, slug] { + jobPtr.reset(); + if (current.slug != slug) { return; } - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + + QJsonParseError parse_error {}; + QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonObject obj = doc.object(); if(parse_error.error != QJsonParseError::NoError) { @@ -189,10 +196,14 @@ void TechnicPage::suggestCurrent() current.websiteUrl = Json::ensureString(obj, "platformUrl", QString(), "__placeholder__"); current.author = Json::ensureString(obj, "user", QString(), "__placeholder__"); current.description = Json::ensureString(obj, "description", QString(), "__placeholder__"); + current.currentVersion = Json::ensureString(obj, "version", QString(), "__placeholder__"); current.metadataLoaded = true; + metadataLoaded(); }); - netJob->start(); + + jobPtr = netJob; + jobPtr->start(); } // expects current.metadataLoaded to be true @@ -202,25 +213,119 @@ void TechnicPage::metadataLoaded() QString name = current.name; if (current.websiteUrl.isEmpty()) - // This allows injecting HTML here. - text = name; + text = name.toHtmlEscaped(); else - // URL not properly escaped for inclusion in HTML. The name allows for injecting HTML. - text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; + text = "<a href=\"" + current.websiteUrl.toHtmlEscaped() + "\">" + name.toHtmlEscaped() + "</a>"; + if (!current.author.isEmpty()) { - // This allows injecting HTML here - text += tr(" by ") + current.author; + text += "<br>" + tr(" by ") + current.author.toHtmlEscaped(); + } + + text += "<br><br>"; + + ui->packDescription->setHtml(text + current.description); + + // Strip trailing forward-slashes from Solder URL's + if (current.isSolder) { + while (current.url.endsWith('/')) current.url.chop(1); + } + + // Display versions from Solder + if (!current.isSolder) { + // If the pack isn't a Solder pack, it only has the single version + ui->versionSelectionBox->addItem(current.currentVersion); + } + else if (current.versionsLoaded) { + // reverse foreach, so that the newest versions are first + for (auto i = current.versions.size(); i--;) { + ui->versionSelectionBox->addItem(current.versions.at(i)); + } + ui->versionSelectionBox->setCurrentText(current.recommended); + } + else { + // For now, until the versions are pulled from the Solder instance, display the current + // version so we can display something quicker + ui->versionSelectionBox->addItem(current.currentVersion); + + auto* netJob = new NetJob(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network()); + auto url = QString("%1/modpack/%2").arg(current.url, current.slug); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); + + QObject::connect(netJob, &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); + + jobPtr = netJob; + jobPtr->start(); + } + + selectVersion(); +} + +void TechnicPage::selectVersion() { + if (!isOpened) { + return; + } + if (current.broken) { + dialog->setSuggestedPack(); + return; } - ui->frame->setModText(text); - ui->frame->setModDescription(current.description); if (!current.isSolder) { - dialog->setSuggestedPack(current.name, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); + dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); } else { - while (current.url.endsWith('/')) current.url.chop(1); - dialog->setSuggestedPack(current.name, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url + "/modpack/" + current.slug, current.minecraftVersion)); + dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url, current.slug, selectedVersion, current.minecraftVersion)); + } +} + +void TechnicPage::onSolderLoaded() { + jobPtr.reset(); + + auto fallback = [this]() { + current.versionsLoaded = true; + + current.versions.clear(); + current.versions.append(current.currentVersion); + }; + + current.versions.clear(); + + QJsonParseError parse_error {}; + auto doc = QJsonDocument::fromJson(response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + fallback(); + return; + } + auto obj = doc.object(); + + TechnicSolder::Pack pack; + try { + TechnicSolder::loadPack(pack, obj); + } + catch (const JSONValidationError& err) { + qCritical() << "Couldn't parse Solder pack metadata:" << err.cause(); + fallback(); + return; + } + + current.versionsLoaded = true; + current.recommended = pack.recommended; + current.versions.append(pack.builds); + + // Finally, let's reload :) + ui->versionSelectionBox->clear(); + metadataLoaded(); +} + +void TechnicPage::onVersionSelectionChanged(QString data) { + if (data.isNull() || data.isEmpty()) { + selectedVersion = ""; + return; } + + selectedVersion = data; + selectVersion(); } diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h index bf4baa58..f4a3b61d 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.h +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,6 +39,7 @@ #include "ui/pages/BasePage.h" #include <Application.h> +#include "net/NetJob.h" #include "tasks/Task.h" #include "TechnicData.h" @@ -86,14 +87,22 @@ public: private: void suggestCurrent(); void metadataLoaded(); + void selectVersion(); private slots: void triggerSearch(); void onSelectionChanged(QModelIndex first, QModelIndex second); + void onSolderLoaded(); + void onVersionSelectionChanged(QString data); private: Ui::TechnicPage *ui = nullptr; NewInstanceDialog* dialog = nullptr; Technic::ListModel* model = nullptr; + Technic::Modpack current; + QString selectedVersion; + + NetJob::Ptr jobPtr; + QByteArray response; }; diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui index 62ab6154..ca6a9b7e 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui @@ -10,86 +10,76 @@ <height>405</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QWidget" name="widget" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QLineEdit" name="searchEdit"> - <property name="placeholderText"> - <string>Search and filter...</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="searchButton"> - <property name="text"> - <string>Search</string> - </property> - </widget> - </item> - </layout> - </widget> + <layout class="QGridLayout" name="gridLayout"> + <item row="3" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="2"> + <widget class="QComboBox" name="versionSelectionBox"/> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Version selected:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> </item> - <item> - <widget class="QListView" name="packView"> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="iconSize"> - <size> - <width>48</width> - <height>48</height> - </size> + <item row="2" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QListView" name="packView"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="iconSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QTextBrowser" name="packDescription"/> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLineEdit" name="searchEdit"> + <property name="placeholderText"> + <string>Search and filter...</string> </property> </widget> </item> - <item> - <widget class="MCModInfoFrame" name="frame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> + <item row="1" column="1"> + <widget class="QPushButton" name="searchButton"> + <property name="text"> + <string>Search</string> </property> </widget> </item> </layout> </widget> - <customwidgets> - <customwidget> - <class>MCModInfoFrame</class> - <extends>QFrame</extends> - <header>ui/widgets/MCModInfoFrame.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <tabstops> - <tabstop>searchEdit</tabstop> - <tabstop>searchButton</tabstop> - <tabstop>packView</tabstop> - </tabstops> <resources/> <connections/> </ui> diff --git a/launcher/ui/widgets/CustomCommands.ui b/launcher/ui/widgets/CustomCommands.ui index dbd54431..650a9cc1 100644 --- a/launcher/ui/widgets/CustomCommands.ui +++ b/launcher/ui/widgets/CustomCommands.ui @@ -74,7 +74,7 @@ <item> <widget class="QLabel" name="labelCustomCmdsDescription"> <property name="text"> - <string><html><head/><body><p>Pre-launch command runs before the instance launches and post-exit command runs after it exits.</p><p>Both will be run in the launcher's working folder with extra environment variables:</p><ul><li>$INST_NAME - Name of the instance</li><li>$INST_ID - ID of the instance (its folder name)</li><li>$INST_DIR - absolute path of the instance</li><li>$INST_MC_DIR - absolute path of Minecraft</li><li>$INST_JAVA - Java binary used for launch</li><li>$INST_JAVA_ARGS - command-line parameters used for launch</li></ul><p>Wrapper command allows launching using an extra wrapper program (like 'optirun' on Linux)</p></body></html></string> + <string><html><head/><body><p>Pre-launch command runs before the instance launches and post-exit command runs after it exits.</p><p>Both will be run in the launcher's working folder with extra environment variables:</p><ul><li>$INST_NAME - Name of the instance</li><li>$INST_ID - ID of the instance (its folder name)</li><li>$INST_DIR - absolute path of the instance</li><li>$INST_MC_DIR - absolute path of Minecraft</li><li>$INST_JAVA - Java binary used for launch</li><li>$INST_JAVA_ARGS - command-line parameters used for launch (warning: will not work correctly if arguments contain spaces)</li></ul><p>Wrapper command allows launching using an extra wrapper program (like 'optirun' on Linux)</p></body></html></string> </property> <property name="alignment"> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> |