1 #include "MinecraftInstance.h"
2 #include <minecraft/launch/CreateServerResourcePacksFolder.h>
3 #include <minecraft/launch/ExtractNatives.h>
4 #include <minecraft/launch/PrintInstanceInfo.h>
5 #include <settings/Setting.h>
6 #include "settings/SettingsObject.h"
7 #include "Env.h"
8 #include <MMCStrings.h>
9 #include <pathmatcher/RegexpMatcher.h>
10 #include <pathmatcher/MultiMatcher.h>
11 #include <FileSystem.h>
12 #include <java/JavaVersion.h>
13
14 #include "launch/LaunchTask.h"
15 #include "launch/steps/PostLaunchCommand.h"
16 #include "launch/steps/Update.h"
17 #include "launch/steps/PreLaunchCommand.h"
18 #include "launch/steps/TextPrint.h"
19 #include "minecraft/launch/LauncherPartLaunch.h"
20 #include "minecraft/launch/DirectJavaLaunch.h"
21 #include "minecraft/launch/ModMinecraftJar.h"
22 #include "minecraft/launch/ClaimAccount.h"
23 #include "minecraft/launch/ReconstructAssets.h"
24 #include "minecraft/launch/ScanModFolders.h"
25 #include "java/launch/CheckJava.h"
26 #include "java/JavaUtils.h"
27 #include "meta/Index.h"
28 #include "meta/VersionList.h"
29
30 #include "mod/ModFolderModel.h"
31 #include "WorldList.h"
32
33 #include "icons/IIconList.h"
34
35 #include <QCoreApplication>
36 #include "ComponentList.h"
37 #include "AssetsUtils.h"
38 #include "MinecraftUpdate.h"
39 #include "MinecraftLoadAndCheck.h"
40 #include <minecraft/gameoptions/GameOptions.h>
41
42 #define IBUS "@im=ibus"
43
44 // all of this because keeping things compatible with deprecated old settings
45 // if either of the settings {a, b} is true, this also resolves to true
46 class OrSetting : public Setting
47 {
48 Q_OBJECT
49 public:
OrSetting(QString id,std::shared_ptr<Setting> a,std::shared_ptr<Setting> b)50 OrSetting(QString id, std::shared_ptr<Setting> a, std::shared_ptr<Setting> b)
51 :Setting({id}, false), m_a(a), m_b(b)
52 {
53 }
get() const54 virtual QVariant get() const
55 {
56 bool a = m_a->get().toBool();
57 bool b = m_b->get().toBool();
58 return a || b;
59 }
reset()60 virtual void reset() {}
set(QVariant value)61 virtual void set(QVariant value) {}
62 private:
63 std::shared_ptr<Setting> m_a;
64 std::shared_ptr<Setting> m_b;
65 };
66
MinecraftInstance(SettingsObjectPtr globalSettings,SettingsObjectPtr settings,const QString & rootDir)67 MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
68 : BaseInstance(globalSettings, settings, rootDir)
69 {
70 // Java Settings
71 auto javaOverride = m_settings->registerSetting("OverrideJava", false);
72 auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
73 auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false);
74
75 // combinations
76 auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
77 auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
78
79 m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation);
80 m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs);
81
82 // special!
83 m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation);
84 m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation);
85 m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation);
86
87 // Window Size
88 auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
89 m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting);
90 m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting);
91 m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting);
92
93 // Memory
94 auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
95 m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting);
96 m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting);
97 m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting);
98
99 // Minecraft launch method
100 auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
101 m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
102
103 // DEPRECATED: Read what versions the user configuration thinks should be used
104 m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
105 m_settings->registerSetting("LWJGLVersion", "");
106 m_settings->registerSetting("ForgeVersion", "");
107 m_settings->registerSetting("LiteloaderVersion", "");
108
109 m_components.reset(new ComponentList(this));
110 m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString());
111 auto setting = m_settings->getSetting("LWJGLVersion");
112 m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString());
113 m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString());
114 m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString());
115 }
116
saveNow()117 void MinecraftInstance::saveNow()
118 {
119 m_components->saveNow();
120 }
121
typeName() const122 QString MinecraftInstance::typeName() const
123 {
124 return "Minecraft";
125 }
126
getComponentList() const127 std::shared_ptr<ComponentList> MinecraftInstance::getComponentList() const
128 {
129 return m_components;
130 }
131
traits() const132 QSet<QString> MinecraftInstance::traits() const
133 {
134 auto components = getComponentList();
135 if (!components)
136 {
137 return {"version-incomplete"};
138 }
139 auto profile = components->getProfile();
140 if (!profile)
141 {
142 return {"version-incomplete"};
143 }
144 return profile->getTraits();
145 }
146
gameRoot() const147 QString MinecraftInstance::gameRoot() const
148 {
149 QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
150 QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
151
152 if (mcDir.exists() && !dotMCDir.exists())
153 return mcDir.filePath();
154 else
155 return dotMCDir.filePath();
156 }
157
binRoot() const158 QString MinecraftInstance::binRoot() const
159 {
160 return FS::PathCombine(gameRoot(), "bin");
161 }
162
getNativePath() const163 QString MinecraftInstance::getNativePath() const
164 {
165 #if defined(Q_OS_FREEBSD)
166 QDir natives_dir(LWJGL_DIR "/");
167 #else
168 QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/"));
169 #endif
170 return natives_dir.absolutePath();
171 }
172
getLocalLibraryPath() const173 QString MinecraftInstance::getLocalLibraryPath() const
174 {
175 QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/"));
176 return libraries_dir.absolutePath();
177 }
178
jarModsDir() const179 QString MinecraftInstance::jarModsDir() const
180 {
181 QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/"));
182 return jarmods_dir.absolutePath();
183 }
184
loaderModsDir() const185 QString MinecraftInstance::loaderModsDir() const
186 {
187 return FS::PathCombine(gameRoot(), "mods");
188 }
189
modsCacheLocation() const190 QString MinecraftInstance::modsCacheLocation() const
191 {
192 return FS::PathCombine(instanceRoot(), "mods.cache");
193 }
194
coreModsDir() const195 QString MinecraftInstance::coreModsDir() const
196 {
197 return FS::PathCombine(gameRoot(), "coremods");
198 }
199
resourcePacksDir() const200 QString MinecraftInstance::resourcePacksDir() const
201 {
202 return FS::PathCombine(gameRoot(), "resourcepacks");
203 }
204
texturePacksDir() const205 QString MinecraftInstance::texturePacksDir() const
206 {
207 return FS::PathCombine(gameRoot(), "texturepacks");
208 }
209
instanceConfigFolder() const210 QString MinecraftInstance::instanceConfigFolder() const
211 {
212 return FS::PathCombine(gameRoot(), "config");
213 }
214
libDir() const215 QString MinecraftInstance::libDir() const
216 {
217 return FS::PathCombine(gameRoot(), "lib");
218 }
219
worldDir() const220 QString MinecraftInstance::worldDir() const
221 {
222 return FS::PathCombine(gameRoot(), "saves");
223 }
224
resourcesDir() const225 QString MinecraftInstance::resourcesDir() const
226 {
227 return FS::PathCombine(gameRoot(), "resources");
228 }
229
librariesPath() const230 QDir MinecraftInstance::librariesPath() const
231 {
232 return QDir::current().absoluteFilePath("libraries");
233 }
234
jarmodsPath() const235 QDir MinecraftInstance::jarmodsPath() const
236 {
237 return QDir(jarModsDir());
238 }
239
versionsPath() const240 QDir MinecraftInstance::versionsPath() const
241 {
242 return QDir::current().absoluteFilePath("versions");
243 }
244
getClassPath() const245 QStringList MinecraftInstance::getClassPath() const
246 {
247 QStringList jars, nativeJars;
248 auto javaArchitecture = settings()->get("JavaArchitecture").toString();
249 auto profile = m_components->getProfile();
250 profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
251 return jars;
252 }
253
getMainClass() const254 QString MinecraftInstance::getMainClass() const
255 {
256 auto profile = m_components->getProfile();
257 return profile->getMainClass();
258 }
259
getNativeJars() const260 QStringList MinecraftInstance::getNativeJars() const
261 {
262 QStringList jars, nativeJars;
263 auto javaArchitecture = settings()->get("JavaArchitecture").toString();
264 auto profile = m_components->getProfile();
265 profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
266 return nativeJars;
267 }
268
extraArguments() const269 QStringList MinecraftInstance::extraArguments() const
270 {
271 auto list = BaseInstance::extraArguments();
272 auto version = getComponentList();
273 if (!version)
274 return list;
275 auto jarMods = getJarMods();
276 if (!jarMods.isEmpty())
277 {
278 list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true",
279 "-Dfml.ignorePatchDiscrepancies=true"});
280 }
281 return list;
282 }
283
javaArguments() const284 QStringList MinecraftInstance::javaArguments() const
285 {
286 QStringList args;
287
288 // custom args go first. we want to override them if we have our own here.
289 args.append(extraArguments());
290
291 // OSX dock icon and name
292 #ifdef Q_OS_MAC
293 args << "-Xdock:icon=icon.png";
294 args << QString("-Xdock:name=\"%1\"").arg(windowTitle());
295 #endif
296 auto traits_ = traits();
297 // HACK: fix issues on macOS with 1.13 snapshots
298 // NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them
299 #ifdef Q_OS_MAC
300 if(traits_.contains("FirstThreadOnMacOS"))
301 {
302 args << QString("-XstartOnFirstThread");
303 }
304 #endif
305
306 // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767
307 #ifdef Q_OS_WIN32
308 args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
309 "minecraft.exe.heapdump");
310 #endif
311
312 int min = settings()->get("MinMemAlloc").toInt();
313 int max = settings()->get("MaxMemAlloc").toInt();
314 if(min < max)
315 {
316 args << QString("-Xms%1m").arg(min);
317 args << QString("-Xmx%1m").arg(max);
318 }
319 else
320 {
321 args << QString("-Xms%1m").arg(max);
322 args << QString("-Xmx%1m").arg(min);
323 }
324
325 // No PermGen in newer java.
326 JavaVersion javaVersion = getJavaVersion();
327 if(javaVersion.requiresPermGen())
328 {
329 auto permgen = settings()->get("PermGen").toInt();
330 if (permgen != 64)
331 {
332 args << QString("-XX:PermSize=%1m").arg(permgen);
333 }
334 }
335
336 args << "-Duser.language=en";
337
338 return args;
339 }
340
getVariables() const341 QMap<QString, QString> MinecraftInstance::getVariables() const
342 {
343 QMap<QString, QString> out;
344 out.insert("INST_NAME", name());
345 out.insert("INST_ID", id());
346 out.insert("INST_DIR", QDir(instanceRoot()).absolutePath());
347 out.insert("INST_MC_DIR", QDir(gameRoot()).absolutePath());
348 out.insert("INST_JAVA", settings()->get("JavaPath").toString());
349 out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
350 return out;
351 }
352
createEnvironment()353 QProcessEnvironment MinecraftInstance::createEnvironment()
354 {
355 // prepare the process environment
356 QProcessEnvironment env = CleanEnviroment();
357
358 // export some infos
359 auto variables = getVariables();
360 for (auto it = variables.begin(); it != variables.end(); ++it)
361 {
362 env.insert(it.key(), it.value());
363 }
364 return env;
365 }
366
replaceTokensIn(QString text,QMap<QString,QString> with)367 static QString replaceTokensIn(QString text, QMap<QString, QString> with)
368 {
369 QString result;
370 QRegExp token_regexp("\\$\\{(.+)\\}");
371 token_regexp.setMinimal(true);
372 QStringList list;
373 int tail = 0;
374 int head = 0;
375 while ((head = token_regexp.indexIn(text, head)) != -1)
376 {
377 result.append(text.mid(tail, head - tail));
378 QString key = token_regexp.cap(1);
379 auto iter = with.find(key);
380 if (iter != with.end())
381 {
382 result.append(*iter);
383 }
384 head += token_regexp.matchedLength();
385 tail = head;
386 }
387 result.append(text.mid(tail));
388 return result;
389 }
390
processMinecraftArgs(AuthSessionPtr session) const391 QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) const
392 {
393 auto profile = m_components->getProfile();
394 QString args_pattern = profile->getMinecraftArguments();
395 for (auto tweaker : profile->getTweakers())
396 {
397 args_pattern += " --tweakClass " + tweaker;
398 }
399
400 QMap<QString, QString> token_mapping;
401 // yggdrasil!
402 if(session)
403 {
404 token_mapping["auth_username"] = session->username;
405 token_mapping["auth_session"] = session->session;
406 token_mapping["auth_access_token"] = session->access_token;
407 token_mapping["auth_player_name"] = session->player_name;
408 token_mapping["auth_uuid"] = session->uuid;
409 token_mapping["user_properties"] = session->serializeUserProperties();
410 token_mapping["user_type"] = session->user_type;
411 }
412
413 // blatant self-promotion.
414 token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5";
415
416 token_mapping["version_type"] = profile->getMinecraftVersionType();
417
418 QString absRootDir = QDir(gameRoot()).absolutePath();
419 token_mapping["game_directory"] = absRootDir;
420 QString absAssetsDir = QDir("assets/").absolutePath();
421 auto assets = profile->getMinecraftAssets();
422 token_mapping["game_assets"] = AssetsUtils::getAssetsDir(assets->id, resourcesDir()).absolutePath();
423
424 // 1.7.3+ assets tokens
425 token_mapping["assets_root"] = absAssetsDir;
426 token_mapping["assets_index_name"] = assets->id;
427
428 QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
429 for (int i = 0; i < parts.length(); i++)
430 {
431 parts[i] = replaceTokensIn(parts[i], token_mapping);
432 }
433 return parts;
434 }
435
createLaunchScript(AuthSessionPtr session)436 QString MinecraftInstance::createLaunchScript(AuthSessionPtr session)
437 {
438 QString launchScript;
439
440 if (!m_components)
441 return QString();
442 auto profile = m_components->getProfile();
443 if(!profile)
444 return QString();
445
446 auto mainClass = getMainClass();
447 if (!mainClass.isEmpty())
448 {
449 launchScript += "mainClass " + mainClass + "\n";
450 }
451 auto appletClass = profile->getAppletClass();
452 if (!appletClass.isEmpty())
453 {
454 launchScript += "appletClass " + appletClass + "\n";
455 }
456
457 // generic minecraft params
458 for (auto param : processMinecraftArgs(session))
459 {
460 launchScript += "param " + param + "\n";
461 }
462
463 // window size, title and state, legacy
464 {
465 QString windowParams;
466 if (settings()->get("LaunchMaximized").toBool())
467 windowParams = "max";
468 else
469 windowParams = QString("%1x%2")
470 .arg(settings()->get("MinecraftWinWidth").toInt())
471 .arg(settings()->get("MinecraftWinHeight").toInt());
472 launchScript += "windowTitle " + windowTitle() + "\n";
473 launchScript += "windowParams " + windowParams + "\n";
474 }
475
476 // legacy auth
477 if(session)
478 {
479 launchScript += "userName " + session->player_name + "\n";
480 launchScript += "sessionId " + session->session + "\n";
481 }
482
483 // libraries and class path.
484 {
485 QStringList jars, nativeJars;
486 auto javaArchitecture = settings()->get("JavaArchitecture").toString();
487 profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
488 for(auto file: jars)
489 {
490 launchScript += "cp " + file + "\n";
491 }
492 for(auto file: nativeJars)
493 {
494 launchScript += "ext " + file + "\n";
495 }
496 launchScript += "natives " + getNativePath() + "\n";
497 }
498
499 for (auto trait : profile->getTraits())
500 {
501 launchScript += "traits " + trait + "\n";
502 }
503 launchScript += "launcher onesix\n";
504 // qDebug() << "Generated launch script:" << launchScript;
505 return launchScript;
506 }
507
verboseDescription(AuthSessionPtr session)508 QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
509 {
510 QStringList out;
511 out << "Main Class:" << " " + getMainClass() << "";
512 out << "Native path:" << " " + getNativePath() << "";
513
514 auto profile = m_components->getProfile();
515
516 auto alltraits = traits();
517 if(alltraits.size())
518 {
519 out << "Traits:";
520 for (auto trait : alltraits)
521 {
522 out << "traits " + trait;
523 }
524 out << "";
525 }
526
527 // libraries and class path.
528 {
529 out << "Libraries:";
530 QStringList jars, nativeJars;
531 auto javaArchitecture = settings()->get("JavaArchitecture").toString();
532 profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
533 auto printLibFile = [&](const QString & path)
534 {
535 QFileInfo info(path);
536 if(info.exists())
537 {
538 out << " " + path;
539 }
540 else
541 {
542 out << " " + path + " (missing)";
543 }
544 };
545 for(auto file: jars)
546 {
547 printLibFile(file);
548 }
549 out << "";
550 out << "Native libraries:";
551 for(auto file: nativeJars)
552 {
553 printLibFile(file);
554 }
555 out << "";
556 }
557
558 auto printModList = [&](const QString & label, ModFolderModel & model) {
559 if(model.size())
560 {
561 out << QString("%1:").arg(label);
562 auto modList = model.allMods();
563 std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) {
564 auto aName = a.filename().completeBaseName();
565 auto bName = b.filename().completeBaseName();
566 return aName.localeAwareCompare(bName) < 0;
567 });
568 for(auto & mod: modList)
569 {
570 if(mod.type() == Mod::MOD_FOLDER)
571 {
572 out << u8" [] " + mod.filename().completeBaseName() + " (folder)";
573 continue;
574 }
575
576 if(mod.enabled()) {
577 out << u8" [✔️] " + mod.filename().completeBaseName();
578 }
579 else {
580 out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)";
581 }
582
583 }
584 out << "";
585 }
586 };
587
588 printModList("Mods", *(loaderModList().get()));
589 printModList("Core Mods", *(coreModList().get()));
590
591 auto & jarMods = profile->getJarMods();
592 if(jarMods.size())
593 {
594 out << "Jar Mods:";
595 for(auto & jarmod: jarMods)
596 {
597 auto displayname = jarmod->displayName(currentSystem);
598 auto realname = jarmod->filename(currentSystem);
599 if(displayname != realname)
600 {
601 out << " " + displayname + " (" + realname + ")";
602 }
603 else
604 {
605 out << " " + realname;
606 }
607 }
608 out << "";
609 }
610
611 auto params = processMinecraftArgs(nullptr);
612 out << "Params:";
613 out << " " + params.join(' ');
614 out << "";
615
616 QString windowParams;
617 if (settings()->get("LaunchMaximized").toBool())
618 {
619 out << "Window size: max (if available)";
620 }
621 else
622 {
623 auto width = settings()->get("MinecraftWinWidth").toInt();
624 auto height = settings()->get("MinecraftWinHeight").toInt();
625 out << "Window size: " + QString::number(width) + " x " + QString::number(height);
626 }
627 out << "";
628 return out;
629 }
630
createCensorFilterFromSession(AuthSessionPtr session)631 QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session)
632 {
633 if(!session)
634 {
635 return QMap<QString, QString>();
636 }
637 auto & sessionRef = *session.get();
638 QMap<QString, QString> filter;
639 auto addToFilter = [&filter](QString key, QString value)
640 {
641 if(key.trimmed().size())
642 {
643 filter[key] = value;
644 }
645 };
646 if (sessionRef.session != "-")
647 {
648 addToFilter(sessionRef.session, tr("<SESSION ID>"));
649 }
650 addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
651 addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
652 addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
653
654 auto i = sessionRef.u.properties.begin();
655 while (i != sessionRef.u.properties.end())
656 {
657 if(i.value().length() <= 3) {
658 ++i;
659 continue;
660 }
661 addToFilter(i.value(), "<" + i.key().toUpper() + ">");
662 ++i;
663 }
664 return filter;
665 }
666
guessLevel(const QString & line,MessageLevel::Enum level)667 MessageLevel::Enum MinecraftInstance::guessLevel(const QString &line, MessageLevel::Enum level)
668 {
669 QRegularExpression re("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]");
670 auto match = re.match(line);
671 if(match.hasMatch())
672 {
673 // New style logs from log4j
674 QString timestamp = match.captured("timestamp");
675 QString levelStr = match.captured("level");
676 if(levelStr == "INFO")
677 level = MessageLevel::Message;
678 if(levelStr == "WARN")
679 level = MessageLevel::Warning;
680 if(levelStr == "ERROR")
681 level = MessageLevel::Error;
682 if(levelStr == "FATAL")
683 level = MessageLevel::Fatal;
684 if(levelStr == "TRACE" || levelStr == "DEBUG")
685 level = MessageLevel::Debug;
686 }
687 else
688 {
689 // Old style forge logs
690 if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") ||
691 line.contains("[FINER]") || line.contains("[FINEST]"))
692 level = MessageLevel::Message;
693 if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
694 level = MessageLevel::Error;
695 if (line.contains("[WARNING]"))
696 level = MessageLevel::Warning;
697 if (line.contains("[DEBUG]"))
698 level = MessageLevel::Debug;
699 }
700 if (line.contains("overwriting existing"))
701 return MessageLevel::Fatal;
702 //NOTE: this diverges from the real regexp. no unicode, the first section is + instead of *
703 static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*";
704 if (line.contains("Exception in thread")
705 || line.contains(QRegularExpression("\\s+at " + javaSymbol))
706 || line.contains(QRegularExpression("Caused by: " + javaSymbol))
707 || line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)"))
708 || line.contains(QRegularExpression("... \\d+ more$"))
709 )
710 return MessageLevel::Error;
711 return level;
712 }
713
getLogFileMatcher()714 IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher()
715 {
716 auto combined = std::make_shared<MultiMatcher>();
717 combined->add(std::make_shared<RegexpMatcher>(".*\\.log(\\.[0-9]*)?(\\.gz)?$"));
718 combined->add(std::make_shared<RegexpMatcher>("crash-.*\\.txt"));
719 combined->add(std::make_shared<RegexpMatcher>("IDMap dump.*\\.txt$"));
720 combined->add(std::make_shared<RegexpMatcher>("ModLoader\\.txt(\\..*)?$"));
721 return combined;
722 }
723
getLogFileRoot()724 QString MinecraftInstance::getLogFileRoot()
725 {
726 return gameRoot();
727 }
728
prettifyTimeDuration(int64_t duration)729 QString MinecraftInstance::prettifyTimeDuration(int64_t duration)
730 {
731 int seconds = (int) (duration % 60);
732 duration /= 60;
733 int minutes = (int) (duration % 60);
734 duration /= 60;
735 int hours = (int) (duration % 24);
736 int days = (int) (duration / 24);
737 if((hours == 0)&&(days == 0))
738 {
739 return tr("%1m %2s").arg(minutes).arg(seconds);
740 }
741 if (days == 0)
742 {
743 return tr("%1h %2m").arg(hours).arg(minutes);
744 }
745 return tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes);
746 }
747
getStatusbarDescription()748 QString MinecraftInstance::getStatusbarDescription()
749 {
750 QStringList traits;
751 if (hasVersionBroken())
752 {
753 traits.append(tr("broken"));
754 }
755
756 QString description;
757 description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName()));
758 if(totalTimePlayed() > 0)
759 {
760 description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed())));
761 }
762 if(hasCrashed())
763 {
764 description.append(tr(", has crashed."));
765 }
766 return description;
767 }
768
createUpdateTask(Net::Mode mode)769 shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
770 {
771 switch (mode)
772 {
773 case Net::Mode::Offline:
774 {
775 return shared_qobject_ptr<Task>(new MinecraftLoadAndCheck(this));
776 }
777 case Net::Mode::Online:
778 {
779 return shared_qobject_ptr<Task>(new MinecraftUpdate(this));
780 }
781 }
782 return nullptr;
783 }
784
createLaunchTask(AuthSessionPtr session)785 shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
786 {
787 // FIXME: get rid of shared_from_this ...
788 auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
789 auto pptr = process.get();
790
791 ENV.icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG");
792
793 // print a header
794 {
795 process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC));
796 }
797
798 // check java
799 {
800 process->appendStep(new CheckJava(pptr));
801 }
802
803 // check launch method
804 QStringList validMethods = {"LauncherPart", "DirectJava"};
805 QString method = launchMethod();
806 if(!validMethods.contains(method))
807 {
808 process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
809 return process;
810 }
811
812 // run pre-launch command if that's needed
813 if(getPreLaunchCommand().size())
814 {
815 auto step = new PreLaunchCommand(pptr);
816 step->setWorkingDirectory(gameRoot());
817 process->appendStep(step);
818 }
819
820 // if we aren't in offline mode,.
821 if(session->status != AuthSession::PlayableOffline)
822 {
823 process->appendStep(new ClaimAccount(pptr, session));
824 process->appendStep(new Update(pptr, Net::Mode::Online));
825 }
826 else
827 {
828 process->appendStep(new Update(pptr, Net::Mode::Offline));
829 }
830
831 // if there are any jar mods
832 {
833 process->appendStep(new ModMinecraftJar(pptr));
834 }
835
836 // if there are any jar mods
837 {
838 process->appendStep(new ScanModFolders(pptr));
839 }
840
841 // print some instance info here...
842 {
843 process->appendStep(new PrintInstanceInfo(pptr, session));
844 }
845
846 // create the server-resource-packs folder (workaround for Minecraft bug MCL-3732)
847 {
848 process->appendStep(new CreateServerResourcePacksFolder(pptr));
849 }
850
851 // extract native jars if needed
852 {
853 process->appendStep(new ExtractNatives(pptr));
854 }
855
856 // reconstruct assets if needed
857 {
858 process->appendStep(new ReconstructAssets(pptr));
859 }
860
861 {
862 // actually launch the game
863 auto method = launchMethod();
864 if(method == "LauncherPart")
865 {
866 auto step = new LauncherPartLaunch(pptr);
867 step->setWorkingDirectory(gameRoot());
868 step->setAuthSession(session);
869 process->appendStep(step);
870 }
871 else if (method == "DirectJava")
872 {
873 auto step = new DirectJavaLaunch(pptr);
874 step->setWorkingDirectory(gameRoot());
875 step->setAuthSession(session);
876 process->appendStep(step);
877 }
878 }
879
880 // run post-exit command if that's needed
881 if(getPostExitCommand().size())
882 {
883 auto step = new PostLaunchCommand(pptr);
884 step->setWorkingDirectory(gameRoot());
885 process->appendStep(step);
886 }
887 if (session)
888 {
889 process->setCensorFilter(createCensorFilterFromSession(session));
890 }
891 m_launchProcess = process;
892 emit launchTaskChanged(m_launchProcess);
893 return m_launchProcess;
894 }
895
launchMethod()896 QString MinecraftInstance::launchMethod()
897 {
898 return m_settings->get("MCLaunchMethod").toString();
899 }
900
getJavaVersion() const901 JavaVersion MinecraftInstance::getJavaVersion() const
902 {
903 return JavaVersion(settings()->get("JavaVersion").toString());
904 }
905
loaderModList() const906 std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
907 {
908 if (!m_loader_mod_list)
909 {
910 m_loader_mod_list.reset(new ModFolderModel(loaderModsDir()));
911 m_loader_mod_list->disableInteraction(isRunning());
912 connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
913 }
914 return m_loader_mod_list;
915 }
916
coreModList() const917 std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
918 {
919 if (!m_core_mod_list)
920 {
921 m_core_mod_list.reset(new ModFolderModel(coreModsDir()));
922 m_core_mod_list->disableInteraction(isRunning());
923 connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
924 }
925 return m_core_mod_list;
926 }
927
resourcePackList() const928 std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
929 {
930 if (!m_resource_pack_list)
931 {
932 m_resource_pack_list.reset(new ModFolderModel(resourcePacksDir()));
933 m_resource_pack_list->disableInteraction(isRunning());
934 connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction);
935 }
936 return m_resource_pack_list;
937 }
938
texturePackList() const939 std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
940 {
941 if (!m_texture_pack_list)
942 {
943 m_texture_pack_list.reset(new ModFolderModel(texturePacksDir()));
944 m_texture_pack_list->disableInteraction(isRunning());
945 connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction);
946 }
947 return m_texture_pack_list;
948 }
949
worldList() const950 std::shared_ptr<WorldList> MinecraftInstance::worldList() const
951 {
952 if (!m_world_list)
953 {
954 m_world_list.reset(new WorldList(worldDir()));
955 }
956 return m_world_list;
957 }
958
gameOptionsModel() const959 std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel() const
960 {
961 if (!m_game_options)
962 {
963 m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt")));
964 }
965 return m_game_options;
966 }
967
getJarMods() const968 QList< Mod > MinecraftInstance::getJarMods() const
969 {
970 auto profile = m_components->getProfile();
971 QList<Mod> mods;
972 for (auto jarmod : profile->getJarMods())
973 {
974 QStringList jar, temp1, temp2, temp3;
975 jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
976 // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem));
977 mods.push_back(Mod(QFileInfo(jar[0])));
978 }
979 return mods;
980 }
981
982
983 #include "MinecraftInstance.moc"
984