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