mirror of
https://github.com/MultiMC/MultiMC5.git
synced 2024-10-06 06:50:16 +00:00
NOISSUE Some work on JRE downloading
This commit is contained in:
parent
9f3c2921f2
commit
afd19099ef
@ -180,3 +180,9 @@ add_subdirectory(buildconfig)
|
|||||||
|
|
||||||
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
|
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
|
||||||
add_subdirectory(launcher)
|
add_subdirectory(launcher)
|
||||||
|
|
||||||
|
option(BUILD_TOOLS "Build miscellaneous tools" OFF)
|
||||||
|
|
||||||
|
if(BUILD_TOOLS)
|
||||||
|
add_subdirectory(tools)
|
||||||
|
endif()
|
||||||
|
@ -77,6 +77,8 @@
|
|||||||
#include <sys.h>
|
#include <sys.h>
|
||||||
|
|
||||||
#include <Secrets.h>
|
#include <Secrets.h>
|
||||||
|
#include "ApplicationSettings.h"
|
||||||
|
#include "jreclient/JREClientDialog.h"
|
||||||
|
|
||||||
|
|
||||||
#define STRINGIFY(x) #x
|
#define STRINGIFY(x) #x
|
||||||
@ -562,162 +564,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
}
|
}
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
// Initialize application settings
|
initializeSettings();
|
||||||
{
|
|
||||||
m_settings.reset(new INISettingsObject(BuildConfig.LAUNCHER_CONFIGFILE, this));
|
|
||||||
// Updates
|
|
||||||
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("ShowGlobalGameTime", true);
|
|
||||||
m_settings->registerSetting("RecordGameTime", true);
|
|
||||||
m_settings->registerSetting("ShowGameTimeHours", false);
|
|
||||||
|
|
||||||
// Minecraft launch method
|
|
||||||
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
|
|
||||||
|
|
||||||
// Minecraft offline player name
|
|
||||||
m_settings->registerSetting("LastOfflinePlayerName", "");
|
|
||||||
|
|
||||||
// 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<LauncherPage>();
|
|
||||||
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
|
#ifndef QT_NO_ACCESSIBILITY
|
||||||
QAccessible::installFactory(groupViewAccessibleFactory);
|
QAccessible::installFactory(groupViewAccessibleFactory);
|
||||||
@ -884,44 +731,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize analytics
|
// Initialize analytics
|
||||||
[this]()
|
initializeAnalytics();
|
||||||
{
|
|
||||||
const int analyticsVersion = 2;
|
|
||||||
if(BuildConfig.ANALYTICS_ID.isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto analyticsSetting = m_settings->getSetting("Analytics");
|
|
||||||
connect(analyticsSetting.get(), &Setting::SettingChanged, this, &Application::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);
|
|
||||||
// FIXME: the ganalytics library has no idea about our fancy shared pointers...
|
|
||||||
m_analytics->setNetworkAccessManager(network().get());
|
|
||||||
|
|
||||||
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())
|
if(createSetupWizard())
|
||||||
{
|
{
|
||||||
@ -930,6 +740,68 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
performMainStartupAction();
|
performMainStartupAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::initializeAnalytics()
|
||||||
|
{
|
||||||
|
const int analyticsVersion = 2;
|
||||||
|
if(BuildConfig.ANALYTICS_ID.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto analyticsSetting = m_settings->getSetting("Analytics");
|
||||||
|
connect(analyticsSetting.get(), &Setting::SettingChanged, this, &Application::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);
|
||||||
|
// FIXME: the ganalytics library has no idea about our fancy shared pointers...
|
||||||
|
m_analytics->setNetworkAccessManager(network().get());
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Application::initializeSettings()
|
||||||
|
{
|
||||||
|
// Initialize application settings
|
||||||
|
m_settings.reset(new ApplicationSettings(BuildConfig.LAUNCHER_CONFIGFILE, this));
|
||||||
|
|
||||||
|
// Init page provider
|
||||||
|
{
|
||||||
|
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
|
||||||
|
m_globalSettingsProvider->addPage<LauncherPage>();
|
||||||
|
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.";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Application::createSetupWizard()
|
bool Application::createSetupWizard()
|
||||||
{
|
{
|
||||||
bool javaRequired = [&]()
|
bool javaRequired = [&]()
|
||||||
@ -1406,6 +1278,13 @@ void Application::ShowGlobalSettings(class QWidget* parent, QString open_page)
|
|||||||
emit globalSettingsClosed();
|
emit globalSettingsClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::ShowJREs(class QWidget* parent)
|
||||||
|
{
|
||||||
|
JREClientDialog dlg(parent);
|
||||||
|
dlg.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
MainWindow* Application::showMainWindow(bool minimized)
|
MainWindow* Application::showMainWindow(bool minimized)
|
||||||
{
|
{
|
||||||
if(m_mainWindow)
|
if(m_mainWindow)
|
||||||
|
@ -142,6 +142,7 @@ public:
|
|||||||
bool updatesAreAllowed();
|
bool updatesAreAllowed();
|
||||||
|
|
||||||
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
|
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
|
||||||
|
void ShowJREs(class QWidget * parent);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateAllowedChanged(bool status);
|
void updateAllowedChanged(bool status);
|
||||||
@ -168,6 +169,8 @@ private slots:
|
|||||||
void setupWizardFinished(int status);
|
void setupWizardFinished(int status);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void initializeSettings();
|
||||||
|
void initializeAnalytics();
|
||||||
bool createSetupWizard();
|
bool createSetupWizard();
|
||||||
void performMainStartupAction();
|
void performMainStartupAction();
|
||||||
|
|
||||||
|
146
launcher/ApplicationSettings.cpp
Normal file
146
launcher/ApplicationSettings.cpp
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
|
||||||
|
#include "ApplicationSettings.h"
|
||||||
|
#include <BuildConfig.h>
|
||||||
|
#include <QFontInfo>
|
||||||
|
|
||||||
|
ApplicationSettings::ApplicationSettings(const QString & path, QObject * parent) : INISettingsObject(path, parent)
|
||||||
|
{
|
||||||
|
// Updates
|
||||||
|
registerSetting("AutoUpdate", true);
|
||||||
|
|
||||||
|
// Theming
|
||||||
|
registerSetting("IconTheme", QString("multimc"));
|
||||||
|
registerSetting("ApplicationTheme", QString("system"));
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
registerSetting("ShownNotifications", QString());
|
||||||
|
|
||||||
|
// Remembered state
|
||||||
|
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(',');
|
||||||
|
|
||||||
|
registerSetting("ConsoleFont", resolvedDefaultMonospace);
|
||||||
|
registerSetting("ConsoleFontSize", defaultSize);
|
||||||
|
registerSetting("ConsoleMaxLines", 100000);
|
||||||
|
registerSetting("ConsoleOverflowStop", true);
|
||||||
|
|
||||||
|
// Folders
|
||||||
|
registerSetting("InstanceDir", "instances");
|
||||||
|
registerSetting({"CentralModsDir", "ModsDir"}, "mods");
|
||||||
|
registerSetting("IconsDir", "icons");
|
||||||
|
|
||||||
|
// Editors
|
||||||
|
registerSetting("JsonEditor", QString());
|
||||||
|
|
||||||
|
// Language
|
||||||
|
registerSetting("Language", QString());
|
||||||
|
|
||||||
|
// Console
|
||||||
|
registerSetting("ShowConsole", false);
|
||||||
|
registerSetting("AutoCloseConsole", false);
|
||||||
|
registerSetting("ShowConsoleOnError", true);
|
||||||
|
registerSetting("LogPrePostOutput", true);
|
||||||
|
|
||||||
|
// Window Size
|
||||||
|
registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false);
|
||||||
|
registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854);
|
||||||
|
registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480);
|
||||||
|
|
||||||
|
// Proxy Settings
|
||||||
|
registerSetting("ProxyType", "None");
|
||||||
|
registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1");
|
||||||
|
registerSetting("ProxyPort", 8080);
|
||||||
|
registerSetting({"ProxyUser", "ProxyUsername"}, "");
|
||||||
|
registerSetting({"ProxyPass", "ProxyPassword"}, "");
|
||||||
|
|
||||||
|
// Memory
|
||||||
|
registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
|
||||||
|
registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024);
|
||||||
|
registerSetting("PermGen", 128);
|
||||||
|
|
||||||
|
// Java Settings
|
||||||
|
registerSetting("JavaPath", "");
|
||||||
|
registerSetting("JavaTimestamp", 0);
|
||||||
|
registerSetting("JavaArchitecture", "");
|
||||||
|
registerSetting("JavaVersion", "");
|
||||||
|
registerSetting("JavaVendor", "");
|
||||||
|
registerSetting("LastHostname", "");
|
||||||
|
registerSetting("JvmArgs", "");
|
||||||
|
|
||||||
|
// Native library workarounds
|
||||||
|
registerSetting("UseNativeOpenAL", false);
|
||||||
|
registerSetting("UseNativeGLFW", false);
|
||||||
|
|
||||||
|
// Game time
|
||||||
|
registerSetting("ShowGameTime", true);
|
||||||
|
registerSetting("ShowGlobalGameTime", true);
|
||||||
|
registerSetting("RecordGameTime", true);
|
||||||
|
registerSetting("ShowGameTimeHours", false);
|
||||||
|
|
||||||
|
// Minecraft launch method
|
||||||
|
registerSetting("MCLaunchMethod", "LauncherPart");
|
||||||
|
|
||||||
|
// Minecraft offline player name
|
||||||
|
registerSetting("LastOfflinePlayerName", "");
|
||||||
|
|
||||||
|
// Wrapper command for launch
|
||||||
|
registerSetting("WrapperCommand", "");
|
||||||
|
|
||||||
|
// Custom Commands
|
||||||
|
registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, "");
|
||||||
|
registerSetting({"PostExitCommand", "PostExitCmd"}, "");
|
||||||
|
|
||||||
|
// The cat
|
||||||
|
registerSetting("TheCat", false);
|
||||||
|
|
||||||
|
registerSetting("InstSortMode", "Name");
|
||||||
|
registerSetting("SelectedInstance", QString());
|
||||||
|
|
||||||
|
// Window state and geometry
|
||||||
|
registerSetting("MainWindowState", "");
|
||||||
|
registerSetting("MainWindowGeometry", "");
|
||||||
|
|
||||||
|
registerSetting("ConsoleWindowState", "");
|
||||||
|
registerSetting("ConsoleWindowGeometry", "");
|
||||||
|
|
||||||
|
registerSetting("SettingsGeometry", "");
|
||||||
|
|
||||||
|
registerSetting("PagedGeometry", "");
|
||||||
|
|
||||||
|
registerSetting("NewInstanceGeometry", "");
|
||||||
|
|
||||||
|
registerSetting("UpdateDialogGeometry", "");
|
||||||
|
|
||||||
|
registerSetting("JREClientDialogGeometry", "");
|
||||||
|
|
||||||
|
// paste.ee API key
|
||||||
|
registerSetting("PasteEEAPIKey", "multimc");
|
||||||
|
|
||||||
|
if(!BuildConfig.ANALYTICS_ID.isEmpty())
|
||||||
|
{
|
||||||
|
// Analytics
|
||||||
|
registerSetting("Analytics", true);
|
||||||
|
registerSetting("AnalyticsSeen", 0);
|
||||||
|
registerSetting("AnalyticsClientID", QString());
|
||||||
|
}
|
||||||
|
}
|
10
launcher/ApplicationSettings.h
Normal file
10
launcher/ApplicationSettings.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
|
class ApplicationSettings: public INISettingsObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ApplicationSettings(const QString & path, QObject * parent = nullptr);
|
||||||
|
virtual ~ApplicationSettings() = default;
|
||||||
|
};
|
1
launcher/ApplicationThemes.cpp
Normal file
1
launcher/ApplicationThemes.cpp
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
1
launcher/ApplicationThemes.h
Normal file
1
launcher/ApplicationThemes.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -352,8 +352,14 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/services/SkinDelete.cpp
|
minecraft/services/SkinDelete.cpp
|
||||||
minecraft/services/SkinDelete.h
|
minecraft/services/SkinDelete.h
|
||||||
|
|
||||||
mojang/PackageManifest.h
|
mojang/ComponentsManifest.cpp
|
||||||
|
mojang/ComponentsManifest.h
|
||||||
|
mojang/PackageInstallTask.cpp
|
||||||
|
mojang/PackageInstallTask.h
|
||||||
mojang/PackageManifest.cpp
|
mojang/PackageManifest.cpp
|
||||||
|
mojang/PackageManifest.h
|
||||||
|
mojang/Path.cpp
|
||||||
|
mojang/Path.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_unit_test(GradleSpecifier
|
add_unit_test(GradleSpecifier
|
||||||
@ -568,10 +574,12 @@ SET(LAUNCHER_SOURCES
|
|||||||
# Application base
|
# Application base
|
||||||
Application.h
|
Application.h
|
||||||
Application.cpp
|
Application.cpp
|
||||||
UpdateController.cpp
|
|
||||||
UpdateController.h
|
|
||||||
ApplicationMessage.h
|
ApplicationMessage.h
|
||||||
ApplicationMessage.cpp
|
ApplicationMessage.cpp
|
||||||
|
ApplicationSettings.cpp
|
||||||
|
ApplicationSettings.h
|
||||||
|
UpdateController.cpp
|
||||||
|
UpdateController.h
|
||||||
|
|
||||||
# GUI - general utilities
|
# GUI - general utilities
|
||||||
DesktopServices.h
|
DesktopServices.h
|
||||||
@ -653,6 +661,11 @@ SET(LAUNCHER_SOURCES
|
|||||||
JavaCommon.h
|
JavaCommon.h
|
||||||
JavaCommon.cpp
|
JavaCommon.cpp
|
||||||
|
|
||||||
|
jreclient/JREClient.h
|
||||||
|
jreclient/JREClient.cpp
|
||||||
|
jreclient/JREClientDialog.h
|
||||||
|
jreclient/JREClientDialog.cpp
|
||||||
|
|
||||||
# GUI - paged dialog base
|
# GUI - paged dialog base
|
||||||
ui/pages/BasePage.h
|
ui/pages/BasePage.h
|
||||||
ui/pages/BasePageContainer.h
|
ui/pages/BasePageContainer.h
|
||||||
@ -886,6 +899,8 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/EditAccountDialog.ui
|
ui/dialogs/EditAccountDialog.ui
|
||||||
ui/dialogs/CreateShortcutDialog.ui
|
ui/dialogs/CreateShortcutDialog.ui
|
||||||
ui/dialogs/ModrinthExportDialog.ui
|
ui/dialogs/ModrinthExportDialog.ui
|
||||||
|
|
||||||
|
jreclient/JREClientDialog.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_resources(LAUNCHER_RESOURCES
|
qt_add_resources(LAUNCHER_RESOURCES
|
||||||
@ -937,6 +952,7 @@ target_link_libraries(Launcher_logic
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(Launcher_logic secrets)
|
target_link_libraries(Launcher_logic secrets)
|
||||||
|
target_include_directories(Launcher_logic PUBLIC .)
|
||||||
|
|
||||||
add_executable(${Launcher_Name} MACOSX_BUNDLE WIN32 main.cpp ${LAUNCHER_RCS})
|
add_executable(${Launcher_Name} MACOSX_BUNDLE WIN32 main.cpp ${LAUNCHER_RCS})
|
||||||
target_link_libraries(${Launcher_Name} Launcher_logic)
|
target_link_libraries(${Launcher_Name} Launcher_logic)
|
||||||
|
@ -9,7 +9,7 @@ static inline QChar getNextChar(const QString &s, int location)
|
|||||||
/// TAKEN FROM Qt, because it doesn't expose it intelligently
|
/// TAKEN FROM Qt, because it doesn't expose it intelligently
|
||||||
int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
|
int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
|
||||||
{
|
{
|
||||||
for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
|
for (int l1 = 0, l2 = 0; l1 <= s1.length() && l2 <= s2.length(); ++l1, ++l2)
|
||||||
{
|
{
|
||||||
// skip spaces, tabs and 0's
|
// skip spaces, tabs and 0's
|
||||||
QChar c1 = getNextChar(s1, l1);
|
QChar c1 = getNextChar(s1, l1);
|
||||||
|
@ -3,9 +3,19 @@
|
|||||||
|
|
||||||
bool JavaInstall::operator<(const JavaInstall &rhs)
|
bool JavaInstall::operator<(const JavaInstall &rhs)
|
||||||
{
|
{
|
||||||
|
// prefer remote
|
||||||
|
if(remote < rhs.remote) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(remote > rhs.remote) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// FIXME: make this prefer native arch
|
||||||
auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
|
auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
|
||||||
if(archCompare != 0)
|
if(archCompare != 0)
|
||||||
return archCompare < 0;
|
return archCompare < 0;
|
||||||
|
|
||||||
|
// FIXME: make this a version compare
|
||||||
if(id < rhs.id)
|
if(id < rhs.id)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
@ -19,7 +29,7 @@ bool JavaInstall::operator<(const JavaInstall &rhs)
|
|||||||
|
|
||||||
bool JavaInstall::operator==(const JavaInstall &rhs)
|
bool JavaInstall::operator==(const JavaInstall &rhs)
|
||||||
{
|
{
|
||||||
return arch == rhs.arch && id == rhs.id && path == rhs.path;
|
return arch == rhs.arch && id == rhs.id && path == rhs.path && remote == rhs.remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JavaInstall::operator>(const JavaInstall &rhs)
|
bool JavaInstall::operator>(const JavaInstall &rhs)
|
||||||
|
@ -33,6 +33,10 @@ struct JavaInstall : public BaseVersion
|
|||||||
QString arch;
|
QString arch;
|
||||||
QString path;
|
QString path;
|
||||||
bool recommended = false;
|
bool recommended = false;
|
||||||
|
|
||||||
|
bool remote = false;
|
||||||
|
QString url;
|
||||||
|
QString installRoot;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<JavaInstall> JavaInstallPtr;
|
typedef std::shared_ptr<JavaInstall> JavaInstallPtr;
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "java/JavaUtils.h"
|
#include "java/JavaUtils.h"
|
||||||
#include "MMCStrings.h"
|
#include "MMCStrings.h"
|
||||||
#include "minecraft/VersionFilterData.h"
|
#include "minecraft/VersionFilterData.h"
|
||||||
|
#include "sys.h"
|
||||||
|
|
||||||
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
|
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
|
||||||
{
|
{
|
||||||
@ -191,16 +192,72 @@ void JavaListLoadTask::javaCheckerFinished()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto kernelInfo = Sys::getKernelInfo();
|
||||||
|
|
||||||
|
// FIXME: data-drive
|
||||||
|
// FIXME: architecture being '32' or '64' is dumb
|
||||||
|
// FIXME: limited.
|
||||||
|
JavaInstallPtr remoteJava;
|
||||||
|
if(kernelInfo.kernelName == "Windows") {
|
||||||
|
remoteJava.reset(new JavaInstall());
|
||||||
|
remoteJava->id = "1.8.0_51";
|
||||||
|
if(Sys::isSystem64bit()) {
|
||||||
|
remoteJava->arch = "64";
|
||||||
|
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/ddc568a50326d2cf85765abb61e752aab191c366/manifest.json";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
remoteJava->arch = "32";
|
||||||
|
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/baa62193c2785f54d877d871d9859c67d65f08ba/manifest.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(kernelInfo.kernelName == "Linux") {
|
||||||
|
remoteJava.reset(new JavaInstall());
|
||||||
|
remoteJava->id = "1.8.0_202";
|
||||||
|
if(Sys::isSystem64bit()) {
|
||||||
|
remoteJava->arch = "64";
|
||||||
|
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/a1c15cc788f8893fba7e988eb27404772f699a84/manifest.json";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
remoteJava->arch = "32";
|
||||||
|
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/64c6a0b8e3427c6c3f3ce82729aada8b2634a955/manifest.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(kernelInfo.kernelName == "Darwin") {
|
||||||
|
if(Sys::isSystem64bit()) {
|
||||||
|
remoteJava.reset(new JavaInstall());
|
||||||
|
remoteJava->id = "1.8.0_74";
|
||||||
|
remoteJava->arch = "64";
|
||||||
|
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/341663b48a0d4e1c448dc789463fced6ba0962e1/manifest.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(remoteJava) {
|
||||||
|
remoteJava->remote = true;
|
||||||
|
remoteJava->recommended = true;
|
||||||
|
|
||||||
|
// Example: "runtimes/Windows/64/1.8.0_51"
|
||||||
|
auto rootPath = QString("runtimes/%1/%2/%3").arg(kernelInfo.kernelName, remoteJava->arch, remoteJava->id.toString());
|
||||||
|
remoteJava->installRoot = rootPath;
|
||||||
|
|
||||||
|
if(kernelInfo.kernelName == "Windows") {
|
||||||
|
remoteJava->path = rootPath + "/data/bin/javaw.exe";
|
||||||
|
}
|
||||||
|
else if (kernelInfo.kernelName == "Darwin") {
|
||||||
|
remoteJava->path = rootPath + "/data/jre.bundle/Contents/Home/bin/java";
|
||||||
|
}
|
||||||
|
else if (kernelInfo.kernelName == "Linux") {
|
||||||
|
remoteJava->path = rootPath + "/data/bin/java";
|
||||||
|
}
|
||||||
|
candidates.append(remoteJava);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QList<BaseVersionPtr> javas_bvp;
|
QList<BaseVersionPtr> javas_bvp;
|
||||||
for (auto java : candidates)
|
for (auto java : candidates)
|
||||||
{
|
{
|
||||||
//qDebug() << java->id << java->arch << " at " << java->path;
|
javas_bvp.append(java);
|
||||||
BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
|
|
||||||
|
|
||||||
if (bp_java)
|
|
||||||
{
|
|
||||||
javas_bvp.append(java);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_list->updateListData(javas_bvp);
|
m_list->updateListData(javas_bvp);
|
||||||
|
16
launcher/jreclient/JREClient.cpp
Normal file
16
launcher/jreclient/JREClient.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "JREClient.h"
|
||||||
|
|
||||||
|
JREClient::JREClient(QObject* parent) : QObject(parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void JREClient::refreshManifest() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
32
launcher/jreclient/JREClient.h
Normal file
32
launcher/jreclient/JREClient.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QObjectPtr.h>
|
||||||
|
#include <tasks/Task.h>
|
||||||
|
|
||||||
|
class JREClient: public QObject {
|
||||||
|
protected:
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
JREClient(QObject * parent);
|
||||||
|
virtual ~JREClient() = default;
|
||||||
|
|
||||||
|
using Ptr = shared_qobject_ptr<JREClient>;
|
||||||
|
|
||||||
|
void refreshManifest();
|
||||||
|
|
||||||
|
public signals:
|
||||||
|
void busyChanged(bool busy);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Task::Ptr m_manifestTask;
|
||||||
|
Task::Ptr m_runtimeTask;
|
||||||
|
};
|
33
launcher/jreclient/JREClientDialog.cpp
Normal file
33
launcher/jreclient/JREClientDialog.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "JREClientDialog.h"
|
||||||
|
#include "ui_JREClientDialog.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include "Application.h"
|
||||||
|
#include <settings/SettingsObject.h>
|
||||||
|
#include <Json.h>
|
||||||
|
|
||||||
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
|
JREClientDialog::JREClientDialog(QWidget *parent) : QDialog(parent), ui(new Ui::JREClientDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
client.reset(new JREClient(this));
|
||||||
|
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("JREClientDialogGeometry").toByteArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
JREClientDialog::~JREClientDialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void JREClientDialog::closeEvent(QCloseEvent* evt)
|
||||||
|
{
|
||||||
|
APPLICATION->settings()->set("JREClientDialogGeometry", saveGeometry().toBase64());
|
||||||
|
QDialog::closeEvent(evt);
|
||||||
|
}
|
||||||
|
|
34
launcher/jreclient/JREClientDialog.h
Normal file
34
launcher/jreclient/JREClientDialog.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
#include "JREClient.h"
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class JREClientDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class JREClientDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit JREClientDialog(QWidget *parent = 0);
|
||||||
|
~JREClientDialog();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void closeEvent(QCloseEvent * ) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::JREClientDialog *ui;
|
||||||
|
JREClient::Ptr client;
|
||||||
|
};
|
22
launcher/jreclient/JREClientDialog.ui
Normal file
22
launcher/jreclient/JREClientDialog.ui
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>JREClientDialog</class>
|
||||||
|
<widget class="QDialog" name="JREClientDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>1250</width>
|
||||||
|
<height>1269</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Java Runtime Client</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout"/>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../resources/multimc/multimc.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
113
launcher/mojang/ComponentsManifest.cpp
Normal file
113
launcher/mojang/ComponentsManifest.cpp
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ComponentsManifest.h"
|
||||||
|
|
||||||
|
#include <Json.h>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
// https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json
|
||||||
|
|
||||||
|
namespace mojang_files {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void fromJson(QJsonDocument & doc, AllPlatformsManifest & out) {
|
||||||
|
if (!doc.isObject())
|
||||||
|
{
|
||||||
|
throw JSONValidationError("file manifest is not an object");
|
||||||
|
}
|
||||||
|
QJsonObject root = doc.object();
|
||||||
|
|
||||||
|
auto platformIter = root.begin();
|
||||||
|
while (platformIter != root.end()) {
|
||||||
|
QString platformName = platformIter.key();
|
||||||
|
auto platformValue = platformIter.value();
|
||||||
|
platformIter++;
|
||||||
|
if (!platformValue.isObject())
|
||||||
|
{
|
||||||
|
throw JSONValidationError("platform entry inside manifest is not an object: " + platformName);
|
||||||
|
}
|
||||||
|
auto platformObject = platformValue.toObject();
|
||||||
|
auto componentIter = platformObject.begin();
|
||||||
|
ComponentsPlatform outPlatform;
|
||||||
|
while (componentIter != platformObject.end()) {
|
||||||
|
QString componentName = componentIter.key();
|
||||||
|
auto componentValue = componentIter.value();
|
||||||
|
componentIter++;
|
||||||
|
if (!componentValue.isArray())
|
||||||
|
{
|
||||||
|
throw JSONValidationError("component version list inside manifest is not an array: " + componentName);
|
||||||
|
}
|
||||||
|
auto versionArray = componentValue.toArray();
|
||||||
|
VersionList outVersionList;
|
||||||
|
int i = 0;
|
||||||
|
for (auto versionValue: versionArray) {
|
||||||
|
if (!versionValue.isObject())
|
||||||
|
{
|
||||||
|
throw JSONValidationError("version is not an object: " + componentName + "[" + QString::number(i) + "]");
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
auto versionObject = versionValue.toObject();
|
||||||
|
ComponentVersion outVersion;
|
||||||
|
auto availaibility = Json::requireObject(versionObject, "availability");
|
||||||
|
outVersion.availability_group = Json::requireInteger(availaibility, "group");
|
||||||
|
outVersion.availability_progress = Json::requireInteger(availaibility, "progress");
|
||||||
|
auto manifest = Json::requireObject(versionObject, "manifest");
|
||||||
|
outVersion.manifest_sha1 = Json::requireString(manifest, "sha1");
|
||||||
|
outVersion.manifest_size = Json::requireInteger(manifest, "size");
|
||||||
|
outVersion.manifest_url = Json::requireUrl(manifest, "url");
|
||||||
|
auto version = Json::requireObject(versionObject, "version");
|
||||||
|
outVersion.version_name = Json::requireString(version, "name");
|
||||||
|
outVersion.version_released = Json::requireDateTime(version, "released");
|
||||||
|
outVersionList.versions.push_back(outVersion);
|
||||||
|
}
|
||||||
|
if(outVersionList.versions.size()) {
|
||||||
|
outPlatform.components[componentName] = std::move(outVersionList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(outPlatform.components.size()) {
|
||||||
|
out.platforms[platformName] = outPlatform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.valid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AllPlatformsManifest AllPlatformsManifest::fromManifestContents(const QByteArray& contents)
|
||||||
|
{
|
||||||
|
AllPlatformsManifest out;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto doc = Json::requireDocument(contents, "AllPlatformsManifest");
|
||||||
|
fromJson(doc, out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
catch (const Exception &e)
|
||||||
|
{
|
||||||
|
qDebug() << QString("Unable to parse manifest: %1").arg(e.cause());
|
||||||
|
out.valid = false;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AllPlatformsManifest AllPlatformsManifest::fromManifestFile(const QString & filename) {
|
||||||
|
AllPlatformsManifest out;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto doc = Json::requireDocument(filename, filename);
|
||||||
|
fromJson(doc, out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
catch (const Exception &e)
|
||||||
|
{
|
||||||
|
qDebug() << QString("Unable to parse manifest file %1: %2").arg(filename, e.cause());
|
||||||
|
out.valid = false;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
55
launcher/mojang/ComponentsManifest.h
Normal file
55
launcher/mojang/ComponentsManifest.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <QString>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QUrl>
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "Path.h"
|
||||||
|
|
||||||
|
namespace mojang_files {
|
||||||
|
|
||||||
|
struct ComponentVersion {
|
||||||
|
int availability_group = 0;
|
||||||
|
int availability_progress = 0;
|
||||||
|
|
||||||
|
QString manifest_sha1;
|
||||||
|
size_t manifest_size = 0;
|
||||||
|
QUrl manifest_url;
|
||||||
|
|
||||||
|
QString version_name;
|
||||||
|
QDateTime version_released;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VersionList {
|
||||||
|
std::vector<ComponentVersion> versions;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ComponentsPlatform {
|
||||||
|
std::map<QString, VersionList> components;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AllPlatformsManifest {
|
||||||
|
static AllPlatformsManifest fromManifestFile(const QString &path);
|
||||||
|
static AllPlatformsManifest fromManifestContents(const QByteArray& contents);
|
||||||
|
|
||||||
|
explicit operator bool() const
|
||||||
|
{
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<QString, ComponentsPlatform> platforms;
|
||||||
|
bool valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
110
launcher/mojang/ComponentsManifest_test.cpp
Normal file
110
launcher/mojang/ComponentsManifest_test.cpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QTest>
|
||||||
|
#include <QDebug>
|
||||||
|
#include "TestUtil.h"
|
||||||
|
|
||||||
|
#include "mojang/ComponentsManifest.h"
|
||||||
|
|
||||||
|
using namespace mojang_files;
|
||||||
|
|
||||||
|
class ComponentsManifestTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void test_parse();
|
||||||
|
void test_parse_file();
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QByteArray basic_manifest = R"END(
|
||||||
|
{
|
||||||
|
"gamecore": {
|
||||||
|
"java-runtime-alpha": [],
|
||||||
|
"jre-legacy": [],
|
||||||
|
"minecraft-java-exe": []
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"java-runtime-alpha": [{
|
||||||
|
"availability": {
|
||||||
|
"group": 5851,
|
||||||
|
"progress": 100
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"sha1": "e968e71afd3360e5032deac19e1c14d7aa32f5bb",
|
||||||
|
"size": 81882,
|
||||||
|
"url": "https://launchermeta.mojang.com/v1/packages/e968e71afd3360e5032deac19e1c14d7aa32f5bb/manifest.json"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"name": "16.0.1.9.1",
|
||||||
|
"released": "2021-05-10T16:43:02+00:00"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"jre-legacy": [{
|
||||||
|
"availability": {
|
||||||
|
"group": 6513,
|
||||||
|
"progress": 100
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"sha1": "a1c15cc788f8893fba7e988eb27404772f699a84",
|
||||||
|
"size": 125581,
|
||||||
|
"url": "https://launchermeta.mojang.com/v1/packages/a1c15cc788f8893fba7e988eb27404772f699a84/manifest.json"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"name": "8u202",
|
||||||
|
"released": "2020-11-17T19:26:25+00:00"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"minecraft-java-exe": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)END";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComponentsManifestTest::test_parse()
|
||||||
|
{
|
||||||
|
auto manifest = AllPlatformsManifest::fromManifestContents(basic_manifest);
|
||||||
|
QVERIFY(manifest.valid == true);
|
||||||
|
QVERIFY(manifest.platforms.count("gamecore") == 0);
|
||||||
|
QVERIFY(manifest.platforms.count("linux") == 1);
|
||||||
|
/*
|
||||||
|
QVERIFY(manifest.files.size() == 1);
|
||||||
|
QVERIFY(manifest.files.count(Path("a/b.txt")));
|
||||||
|
auto &file = manifest.files[Path("a/b.txt")];
|
||||||
|
QVERIFY(file.executable == true);
|
||||||
|
QVERIFY(file.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709");
|
||||||
|
QVERIFY(file.size == 0);
|
||||||
|
QVERIFY(manifest.folders.size() == 4);
|
||||||
|
QVERIFY(manifest.folders.count(Path(".")));
|
||||||
|
QVERIFY(manifest.folders.count(Path("a")));
|
||||||
|
QVERIFY(manifest.folders.count(Path("a/b")));
|
||||||
|
QVERIFY(manifest.folders.count(Path("a/b/c")));
|
||||||
|
QVERIFY(manifest.symlinks.size() == 1);
|
||||||
|
auto symlinkPath = Path("a/b/c.txt");
|
||||||
|
QVERIFY(manifest.symlinks.count(symlinkPath));
|
||||||
|
auto &symlink = manifest.symlinks[symlinkPath];
|
||||||
|
QVERIFY(symlink == Path("../b.txt"));
|
||||||
|
QVERIFY(manifest.sources.size() == 1);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComponentsManifestTest::test_parse_file() {
|
||||||
|
auto path = QFINDTESTDATA("testdata/all.json");
|
||||||
|
auto manifest = AllPlatformsManifest::fromManifestFile(path);
|
||||||
|
QVERIFY(manifest.valid == true);
|
||||||
|
QVERIFY(manifest.platforms.count("gamecore") == 0);
|
||||||
|
QVERIFY(manifest.platforms.count("linux") == 1);
|
||||||
|
/*
|
||||||
|
QVERIFY(manifest.sources.count("c725183c757011e7ba96c83c1e86ee7e8b516a2b") == 1);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(ComponentsManifestTest)
|
||||||
|
|
||||||
|
#include "ComponentsManifest_test.moc"
|
260
launcher/mojang/PackageInstallTask.cpp
Normal file
260
launcher/mojang/PackageInstallTask.cpp
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PackageInstallTask.h"
|
||||||
|
|
||||||
|
#include <QThread>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QRunnable>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
#include <net/NetJob.h>
|
||||||
|
#include <net/ChecksumValidator.h>
|
||||||
|
|
||||||
|
#include "PackageManifest.h"
|
||||||
|
#include <FileSystem.h>
|
||||||
|
|
||||||
|
using Package = mojang_files::Package;
|
||||||
|
using UpdateOperations = mojang_files::UpdateOperations;
|
||||||
|
|
||||||
|
struct PackageInstallTaskData
|
||||||
|
{
|
||||||
|
QString root;
|
||||||
|
QString version;
|
||||||
|
QString packageURL;
|
||||||
|
Net::Mode netmode;
|
||||||
|
QFuture<Package> inspectionFuture;
|
||||||
|
QFutureWatcher<Package> inspectionWatcher;
|
||||||
|
Package inspectedPackage;
|
||||||
|
|
||||||
|
NetJob::Ptr manifestDownloadJob;
|
||||||
|
bool manifestDone = false;
|
||||||
|
Package downloadedPackage;
|
||||||
|
|
||||||
|
UpdateOperations updateOps;
|
||||||
|
NetJob::Ptr mainDownloadJob;
|
||||||
|
shared_qobject_ptr<QNetworkAccessManager> network;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class InspectFolder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InspectFolder(const QString & folder) : folder(folder) {}
|
||||||
|
Package operator()()
|
||||||
|
{
|
||||||
|
return Package::fromInspectedFolder(folder);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
QString folder;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ParsingValidator : public Net::Validator
|
||||||
|
{
|
||||||
|
public: /* con/des */
|
||||||
|
ParsingValidator(Package &package) : m_package(package)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual ~ParsingValidator() = default;
|
||||||
|
|
||||||
|
public: /* methods */
|
||||||
|
bool init(QNetworkRequest &) override
|
||||||
|
{
|
||||||
|
m_package.valid = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool write(QByteArray & data) override
|
||||||
|
{
|
||||||
|
this->data.append(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool abort() override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool validate(QNetworkReply &) override
|
||||||
|
{
|
||||||
|
m_package = Package::fromManifestContents(data);
|
||||||
|
return m_package.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private: /* data */
|
||||||
|
QByteArray data;
|
||||||
|
Package &m_package;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInstallTask::PackageInstallTask(
|
||||||
|
shared_qobject_ptr<QNetworkAccessManager> network,
|
||||||
|
Net::Mode netmode,
|
||||||
|
QString version,
|
||||||
|
QString packageURL,
|
||||||
|
QString targetPath,
|
||||||
|
QObject* parent
|
||||||
|
) : Task(parent) {
|
||||||
|
d.reset(new PackageInstallTaskData);
|
||||||
|
d->network = network;
|
||||||
|
d->netmode = netmode;
|
||||||
|
d->root = targetPath;
|
||||||
|
d->packageURL = packageURL;
|
||||||
|
d->version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInstallTask::~PackageInstallTask() {}
|
||||||
|
|
||||||
|
void PackageInstallTask::executeTask() {
|
||||||
|
// inspect the data folder in a thread
|
||||||
|
d->inspectionFuture = QtConcurrent::run(QThreadPool::globalInstance(), InspectFolder(FS::PathCombine(d->root, "data")));
|
||||||
|
connect(&d->inspectionWatcher, &QFutureWatcher<Package>::finished, this, &PackageInstallTask::inspectionFinished);
|
||||||
|
d->inspectionWatcher.setFuture(d->inspectionFuture);
|
||||||
|
|
||||||
|
// while inspecting, grab the manifest from remote
|
||||||
|
d->manifestDownloadJob.reset(new NetJob(QObject::tr("Download of package manifest %1").arg(d->packageURL), d->network));
|
||||||
|
auto url = d->packageURL;
|
||||||
|
auto dl = Net::Download::makeFile(url, FS::PathCombine(d->root, "manifest.json"));
|
||||||
|
/*
|
||||||
|
* The validator parses the file and loads it into the object.
|
||||||
|
* If that fails, the file is not written to storage.
|
||||||
|
*/
|
||||||
|
dl->addValidator(new ParsingValidator(d->downloadedPackage));
|
||||||
|
d->manifestDownloadJob->addNetAction(dl);
|
||||||
|
auto job = d->manifestDownloadJob.get();
|
||||||
|
connect(job, &NetJob::finished, this, &PackageInstallTask::manifestFetchFinished);
|
||||||
|
d->manifestDownloadJob->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageInstallTask::inspectionFinished() {
|
||||||
|
d->inspectedPackage = d->inspectionWatcher.result();
|
||||||
|
processInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageInstallTask::manifestFetchFinished()
|
||||||
|
{
|
||||||
|
d->manifestDone = true;
|
||||||
|
d->manifestDownloadJob.reset();
|
||||||
|
processInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageInstallTask::processInputs()
|
||||||
|
{
|
||||||
|
if(!d->manifestDone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!d->inspectionFuture.isFinished()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!d->downloadedPackage.valid) {
|
||||||
|
emitFailed("Downloading package manifest failed...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!d->inspectedPackage.valid) {
|
||||||
|
emitFailed("Inspecting local data folder failed...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
d->updateOps = UpdateOperations::resolve(d->inspectedPackage, d->downloadedPackage);
|
||||||
|
|
||||||
|
if(!d->updateOps.valid) {
|
||||||
|
emitFailed("Unable to determine update actions...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(d->updateOps.empty()) {
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dataRoot = FS::PathCombine(d->root, "data");
|
||||||
|
|
||||||
|
// first, ensure data path exists
|
||||||
|
QDir temp;
|
||||||
|
temp.mkpath(dataRoot);
|
||||||
|
|
||||||
|
for(auto & rm: d->updateOps.deletes) {
|
||||||
|
auto filePath = FS::PathCombine(dataRoot, rm.toString());
|
||||||
|
qDebug() << "RM" << filePath;
|
||||||
|
QFile::remove(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto & rmdir: d->updateOps.rmdirs) {
|
||||||
|
auto folderPath = FS::PathCombine(dataRoot, rmdir.toString());
|
||||||
|
qDebug() << "RMDIR" << folderPath;
|
||||||
|
QDir dir;
|
||||||
|
dir.rmdir(folderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto & mkdir: d->updateOps.mkdirs) {
|
||||||
|
auto folderPath = FS::PathCombine(dataRoot, mkdir.toString());
|
||||||
|
qDebug() << "MKDIR" << folderPath;
|
||||||
|
QDir dir;
|
||||||
|
dir.mkdir(folderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto & mklink: d->updateOps.mklinks) {
|
||||||
|
auto linkPath = FS::PathCombine(dataRoot, mklink.first.toString());
|
||||||
|
auto linkTarget = mklink.second.toString();
|
||||||
|
qDebug() << "MKLINK" << linkPath << "->" << linkTarget;
|
||||||
|
QFile::link(linkTarget, linkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto & fix: d->updateOps.executable_fixes) {
|
||||||
|
const auto &path = fix.first;
|
||||||
|
bool executable = fix.second;
|
||||||
|
auto targetPath = FS::PathCombine(dataRoot, path.toString());
|
||||||
|
qDebug() << "FIX_EXEC" << targetPath << "->" << (executable ? "EXECUTABLE" : "REGULAR");
|
||||||
|
|
||||||
|
auto perms = QFile::permissions(targetPath);
|
||||||
|
if(executable) {
|
||||||
|
perms |= QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
perms &= ~(QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||||
|
}
|
||||||
|
QFile::setPermissions(targetPath, perms);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!d->updateOps.downloads.size()) {
|
||||||
|
emitSucceeded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we download.
|
||||||
|
d->manifestDownloadJob.reset(new NetJob(QObject::tr("Download of files for %1").arg(d->packageURL), d->network));
|
||||||
|
connect(d->manifestDownloadJob.get(), &NetJob::succeeded, this, &PackageInstallTask::downloadsSucceeded);
|
||||||
|
connect(d->manifestDownloadJob.get(), &NetJob::failed, this, &PackageInstallTask::downloadsFailed);
|
||||||
|
connect(d->manifestDownloadJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
||||||
|
setProgress(current, total);
|
||||||
|
});
|
||||||
|
|
||||||
|
using Option = Net::Download::Option;
|
||||||
|
for(auto & download: d->updateOps.downloads) {
|
||||||
|
const auto &path = download.first;
|
||||||
|
const auto &object = download.second;
|
||||||
|
auto targetPath = FS::PathCombine(dataRoot, path.toString());
|
||||||
|
auto dl = Net::Download::makeFile(object.url, targetPath, object.executable ? Option::SetExecutable : Option::NoOptions);
|
||||||
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, QByteArray::fromHex(object.hash.toLatin1())));
|
||||||
|
dl->m_total_progress = object.size;
|
||||||
|
d->manifestDownloadJob->addNetAction(dl);
|
||||||
|
qDebug() << "DOWNLOAD" << object.url << "to" << targetPath << (object.executable ? "(EXECUTABLE)" : "(REGULAR)");
|
||||||
|
}
|
||||||
|
d->manifestDownloadJob->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageInstallTask::downloadsFailed(QString reason)
|
||||||
|
{
|
||||||
|
d->manifestDownloadJob.reset();
|
||||||
|
emitFailed(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageInstallTask::downloadsSucceeded()
|
||||||
|
{
|
||||||
|
d->manifestDownloadJob.reset();
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
55
launcher/mojang/PackageInstallTask.h
Normal file
55
launcher/mojang/PackageInstallTask.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
#include "net/Mode.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <net/NetJob.h>
|
||||||
|
|
||||||
|
struct PackageInstallTaskData;
|
||||||
|
|
||||||
|
class PackageInstallTask : public Task
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum class Mode
|
||||||
|
{
|
||||||
|
Launch,
|
||||||
|
Resolution
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PackageInstallTask(
|
||||||
|
shared_qobject_ptr<QNetworkAccessManager> network,
|
||||||
|
Net::Mode netmode,
|
||||||
|
QString version,
|
||||||
|
QString packageURL,
|
||||||
|
QString targetPath,
|
||||||
|
QObject *parent = 0
|
||||||
|
);
|
||||||
|
virtual ~PackageInstallTask();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void executeTask() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void inspectionFinished();
|
||||||
|
void manifestFetchFinished();
|
||||||
|
|
||||||
|
void processInputs();
|
||||||
|
|
||||||
|
void downloadsSucceeded();
|
||||||
|
void downloadsFailed(QString reason);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<PackageInstallTaskData> d;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2020 Petr Mrázek
|
* Copyright 2020 Petr Mrázek
|
||||||
*
|
*
|
||||||
@ -74,8 +81,8 @@ void Package::addLink(const Path& path, const Path& target) {
|
|||||||
symlinks[path] = target;
|
symlinks[path] = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Package::addSource(const FileSource& source) {
|
void Package::addSource(const Hash & rawHash, const FileSource& source) {
|
||||||
sources[source.hash] = source;
|
sources[rawHash] = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -131,6 +138,8 @@ void fromJson(QJsonDocument & doc, Package & out) {
|
|||||||
}
|
}
|
||||||
else if (compression == "lzma") {
|
else if (compression == "lzma") {
|
||||||
source.compression = Compression::Lzma;
|
source.compression = Compression::Lzma;
|
||||||
|
// FIXME: remove this line when we implement LZMA filter for downloads again
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
@ -141,11 +150,10 @@ void fromJson(QJsonDocument & doc, Package & out) {
|
|||||||
throw JSONValidationError("No valid compression method for file " + iter.key());
|
throw JSONValidationError("No valid compression method for file " + iter.key());
|
||||||
}
|
}
|
||||||
out.addFile(objectPath, file);
|
out.addFile(objectPath, file);
|
||||||
out.addSource(bestSource);
|
out.addSource(file.hash, bestSource);
|
||||||
}
|
}
|
||||||
else if(type == "link") {
|
else if(type == "link") {
|
||||||
auto target = Json::requireString(fileObject, "target");
|
auto target = Json::requireString(fileObject, "target");
|
||||||
out.symlinks[objectPath] = target;
|
|
||||||
out.addLink(objectPath, target);
|
out.addLink(objectPath, target);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -245,7 +253,8 @@ Package Package::fromInspectedFolder(const QString& folderPath)
|
|||||||
iterator.next();
|
iterator.next();
|
||||||
|
|
||||||
auto fileInfo = iterator.fileInfo();
|
auto fileInfo = iterator.fileInfo();
|
||||||
auto relPath = root.relativeFilePath(fileInfo.filePath());
|
auto itemPath = fileInfo.absoluteFilePath();
|
||||||
|
auto relPath = root.relativeFilePath(itemPath);
|
||||||
// FIXME: this is probably completely busted on Windows anyway, so just disable it.
|
// FIXME: this is probably completely busted on Windows anyway, so just disable it.
|
||||||
// Qt makes shit up and doesn't understand the platform details
|
// Qt makes shit up and doesn't understand the platform details
|
||||||
// TODO: Actually use a filesystem library that isn't terrible and has decen license.
|
// TODO: Actually use a filesystem library that isn't terrible and has decen license.
|
||||||
@ -253,7 +262,7 @@ Package Package::fromInspectedFolder(const QString& folderPath)
|
|||||||
#ifndef Q_OS_WIN32
|
#ifndef Q_OS_WIN32
|
||||||
if(fileInfo.isSymLink()) {
|
if(fileInfo.isSymLink()) {
|
||||||
Path targetPath;
|
Path targetPath;
|
||||||
if(!actually_read_symlink_target(fileInfo.filePath(), targetPath)) {
|
if(!actually_read_symlink_target(fileInfo.absoluteFilePath(), targetPath)) {
|
||||||
qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath();
|
qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath();
|
||||||
out.valid = false;
|
out.valid = false;
|
||||||
}
|
}
|
||||||
@ -371,12 +380,21 @@ UpdateOperations UpdateOperations::resolve(const Package& from, const Package& t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(auto iter = to.files.begin(); iter != to.files.end(); iter++) {
|
for(auto iter = to.files.begin(); iter != to.files.end(); iter++) {
|
||||||
auto path = iter->first;
|
auto & path = iter->first;
|
||||||
|
auto & file = iter->second;
|
||||||
|
auto & fileHash = file.hash;
|
||||||
|
auto executable = file.executable;
|
||||||
|
if(!to.sources.count(fileHash)) {
|
||||||
|
out.valid = false;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
auto & source = to.sources.at(fileHash);
|
||||||
|
// it wasn't there before, it is there now... therefore we fill it in
|
||||||
if(!from.files.count(path)) {
|
if(!from.files.count(path)) {
|
||||||
out.downloads.emplace(
|
out.downloads.emplace(
|
||||||
std::pair<Path, FileDownload>{
|
std::pair<Path, FileDownload>{
|
||||||
path,
|
path,
|
||||||
FileDownload(to.sources.at(iter->second.hash), iter->second.executable)
|
FileDownload(source, executable)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -418,13 +436,14 @@ UpdateOperations UpdateOperations::resolve(const Package& from, const Package& t
|
|||||||
const auto &new_target = iter2->second;
|
const auto &new_target = iter2->second;
|
||||||
if (current_target != new_target) {
|
if (current_target != new_target) {
|
||||||
out.deletes.push_back(path);
|
out.deletes.push_back(path);
|
||||||
out.mklinks[path] = iter2->second;
|
out.mklinks[path] = new_target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(auto iter = to.symlinks.begin(); iter != to.symlinks.end(); iter++) {
|
for(auto iter = to.symlinks.begin(); iter != to.symlinks.end(); iter++) {
|
||||||
auto path = iter->first;
|
auto path = iter->first;
|
||||||
|
const auto &new_target = iter->second;
|
||||||
if(!from.symlinks.count(path)) {
|
if(!from.symlinks.count(path)) {
|
||||||
out.mklinks[path] = iter->second;
|
out.mklinks[path] = new_target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.valid = true;
|
out.valid = true;
|
||||||
|
@ -13,97 +13,13 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
#include "Path.h"
|
||||||
|
|
||||||
namespace mojang_files {
|
namespace mojang_files {
|
||||||
|
|
||||||
using Hash = QString;
|
using Hash = QString;
|
||||||
extern const Hash empty_hash;
|
extern const Hash empty_hash;
|
||||||
|
|
||||||
// simple-ish path implementation. assumes always relative and does not allow '..' entries
|
|
||||||
class Path
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using parts_type = QStringList;
|
|
||||||
|
|
||||||
Path() = default;
|
|
||||||
Path(QString string) {
|
|
||||||
auto parts_in = string.split('/');
|
|
||||||
for(auto & part: parts_in) {
|
|
||||||
if(part.isEmpty() || part == ".") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(part == "..") {
|
|
||||||
if(parts.size()) {
|
|
||||||
parts.pop_back();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parts.push_back(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_parent_path() const
|
|
||||||
{
|
|
||||||
return parts.size() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Path parent_path() const
|
|
||||||
{
|
|
||||||
if (parts.empty())
|
|
||||||
return Path();
|
|
||||||
return Path(parts.begin(), std::prev(parts.end()));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool empty() const
|
|
||||||
{
|
|
||||||
return parts.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
int length() const
|
|
||||||
{
|
|
||||||
return parts.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Path & rhs) const {
|
|
||||||
return parts == rhs.parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(const Path & rhs) const {
|
|
||||||
return parts != rhs.parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator<(const Path& rhs) const
|
|
||||||
{
|
|
||||||
return compare(rhs) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
parts_type::const_iterator begin() const
|
|
||||||
{
|
|
||||||
return parts.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
parts_type::const_iterator end() const
|
|
||||||
{
|
|
||||||
return parts.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString toString() const {
|
|
||||||
return parts.join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) {
|
|
||||||
auto cursor = start;
|
|
||||||
while(cursor != end) {
|
|
||||||
parts.push_back(*cursor);
|
|
||||||
cursor++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int compare(const Path& p) const;
|
|
||||||
|
|
||||||
parts_type parts;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
enum class Compression {
|
enum class Compression {
|
||||||
Raw,
|
Raw,
|
||||||
Lzma,
|
Lzma,
|
||||||
@ -146,7 +62,7 @@ struct Package {
|
|||||||
void addFolder(Path folder);
|
void addFolder(Path folder);
|
||||||
void addFile(const Path & path, const File & file);
|
void addFile(const Path & path, const File & file);
|
||||||
void addLink(const Path & path, const Path & target);
|
void addLink(const Path & path, const Path & target);
|
||||||
void addSource(const FileSource & source);
|
void addSource(const Hash & rawHash, const FileSource & source);
|
||||||
|
|
||||||
std::map<Hash, FileSource> sources;
|
std::map<Hash, FileSource> sources;
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
@ -173,6 +89,19 @@ struct UpdateOperations {
|
|||||||
std::map<Path, FileDownload> downloads;
|
std::map<Path, FileDownload> downloads;
|
||||||
std::map<Path, Path> mklinks;
|
std::map<Path, Path> mklinks;
|
||||||
std::map<Path, bool> executable_fixes;
|
std::map<Path, bool> executable_fixes;
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
if(!valid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return
|
||||||
|
deletes.empty() &&
|
||||||
|
rmdirs.empty() &&
|
||||||
|
mkdirs.empty() &&
|
||||||
|
downloads.empty() &&
|
||||||
|
mklinks.empty() &&
|
||||||
|
executable_fixes.empty();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,7 @@ void PackageManifestTest::test_parse_file() {
|
|||||||
auto path = QFINDTESTDATA("testdata/1.8.0_202-x64.json");
|
auto path = QFINDTESTDATA("testdata/1.8.0_202-x64.json");
|
||||||
auto manifest = Package::fromManifestFile(path);
|
auto manifest = Package::fromManifestFile(path);
|
||||||
QVERIFY(manifest.valid == true);
|
QVERIFY(manifest.valid == true);
|
||||||
|
QVERIFY(manifest.sources.count("c725183c757011e7ba96c83c1e86ee7e8b516a2b") == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
8
launcher/mojang/Path.cpp
Normal file
8
launcher/mojang/Path.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// NOOP
|
100
launcher/mojang/Path.h
Normal file
100
launcher/mojang/Path.h
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Petr Mrázek
|
||||||
|
*
|
||||||
|
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||||
|
* Please see the COPYING.md file for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
namespace mojang_files {
|
||||||
|
|
||||||
|
// simple-ish path implementation. assumes always relative and does not allow '..' entries
|
||||||
|
class Path
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using parts_type = QStringList;
|
||||||
|
|
||||||
|
Path() = default;
|
||||||
|
Path(QString string) {
|
||||||
|
auto parts_in = string.split('/');
|
||||||
|
for(auto & part: parts_in) {
|
||||||
|
if(part.isEmpty() || part == ".") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(part == "..") {
|
||||||
|
if(parts.size()) {
|
||||||
|
parts.pop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts.push_back(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_parent_path() const
|
||||||
|
{
|
||||||
|
return parts.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path parent_path() const
|
||||||
|
{
|
||||||
|
if (parts.empty())
|
||||||
|
return Path();
|
||||||
|
return Path(parts.begin(), std::prev(parts.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return parts.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
int length() const
|
||||||
|
{
|
||||||
|
return parts.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Path & rhs) const {
|
||||||
|
return parts == rhs.parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const Path & rhs) const {
|
||||||
|
return parts != rhs.parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator<(const Path& rhs) const
|
||||||
|
{
|
||||||
|
return compare(rhs) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts_type::const_iterator begin() const
|
||||||
|
{
|
||||||
|
return parts.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
parts_type::const_iterator end() const
|
||||||
|
{
|
||||||
|
return parts.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString toString() const {
|
||||||
|
return parts.join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) {
|
||||||
|
auto cursor = start;
|
||||||
|
while(cursor != end) {
|
||||||
|
parts.push_back(*cursor);
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int compare(const Path& p) const;
|
||||||
|
|
||||||
|
parts_type parts;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
BIN
launcher/mojang/testdata/all.json
vendored
Normal file
BIN
launcher/mojang/testdata/all.json
vendored
Normal file
Binary file not shown.
@ -34,7 +34,7 @@ public: /* methods */
|
|||||||
{
|
{
|
||||||
if(m_expected.size() && m_expected != hash())
|
if(m_expected.size() && m_expected != hash())
|
||||||
{
|
{
|
||||||
qWarning() << "Checksum mismatch, download is bad.";
|
qWarning() << "Checksum mismatch. expected:" << m_expected << "got:" << hash();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -52,4 +52,4 @@ private: /* data */
|
|||||||
QCryptographicHash m_checksum;
|
QCryptographicHash m_checksum;
|
||||||
QByteArray m_expected;
|
QByteArray m_expected;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ Download::Ptr Download::makeFile(QUrl url, QString path, Options options)
|
|||||||
Download * dl = new Download();
|
Download * dl = new Download();
|
||||||
dl->m_url = url;
|
dl->m_url = url;
|
||||||
dl->m_options = options;
|
dl->m_options = options;
|
||||||
dl->m_sink.reset(new FileSink(path));
|
dl->m_sink.reset(new FileSink(path, options & Option::SetExecutable));
|
||||||
return dl;
|
return dl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,8 @@ public: /* types */
|
|||||||
enum class Option
|
enum class Option
|
||||||
{
|
{
|
||||||
NoOptions = 0,
|
NoOptions = 0,
|
||||||
AcceptLocalFiles = 1
|
AcceptLocalFiles = 1,
|
||||||
|
SetExecutable = 2
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(Options, Option)
|
Q_DECLARE_FLAGS(Options, Option)
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
FileSink::FileSink(QString filename)
|
FileSink::FileSink(QString filename, bool setExecutable)
|
||||||
:m_filename(filename)
|
:m_filename(filename), setExecutable(setExecutable)
|
||||||
{
|
{
|
||||||
// nil
|
// nil
|
||||||
}
|
}
|
||||||
@ -94,6 +94,10 @@ JobStatus FileSink::finalize(QNetworkReply& reply)
|
|||||||
m_output_file->cancelWriting();
|
m_output_file->cancelWriting();
|
||||||
return Job_Failed;
|
return Job_Failed;
|
||||||
}
|
}
|
||||||
|
if(setExecutable) {
|
||||||
|
auto permissions = QFile::permissions(m_filename);
|
||||||
|
QFile::setPermissions(m_filename, permissions | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// then get rid of the save file
|
// then get rid of the save file
|
||||||
m_output_file.reset();
|
m_output_file.reset();
|
||||||
|
@ -6,7 +6,7 @@ namespace Net {
|
|||||||
class FileSink : public Sink
|
class FileSink : public Sink
|
||||||
{
|
{
|
||||||
public: /* con/des */
|
public: /* con/des */
|
||||||
FileSink(QString filename);
|
FileSink(QString filename, bool setExecutable = false);
|
||||||
virtual ~FileSink();
|
virtual ~FileSink();
|
||||||
|
|
||||||
public: /* methods */
|
public: /* methods */
|
||||||
@ -24,5 +24,6 @@ protected: /* data */
|
|||||||
QString m_filename;
|
QString m_filename;
|
||||||
bool wroteAnyData = false;
|
bool wroteAnyData = false;
|
||||||
std::unique_ptr<QSaveFile> m_output_file;
|
std::unique_ptr<QSaveFile> m_output_file;
|
||||||
|
bool setExecutable = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,6 @@ class MainWindow::Ui
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TranslatedAction actionAddInstance;
|
TranslatedAction actionAddInstance;
|
||||||
//TranslatedAction actionRefresh;
|
|
||||||
TranslatedAction actionCheckUpdate;
|
TranslatedAction actionCheckUpdate;
|
||||||
TranslatedAction actionSettings;
|
TranslatedAction actionSettings;
|
||||||
TranslatedAction actionPatreon;
|
TranslatedAction actionPatreon;
|
||||||
@ -220,6 +219,7 @@ public:
|
|||||||
TranslatedAction actionDeleteInstance;
|
TranslatedAction actionDeleteInstance;
|
||||||
TranslatedAction actionConfig_Folder;
|
TranslatedAction actionConfig_Folder;
|
||||||
TranslatedAction actionCAT;
|
TranslatedAction actionCAT;
|
||||||
|
TranslatedAction actionJREs;
|
||||||
TranslatedAction actionCopyInstance;
|
TranslatedAction actionCopyInstance;
|
||||||
TranslatedAction actionLaunchInstanceOffline;
|
TranslatedAction actionLaunchInstanceOffline;
|
||||||
TranslatedAction actionScreenshots;
|
TranslatedAction actionScreenshots;
|
||||||
@ -421,6 +421,15 @@ public:
|
|||||||
all_actions.append(&actionCAT);
|
all_actions.append(&actionCAT);
|
||||||
mainToolBar->addAction(actionCAT);
|
mainToolBar->addAction(actionCAT);
|
||||||
|
|
||||||
|
actionJREs = TranslatedAction(MainWindow);
|
||||||
|
actionJREs->setObjectName(QStringLiteral("actionJREs"));
|
||||||
|
actionJREs->setIcon(APPLICATION->getThemedIcon("java"));
|
||||||
|
actionJREs.setTextId(QT_TRANSLATE_NOOP("MainWindow", "JREs"));
|
||||||
|
actionJREs.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the Java Runtime manager"));
|
||||||
|
actionJREs->setPriority(QAction::LowPriority);
|
||||||
|
all_actions.append(&actionJREs);
|
||||||
|
mainToolBar->addAction(actionJREs);
|
||||||
|
|
||||||
// profile menu and its actions
|
// profile menu and its actions
|
||||||
actionManageAccounts = TranslatedAction(MainWindow);
|
actionManageAccounts = TranslatedAction(MainWindow);
|
||||||
actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts"));
|
actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts"));
|
||||||
@ -1681,6 +1690,11 @@ void MainWindow::on_actionSettings_triggered()
|
|||||||
APPLICATION->ShowGlobalSettings(this, "global-settings");
|
APPLICATION->ShowGlobalSettings(this, "global-settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::on_actionJREs_triggered()
|
||||||
|
{
|
||||||
|
APPLICATION->ShowJREs(this);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::globalSettingsClosed()
|
void MainWindow::globalSettingsClosed()
|
||||||
{
|
{
|
||||||
// FIXME: quick HACK to make this work. improve, optimize.
|
// FIXME: quick HACK to make this work. improve, optimize.
|
||||||
|
@ -103,6 +103,8 @@ private slots:
|
|||||||
|
|
||||||
void on_actionSettings_triggered();
|
void on_actionSettings_triggered();
|
||||||
|
|
||||||
|
void on_actionJREs_triggered();
|
||||||
|
|
||||||
void on_actionInstanceSettings_triggered();
|
void on_actionInstanceSettings_triggered();
|
||||||
|
|
||||||
void on_actionManageAccounts_triggered();
|
void on_actionManageAccounts_triggered();
|
||||||
|
1
tools/CMakeLists.txt
Normal file
1
tools/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
add_subdirectory(grabjre)
|
2
tools/grabjre/CMakeLists.txt
Normal file
2
tools/grabjre/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
add_executable(GrabJRE GrabJRE.cpp)
|
||||||
|
target_link_libraries(GrabJRE Launcher_logic)
|
58
tools/grabjre/GrabJRE.cpp
Normal file
58
tools/grabjre/GrabJRE.cpp
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#include <QTextStream>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
#include "mojang/PackageManifest.h"
|
||||||
|
#include "mojang/PackageInstallTask.h"
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
|
||||||
|
int main(int argc, char ** argv) {
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
|
QCoreApplication::setApplicationName("GrabJRE");
|
||||||
|
QCoreApplication::setApplicationVersion("1.0");
|
||||||
|
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.setApplicationDescription("Stupid thing that grabs a piston package and updates a local folder with it");
|
||||||
|
parser.addHelpOption();
|
||||||
|
parser.addVersionOption();
|
||||||
|
|
||||||
|
parser.addPositionalArgument("url", "Source URL");
|
||||||
|
parser.addPositionalArgument("version", "Source version");
|
||||||
|
parser.addPositionalArgument("destination", "Destination folder to update");
|
||||||
|
|
||||||
|
parser.process(app);
|
||||||
|
|
||||||
|
const QStringList args = parser.positionalArguments();
|
||||||
|
if (args.size() != 3) {
|
||||||
|
parser.showHelp(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run like ./GrabJRE "https://launchermeta.mojang.com/v1/packages/a1c15cc788f8893fba7e988eb27404772f699a84/manifest.json" "1.8.0_202" "test"
|
||||||
|
|
||||||
|
shared_qobject_ptr<QNetworkAccessManager> nam = new QNetworkAccessManager();
|
||||||
|
auto url = args[0];
|
||||||
|
auto version = args[1];
|
||||||
|
auto destination = args[2];
|
||||||
|
PackageInstallTask installTask(
|
||||||
|
nam,
|
||||||
|
Net::Mode::Online,
|
||||||
|
version,
|
||||||
|
url,
|
||||||
|
destination
|
||||||
|
);
|
||||||
|
installTask.start();
|
||||||
|
QCoreApplication::connect(&installTask, &PackageInstallTask::progress, [&](qint64 now, qint64 total) {
|
||||||
|
static int percentage = 0;
|
||||||
|
if(total > 0) {
|
||||||
|
int newPercentage = (now * 100.0f) / double(total);
|
||||||
|
if(newPercentage != percentage) {
|
||||||
|
percentage = newPercentage;
|
||||||
|
QTextStream(stdout) << "Downloading: " << percentage << "% done\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
QCoreApplication::connect(&installTask, &PackageInstallTask::finished, [&]() {
|
||||||
|
app.exit(installTask.wasSuccessful() ? 0 : 1);
|
||||||
|
});
|
||||||
|
app.exec();
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user