// SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 Jamie Mansfield * * 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 "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 "Application.h" #include "MMCStrings.h" #include "pathmatcher/RegexpMatcher.h" #include "pathmatcher/MultiMatcher.h" #include "FileSystem.h" #include "java/JavaVersion.h" #include "MMCTime.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" #include "launch/steps/TextPrint.h" #include "launch/steps/CheckJava.h" #include "launch/steps/QuitAfterGameStop.h" #include "minecraft/launch/LauncherPartLaunch.h" #include "minecraft/launch/DirectJavaLaunch.h" #include "minecraft/launch/ModMinecraftJar.h" #include "minecraft/launch/ClaimAccount.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 "PackProfile.h" #include "AssetsUtils.h" #include "MinecraftUpdate.h" #include "MinecraftLoadAndCheck.h" #include "minecraft/gameoptions/GameOptions.h" #include "minecraft/update/FoldersTask.h" #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("JavaTimestamp"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), 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); // Minecraft launch method auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride); // Native library workarounds auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), 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); 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", ""); // Account override m_settings->registerSetting("OverrideAccount", false); m_settings->registerSetting("OverrideAccountProfileId", ""); 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(); } 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.fandom.com/wiki/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::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())); } 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"; return args; } QMap MinecraftInstance::getVariables() { QMap out; out.insert("INST_NAME", name()); out.insert("INST_ID", id()); out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); out.insert("INST_MC_DIR", QDir(gameRoot()).absolutePath()); out.insert("INST_JAVA", settings()->get("JavaPath").toString()); out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); 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) { auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so"; env.insert("LD_PRELOAD", preload); 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 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 = "max"; else windowParams = QString("%1x%2") .arg(settings()->get("MinecraftWinWidth").toInt()) .arg(settings()->get("MinecraftWinHeight").toInt()); launchScript += "windowTitle " + windowTitle() + "\n"; launchScript += "windowParams " + windowParams + "\n"; } // legacy auth if(session) { launchScript += "userName " + session->player_name + "\n"; launchScript += "sessionId " + session->session + "\n"; } // libraries and class path. { QStringList jars, nativeJars; profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot()); for(auto file: jars) { launchScript += "cp " + file + "\n"; } for(auto file: nativeJars) { launchScript += "ext " + file + "\n"; } launchScript += "natives " + getNativePath() + "\n"; } for (auto trait : profile->getTraits()) { launchScript += "traits " + trait + "\n"; } launchScript += "launcher onesix\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 << ""; 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 != "offline") { addToFilter(sessionRef.access_token, tr("")); } if(sessionRef.client_token.size()) { addToFilter(sessionRef.client_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) { description.append(tr(", last played for %1").arg(Time::prettifyDuration(lastTimePlayed()))); } if (totalTimePlayed() > 0) { description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed()))); } } 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(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher)); } // check java { process->appendStep(new CheckJava(pptr)); } // check launch method QStringList validMethods = {"LauncherPart", "DirectJava"}; QString method = launchMethod(); if(!validMethods.contains(method)) { process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); return process; } // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) { process->appendStep(new 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 = new 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 = new 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(new ClaimAccount(pptr, session)); } process->appendStep(new Update(pptr, Net::Mode::Online)); } else { process->appendStep(new Update(pptr, Net::Mode::Offline)); } // if there are any jar mods { process->appendStep(new ModMinecraftJar(pptr)); } // Scan mods folders for mods { process->appendStep(new ScanModFolders(pptr)); } // print some instance info here... { process->appendStep(new PrintInstanceInfo(pptr, session, serverToJoin)); } // extract native jars if needed { process->appendStep(new ExtractNatives(pptr)); } // reconstruct assets if needed { process->appendStep(new ReconstructAssets(pptr)); } // verify that minimum Java requirements are met { process->appendStep(new VerifyJavaInstall(pptr)); } { // actually launch the game auto method = launchMethod(); if(method == "LauncherPart") { auto step = new LauncherPartLaunch(pptr); step->setWorkingDirectory(gameRoot()); step->setAuthSession(session); step->setServerToJoin(serverToJoin); process->appendStep(step); } else if (method == "DirectJava") { auto step = new DirectJavaLaunch(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 = new PostLaunchCommand(pptr); step->setWorkingDirectory(gameRoot()); process->appendStep(step); } if (session) { process->setCensorFilter(createCensorFilterFromSession(session)); } if(m_settings->get("QuitAfterGameStop").toBool()) { auto step = new QuitAfterGameStop(pptr); process->appendStep(step); } m_launchProcess = process; emit launchTaskChanged(m_launchProcess); return m_launchProcess; } QString MinecraftInstance::launchMethod() { return settings()->get("MCLaunchMethod").toString(); } JavaVersion MinecraftInstance::getJavaVersion() { return JavaVersion(settings()->get("JavaVersion").toString()); } std::shared_ptr MinecraftInstance::loaderModList() const { if (!m_loader_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_loader_mod_list.reset(new ModFolderModel(modsRoot(), is_indexed)); m_loader_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); } return m_loader_mod_list; } std::shared_ptr MinecraftInstance::coreModList() const { if (!m_core_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_core_mod_list.reset(new ModFolderModel(coreModsDir(), is_indexed)); m_core_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); } return m_core_mod_list; } std::shared_ptr MinecraftInstance::resourcePackList() const { if (!m_resource_pack_list) { m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir())); m_resource_pack_list->enableInteraction(!isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ResourcePackFolderModel::disableInteraction); } return m_resource_pack_list; } std::shared_ptr MinecraftInstance::texturePackList() const { if (!m_texture_pack_list) { 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); } return m_texture_pack_list; } std::shared_ptr MinecraftInstance::shaderPackList() const { if (!m_shader_pack_list) { m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir())); m_shader_pack_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_shader_pack_list.get(), &ModFolderModel::disableInteraction); } return m_shader_pack_list; } std::shared_ptr MinecraftInstance::worldList() const { if (!m_world_list) { m_world_list.reset(new WorldList(worldDir())); } return m_world_list; } std::shared_ptr MinecraftInstance::gameOptionsModel() const { 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"