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.
|
||||
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 <Secrets.h>
|
||||
#include "ApplicationSettings.h"
|
||||
#include "jreclient/JREClientDialog.h"
|
||||
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
@ -562,162 +564,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
}
|
||||
} while(false);
|
||||
|
||||
// Initialize application settings
|
||||
{
|
||||
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.";
|
||||
}
|
||||
initializeSettings();
|
||||
|
||||
#ifndef QT_NO_ACCESSIBILITY
|
||||
QAccessible::installFactory(groupViewAccessibleFactory);
|
||||
@ -884,44 +731,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
}
|
||||
|
||||
// Initialize analytics
|
||||
[this]()
|
||||
{
|
||||
const int analyticsVersion = 2;
|
||||
if(BuildConfig.ANALYTICS_ID.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto analyticsSetting = m_settings->getSetting("Analytics");
|
||||
connect(analyticsSetting.get(), &Setting::SettingChanged, this, &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;
|
||||
}();
|
||||
initializeAnalytics();
|
||||
|
||||
if(createSetupWizard())
|
||||
{
|
||||
@ -930,6 +740,68 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
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 javaRequired = [&]()
|
||||
@ -1406,6 +1278,13 @@ void Application::ShowGlobalSettings(class QWidget* parent, QString open_page)
|
||||
emit globalSettingsClosed();
|
||||
}
|
||||
|
||||
void Application::ShowJREs(class QWidget* parent)
|
||||
{
|
||||
JREClientDialog dlg(parent);
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
|
||||
MainWindow* Application::showMainWindow(bool minimized)
|
||||
{
|
||||
if(m_mainWindow)
|
||||
|
@ -142,6 +142,7 @@ public:
|
||||
bool updatesAreAllowed();
|
||||
|
||||
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
|
||||
void ShowJREs(class QWidget * parent);
|
||||
|
||||
signals:
|
||||
void updateAllowedChanged(bool status);
|
||||
@ -168,6 +169,8 @@ private slots:
|
||||
void setupWizardFinished(int status);
|
||||
|
||||
private:
|
||||
void initializeSettings();
|
||||
void initializeAnalytics();
|
||||
bool createSetupWizard();
|
||||
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.h
|
||||
|
||||
mojang/PackageManifest.h
|
||||
mojang/ComponentsManifest.cpp
|
||||
mojang/ComponentsManifest.h
|
||||
mojang/PackageInstallTask.cpp
|
||||
mojang/PackageInstallTask.h
|
||||
mojang/PackageManifest.cpp
|
||||
mojang/PackageManifest.h
|
||||
mojang/Path.cpp
|
||||
mojang/Path.h
|
||||
)
|
||||
|
||||
add_unit_test(GradleSpecifier
|
||||
@ -568,10 +574,12 @@ SET(LAUNCHER_SOURCES
|
||||
# Application base
|
||||
Application.h
|
||||
Application.cpp
|
||||
UpdateController.cpp
|
||||
UpdateController.h
|
||||
ApplicationMessage.h
|
||||
ApplicationMessage.cpp
|
||||
ApplicationSettings.cpp
|
||||
ApplicationSettings.h
|
||||
UpdateController.cpp
|
||||
UpdateController.h
|
||||
|
||||
# GUI - general utilities
|
||||
DesktopServices.h
|
||||
@ -653,6 +661,11 @@ SET(LAUNCHER_SOURCES
|
||||
JavaCommon.h
|
||||
JavaCommon.cpp
|
||||
|
||||
jreclient/JREClient.h
|
||||
jreclient/JREClient.cpp
|
||||
jreclient/JREClientDialog.h
|
||||
jreclient/JREClientDialog.cpp
|
||||
|
||||
# GUI - paged dialog base
|
||||
ui/pages/BasePage.h
|
||||
ui/pages/BasePageContainer.h
|
||||
@ -886,6 +899,8 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/dialogs/EditAccountDialog.ui
|
||||
ui/dialogs/CreateShortcutDialog.ui
|
||||
ui/dialogs/ModrinthExportDialog.ui
|
||||
|
||||
jreclient/JREClientDialog.ui
|
||||
)
|
||||
|
||||
qt_add_resources(LAUNCHER_RESOURCES
|
||||
@ -937,6 +952,7 @@ target_link_libraries(Launcher_logic
|
||||
)
|
||||
|
||||
target_link_libraries(Launcher_logic secrets)
|
||||
target_include_directories(Launcher_logic PUBLIC .)
|
||||
|
||||
add_executable(${Launcher_Name} MACOSX_BUNDLE WIN32 main.cpp ${LAUNCHER_RCS})
|
||||
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
|
||||
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
|
||||
QChar c1 = getNextChar(s1, l1);
|
||||
|
@ -3,9 +3,19 @@
|
||||
|
||||
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);
|
||||
if(archCompare != 0)
|
||||
return archCompare < 0;
|
||||
|
||||
// FIXME: make this a version compare
|
||||
if(id < rhs.id)
|
||||
{
|
||||
return true;
|
||||
@ -19,7 +29,7 @@ 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)
|
||||
|
@ -33,6 +33,10 @@ struct JavaInstall : public BaseVersion
|
||||
QString arch;
|
||||
QString path;
|
||||
bool recommended = false;
|
||||
|
||||
bool remote = false;
|
||||
QString url;
|
||||
QString installRoot;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<JavaInstall> JavaInstallPtr;
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "java/JavaUtils.h"
|
||||
#include "MMCStrings.h"
|
||||
#include "minecraft/VersionFilterData.h"
|
||||
#include "sys.h"
|
||||
|
||||
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;
|
||||
for (auto java : candidates)
|
||||
{
|
||||
//qDebug() << java->id << java->arch << " at " << java->path;
|
||||
BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
|
||||
|
||||
if (bp_java)
|
||||
{
|
||||
javas_bvp.append(java);
|
||||
}
|
||||
javas_bvp.append(java);
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
@ -74,8 +81,8 @@ void Package::addLink(const Path& path, const Path& target) {
|
||||
symlinks[path] = target;
|
||||
}
|
||||
|
||||
void Package::addSource(const FileSource& source) {
|
||||
sources[source.hash] = source;
|
||||
void Package::addSource(const Hash & rawHash, const FileSource& source) {
|
||||
sources[rawHash] = source;
|
||||
}
|
||||
|
||||
|
||||
@ -131,6 +138,8 @@ void fromJson(QJsonDocument & doc, Package & out) {
|
||||
}
|
||||
else if (compression == "lzma") {
|
||||
source.compression = Compression::Lzma;
|
||||
// FIXME: remove this line when we implement LZMA filter for downloads again
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
@ -141,11 +150,10 @@ void fromJson(QJsonDocument & doc, Package & out) {
|
||||
throw JSONValidationError("No valid compression method for file " + iter.key());
|
||||
}
|
||||
out.addFile(objectPath, file);
|
||||
out.addSource(bestSource);
|
||||
out.addSource(file.hash, bestSource);
|
||||
}
|
||||
else if(type == "link") {
|
||||
auto target = Json::requireString(fileObject, "target");
|
||||
out.symlinks[objectPath] = target;
|
||||
out.addLink(objectPath, target);
|
||||
}
|
||||
else {
|
||||
@ -245,7 +253,8 @@ Package Package::fromInspectedFolder(const QString& folderPath)
|
||||
iterator.next();
|
||||
|
||||
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.
|
||||
// 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.
|
||||
@ -253,7 +262,7 @@ Package Package::fromInspectedFolder(const QString& folderPath)
|
||||
#ifndef Q_OS_WIN32
|
||||
if(fileInfo.isSymLink()) {
|
||||
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();
|
||||
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++) {
|
||||
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)) {
|
||||
out.downloads.emplace(
|
||||
std::pair<Path, FileDownload>{
|
||||
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;
|
||||
if (current_target != new_target) {
|
||||
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++) {
|
||||
auto path = iter->first;
|
||||
const auto &new_target = iter->second;
|
||||
if(!from.symlinks.count(path)) {
|
||||
out.mklinks[path] = iter->second;
|
||||
out.mklinks[path] = new_target;
|
||||
}
|
||||
}
|
||||
out.valid = true;
|
||||
|
@ -13,97 +13,13 @@
|
||||
#include <QStringList>
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include "Path.h"
|
||||
|
||||
namespace mojang_files {
|
||||
|
||||
using Hash = QString;
|
||||
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 {
|
||||
Raw,
|
||||
Lzma,
|
||||
@ -146,7 +62,7 @@ struct Package {
|
||||
void addFolder(Path folder);
|
||||
void addFile(const Path & path, const File & file);
|
||||
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;
|
||||
bool valid = true;
|
||||
@ -173,6 +89,19 @@ struct UpdateOperations {
|
||||
std::map<Path, FileDownload> downloads;
|
||||
std::map<Path, Path> mklinks;
|
||||
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 manifest = Package::fromManifestFile(path);
|
||||
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())
|
||||
{
|
||||
qWarning() << "Checksum mismatch, download is bad.";
|
||||
qWarning() << "Checksum mismatch. expected:" << m_expected << "got:" << hash();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -59,7 +59,7 @@ Download::Ptr Download::makeFile(QUrl url, QString path, Options options)
|
||||
Download * dl = new Download();
|
||||
dl->m_url = url;
|
||||
dl->m_options = options;
|
||||
dl->m_sink.reset(new FileSink(path));
|
||||
dl->m_sink.reset(new FileSink(path, options & Option::SetExecutable));
|
||||
return dl;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,8 @@ public: /* types */
|
||||
enum class Option
|
||||
{
|
||||
NoOptions = 0,
|
||||
AcceptLocalFiles = 1
|
||||
AcceptLocalFiles = 1,
|
||||
SetExecutable = 2
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
namespace Net {
|
||||
|
||||
FileSink::FileSink(QString filename)
|
||||
:m_filename(filename)
|
||||
FileSink::FileSink(QString filename, bool setExecutable)
|
||||
:m_filename(filename), setExecutable(setExecutable)
|
||||
{
|
||||
// nil
|
||||
}
|
||||
@ -94,6 +94,10 @@ JobStatus FileSink::finalize(QNetworkReply& reply)
|
||||
m_output_file->cancelWriting();
|
||||
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
|
||||
m_output_file.reset();
|
||||
|
@ -6,7 +6,7 @@ namespace Net {
|
||||
class FileSink : public Sink
|
||||
{
|
||||
public: /* con/des */
|
||||
FileSink(QString filename);
|
||||
FileSink(QString filename, bool setExecutable = false);
|
||||
virtual ~FileSink();
|
||||
|
||||
public: /* methods */
|
||||
@ -24,5 +24,6 @@ protected: /* data */
|
||||
QString m_filename;
|
||||
bool wroteAnyData = false;
|
||||
std::unique_ptr<QSaveFile> m_output_file;
|
||||
bool setExecutable = false;
|
||||
};
|
||||
}
|
||||
|
@ -200,7 +200,6 @@ class MainWindow::Ui
|
||||
{
|
||||
public:
|
||||
TranslatedAction actionAddInstance;
|
||||
//TranslatedAction actionRefresh;
|
||||
TranslatedAction actionCheckUpdate;
|
||||
TranslatedAction actionSettings;
|
||||
TranslatedAction actionPatreon;
|
||||
@ -220,6 +219,7 @@ public:
|
||||
TranslatedAction actionDeleteInstance;
|
||||
TranslatedAction actionConfig_Folder;
|
||||
TranslatedAction actionCAT;
|
||||
TranslatedAction actionJREs;
|
||||
TranslatedAction actionCopyInstance;
|
||||
TranslatedAction actionLaunchInstanceOffline;
|
||||
TranslatedAction actionScreenshots;
|
||||
@ -421,6 +421,15 @@ public:
|
||||
all_actions.append(&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
|
||||
actionManageAccounts = TranslatedAction(MainWindow);
|
||||
actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts"));
|
||||
@ -1681,6 +1690,11 @@ void MainWindow::on_actionSettings_triggered()
|
||||
APPLICATION->ShowGlobalSettings(this, "global-settings");
|
||||
}
|
||||
|
||||
void MainWindow::on_actionJREs_triggered()
|
||||
{
|
||||
APPLICATION->ShowJREs(this);
|
||||
}
|
||||
|
||||
void MainWindow::globalSettingsClosed()
|
||||
{
|
||||
// FIXME: quick HACK to make this work. improve, optimize.
|
||||
|
@ -103,6 +103,8 @@ private slots:
|
||||
|
||||
void on_actionSettings_triggered();
|
||||
|
||||
void on_actionJREs_triggered();
|
||||
|
||||
void on_actionInstanceSettings_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