text: #include "MultiMC.h"
  #include "BuildConfig.h"
  #include "MainWindow.h"
  #include "InstanceWindow.h"
  
  #include "groupview/AccessibleGroupView.h"
  #include <QAccessible>
  
  #include "pages/BasePageProvider.h"
  #include "pages/global/MultiMCPage.h"
  #include "pages/global/MinecraftPage.h"
  #include "pages/global/JavaPage.h"
  #include "pages/global/LanguagePage.h"
  #include "pages/global/ProxyPage.h"
  #include "pages/global/ExternalToolsPage.h"
  #include "pages/global/AccountListPage.h"
  #include "pages/global/PasteEEPage.h"
  #include "pages/global/CustomCommandsPage.h"
  
  #include "themes/ITheme.h"
  #include "themes/SystemTheme.h"
  #include "themes/DarkTheme.h"
  #include "themes/BrightTheme.h"
  #include "themes/CustomTheme.h"
  
  #include "setupwizard/SetupWizard.h"
  #include "setupwizard/LanguageWizardPage.h"
  #include "setupwizard/JavaWizardPage.h"
  #include "setupwizard/AnalyticsWizardPage.h"
  
  #include <iostream>
  #include <QDir>
  #include <QFileInfo>
  #include <QNetworkAccessManager>
  #include <QTranslator>
  #include <QLibraryInfo>
  #include <QList>
  #include <QStringList>
  #include <QDebug>
  #include <QStyleFactory>
  
  #include "dialogs/CustomMessageBox.h"
  #include "InstanceList.h"
  
  #include <minecraft/auth/MojangAccountList.h>
  #include "icons/IconList.h"
  #include "net/HttpMetaCache.h"
  #include "Env.h"
  
  #include "java/JavaUtils.h"
  
  #include "updater/UpdateChecker.h"
  
  #include "tools/JProfiler.h"
  #include "tools/JVisualVM.h"
  #include "tools/MCEditTool.h"
  
  #include <xdgicon.h>
  #include "settings/INISettingsObject.h"
  #include "settings/Setting.h"
  
  #include "translations/TranslationsModel.h"
  
  #include <Commandline.h>
  #include <FileSystem.h>
  #include <DesktopServices.h>
  #include <LocalPeer.h>
  
  #include <ganalytics.h>
  #include <sys.h>
  
  #include "pagedialog/PageDialog.h"
  
  
  #if defined Q_OS_WIN32
  #ifndef WIN32_LEAN_AND_MEAN
  #define WIN32_LEAN_AND_MEAN
  #endif
  #include <windows.h>
  #include <stdio.h>
  #endif
  
  #define STRINGIFY(x) #x
  #define TOSTRING(x) STRINGIFY(x)
  
  static const QLatin1String liveCheckFile("live.check");
  
  using namespace Commandline;
  
  #define MACOS_HINT "If you are on macOS Sierra, you might have to move MultiMC.app to your /Applications or ~/Applications folder. "\
      "This usually fixes the problem and you can move the application elsewhere afterwards.\n"\
      "\n"
  
  static void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
  {
      const char *levels = "DWCFIS";
      const QString format("%1 %2 %3\n");
  
      qint64 msecstotal = MMC->timeSinceStart();
      qint64 seconds = msecstotal / 1000;
      qint64 msecs = msecstotal % 1000;
      QString foo;
      char buf[1025] = {0};
      ::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs);
  
      QString out = format.arg(buf).arg(levels[type]).arg(msg);
  
      MMC->logFile->write(out.toUtf8());
      MMC->logFile->flush();
      QTextStream(stderr) << out.toLocal8Bit();
      fflush(stderr);
  }
  
  MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
  {
  #if defined Q_OS_WIN32
      // attach the parent console
      if(AttachConsole(ATTACH_PARENT_PROCESS))
      {
          // if attach succeeds, reopen and sync all the i/o
          if(freopen("CON", "w", stdout))
          {
              std::cout.sync_with_stdio();
          }
          if(freopen("CON", "w", stderr))
          {
              std::cerr.sync_with_stdio();
          }
          if(freopen("CON", "r", stdin))
          {
              std::cin.sync_with_stdio();
          }
          auto out = GetStdHandle (STD_OUTPUT_HANDLE);
          DWORD written;
          const char * endline = "\n";
          WriteConsole(out, endline, strlen(endline), &written, NULL);
          consoleAttached = true;
      }
  #endif
      setOrganizationName("MultiMC");
      setOrganizationDomain("multimc.org");
      setApplicationName("MultiMC5");
      setApplicationDisplayName("MultiMC 5");
      setApplicationVersion(BuildConfig.printableVersionString());
  
      startTime = QDateTime::currentDateTime();
  
  #ifdef Q_OS_LINUX
      {
          QFile osrelease("/proc/sys/kernel/osrelease");
          if (osrelease.open(QFile::ReadOnly | QFile::Text)) {
              QTextStream in(&osrelease);
              auto contents = in.readAll();
              if(
                  contents.contains("WSL", Qt::CaseInsensitive) ||
                  contents.contains("Microsoft", Qt::CaseInsensitive)
              ) {
                  showFatalErrorMessage(
                      "Unsupported system detected!",
                      "Linux-on-Windows distributions are not supported.\n\n"
                      "Please use the Windows MultiMC binary when playing on Windows."
                  );
                  return;
              }
          }
      }
  #endif
  
      // Don't quit on hiding the last window
      this->setQuitOnLastWindowClosed(false);
  
      // Commandline parsing
      QHash<QString, QVariant> args;
      {
          Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals);
  
          // --help
          parser.addSwitch("help");
          parser.addShortOpt("help", 'h');
          parser.addDocumentation("help", "Display this help and exit.");
          // --version
          parser.addSwitch("version");
          parser.addShortOpt("version", 'V');
          parser.addDocumentation("version", "Display program version and exit.");
          // --dir
          parser.addOption("dir");
          parser.addShortOpt("dir", 'd');
          parser.addDocumentation("dir", "Use the supplied folder as MultiMC root instead of "
                                         "the binary location (use '.' for current)");
          // --launch
          parser.addOption("launch");
          parser.addShortOpt("launch", 'l');
          parser.addDocumentation("launch", "Launch the specified instance (by instance ID)");
          // --server
          parser.addOption("server");
          parser.addShortOpt("server", 's');
          parser.addDocumentation("server", "Join the specified server on launch "
                                            "(only valid in combination with --launch)");
          // --alive
          parser.addSwitch("alive");
          parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after MultiMC starts");
          // --import
          parser.addOption("import");
          parser.addShortOpt("import", 'I');
          parser.addDocumentation("import", "Import instance from specified zip (local path or URL)");
  
          // parse the arguments
          try
          {
              args = parser.parse(arguments());
          }
          catch (const ParsingError &e)
          {
              std::cerr << "CommandLineError: " << e.what() << std::endl;
              if(argc > 0)
                  std::cerr << "Try '" << argv[0] << " -h' to get help on MultiMC's command line parameters."
                            << std::endl;
              m_status = MultiMC::Failed;
              return;
          }
  
          // display help and exit
          if (args["help"].toBool())
          {
              std::cout << qPrintable(parser.compileHelp(arguments()[0]));
              m_status = MultiMC::Succeeded;
              return;
          }
  
          // display version and exit
          if (args["version"].toBool())
          {
              std::cout << "Version " << BuildConfig.printableVersionString().toStdString() << std::endl;
              std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl;
              m_status = MultiMC::Succeeded;
              return;
          }
      }
      m_instanceIdToLaunch = args["launch"].toString();
      m_serverToJoin = args["server"].toString();
      m_liveCheck = args["alive"].toBool();
      m_zipToImport = args["import"].toUrl();
  
      QString origcwdPath = QDir::currentPath();
      QString binPath = applicationDirPath();
      QString adjustedBy;
      QString dataPath;
      // change folder
      QString dirParam = args["dir"].toString();
      if (!dirParam.isEmpty())
      {
          // the dir param. it makes multimc data path point to whatever the user specified
          // on command line
          adjustedBy += "Command line " + dirParam;
          dataPath = dirParam;
      }
      else
      {
  #ifdef MULTIMC_LINUX_DATADIR
          QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME"));
          if (xdgDataHome.isEmpty())
              xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
          dataPath = xdgDataHome + "/multimc";
          adjustedBy += "XDG standard " + dataPath;
  #elif defined(Q_OS_MAC)
          QDir foo(FS::PathCombine(applicationDirPath(), "../../Data"));
          dataPath = foo.absolutePath();
          adjustedBy += "Fallback to special Mac location " + dataPath;
  #else
          dataPath = applicationDirPath();
          adjustedBy += "Fallback to binary path " + dataPath;
  #endif
      }
  
      if (!FS::ensureFolderPathExists(dataPath))
      {
          showFatalErrorMessage(
              "MultiMC data folder could not be created.",
              "MultiMC data folder could not be created.\n"
              "\n"
  #if defined(Q_OS_MAC)
              MACOS_HINT
  #endif
              "Make sure you have the right permissions to the MultiMC data folder and any folder needed to access it.\n"
              "\n"
              "MultiMC cannot continue until you fix this problem."
          );
          return;
      }
      if (!QDir::setCurrent(dataPath))
      {
          showFatalErrorMessage(
              "MultiMC data folder could not be opened.",
              "MultiMC data folder could not be opened.\n"
              "\n"
  #if defined(Q_OS_MAC)
              MACOS_HINT
  #endif
              "Make sure you have the right permissions to the MultiMC data folder.\n"
              "\n"
              "MultiMC cannot continue until you fix this problem."
          );
          return;
      }
  
      if(m_instanceIdToLaunch.isEmpty() && !m_serverToJoin.isEmpty())
      {
          std::cerr << "--server can only be used in combination with --launch!" << std::endl;
          m_status = MultiMC::Failed;
          return;
      }
  
  #if defined(Q_OS_MAC)
      // move user data to new location if on macOS and it still exists in Contents/MacOS
      QDir fi(applicationDirPath());
      QString originalData = fi.absolutePath();
      // if the config file exists in Contents/MacOS, then user data is still there and needs to moved
      if (QFileInfo::exists(FS::PathCombine(originalData, "multimc.cfg")))
      {
          if (!QFileInfo::exists(FS::PathCombine(originalData, "dontmovemacdata")))
          {
              QMessageBox::StandardButton askMoveDialogue;
              askMoveDialogue = QMessageBox::question(nullptr, "MultiMC 5", "Would you like to move application data to a new data location? It will improve MultiMC's performance, but if you switch to older versions it will look like instances have disappeared. If you select no, you can migrate later in settings. You should select yes unless you're commonly switching between different versions of MultiMC (eg. develop and stable).", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
              if (askMoveDialogue == QMessageBox::Yes)
              {
                  qDebug() << "On macOS and found config file in old location, moving user data...";
                  QDir dir;
                  QStringList dataFiles {
                      "*.log", // MultiMC-@.log
                      "accounts.json",
                      "accounts",
                      "assets",
                      "cache",
                      "icons",
                      "instances",
                      "libraries",
                      "meta",
                      "metacache",
                      "mods",
                      "multimc.cfg",
                      "themes",
                      "translations"
                  };
                  QDirIterator files(originalData, dataFiles);
                  while (files.hasNext()) {
                      QString filePath(files.next());
                      QString fileName(files.fileName());
                      if (!dir.rename(filePath, FS::PathCombine(dataPath, fileName)))
                      {
                          qWarning() << "Failed to move " << fileName;
                      }
                  }
              }
              else
              {
                  dataPath = originalData;
                  QDir::setCurrent(dataPath);
                  QFile file(originalData + "/dontmovemacdata");
                  file.open(QIODevice::WriteOnly);
              }
          }
          else
          {
              dataPath = originalData;
              QDir::setCurrent(dataPath);
          }
      }
  #endif
  
      /*
       * Establish the mechanism for communication with an already running MultiMC that uses the same data path.
       * If there is one, tell it what the user actually wanted to do and exit.
       * We want to initialize this before logging to avoid messing with the log of a potential already running copy.
       */
      auto appID = ApplicationId::fromPathAndVersion(QDir::currentPath(), BuildConfig.printableVersionString());
      {
          // FIXME: you can run the same binaries with multiple data dirs and they won't clash. This could cause issues for updates.
          m_peerInstance = new LocalPeer(this, appID);
          connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived);
          if(m_peerInstance->isClient())
          {
              int timeout = 2000;
  
              if(m_instanceIdToLaunch.isEmpty())
              {
                  m_peerInstance->sendMessage("activate", timeout);
  
                  if(!m_zipToImport.isEmpty())
                  {
                      m_peerInstance->sendMessage("import " + m_zipToImport.toString(), timeout);
                  }
              }
              else
              {
                  if(!m_serverToJoin.isEmpty())
                  {
                      m_peerInstance->sendMessage(
                              "launch-with-server " + m_instanceIdToLaunch + " " + m_serverToJoin, timeout);
                  }
                  else
                  {
                      m_peerInstance->sendMessage("launch " + m_instanceIdToLaunch, timeout);
                  }
              }
              m_status = MultiMC::Succeeded;
              return;
          }
      }
  
      // init the logger
      {
          static const QString logBase = "MultiMC-%0.log";
          auto moveFile = [](const QString &oldName, const QString &newName)
          {
              QFile::remove(newName);
              QFile::copy(oldName, newName);
              QFile::remove(oldName);
          };
  
          moveFile(logBase.arg(3), logBase.arg(4));
          moveFile(logBase.arg(2), logBase.arg(3));
          moveFile(logBase.arg(1), logBase.arg(2));
          moveFile(logBase.arg(0), logBase.arg(1));
  
          logFile = std::unique_ptr<QFile>(new QFile(logBase.arg(0)));
          if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
          {
              showFatalErrorMessage(
                  "MultiMC data folder is not writable!",
                  "MultiMC couldn't create a log file - the MultiMC data folder is not writable.\n"
                  "\n"
      #if defined(Q_OS_MAC)
                  MACOS_HINT
      #endif
                  "Make sure you have write permissions to the MultiMC data folder.\n"
                  "\n"
                  "MultiMC cannot continue until you fix this problem."
              );
              return;
          }
          qInstallMessageHandler(appDebugOutput);
          qDebug() << "<> Log initialized.";
      }
  
      // Set up paths
      {
          // Root path is used for updates.
  #ifdef Q_OS_LINUX
          QDir foo(FS::PathCombine(binPath, ".."));
          m_rootPath = foo.absolutePath();
  #elif defined(Q_OS_WIN32)
          m_rootPath = binPath;
  #elif defined(Q_OS_MAC)
          QDir foo(FS::PathCombine(binPath, "../.."));
          m_rootPath = foo.absolutePath();
          // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues)
          FS::updateTimestamp(m_rootPath);
  #endif
  
  #ifdef MULTIMC_JARS_LOCATION
          ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) );
  #endif
  
          qDebug() << "MultiMC 5, (c) 2013-2021 MultiMC Contributors";
          qDebug() << "Version                    : " << BuildConfig.printableVersionString();
          qDebug() << "Git commit                 : " << BuildConfig.GIT_COMMIT;
          qDebug() << "Git refspec                : " << BuildConfig.GIT_REFSPEC;
          if (adjustedBy.size())
          {
              qDebug() << "Work dir before adjustment : " << origcwdPath;
              qDebug() << "Work dir after adjustment  : " << QDir::currentPath();
              qDebug() << "Adjusted by                : " << adjustedBy;
          }
          else
          {
              qDebug() << "Work dir                   : " << QDir::currentPath();
          }
          qDebug() << "Binary path                : " << binPath;
          qDebug() << "Application root path      : " << m_rootPath;
          if(!m_instanceIdToLaunch.isEmpty())
          {
              qDebug() << "ID of instance to launch   : " << m_instanceIdToLaunch;
          }
          if(!m_serverToJoin.isEmpty())
          {
              qDebug() << "Address of server to join  :" << m_serverToJoin;
          }
          qDebug() << "<> Paths set.";
      }
  
      do // once
      {
          if(m_liveCheck)
          {
              QFile check(liveCheckFile);
              if(!check.open(QIODevice::WriteOnly | QIODevice::Truncate))
              {
                  qWarning() << "Could not open" << liveCheckFile << "for writing!";
                  break;
              }
              auto payload = appID.toString().toUtf8();
              if(check.write(payload) != payload.size())
              {
                  qWarning() << "Could not write into" << liveCheckFile << "!";
                  check.remove();
                  break;
              }
              check.close();
          }
      } while(false);
  
      // Initialize application settings
      {
          m_settings.reset(new INISettingsObject("multimc.cfg", this));
          // Updates
          m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
          m_settings->registerSetting("AutoUpdate", true);
  
          // Theming
          m_settings->registerSetting("IconTheme", QString("multimc"));
          m_settings->registerSetting("ApplicationTheme", QString("system"));
  
          // Notifications
          m_settings->registerSetting("ShownNotifications", QString());
  
          // Remembered state
          m_settings->registerSetting("LastUsedGroupForNewInstance", QString());
  
          QString defaultMonospace;
          int defaultSize = 11;
  #ifdef Q_OS_WIN32
          defaultMonospace = "Courier";
          defaultSize = 10;
  #elif defined(Q_OS_MAC)
          defaultMonospace = "Menlo";
  #else
          defaultMonospace = "Monospace";
  #endif
  
          // resolve the font so the default actually matches
          QFont consoleFont;
          consoleFont.setFamily(defaultMonospace);
          consoleFont.setStyleHint(QFont::Monospace);
          consoleFont.setFixedPitch(true);
          QFontInfo consoleFontInfo(consoleFont);
          QString resolvedDefaultMonospace = consoleFontInfo.family();
          QFont resolvedFont(resolvedDefaultMonospace);
          qDebug() << "Detected default console font:" << resolvedDefaultMonospace
              << ", substitutions:" << resolvedFont.substitutions().join(',');
  
          m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace);
          m_settings->registerSetting("ConsoleFontSize", defaultSize);
          m_settings->registerSetting("ConsoleMaxLines", 100000);
          m_settings->registerSetting("ConsoleOverflowStop", true);
  
          // Folders
          m_settings->registerSetting("InstanceDir", "instances");
          m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods");
          m_settings->registerSetting("IconsDir", "icons");
  
          // Editors
          m_settings->registerSetting("JsonEditor", QString());
  
          // Language
          m_settings->registerSetting("Language", QString());
  
          // Console
          m_settings->registerSetting("ShowConsole", false);
          m_settings->registerSetting("AutoCloseConsole", false);
          m_settings->registerSetting("ShowConsoleOnError", true);
          m_settings->registerSetting("LogPrePostOutput", true);
  
          // Window Size
          m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false);
          m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854);
          m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480);
  
          // Proxy Settings
          m_settings->registerSetting("ProxyType", "None");
          m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1");
          m_settings->registerSetting("ProxyPort", 8080);
          m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, "");
          m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, "");
  
          // Memory
          m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
          m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024);
          m_settings->registerSetting("PermGen", 128);
  
          // Java Settings
          m_settings->registerSetting("JavaPath", "");
          m_settings->registerSetting("JavaTimestamp", 0);
          m_settings->registerSetting("JavaArchitecture", "");
          m_settings->registerSetting("JavaVersion", "");
          m_settings->registerSetting("JavaVendor", "");
          m_settings->registerSetting("LastHostname", "");
          m_settings->registerSetting("JvmArgs", "");
  
          // Native library workarounds
          m_settings->registerSetting("UseNativeOpenAL", false);
          m_settings->registerSetting("UseNativeGLFW", false);
  
          // Game time
          m_settings->registerSetting("ShowGameTime", true);
          m_settings->registerSetting("RecordGameTime", true);
  
          // Minecraft launch method
          m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
  
          // Wrapper command for launch
          m_settings->registerSetting("WrapperCommand", "");
  
          // Custom Commands
          m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, "");
          m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, "");
  
          // The cat
          m_settings->registerSetting("TheCat", false);
  
          m_settings->registerSetting("InstSortMode", "Name");
          m_settings->registerSetting("SelectedInstance", QString());
  
          // Window state and geometry
          m_settings->registerSetting("MainWindowState", "");
          m_settings->registerSetting("MainWindowGeometry", "");
  
          m_settings->registerSetting("ConsoleWindowState", "");
          m_settings->registerSetting("ConsoleWindowGeometry", "");
  
          m_settings->registerSetting("SettingsGeometry", "");
  
          m_settings->registerSetting("PagedGeometry", "");
  
          m_settings->registerSetting("NewInstanceGeometry", "");
  
          m_settings->registerSetting("UpdateDialogGeometry", "");
  
          // paste.ee API key
          m_settings->registerSetting("PasteEEAPIKey", "multimc");
  
          if(!BuildConfig.ANALYTICS_ID.isEmpty())
          {
              // Analytics
              m_settings->registerSetting("Analytics", true);
              m_settings->registerSetting("AnalyticsSeen", 0);
              m_settings->registerSetting("AnalyticsClientID", QString());
          }
  
          // Init page provider
          {
              m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
              m_globalSettingsProvider->addPage<MultiMCPage>();
              m_globalSettingsProvider->addPage<MinecraftPage>();
              m_globalSettingsProvider->addPage<JavaPage>();
              m_globalSettingsProvider->addPage<LanguagePage>();
              m_globalSettingsProvider->addPage<CustomCommandsPage>();
              m_globalSettingsProvider->addPage<ProxyPage>();
              m_globalSettingsProvider->addPage<ExternalToolsPage>();
              m_globalSettingsProvider->addPage<AccountListPage>();
              m_globalSettingsProvider->addPage<PasteEEPage>();
          }
          qDebug() << "<> Settings loaded.";
      }
  
  #ifndef QT_NO_ACCESSIBILITY
      QAccessible::installFactory(groupViewAccessibleFactory);
  #endif /* !QT_NO_ACCESSIBILITY */
  
      // load translations
      {
          m_translations.reset(new TranslationsModel("translations"));
          auto bcp47Name = m_settings->get("Language").toString();
          m_translations->selectLanguage(bcp47Name);
          qDebug() << "Your language is" << bcp47Name;
          qDebug() << "<> Translations loaded.";
      }
  
      // initialize the updater
      if(BuildConfig.UPDATER_ENABLED)
      {
          m_updateChecker.reset(new UpdateChecker(BuildConfig.CHANLIST_URL, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
          qDebug() << "<> Updater started.";
      }
  
      // Instance icons
      {
          auto setting = MMC->settings()->getSetting("IconsDir");
          QStringList instFolders =
          {
              ":/icons/multimc/32x32/instances/",
              ":/icons/multimc/50x50/instances/",
              ":/icons/multimc/128x128/instances/",
              ":/icons/multimc/scalable/instances/"
          };
          m_icons.reset(new IconList(instFolders, setting->get().toString()));
          connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value)
          {
              m_icons->directoryChanged(value.toString());
          });
          ENV.registerIconList(m_icons);
          qDebug() << "<> Instance icons intialized.";
      }
  
      // Icon themes
      {
          // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies!
          // set icon theme search path!
          auto searchPaths = QIcon::themeSearchPaths();
          searchPaths.append("iconthemes");
          QIcon::setThemeSearchPaths(searchPaths);
          qDebug() << "<> Icon themes initialized.";
      }
  
      // Initialize widget themes
      {
          auto insertTheme = [this](ITheme * theme)
          {
              m_themes.insert(std::make_pair(theme->id(), std::unique_ptr<ITheme>(theme)));
          };
          auto darkTheme = new DarkTheme();
          insertTheme(new SystemTheme());
          insertTheme(darkTheme);
          insertTheme(new BrightTheme());
          insertTheme(new CustomTheme(darkTheme, "custom"));
          qDebug() << "<> Widget themes initialized.";
      }
  
      // initialize and load all instances
      {
          auto InstDirSetting = m_settings->getSetting("InstanceDir");
          // instance path: check for problems with '!' in instance path and warn the user in the log
          // and remember that we have to show him a dialog when the gui starts (if it does so)
          QString instDir = InstDirSetting->get().toString();
          qDebug() << "Instance path              : " << instDir;
          if (FS::checkProblemticPathJava(QDir(instDir)))
          {
              qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!";
          }
          m_instances.reset(new InstanceList(m_settings, instDir, this));
          connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged);
          qDebug() << "Loading Instances...";
          m_instances->loadList();
          qDebug() << "<> Instances loaded.";
      }
  
      // and accounts
      {
          m_accounts.reset(new MojangAccountList(this));
          qDebug() << "Loading accounts...";
          m_accounts->setListFilePath("accounts.json", true);
          m_accounts->loadList();
          qDebug() << "<> Accounts loaded.";
      }
  
      // init the http meta cache
      {
          ENV.initHttpMetaCache();
          qDebug() << "<> Cache initialized.";
      }
  
      // init proxy settings
      {
          QString proxyTypeStr = settings()->get("ProxyType").toString();
          QString addr = settings()->get("ProxyAddr").toString();
          int port = settings()->get("ProxyPort").value<qint16>();
          QString user = settings()->get("ProxyUser").toString();
          QString pass = settings()->get("ProxyPass").toString();
          ENV.updateProxySettings(proxyTypeStr, addr, port, user, pass);
          qDebug() << "<> Proxy settings done.";
      }
  
      // now we have network, download translation updates
      m_translations->downloadIndex();
  
      //FIXME: what to do with these?
      m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory()));
      m_profilers.insert("jvisualvm", std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory()));
      for (auto profiler : m_profilers.values())
      {
          profiler->registerSettings(m_settings);
      }
  
      // Create the MCEdit thing... why is this here?
      {
          m_mcedit.reset(new MCEditTool(m_settings));
      }
  
      connect(this, &MultiMC::aboutToQuit, [this](){
          if(m_instances)
          {
              // save any remaining instance state
              m_instances->saveNow();
          }
          if(logFile)
          {
              logFile->flush();
              logFile->close();
          }
      });
  
      {
          setIconTheme(settings()->get("IconTheme").toString());
          qDebug() << "<> Icon theme set.";
          setApplicationTheme(settings()->get("ApplicationTheme").toString(), true);
          qDebug() << "<> Application theme set.";
      }
  
      // Initialize analytics
      [this]()
      {
          const int analyticsVersion = 2;
          if(BuildConfig.ANALYTICS_ID.isEmpty())
          {
              return;
          }
  
          auto analyticsSetting = m_settings->getSetting("Analytics");
          connect(analyticsSetting.get(), &Setting::SettingChanged, this, &MultiMC::analyticsSettingChanged);
          QString clientID = m_settings->get("AnalyticsClientID").toString();
          if(clientID.isEmpty())
          {
              clientID = QUuid::createUuid().toString();
              clientID.remove(QLatin1Char('{'));
              clientID.remove(QLatin1Char('}'));
              m_settings->set("AnalyticsClientID", clientID);
          }
          m_analytics = new GAnalytics(BuildConfig.ANALYTICS_ID, clientID, analyticsVersion, this);
          m_analytics->setLogLevel(GAnalytics::Debug);
          m_analytics->setAnonymizeIPs(true);
          m_analytics->setNetworkAccessManager(&ENV.qnam());
  
          if(m_settings->get("AnalyticsSeen").toInt() < m_analytics->version())
          {
              qDebug() << "Analytics info not seen by user yet (or old version).";
              return;
          }
          if(!m_settings->get("Analytics").toBool())
          {
              qDebug() << "Analytics disabled by user.";
              return;
          }
  
          m_analytics->enable();
          qDebug() << "<> Initialized analytics with tid" << BuildConfig.ANALYTICS_ID;
      }();
  
      if(createSetupWizard())
      {
          return;
      }
      performMainStartupAction();
  }
  
  bool MultiMC::createSetupWizard()
  {
      bool javaRequired = [&]()
      {
          QString currentHostName = QHostInfo::localHostName();
          QString oldHostName = settings()->get("LastHostname").toString();
          if (currentHostName != oldHostName)
          {
              settings()->set("LastHostname", currentHostName);
              return true;
          }
          QString currentJavaPath = settings()->get("JavaPath").toString();
          QString actualPath = FS::ResolveExecutable(currentJavaPath);
          if (actualPath.isNull())
          {
              return true;
          }
          return false;
      }();
      bool analyticsRequired = [&]()
      {
          if(BuildConfig.ANALYTICS_ID.isEmpty())
          {
              return false;
          }
          if (!settings()->get("Analytics").toBool())
          {
              return false;
          }
          if (settings()->get("AnalyticsSeen").toInt() < analytics()->version())
          {
              return true;
          }
          return false;
      }();
      bool languageRequired = [&]()
      {
          if (settings()->get("Language").toString().isEmpty())
              return true;
          return false;
      }();
      bool wizardRequired = javaRequired || analyticsRequired || languageRequired;
  
      if(wizardRequired)
      {
          m_setupWizard = new SetupWizard(nullptr);
          if (languageRequired)
          {
              m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard));
          }
          if (javaRequired)
          {
              m_setupWizard->addPage(new JavaWizardPage(m_setupWizard));
          }
          if(analyticsRequired)
          {
              m_setupWizard->addPage(new AnalyticsWizardPage(m_setupWizard));
          }
          connect(m_setupWizard, &QDialog::finished, this, &MultiMC::setupWizardFinished);
          m_setupWizard->show();
          return true;
      }
      return false;
  }
  
  void MultiMC::setupWizardFinished(int status)
  {
      qDebug() << "Wizard result =" << status;
      performMainStartupAction();
  }
  
  void MultiMC::performMainStartupAction()
  {
      m_status = MultiMC::Initialized;
      if(!m_instanceIdToLaunch.isEmpty())
      {
          auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
          if(inst)
          {
              MinecraftServerTargetPtr serverToJoin = nullptr;
  
              if(!m_serverToJoin.isEmpty())
              {
                  serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin)));
                  qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching with server" << m_serverToJoin;
              }
              else
              {
                  qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching";
              }
  
              launch(inst, true, nullptr, serverToJoin);
              return;
          }
      }
      if(!m_mainWindow)
      {
          // normal main window
          showMainWindow(false);
          qDebug() << "<> Main window shown.";
      }
      if(!m_zipToImport.isEmpty())
      {
          qDebug() << "<> Importing instance from zip:" << m_zipToImport;
          m_mainWindow->droppedURLs({ m_zipToImport });
      }
  }
  
  void MultiMC::showFatalErrorMessage(const QString& title, const QString& content)
  {
      m_status = MultiMC::Failed;
      auto dialog = CustomMessageBox::selectable(nullptr, title, content, QMessageBox::Critical);
      dialog->exec();
  }
  
  MultiMC::~MultiMC()
  {
      // kill the other globals.
      Env::dispose();
  
      // Shut down logger by setting the logger function to nothing
      qInstallMessageHandler(nullptr);
  
  #if defined Q_OS_WIN32
      // Detach from Windows console
      if(consoleAttached)
      {
          fclose(stdout);
          fclose(stdin);
          fclose(stderr);
          FreeConsole();
      }
  #endif
  }
  
  void MultiMC::messageReceived(const QString& message)
  {
      if(status() != Initialized)
      {
          qDebug() << "Received message" << message << "while still initializing. It will be ignored.";
          return;
      }
  
      QString command = message.section(' ', 0, 0);
  
      if(command == "activate")
      {
          showMainWindow();
      }
      else if(command == "import")
      {
          QString arg = message.section(' ', 1);
          if(arg.isEmpty())
          {
              qWarning() << "Received" << command << "message without a zip path/URL.";
              return;
          }
          m_mainWindow->droppedURLs({ QUrl(arg) });
      }
      else if(command == "launch")
      {
          QString arg = message.section(' ', 1);
          if(arg.isEmpty())
          {
              qWarning() << "Received" << command << "message without an instance ID.";
              return;
          }
          auto inst = instances()->getInstanceById(arg);
          if(inst)
          {
              launch(inst, true, nullptr);
          }
      }
      else if(command == "launch-with-server")
      {
          QString instanceID = message.section(' ', 1, 1);
          QString serverToJoin = message.section(' ', 2, 2);
          if(instanceID.isEmpty())
          {
              qWarning() << "Received" << command << "message without an instance ID.";
              return;
          }
          if(serverToJoin.isEmpty())
          {
              qWarning() << "Received" << command << "message without a server to join.";
              return;
          }
          auto inst = instances()->getInstanceById(instanceID);
          if(inst)
          {
              launch(
                      inst,
                      true,
                      nullptr,
                      std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(serverToJoin))
              );
          }
      }
      else
      {
          qWarning() << "Received invalid message" << message;
      }
  }
  
  void MultiMC::analyticsSettingChanged(const Setting&, QVariant value)
  {
      if(!m_analytics)
          return;
      bool enabled = value.toBool();
      if(enabled)
      {
          qDebug() << "Analytics enabled by user.";
      }
      else
      {
          qDebug() << "Analytics disabled by user.";
      }
      m_analytics->enable(enabled);
  }
  
  std::shared_ptr<TranslationsModel> MultiMC::translations()
  {
      return m_translations;
  }
  
  std::shared_ptr<JavaInstallList> MultiMC::javalist()
  {
      if (!m_javalist)
      {
          m_javalist.reset(new JavaInstallList());
      }
      return m_javalist;
  }
  
  std::vector<ITheme *> MultiMC::getValidApplicationThemes()
  {
      std::vector<ITheme *> ret;
      auto iter = m_themes.cbegin();
      while (iter != m_themes.cend())
      {
          ret.push_back((*iter).second.get());
          iter++;
      }
      return ret;
  }
  
  void MultiMC::setApplicationTheme(const QString& name, bool initial)
  {
      auto systemPalette = qApp->palette();
      auto themeIter = m_themes.find(name);
      if(themeIter != m_themes.end())
      {
          auto & theme = (*themeIter).second;
          theme->apply(initial);
      }
      else
      {
          qWarning() << "Tried to set invalid theme:" << name;
      }
  }
  
  void MultiMC::setIconTheme(const QString& name)
  {
      XdgIcon::setThemeName(name);
  }
  
  QIcon MultiMC::getThemedIcon(const QString& name)
  {
      return XdgIcon::fromTheme(name);
  }
  
  bool MultiMC::openJsonEditor(const QString &filename)
  {
      const QString file = QDir::current().absoluteFilePath(filename);
      if (m_settings->get("JsonEditor").toString().isEmpty())
      {
          return DesktopServices::openUrl(QUrl::fromLocalFile(file));
      }
      else
      {
          //return DesktopServices::openFile(m_settings->get("JsonEditor").toString(), file);
          return DesktopServices::run(m_settings->get("JsonEditor").toString(), {file});
      }
  }
  
  bool MultiMC::launch(
          InstancePtr instance,
          bool online,
          BaseProfilerFactory *profiler,
          MinecraftServerTargetPtr serverToJoin
  ) {
      if(m_updateRunning)
      {
          qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
      }
      else if(instance->canLaunch())
      {
          auto & extras = m_instanceExtras[instance->id()];
          auto & window = extras.window;
          if(window)
          {
              if(!window->saveAll())
              {
                  return false;
              }
          }
          auto & controller = extras.controller;
          controller.reset(new LaunchController());
          controller->setInstance(instance);
          controller->setOnline(online);
          controller->setProfiler(profiler);
          controller->setServerToJoin(serverToJoin);
          if(window)
          {
              controller->setParentWidget(window);
          }
          else if(m_mainWindow)
          {
              controller->setParentWidget(m_mainWindow);
          }
          connect(controller.get(), &LaunchController::succeeded, this, &MultiMC::controllerSucceeded);
          connect(controller.get(), &LaunchController::failed, this, &MultiMC::controllerFailed);
          addRunningInstance();
          controller->start();
          return true;
      }
      else if (instance->isRunning())
      {
          showInstanceWindow(instance, "console");
          return true;
      }
      else if (instance->canEdit())
      {
          showInstanceWindow(instance);
          return true;
      }
      return false;
  }
  
  bool MultiMC::kill(InstancePtr instance)
  {
      if (!instance->isRunning())
      {
          qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
          return false;
      }
      auto & extras = m_instanceExtras[instance->id()];
      // NOTE: copy of the shared pointer keeps it alive
      auto controller = extras.controller;
      if(controller)
      {
          return controller->abort();
      }
      return true;
  }
  
  void MultiMC::addRunningInstance()
  {
      m_runningInstances ++;
      if(m_runningInstances == 1)
      {
          emit updateAllowedChanged(false);
      }
  }
  
  void MultiMC::subRunningInstance()
  {
      if(m_runningInstances == 0)
      {
          qCritical() << "Something went really wrong and we now have less than 0 running instances... WTF";
          return;
      }
      m_runningInstances --;
      if(m_runningInstances == 0)
      {
          emit updateAllowedChanged(true);
      }
  }
  
  bool MultiMC::shouldExitNow() const
  {
      return m_runningInstances == 0 && m_openWindows == 0;
  }
  
  bool MultiMC::updatesAreAllowed()
  {
      return m_runningInstances == 0;
  }
  
  void MultiMC::updateIsRunning(bool running)
  {
      m_updateRunning = running;
  }
  
  
  void MultiMC::controllerSucceeded()
  {
      auto controller = qobject_cast<LaunchController *>(QObject::sender());
      if(!controller)
          return;
      auto id = controller->id();
      auto & extras = m_instanceExtras[id];
  
      // on success, do...
      if (controller->instance()->settings()->get("AutoCloseConsole").toBool())
      {
          if(extras.window)
          {
              extras.window->close();
          }
      }
      extras.controller.reset();
      subRunningInstance();
  
      // quit when there are no more windows.
      if(shouldExitNow())
      {
          m_status = Status::Succeeded;
          exit(0);
      }
  }
  
  void MultiMC::controllerFailed(const QString& error)
  {
      Q_UNUSED(error);
      auto controller = qobject_cast<LaunchController *>(QObject::sender());
      if(!controller)
          return;
      auto id = controller->id();
      auto & extras = m_instanceExtras[id];
  
      // on failure, do... nothing
      extras.controller.reset();
      subRunningInstance();
  
      // quit when there are no more windows.
      if(shouldExitNow())
      {
          m_status = Status::Failed;
          exit(1);
      }
  }
  
  void MultiMC::ShowGlobalSettings(class QWidget* parent, QString open_page)
  {
      if(!m_globalSettingsProvider) {
          return;
      }
      emit globalSettingsAboutToOpen();
      {
          SettingsObject::Lock lock(MMC->settings());
          PageDialog dlg(m_globalSettingsProvider.get(), open_page, parent);
          dlg.exec();
      }
      emit globalSettingsClosed();
  }
  
  MainWindow* MultiMC::showMainWindow(bool minimized)
  {
      if(m_mainWindow)
      {
          m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized);
          m_mainWindow->raise();
          m_mainWindow->activateWindow();
      }
      else
      {
          m_mainWindow = new MainWindow();
          m_mainWindow->restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray()));
          m_mainWindow->restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray()));
          if(minimized)
          {
              m_mainWindow->showMinimized();
          }
          else
          {
              m_mainWindow->show();
          }
  
          m_mainWindow->checkInstancePathForProblems();
          connect(this, &MultiMC::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged);
          connect(m_mainWindow, &MainWindow::isClosing, this, &MultiMC::on_windowClose);
          m_openWindows++;
      }
      // FIXME: move this somewhere else...
      if(m_analytics)
      {
          auto windowSize = m_mainWindow->size();
          auto sizeString = QString("%1x%2").arg(windowSize.width()).arg(windowSize.height());
          qDebug() << "Viewport size" << sizeString;
          m_analytics->setViewportSize(sizeString);
          /*
           * cm1 = java min heap [MB]
           * cm2 = java max heap [MB]
           * cm3 = system RAM [MB]
           *
           * cd1 = java version
           * cd2 = java architecture
           * cd3 = system architecture
           * cd4 = CPU architecture
           */
          QVariantMap customValues;
          int min = m_settings->get("MinMemAlloc").toInt();
          int max = m_settings->get("MaxMemAlloc").toInt();
          if(min < max)
          {
              customValues["cm1"] = min;
              customValues["cm2"] = max;
          }
          else
          {
              customValues["cm1"] = max;
              customValues["cm2"] = min;
          }
  
          constexpr uint64_t Mega = 1024ull * 1024ull;
          int ramSize = int(Sys::getSystemRam() / Mega);
          qDebug() << "RAM size is" << ramSize << "MB";
          customValues["cm3"] = ramSize;
  
          customValues["cd1"] = m_settings->get("JavaVersion");
          customValues["cd2"] = m_settings->get("JavaArchitecture");
          customValues["cd3"] = Sys::isSystem64bit() ? "64":"32";
          customValues["cd4"] = Sys::isCPU64bit() ? "64":"32";
          auto kernelInfo = Sys::getKernelInfo();
          customValues["cd5"] = kernelInfo.kernelName;
          customValues["cd6"] = kernelInfo.kernelVersion;
          auto distInfo = Sys::getDistributionInfo();
          if(!distInfo.distributionName.isEmpty())
          {
              customValues["cd7"] = distInfo.distributionName;
          }
          if(!distInfo.distributionVersion.isEmpty())
          {
              customValues["cd8"] = distInfo.distributionVersion;
          }
          m_analytics->sendScreenView("Main Window", customValues);
      }
      return m_mainWindow;
  }
  
  InstanceWindow *MultiMC::showInstanceWindow(InstancePtr instance, QString page)
  {
      if(!instance)
          return nullptr;
      auto id = instance->id();
      auto & extras = m_instanceExtras[id];
      auto & window = extras.window;
  
      if(window)
      {
          window->raise();
          window->activateWindow();
      }
      else
      {
          window = new InstanceWindow(instance);
          m_openWindows ++;
          connect(window, &InstanceWindow::isClosing, this, &MultiMC::on_windowClose);
      }
      if(!page.isEmpty())
      {
          window->selectPage(page);
      }
      if(extras.controller)
      {
          extras.controller->setParentWidget(window);
      }
      return window;
  }
  
  void MultiMC::on_windowClose()
  {
      m_openWindows--;
      auto instWindow = qobject_cast<InstanceWindow *>(QObject::sender());
      if(instWindow)
      {
          auto & extras = m_instanceExtras[instWindow->instanceId()];
          extras.window = nullptr;
          if(extras.controller)
          {
              extras.controller->setParentWidget(m_mainWindow);
          }
      }
      auto mainWindow = qobject_cast<MainWindow *>(QObject::sender());
      if(mainWindow)
      {
          m_mainWindow = nullptr;
      }
      // quit when there are no more windows.
      if(shouldExitNow())
      {
          exit(0);
      }
  }
  