mirror of
https://github.com/UltimMC/Launcher.git
synced 2025-12-24 12:32:42 +00:00
@@ -134,6 +134,12 @@ void BaseInstance::setRunning(bool running)
|
||||
|
||||
m_isRunning = running;
|
||||
|
||||
if(!m_settings->get("RecordGameTime").toBool())
|
||||
{
|
||||
emit runningStatusChanged(running);
|
||||
return;
|
||||
}
|
||||
|
||||
if(running)
|
||||
{
|
||||
m_timeStarted = QDateTime::currentDateTime();
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||
|
||||
class QDir;
|
||||
class Task;
|
||||
class LaunchTask;
|
||||
@@ -145,7 +147,8 @@ public:
|
||||
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
|
||||
|
||||
/// returns a valid launcher (task container)
|
||||
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
||||
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(
|
||||
AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
|
||||
|
||||
/// returns the current launch task (if any)
|
||||
shared_qobject_ptr<LaunchTask> getLaunchTask();
|
||||
@@ -221,9 +224,9 @@ public:
|
||||
bool reloadSettings();
|
||||
|
||||
/**
|
||||
* 'print' a verbose desription of the instance into a QStringList
|
||||
* 'print' a verbose description of the instance into a QStringList
|
||||
*/
|
||||
virtual QStringList verboseDescription(AuthSessionPtr session) = 0;
|
||||
virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) = 0;
|
||||
|
||||
Status currentStatus() const;
|
||||
|
||||
|
||||
@@ -124,6 +124,8 @@ set(NET_SOURCES
|
||||
|
||||
# Game launch logic
|
||||
set(LAUNCH_SOURCES
|
||||
launch/steps/LookupServerAddress.cpp
|
||||
launch/steps/LookupServerAddress.h
|
||||
launch/steps/PostLaunchCommand.cpp
|
||||
launch/steps/PostLaunchCommand.h
|
||||
launch/steps/PreLaunchCommand.cpp
|
||||
@@ -236,12 +238,16 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/launch/ExtractNatives.h
|
||||
minecraft/launch/LauncherPartLaunch.cpp
|
||||
minecraft/launch/LauncherPartLaunch.h
|
||||
minecraft/launch/MinecraftServerTarget.cpp
|
||||
minecraft/launch/MinecraftServerTarget.h
|
||||
minecraft/launch/PrintInstanceInfo.cpp
|
||||
minecraft/launch/PrintInstanceInfo.h
|
||||
minecraft/launch/ReconstructAssets.cpp
|
||||
minecraft/launch/ReconstructAssets.h
|
||||
minecraft/launch/ScanModFolders.cpp
|
||||
minecraft/launch/ScanModFolders.h
|
||||
minecraft/launch/VerifyJavaInstall.cpp
|
||||
minecraft/launch/VerifyJavaInstall.h
|
||||
|
||||
minecraft/legacy/LegacyModList.h
|
||||
minecraft/legacy/LegacyModList.cpp
|
||||
@@ -298,6 +304,10 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/mod/ModFolderLoadTask.cpp
|
||||
minecraft/mod/LocalModParseTask.h
|
||||
minecraft/mod/LocalModParseTask.cpp
|
||||
minecraft/mod/ResourcePackFolderModel.h
|
||||
minecraft/mod/ResourcePackFolderModel.cpp
|
||||
minecraft/mod/TexturePackFolderModel.h
|
||||
minecraft/mod/TexturePackFolderModel.cpp
|
||||
|
||||
# Assets
|
||||
minecraft/AssetsUtils.h
|
||||
@@ -540,7 +550,7 @@ set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISI
|
||||
generate_export_header(MultiMC_logic)
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC_logic systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES} optional-bare BuildConfig)
|
||||
target_link_libraries(MultiMC_logic systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES} optional-bare tomlc99 BuildConfig)
|
||||
target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent)
|
||||
|
||||
# Mark and export headers
|
||||
|
||||
@@ -238,6 +238,7 @@ void InstanceImportTask::processFlame()
|
||||
}
|
||||
|
||||
QString forgeVersion;
|
||||
QString fabricVersion;
|
||||
for(auto &loader: pack.minecraft.modLoaders)
|
||||
{
|
||||
auto id = loader.id;
|
||||
@@ -247,6 +248,12 @@ void InstanceImportTask::processFlame()
|
||||
forgeVersion = id;
|
||||
continue;
|
||||
}
|
||||
if(id.startsWith("fabric-"))
|
||||
{
|
||||
id.remove("fabric-");
|
||||
fabricVersion = id;
|
||||
continue;
|
||||
}
|
||||
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
|
||||
}
|
||||
|
||||
@@ -281,6 +288,10 @@ void InstanceImportTask::processFlame()
|
||||
}
|
||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
||||
}
|
||||
if(!fabricVersion.isEmpty())
|
||||
{
|
||||
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
|
||||
}
|
||||
if (m_instIcon != "default")
|
||||
{
|
||||
instance.setIconKey(m_instIcon);
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
{
|
||||
return instanceRoot();
|
||||
};
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
QStringList verboseDescription(AuthSessionPtr session) override
|
||||
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
|
||||
{
|
||||
QStringList out;
|
||||
out << "Null instance - placeholder.";
|
||||
|
||||
@@ -150,7 +150,7 @@ JavaInstallPtr JavaUtils::GetDefaultJava()
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
|
||||
QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName, QString keyJavaDir, QString subkeySuffix)
|
||||
{
|
||||
QList<JavaInstallPtr> javas;
|
||||
|
||||
@@ -175,8 +175,6 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
|
||||
RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz);
|
||||
}
|
||||
|
||||
QString recommended = value;
|
||||
|
||||
TCHAR subKeyName[255];
|
||||
DWORD subKeyNameSize, numSubKeys, retCode;
|
||||
|
||||
@@ -195,7 +193,7 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
|
||||
if (retCode == ERROR_SUCCESS)
|
||||
{
|
||||
// Now open the registry key for the version that we just got.
|
||||
QString newKeyName = keyName + "\\" + subKeyName;
|
||||
QString newKeyName = keyName + "\\" + subKeyName + subkeySuffix;
|
||||
|
||||
HKEY newKey;
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0,
|
||||
@@ -204,11 +202,11 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
|
||||
// Read the JavaHome value to find where Java is installed.
|
||||
value = new char[0];
|
||||
valueSz = 0;
|
||||
if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
|
||||
if (RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value,
|
||||
&valueSz) == ERROR_MORE_DATA)
|
||||
{
|
||||
value = new char[valueSz];
|
||||
RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
|
||||
RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value,
|
||||
&valueSz);
|
||||
|
||||
// Now, we construct the version object and add it to the list.
|
||||
@@ -237,25 +235,78 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
{
|
||||
QList<JavaInstallPtr> java_candidates;
|
||||
|
||||
// Oracle
|
||||
QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment", "JavaHome");
|
||||
QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit", "JavaHome");
|
||||
QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment", "JavaHome");
|
||||
QList<JavaInstallPtr> JDK32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit", "JavaHome");
|
||||
|
||||
// Oracle for Java 9 and newer
|
||||
QList<JavaInstallPtr> NEWJRE64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\JRE", "JavaHome");
|
||||
QList<JavaInstallPtr> NEWJDK64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\JDK", "JavaHome");
|
||||
QList<JavaInstallPtr> NEWJRE32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\JRE", "JavaHome");
|
||||
QList<JavaInstallPtr> NEWJDK32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\JDK", "JavaHome");
|
||||
|
||||
// AdoptOpenJDK
|
||||
QList<JavaInstallPtr> ADOPTOPENJRE32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\AdoptOpenJDK\\JRE", "Path", "\\hotspot\\MSI");
|
||||
QList<JavaInstallPtr> ADOPTOPENJRE64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\AdoptOpenJDK\\JRE", "Path", "\\hotspot\\MSI");
|
||||
QList<JavaInstallPtr> ADOPTOPENJDK32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\AdoptOpenJDK\\JDK", "Path", "\\hotspot\\MSI");
|
||||
QList<JavaInstallPtr> ADOPTOPENJDK64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\AdoptOpenJDK\\JDK", "Path", "\\hotspot\\MSI");
|
||||
|
||||
// Microsoft
|
||||
QList<JavaInstallPtr> MICROSOFTJDK64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\Microsoft\\JDK", "Path", "\\hotspot\\MSI");
|
||||
|
||||
// Azul Zulu
|
||||
QList<JavaInstallPtr> ZULU64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\Azul Systems\\Zulu", "InstallationPath");
|
||||
QList<JavaInstallPtr> ZULU32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\Azul Systems\\Zulu", "InstallationPath");
|
||||
|
||||
// BellSoft Liberica
|
||||
QList<JavaInstallPtr> LIBERICA64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\BellSoft\\Liberica", "InstallationPath");
|
||||
QList<JavaInstallPtr> LIBERICA32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\BellSoft\\Liberica", "InstallationPath");
|
||||
|
||||
// List x64 before x86
|
||||
java_candidates.append(JRE64s);
|
||||
java_candidates.append(NEWJRE64s);
|
||||
java_candidates.append(ADOPTOPENJRE64s);
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
|
||||
java_candidates.append(JDK64s);
|
||||
java_candidates.append(NEWJDK64s);
|
||||
java_candidates.append(ADOPTOPENJDK64s);
|
||||
java_candidates.append(MICROSOFTJDK64s);
|
||||
java_candidates.append(ZULU64s);
|
||||
java_candidates.append(LIBERICA64s);
|
||||
|
||||
java_candidates.append(JRE32s);
|
||||
java_candidates.append(NEWJRE32s);
|
||||
java_candidates.append(ADOPTOPENJRE32s);
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
|
||||
java_candidates.append(JDK32s);
|
||||
java_candidates.append(NEWJDK32s);
|
||||
java_candidates.append(ADOPTOPENJDK32s);
|
||||
java_candidates.append(ZULU32s);
|
||||
java_candidates.append(LIBERICA32s);
|
||||
|
||||
java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path));
|
||||
|
||||
QList<QString> candidates;
|
||||
@@ -330,6 +381,9 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
scanJavaDir("/usr/lib32/jvm");
|
||||
// javas stored in MultiMC's folder
|
||||
scanJavaDir("java");
|
||||
// manually installed JDKs in /opt
|
||||
scanJavaDir("/opt/jdk");
|
||||
scanJavaDir("/opt/jdks");
|
||||
return javas;
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -39,6 +39,6 @@ public:
|
||||
JavaInstallPtr GetDefaultJava();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
|
||||
QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName, QString keyJavaDir, QString subkeySuffix = "");
|
||||
#endif
|
||||
};
|
||||
|
||||
95
api/logic/launch/steps/LookupServerAddress.cpp
Normal file
95
api/logic/launch/steps/LookupServerAddress.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/* 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 "LookupServerAddress.h"
|
||||
|
||||
#include <launch/LaunchTask.h>
|
||||
|
||||
LookupServerAddress::LookupServerAddress(LaunchTask *parent) :
|
||||
LaunchStep(parent), m_dnsLookup(new QDnsLookup(this))
|
||||
{
|
||||
connect(m_dnsLookup, &QDnsLookup::finished, this, &LookupServerAddress::on_dnsLookupFinished);
|
||||
|
||||
m_dnsLookup->setType(QDnsLookup::SRV);
|
||||
}
|
||||
|
||||
void LookupServerAddress::setLookupAddress(const QString &lookupAddress)
|
||||
{
|
||||
m_lookupAddress = lookupAddress;
|
||||
m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress));
|
||||
}
|
||||
|
||||
void LookupServerAddress::setOutputAddressPtr(MinecraftServerTargetPtr output)
|
||||
{
|
||||
m_output = std::move(output);
|
||||
}
|
||||
|
||||
bool LookupServerAddress::abort()
|
||||
{
|
||||
m_dnsLookup->abort();
|
||||
emitFailed("Aborted");
|
||||
return true;
|
||||
}
|
||||
|
||||
void LookupServerAddress::executeTask()
|
||||
{
|
||||
m_dnsLookup->lookup();
|
||||
}
|
||||
|
||||
void LookupServerAddress::on_dnsLookupFinished()
|
||||
{
|
||||
if (isFinished())
|
||||
{
|
||||
// Aborted
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_dnsLookup->error() != QDnsLookup::NoError)
|
||||
{
|
||||
emit logLine(QString("Failed to resolve server address (this is NOT an error!) %1: %2\n")
|
||||
.arg(m_dnsLookup->name(), m_dnsLookup->errorString()), MessageLevel::MultiMC);
|
||||
resolve(m_lookupAddress, 25565); // Technically the task failed, however, we don't abort the launch
|
||||
// and leave it up to minecraft to fail (or maybe not) when connecting
|
||||
return;
|
||||
}
|
||||
|
||||
const auto records = m_dnsLookup->serviceRecords();
|
||||
if (records.empty())
|
||||
{
|
||||
emit logLine(
|
||||
QString("Failed to resolve server address %1: the DNS lookup succeeded, but no records were returned.\n")
|
||||
.arg(m_dnsLookup->name()), MessageLevel::Warning);
|
||||
resolve(m_lookupAddress, 25565); // Technically the task failed, however, we don't abort the launch
|
||||
// and leave it up to minecraft to fail (or maybe not) when connecting
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &firstRecord = records.at(0);
|
||||
quint16 port = firstRecord.port();
|
||||
|
||||
emit logLine(QString("Resolved server address %1 to %2 with port %3\n").arg(
|
||||
m_dnsLookup->name(), firstRecord.target(), QString::number(port)),MessageLevel::MultiMC);
|
||||
resolve(firstRecord.target(), port);
|
||||
}
|
||||
|
||||
void LookupServerAddress::resolve(const QString &address, quint16 port)
|
||||
{
|
||||
m_output->address = address;
|
||||
m_output->port = port;
|
||||
|
||||
emitSucceeded();
|
||||
m_dnsLookup->deleteLater();
|
||||
}
|
||||
49
api/logic/launch/steps/LookupServerAddress.h
Normal file
49
api/logic/launch/steps/LookupServerAddress.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* 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 <QObjectPtr.h>
|
||||
#include <QDnsLookup>
|
||||
|
||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||
|
||||
class LookupServerAddress: public LaunchStep {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LookupServerAddress(LaunchTask *parent);
|
||||
virtual ~LookupServerAddress() {};
|
||||
|
||||
virtual void executeTask();
|
||||
virtual bool abort();
|
||||
virtual bool canAbort() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void setLookupAddress(const QString &lookupAddress);
|
||||
void setOutputAddressPtr(MinecraftServerTargetPtr output);
|
||||
|
||||
private slots:
|
||||
void on_dnsLookupFinished();
|
||||
|
||||
private:
|
||||
void resolve(const QString &address, quint16 port);
|
||||
|
||||
QDnsLookup *m_dnsLookup;
|
||||
QString m_lookupAddress;
|
||||
MinecraftServerTargetPtr m_output;
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <java/JavaVersion.h>
|
||||
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "launch/steps/LookupServerAddress.h"
|
||||
#include "launch/steps/PostLaunchCommand.h"
|
||||
#include "launch/steps/Update.h"
|
||||
#include "launch/steps/PreLaunchCommand.h"
|
||||
@@ -22,12 +23,15 @@
|
||||
#include "minecraft/launch/ClaimAccount.h"
|
||||
#include "minecraft/launch/ReconstructAssets.h"
|
||||
#include "minecraft/launch/ScanModFolders.h"
|
||||
#include "minecraft/launch/VerifyJavaInstall.h"
|
||||
#include "java/launch/CheckJava.h"
|
||||
#include "java/JavaUtils.h"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
#include "mod/ModFolderModel.h"
|
||||
#include "mod/ResourcePackFolderModel.h"
|
||||
#include "mod/TexturePackFolderModel.h"
|
||||
#include "WorldList.h"
|
||||
|
||||
#include "icons/IIconList.h"
|
||||
@@ -106,6 +110,15 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
|
||||
m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
|
||||
m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
|
||||
|
||||
// Game time
|
||||
auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
|
||||
m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
|
||||
m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride);
|
||||
|
||||
// Join server on launch, this does not have a global override
|
||||
m_settings->registerSetting("JoinServerOnLaunch", false);
|
||||
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
|
||||
|
||||
// DEPRECATED: Read what versions the user configuration thinks should be used
|
||||
m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
|
||||
m_settings->registerSetting("LWJGLVersion", "");
|
||||
@@ -390,7 +403,8 @@ static QString replaceTokensIn(QString text, QMap<QString, QString> with)
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) const
|
||||
QStringList MinecraftInstance::processMinecraftArgs(
|
||||
AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const
|
||||
{
|
||||
auto profile = m_components->getProfile();
|
||||
QString args_pattern = profile->getMinecraftArguments();
|
||||
@@ -399,6 +413,12 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) cons
|
||||
args_pattern += " --tweakClass " + tweaker;
|
||||
}
|
||||
|
||||
if (serverToJoin && !serverToJoin->address.isEmpty())
|
||||
{
|
||||
args_pattern += " --server " + serverToJoin->address;
|
||||
args_pattern += " --port " + QString::number(serverToJoin->port);
|
||||
}
|
||||
|
||||
QMap<QString, QString> token_mapping;
|
||||
// yggdrasil!
|
||||
if(session)
|
||||
@@ -435,7 +455,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) cons
|
||||
return parts;
|
||||
}
|
||||
|
||||
QString MinecraftInstance::createLaunchScript(AuthSessionPtr session)
|
||||
QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
||||
{
|
||||
QString launchScript;
|
||||
|
||||
@@ -456,8 +476,17 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session)
|
||||
launchScript += "appletClass " + appletClass + "\n";
|
||||
}
|
||||
|
||||
if (serverToJoin && !serverToJoin->address.isEmpty())
|
||||
{
|
||||
launchScript += "serverAddress " + serverToJoin->address + "\n";
|
||||
launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n";
|
||||
}
|
||||
|
||||
// generic minecraft params
|
||||
for (auto param : processMinecraftArgs(session))
|
||||
for (auto param : processMinecraftArgs(
|
||||
session,
|
||||
nullptr /* When using a launch script, the server parameters are handled by it*/
|
||||
))
|
||||
{
|
||||
launchScript += "param " + param + "\n";
|
||||
}
|
||||
@@ -507,7 +536,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session)
|
||||
return launchScript;
|
||||
}
|
||||
|
||||
QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
|
||||
QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
||||
{
|
||||
QStringList out;
|
||||
out << "Main Class:" << " " + getMainClass() << "";
|
||||
@@ -622,7 +651,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
|
||||
out << "";
|
||||
}
|
||||
|
||||
auto params = processMinecraftArgs(nullptr);
|
||||
auto params = processMinecraftArgs(nullptr, serverToJoin);
|
||||
out << "Params:";
|
||||
out << " " + params.join(' ');
|
||||
out << "";
|
||||
@@ -769,7 +798,7 @@ QString MinecraftInstance::getStatusbarDescription()
|
||||
|
||||
QString description;
|
||||
description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName()));
|
||||
if(totalTimePlayed() > 0)
|
||||
if(m_settings->get("ShowGameTime").toBool() && totalTimePlayed() > 0)
|
||||
{
|
||||
description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed())));
|
||||
}
|
||||
@@ -796,7 +825,7 @@ shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
|
||||
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
||||
{
|
||||
// FIXME: get rid of shared_from_this ...
|
||||
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
|
||||
@@ -828,6 +857,21 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
process->appendStep(new CreateGameFolders(pptr));
|
||||
}
|
||||
|
||||
if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool())
|
||||
{
|
||||
QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString();
|
||||
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
|
||||
}
|
||||
|
||||
if(serverToJoin && serverToJoin->port == 25565)
|
||||
{
|
||||
// Resolve server address to join on launch
|
||||
auto *step = new LookupServerAddress(pptr);
|
||||
step->setLookupAddress(serverToJoin->address);
|
||||
step->setOutputAddressPtr(serverToJoin);
|
||||
process->appendStep(step);
|
||||
}
|
||||
|
||||
// run pre-launch command if that's needed
|
||||
if(getPreLaunchCommand().size())
|
||||
{
|
||||
@@ -860,7 +904,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
|
||||
// print some instance info here...
|
||||
{
|
||||
process->appendStep(new PrintInstanceInfo(pptr, session));
|
||||
process->appendStep(new PrintInstanceInfo(pptr, session, serverToJoin));
|
||||
}
|
||||
|
||||
// extract native jars if needed
|
||||
@@ -873,6 +917,11 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
process->appendStep(new ReconstructAssets(pptr));
|
||||
}
|
||||
|
||||
// verify that minimum Java requirements are met
|
||||
{
|
||||
process->appendStep(new VerifyJavaInstall(pptr));
|
||||
}
|
||||
|
||||
{
|
||||
// actually launch the game
|
||||
auto method = launchMethod();
|
||||
@@ -881,6 +930,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
auto step = new LauncherPartLaunch(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
step->setAuthSession(session);
|
||||
step->setServerToJoin(serverToJoin);
|
||||
process->appendStep(step);
|
||||
}
|
||||
else if (method == "DirectJava")
|
||||
@@ -888,6 +938,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
auto step = new DirectJavaLaunch(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
step->setAuthSession(session);
|
||||
step->setServerToJoin(serverToJoin);
|
||||
process->appendStep(step);
|
||||
}
|
||||
}
|
||||
@@ -944,7 +995,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
|
||||
{
|
||||
if (!m_resource_pack_list)
|
||||
{
|
||||
m_resource_pack_list.reset(new ModFolderModel(resourcePacksDir()));
|
||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
|
||||
m_resource_pack_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction);
|
||||
}
|
||||
@@ -955,7 +1006,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
|
||||
{
|
||||
if (!m_texture_pack_list)
|
||||
{
|
||||
m_texture_pack_list.reset(new ModFolderModel(texturePacksDir()));
|
||||
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir()));
|
||||
m_texture_pack_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QProcess>
|
||||
#include <QDir>
|
||||
#include "multimc_logic_export.h"
|
||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||
|
||||
class ModFolderModel;
|
||||
class WorldList;
|
||||
@@ -76,11 +77,11 @@ public:
|
||||
|
||||
////// Launch stuff //////
|
||||
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override;
|
||||
QStringList extraArguments() const override;
|
||||
QStringList verboseDescription(AuthSessionPtr session) override;
|
||||
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
|
||||
QList<Mod> getJarMods() const;
|
||||
QString createLaunchScript(AuthSessionPtr session);
|
||||
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
|
||||
/// get arguments passed to java
|
||||
QStringList javaArguments() const;
|
||||
|
||||
@@ -107,7 +108,7 @@ public:
|
||||
virtual QString getMainClass() const;
|
||||
|
||||
// FIXME: remove
|
||||
virtual QStringList processMinecraftArgs(AuthSessionPtr account) const;
|
||||
virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const;
|
||||
|
||||
virtual JavaVersion getJavaVersion() const;
|
||||
|
||||
|
||||
@@ -65,4 +65,7 @@ VersionFilterData::VersionFilterData()
|
||||
QSet<QString>{"net.java.jinput:jinput", "net.java.jinput:jinput-platform",
|
||||
"net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl",
|
||||
"org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"};
|
||||
|
||||
java8BeginsDate = timeFromS3Time("2017-03-30T09:32:19+00:00");
|
||||
java16BeginsDate = timeFromS3Time("2021-05-12T11:19:15+00:00");
|
||||
}
|
||||
|
||||
@@ -23,5 +23,9 @@ struct VersionFilterData
|
||||
QDateTime legacyCutoffDate;
|
||||
// Libraries that belong to LWJGL
|
||||
QSet<QString> lwjglWhitelist;
|
||||
// release date of first version to require Java 8 (17w13a)
|
||||
QDateTime java8BeginsDate;
|
||||
// release data of first version to require Java 16 (21w19a)
|
||||
QDateTime java16BeginsDate;
|
||||
};
|
||||
extern VersionFilterData MULTIMC_LOGIC_EXPORT g_VersionFilterData;
|
||||
|
||||
@@ -15,7 +15,7 @@ void CreateGameFolders::executeTask()
|
||||
if(!FS::ensureFolderPathExists(minecraftInstance->gameRoot()))
|
||||
{
|
||||
emit logLine("Couldn't create the main game folder", MessageLevel::Error);
|
||||
emitFailed("Couldn't create the main game folder");
|
||||
emitFailed(tr("Couldn't create the main game folder"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ void DirectJavaLaunch::executeTask()
|
||||
// make detachable - this will keep the process running even if the object is destroyed
|
||||
m_process.setDetachable(true);
|
||||
|
||||
auto mcArgs = minecraftInstance->processMinecraftArgs(m_session);
|
||||
auto mcArgs = minecraftInstance->processMinecraftArgs(m_session, m_serverToJoin);
|
||||
args.append(mcArgs);
|
||||
|
||||
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
|
||||
@@ -66,9 +66,9 @@ void DirectJavaLaunch::executeTask()
|
||||
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
|
||||
if (realWrapperCommand.isEmpty())
|
||||
{
|
||||
QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand);
|
||||
emit logLine(reason, MessageLevel::Fatal);
|
||||
emitFailed(reason);
|
||||
const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found.");
|
||||
emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal);
|
||||
emitFailed(tr(reason).arg(wrapperCommand));
|
||||
return;
|
||||
}
|
||||
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC);
|
||||
@@ -87,18 +87,17 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state)
|
||||
{
|
||||
case LoggedProcess::FailedToStart:
|
||||
{
|
||||
//: Error message displayed if instace can't start
|
||||
QString reason = tr("Could not launch minecraft!");
|
||||
//: Error message displayed if instance can't start
|
||||
const char *reason = QT_TR_NOOP("Could not launch minecraft!");
|
||||
emit logLine(reason, MessageLevel::Fatal);
|
||||
emitFailed(reason);
|
||||
emitFailed(tr(reason));
|
||||
return;
|
||||
}
|
||||
case LoggedProcess::Aborted:
|
||||
case LoggedProcess::Crashed:
|
||||
|
||||
{
|
||||
m_parent->setPid(-1);
|
||||
emitFailed("Game crashed.");
|
||||
emitFailed(tr("Game crashed."));
|
||||
return;
|
||||
}
|
||||
case LoggedProcess::Finished:
|
||||
@@ -108,7 +107,7 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state)
|
||||
auto exitCode = m_process.exitCode();
|
||||
if(exitCode != 0)
|
||||
{
|
||||
emitFailed("Game crashed.");
|
||||
emitFailed(tr("Game crashed."));
|
||||
return;
|
||||
}
|
||||
//FIXME: make this work again
|
||||
@@ -118,7 +117,7 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state)
|
||||
break;
|
||||
}
|
||||
case LoggedProcess::Running:
|
||||
emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
|
||||
emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
|
||||
m_parent->setPid(m_process.processId());
|
||||
m_parent->instance()->setLastLaunch();
|
||||
break;
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include <LoggedProcess.h>
|
||||
#include <minecraft/auth/AuthSession.h>
|
||||
|
||||
#include "MinecraftServerTarget.h"
|
||||
|
||||
class DirectJavaLaunch: public LaunchStep
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -38,6 +40,12 @@ public:
|
||||
{
|
||||
m_session = session;
|
||||
}
|
||||
|
||||
void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
|
||||
{
|
||||
m_serverToJoin = std::move(serverToJoin);
|
||||
}
|
||||
|
||||
private slots:
|
||||
void on_state(LoggedProcess::State state);
|
||||
|
||||
@@ -45,5 +53,6 @@ private:
|
||||
LoggedProcess m_process;
|
||||
QString m_command;
|
||||
AuthSessionPtr m_session;
|
||||
MinecraftServerTargetPtr m_serverToJoin;
|
||||
};
|
||||
|
||||
|
||||
@@ -94,9 +94,9 @@ void ExtractNatives::executeTask()
|
||||
{
|
||||
if(!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW))
|
||||
{
|
||||
auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath);
|
||||
emit logLine(reason, MessageLevel::Fatal);
|
||||
emitFailed(reason);
|
||||
const char *reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'");
|
||||
emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal);
|
||||
emitFailed(tr(reason).arg(source, outputPath));
|
||||
}
|
||||
}
|
||||
emitSucceeded();
|
||||
|
||||
@@ -59,7 +59,7 @@ void LauncherPartLaunch::executeTask()
|
||||
auto instance = m_parent->instance();
|
||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
||||
|
||||
m_launchScript = minecraftInstance->createLaunchScript(m_session);
|
||||
m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin);
|
||||
QStringList args = minecraftInstance->javaArguments();
|
||||
QString allArgs = args.join(", ");
|
||||
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC);
|
||||
@@ -118,9 +118,9 @@ void LauncherPartLaunch::executeTask()
|
||||
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
|
||||
if (realWrapperCommand.isEmpty())
|
||||
{
|
||||
QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand);
|
||||
emit logLine(reason, MessageLevel::Fatal);
|
||||
emitFailed(reason);
|
||||
const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found.");
|
||||
emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal);
|
||||
emitFailed(tr(reason).arg(wrapperCommand));
|
||||
return;
|
||||
}
|
||||
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC);
|
||||
@@ -140,17 +140,16 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
|
||||
case LoggedProcess::FailedToStart:
|
||||
{
|
||||
//: Error message displayed if instace can't start
|
||||
QString reason = tr("Could not launch minecraft!");
|
||||
const char *reason = QT_TR_NOOP("Could not launch minecraft!");
|
||||
emit logLine(reason, MessageLevel::Fatal);
|
||||
emitFailed(reason);
|
||||
emitFailed(tr(reason));
|
||||
return;
|
||||
}
|
||||
case LoggedProcess::Aborted:
|
||||
case LoggedProcess::Crashed:
|
||||
|
||||
{
|
||||
m_parent->setPid(-1);
|
||||
emitFailed("Game crashed.");
|
||||
emitFailed(tr("Game crashed."));
|
||||
return;
|
||||
}
|
||||
case LoggedProcess::Finished:
|
||||
@@ -160,7 +159,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
|
||||
auto exitCode = m_process.exitCode();
|
||||
if(exitCode != 0)
|
||||
{
|
||||
emitFailed("Game crashed.");
|
||||
emitFailed(tr("Game crashed."));
|
||||
return;
|
||||
}
|
||||
//FIXME: make this work again
|
||||
@@ -170,7 +169,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
|
||||
break;
|
||||
}
|
||||
case LoggedProcess::Running:
|
||||
emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
|
||||
emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
|
||||
m_parent->setPid(m_process.processId());
|
||||
m_parent->instance()->setLastLaunch();
|
||||
// send the launch script to the launcher part
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include <LoggedProcess.h>
|
||||
#include <minecraft/auth/AuthSession.h>
|
||||
|
||||
#include "MinecraftServerTarget.h"
|
||||
|
||||
class LauncherPartLaunch: public LaunchStep
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -39,6 +41,11 @@ public:
|
||||
m_session = session;
|
||||
}
|
||||
|
||||
void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
|
||||
{
|
||||
m_serverToJoin = std::move(serverToJoin);
|
||||
}
|
||||
|
||||
private slots:
|
||||
void on_state(LoggedProcess::State state);
|
||||
|
||||
@@ -47,5 +54,7 @@ private:
|
||||
QString m_command;
|
||||
AuthSessionPtr m_session;
|
||||
QString m_launchScript;
|
||||
MinecraftServerTargetPtr m_serverToJoin;
|
||||
|
||||
bool mayProceed = false;
|
||||
};
|
||||
|
||||
66
api/logic/minecraft/launch/MinecraftServerTarget.cpp
Normal file
66
api/logic/minecraft/launch/MinecraftServerTarget.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "MinecraftServerTarget.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
MinecraftServerTarget MinecraftServerTarget::parse(const QString &fullAddress) {
|
||||
QStringList split = fullAddress.split(":");
|
||||
|
||||
// The logic below replicates the exact logic minecraft uses for parsing server addresses.
|
||||
// While the conversion is not lossless and eats errors, it ensures the same behavior
|
||||
// within Minecraft and MultiMC when entering server addresses.
|
||||
if (fullAddress.startsWith("["))
|
||||
{
|
||||
int bracket = fullAddress.indexOf("]");
|
||||
if (bracket > 0)
|
||||
{
|
||||
QString ipv6 = fullAddress.mid(1, bracket - 1);
|
||||
QString port = fullAddress.mid(bracket + 1).trimmed();
|
||||
|
||||
if (port.startsWith(":") && !ipv6.isEmpty())
|
||||
{
|
||||
port = port.mid(1);
|
||||
split = QStringList({ ipv6, port });
|
||||
}
|
||||
else
|
||||
{
|
||||
split = QStringList({ipv6});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (split.size() > 2)
|
||||
{
|
||||
split = QStringList({fullAddress});
|
||||
}
|
||||
|
||||
QString realAddress = split[0];
|
||||
|
||||
quint16 realPort = 25565;
|
||||
if (split.size() > 1)
|
||||
{
|
||||
bool ok;
|
||||
realPort = split[1].toUInt(&ok);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
realPort = 25565;
|
||||
}
|
||||
}
|
||||
|
||||
return MinecraftServerTarget { realAddress, realPort };
|
||||
}
|
||||
30
api/logic/minecraft/launch/MinecraftServerTarget.h
Normal file
30
api/logic/minecraft/launch/MinecraftServerTarget.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QString>
|
||||
#include <multimc_logic_export.h>
|
||||
|
||||
struct MinecraftServerTarget {
|
||||
QString address;
|
||||
quint16 port;
|
||||
|
||||
static MULTIMC_LOGIC_EXPORT MinecraftServerTarget parse(const QString &fullAddress);
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<MinecraftServerTarget> MinecraftServerTargetPtr;
|
||||
@@ -101,6 +101,6 @@ void PrintInstanceInfo::executeTask()
|
||||
#endif
|
||||
|
||||
logLines(log, MessageLevel::MultiMC);
|
||||
logLines(instance->verboseDescription(m_session), MessageLevel::MultiMC);
|
||||
logLines(instance->verboseDescription(m_session, m_serverToJoin), MessageLevel::MultiMC);
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
@@ -18,13 +18,15 @@
|
||||
#include <launch/LaunchStep.h>
|
||||
#include <memory>
|
||||
#include "minecraft/auth/AuthSession.h"
|
||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||
|
||||
// FIXME: temporary wrapper for existing task.
|
||||
class PrintInstanceInfo: public LaunchStep
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session) : LaunchStep(parent), m_session(session) {};
|
||||
explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) :
|
||||
LaunchStep(parent), m_session(session), m_serverToJoin(serverToJoin) {};
|
||||
virtual ~PrintInstanceInfo(){};
|
||||
|
||||
virtual void executeTask();
|
||||
@@ -34,5 +36,6 @@ public:
|
||||
}
|
||||
private:
|
||||
AuthSessionPtr m_session;
|
||||
MinecraftServerTargetPtr m_serverToJoin;
|
||||
};
|
||||
|
||||
|
||||
34
api/logic/minecraft/launch/VerifyJavaInstall.cpp
Normal file
34
api/logic/minecraft/launch/VerifyJavaInstall.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "VerifyJavaInstall.h"
|
||||
|
||||
#include <launch/LaunchTask.h>
|
||||
#include <minecraft/MinecraftInstance.h>
|
||||
#include <minecraft/PackProfile.h>
|
||||
#include <minecraft/VersionFilterData.h>
|
||||
|
||||
void VerifyJavaInstall::executeTask() {
|
||||
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
|
||||
|
||||
auto javaVersion = m_inst->getJavaVersion();
|
||||
auto minecraftComponent = m_inst->getPackProfile()->getComponent("net.minecraft");
|
||||
|
||||
// Java 16 requirement
|
||||
if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java16BeginsDate) {
|
||||
if (javaVersion.major() < 16) {
|
||||
emit logLine("Minecraft 21w19a and above require the use of Java 16",
|
||||
MessageLevel::Fatal);
|
||||
emitFailed(tr("Minecraft 21w19a and above require the use of Java 16"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Java 8 requirement
|
||||
else if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java8BeginsDate) {
|
||||
if (javaVersion.major() < 8) {
|
||||
emit logLine("Minecraft 17w13a and above require the use of Java 8",
|
||||
MessageLevel::Fatal);
|
||||
emitFailed(tr("Minecraft 17w13a and above require the use of Java 8"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
emitSucceeded();
|
||||
}
|
||||
17
api/logic/minecraft/launch/VerifyJavaInstall.h
Normal file
17
api/logic/minecraft/launch/VerifyJavaInstall.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <launch/LaunchStep.h>
|
||||
|
||||
class VerifyJavaInstall : public LaunchStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VerifyJavaInstall(LaunchTask *parent) : LaunchStep(parent) {
|
||||
};
|
||||
~VerifyJavaInstall() override = default;
|
||||
|
||||
void executeTask() override;
|
||||
bool canAbort() const override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -225,7 +225,7 @@ QString LegacyInstance::getStatusbarDescription()
|
||||
return tr("Instance from previous versions.");
|
||||
}
|
||||
|
||||
QStringList LegacyInstance::verboseDescription(AuthSessionPtr session)
|
||||
QStringList LegacyInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
||||
{
|
||||
QStringList out;
|
||||
|
||||
|
||||
@@ -111,7 +111,8 @@ public:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(
|
||||
AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -125,7 +126,7 @@ public:
|
||||
}
|
||||
|
||||
QString getStatusbarDescription() override;
|
||||
QStringList verboseDescription(AuthSessionPtr session) override;
|
||||
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
|
||||
|
||||
QProcessEnvironment createEnvironment() override
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QJsonValue>
|
||||
#include <quazip.h>
|
||||
#include <quazipfile.h>
|
||||
#include <toml.h>
|
||||
|
||||
#include "settings/INIFile.h"
|
||||
#include "FileSystem.h"
|
||||
@@ -90,6 +91,124 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// https://github.com/MinecraftForge/Documentation/blob/5ab4ba6cf9abc0ac4c0abd96ad187461aefd72af/docs/gettingstarted/structuring.md
|
||||
std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
|
||||
{
|
||||
std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
|
||||
|
||||
char errbuf[200];
|
||||
// top-level table
|
||||
toml_table_t* tomlData = toml_parse(contents.data(), errbuf, sizeof(errbuf));
|
||||
|
||||
if(!tomlData)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// array defined by [[mods]]
|
||||
toml_array_t* tomlModsArr = toml_array_in(tomlData, "mods");
|
||||
// we only really care about the first element, since multiple mods in one file is not supported by us at the moment
|
||||
toml_table_t* tomlModsTable0 = toml_table_at(tomlModsArr, 0);
|
||||
|
||||
// mandatory properties - always in [[mods]]
|
||||
toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId");
|
||||
if(modIdDatum.ok)
|
||||
{
|
||||
details->mod_id = modIdDatum.u.s;
|
||||
// library says this is required for strings
|
||||
free(modIdDatum.u.s);
|
||||
}
|
||||
toml_datum_t versionDatum = toml_string_in(tomlModsTable0, "version");
|
||||
if(versionDatum.ok)
|
||||
{
|
||||
details->version = versionDatum.u.s;
|
||||
free(versionDatum.u.s);
|
||||
}
|
||||
toml_datum_t displayNameDatum = toml_string_in(tomlModsTable0, "displayName");
|
||||
if(displayNameDatum.ok)
|
||||
{
|
||||
details->name = displayNameDatum.u.s;
|
||||
free(displayNameDatum.u.s);
|
||||
}
|
||||
toml_datum_t descriptionDatum = toml_string_in(tomlModsTable0, "description");
|
||||
if(descriptionDatum.ok)
|
||||
{
|
||||
details->description = descriptionDatum.u.s;
|
||||
free(descriptionDatum.u.s);
|
||||
}
|
||||
|
||||
// optional properties - can be in the root table or [[mods]]
|
||||
toml_datum_t authorsDatum = toml_string_in(tomlData, "authors");
|
||||
QString authors = "";
|
||||
if(authorsDatum.ok)
|
||||
{
|
||||
authors = authorsDatum.u.s;
|
||||
free(authorsDatum.u.s);
|
||||
}
|
||||
else
|
||||
{
|
||||
authorsDatum = toml_string_in(tomlModsTable0, "authors");
|
||||
if(authorsDatum.ok)
|
||||
{
|
||||
authors = authorsDatum.u.s;
|
||||
free(authorsDatum.u.s);
|
||||
}
|
||||
}
|
||||
if(!authors.isEmpty())
|
||||
{
|
||||
// author information is stored as a string now, not a list
|
||||
details->authors.append(authors);
|
||||
}
|
||||
// is credits even used anywhere? including this for completion/parity with old data version
|
||||
toml_datum_t creditsDatum = toml_string_in(tomlData, "credits");
|
||||
QString credits = "";
|
||||
if(creditsDatum.ok)
|
||||
{
|
||||
authors = creditsDatum.u.s;
|
||||
free(creditsDatum.u.s);
|
||||
}
|
||||
else
|
||||
{
|
||||
creditsDatum = toml_string_in(tomlModsTable0, "credits");
|
||||
if(creditsDatum.ok)
|
||||
{
|
||||
credits = creditsDatum.u.s;
|
||||
free(creditsDatum.u.s);
|
||||
}
|
||||
}
|
||||
details->credits = credits;
|
||||
toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL");
|
||||
QString homeurl = "";
|
||||
if(homeurlDatum.ok)
|
||||
{
|
||||
homeurl = homeurlDatum.u.s;
|
||||
free(homeurlDatum.u.s);
|
||||
}
|
||||
else
|
||||
{
|
||||
homeurlDatum = toml_string_in(tomlModsTable0, "displayURL");
|
||||
if(homeurlDatum.ok)
|
||||
{
|
||||
homeurl = homeurlDatum.u.s;
|
||||
free(homeurlDatum.u.s);
|
||||
}
|
||||
}
|
||||
if(!homeurl.isEmpty())
|
||||
{
|
||||
// fix up url.
|
||||
if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://"))
|
||||
{
|
||||
homeurl.prepend("http://");
|
||||
}
|
||||
}
|
||||
details->homeurl = homeurl;
|
||||
|
||||
// this seems to be recursive, so it should free everything
|
||||
toml_free(tomlData);
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
// https://fabricmc.net/wiki/documentation:fabric_mod_json
|
||||
std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
|
||||
{
|
||||
@@ -198,7 +317,57 @@ void LocalModParseTask::processAsZip()
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile("mcmod.info"))
|
||||
if (zip.setCurrentFile("META-INF/mods.toml"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
m_result->details = ReadMCModTOML(file.readAll());
|
||||
file.close();
|
||||
|
||||
// to replace ${file.jarVersion} with the actual version, as needed
|
||||
if (m_result->details && m_result->details->version == "${file.jarVersion}")
|
||||
{
|
||||
if (zip.setCurrentFile("META-INF/MANIFEST.MF"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// quick and dirty line-by-line parser
|
||||
auto manifestLines = file.readAll().split('\n');
|
||||
QString manifestVersion = "";
|
||||
for (auto &line : manifestLines)
|
||||
{
|
||||
if (QString(line).startsWith("Implementation-Version: "))
|
||||
{
|
||||
manifestVersion = QString(line).remove("Implementation-Version: ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// some mods use ${projectversion} in their build.gradle, causing this mess to show up in MANIFEST.MF
|
||||
// also keep with forge's behavior of setting the version to "NONE" if none is found
|
||||
if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "")
|
||||
{
|
||||
manifestVersion = "NONE";
|
||||
}
|
||||
|
||||
m_result->details->version = manifestVersion;
|
||||
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
else if (zip.setCurrentFile("mcmod.info"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
|
||||
23
api/logic/minecraft/mod/ResourcePackFolderModel.cpp
Normal file
23
api/logic/minecraft/mod/ResourcePackFolderModel.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "ResourcePackFolderModel.h"
|
||||
|
||||
ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) {
|
||||
}
|
||||
|
||||
QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (role == Qt::ToolTipRole) {
|
||||
switch (section) {
|
||||
case ActiveColumn:
|
||||
return tr("Is the resource pack enabled?");
|
||||
case NameColumn:
|
||||
return tr("The name of the resource pack.");
|
||||
case VersionColumn:
|
||||
return tr("The version of the resource pack.");
|
||||
case DateColumn:
|
||||
return tr("The date and time this resource pack was last changed (or added).");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
return ModFolderModel::headerData(section, orientation, role);
|
||||
}
|
||||
13
api/logic/minecraft/mod/ResourcePackFolderModel.h
Normal file
13
api/logic/minecraft/mod/ResourcePackFolderModel.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ModFolderModel.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ResourcePackFolderModel : public ModFolderModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ResourcePackFolderModel(const QString &dir);
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
};
|
||||
23
api/logic/minecraft/mod/TexturePackFolderModel.cpp
Normal file
23
api/logic/minecraft/mod/TexturePackFolderModel.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "TexturePackFolderModel.h"
|
||||
|
||||
TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) {
|
||||
}
|
||||
|
||||
QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (role == Qt::ToolTipRole) {
|
||||
switch (section) {
|
||||
case ActiveColumn:
|
||||
return tr("Is the texture pack enabled?");
|
||||
case NameColumn:
|
||||
return tr("The name of the texture pack.");
|
||||
case VersionColumn:
|
||||
return tr("The version of the texture pack.");
|
||||
case DateColumn:
|
||||
return tr("The date and time this texture pack was last changed (or added).");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
return ModFolderModel::headerData(section, orientation, role);
|
||||
}
|
||||
13
api/logic/minecraft/mod/TexturePackFolderModel.h
Normal file
13
api/logic/minecraft/mod/TexturePackFolderModel.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ModFolderModel.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT TexturePackFolderModel : public ModFolderModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TexturePackFolderModel(const QString &dir);
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
};
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <MMCZip.h>
|
||||
#include <minecraft/OneSixVersionFormat.h>
|
||||
#include <Version.h>
|
||||
#include <net/ChecksumValidator.h>
|
||||
#include "ATLPackInstallTask.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
@@ -18,15 +19,20 @@
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
PackInstallTask::PackInstallTask(QString pack, QString version)
|
||||
PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version)
|
||||
{
|
||||
m_support = support;
|
||||
m_pack = pack;
|
||||
m_version_name = version;
|
||||
}
|
||||
|
||||
bool PackInstallTask::abort()
|
||||
{
|
||||
return true;
|
||||
if(abortable)
|
||||
{
|
||||
return jobPtr->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PackInstallTask::executeTask()
|
||||
@@ -154,23 +160,56 @@ QString PackInstallTask::getVersionForLoader(QString uid)
|
||||
auto vlist = ENV.metadataIndex()->get(uid);
|
||||
if(!vlist)
|
||||
{
|
||||
emitFailed(tr("Failed to get local metadata index for ") + uid);
|
||||
emitFailed(tr("Failed to get local metadata index for %1").arg(uid));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
// todo: filter by Minecraft version
|
||||
|
||||
if(m_version.loader.recommended) {
|
||||
return vlist.get()->getRecommended().get()->descriptor();
|
||||
if(!vlist->isLoaded()) {
|
||||
vlist->load(Net::Mode::Online);
|
||||
}
|
||||
else if(m_version.loader.latest) {
|
||||
return vlist.get()->at(0)->descriptor();
|
||||
|
||||
if(m_version.loader.recommended || m_version.loader.latest) {
|
||||
for (int i = 0; i < vlist->versions().size(); i++) {
|
||||
auto version = vlist->versions().at(i);
|
||||
auto reqs = version->requires();
|
||||
|
||||
// filter by minecraft version, if the loader depends on a certain version.
|
||||
// not all mod loaders depend on a given Minecraft version, so we won't do this
|
||||
// filtering for those loaders.
|
||||
if (m_version.loader.type != "fabric") {
|
||||
auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) {
|
||||
return req.uid == "net.minecraft";
|
||||
});
|
||||
if (iter == reqs.end()) continue;
|
||||
if (iter->equalsVersion != m_version.minecraft) continue;
|
||||
}
|
||||
|
||||
if (m_version.loader.recommended) {
|
||||
// first recommended build we find, we use.
|
||||
if (!version->isRecommended()) continue;
|
||||
}
|
||||
|
||||
return version->descriptor();
|
||||
}
|
||||
|
||||
emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
else if(m_version.loader.choose) {
|
||||
// todo: implement
|
||||
// Fabric Loader doesn't depend on a given Minecraft version.
|
||||
if (m_version.loader.type == "fabric") {
|
||||
return m_support->chooseVersion(vlist, Q_NULLPTR);
|
||||
}
|
||||
|
||||
return m_support->chooseVersion(vlist, m_version.minecraft);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_version.loader.version == Q_NULLPTR || m_version.loader.version.isEmpty()) {
|
||||
emitFailed(tr("No loader version set for modpack!"));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
return m_version.loader.version;
|
||||
}
|
||||
|
||||
@@ -373,21 +412,29 @@ void PackInstallTask::installConfigs()
|
||||
auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", path);
|
||||
entry->setStale(true);
|
||||
|
||||
jobPtr->addNetAction(Net::Download::makeCached(url, entry));
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
if (!m_version.configs.sha1.isEmpty()) {
|
||||
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||
}
|
||||
jobPtr->addNetAction(dl);
|
||||
archivePath = entry->getFullPath();
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
extractConfigs();
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
abortable = true;
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
||||
@@ -423,13 +470,31 @@ void PackInstallTask::extractConfigs()
|
||||
void PackInstallTask::downloadMods()
|
||||
{
|
||||
qDebug() << "PackInstallTask::installMods: " << QThread::currentThreadId();
|
||||
|
||||
QVector<ATLauncher::VersionMod> optionalMods;
|
||||
for (const auto& mod : m_version.mods) {
|
||||
if (mod.optional) {
|
||||
optionalMods.push_back(mod);
|
||||
}
|
||||
}
|
||||
|
||||
// Select optional mods, if pack contains any
|
||||
QVector<QString> selectedMods;
|
||||
if (!optionalMods.isEmpty()) {
|
||||
setStatus(tr("Selecting optional mods..."));
|
||||
selectedMods = m_support->chooseOptionalMods(optionalMods);
|
||||
}
|
||||
|
||||
setStatus(tr("Downloading mods..."));
|
||||
|
||||
jarmods.clear();
|
||||
jobPtr.reset(new NetJob(tr("Mod download")));
|
||||
for(const auto& mod : m_version.mods) {
|
||||
// skip optional mods for now
|
||||
if(mod.optional) continue;
|
||||
// skip non-client mods
|
||||
if(!mod.client) continue;
|
||||
|
||||
// skip optional mods that were not selected
|
||||
if(mod.optional && !selectedMods.contains(mod.name)) continue;
|
||||
|
||||
QString url;
|
||||
switch(mod.download) {
|
||||
@@ -451,12 +516,15 @@ void PackInstallTask::downloadMods()
|
||||
auto cacheName = fileName.completeBaseName() + "-" + mod.md5 + "." + fileName.suffix();
|
||||
|
||||
if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) {
|
||||
|
||||
auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName);
|
||||
entry->setStale(true);
|
||||
modsToExtract.insert(entry->getFullPath(), mod);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
}
|
||||
jobPtr->addNetAction(dl);
|
||||
}
|
||||
else if(mod.type == ModType::Decomp) {
|
||||
@@ -465,6 +533,10 @@ void PackInstallTask::downloadMods()
|
||||
modsToDecomp.insert(entry->getFullPath(), mod);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
}
|
||||
jobPtr->addNetAction(dl);
|
||||
}
|
||||
else {
|
||||
@@ -475,6 +547,10 @@ void PackInstallTask::downloadMods()
|
||||
entry->setStale(true);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
}
|
||||
jobPtr->addNetAction(dl);
|
||||
|
||||
auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
|
||||
@@ -507,11 +583,13 @@ void PackInstallTask::downloadMods()
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModsDownloaded);
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
abortable = true;
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
||||
@@ -519,10 +597,12 @@ void PackInstallTask::downloadMods()
|
||||
}
|
||||
|
||||
void PackInstallTask::onModsDownloaded() {
|
||||
abortable = false;
|
||||
|
||||
qDebug() << "PackInstallTask::onModsDownloaded: " << QThread::currentThreadId();
|
||||
jobPtr.reset();
|
||||
|
||||
if(modsToExtract.size() || modsToDecomp.size() || modsToCopy.size()) {
|
||||
if(!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) {
|
||||
m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy);
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted);
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]()
|
||||
@@ -629,6 +709,7 @@ void PackInstallTask::install()
|
||||
|
||||
// Use a component to add libraries BEFORE Minecraft
|
||||
if(!createLibrariesComponent(instance.instanceRoot(), components)) {
|
||||
emitFailed(tr("Failed to create libraries component"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -666,6 +747,7 @@ void PackInstallTask::install()
|
||||
// Use a component to fill in the rest of the data
|
||||
// todo: use more detection
|
||||
if(!createPackComponent(instance.instanceRoot(), components)) {
|
||||
emitFailed(tr("Failed to create pack component"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,31 @@
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT UserInteractionSupport {
|
||||
|
||||
public:
|
||||
/**
|
||||
* Requests a user interaction to select which optional mods should be installed.
|
||||
*/
|
||||
virtual QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) = 0;
|
||||
|
||||
/**
|
||||
* Requests a user interaction to select a component version from a given version list
|
||||
* and constrained to a given Minecraft version.
|
||||
*/
|
||||
virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0;
|
||||
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PackInstallTask(QString pack, QString version);
|
||||
explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version);
|
||||
virtual ~PackInstallTask(){}
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
@@ -54,6 +71,10 @@ private:
|
||||
void install();
|
||||
|
||||
private:
|
||||
UserInteractionSupport *m_support;
|
||||
|
||||
bool abortable = false;
|
||||
|
||||
NetJobPtr jobPtr;
|
||||
QByteArray response;
|
||||
|
||||
@@ -76,9 +97,6 @@ private:
|
||||
QFuture<bool> m_modExtractFuture;
|
||||
QFutureWatcher<bool> m_modExtractFutureWatcher;
|
||||
|
||||
QFuture<bool> m_decompFuture;
|
||||
QFutureWatcher<bool> m_decompFutureWatcher;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -81,12 +81,21 @@ static ATLauncher::ModType parseModType(QString rawType) {
|
||||
|
||||
static void loadVersionLoader(ATLauncher::VersionLoader & p, QJsonObject & obj) {
|
||||
p.type = Json::requireString(obj, "type");
|
||||
p.latest = Json::ensureBoolean(obj, QString("latest"), false);
|
||||
p.choose = Json::ensureBoolean(obj, QString("choose"), false);
|
||||
p.recommended = Json::ensureBoolean(obj, QString("recommended"), false);
|
||||
|
||||
auto metadata = Json::requireObject(obj, "metadata");
|
||||
p.version = Json::requireString(metadata, "version");
|
||||
p.latest = Json::ensureBoolean(metadata, QString("latest"), false);
|
||||
p.recommended = Json::ensureBoolean(metadata, QString("recommended"), false);
|
||||
|
||||
// Minecraft Forge
|
||||
if (p.type == "forge") {
|
||||
p.version = Json::ensureString(metadata, "version", "");
|
||||
}
|
||||
|
||||
// Fabric Loader
|
||||
if (p.type == "fabric") {
|
||||
p.version = Json::ensureString(metadata, "loader", "");
|
||||
}
|
||||
}
|
||||
|
||||
static void loadVersionLibrary(ATLauncher::VersionLibrary & p, QJsonObject & obj) {
|
||||
@@ -100,6 +109,11 @@ static void loadVersionLibrary(ATLauncher::VersionLibrary & p, QJsonObject & obj
|
||||
p.server = Json::ensureString(obj, "server", "");
|
||||
}
|
||||
|
||||
static void loadVersionConfigs(ATLauncher::VersionConfigs & p, QJsonObject & obj) {
|
||||
p.filesize = Json::requireInteger(obj, "filesize");
|
||||
p.sha1 = Json::requireString(obj, "sha1");
|
||||
}
|
||||
|
||||
static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
|
||||
p.name = Json::requireString(obj, "name");
|
||||
p.version = Json::requireString(obj, "version");
|
||||
@@ -134,7 +148,24 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
|
||||
p.decompFile = Json::requireString(obj, "decompFile");
|
||||
}
|
||||
|
||||
p.description = Json::ensureString(obj, QString("description"), "");
|
||||
p.optional = Json::ensureBoolean(obj, QString("optional"), false);
|
||||
p.recommended = Json::ensureBoolean(obj, QString("recommended"), false);
|
||||
p.selected = Json::ensureBoolean(obj, QString("selected"), false);
|
||||
p.hidden = Json::ensureBoolean(obj, QString("hidden"), false);
|
||||
p.library = Json::ensureBoolean(obj, QString("library"), false);
|
||||
p.group = Json::ensureString(obj, QString("group"), "");
|
||||
if(obj.contains("depends")) {
|
||||
auto dependsArr = Json::requireArray(obj, "depends");
|
||||
for (const auto depends : dependsArr) {
|
||||
p.depends.append(Json::requireString(depends));
|
||||
}
|
||||
}
|
||||
|
||||
p.client = Json::ensureBoolean(obj, QString("client"), false);
|
||||
|
||||
// computed
|
||||
p.effectively_hidden = p.hidden || p.library;
|
||||
}
|
||||
|
||||
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
|
||||
@@ -169,12 +200,19 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
|
||||
}
|
||||
}
|
||||
|
||||
auto mods = Json::requireArray(obj, "mods");
|
||||
for (const auto modRaw : mods)
|
||||
{
|
||||
auto modObj = Json::requireObject(modRaw);
|
||||
ATLauncher::VersionMod mod;
|
||||
loadVersionMod(mod, modObj);
|
||||
v.mods.append(mod);
|
||||
if(obj.contains("mods")) {
|
||||
auto mods = Json::requireArray(obj, "mods");
|
||||
for (const auto modRaw : mods)
|
||||
{
|
||||
auto modObj = Json::requireObject(modRaw);
|
||||
ATLauncher::VersionMod mod;
|
||||
loadVersionMod(mod, modObj);
|
||||
v.mods.append(mod);
|
||||
}
|
||||
}
|
||||
|
||||
if(obj.contains("configs")) {
|
||||
auto configsObj = Json::requireObject(obj, "configs");
|
||||
loadVersionConfigs(v.configs, configsObj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,25 @@ struct VersionMod
|
||||
QString decompType_raw;
|
||||
QString decompFile;
|
||||
|
||||
QString description;
|
||||
bool optional;
|
||||
bool recommended;
|
||||
bool selected;
|
||||
bool hidden;
|
||||
bool library;
|
||||
QString group;
|
||||
QVector<QString> depends;
|
||||
|
||||
bool client;
|
||||
|
||||
// computed
|
||||
bool effectively_hidden;
|
||||
};
|
||||
|
||||
struct VersionConfigs
|
||||
{
|
||||
int filesize;
|
||||
QString sha1;
|
||||
};
|
||||
|
||||
struct PackVersion
|
||||
@@ -100,6 +118,7 @@ struct PackVersion
|
||||
VersionLoader loader;
|
||||
QVector<VersionLibrary> libraries;
|
||||
QVector<VersionMod> mods;
|
||||
VersionConfigs configs;
|
||||
};
|
||||
|
||||
MULTIMC_LOGIC_EXPORT void loadVersion(PackVersion & v, QJsonObject & obj);
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
explicit PackInstallTask(Modpack pack, QString version);
|
||||
virtual ~PackInstallTask(){}
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "FTBPackInstallTask.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
#include "Env.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
namespace ModpacksCH {
|
||||
@@ -17,7 +19,11 @@ PackInstallTask::PackInstallTask(Modpack pack, QString version)
|
||||
|
||||
bool PackInstallTask::abort()
|
||||
{
|
||||
return true;
|
||||
if(abortable)
|
||||
{
|
||||
return jobPtr->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PackInstallTask::executeTask()
|
||||
@@ -93,31 +99,41 @@ void PackInstallTask::downloadPack()
|
||||
for(auto file : m_version.files) {
|
||||
if(file.serverOnly) continue;
|
||||
|
||||
QFileInfo fileName(file.name);
|
||||
auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix();
|
||||
|
||||
auto entry = ENV.metacache()->resolveEntry("ModpacksCHPacks", cacheName);
|
||||
entry->setStale(true);
|
||||
|
||||
auto relpath = FS::PathCombine("minecraft", file.path, file.name);
|
||||
auto path = FS::PathCombine(m_stagingPath, relpath);
|
||||
|
||||
qDebug() << "Will download" << file.url << "to" << path;
|
||||
auto dl = Net::Download::makeFile(file.url, path);
|
||||
filesToCopy[entry->getFullPath()] = path;
|
||||
|
||||
auto dl = Net::Download::makeCached(file.url, entry);
|
||||
if (!file.sha1.isEmpty()) {
|
||||
auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||
}
|
||||
jobPtr->addNetAction(dl);
|
||||
}
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
install();
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
|
||||
// FIXME: Temporarily ignore file download failures (matching FTB's installer),
|
||||
// while FTB's data is fucked.
|
||||
qWarning() << "Failed to download files for modpack: " + reason;
|
||||
|
||||
install();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
abortable = true;
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
||||
@@ -126,6 +142,19 @@ void PackInstallTask::downloadPack()
|
||||
|
||||
void PackInstallTask::install()
|
||||
{
|
||||
setStatus(tr("Copying modpack files"));
|
||||
|
||||
for (auto iter = filesToCopy.begin(); iter != filesToCopy.end(); iter++) {
|
||||
auto &from = iter.key();
|
||||
auto &to = iter.value();
|
||||
FS::copy fileCopyOperation(from, to);
|
||||
if(!fileCopyOperation()) {
|
||||
qWarning() << "Failed to copy" << from << "to" << to;
|
||||
emitFailed(tr("Failed to copy files"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setStatus(tr("Installing modpack"));
|
||||
|
||||
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
explicit PackInstallTask(Modpack pack, QString version);
|
||||
virtual ~PackInstallTask(){}
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
@@ -30,6 +31,8 @@ private:
|
||||
void install();
|
||||
|
||||
private:
|
||||
bool abortable = false;
|
||||
|
||||
NetJobPtr jobPtr;
|
||||
QByteArray response;
|
||||
|
||||
@@ -37,6 +40,8 @@ private:
|
||||
QString m_version_name;
|
||||
Version m_version;
|
||||
|
||||
QMap<QString, QString> filesToCopy;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,14 @@ Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUr
|
||||
m_minecraftVersion = minecraftVersion;
|
||||
}
|
||||
|
||||
bool Technic::SingleZipPackInstallTask::abort() {
|
||||
if(m_abortable)
|
||||
{
|
||||
return m_filesNetJob->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Technic::SingleZipPackInstallTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
|
||||
@@ -47,6 +55,8 @@ void Technic::SingleZipPackInstallTask::executeTask()
|
||||
|
||||
void Technic::SingleZipPackInstallTask::downloadSucceeded()
|
||||
{
|
||||
m_abortable = false;
|
||||
|
||||
setStatus(tr("Extracting modpack"));
|
||||
QDir extractDir(FS::PathCombine(m_stagingPath, ".minecraft"));
|
||||
qDebug() << "Attempting to create instance from" << m_archivePath;
|
||||
@@ -67,12 +77,14 @@ void Technic::SingleZipPackInstallTask::downloadSucceeded()
|
||||
|
||||
void Technic::SingleZipPackInstallTask::downloadFailed(QString reason)
|
||||
{
|
||||
m_abortable = false;
|
||||
emitFailed(reason);
|
||||
m_filesNetJob.reset();
|
||||
}
|
||||
|
||||
void Technic::SingleZipPackInstallTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||
{
|
||||
m_abortable = true;
|
||||
setProgress(current / 2, total);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ class MULTIMC_LOGIC_EXPORT SingleZipPackInstallTask : public InstanceTask
|
||||
public:
|
||||
SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion);
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
void executeTask() override;
|
||||
|
||||
@@ -48,6 +51,8 @@ private slots:
|
||||
void extractAborted();
|
||||
|
||||
private:
|
||||
bool m_abortable = false;
|
||||
|
||||
QUrl m_sourceUrl;
|
||||
QString m_minecraftVersion;
|
||||
QString m_archivePath;
|
||||
|
||||
@@ -27,6 +27,14 @@ Technic::SolderPackInstallTask::SolderPackInstallTask(const QUrl &sourceUrl, con
|
||||
m_minecraftVersion = minecraftVersion;
|
||||
}
|
||||
|
||||
bool Technic::SolderPackInstallTask::abort() {
|
||||
if(m_abortable)
|
||||
{
|
||||
return m_filesNetJob->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Finding recommended version:\n%1").arg(m_sourceUrl.toString()));
|
||||
@@ -106,6 +114,8 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
|
||||
|
||||
void Technic::SolderPackInstallTask::downloadSucceeded()
|
||||
{
|
||||
m_abortable = false;
|
||||
|
||||
setStatus(tr("Extracting modpack"));
|
||||
m_filesNetJob.reset();
|
||||
m_extractFuture = QtConcurrent::run([this]()
|
||||
@@ -132,12 +142,14 @@ void Technic::SolderPackInstallTask::downloadSucceeded()
|
||||
|
||||
void Technic::SolderPackInstallTask::downloadFailed(QString reason)
|
||||
{
|
||||
m_abortable = false;
|
||||
emitFailed(reason);
|
||||
m_filesNetJob.reset();
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||
{
|
||||
m_abortable = true;
|
||||
setProgress(current / 2, total);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Technic
|
||||
public:
|
||||
explicit SolderPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion);
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
@@ -43,6 +46,8 @@ namespace Technic
|
||||
void extractAborted();
|
||||
|
||||
private:
|
||||
bool m_abortable = false;
|
||||
|
||||
NetJobPtr m_filesNetJob;
|
||||
QUrl m_sourceUrl;
|
||||
QString m_minecraftVersion;
|
||||
|
||||
Reference in New Issue
Block a user