0ee8f90d40
Currently it doesn't show it anywhere in the UI. That's next.
517 lines
14 KiB
C++
517 lines
14 KiB
C++
|
|
#include "MultiMC.h"
|
|
#include <iostream>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QNetworkAccessManager>
|
|
#include <QTranslator>
|
|
#include <QLibraryInfo>
|
|
#include <QMessageBox>
|
|
#include <QStringList>
|
|
|
|
#include "gui/MainWindow.h"
|
|
#include "gui/dialogs/VersionSelectDialog.h"
|
|
#include "logic/lists/InstanceList.h"
|
|
#include "logic/auth/MojangAccountList.h"
|
|
#include "logic/lists/IconList.h"
|
|
#include "logic/lists/LwjglVersionList.h"
|
|
#include "logic/lists/MinecraftVersionList.h"
|
|
#include "logic/lists/ForgeVersionList.h"
|
|
|
|
#include "logic/news/NewsChecker.h"
|
|
|
|
#include "logic/InstanceLauncher.h"
|
|
#include "logic/net/HttpMetaCache.h"
|
|
|
|
#include "logic/JavaUtils.h"
|
|
|
|
#include "logic/updater/UpdateChecker.h"
|
|
|
|
#include "pathutils.h"
|
|
#include "cmdutils.h"
|
|
#include <inisettingsobject.h>
|
|
#include <setting.h>
|
|
#include "logger/QsLog.h"
|
|
#include <logger/QsLogDest.h>
|
|
|
|
#include "config.h"
|
|
using namespace Util::Commandline;
|
|
|
|
MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv),
|
|
m_version{VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_CHANNEL, VERSION_BUILD_TYPE}
|
|
{
|
|
setOrganizationName("MultiMC");
|
|
setApplicationName("MultiMC5");
|
|
|
|
initTranslations();
|
|
|
|
// Don't quit on hiding the last window
|
|
this->setQuitOnLastWindowClosed(false);
|
|
|
|
// Print app header
|
|
std::cout << "MultiMC 5" << std::endl;
|
|
std::cout << "(c) 2013 MultiMC Contributors" << std::endl << std::endl;
|
|
|
|
// 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", applicationDirPath());
|
|
parser.addShortOpt("dir", 'd');
|
|
parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of "
|
|
"the binary location (use '.' for current)");
|
|
// --update
|
|
parser.addOption("update");
|
|
parser.addShortOpt("update", 'u');
|
|
parser.addDocumentation("update", "replaces the given file with the running executable",
|
|
"<path>");
|
|
// --quietupdate
|
|
parser.addSwitch("quietupdate");
|
|
parser.addShortOpt("quietupdate", 'U');
|
|
parser.addDocumentation("quietupdate",
|
|
"doesn't restart MultiMC after installing updates");
|
|
// WARNING: disabled until further notice
|
|
/*
|
|
// --launch
|
|
parser.addOption("launch");
|
|
parser.addShortOpt("launch", 'l');
|
|
parser.addDocumentation("launch", "tries to launch the given instance", "<inst>");
|
|
*/
|
|
// parse the arguments
|
|
try
|
|
{
|
|
args = parser.parse(arguments());
|
|
}
|
|
catch (ParsingError e)
|
|
{
|
|
std::cerr << "CommandLineError: " << e.what() << std::endl;
|
|
std::cerr << "Try '%1 -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 " << VERSION_STR << std::endl;
|
|
std::cout << "Git " << GIT_COMMIT << std::endl;
|
|
m_status = MultiMC::Succeeded;
|
|
return;
|
|
}
|
|
|
|
// update
|
|
// Note: cwd is always the current executable path!
|
|
if (!args["update"].isNull())
|
|
{
|
|
std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString())
|
|
<< std::endl;
|
|
QString cwd = QDir::currentPath();
|
|
QDir::setCurrent(applicationDirPath());
|
|
QFile file(applicationFilePath());
|
|
file.copy(args["update"].toString());
|
|
if (args["quietupdate"].toBool())
|
|
{
|
|
m_status = MultiMC::Succeeded;
|
|
return;
|
|
}
|
|
QDir::setCurrent(cwd);
|
|
}
|
|
}
|
|
|
|
// change directory
|
|
QDir::setCurrent(args["dir"].toString());
|
|
|
|
// init the logger
|
|
initLogger();
|
|
|
|
// load settings
|
|
initGlobalSettings();
|
|
|
|
// initialize the updater
|
|
m_updateChecker.reset(new UpdateChecker());
|
|
|
|
// initialize the news checker
|
|
m_newsChecker.reset(new NewsChecker(NEWS_RSS_URL));
|
|
|
|
// and instances
|
|
auto InstDirSetting = m_settings->getSetting("InstanceDir");
|
|
m_instances.reset(new InstanceList(InstDirSetting->get().toString(), this));
|
|
QLOG_INFO() << "Loading Instances...";
|
|
m_instances->loadList();
|
|
connect(InstDirSetting, SIGNAL(settingChanged(const Setting &, QVariant)),
|
|
m_instances.get(), SLOT(on_InstFolderChanged(const Setting &, QVariant)));
|
|
|
|
// and accounts
|
|
m_accounts.reset(new MojangAccountList(this));
|
|
QLOG_INFO() << "Loading accounts...";
|
|
m_accounts->setListFilePath("accounts.json", true);
|
|
m_accounts->loadList();
|
|
|
|
// init the http meta cache
|
|
initHttpMetaCache();
|
|
|
|
// set up a basic autodetected proxy (system default)
|
|
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
|
|
|
QLOG_INFO() << "Detecting system proxy settings...";
|
|
auto proxies = QNetworkProxyFactory::systemProxyForQuery();
|
|
if (proxies.size() == 1 && proxies[0].type() == QNetworkProxy::NoProxy)
|
|
{
|
|
QLOG_INFO() << "No proxy found.";
|
|
}
|
|
else for (auto proxy : proxies)
|
|
{
|
|
QString proxyDesc;
|
|
if (proxy.type() == QNetworkProxy::NoProxy)
|
|
{
|
|
QLOG_INFO() << "Using no proxy is an option!";
|
|
continue;
|
|
}
|
|
switch (proxy.type())
|
|
{
|
|
case QNetworkProxy::DefaultProxy:
|
|
proxyDesc = "Default proxy: ";
|
|
break;
|
|
case QNetworkProxy::Socks5Proxy:
|
|
proxyDesc = "Socks5 proxy: ";
|
|
break;
|
|
case QNetworkProxy::HttpProxy:
|
|
proxyDesc = "HTTP proxy: ";
|
|
break;
|
|
case QNetworkProxy::HttpCachingProxy:
|
|
proxyDesc = "HTTP caching: ";
|
|
break;
|
|
case QNetworkProxy::FtpCachingProxy:
|
|
proxyDesc = "FTP caching: ";
|
|
break;
|
|
default:
|
|
proxyDesc = "DERP proxy: ";
|
|
break;
|
|
}
|
|
proxyDesc += QString("%3@%1:%2 pass %4")
|
|
.arg(proxy.hostName())
|
|
.arg(proxy.port())
|
|
.arg(proxy.user())
|
|
.arg(proxy.password());
|
|
QLOG_INFO() << proxyDesc;
|
|
}
|
|
|
|
// create the global network manager
|
|
m_qnam.reset(new QNetworkAccessManager(this));
|
|
|
|
// launch instance, if that's what should be done
|
|
// WARNING: disabled until further notice
|
|
/*
|
|
if (!args["launch"].isNull())
|
|
{
|
|
if (InstanceLauncher(args["launch"].toString()).launch())
|
|
m_status = MultiMC::Succeeded;
|
|
else
|
|
m_status = MultiMC::Failed;
|
|
return;
|
|
}
|
|
*/
|
|
m_status = MultiMC::Initialized;
|
|
}
|
|
|
|
MultiMC::~MultiMC()
|
|
{
|
|
if (m_mmc_translator)
|
|
{
|
|
removeTranslator(m_mmc_translator.get());
|
|
}
|
|
if (m_qt_translator)
|
|
{
|
|
removeTranslator(m_qt_translator.get());
|
|
}
|
|
}
|
|
|
|
void MultiMC::initTranslations()
|
|
{
|
|
m_qt_translator.reset(new QTranslator());
|
|
if (m_qt_translator->load("qt_" + QLocale::system().name(),
|
|
QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
|
|
{
|
|
std::cout << "Loading Qt Language File for "
|
|
<< QLocale::system().name().toLocal8Bit().constData() << "...";
|
|
if (!installTranslator(m_qt_translator.get()))
|
|
{
|
|
std::cout << " failed.";
|
|
m_qt_translator.reset();
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
else
|
|
{
|
|
m_qt_translator.reset();
|
|
}
|
|
|
|
m_mmc_translator.reset(new QTranslator());
|
|
if (m_mmc_translator->load("mmc_" + QLocale::system().name(),
|
|
QDir("translations").absolutePath()))
|
|
{
|
|
std::cout << "Loading MMC Language File for "
|
|
<< QLocale::system().name().toLocal8Bit().constData() << "...";
|
|
if (!installTranslator(m_mmc_translator.get()))
|
|
{
|
|
std::cout << " failed.";
|
|
m_mmc_translator.reset();
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
else
|
|
{
|
|
m_mmc_translator.reset();
|
|
}
|
|
}
|
|
|
|
void MultiMC::initLogger()
|
|
{
|
|
// init the logging mechanism
|
|
QsLogging::Logger &logger = QsLogging::Logger::instance();
|
|
logger.setLoggingLevel(QsLogging::TraceLevel);
|
|
m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination("MultiMC.log");
|
|
m_debugDestination = QsLogging::DestinationFactory::MakeDebugOutputDestination();
|
|
logger.addDestination(m_fileDestination.get());
|
|
logger.addDestination(m_debugDestination.get());
|
|
// log all the things
|
|
logger.setLoggingLevel(QsLogging::TraceLevel);
|
|
}
|
|
|
|
void MultiMC::initGlobalSettings()
|
|
{
|
|
m_settings.reset(new INISettingsObject("multimc.cfg", this));
|
|
// Updates
|
|
m_settings->registerSetting(new Setting("UseDevBuilds", false));
|
|
m_settings->registerSetting(new Setting("AutoUpdate", true));
|
|
|
|
// Folders
|
|
m_settings->registerSetting(new Setting("InstanceDir", "instances"));
|
|
m_settings->registerSetting(new Setting("CentralModsDir", "mods"));
|
|
m_settings->registerSetting(new Setting("LWJGLDir", "lwjgl"));
|
|
|
|
// Console
|
|
m_settings->registerSetting(new Setting("ShowConsole", true));
|
|
m_settings->registerSetting(new Setting("AutoCloseConsole", true));
|
|
|
|
// Console Colors
|
|
// m_settings->registerSetting(new Setting("SysMessageColor", QColor(Qt::blue)));
|
|
// m_settings->registerSetting(new Setting("StdOutColor", QColor(Qt::black)));
|
|
// m_settings->registerSetting(new Setting("StdErrColor", QColor(Qt::red)));
|
|
|
|
// Window Size
|
|
m_settings->registerSetting(new Setting("LaunchMaximized", false));
|
|
m_settings->registerSetting(new Setting("MinecraftWinWidth", 854));
|
|
m_settings->registerSetting(new Setting("MinecraftWinHeight", 480));
|
|
|
|
// Auto login
|
|
m_settings->registerSetting(new Setting("AutoLogin", false));
|
|
|
|
// Memory
|
|
m_settings->registerSetting(new Setting("MinMemAlloc", 512));
|
|
m_settings->registerSetting(new Setting("MaxMemAlloc", 1024));
|
|
m_settings->registerSetting(new Setting("PermGen", 64));
|
|
|
|
// Java Settings
|
|
m_settings->registerSetting(new Setting("JavaPath", ""));
|
|
m_settings->registerSetting(new Setting("LastHostname", ""));
|
|
m_settings->registerSetting(new Setting("JvmArgs", ""));
|
|
|
|
// Custom Commands
|
|
m_settings->registerSetting(new Setting("PreLaunchCommand", ""));
|
|
m_settings->registerSetting(new Setting("PostExitCommand", ""));
|
|
|
|
// The cat
|
|
m_settings->registerSetting(new Setting("TheCat", false));
|
|
|
|
|
|
m_settings->registerSetting(new Setting("InstSortMode", "Name"));
|
|
m_settings->registerSetting(new Setting("SelectedInstance", QString()));
|
|
|
|
// Persistent value for the client ID
|
|
m_settings->registerSetting(new Setting("YggdrasilClientToken", ""));
|
|
QString currentYggID = m_settings->get("YggdrasilClientToken").toString();
|
|
if (currentYggID.isEmpty())
|
|
{
|
|
QUuid uuid = QUuid::createUuid();
|
|
m_settings->set("YggdrasilClientToken", uuid.toString());
|
|
}
|
|
|
|
// Window state and geometry
|
|
m_settings->registerSetting(new Setting("MainWindowState", ""));
|
|
m_settings->registerSetting(new Setting("MainWindowGeometry", ""));
|
|
|
|
m_settings->registerSetting(new Setting("ConsoleWindowState", ""));
|
|
m_settings->registerSetting(new Setting("ConsoleWindowGeometry", ""));
|
|
}
|
|
|
|
void MultiMC::initHttpMetaCache()
|
|
{
|
|
m_metacache.reset(new HttpMetaCache("metacache"));
|
|
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
|
|
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
|
|
m_metacache->addBase("versions", QDir("versions").absolutePath());
|
|
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
|
|
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
|
|
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
|
|
m_metacache->Load();
|
|
}
|
|
|
|
std::shared_ptr<IconList> MultiMC::icons()
|
|
{
|
|
if (!m_icons)
|
|
{
|
|
m_icons.reset(new IconList);
|
|
}
|
|
return m_icons;
|
|
}
|
|
|
|
std::shared_ptr<LWJGLVersionList> MultiMC::lwjgllist()
|
|
{
|
|
if (!m_lwjgllist)
|
|
{
|
|
m_lwjgllist.reset(new LWJGLVersionList());
|
|
}
|
|
return m_lwjgllist;
|
|
}
|
|
|
|
std::shared_ptr<ForgeVersionList> MultiMC::forgelist()
|
|
{
|
|
if (!m_forgelist)
|
|
{
|
|
m_forgelist.reset(new ForgeVersionList());
|
|
}
|
|
return m_forgelist;
|
|
}
|
|
|
|
std::shared_ptr<MinecraftVersionList> MultiMC::minecraftlist()
|
|
{
|
|
if (!m_minecraftlist)
|
|
{
|
|
m_minecraftlist.reset(new MinecraftVersionList());
|
|
}
|
|
return m_minecraftlist;
|
|
}
|
|
|
|
std::shared_ptr<JavaVersionList> MultiMC::javalist()
|
|
{
|
|
if (!m_javalist)
|
|
{
|
|
m_javalist.reset(new JavaVersionList());
|
|
}
|
|
return m_javalist;
|
|
}
|
|
|
|
#ifdef WINDOWS
|
|
#define UPDATER_BIN "updater.exe"
|
|
#elif LINUX
|
|
#define UPDATER_BIN "updater"
|
|
#elif OSX
|
|
#define UPDATER_BIN "updater"
|
|
#else
|
|
#error Unsupported operating system.
|
|
#endif
|
|
|
|
void MultiMC::installUpdates(const QString& updateFilesDir, bool restartOnFinish)
|
|
{
|
|
QLOG_INFO() << "Installing updates.";
|
|
#if LINUX
|
|
// On Linux, the MultiMC executable file is actually in the bin folder inside the installation directory.
|
|
// This means that MultiMC's *actual* install path is the parent folder.
|
|
// We need to tell the updater to run with this directory as the install path, rather than the bin folder where the executable is.
|
|
// On other operating systems, we'll just use the path to the executable.
|
|
QString appDir = QFileInfo(MMC->applicationDirPath()).dir().path();
|
|
|
|
// On Linux, we also need to set the finish command to the launch script, rather than the binary.
|
|
QString finishCmd = PathCombine(appDir, "MultiMC");
|
|
#else
|
|
QString appDir = MMC->applicationDirPath();
|
|
QString finishCmd = MMC->applicationFilePath();
|
|
#endif
|
|
|
|
// Build the command we'll use to run the updater.
|
|
// Note, the above comment about the app dir path on Linux is irrelevant here because the updater binary is always in the
|
|
// same folder as the main binary.
|
|
QString updaterBinary = PathCombine(MMC->applicationDirPath(), UPDATER_BIN);
|
|
QStringList args;
|
|
// ./updater --install-dir $INSTALL_DIR --package-dir $UPDATEFILES_DIR --script $UPDATEFILES_DIR/file_list.xml --wait $PID --mode main
|
|
args << "--install-dir" << appDir;
|
|
args << "--package-dir" << updateFilesDir;
|
|
args << "--script" << PathCombine(updateFilesDir, "file_list.xml");
|
|
args << "--wait" << QString::number(MMC->applicationPid());
|
|
|
|
if (restartOnFinish)
|
|
args << "--finish-cmd" << finishCmd;
|
|
|
|
QLOG_INFO() << "Running updater with command" << updaterBinary << args.join(" ");
|
|
|
|
QProcess::startDetached(updaterBinary, args);
|
|
|
|
// Now that we've started the updater, quit MultiMC.
|
|
MMC->quit();
|
|
}
|
|
|
|
void MultiMC::setUpdateOnExit(const QString& updateFilesDir)
|
|
{
|
|
m_updateOnExitPath = updateFilesDir;
|
|
}
|
|
|
|
QString MultiMC::getExitUpdatePath() const
|
|
{
|
|
return m_updateOnExitPath;
|
|
}
|
|
|
|
int main_gui(MultiMC &app)
|
|
{
|
|
// show main window
|
|
MainWindow mainWin;
|
|
mainWin.restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray()));
|
|
mainWin.restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray()));
|
|
mainWin.show();
|
|
mainWin.checkSetDefaultJava();
|
|
auto exitCode = app.exec();
|
|
|
|
// Update if necessary.
|
|
if (!app.getExitUpdatePath().isEmpty())
|
|
app.installUpdates(app.getExitUpdatePath(), false);
|
|
|
|
return exitCode;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
// initialize Qt
|
|
MultiMC app(argc, argv);
|
|
|
|
switch (app.status())
|
|
{
|
|
case MultiMC::Initialized:
|
|
return main_gui(app);
|
|
case MultiMC::Failed:
|
|
return 1;
|
|
case MultiMC::Succeeded:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#include "MultiMC.moc"
|