ShatteredPrism/launcher/minecraft/MinecraftInstance.cpp
Evan Goode 639a7820fe Fix offline multiplayer in 1.16.4-1.16.5
Resolves https://github.com/fn2006/PollyMC/issues/150

If the game is launched in offline mode, pass invalid API servers via
the -Dminecraft.api.* system properties. This workaround is mentioned
here:
https://github.com/FabricMC/fabric-loom/issues/915#issuecomment-1609154390

I think this change is appropriate to make here in PollyMC even though
the Fabric developers decided against using it there. If a user wants to
override the API servers back to the vanilla values (in order to use
Auth Me or something), they can do so by setting the following custom
JVM args on the instance:

-Dminecraft.api.env=PROD
-Dminecraft.api.auth.host=https://authserver.mojang.com
-Dminecraft.api.account.host=https://api.mojang.com
-Dminecraft.api.session.host=https://sessionserver.mojang.com
-Dminecraft.api.services.host=https://api.minecraftservices.com

Previously, auth args would override any user-specified args, but this
patch also changes that behavior so user-specified args are now passed
last.
2024-03-04 21:53:58 -05:00

1264 lines
44 KiB
C++

// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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 "MinecraftInstance.h"
#include "Application.h"
#include "BuildConfig.h"
#include "minecraft/launch/CreateGameFolders.h"
#include "minecraft/launch/ExtractNatives.h"
#include "minecraft/launch/PrintInstanceInfo.h"
#include "settings/Setting.h"
#include "settings/SettingsObject.h"
#include "FileSystem.h"
#include "MMCTime.h"
#include "java/JavaVersion.h"
#include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/RegexpMatcher.h"
#include "launch/LaunchTask.h"
#include "launch/steps/CheckJava.h"
#include "launch/steps/LookupServerAddress.h"
#include "launch/steps/PostLaunchCommand.h"
#include "launch/steps/PreLaunchCommand.h"
#include "launch/steps/QuitAfterGameStop.h"
#include "launch/steps/TextPrint.h"
#include "launch/steps/Update.h"
#include "minecraft/launch/ClaimAccount.h"
#include "minecraft/launch/LauncherPartLaunch.h"
#include "minecraft/launch/ModMinecraftJar.h"
#include "minecraft/launch/ReconstructAssets.h"
#include "minecraft/launch/ScanModFolders.h"
#include "minecraft/launch/VerifyJavaInstall.h"
#include "java/JavaUtils.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
#include "icons/IconList.h"
#include "mod/ModFolderModel.h"
#include "mod/ResourcePackFolderModel.h"
#include "mod/ShaderPackFolderModel.h"
#include "mod/TexturePackFolderModel.h"
#include "WorldList.h"
#include "AssetsUtils.h"
#include "MinecraftLoadAndCheck.h"
#include "MinecraftUpdate.h"
#include "PackProfile.h"
#include "minecraft/gameoptions/GameOptions.h"
#include "minecraft/update/FoldersTask.h"
#include "tools/BaseProfiler.h"
#include <QActionGroup>
#ifdef Q_OS_LINUX
#include "MangoHud.h"
#endif
#define IBUS "@im=ibus"
// all of this because keeping things compatible with deprecated old settings
// if either of the settings {a, b} is true, this also resolves to true
class OrSetting : public Setting {
Q_OBJECT
public:
OrSetting(QString id, std::shared_ptr<Setting> a, std::shared_ptr<Setting> b) : Setting({ id }, false), m_a(a), m_b(b) {}
virtual QVariant get() const
{
bool a = m_a->get().toBool();
bool b = m_b->get().toBool();
return a || b;
}
virtual void reset() {}
virtual void set(QVariant value) {}
private:
std::shared_ptr<Setting> m_a;
std::shared_ptr<Setting> m_b;
};
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
: BaseInstance(globalSettings, settings, rootDir)
{
m_components.reset(new PackProfile(this));
}
void MinecraftInstance::saveNow()
{
m_components->saveNow();
}
void MinecraftInstance::loadSpecificSettings()
{
if (isSpecificSettingsLoaded())
return;
// Java Settings
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false);
// combinations
auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
if (auto global_settings = globalSettings()) {
m_settings->registerOverride(global_settings->getSetting("JavaPath"), javaOrLocation);
m_settings->registerOverride(global_settings->getSetting("JvmArgs"), javaOrArgs);
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
// special!
m_settings->registerPassthrough(global_settings->getSetting("JavaSignature"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaVendor"), javaOrLocation);
// Window Size
auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
m_settings->registerOverride(global_settings->getSetting("LaunchMaximized"), windowSetting);
m_settings->registerOverride(global_settings->getSetting("MinecraftWinWidth"), windowSetting);
m_settings->registerOverride(global_settings->getSetting("MinecraftWinHeight"), windowSetting);
// Memory
auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting);
m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("CustomOpenALPath"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride);
// Peformance related options
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
m_settings->registerOverride(global_settings->getSetting("EnableFeralGamemode"), performanceOverride);
m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride);
m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride);
// Miscellaneous
auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
// Legacy-related options
auto legacySettings = m_settings->registerSetting("OverrideLegacySettings", false);
m_settings->registerOverride(global_settings->getSetting("OnlineFixes"), legacySettings);
auto envSetting = m_settings->registerSetting("OverrideEnv", false);
m_settings->registerOverride(global_settings->getSetting("Env"), envSetting);
m_settings->set("InstanceType", "OneSix");
}
// Join server on launch, this does not have a global override
m_settings->registerSetting("JoinServerOnLaunch", false);
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
// Use account for instance, this does not have a global override
m_settings->registerSetting("UseAccountForInstance", false);
m_settings->registerSetting("InstanceAccountId", "");
m_settings->registerSetting("ExportName", "");
m_settings->registerSetting("ExportVersion", "1.0.0");
m_settings->registerSetting("ExportSummary", "");
m_settings->registerSetting("ExportAuthor", "");
m_settings->registerSetting("ExportOptionalFiles", true);
qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true);
updateRuntimeContext();
}
void MinecraftInstance::updateRuntimeContext()
{
m_runtimeContext.updateFromInstanceSettings(m_settings);
}
QString MinecraftInstance::typeName() const
{
return "Minecraft";
}
std::shared_ptr<PackProfile> MinecraftInstance::getPackProfile() const
{
return m_components;
}
QSet<QString> MinecraftInstance::traits() const
{
auto components = getPackProfile();
if (!components) {
return { "version-incomplete" };
}
auto profile = components->getProfile();
if (!profile) {
return { "version-incomplete" };
}
return profile->getTraits();
}
// FIXME: move UI code out of MinecraftInstance
void MinecraftInstance::populateLaunchMenu(QMenu* menu)
{
QAction* normalLaunch = menu->addAction(tr("&Launch"));
normalLaunch->setShortcut(QKeySequence::Open);
QAction* normalLaunchOffline = menu->addAction(tr("Launch &Offline"));
normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
QAction* normalLaunchDemo = menu->addAction(tr("Launch &Demo"));
normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
normalLaunchDemo->setEnabled(supportsDemo());
connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this()); });
connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, false); });
connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, true); });
QString profilersTitle = tr("Profilers");
menu->addSeparator()->setText(profilersTitle);
auto profilers = new QActionGroup(menu);
profilers->setExclusive(true);
connect(profilers, &QActionGroup::triggered, [this](QAction* action) {
settings()->set("Profiler", action->data());
emit profilerChanged();
});
QAction* noProfilerAction = menu->addAction(tr("&No Profiler"));
noProfilerAction->setData("");
noProfilerAction->setCheckable(true);
noProfilerAction->setChecked(true);
profilers->addAction(noProfilerAction);
for (auto profiler = APPLICATION->profilers().begin(); profiler != APPLICATION->profilers().end(); profiler++) {
QAction* profilerAction = menu->addAction(profiler.value()->name());
profilers->addAction(profilerAction);
profilerAction->setData(profiler.key());
profilerAction->setCheckable(true);
profilerAction->setChecked(settings()->get("Profiler").toString() == profiler.key());
QString error;
profilerAction->setEnabled(profiler.value()->check(&error));
}
}
QString MinecraftInstance::gameRoot() const
{
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
if (mcDir.exists() && !dotMCDir.exists())
return mcDir.filePath();
else
return dotMCDir.filePath();
}
QString MinecraftInstance::binRoot() const
{
return FS::PathCombine(gameRoot(), "bin");
}
QString MinecraftInstance::getNativePath() const
{
QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/"));
return natives_dir.absolutePath();
}
QString MinecraftInstance::getLocalLibraryPath() const
{
QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/"));
return libraries_dir.absolutePath();
}
bool MinecraftInstance::supportsDemo() const
{
Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") };
// Demo mode was introduced in 1.3.1: https://minecraft.wiki/w/Demo_mode#History
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
return instance_ver >= Version("1.3.1");
}
QString MinecraftInstance::jarModsDir() const
{
QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/"));
return jarmods_dir.absolutePath();
}
QString MinecraftInstance::modsRoot() const
{
return FS::PathCombine(gameRoot(), "mods");
}
QString MinecraftInstance::modsCacheLocation() const
{
return FS::PathCombine(instanceRoot(), "mods.cache");
}
QString MinecraftInstance::coreModsDir() const
{
return FS::PathCombine(gameRoot(), "coremods");
}
QString MinecraftInstance::nilModsDir() const
{
return FS::PathCombine(gameRoot(), "nilmods");
}
QString MinecraftInstance::resourcePacksDir() const
{
return FS::PathCombine(gameRoot(), "resourcepacks");
}
QString MinecraftInstance::texturePacksDir() const
{
return FS::PathCombine(gameRoot(), "texturepacks");
}
QString MinecraftInstance::shaderPacksDir() const
{
return FS::PathCombine(gameRoot(), "shaderpacks");
}
QString MinecraftInstance::instanceConfigFolder() const
{
return FS::PathCombine(gameRoot(), "config");
}
QString MinecraftInstance::libDir() const
{
return FS::PathCombine(gameRoot(), "lib");
}
QString MinecraftInstance::worldDir() const
{
return FS::PathCombine(gameRoot(), "saves");
}
QString MinecraftInstance::resourcesDir() const
{
return FS::PathCombine(gameRoot(), "resources");
}
QDir MinecraftInstance::librariesPath() const
{
return QDir::current().absoluteFilePath("libraries");
}
QDir MinecraftInstance::jarmodsPath() const
{
return QDir(jarModsDir());
}
QDir MinecraftInstance::versionsPath() const
{
return QDir::current().absoluteFilePath("versions");
}
QStringList MinecraftInstance::getClassPath()
{
QStringList jars, nativeJars;
auto profile = m_components->getProfile();
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
return jars;
}
QString MinecraftInstance::getMainClass() const
{
auto profile = m_components->getProfile();
return profile->getMainClass();
}
QStringList MinecraftInstance::getNativeJars()
{
QStringList jars, nativeJars;
auto profile = m_components->getProfile();
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
return nativeJars;
}
QStringList MinecraftInstance::extraArguments()
{
auto list = BaseInstance::extraArguments();
auto version = getPackProfile();
if (!version)
return list;
auto jarMods = getJarMods();
if (!jarMods.isEmpty()) {
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) {
if (MANAGED_AGENTS.find(agent->library()->artifactPrefix().toStdString()) != MANAGED_AGENTS.end()) {
continue;
}
QStringList jar, temp1, temp2, temp3;
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
list.append("-javaagent:" + jar[0] + (agent->argument().isEmpty() ? "" : "=" + agent->argument()));
}
{
QString openALPath;
QString glfwPath;
if (settings()->get("UseNativeOpenAL").toBool()) {
openALPath = APPLICATION->m_detectedOpenALPath;
auto customPath = settings()->get("CustomOpenALPath").toString();
if (!customPath.isEmpty())
openALPath = customPath;
}
if (settings()->get("UseNativeGLFW").toBool()) {
glfwPath = APPLICATION->m_detectedGLFWPath;
auto customPath = settings()->get("CustomGLFWPath").toString();
if (!customPath.isEmpty())
glfwPath = customPath;
}
QFileInfo openALInfo(openALPath);
QFileInfo glfwInfo(glfwPath);
if (!openALPath.isEmpty() && openALInfo.exists())
list.append("-Dorg.lwjgl.openal.libname=" + openALInfo.absoluteFilePath());
if (!glfwPath.isEmpty() && glfwInfo.exists())
list.append("-Dorg.lwjgl.glfw.libname=" + glfwInfo.absoluteFilePath());
}
return list;
}
QStringList MinecraftInstance::javaArguments()
{
QStringList args;
// custom args go first. we want to override them if we have our own here.
args.append(extraArguments());
// OSX dock icon and name
#ifdef Q_OS_MAC
args << "-Xdock:icon=icon.png";
args << QString("-Xdock:name=\"%1\"").arg(windowTitle());
#endif
auto traits_ = traits();
// HACK: fix issues on macOS with 1.13 snapshots
// NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them
#ifdef Q_OS_MAC
if (traits_.contains("FirstThreadOnMacOS")) {
args << QString("-XstartOnFirstThread");
}
#endif
// HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767
#ifdef Q_OS_WIN32
args << QString(
"-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
"minecraft.exe.heapdump");
#endif
int min = settings()->get("MinMemAlloc").toInt();
int max = settings()->get("MaxMemAlloc").toInt();
if (min < max) {
args << QString("-Xms%1m").arg(min);
args << QString("-Xmx%1m").arg(max);
} else {
args << QString("-Xms%1m").arg(max);
args << QString("-Xmx%1m").arg(min);
}
// No PermGen in newer java.
JavaVersion javaVersion = getJavaVersion();
if (javaVersion.requiresPermGen()) {
auto permgen = settings()->get("PermGen").toInt();
if (permgen != 64) {
args << QString("-XX:PermSize=%1m").arg(permgen);
}
}
args << "-Duser.language=en";
if (javaVersion.isModular() && shouldApplyOnlineFixes())
// allow reflective access to java.net - required by the skin fix
args << "--add-opens"
<< "java.base/java.net=ALL-UNNAMED";
return args;
}
QString MinecraftInstance::getLauncher()
{
// use legacy launcher if the traits are set
if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
return "legacy";
return "standard";
}
QStringList MinecraftInstance::processAuthArgs(AuthSessionPtr session) const
{
QStringList args;
QString v = m_components->getProfile()->getMinecraftVersion();
if (session->uses_custom_api_servers) {
args << "-Dminecraft.api.env=custom";
args << "-Dminecraft.api.auth.host=" + session->auth_server_url;
args << "-Dminecraft.api.account.host=" + session->account_server_url;
args << "-Dminecraft.api.session.host=" + session->session_server_url;
args << "-Dminecraft.api.services.host=" + session->services_server_url;
auto agents = m_components->getProfile()->getAgents();
for (auto agent : agents) {
if (agent->library()->artifactPrefix() == "moe.yushi:authlibinjector") {
QStringList jar, temp1, temp2, temp3;
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
QString argument{ agent->argument() };
if (argument.isEmpty()) {
argument = session->authlib_injector_url;
}
args << "-javaagent:" + jar[0] + (argument.isEmpty() ? "" : "=" + argument);
if (session->authlib_injector_metadata != "") {
args << "-Dauthlibinjector.yggdrasil.prefetched=" + session->authlib_injector_metadata;
}
break;
}
}
} else if (session->wants_online && (v == "1.16.4" || v == "1.16.5")) {
// https://github.com/FabricMC/fabric-loom/issues/915#issuecomment-1609154390
QString invalid_url{ "https://invalid.invalid" };
args << "-Dminecraft.api.env=custom";
args << "-Dminecraft.api.auth.host=" + invalid_url;
args << "-Dminecraft.api.account.host=" + invalid_url;
args << "-Dminecraft.api.session.host=" + invalid_url;
args << "-Dminecraft.api.services.host=" + invalid_url;
}
return args;
}
bool MinecraftInstance::shouldApplyOnlineFixes()
{
return traits().contains("legacyServices") && settings()->get("OnlineFixes").toBool();
}
QMap<QString, QString> MinecraftInstance::getVariables()
{
QMap<QString, QString> out;
out.insert("INST_NAME", name());
out.insert("INST_ID", id());
out.insert("INST_DIR", QDir::toNativeSeparators(QDir(instanceRoot()).absolutePath()));
out.insert("INST_MC_DIR", QDir::toNativeSeparators(QDir(gameRoot()).absolutePath()));
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
out.insert("NO_COLOR", "1");
return out;
}
QProcessEnvironment MinecraftInstance::createEnvironment()
{
// prepare the process environment
QProcessEnvironment env = CleanEnviroment();
// export some infos
auto variables = getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it) {
env.insert(it.key(), it.value());
}
return env;
}
QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
{
// prepare the process environment
QProcessEnvironment env = createEnvironment();
#ifdef Q_OS_LINUX
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud) {
QStringList preloadList;
if (auto value = env.value("LD_PRELOAD"); !value.isEmpty())
preloadList = value.split(QLatin1String(":"));
QStringList libPaths;
if (auto value = env.value("LD_LIBRARY_PATH"); !value.isEmpty())
libPaths = value.split(QLatin1String(":"));
auto mangoHudLibString = MangoHud::getLibraryString();
if (!mangoHudLibString.isEmpty()) {
QFileInfo mangoHudLib(mangoHudLibString);
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
preloadList << "libMangoHud_dlsym.so"
<< "libMangoHud_opengl.so" << mangoHudLib.fileName();
libPaths << mangoHudLib.absolutePath();
}
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
env.insert("LD_LIBRARY_PATH", libPaths.join(QLatin1String(":")));
env.insert("MANGOHUD", "1");
}
if (settings()->get("UseDiscreteGpu").toBool()) {
// Open Source Drivers
env.insert("DRI_PRIME", "1");
// Proprietary Nvidia Drivers
env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
}
#endif
// custom env
auto insertEnv = [&env](QMap<QString, QVariant> envMap) {
if (envMap.isEmpty())
return;
for (auto iter = envMap.begin(); iter != envMap.end(); iter++)
env.insert(iter.key(), iter.value().toString());
};
bool overrideEnv = settings()->get("OverrideEnv").toBool();
if (!overrideEnv)
insertEnv(APPLICATION->settings()->get("Env").toMap());
else
insertEnv(settings()->get("Env").toMap());
return env;
}
static QString replaceTokensIn(QString text, QMap<QString, QString> with)
{
// TODO: does this still work??
QString result;
QRegularExpression token_regexp("\\$\\{(.+)\\}", QRegularExpression::InvertedGreedinessOption);
QStringList list;
QRegularExpressionMatchIterator i = token_regexp.globalMatch(text);
int lastCapturedEnd = 0;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
result.append(text.mid(lastCapturedEnd, match.capturedStart()));
QString key = match.captured(1);
auto iter = with.find(key);
if (iter != with.end()) {
result.append(*iter);
}
lastCapturedEnd = match.capturedEnd();
}
result.append(text.mid(lastCapturedEnd));
return result;
}
QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const
{
auto profile = m_components->getProfile();
QString args_pattern = profile->getMinecraftArguments();
for (auto tweaker : profile->getTweakers()) {
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) {
// token_mapping["auth_username"] = session->username;
token_mapping["auth_session"] = session->session;
token_mapping["auth_access_token"] = session->access_token;
token_mapping["auth_player_name"] = session->player_name;
token_mapping["auth_uuid"] = session->uuid;
token_mapping["user_properties"] = session->serializeUserProperties();
token_mapping["user_type"] = session->user_type;
if (session->demo) {
args_pattern += " --demo";
}
}
token_mapping["profile_name"] = name();
token_mapping["version_name"] = profile->getMinecraftVersion();
token_mapping["version_type"] = profile->getMinecraftVersionType();
QString absRootDir = QDir(gameRoot()).absolutePath();
token_mapping["game_directory"] = absRootDir;
QString absAssetsDir = QDir("assets/").absolutePath();
auto assets = profile->getMinecraftAssets();
token_mapping["game_assets"] = AssetsUtils::getAssetsDir(assets->id, resourcesDir()).absolutePath();
// 1.7.3+ assets tokens
token_mapping["assets_root"] = absAssetsDir;
token_mapping["assets_index_name"] = assets->id;
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList parts = args_pattern.split(' ', Qt::SkipEmptyParts);
#else
QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
#endif
for (int i = 0; i < parts.length(); i++) {
parts[i] = replaceTokensIn(parts[i], token_mapping);
}
return parts;
}
QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{
QString launchScript;
if (!m_components)
return QString();
auto profile = m_components->getProfile();
if (!profile)
return QString();
auto mainClass = getMainClass();
if (!mainClass.isEmpty()) {
launchScript += "mainClass " + mainClass + "\n";
}
auto appletClass = profile->getAppletClass();
if (!appletClass.isEmpty()) {
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, nullptr /* When using a launch script, the server parameters are handled by it*/
)) {
launchScript += "param " + param + "\n";
}
// window size, title and state, legacy
{
QString windowParams;
if (settings()->get("LaunchMaximized").toBool())
windowParams = "maximized";
else
windowParams =
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
launchScript += "windowTitle " + windowTitle() + "\n";
launchScript += "windowParams " + windowParams + "\n";
}
// launcher info
{
launchScript += "launcherBrand " + BuildConfig.LAUNCHER_NAME + "\n";
launchScript += "launcherVersion " + BuildConfig.printableVersionString() + "\n";
}
// instance info
{
launchScript += "instanceName " + name() + "\n";
launchScript += "instanceIconKey " + name() + "\n";
launchScript += "instanceIconPath icon.png\n"; // we already save a copy here
}
// legacy auth
if (session) {
launchScript += "userName " + session->player_name + "\n";
launchScript += "sessionId " + session->session + "\n";
}
for (auto trait : profile->getTraits()) {
launchScript += "traits " + trait + "\n";
}
if (shouldApplyOnlineFixes())
launchScript += "onlineFixes true\n";
launchScript += "launcher " + getLauncher() + "\n";
// qDebug() << "Generated launch script:" << launchScript;
return launchScript;
}
QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{
QStringList out;
out << "Main Class:"
<< " " + getMainClass() << "";
out << "Native path:"
<< " " + getNativePath() << "";
auto profile = m_components->getProfile();
auto alltraits = traits();
if (alltraits.size()) {
out << "Traits:";
for (auto trait : alltraits) {
out << "traits " + trait;
}
out << "";
}
auto settings = this->settings();
bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
if (nativeOpenAL || nativeGLFW) {
if (nativeOpenAL)
out << "Using system OpenAL.";
if (nativeGLFW)
out << "Using system GLFW.";
out << "";
}
// libraries and class path.
{
out << "Libraries:";
QStringList jars, nativeJars;
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
auto printLibFile = [&](const QString& path) {
QFileInfo info(path);
if (info.exists()) {
out << " " + path;
} else {
out << " " + path + " (missing)";
}
};
for (auto file : jars) {
printLibFile(file);
}
out << "";
out << "Native libraries:";
for (auto file : nativeJars) {
printLibFile(file);
}
out << "";
}
auto printModList = [&](const QString& label, ModFolderModel& model) {
if (model.size()) {
out << QString("%1:").arg(label);
auto modList = model.allMods();
std::sort(modList.begin(), modList.end(), [](auto a, auto b) {
auto aName = a->fileinfo().completeBaseName();
auto bName = b->fileinfo().completeBaseName();
return aName.localeAwareCompare(bName) < 0;
});
for (auto mod : modList) {
if (mod->type() == ResourceType::FOLDER) {
out << u8" [🖿] " + mod->fileinfo().completeBaseName() + " (folder)";
continue;
}
if (mod->enabled()) {
out << u8" [✔] " + mod->fileinfo().completeBaseName();
} else {
out << u8" [✘] " + mod->fileinfo().completeBaseName() + " (disabled)";
}
}
out << "";
}
};
printModList("Mods", *(loaderModList().get()));
printModList("Core Mods", *(coreModList().get()));
auto& jarMods = profile->getJarMods();
if (jarMods.size()) {
out << "Jar Mods:";
for (auto& jarmod : jarMods) {
auto displayname = jarmod->displayName(runtimeContext());
auto realname = jarmod->filename(runtimeContext());
if (displayname != realname) {
out << " " + displayname + " (" + realname + ")";
} else {
out << " " + realname;
}
}
out << "";
}
auto params = processMinecraftArgs(nullptr, serverToJoin);
out << "Params:";
out << " " + params.join(' ');
out << "";
QString windowParams;
if (settings->get("LaunchMaximized").toBool()) {
out << "Window size: max (if available)";
} else {
auto width = settings->get("MinecraftWinWidth").toInt();
auto height = settings->get("MinecraftWinHeight").toInt();
out << "Window size: " + QString::number(width) + " x " + QString::number(height);
}
out << "";
out << "Launcher: " + getLauncher();
out << "";
return out;
}
QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session)
{
if (!session) {
return QMap<QString, QString>();
}
auto& sessionRef = *session.get();
QMap<QString, QString> filter;
auto addToFilter = [&filter](QString key, QString value) {
if (key.trimmed().size()) {
filter[key] = value;
}
};
if (sessionRef.session != "-") {
addToFilter(sessionRef.session, tr("<SESSION ID>"));
}
if (sessionRef.access_token != "0") {
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
}
addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
return filter;
}
MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLevel::Enum level)
{
QRegularExpression re("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]");
auto match = re.match(line);
if (match.hasMatch()) {
// New style logs from log4j
QString timestamp = match.captured("timestamp");
QString levelStr = match.captured("level");
if (levelStr == "INFO")
level = MessageLevel::Message;
if (levelStr == "WARN")
level = MessageLevel::Warning;
if (levelStr == "ERROR")
level = MessageLevel::Error;
if (levelStr == "FATAL")
level = MessageLevel::Fatal;
if (levelStr == "TRACE" || levelStr == "DEBUG")
level = MessageLevel::Debug;
} else {
// Old style forge logs
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") ||
line.contains("[FINEST]"))
level = MessageLevel::Message;
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
level = MessageLevel::Error;
if (line.contains("[WARNING]"))
level = MessageLevel::Warning;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
}
if (line.contains("overwriting existing"))
return MessageLevel::Fatal;
// NOTE: this diverges from the real regexp. no unicode, the first section is + instead of *
static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*";
if (line.contains("Exception in thread") || line.contains(QRegularExpression("\\s+at " + javaSymbol)) ||
line.contains(QRegularExpression("Caused by: " + javaSymbol)) ||
line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) ||
line.contains(QRegularExpression("... \\d+ more$")))
return MessageLevel::Error;
return level;
}
IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher()
{
auto combined = std::make_shared<MultiMatcher>();
combined->add(std::make_shared<RegexpMatcher>(".*\\.log(\\.[0-9]*)?(\\.gz)?$"));
combined->add(std::make_shared<RegexpMatcher>("crash-.*\\.txt"));
combined->add(std::make_shared<RegexpMatcher>("IDMap dump.*\\.txt$"));
combined->add(std::make_shared<RegexpMatcher>("ModLoader\\.txt(\\..*)?$"));
return combined;
}
QString MinecraftInstance::getLogFileRoot()
{
return gameRoot();
}
QString MinecraftInstance::getStatusbarDescription()
{
QStringList traits;
if (hasVersionBroken()) {
traits.append(tr("broken"));
}
QString mcVersion = m_components->getComponentVersion("net.minecraft");
if (mcVersion.isEmpty()) {
// Load component info if needed
m_components->reload(Net::Mode::Offline);
mcVersion = m_components->getComponentVersion("net.minecraft");
}
QString description;
description.append(tr("Minecraft %1").arg(mcVersion));
if (m_settings->get("ShowGameTime").toBool()) {
if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
description.append(
tr(", last played on %1 for %2")
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
.arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
if (totalTimePlayed() > 0) {
description.append(
tr(", total played for %1")
.arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
}
if (hasCrashed()) {
description.append(tr(", has crashed."));
}
return description;
}
Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
{
updateRuntimeContext();
switch (mode) {
case Net::Mode::Offline: {
return Task::Ptr(new MinecraftLoadAndCheck(this));
}
case Net::Mode::Online: {
return Task::Ptr(new MinecraftUpdate(this));
}
}
return nullptr;
}
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{
updateRuntimeContext();
// FIXME: get rid of shared_from_this ...
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
auto pptr = process.get();
APPLICATION->icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG");
// print a header
{
process->appendStep(makeShared<TextPrint>(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher));
}
// check java
{
process->appendStep(makeShared<CheckJava>(pptr));
}
// create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732)
{
process->appendStep(makeShared<CreateGameFolders>(pptr));
}
if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool()) {
QString fullAddress = 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 = makeShared<LookupServerAddress>(pptr);
step->setLookupAddress(serverToJoin->address);
step->setOutputAddressPtr(serverToJoin);
process->appendStep(step);
}
// run pre-launch command if that's needed
if (getPreLaunchCommand().size()) {
auto step = makeShared<PreLaunchCommand>(pptr);
step->setWorkingDirectory(gameRoot());
process->appendStep(step);
}
// if we aren't in offline mode,.
if (session->status != AuthSession::PlayableOffline) {
if (!session->demo) {
process->appendStep(makeShared<ClaimAccount>(pptr, session));
}
process->appendStep(makeShared<Update>(pptr, Net::Mode::Online));
} else {
process->appendStep(makeShared<Update>(pptr, Net::Mode::Offline));
}
// if there are any jar mods
{
process->appendStep(makeShared<ModMinecraftJar>(pptr));
}
// Scan mods folders for mods
{
process->appendStep(makeShared<ScanModFolders>(pptr));
}
// print some instance info here...
{
process->appendStep(makeShared<PrintInstanceInfo>(pptr, session, serverToJoin));
}
// extract native jars if needed
{
process->appendStep(makeShared<ExtractNatives>(pptr));
}
// reconstruct assets if needed
{
process->appendStep(makeShared<ReconstructAssets>(pptr));
}
// verify that minimum Java requirements are met
{
process->appendStep(makeShared<VerifyJavaInstall>(pptr));
}
{
// actually launch the game
auto step = makeShared<LauncherPartLaunch>(pptr);
step->setWorkingDirectory(gameRoot());
step->setAuthSession(session);
step->setServerToJoin(serverToJoin);
process->appendStep(step);
}
// run post-exit command if that's needed
if (getPostExitCommand().size()) {
auto step = makeShared<PostLaunchCommand>(pptr);
step->setWorkingDirectory(gameRoot());
process->appendStep(step);
}
if (session) {
process->setCensorFilter(createCensorFilterFromSession(session));
}
if (m_settings->get("QuitAfterGameStop").toBool()) {
process->appendStep(makeShared<QuitAfterGameStop>(pptr));
}
m_launchProcess = process;
emit launchTaskChanged(m_launchProcess);
return m_launchProcess;
}
JavaVersion MinecraftInstance::getJavaVersion()
{
return JavaVersion(settings()->get("JavaVersion").toString());
}
std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList()
{
if (!m_loader_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed));
}
return m_loader_mod_list;
}
std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList()
{
if (!m_core_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed));
}
return m_core_mod_list;
}
std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
{
if (!m_nil_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), this, is_indexed, false));
}
return m_nil_mod_list;
}
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
{
if (!m_resource_pack_list) {
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this));
}
return m_resource_pack_list;
}
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
{
if (!m_texture_pack_list) {
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this));
}
return m_texture_pack_list;
}
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
{
if (!m_shader_pack_list) {
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this));
}
return m_shader_pack_list;
}
std::shared_ptr<WorldList> MinecraftInstance::worldList()
{
if (!m_world_list) {
m_world_list.reset(new WorldList(worldDir(), this));
}
return m_world_list;
}
std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel()
{
if (!m_game_options) {
m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt")));
}
return m_game_options;
}
QList<Mod*> MinecraftInstance::getJarMods() const
{
auto profile = m_components->getProfile();
QList<Mod*> mods;
for (auto jarmod : profile->getJarMods()) {
QStringList jar, temp1, temp2, temp3;
jarmod->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
// QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem));
mods.push_back(new Mod(QFileInfo(jar[0])));
}
return mods;
}
#include "MinecraftInstance.moc"