// SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 Jamie Mansfield * Copyright (C) 2023 TheKodeToad * * 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 . * * 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 #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 a, std::shared_ptr 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 m_a; std::shared_ptr 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("JavaOrLocationOverride", javaOverride, locationOverride); auto javaOrArgs = std::make_shared("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 MinecraftInstance::getPackProfile() const { return m_components; } QSet 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) { 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"; } bool MinecraftInstance::shouldApplyOnlineFixes() { return traits().contains("legacyServices") && settings()->get("OnlineFixes").toBool(); } QMap MinecraftInstance::getVariables() { QMap 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 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 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 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 MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session) { if (!session) { return QMap(); } auto& sessionRef = *session.get(); QMap filter; auto addToFilter = [&filter](QString key, QString value) { if (key.trimmed().size()) { filter[key] = value; } }; if (sessionRef.session != "-") { addToFilter(sessionRef.session, tr("")); } if (sessionRef.access_token != "0") { addToFilter(sessionRef.access_token, tr("")); } addToFilter(sessionRef.uuid, tr("")); return filter; } MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLevel::Enum level) { QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); 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(); combined->add(std::make_shared(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); combined->add(std::make_shared("crash-.*\\.txt")); combined->add(std::make_shared("IDMap dump.*\\.txt$")); combined->add(std::make_shared("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 MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) { updateRuntimeContext(); // FIXME: get rid of shared_from_this ... auto process = LaunchTask::create(std::dynamic_pointer_cast(shared_from_this())); auto pptr = process.get(); APPLICATION->icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG"); // print a header { process->appendStep(makeShared(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher)); } // check java { process->appendStep(makeShared(pptr)); } // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) { process->appendStep(makeShared(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(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(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(pptr, session)); } process->appendStep(makeShared(pptr, Net::Mode::Online)); } else { process->appendStep(makeShared(pptr, Net::Mode::Offline)); } // if there are any jar mods { process->appendStep(makeShared(pptr)); } // Scan mods folders for mods { process->appendStep(makeShared(pptr)); } // print some instance info here... { process->appendStep(makeShared(pptr, session, serverToJoin)); } // extract native jars if needed { process->appendStep(makeShared(pptr)); } // reconstruct assets if needed { process->appendStep(makeShared(pptr)); } // verify that minimum Java requirements are met { process->appendStep(makeShared(pptr)); } { // actually launch the game auto step = makeShared(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(pptr); step->setWorkingDirectory(gameRoot()); process->appendStep(step); } if (session) { process->setCensorFilter(createCensorFilterFromSession(session)); } if (m_settings->get("QuitAfterGameStop").toBool()) { process->appendStep(makeShared(pptr)); } m_launchProcess = process; emit launchTaskChanged(m_launchProcess); return m_launchProcess; } JavaVersion MinecraftInstance::getJavaVersion() { return JavaVersion(settings()->get("JavaVersion").toString()); } std::shared_ptr 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 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 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 MinecraftInstance::resourcePackList() { if (!m_resource_pack_list) { m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this)); } return m_resource_pack_list; } std::shared_ptr MinecraftInstance::texturePackList() { if (!m_texture_pack_list) { m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this)); } return m_texture_pack_list; } std::shared_ptr MinecraftInstance::shaderPackList() { if (!m_shader_pack_list) { m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this)); } return m_shader_pack_list; } std::shared_ptr MinecraftInstance::worldList() { if (!m_world_list) { m_world_list.reset(new WorldList(worldDir(), this)); } return m_world_list; } std::shared_ptr MinecraftInstance::gameOptionsModel() { if (!m_game_options) { m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt"))); } return m_game_options; } QList MinecraftInstance::getJarMods() const { auto profile = m_components->getProfile(); QList 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"