aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp8
-rw-r--r--launcher/BaseInstance.cpp21
-rw-r--r--launcher/BaseInstance.h24
-rw-r--r--launcher/CMakeLists.txt6
-rw-r--r--launcher/FileSystem.cpp227
-rw-r--r--launcher/FileSystem.h51
-rw-r--r--launcher/InstanceImportTask.cpp9
-rw-r--r--launcher/InstanceImportTask.h6
-rw-r--r--launcher/InstanceList.cpp428
-rw-r--r--launcher/InstanceList.h13
-rw-r--r--launcher/LaunchController.cpp20
-rw-r--r--launcher/LoggedProcess.cpp32
-rw-r--r--launcher/LoggedProcess.h5
-rw-r--r--launcher/MMCZip.cpp21
-rw-r--r--launcher/MMCZip.h8
-rw-r--r--launcher/NullInstance.h6
-rw-r--r--launcher/QObjectPtr.h88
-rw-r--r--launcher/java/JavaUtils.cpp12
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp129
-rw-r--r--launcher/minecraft/MinecraftInstance.h14
-rw-r--r--launcher/minecraft/MinecraftUpdate.cpp10
-rw-r--r--launcher/minecraft/MinecraftUpdate.h2
-rw-r--r--launcher/minecraft/World.cpp8
-rw-r--r--launcher/minecraft/World.h6
-rw-r--r--launcher/minecraft/auth/AccountList.cpp11
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.cpp2
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp6
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h1
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp17
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.h3
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.cpp144
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.h16
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.cpp64
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.h23
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp7
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.h3
-rw-r--r--launcher/modplatform/legacy_ftb/PackInstallTask.h6
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.cpp282
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.h80
-rw-r--r--launcher/modplatform/modpacksch/FTBPackManifest.cpp46
-rw-r--r--launcher/modplatform/modpacksch/FTBPackManifest.h48
-rw-r--r--launcher/modplatform/technic/SingleZipPackInstallTask.h6
-rw-r--r--launcher/net/HttpMetaCache.cpp14
-rw-r--r--launcher/net/HttpMetaCache.h10
-rw-r--r--launcher/net/MetaCacheSink.cpp38
-rw-r--r--launcher/settings/SettingsObject.h1
-rw-r--r--launcher/ui/MainWindow.cpp35
-rw-r--r--launcher/ui/MainWindow.h1
-rw-r--r--launcher/ui/dialogs/AboutDialog.cpp11
-rw-r--r--launcher/ui/dialogs/AboutDialog.ui20
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.cpp28
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.h22
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.ui84
-rw-r--r--launcher/ui/dialogs/LoginDialog.cpp2
-rw-r--r--launcher/ui/dialogs/MSALoginDialog.cpp2
-rw-r--r--launcher/ui/dialogs/OfflineLoginDialog.cpp2
-rw-r--r--launcher/ui/dialogs/ProgressDialog.cpp4
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp10
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.h2
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp82
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h9
-rw-r--r--launcher/ui/pages/instance/WorldListPage.cpp12
-rw-r--r--launcher/ui/pages/instance/WorldListPage.h2
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp59
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.h6
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp95
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h56
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbPage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.ui18
-rw-r--r--launcher/updater/UpdateChecker.cpp3
-rw-r--r--launcher/updater/UpdateChecker.h2
71 files changed, 1668 insertions, 884 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 2bd91fd7..553b3229 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -321,7 +321,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
{
// Root path is used for updates and portable data
-#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
+#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr
m_rootPath = foo.absolutePath();
#elif defined(Q_OS_WIN32)
@@ -774,7 +774,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM);
auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json";
qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl;
- m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
+ m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL));
qDebug() << "<> Updater started.";
}
@@ -864,6 +864,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
+ m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath());
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
@@ -1258,6 +1259,9 @@ bool Application::launch(
}
connect(controller.get(), &LaunchController::succeeded, this, &Application::controllerSucceeded);
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
+ connect(controller.get(), &LaunchController::aborted, this, [this] {
+ controllerFailed(tr("Aborted"));
+ });
addRunningInstance();
controller->start();
return true;
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 5a84a931..e6d4d8e3 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -53,15 +53,22 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
: QObject()
{
m_settings = settings;
+ m_global_settings = globalSettings;
m_rootDir = rootDir;
m_settings->registerSetting("name", "Unnamed Instance");
m_settings->registerSetting("iconKey", "default");
m_settings->registerSetting("notes", "");
+
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0);
+ // Game time override
+ auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
+ m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
+ m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride);
+
// NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
// a locally stored instance
if (!m_settings->getSetting("InstanceType"))
@@ -149,7 +156,7 @@ void BaseInstance::setManagedPack(const QString& type, const QString& id, const
int BaseInstance::getConsoleMaxLines() const
{
- auto lineSetting = settings()->getSetting("ConsoleMaxLines");
+ auto lineSetting = m_settings->getSetting("ConsoleMaxLines");
bool conversionOk = false;
int maxLines = lineSetting->get().toInt(&conversionOk);
if(!conversionOk)
@@ -162,7 +169,7 @@ int BaseInstance::getConsoleMaxLines() const
bool BaseInstance::shouldStopOnConsoleOverflow() const
{
- return settings()->get("ConsoleOverflowStop").toBool();
+ return m_settings->get("ConsoleOverflowStop").toBool();
}
void BaseInstance::iconUpdated(QString key)
@@ -237,7 +244,7 @@ void BaseInstance::setRunning(bool running)
int64_t BaseInstance::totalTimePlayed() const
{
- qint64 current = settings()->get("totalTimePlayed").toLongLong();
+ qint64 current = m_settings->get("totalTimePlayed").toLongLong();
if(m_isRunning)
{
QDateTime timeNow = QDateTime::currentDateTime();
@@ -253,7 +260,7 @@ int64_t BaseInstance::lastTimePlayed() const
QDateTime timeNow = QDateTime::currentDateTime();
return m_timeStarted.secsTo(timeNow);
}
- return settings()->get("lastTimePlayed").toLongLong();
+ return m_settings->get("lastTimePlayed").toLongLong();
}
void BaseInstance::resetTimePlayed()
@@ -272,8 +279,10 @@ QString BaseInstance::instanceRoot() const
return m_rootDir;
}
-SettingsObjectPtr BaseInstance::settings() const
+SettingsObjectPtr BaseInstance::settings()
{
+ loadSpecificSettings();
+
return m_settings;
}
@@ -340,7 +349,7 @@ QString BaseInstance::windowTitle() const
}
// FIXME: why is this here? move it to MinecraftInstance!!!
-QStringList BaseInstance::extraArguments() const
+QStringList BaseInstance::extraArguments()
{
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
}
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 2a94dcc6..3af104e9 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -154,7 +154,7 @@ public:
return level;
};
- virtual QStringList extraArguments() const;
+ virtual QStringList extraArguments();
/// Traits. Normally inside the version, depends on instance implementation.
virtual QSet <QString> traits() const = 0;
@@ -170,9 +170,18 @@ public:
/*!
* \brief Gets this instance's settings object.
* This settings object stores instance-specific settings.
+ *
+ * Note that this method is not const.
+ * It may call loadSpecificSettings() to ensure those are loaded.
+ *
* \return A pointer to this instance's settings object.
*/
- virtual SettingsObjectPtr settings() const;
+ virtual SettingsObjectPtr settings();
+
+ /*!
+ * \brief Loads settings specific to an instance type if they're not already loaded.
+ */
+ virtual void loadSpecificSettings() = 0;
/// returns a valid update task
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0;
@@ -206,7 +215,7 @@ public:
virtual QString instanceConfigFolder() const = 0;
/// get variables this instance exports
- virtual QMap<QString, QString> getVariables() const = 0;
+ virtual QMap<QString, QString> getVariables() = 0;
virtual QString typeName() const = 0;
@@ -268,6 +277,11 @@ public:
protected:
void changeStatus(Status newStatus);
+ SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); };
+
+ bool isSpecificSettingsLoaded() const { return m_specific_settings_loaded; }
+ void setSpecificSettingsLoaded(bool loaded) { m_specific_settings_loaded = loaded; }
+
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
@@ -296,6 +310,10 @@ private: /* data */
bool m_crashed = false;
bool m_hasUpdate = false;
bool m_hasBrokenVersion = false;
+
+ SettingsObjectWeakPtr m_global_settings;
+ bool m_specific_settings_loaded = false;
+
};
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 3811ceaa..1a5579c8 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -766,6 +766,8 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
ui/pages/modplatform/atlauncher/AtlPage.cpp
ui/pages/modplatform/atlauncher/AtlPage.h
+ ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
+ ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
ui/pages/modplatform/ftb/FtbFilterModel.cpp
ui/pages/modplatform/ftb/FtbFilterModel.h
@@ -851,6 +853,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ModDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp
ui/dialogs/ScrollMessageBox.h
+ ui/dialogs/BlockedModsDialog.cpp
+ ui/dialogs/BlockedModsDialog.h
ui/dialogs/ChooseProviderDialog.h
ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ModUpdateDialog.cpp
@@ -960,6 +964,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
+ ui/dialogs/BlockedModsDialog.ui
ui/dialogs/ChooseProviderDialog.ui
)
@@ -990,7 +995,6 @@ target_link_libraries(Launcher_logic
Launcher_murmur2
nbt++
${ZLIB_LIBRARIES}
- optional-bare
tomlc99
BuildConfig
Katabasis
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index ebb4460d..21edbb48 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -35,76 +35,64 @@
#include "FileSystem.h"
+#include <QDebug>
#include <QDir>
#include <QFile>
-#include <QSaveFile>
#include <QFileInfo>
-#include <QDebug>
-#include <QUrl>
+#include <QSaveFile>
#include <QStandardPaths>
#include <QTextStream>
+#include <QUrl>
#if defined Q_OS_WIN32
- #include <windows.h>
- #include <string>
- #include <sys/utime.h>
- #include <winnls.h>
- #include <shobjidl.h>
- #include <objbase.h>
- #include <objidl.h>
- #include <shlguid.h>
- #include <shlobj.h>
+#include <objbase.h>
+#include <objidl.h>
+#include <shlguid.h>
+#include <shlobj.h>
+#include <shobjidl.h>
+#include <sys/utime.h>
+#include <windows.h>
+#include <winnls.h>
+#include <string>
#else
- #include <utime.h>
+#include <utime.h>
#endif
namespace FS {
-void ensureExists(const QDir &dir)
+void ensureExists(const QDir& dir)
{
- if (!QDir().mkpath(dir.absolutePath()))
- {
- throw FileSystemException("Unable to create folder " + dir.dirName() + " (" +
- dir.absolutePath() + ")");
+ if (!QDir().mkpath(dir.absolutePath())) {
+ throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + dir.absolutePath() + ")");
}
}
-void write(const QString &filename, const QByteArray &data)
+void write(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
QSaveFile file(filename);
- if (!file.open(QSaveFile::WriteOnly))
- {
- throw FileSystemException("Couldn't open " + filename + " for writing: " +
- file.errorString());
+ if (!file.open(QSaveFile::WriteOnly)) {
+ throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
- if (data.size() != file.write(data))
- {
- throw FileSystemException("Error writing data to " + filename + ": " +
- file.errorString());
+ if (data.size() != file.write(data)) {
+ throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
}
- if (!file.commit())
- {
- throw FileSystemException("Error while committing data to " + filename + ": " +
- file.errorString());
+ if (!file.commit()) {
+ throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
}
}
-QByteArray read(const QString &filename)
+QByteArray read(const QString& filename)
{
QFile file(filename);
- if (!file.open(QFile::ReadOnly))
- {
- throw FileSystemException("Unable to open " + filename + " for reading: " +
- file.errorString());
+ if (!file.open(QFile::ReadOnly)) {
+ throw FileSystemException("Unable to open " + filename + " for reading: " + file.errorString());
}
const qint64 size = file.size();
QByteArray data(int(size), 0);
const qint64 ret = file.read(data.data(), size);
- if (ret == -1 || ret != size)
- {
- throw FileSystemException("Error reading data from " + filename + ": " +
- file.errorString());
+ if (ret == -1 || ret != size) {
+ throw FileSystemException("Error reading data from " + filename + ": " + file.errorString());
}
return data;
}
@@ -138,12 +126,12 @@ bool ensureFolderPathExists(QString foldernamepath)
return success;
}
-bool copy::operator()(const QString &offset)
+bool copy::operator()(const QString& offset)
{
- //NOTE always deep copy on windows. the alternatives are too messy.
- #if defined Q_OS_WIN32
+// NOTE always deep copy on windows. the alternatives are too messy.
+#if defined Q_OS_WIN32
m_followSymlinks = true;
- #endif
+#endif
auto src = PathCombine(m_src.absolutePath(), offset);
auto dst = PathCombine(m_dst.absolutePath(), offset);
@@ -152,52 +140,39 @@ bool copy::operator()(const QString &offset)
if (!currentSrc.exists())
return false;
- if(!m_followSymlinks && currentSrc.isSymLink())
- {
+ if (!m_followSymlinks && currentSrc.isSymLink()) {
qDebug() << "creating symlink" << src << " - " << dst;
- if (!ensureFilePathExists(dst))
- {
+ if (!ensureFilePathExists(dst)) {
qWarning() << "Cannot create path!";
return false;
}
return QFile::link(currentSrc.symLinkTarget(), dst);
- }
- else if(currentSrc.isFile())
- {
+ } else if (currentSrc.isFile()) {
qDebug() << "copying file" << src << " - " << dst;
- if (!ensureFilePathExists(dst))
- {
+ if (!ensureFilePathExists(dst)) {
qWarning() << "Cannot create path!";
return false;
}
return QFile::copy(src, dst);
- }
- else if(currentSrc.isDir())
- {
+ } else if (currentSrc.isDir()) {
qDebug() << "recursing" << offset;
- if (!ensureFolderPathExists(dst))
- {
+ if (!ensureFolderPathExists(dst)) {
qWarning() << "Cannot create path!";
return false;
}
QDir currentDir(src);
- for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
- {
+ for (auto& f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) {
auto inner_offset = PathCombine(offset, f);
// ignore and skip stuff that matches the blacklist.
- if(m_blacklist && m_blacklist->matches(inner_offset))
- {
+ if (m_blacklist && m_blacklist->matches(inner_offset)) {
continue;
}
- if(!operator()(inner_offset))
- {
+ if (!operator()(inner_offset)) {
qWarning() << "Failed to copy" << inner_offset;
return false;
}
}
- }
- else
- {
+ } else {
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
return false;
}
@@ -208,55 +183,41 @@ bool deletePath(QString path)
{
bool OK = true;
QFileInfo finfo(path);
- if(finfo.isFile()) {
+ if (finfo.isFile()) {
return QFile::remove(path);
}
QDir dir(path);
- if (!dir.exists())
- {
+ if (!dir.exists()) {
return OK;
}
- auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
- QDir::AllDirs | QDir::Files,
- QDir::DirsFirst);
+ auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
- for(auto & info: allEntries)
- {
+ for (auto& info : allEntries) {
#if defined Q_OS_WIN32
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
auto wString = nativePath.toStdWString();
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
// Windows: check for junctions, reparse points and other nasty things of that sort
- if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
- {
- if (info.isFile())
- {
+ if (dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) {
+ if (info.isFile()) {
OK &= QFile::remove(info.absoluteFilePath());
- }
- else if (info.isDir())
- {
+ } else if (info.isDir()) {
OK &= dir.rmdir(info.absoluteFilePath());
}
}
#else
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
- if(info.isSymLink())
- {
+ if (info.isSymLink()) {
OK &= QFile::remove(info.absoluteFilePath());
}
#endif
- else if (info.isDir())
- {
+ else if (info.isDir()) {
OK &= deletePath(info.absoluteFilePath());
- }
- else if (info.isFile())
- {
+ } else if (info.isFile()) {
OK &= QFile::remove(info.absoluteFilePath());
- }
- else
- {
+ } else {
OK = false;
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
}
@@ -265,22 +226,30 @@ bool deletePath(QString path)
return OK;
}
+bool trash(QString path, QString *pathInTrash = nullptr)
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
+ return false;
+#else
+ return QFile::moveToTrash(path, pathInTrash);
+#endif
+}
-QString PathCombine(const QString & path1, const QString & path2)
+QString PathCombine(const QString& path1, const QString& path2)
{
- if(!path1.size())
+ if (!path1.size())
return path2;
- if(!path2.size())
+ if (!path2.size())
return path1;
return QDir::cleanPath(path1 + QDir::separator() + path2);
}
-QString PathCombine(const QString & path1, const QString & path2, const QString & path3)
+QString PathCombine(const QString& path1, const QString& path2, const QString& path3)
{
return PathCombine(PathCombine(path1, path2), path3);
}
-QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4)
+QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4)
{
return PathCombine(PathCombine(path1, path2, path3), path4);
}
@@ -292,17 +261,14 @@ QString AbsolutePath(QString path)
QString ResolveExecutable(QString path)
{
- if (path.isEmpty())
- {
+ if (path.isEmpty()) {
return QString();
}
- if(!path.contains('/'))
- {
+ if (!path.contains('/')) {
path = QStandardPaths::findExecutable(path);
}
QFileInfo pathInfo(path);
- if(!pathInfo.exists() || !pathInfo.isExecutable())
- {
+ if (!pathInfo.exists() || !pathInfo.isExecutable()) {
return QString();
}
return pathInfo.absoluteFilePath();
@@ -322,12 +288,9 @@ QString NormalizePath(QString path)
QDir b(path);
QString newAbsolute = b.absolutePath();
- if (newAbsolute.startsWith(currentAbsolute))
- {
+ if (newAbsolute.startsWith(currentAbsolute)) {
return a.relativeFilePath(newAbsolute);
- }
- else
- {
+ } else {
return newAbsolute;
}
}
@@ -336,10 +299,8 @@ QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{
- for (int i = 0; i < string.length(); i++)
- {
- if (badFilenameChars.contains(string[i]))
- {
+ for (int i = 0; i < string.length(); i++) {
+ if (badFilenameChars.contains(string[i])) {
string[i] = replaceWith;
}
}
@@ -351,15 +312,12 @@ QString DirNameFromString(QString string, QString inDir)
int num = 0;
QString baseName = RemoveInvalidFilenameChars(string, '-');
QString dirName;
- do
- {
- if(num == 0)
- {
+ do {
+ if (num == 0) {
dirName = baseName;
- }
- else
- {
- dirName = baseName + QString::number(num);;
+ } else {
+ dirName = baseName + QString::number(num);
+ ;
}
// If it's over 9000
@@ -383,36 +341,31 @@ bool checkProblemticPathJava(QDir folder)
bool called_coinit = false;
-HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
+HRESULT CreateLink(LPCCH linkPath, LPCWSTR targetPath, LPCWSTR args)
{
HRESULT hres;
- if (!called_coinit)
- {
+ if (!called_coinit) {
hres = CoInitialize(NULL);
called_coinit = true;
- if (!SUCCEEDED(hres))
- {
+ if (!SUCCEEDED(hres)) {
qWarning("Failed to initialize COM. Error 0x%08lX", hres);
return hres;
}
}
- IShellLinkA *link;
- hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
- (LPVOID *)&link);
+ IShellLink* link;
+ hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&link);
- if (SUCCEEDED(hres))
- {
- IPersistFile *persistFile;
+ if (SUCCEEDED(hres)) {
+ IPersistFile* persistFile;
link->SetPath(targetPath);
link->SetArguments(args);
- hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
- if (SUCCEEDED(hres))
- {
+ hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile);
+ if (SUCCEEDED(hres)) {
WCHAR wstr[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
@@ -433,8 +386,7 @@ QString getDesktopDir()
}
// Cross-platform Shortcut creation
-bool createShortCut(QString location, QString dest, QStringList args, QString name,
- QString icon)
+bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon)
{
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
location = PathCombine(location, name + ".desktop");
@@ -459,8 +411,7 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
stream.flush();
f.close();
- f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
- QFileDevice::ExeOther);
+ f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
return true;
#elif defined Q_OS_WIN
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index fd305b01..b46f3281 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -41,29 +41,27 @@
#include <QDir>
#include <QFlags>
-namespace FS
-{
+namespace FS {
-class FileSystemException : public ::Exception
-{
-public:
- FileSystemException(const QString &message) : Exception(message) {}
+class FileSystemException : public ::Exception {
+ public:
+ FileSystemException(const QString& message) : Exception(message) {}
};
/**
* write data to a file safely
*/
-void write(const QString &filename, const QByteArray &data);
+void write(const QString& filename, const QByteArray& data);
/**
* read data from a file safely\
*/
-QByteArray read(const QString &filename);
+QByteArray read(const QString& filename);
/**
* Update the last changed timestamp of an existing file
*/
-bool updateTimestamp(const QString & filename);
+bool updateTimestamp(const QString& filename);
/**
* Creates all the folders in a path for the specified path
@@ -77,35 +75,31 @@ bool ensureFilePathExists(QString filenamepath);
*/
bool ensureFolderPathExists(QString filenamepath);
-class copy
-{
-public:
- copy(const QString & src, const QString & dst)
+class copy {
+ public:
+ copy(const QString& src, const QString& dst)
{
m_src.setPath(src);
m_dst.setPath(dst);
}
- copy & followSymlinks(const bool follow)
+ copy& followSymlinks(const bool follow)
{
m_followSymlinks = follow;
return *this;
}
- copy & blacklist(const IPathMatcher * filter)
+ copy& blacklist(const IPathMatcher* filter)
{
m_blacklist = filter;
return *this;
}
- bool operator()()
- {
- return operator()(QString());
- }
+ bool operator()() { return operator()(QString()); }
-private:
- bool operator()(const QString &offset);
+ private:
+ bool operator()(const QString& offset);
-private:
+ private:
bool m_followSymlinks = true;
- const IPathMatcher * m_blacklist = nullptr;
+ const IPathMatcher* m_blacklist = nullptr;
QDir m_src;
QDir m_dst;
};
@@ -115,9 +109,14 @@ private:
*/
bool deletePath(QString path);
-QString PathCombine(const QString &path1, const QString &path2);
-QString PathCombine(const QString &path1, const QString &path2, const QString &path3);
-QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4);
+/**
+ * Trash a folder / file
+ */
+bool trash(QString path, QString *pathInTrash);
+
+QString PathCombine(const QString& path1, const QString& path2);
+QString PathCombine(const QString& path1, const QString& path2, const QString& path3);
+QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4);
QString AbsolutePath(QString path);
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 14e1cd47..de0afc96 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -60,7 +60,7 @@
#include "net/ChecksumValidator.h"
#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/ScrollMessageBox.h"
+#include "ui/dialogs/BlockedModsDialog.h"
#include <algorithm>
@@ -396,21 +396,24 @@ void InstanceImportTask::processFlame()
auto results = m_modIdResolver->getResults();
//first check for blocked mods
QString text;
+ QList<QUrl> urls;
auto anyBlocked = false;
for(const auto& result: results.files.values()) {
if (!result.resolved || result.url.isEmpty()) {
text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
+ urls.append(QUrl(result.websiteUrl));
anyBlocked = true;
}
}
if(anyBlocked) {
qWarning() << "Blocked mods found, displaying mod list";
- auto message_dialog = new ScrollMessageBox(m_parent,
+ auto message_dialog = new BlockedModsDialog(m_parent,
tr("Blocked mods found"),
tr("The following mods were blocked on third party launchers.<br/>"
"You will need to manually download them and add them to the modpack"),
- text);
+ text,
+ urls);
message_dialog->setModal(true);
if (message_dialog->exec()) {
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index b67d48f3..48ba2161 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -44,7 +44,7 @@
#include "QObjectPtr.h"
#include "modplatform/flame/PackManifest.h"
-#include <nonstd/optional>
+#include <optional>
class QuaZip;
namespace Flame
@@ -90,8 +90,8 @@ private: /* data */
QString m_archivePath;
bool m_downloadRequired = false;
std::unique_ptr<QuaZip> m_packZip;
- QFuture<nonstd::optional<QStringList>> m_extractFuture;
- QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
+ QFuture<std::optional<QStringList>> m_extractFuture;
+ QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
QVector<Flame::File> m_blockedMods;
enum class ModpackType{
Unknown,
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index fb7103dd..4447a17c 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -33,30 +33,32 @@
* limitations under the License.
*/
+#include <QDebug>
#include <QDir>
#include <QDirIterator>
-#include <QSet>
#include <QFile>
-#include <QThread>
-#include <QTextStream>
-#include <QXmlStreamReader>
-#include <QTimer>
-#include <QDebug>
#include <QFileSystemWatcher>
-#include <QUuid>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMimeData>
+#include <QSet>
+#include <QStack>
+#include <QPair>
+#include <QTextStream>
+#include <QThread>
+#include <QTimer>
+#include <QUuid>
+#include <QXmlStreamReader>
-#include "InstanceList.h"
#include "BaseInstance.h"
+#include "ExponentialSeries.h"
+#include "FileSystem.h"
+#include "InstanceList.h"
#include "InstanceTask.h"
-#include "settings/INISettingsObject.h"
#include "NullInstance.h"
-#include "minecraft/MinecraftInstance.h"
-#include "FileSystem.h"
-#include "ExponentialSeries.h"
#include "WatchLock.h"
+#include "minecraft/MinecraftInstance.h"
+#include "settings/INISettingsObject.h"
#ifdef Q_OS_WIN32
#include <Windows.h>
@@ -64,13 +66,12 @@
const static int GROUP_FILE_FORMAT_VERSION = 1;
-InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent)
+InstanceList::InstanceList(SettingsObjectPtr settings, const QString& instDir, QObject* parent)
: QAbstractListModel(parent), m_globalSettings(settings)
{
resumeWatch();
// Create aand normalize path
- if (!QDir::current().exists(instDir))
- {
+ if (!QDir::current().exists(instDir)) {
QDir::current().mkpath(instDir);
}
@@ -83,9 +84,7 @@ InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir,
m_watcher->addPath(m_instDir);
}
-InstanceList::~InstanceList()
-{
-}
+InstanceList::~InstanceList() {}
Qt::DropActions InstanceList::supportedDragActions() const
{
@@ -99,7 +98,7 @@ Qt::DropActions InstanceList::supportedDropActions() const
bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
{
- if(data && data->hasFormat("application/x-instanceid")) {
+ if (data && data->hasFormat("application/x-instanceid")) {
return true;
}
return false;
@@ -107,7 +106,7 @@ bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action,
bool InstanceList::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
{
- if(data && data->hasFormat("application/x-instanceid")) {
+ if (data && data->hasFormat("application/x-instanceid")) {
return true;
}
return false;
@@ -120,35 +119,33 @@ QStringList InstanceList::mimeTypes() const
return types;
}
-QMimeData * InstanceList::mimeData(const QModelIndexList& indexes) const
+QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const
{
auto mimeData = QAbstractListModel::mimeData(indexes);
- if(indexes.size() == 1) {
+ if (indexes.size() == 1) {
auto instanceId = data(indexes[0], InstanceIDRole).toString();
mimeData->setData("application/x-instanceid", instanceId.toUtf8());
}
return mimeData;
}
-
-int InstanceList::rowCount(const QModelIndex &parent) const
+int InstanceList::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return m_instances.count();
}
-QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const
+QModelIndex InstanceList::index(int row, int column, const QModelIndex& parent) const
{
Q_UNUSED(parent);
if (row < 0 || row >= m_instances.size())
return QModelIndex();
- return createIndex(row, column, (void *)m_instances.at(row).get());
+ return createIndex(row, column, (void*)m_instances.at(row).get());
}
-QVariant InstanceList::data(const QModelIndex &index, int role) const
+QVariant InstanceList::data(const QModelIndex& index, int role) const
{
- if (!index.isValid())
- {
+ if (!index.isValid()) {
return QVariant();
}
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
@@ -193,29 +190,25 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
{
- if (!index.isValid())
- {
+ if (!index.isValid()) {
return false;
}
- if(role != Qt::EditRole)
- {
+ if (role != Qt::EditRole) {
return false;
}
- BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
+ BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
auto newName = value.toString();
- if(pdata->name() == newName)
- {
+ if (pdata->name() == newName) {
return true;
}
pdata->setName(newName);
return true;
}
-Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
+Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
{
Qt::ItemFlags f;
- if (index.isValid())
- {
+ if (index.isValid()) {
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
}
return f;
@@ -224,13 +217,11 @@ Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
{
auto inst = getInstanceById(id);
- if(!inst)
- {
+ if (!inst) {
return GroupId();
}
auto iter = m_instanceGroupIndex.find(inst->id());
- if(iter != m_instanceGroupIndex.end())
- {
+ if (iter != m_instanceGroupIndex.end()) {
return *iter;
}
return GroupId();
@@ -239,33 +230,27 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
{
auto inst = getInstanceById(id);
- if(!inst)
- {
+ if (!inst) {
qDebug() << "Attempt to set a null instance's group";
return;
}
bool changed = false;
auto iter = m_instanceGroupIndex.find(inst->id());
- if(iter != m_instanceGroupIndex.end())
- {
- if(*iter != name)
- {
+ if (iter != m_instanceGroupIndex.end()) {
+ if (*iter != name) {
*iter = name;
changed = true;
}
- }
- else
- {
+ } else {
changed = true;
m_instanceGroupIndex[id] = name;
}
- if(changed)
- {
+ if (changed) {
m_groupNameCache.insert(name);
auto idx = getInstIndex(inst.get());
- emit dataChanged(index(idx), index(idx), {GroupRole});
+ emit dataChanged(index(idx), index(idx), { GroupRole });
saveGroupList();
}
}
@@ -279,24 +264,20 @@ void InstanceList::deleteGroup(const QString& name)
{
bool removed = false;
qDebug() << "Delete group" << name;
- for(auto & instance: m_instances)
- {
- const auto & instID = instance->id();
+ for (auto& instance : m_instances) {
+ const auto& instID = instance->id();
auto instGroupName = getInstanceGroup(instID);
- if(instGroupName == name)
- {
+ if (instGroupName == name) {
m_instanceGroupIndex.remove(instID);
qDebug() << "Remove" << instID << "from group" << name;
removed = true;
auto idx = getInstIndex(instance.get());
- if(idx > 0)
- {
- emit dataChanged(index(idx), index(idx), {GroupRole});
+ if (idx > 0) {
+ emit dataChanged(index(idx), index(idx), { GroupRole });
}
}
}
- if(removed)
- {
+ if (removed) {
saveGroupList();
}
}
@@ -306,23 +287,75 @@ bool InstanceList::isGroupCollapsed(const QString& group)
return m_collapsedGroups.contains(group);
}
+bool InstanceList::trashInstance(const InstanceId& id)
+{
+ auto inst = getInstanceById(id);
+ if (!inst) {
+ qDebug() << "Cannot trash instance" << id << ". No such instance is present (deleted externally?).";
+ return false;
+ }
+
+ auto cachedGroupId = m_instanceGroupIndex[id];
+
+ qDebug() << "Will trash instance" << id;
+ QString trashedLoc;
+
+ if (m_instanceGroupIndex.remove(id)) {
+ saveGroupList();
+ }
+
+ if (!FS::trash(inst->instanceRoot(), &trashedLoc)) {
+ qDebug() << "Trash of instance" << id << "has not been completely successfully...";
+ return false;
+ }
+
+ qDebug() << "Instance" << id << "has been trashed by the launcher.";
+ m_trashHistory.push({id, inst->instanceRoot(), trashedLoc, cachedGroupId});
+
+ return true;
+}
+
+bool InstanceList::trashedSomething() {
+ return !m_trashHistory.empty();
+}
+
+void InstanceList::undoTrashInstance() {
+ if (m_trashHistory.empty()) {
+ qWarning() << "Nothing to recover from trash.";
+ return;
+ }
+
+ auto top = m_trashHistory.pop();
+
+ while (QDir(top.polyPath).exists()) {
+ top.id += "1";
+ top.polyPath += "1";
+ }
+
+ qDebug() << "Moving" << top.trashPath << "back to" << top.polyPath;
+ QFile(top.trashPath).rename(top.polyPath);
+
+ m_instanceGroupIndex[top.id] = top.groupName;
+ m_groupNameCache.insert(top.groupName);
+
+ saveGroupList();
+ emit instancesChanged();
+}
+
void InstanceList::deleteInstance(const InstanceId& id)
{
auto inst = getInstanceById(id);
- if(!inst)
- {
+ if (!inst) {
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
return;
}
- if(m_instanceGroupIndex.remove(id))
- {
+ if (m_instanceGroupIndex.remove(id)) {
saveGroupList();
}
qDebug() << "Will delete instance" << id;
- if(!FS::deletePath(inst->instanceRoot()))
- {
+ if (!FS::deletePath(inst->instanceRoot())) {
qWarning() << "Deletion of instance" << id << "has not been completely successful ...";
return;
}
@@ -330,15 +363,13 @@ void InstanceList::deleteInstance(const InstanceId& id)
qDebug() << "Instance" << id << "has been deleted by the launcher.";
}
-static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list)
+static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr>& list)
{
QMap<InstanceId, InstanceLocator> out;
int i = 0;
- for(auto & item: list)
- {
+ for (auto& item : list) {
auto id = item->id();
- if(out.contains(id))
- {
+ if (out.contains(id)) {
qWarning() << "Duplicate ID" << id << "in instance list";
}
out[id] = std::make_pair(item, i);
@@ -347,24 +378,21 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &
return out;
}
-QList< InstanceId > InstanceList::discoverInstances()
+QList<InstanceId> InstanceList::discoverInstances()
{
qDebug() << "Discovering instances in" << m_instDir;
QList<InstanceId> out;
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
- while (iter.hasNext())
- {
+ while (iter.hasNext()) {
QString subDir = iter.next();
QFileInfo dirInfo(subDir);
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
continue;
// if it is a symlink, ignore it if it goes to the instance folder
- if(dirInfo.isSymLink())
- {
+ if (dirInfo.isSymLink()) {
QFileInfo targetInfo(dirInfo.symLinkTarget());
QFileInfo instDirInfo(m_instDir);
- if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
- {
+ if (targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) {
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
continue;
}
@@ -388,74 +416,56 @@ InstanceList::InstListError InstanceList::loadList()
QList<InstancePtr> newList;
- for(auto & id: discoverInstances())
- {
- if(existingIds.contains(id))
- {
+ for (auto& id : discoverInstances()) {
+ if (existingIds.contains(id)) {
auto instPair = existingIds[id];
existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id;
- }
- else
- {
+ } else {
InstancePtr instPtr = loadInstance(id);
- if(instPtr)
- {
+ if (instPtr) {
newList.append(instPtr);
}
}
}
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
- if(!existingIds.isEmpty())
- {
+ if (!existingIds.isEmpty()) {
// get the list of removed instances and sort it by their original index, from last to first
auto deadList = existingIds.values();
- auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool
- {
- return a.second > b.second;
- };
+ auto orderSortPredicate = [](const InstanceLocator& a, const InstanceLocator& b) -> bool { return a.second > b.second; };
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
// remove the contiguous ranges of rows
int front_bookmark = -1;
int back_bookmark = -1;
int currentItem = -1;
- auto removeNow = [&]()
- {
+ auto removeNow = [&]() {
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
endRemoveRows();
front_bookmark = -1;
back_bookmark = currentItem;
};
- for(auto & removedItem: deadList)
- {
+ for (auto& removedItem : deadList) {
auto instPtr = removedItem.first;
instPtr->invalidate();
currentItem = removedItem.second;
- if(back_bookmark == -1)
- {
+ if (back_bookmark == -1) {
// no bookmark yet
back_bookmark = currentItem;
- }
- else if(currentItem == front_bookmark - 1)
- {
+ } else if (currentItem == front_bookmark - 1) {
// part of contiguous sequence, continue
- }
- else
- {
+ } else {
// seam between previous and current item
removeNow();
}
front_bookmark = currentItem;
}
- if(back_bookmark != -1)
- {
+ if (back_bookmark != -1) {
removeNow();
}
}
- if(newList.size())
- {
+ if (newList.size()) {
add(newList);
}
m_dirty = false;
@@ -466,26 +476,23 @@ InstanceList::InstListError InstanceList::loadList()
void InstanceList::updateTotalPlayTime()
{
totalPlayTime = 0;
- for(auto const& itr : m_instances)
- {
+ for (auto const& itr : m_instances) {
totalPlayTime += itr.get()->totalTimePlayed();
}
}
void InstanceList::saveNow()
{
- for(auto & item: m_instances)
- {
+ for (auto& item : m_instances) {
item->saveNow();
}
}
-void InstanceList::add(const QList<InstancePtr> &t)
+void InstanceList::add(const QList<InstancePtr>& t)
{
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
m_instances.append(t);
- for(auto & ptr : t)
- {
+ for (auto& ptr : t) {
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
}
endInsertRows();
@@ -493,69 +500,61 @@ void InstanceList::add(const QList<InstancePtr> &t)
void InstanceList::resumeWatch()
{
- if(m_watchLevel > 0)
- {
+ if (m_watchLevel > 0) {
qWarning() << "Bad suspend level resume in instance list";
return;
}
m_watchLevel++;
- if(m_watchLevel > 0 && m_dirty)
- {
+ if (m_watchLevel > 0 && m_dirty) {
loadList();
}
}
void InstanceList::suspendWatch()
{
- m_watchLevel --;
+ m_watchLevel--;
}
void InstanceList::providerUpdated()
{
m_dirty = true;
- if(m_watchLevel == 1)
- {
+ if (m_watchLevel == 1) {
loadList();
}
}
InstancePtr InstanceList::getInstanceById(QString instId) const
{
- if(instId.isEmpty())
+ if (instId.isEmpty())
return InstancePtr();
- for(auto & inst: m_instances)
- {
- if (inst->id() == instId)
- {
+ for (auto& inst : m_instances) {
+ if (inst->id() == instId) {
return inst;
}
}
return InstancePtr();
}
-QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
+QModelIndex InstanceList::getInstanceIndexById(const QString& id) const
{
return index(getInstIndex(getInstanceById(id).get()));
}
-int InstanceList::getInstIndex(BaseInstance *inst) const
+int InstanceList::getInstIndex(BaseInstance* inst) const
{
int count = m_instances.count();
- for (int i = 0; i < count; i++)
- {
- if (inst == m_instances[i].get())
- {
+ for (int i = 0; i < count; i++) {
+ if (inst == m_instances[i].get()) {
return i;
}
}
return -1;
}
-void InstanceList::propertiesChanged(BaseInstance *inst)
+void InstanceList::propertiesChanged(BaseInstance* inst)
{
int i = getInstIndex(inst);
- if (i != -1)
- {
+ if (i != -1) {
emit dataChanged(index(i), index(i));
updateTotalPlayTime();
}
@@ -563,8 +562,7 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
InstancePtr InstanceList::loadInstance(const InstanceId& id)
{
- if(!m_groupsLoaded)
- {
+ if (!m_groupsLoaded) {
loadGroupList();
}
@@ -592,50 +590,42 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
void InstanceList::saveGroupList()
{
qDebug() << "Will save group list now.";
- if(!m_instancesProbed)
- {
+ if (!m_instancesProbed) {
qDebug() << "Group saving prevented because we don't know the full list of instances yet.";
return;
}
WatchLock foo(m_watcher, m_instDir);
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> reverseGroupMap;
- for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++)
- {
+ for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
QString id = iter.key();
QString group = iter.value();
if (group.isEmpty())
continue;
- if(!instanceSet.contains(id))
- {
+ if (!instanceSet.contains(id)) {
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
continue;
}
- if (!reverseGroupMap.count(group))
- {
+ if (!reverseGroupMap.count(group)) {
QSet<QString> set;
set.insert(id);
reverseGroupMap[group] = set;
- }
- else
- {
- QSet<QString> &set = reverseGroupMap[group];
+ } else {
+ QSet<QString>& set = reverseGroupMap[group];
set.insert(id);
}
}
QJsonObject toplevel;
toplevel.insert("formatVersion", QJsonValue(QString("1")));
QJsonObject groupsArr;
- for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
- {
+ for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++) {
auto list = iter.value();
auto name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
- for (auto item : list)
- {
+ for (auto item : list) {
instanceArr.append(QJsonValue(item));
}
groupObj.insert("instances", instanceArr);
@@ -643,13 +633,10 @@ void InstanceList::saveGroupList()
}
toplevel.insert("groups", groupsArr);
QJsonDocument doc(toplevel);
- try
- {
+ try {
FS::write(groupFileName, doc.toJson());
qDebug() << "Group list saved.";
- }
- catch (const FS::FileSystemException &e)
- {
+ } catch (const FS::FileSystemException& e) {
qCritical() << "Failed to write instance group file :" << e.cause();
}
}
@@ -665,12 +652,9 @@ void InstanceList::loadGroupList()
return;
QByteArray jsonData;
- try
- {
+ try {
jsonData = FS::read(groupFileName);
- }
- catch (const FS::FileSystemException &e)
- {
+ } catch (const FS::FileSystemException& e) {
qCritical() << "Failed to read instance group file :" << e.cause();
return;
}
@@ -679,17 +663,15 @@ void InstanceList::loadGroupList()
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
// if the json was bad, fail
- if (error.error != QJsonParseError::NoError)
- {
+ if (error.error != QJsonParseError::NoError) {
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
- .arg(error.errorString(), QString::number(error.offset))
- .toUtf8();
+ .arg(error.errorString(), QString::number(error.offset))
+ .toUtf8();
return;
}
// if the root of the json wasn't an object, fail
- if (!jsonDoc.isObject())
- {
+ if (!jsonDoc.isObject()) {
qWarning() << "Invalid group file. Root entry should be an object.";
return;
}
@@ -701,8 +683,7 @@ void InstanceList::loadGroupList()
return;
// Get the groups. if it's not an object, fail
- if (!rootObj.value("groups").isObject())
- {
+ if (!rootObj.value("groups").isObject()) {
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
return;
}
@@ -712,21 +693,20 @@ void InstanceList::loadGroupList()
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
- for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
- {
+ for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
QString groupName = iter.key();
// If not an object, complain and skip to the next one.
- if (!iter.value().isObject())
- {
+ if (!iter.value().isObject()) {
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
continue;
}
QJsonObject groupObj = iter.value().toObject();
- if (!groupObj.value("instances").isArray())
- {
- qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.").arg(groupName).toUtf8();
+ if (!groupObj.value("instances").isArray()) {
+ qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.")
+ .arg(groupName)
+ .toUtf8();
continue;
}
@@ -734,15 +714,14 @@ void InstanceList::loadGroupList()
groupSet.insert(groupName);
auto hidden = groupObj.value("hidden").toBool(false);
- if(hidden) {
+ if (hidden) {
m_collapsedGroups.insert(groupName);
}
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
- for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++)
- {
+ for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
m_instanceGroupIndex[(*iter2).toString()] = groupName;
}
}
@@ -757,13 +736,11 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
emit instancesChanged();
}
-void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
+void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
{
QString newInstDir = QDir(value.toString()).canonicalPath();
- if(newInstDir != m_instDir)
- {
- if(m_groupsLoaded)
- {
+ if (newInstDir != m_instDir) {
+ if (m_groupsLoaded) {
saveGroupList();
}
m_instDir = newInstDir;
@@ -775,7 +752,7 @@ void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
{
qDebug() << "Group" << group << (collapsed ? "collapsed" : "expanded");
- if(collapsed) {
+ if (collapsed) {
m_collapsedGroups.insert(group);
} else {
m_collapsedGroups.remove(group);
@@ -783,19 +760,14 @@ void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
saveGroupList();
}
-class InstanceStaging : public Task
-{
-Q_OBJECT
+class InstanceStaging : public Task {
+ Q_OBJECT
const unsigned minBackoff = 1;
const unsigned maxBackoff = 16;
-public:
- InstanceStaging (
- InstanceList * parent,
- Task * child,
- const QString & stagingPath,
- const QString& instanceName,
- const QString& groupName )
- : backoff(minBackoff, maxBackoff)
+
+ public:
+ InstanceStaging(InstanceList* parent, Task* child, const QString& stagingPath, const QString& instanceName, const QString& groupName)
+ : backoff(minBackoff, maxBackoff)
{
m_parent = parent;
m_child.reset(child);
@@ -810,62 +782,51 @@ public:
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
}
- virtual ~InstanceStaging() {};
-
+ virtual ~InstanceStaging(){};
// FIXME/TODO: add ability to abort during instance commit retries
bool abort() override
{
- if(m_child && m_child->canAbort())
- {
+ if (m_child && m_child->canAbort()) {
return m_child->abort();
}
return false;
}
bool canAbort() const override
{
- if(m_child && m_child->canAbort())
- {
+ if (m_child && m_child->canAbort()) {
return true;
}
return false;
}
-protected:
- virtual void executeTask() override
- {
- m_child->start();
- }
- QStringList warnings() const override
- {
- return m_child->warnings();
- }
+ protected:
+ virtual void executeTask() override { m_child->start(); }
+ QStringList warnings() const override { return m_child->warnings(); }
-private slots:
+ private slots:
void childSucceded()
{
unsigned sleepTime = backoff();
- if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName))
- {
+ if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) {
emitSucceeded();
return;
}
// we actually failed, retry?
- if(sleepTime == maxBackoff)
- {
+ if (sleepTime == maxBackoff) {
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
return;
}
qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime;
m_backoffTimer.start(sleepTime * 500);
}
- void childFailed(const QString & reason)
+ void childFailed(const QString& reason)
{
m_parent->destroyStagingPath(m_stagingPath);
emitFailed(reason);
}
-private:
+ private:
/*
* WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows.
* Basically, it starts messing things up while the launcher is extracting/creating instances
@@ -873,14 +834,14 @@ private:
*/
ExponentialSeries backoff;
QString m_stagingPath;
- InstanceList * m_parent;
+ InstanceList* m_parent;
unique_qobject_ptr<Task> m_child;
QString m_instanceName;
QString m_groupName;
QTimer m_backoffTimer;
};
-Task * InstanceList::wrapInstanceTask(InstanceTask * task)
+Task* InstanceList::wrapInstanceTask(InstanceTask* task)
{
auto stagingPath = getStagedInstancePath();
task->setStagingPath(stagingPath);
@@ -895,8 +856,7 @@ QString InstanceList::getStagedInstancePath()
QString relPath = FS::PathCombine(tempDir, key);
QDir rootPath(m_instDir);
auto path = FS::PathCombine(m_instDir, relPath);
- if(!rootPath.mkpath(relPath))
- {
+ if (!rootPath.mkpath(relPath)) {
return QString();
}
#ifdef Q_OS_WIN32
@@ -913,8 +873,7 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst
{
WatchLock lock(m_watcher, m_instDir);
QString destination = FS::PathCombine(m_instDir, instID);
- if(!dir.rename(path, destination))
- {
+ if (!dir.rename(path, destination)) {
qWarning() << "Failed to move" << path << "to" << destination;
return false;
}
@@ -933,7 +892,8 @@ bool InstanceList::destroyStagingPath(const QString& keyPath)
return FS::deletePath(keyPath);
}
-int InstanceList::getTotalPlayTime() {
+int InstanceList::getTotalPlayTime()
+{
updateTotalPlayTime();
return totalPlayTime;
}
diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h
index bc6c3af0..62282f04 100644
--- a/launcher/InstanceList.h
+++ b/launcher/InstanceList.h
@@ -19,6 +19,8 @@
#include <QAbstractListModel>
#include <QSet>
#include <QList>
+#include <QStack>
+#include <QPair>
#include "BaseInstance.h"
@@ -46,6 +48,12 @@ enum class GroupsState
Dirty
};
+struct TrashHistoryItem {
+ QString id;
+ QString polyPath;
+ QString trashPath;
+ QString groupName;
+};
class InstanceList : public QAbstractListModel
{
@@ -102,6 +110,9 @@ public:
void setInstanceGroup(const InstanceId & id, const GroupId& name);
void deleteGroup(const GroupId & name);
+ bool trashInstance(const InstanceId &id);
+ bool trashedSomething();
+ void undoTrashInstance();
void deleteInstance(const InstanceId & id);
// Wrap an instance creation task in some more task machinery and make it ready to be used
@@ -180,4 +191,6 @@ private:
QSet<InstanceId> instanceSet;
bool m_groupsLoaded = false;
bool m_instancesProbed = false;
+
+ QStack<TrashHistoryItem> m_trashHistory;
};
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index d36ee3fe..11f9b2bb 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -145,16 +145,26 @@ void LaunchController::login() {
return;
}
- // we try empty password first :)
- QString password;
// we loop until the user succeeds in logging in or gives up
bool tryagain = true;
- // the failure. the default failure.
- const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again. <br /> <br /> This could be caused by a password change.");
- QString failReason = needLoginAgain;
+ unsigned int tries = 0;
while (tryagain)
{
+ if (tries > 0 && tries % 3 == 0) {
+ auto result = QMessageBox::question(
+ m_parentWidget,
+ tr("Continue launch?"),
+ tr("It looks like we couldn't launch after %1 tries. Do you want to continue trying?")
+ .arg(tries)
+ );
+
+ if (result == QMessageBox::No) {
+ emitAborted();
+ return;
+ }
+ }
+ tries++;
m_session = std::make_shared<AuthSession>();
m_session->wants_online = m_online;
m_accountToUse->fillSession(m_session);
diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
index fbdeed8f..6447f5c6 100644
--- a/launcher/LoggedProcess.cpp
+++ b/launcher/LoggedProcess.cpp
@@ -34,8 +34,9 @@
*/
#include "LoggedProcess.h"
-#include "MessageLevel.h"
#include <QDebug>
+#include <QTextDecoder>
+#include "MessageLevel.h"
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
{
@@ -59,25 +60,26 @@ LoggedProcess::~LoggedProcess()
}
}
-QStringList reprocess(const QByteArray & data, QString & leftover)
+QStringList reprocess(const QByteArray& data, QTextDecoder& decoder)
{
- QString str = leftover + QString::fromLocal8Bit(data);
-
- str.remove('\r');
- QStringList lines = str.split("\n");
- leftover = lines.takeLast();
+ auto str = decoder.toUnicode(data);
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+ auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
+#else
+ auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
+#endif
return lines;
}
void LoggedProcess::on_stdErr()
{
- auto lines = reprocess(readAllStandardError(), m_err_leftover);
+ auto lines = reprocess(readAllStandardError(), m_err_decoder);
emit log(lines, MessageLevel::StdErr);
}
void LoggedProcess::on_stdOut()
{
- auto lines = reprocess(readAllStandardOutput(), m_out_leftover);
+ auto lines = reprocess(readAllStandardOutput(), m_out_decoder);
emit log(lines, MessageLevel::StdOut);
}
@@ -86,18 +88,6 @@ void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
// save the exit code
m_exit_code = exit_code;
- // Flush console window
- if (!m_err_leftover.isEmpty())
- {
- emit log({m_err_leftover}, MessageLevel::StdErr);
- m_err_leftover.clear();
- }
- if (!m_out_leftover.isEmpty())
- {
- emit log({m_err_leftover}, MessageLevel::StdOut);
- m_out_leftover.clear();
- }
-
// based on state, send signals
if (!m_is_aborting)
{
diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h
index 61e74bd9..2360d1ea 100644
--- a/launcher/LoggedProcess.h
+++ b/launcher/LoggedProcess.h
@@ -36,6 +36,7 @@
#pragma once
#include <QProcess>
+#include <QTextDecoder>
#include "MessageLevel.h"
/*
@@ -88,8 +89,8 @@ private:
void changeState(LoggedProcess::State state);
private:
- QString m_err_leftover;
- QString m_out_leftover;
+ QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
+ QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
bool m_killed = false;
State m_state = NotRunning;
int m_exit_code = 0;
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index 1627ee07..04ca5094 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -141,9 +141,10 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
QSet<QString> addedFiles;
// Modify the jar
- for (auto i = mods.constEnd(); i != mods.constBegin(); --i)
+ // This needs to be done in reverse-order to ensure we respect the loading order of components
+ for (auto i = mods.crbegin(); i != mods.crend(); i++)
{
- const Mod* mod = *i;
+ const auto* mod = *i;
// do not merge disabled mods.
if (!mod->enabled())
continue;
@@ -267,7 +268,7 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re
// ours
-nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
+std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
{
QDir directory(target);
QStringList extracted;
@@ -276,7 +277,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
auto numEntries = zip->getEntriesCount();
if(numEntries < 0) {
qWarning() << "Failed to enumerate files in archive";
- return nonstd::nullopt;
+ return std::nullopt;
}
else if(numEntries == 0) {
qDebug() << "Extracting empty archives seems odd...";
@@ -285,7 +286,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
else if (!zip->goToFirstFile())
{
qWarning() << "Failed to seek to first file in zip";
- return nonstd::nullopt;
+ return std::nullopt;
}
do
@@ -322,7 +323,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
{
qWarning() << "Failed to extract file" << original_name << "to" << absFilePath;
JlCompress::removeFile(extracted);
- return nonstd::nullopt;
+ return std::nullopt;
}
extracted.append(absFilePath);
@@ -340,7 +341,7 @@ bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &tar
}
// ours
-nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
+std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip))
@@ -351,13 +352,13 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
return QStringList();
}
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
- return nonstd::nullopt;
+ return std::nullopt;
}
return MMCZip::extractSubDir(&zip, "", dir);
}
// ours
-nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
+std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip))
@@ -368,7 +369,7 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
return QStringList();
}
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
- return nonstd::nullopt;
+ return std::nullopt;
}
return MMCZip::extractSubDir(&zip, subdir, dir);
}
diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h
index 7f43d158..ce9775bd 100644
--- a/launcher/MMCZip.h
+++ b/launcher/MMCZip.h
@@ -42,7 +42,7 @@
#include <functional>
#include <quazip/JlCompress.h>
-#include <nonstd/optional>
+#include <optional>
namespace MMCZip
{
@@ -95,7 +95,7 @@ namespace MMCZip
/**
* Extract a subdirectory from an archive
*/
- nonstd::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
+ std::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
bool extractRelFile(QuaZip *zip, const QString & file, const QString &target);
@@ -106,7 +106,7 @@ namespace MMCZip
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
- nonstd::optional<QStringList> extractDir(QString fileCompressed, QString dir);
+ std::optional<QStringList> extractDir(QString fileCompressed, QString dir);
/**
* Extract a subdirectory from an archive
@@ -116,7 +116,7 @@ namespace MMCZip
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
- nonstd::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir);
+ std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir);
/**
* Extract a single file from an archive into a directory
diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h
index 9b0a9331..53e64a05 100644
--- a/launcher/NullInstance.h
+++ b/launcher/NullInstance.h
@@ -15,6 +15,10 @@ public:
void saveNow() override
{
}
+ void loadSpecificSettings() override
+ {
+ setSpecificSettingsLoaded(true);
+ }
QString getStatusbarDescription() override
{
return tr("Unknown instance type");
@@ -43,7 +47,7 @@ public:
{
return QProcessEnvironment();
}
- QMap<QString, QString> getVariables() const override
+ QMap<QString, QString> getVariables() override
{
return QMap<QString, QString>();
}
diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h
index 57974939..b1ef1c8d 100644
--- a/launcher/QObjectPtr.h
+++ b/launcher/QObjectPtr.h
@@ -1,89 +1,37 @@
#pragma once
+#include <QObject>
+#include <QSharedPointer>
+
#include <functional>
#include <memory>
-#include <QObject>
-namespace details
-{
-struct DeleteQObjectLater
-{
- void operator()(QObject *obj) const
- {
- obj->deleteLater();
- }
-};
-}
/**
* A unique pointer class with unique pointer semantics intended for derivates of QObject
* Calls deleteLater() instead of destroying the contained object immediately
*/
-template<typename T> using unique_qobject_ptr = std::unique_ptr<T, details::DeleteQObjectLater>;
+template <typename T>
+using unique_qobject_ptr = QScopedPointer<T, QScopedPointerDeleteLater>;
/**
* A shared pointer class with shared pointer semantics intended for derivates of QObject
* Calls deleteLater() instead of destroying the contained object immediately
*/
template <typename T>
-class shared_qobject_ptr
-{
-public:
- shared_qobject_ptr(){}
- shared_qobject_ptr(T * wrap)
- {
- reset(wrap);
- }
- shared_qobject_ptr(const shared_qobject_ptr<T>& other)
- {
- m_ptr = other.m_ptr;
- }
- template<typename Derived>
- shared_qobject_ptr(const shared_qobject_ptr<Derived> &other)
- {
- m_ptr = other.unwrap();
- }
+class shared_qobject_ptr : public QSharedPointer<T> {
+ public:
+ constexpr shared_qobject_ptr() : QSharedPointer<T>() {}
+ constexpr shared_qobject_ptr(T* ptr) : QSharedPointer<T>(ptr, &QObject::deleteLater) {}
+ constexpr shared_qobject_ptr(std::nullptr_t null_ptr) : QSharedPointer<T>(null_ptr, &QObject::deleteLater) {}
-public:
- void reset(T * wrap)
- {
- using namespace std::placeholders;
- m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1));
- }
- void reset(const shared_qobject_ptr<T> &other)
- {
- m_ptr = other.m_ptr;
- }
- void reset()
- {
- m_ptr.reset();
- }
- T * get() const
- {
- return m_ptr.get();
- }
- T * operator->() const
- {
- return m_ptr.get();
- }
- T & operator*() const
- {
- return *m_ptr.get();
- }
- operator bool() const
- {
- return m_ptr.get() != nullptr;
- }
- const std::shared_ptr <T> unwrap() const
+ template <typename Derived>
+ constexpr shared_qobject_ptr(const shared_qobject_ptr<Derived>& other) : QSharedPointer<T>(other)
+ {}
+
+ void reset() { QSharedPointer<T>::reset(); }
+ void reset(const shared_qobject_ptr<T>& other)
{
- return m_ptr;
+ shared_qobject_ptr<T> t(other);
+ this->swap(t);
}
- bool operator==(const shared_qobject_ptr<T>& other) {
- return m_ptr == other.m_ptr;
- }
- bool operator!=(const shared_qobject_ptr<T>& other) {
- return m_ptr != other.m_ptr;
- }
-
-private:
- std::shared_ptr <T> m_ptr;
};
diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp
index 2b19fca0..2f91605b 100644
--- a/launcher/java/JavaUtils.cpp
+++ b/launcher/java/JavaUtils.cpp
@@ -174,11 +174,17 @@ JavaInstallPtr JavaUtils::GetDefaultJava()
QStringList addJavasFromEnv(QList<QString> javas)
{
- QByteArray env = qgetenv("POLYMC_JAVA_PATHS");
+ auto env = qEnvironmentVariable("POLYMC_JAVA_PATHS");
#if defined(Q_OS_WIN32)
- QList<QString> javaPaths = QString::fromLocal8Bit(env).replace("\\", "/").split(QLatin1String(";"));
+ QList<QString> javaPaths = env.replace("\\", "/").split(QLatin1String(";"));
+
+ auto envPath = qEnvironmentVariable("PATH");
+ QList<QString> javaPathsfromPath = envPath.replace("\\", "/").split(QLatin1String(";"));
+ for (QString string : javaPathsfromPath) {
+ javaPaths.append(string + "/javaw.exe");
+ }
#else
- QList<QString> javaPaths = QString::fromLocal8Bit(env).split(QLatin1String(":"));
+ QList<QString> javaPaths = env.split(QLatin1String(":"));
#endif
for(QString i : javaPaths)
{
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 5a6f8de0..c677b677 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -115,6 +115,19 @@ private:
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: BaseInstance(globalSettings, settings, rootDir)
{
+ m_components.reset(new PackProfile(this));
+}
+
+void MinecraftInstance::saveNow()
+{
+ m_components->saveNow();
+}
+
+void MinecraftInstance::loadSpecificSettings()
+{
+ if (isSpecificSettingsLoaded())
+ return;
+
// Java Settings
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
@@ -124,64 +137,58 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
- m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation);
- m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs);
- m_settings->registerOverride(globalSettings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
-
- // special!
- m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation);
- m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation);
- m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation);
-
- // Window Size
- auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
- m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting);
- m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting);
- m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting);
-
- // Memory
- auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
- m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting);
- m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting);
- m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting);
-
- // Minecraft launch method
- auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
- m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
-
- // Native library workarounds
- auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
- m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
- m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
-
- // Peformance related options
- auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
- m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride);
- m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride);
- m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride);
-
- // Game time
- auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
- m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
- m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride);
+ if (auto global_settings = globalSettings()) {
+ m_settings->registerOverride(global_settings->getSetting("JavaPath"), javaOrLocation);
+ m_settings->registerOverride(global_settings->getSetting("JvmArgs"), javaOrArgs);
+ m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
+
+ // special!
+ m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation);
+ m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
+ m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
+
+ // Window Size
+ auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
+ m_settings->registerOverride(global_settings->getSetting("LaunchMaximized"), windowSetting);
+ m_settings->registerOverride(global_settings->getSetting("MinecraftWinWidth"), windowSetting);
+ m_settings->registerOverride(global_settings->getSetting("MinecraftWinHeight"), windowSetting);
+
+ // Memory
+ auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
+ m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting);
+ m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
+ m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
+
+ // Minecraft launch method
+ auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
+ m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride);
+
+ // Native library workarounds
+ auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
+ m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
+ m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
+
+ // Peformance related options
+ auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
+ m_settings->registerOverride(global_settings->getSetting("EnableFeralGamemode"), performanceOverride);
+ m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride);
+ m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride);
+
+ // Miscellaneous
+ auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
+ m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
+ m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
+
+ m_settings->set("InstanceType", "OneSix");
+ }
// Join server on launch, this does not have a global override
m_settings->registerSetting("JoinServerOnLaunch", false);
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
- // Miscellaneous
- auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
- m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
- m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
-
- m_settings->set("InstanceType", "OneSix");
+ qDebug() << "Instance-type specific settings were loaded!";
- m_components.reset(new PackProfile(this));
-}
-
-void MinecraftInstance::saveNow()
-{
- m_components->saveNow();
+ setSpecificSettingsLoaded(true);
}
QString MinecraftInstance::typeName() const
@@ -308,7 +315,7 @@ QDir MinecraftInstance::versionsPath() const
return QDir::current().absoluteFilePath("versions");
}
-QStringList MinecraftInstance::getClassPath() const
+QStringList MinecraftInstance::getClassPath()
{
QStringList jars, nativeJars;
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
@@ -323,7 +330,7 @@ QString MinecraftInstance::getMainClass() const
return profile->getMainClass();
}
-QStringList MinecraftInstance::getNativeJars() const
+QStringList MinecraftInstance::getNativeJars()
{
QStringList jars, nativeJars;
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
@@ -332,7 +339,7 @@ QStringList MinecraftInstance::getNativeJars() const
return nativeJars;
}
-QStringList MinecraftInstance::extraArguments() const
+QStringList MinecraftInstance::extraArguments()
{
auto list = BaseInstance::extraArguments();
auto version = getPackProfile();
@@ -358,7 +365,7 @@ QStringList MinecraftInstance::extraArguments() const
return list;
}
-QStringList MinecraftInstance::javaArguments() const
+QStringList MinecraftInstance::javaArguments()
{
QStringList args;
@@ -415,7 +422,7 @@ QStringList MinecraftInstance::javaArguments() const
return args;
}
-QMap<QString, QString> MinecraftInstance::getVariables() const
+QMap<QString, QString> MinecraftInstance::getVariables()
{
QMap<QString, QString> out;
out.insert("INST_NAME", name());
@@ -943,9 +950,9 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(new CreateGameFolders(pptr));
}
- if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool())
+ if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool())
{
- QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString();
+ QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
}
@@ -1053,10 +1060,10 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
QString MinecraftInstance::launchMethod()
{
- return m_settings->get("MCLaunchMethod").toString();
+ return settings()->get("MCLaunchMethod").toString();
}
-JavaVersion MinecraftInstance::getJavaVersion() const
+JavaVersion MinecraftInstance::getJavaVersion()
{
return JavaVersion(settings()->get("JavaVersion").toString());
}
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index 8e1c67f2..7a75f452 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -20,6 +20,8 @@ public:
virtual ~MinecraftInstance() {};
virtual void saveNow() override;
+ void loadSpecificSettings() override;
+
// FIXME: remove
QString typeName() const override;
// FIXME: remove
@@ -79,15 +81,15 @@ public:
////// Launch stuff //////
Task::Ptr createUpdateTask(Net::Mode mode) override;
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override;
- QStringList extraArguments() const override;
+ QStringList extraArguments() override;
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
QList<Mod*> getJarMods() const;
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
/// get arguments passed to java
- QStringList javaArguments() const;
+ QStringList javaArguments();
/// get variables for launch command variable substitution/environment
- QMap<QString, QString> getVariables() const override;
+ QMap<QString, QString> getVariables() override;
/// create an environment for launching processes
QProcessEnvironment createEnvironment() override;
@@ -103,16 +105,16 @@ public:
QString getStatusbarDescription() override;
// FIXME: remove
- virtual QStringList getClassPath() const;
+ virtual QStringList getClassPath();
// FIXME: remove
- virtual QStringList getNativeJars() const;
+ virtual QStringList getNativeJars();
// FIXME: remove
virtual QString getMainClass() const;
// FIXME: remove
virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const;
- virtual JavaVersion getJavaVersion() const;
+ virtual JavaVersion getJavaVersion();
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp
index 0ce0c347..3a3aa864 100644
--- a/launcher/minecraft/MinecraftUpdate.cpp
+++ b/launcher/minecraft/MinecraftUpdate.cpp
@@ -43,7 +43,7 @@ void MinecraftUpdate::executeTask()
m_tasks.clear();
// create folders
{
- m_tasks.append(std::make_shared<FoldersTask>(m_inst));
+ m_tasks.append(new FoldersTask(m_inst));
}
// add metadata update task if necessary
@@ -53,23 +53,23 @@ void MinecraftUpdate::executeTask()
auto task = components->getCurrentTask();
if(task)
{
- m_tasks.append(task.unwrap());
+ m_tasks.append(task);
}
}
// libraries download
{
- m_tasks.append(std::make_shared<LibrariesTask>(m_inst));
+ m_tasks.append(new LibrariesTask(m_inst));
}
// FML libraries download and copy into the instance
{
- m_tasks.append(std::make_shared<FMLLibrariesTask>(m_inst));
+ m_tasks.append(new FMLLibrariesTask(m_inst));
}
// assets update
{
- m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst));
+ m_tasks.append(new AssetUpdateTask(m_inst));
}
if(!m_preFailure.isEmpty())
diff --git a/launcher/minecraft/MinecraftUpdate.h b/launcher/minecraft/MinecraftUpdate.h
index acf2eb86..c9cf8624 100644
--- a/launcher/minecraft/MinecraftUpdate.h
+++ b/launcher/minecraft/MinecraftUpdate.h
@@ -50,7 +50,7 @@ private:
private:
MinecraftInstance *m_inst = nullptr;
- QList<std::shared_ptr<Task>> m_tasks;
+ QList<Task::Ptr> m_tasks;
QString m_preFailure;
int m_currentTask = -1;
bool m_abort = false;
diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp
index dfcb43d8..90fcf337 100644
--- a/launcher/minecraft/World.cpp
+++ b/launcher/minecraft/World.cpp
@@ -53,12 +53,12 @@
#include <QCoreApplication>
-#include <nonstd/optional>
+#include <optional>
-using nonstd::optional;
-using nonstd::nullopt;
+using std::optional;
+using std::nullopt;
-GameType::GameType(nonstd::optional<int> original):
+GameType::GameType(std::optional<int> original):
original(original)
{
if(!original) {
diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h
index 0f587620..8327253a 100644
--- a/launcher/minecraft/World.h
+++ b/launcher/minecraft/World.h
@@ -16,11 +16,11 @@
#pragma once
#include <QFileInfo>
#include <QDateTime>
-#include <nonstd/optional>
+#include <optional>
struct GameType {
GameType() = default;
- GameType (nonstd::optional<int> original);
+ GameType (std::optional<int> original);
QString toTranslatedString() const;
QString toLogString() const;
@@ -33,7 +33,7 @@ struct GameType {
Adventure,
Spectator
} type = Unknown;
- nonstd::optional<int> original;
+ std::optional<int> original;
};
class World
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index 2b851e18..b3b57c74 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -109,8 +109,10 @@ QStringList AccountList::profileNames() const {
void AccountList::addAccount(const MinecraftAccountPtr account)
{
- // NOTE: Do not allow adding something that's already there
- if(m_accounts.contains(account)) {
+ // NOTE: Do not allow adding something that's already there. We shouldn't let it continue
+ // because of the signal / slot connections after this.
+ if (m_accounts.contains(account)) {
+ qDebug() << "Tried to add account that's already on the accounts list!";
return;
}
@@ -123,6 +125,8 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
if(profileId.size()) {
auto existingAccount = findAccountByProfileId(profileId);
if(existingAccount != -1) {
+ qDebug() << "Replacing old account with a new one with the same profile ID!";
+
MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount];
m_accounts[existingAccount] = account;
if(m_defaultAccount == existingAccountPtr) {
@@ -138,9 +142,12 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
// if we don't have this profileId yet, add the account to the end
int row = m_accounts.count();
+ qDebug() << "Inserting account at index" << row;
+
beginInsertRows(QModelIndex(), row, row);
m_accounts.append(account);
endInsertRows();
+
onListChanged();
}
diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp
index a5c6f542..73d570f1 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -238,7 +238,7 @@ void MinecraftAccount::authFailed(QString reason)
}
bool MinecraftAccount::isActive() const {
- return m_currentTask;
+ return !m_currentTask.isNull();
}
bool MinecraftAccount::shouldRefresh() const {
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 112d219e..d4c5e819 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -63,6 +63,9 @@ void ModFolderModel::startWatching()
if(is_watching)
return;
+ // Remove orphaned metadata next time
+ m_first_folder_load = true;
+
update();
// Watch the mods folder
@@ -113,7 +116,8 @@ bool ModFolderModel::update()
}
auto index_dir = indexDir();
- auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed);
+ auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
+ m_first_folder_load = false;
m_update = task->result();
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index a7d3ece0..3d6efac3 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -172,6 +172,7 @@ protected:
bool interaction_disabled = false;
QDir m_dir;
bool m_is_indexed;
+ bool m_first_folder_load = true;
QMap<QString, int> modsIndex;
QMap<int, LocalModParseTask::ResultPtr> activeTickets;
int nextResolutionTicket = 0;
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
index a2e055ba..015ead80 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
@@ -38,8 +38,8 @@
#include "minecraft/mod/MetadataHandler.h"
-ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed)
- : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result())
+ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan)
+ : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result())
{}
void ModFolderLoadTask::run()
@@ -83,6 +83,19 @@ void ModFolderLoadTask::run()
}
}
+ // Remove orphan metadata to prevent issues
+ // See https://github.com/PolyMC/PolyMC/issues/996
+ if (m_clean_orphan) {
+ QMutableMapIterator<QString, Mod::Ptr> iter(m_result->mods);
+ while (iter.hasNext()) {
+ auto mod = iter.next().value();
+ if (mod->status() == ModStatus::NotInstalled) {
+ mod->destroy(m_index_dir, false);
+ iter.remove();
+ }
+ }
+ }
+
emit succeeded();
}
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
index 0b6bb6cc..1f2015d2 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
@@ -56,7 +56,7 @@ public:
}
public:
- ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed);
+ ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan = false);
void run();
signals:
void succeeded();
@@ -67,5 +67,6 @@ private:
private:
QDir& m_mods_dir, m_index_dir;
bool m_is_indexed;
+ bool m_clean_orphan;
ResultPtr m_result;
};
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
index 0ed0ad29..5ed13470 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
@@ -60,12 +60,13 @@ namespace ATLauncher {
static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version);
-PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version)
+PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode)
{
m_support = support;
m_pack_name = packName;
m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), "");
m_version_name = version;
+ m_install_mode = installMode;
}
bool PackInstallTask::abort()
@@ -117,9 +118,30 @@ void PackInstallTask::onDownloadSucceeded()
}
m_version = version;
- // Display install message if one exists
- if (!m_version.messages.install.isEmpty())
- m_support->displayMessage(m_version.messages.install);
+ // Derived from the installation mode
+ QString message;
+ bool resetDirectory;
+
+ switch (m_install_mode) {
+ case InstallMode::Reinstall:
+ case InstallMode::Update:
+ message = m_version.messages.update;
+ resetDirectory = true;
+ break;
+
+ case InstallMode::Install:
+ message = m_version.messages.install;
+ resetDirectory = false;
+ break;
+
+ default:
+ emitFailed(tr("Unsupported installation mode"));
+ break;
+ }
+
+ // Display message if one exists
+ if (!message.isEmpty())
+ m_support->displayMessage(message);
auto ver = getComponentVersion("net.minecraft", m_version.minecraft);
if (!ver) {
@@ -128,6 +150,10 @@ void PackInstallTask::onDownloadSucceeded()
}
minecraftVersion = ver;
+ if (resetDirectory) {
+ deleteExistingFiles();
+ }
+
if(m_version.noConfigs) {
downloadMods();
}
@@ -143,6 +169,116 @@ void PackInstallTask::onDownloadFailed(QString reason)
emitFailed(reason);
}
+void PackInstallTask::deleteExistingFiles()
+{
+ setStatus(tr("Deleting existing files..."));
+
+ // Setup defaults, as per https://wiki.atlauncher.com/pack-admin/xml/delete
+ VersionDeletes deletes;
+ deletes.folders.append(VersionDelete{ "root", "mods%s%" });
+ deletes.folders.append(VersionDelete{ "root", "configs%s%" });
+ deletes.folders.append(VersionDelete{ "root", "bin%s%" });
+
+ // Setup defaults, as per https://wiki.atlauncher.com/pack-admin/xml/keep
+ VersionKeeps keeps;
+ keeps.files.append(VersionKeep{ "root", "mods%s%PortalGunSounds.pak" });
+ keeps.folders.append(VersionKeep{ "root", "mods%s%rei_minimap%s%" });
+ keeps.folders.append(VersionKeep{ "root", "mods%s%VoxelMods%s%" });
+ keeps.files.append(VersionKeep{ "root", "config%s%NEI.cfg" });
+ keeps.files.append(VersionKeep{ "root", "options.txt" });
+ keeps.files.append(VersionKeep{ "root", "servers.dat" });
+
+ // Merge with version deletes and keeps
+ for (const auto& item : m_version.deletes.files)
+ deletes.files.append(item);
+ for (const auto& item : m_version.deletes.folders)
+ deletes.folders.append(item);
+ for (const auto& item : m_version.keeps.files)
+ keeps.files.append(item);
+ for (const auto& item : m_version.keeps.folders)
+ keeps.folders.append(item);
+
+ auto getPathForBase = [this](const QString& base) {
+ auto minecraftPath = FS::PathCombine(m_stagingPath, "minecraft");
+
+ if (base == "root") {
+ return minecraftPath;
+ }
+ else if (base == "config") {
+ return FS::PathCombine(minecraftPath, "config");
+ }
+ else {
+ qWarning() << "Unrecognised base path" << base;
+ return minecraftPath;
+ }
+ };
+
+ auto convertToSystemPath = [](const QString& path) {
+ auto t = path;
+ t.replace("%s%", QDir::separator());
+ return t;
+ };
+
+ auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) {
+ for (const auto& item : keeps.files) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto path = FS::PathCombine(basePath, targetPath);
+
+ if (fullPath == path) {
+ return true;
+ }
+ }
+
+ for (const auto& item : keeps.folders) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto path = FS::PathCombine(basePath, targetPath);
+
+ if (fullPath.startsWith(path)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ // Keep track of files to delete
+ QSet<QString> filesToDelete;
+
+ for (const auto& item : deletes.files) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto fullPath = FS::PathCombine(basePath, targetPath);
+
+ if (shouldKeep(fullPath))
+ continue;
+
+ filesToDelete.insert(fullPath);
+ }
+
+ for (const auto& item : deletes.folders) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto fullPath = FS::PathCombine(basePath, targetPath);
+
+ QDirIterator it(fullPath, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ auto path = it.next();
+
+ if (shouldKeep(path))
+ continue;
+
+ filesToDelete.insert(path);
+ }
+ }
+
+ // Delete the files
+ for (const auto& item : filesToDelete) {
+ QFile::remove(item);
+ }
+}
+
QString PackInstallTask::getDirForModType(ModType type, QString raw)
{
switch (type) {
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
index f55873e9..a7124d59 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
@@ -46,10 +46,16 @@
#include "minecraft/PackProfile.h"
#include "meta/Version.h"
-#include <nonstd/optional>
+#include <optional>
namespace ATLauncher {
+enum class InstallMode {
+ Install,
+ Reinstall,
+ Update,
+};
+
class UserInteractionSupport {
public:
@@ -75,7 +81,7 @@ class PackInstallTask : public InstanceTask
Q_OBJECT
public:
- explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version);
+ explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode = InstallMode::Install);
virtual ~PackInstallTask(){}
bool canAbort() const override { return true; }
@@ -99,6 +105,7 @@ private:
bool createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
bool createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
+ void deleteExistingFiles();
void installConfigs();
void extractConfigs();
void downloadMods();
@@ -117,6 +124,7 @@ private:
NetJob::Ptr jobPtr;
QByteArray response;
+ InstallMode m_install_mode;
QString m_pack_name;
QString m_pack_safe_name;
QString m_version_name;
@@ -131,8 +139,8 @@ private:
Meta::VersionPtr minecraftVersion;
QMap<QString, Meta::VersionPtr> componentsToInstall;
- QFuture<nonstd::optional<QStringList>> m_extractFuture;
- QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
+ QFuture<std::optional<QStringList>> m_extractFuture;
+ QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
QFuture<bool> m_modExtractFuture;
QFutureWatcher<bool> m_modExtractFutureWatcher;
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
index 3af02a09..5a458f4e 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
@@ -224,6 +224,64 @@ static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a,
a.depends = Json::ensureString(obj, "depends", "");
}
+static void loadVersionKeep(ATLauncher::VersionKeep& k, QJsonObject& obj)
+{
+ k.base = Json::requireString(obj, "base");
+ k.target = Json::requireString(obj, "target");
+}
+
+static void loadVersionKeeps(ATLauncher::VersionKeeps& k, QJsonObject& obj)
+{
+ if (obj.contains("files")) {
+ auto files = Json::requireArray(obj, "files");
+ for (const auto keepRaw : files) {
+ auto keepObj = Json::requireObject(keepRaw);
+ ATLauncher::VersionKeep keep;
+ loadVersionKeep(keep, keepObj);
+ k.files.append(keep);
+ }
+ }
+
+ if (obj.contains("folders")) {
+ auto folders = Json::requireArray(obj, "folders");
+ for (const auto keepRaw : folders) {
+ auto keepObj = Json::requireObject(keepRaw);
+ ATLauncher::VersionKeep keep;
+ loadVersionKeep(keep, keepObj);
+ k.folders.append(keep);
+ }
+ }
+}
+
+static void loadVersionDelete(ATLauncher::VersionDelete& d, QJsonObject& obj)
+{
+ d.base = Json::requireString(obj, "base");
+ d.target = Json::requireString(obj, "target");
+}
+
+static void loadVersionDeletes(ATLauncher::VersionDeletes& d, QJsonObject& obj)
+{
+ if (obj.contains("files")) {
+ auto files = Json::requireArray(obj, "files");
+ for (const auto deleteRaw : files) {
+ auto deleteObj = Json::requireObject(deleteRaw);
+ ATLauncher::VersionDelete versionDelete;
+ loadVersionDelete(versionDelete, deleteObj);
+ d.files.append(versionDelete);
+ }
+ }
+
+ if (obj.contains("folders")) {
+ auto folders = Json::requireArray(obj, "folders");
+ for (const auto deleteRaw : folders) {
+ auto deleteObj = Json::requireObject(deleteRaw);
+ ATLauncher::VersionDelete versionDelete;
+ loadVersionDelete(versionDelete, deleteObj);
+ d.folders.append(versionDelete);
+ }
+ }
+}
+
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
{
v.version = Json::requireString(obj, "version");
@@ -284,4 +342,10 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
auto messages = Json::ensureObject(obj, "messages");
loadVersionMessages(v.messages, messages);
+
+ auto keeps = Json::ensureObject(obj, "keeps");
+ loadVersionKeeps(v.keeps, keeps);
+
+ auto deletes = Json::ensureObject(obj, "deletes");
+ loadVersionDeletes(v.deletes, deletes);
}
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h
index 43510c50..571c976d 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.h
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.h
@@ -150,6 +150,26 @@ struct VersionMessages
QString update;
};
+struct VersionKeep {
+ QString base;
+ QString target;
+};
+
+struct VersionKeeps {
+ QVector<VersionKeep> files;
+ QVector<VersionKeep> folders;
+};
+
+struct VersionDelete {
+ QString base;
+ QString target;
+};
+
+struct VersionDeletes {
+ QVector<VersionDelete> files;
+ QVector<VersionDelete> folders;
+};
+
struct PackVersionMainClass
{
QString mainClass;
@@ -178,6 +198,9 @@ struct PackVersion
QMap<QString, QString> colours;
QMap<QString, QString> warnings;
VersionMessages messages;
+
+ VersionKeeps keeps;
+ VersionDeletes deletes;
};
void loadVersion(PackVersion & v, QJsonObject & obj);
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index c1f56658..058d2471 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -7,6 +7,13 @@ Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAcc
: m_network(network), m_toProcess(toProcess)
{}
+bool Flame::FileResolvingTask::abort()
+{
+ if (m_dljob)
+ return m_dljob->abort();
+ return true;
+}
+
void Flame::FileResolvingTask::executeTask()
{
setStatus(tr("Resolving mod IDs..."));
diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h
index 87981f0a..f71b87ce 100644
--- a/launcher/modplatform/flame/FileResolvingTask.h
+++ b/launcher/modplatform/flame/FileResolvingTask.h
@@ -13,6 +13,9 @@ public:
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess);
virtual ~FileResolvingTask() {};
+ bool canAbort() const override { return true; }
+ bool abort() override;
+
const Flame::Manifest &getResults() const
{
return m_toProcess;
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h
index a7395220..da4c0da5 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.h
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h
@@ -10,7 +10,7 @@
#include "net/NetJob.h"
-#include <nonstd/optional>
+#include <optional>
namespace LegacyFTB {
@@ -46,8 +46,8 @@ private: /* data */
shared_qobject_ptr<QNetworkAccessManager> m_network;
bool abortable = false;
std::unique_ptr<QuaZip> m_packZip;
- QFuture<nonstd::optional<QStringList>> m_extractFuture;
- QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
+ QFuture<std::optional<QStringList>> m_extractFuture;
+ QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
NetJob::Ptr netJobContainer;
QString archivePath;
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
index cac432cd..3c15667c 100644
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
+++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -40,103 +42,193 @@
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
+#include "modplatform/flame/PackManifest.h"
#include "net/ChecksumValidator.h"
#include "settings/INISettingsObject.h"
-#include "BuildConfig.h"
#include "Application.h"
+#include "BuildConfig.h"
+#include "ui/dialogs/BlockedModsDialog.h"
namespace ModpacksCH {
-PackInstallTask::PackInstallTask(Modpack pack, QString version)
-{
- m_pack = pack;
- m_version_name = version;
-}
+PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent)
+ : m_pack(std::move(pack)), m_version_name(std::move(version)), m_parent(parent)
+{}
bool PackInstallTask::abort()
{
- if(abortable)
- {
- return jobPtr->abort();
- }
- return false;
+ bool aborted = true;
+
+ if (m_net_job)
+ aborted &= m_net_job->abort();
+ if (m_mod_id_resolver_task)
+ aborted &= m_mod_id_resolver_task->abort();
+
+ // FIXME: This should be 'emitAborted()', but InstanceStaging doesn't connect to the abort signal yet...
+ if (aborted)
+ emitFailed(tr("Aborted"));
+
+ return aborted;
}
void PackInstallTask::executeTask()
{
- // Find pack version
- bool found = false;
- VersionInfo version;
+ setStatus(tr("Getting the manifest..."));
- for(auto vInfo : m_pack.versions) {
- if (vInfo.name == m_version_name) {
- found = true;
- version = vInfo;
- break;
- }
- }
+ // Find pack version
+ auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(),
+ [this](ModpacksCH::VersionInfo const& a) { return a.name == m_version_name; });
- if(!found) {
+ if (version_it == m_pack.versions.constEnd()) {
emitFailed(tr("Failed to find pack version %1").arg(m_version_name));
return;
}
- auto *netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network());
+ auto version = *version_it;
+
+ auto* netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network());
+
auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
- jobPtr = netJob;
- jobPtr->start();
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &m_response));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded);
+ QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed);
+ QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress);
- QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
- QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
+ m_net_job = netJob;
+
+ netJob->start();
}
-void PackInstallTask::onDownloadSucceeded()
+void PackInstallTask::onManifestDownloadSucceeded()
{
- jobPtr.reset();
-
- QJsonParseError parse_error;
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
- if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
+ m_net_job.reset();
+
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << m_response;
return;
}
- auto obj = doc.object();
-
ModpacksCH::Version version;
- try
- {
+ try {
+ auto obj = Json::requireObject(doc);
ModpacksCH::loadVersion(version, obj);
- }
- catch (const JSONValidationError &e)
- {
+ } catch (const JSONValidationError& e) {
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
return;
}
+
m_version = version;
- downloadPack();
+ resolveMods();
}
-void PackInstallTask::onDownloadFailed(QString reason)
+void PackInstallTask::resolveMods()
{
- jobPtr.reset();
- emitFailed(reason);
+ setStatus(tr("Resolving mods..."));
+ setProgress(0, 100);
+
+ m_file_id_map.clear();
+
+ Flame::Manifest manifest;
+ int index = 0;
+
+ for (auto const& file : m_version.files) {
+ if (!file.serverOnly && file.url.isEmpty()) {
+ if (file.curseforge.file_id <= 0) {
+ emitFailed(tr("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name));
+ return;
+ }
+
+ Flame::File flame_file;
+ flame_file.projectId = file.curseforge.project_id;
+ flame_file.fileId = file.curseforge.file_id;
+ flame_file.hash = file.sha1;
+
+ manifest.files.insert(flame_file.fileId, flame_file);
+ m_file_id_map.append(flame_file.fileId);
+ } else {
+ m_file_id_map.append(-1);
+ }
+
+ index++;
+ }
+
+ m_mod_id_resolver_task = new Flame::FileResolvingTask(APPLICATION->network(), manifest);
+
+ connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded);
+ connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed);
+ connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress);
+
+ m_mod_id_resolver_task->start();
+}
+
+void PackInstallTask::onResolveModsSucceeded()
+{
+ m_abortable = false;
+
+ QString text;
+ QList<QUrl> urls;
+ auto anyBlocked = false;
+
+ Flame::Manifest results = m_mod_id_resolver_task->getResults();
+ for (int index = 0; index < m_file_id_map.size(); index++) {
+ auto const file_id = m_file_id_map.at(index);
+ if (file_id < 0)
+ continue;
+
+ Flame::File results_file = results.files[file_id];
+ VersionFile& local_file = m_version.files[index];
+
+ // First check for blocked mods
+ if (!results_file.resolved || results_file.url.isEmpty()) {
+ QString type(local_file.type);
+
+ type[0] = type[0].toUpper();
+ text += QString("%1: %2 - <a href='%3'>%3</a><br/>").arg(type, local_file.name, results_file.websiteUrl);
+ urls.append(QUrl(results_file.websiteUrl));
+ anyBlocked = true;
+ } else {
+ local_file.url = results_file.url.toString();
+ }
+ }
+
+ m_mod_id_resolver_task.reset();
+
+ if (anyBlocked) {
+ qDebug() << "Blocked files found, displaying file list";
+
+ auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"),
+ tr("The following files are not available for download in third party launchers.<br/>"
+ "You will need to manually download them and add them to the instance."),
+ text,
+ urls);
+
+ if (message_dialog->exec() == QDialog::Accepted)
+ downloadPack();
+ else
+ abort();
+ } else {
+ downloadPack();
+ }
}
void PackInstallTask::downloadPack()
{
setStatus(tr("Downloading mods..."));
- jobPtr = new NetJob(tr("Mod download"), APPLICATION->network());
- for(auto file : m_version.files) {
- if(file.serverOnly) continue;
+ auto* jobPtr = new NetJob(tr("Mod download"), APPLICATION->network());
+ for (auto const& file : m_version.files) {
+ if (file.serverOnly || file.url.isEmpty())
+ continue;
- QFileInfo fileName(file.name);
- auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix();
+ QFileInfo file_info(file.name);
+ auto cacheName = file_info.completeBaseName() + "-" + file.sha1 + "." + file_info.suffix();
auto entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", cacheName);
entry->setStale(true);
@@ -144,58 +236,64 @@ void PackInstallTask::downloadPack()
auto relpath = FS::PathCombine("minecraft", file.path, file.name);
auto path = FS::PathCombine(m_stagingPath, relpath);
- if (filesToCopy.contains(path)) {
+ if (m_files_to_copy.contains(path)) {
qWarning() << "Ignoring" << file.url << "as a file of that path is already downloading.";
continue;
}
+
qDebug() << "Will download" << file.url << "to" << path;
- filesToCopy[path] = entry->getFullPath();
+ m_files_to_copy[path] = entry->getFullPath();
auto dl = Net::Download::makeCached(file.url, entry);
if (!file.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
}
+
jobPtr->addNetAction(dl);
}
- connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
- {
- abortable = false;
- jobPtr.reset();
- install();
- });
- connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
- {
- abortable = false;
- jobPtr.reset();
- emitFailed(reason);
- });
- connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
- {
- abortable = true;
- setProgress(current, total);
- });
+ connect(jobPtr, &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded);
+ connect(jobPtr, &NetJob::failed, this, &PackInstallTask::onModDownloadFailed);
+ connect(jobPtr, &NetJob::progress, this, &PackInstallTask::setProgress);
+ m_net_job = jobPtr;
jobPtr->start();
+
+ m_abortable = true;
+}
+
+void PackInstallTask::onModDownloadSucceeded()
+{
+ m_net_job.reset();
+ install();
}
void PackInstallTask::install()
{
- setStatus(tr("Copying modpack files"));
+ setStatus(tr("Copying modpack files..."));
+ setProgress(0, m_files_to_copy.size());
+ QCoreApplication::processEvents();
+
+ m_abortable = false;
- for (auto iter = filesToCopy.begin(); iter != filesToCopy.end(); iter++) {
- auto &to = iter.key();
- auto &from = iter.value();
+ int i = 0;
+ for (auto iter = m_files_to_copy.constBegin(); iter != m_files_to_copy.constEnd(); iter++) {
+ auto& to = iter.key();
+ auto& from = iter.value();
FS::copy fileCopyOperation(from, to);
- if(!fileCopyOperation()) {
+ if (!fileCopyOperation()) {
qWarning() << "Failed to copy" << from << "to" << to;
emitFailed(tr("Failed to copy files"));
return;
}
+
+ setProgress(i++, m_files_to_copy.size());
+ QCoreApplication::processEvents();
}
- setStatus(tr("Installing modpack"));
+ setStatus(tr("Installing modpack..."));
+ QCoreApplication::processEvents();
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
@@ -205,20 +303,20 @@ void PackInstallTask::install()
auto components = instance.getPackProfile();
components->buildingFromScratch();
- for(auto target : m_version.targets) {
- if(target.type == "game" && target.name == "minecraft") {
+ for (auto target : m_version.targets) {
+ if (target.type == "game" && target.name == "minecraft") {
components->setComponentVersion("net.minecraft", target.version, true);
break;
}
}
- for(auto target : m_version.targets) {
- if(target.type != "modloader") continue;
+ for (auto target : m_version.targets) {
+ if (target.type != "modloader")
+ continue;
- if(target.name == "forge") {
+ if (target.name == "forge") {
components->setComponentVersion("net.minecraftforge", target.version);
- }
- else if(target.name == "fabric") {
+ } else if (target.name == "fabric") {
components->setComponentVersion("net.fabricmc.fabric-loader", target.version);
}
}
@@ -245,4 +343,20 @@ void PackInstallTask::install()
emitSucceeded();
}
+void PackInstallTask::onManifestDownloadFailed(QString reason)
+{
+ m_net_job.reset();
+ emitFailed(reason);
+}
+void PackInstallTask::onResolveModsFailed(QString reason)
+{
+ m_net_job.reset();
+ emitFailed(reason);
}
+void PackInstallTask::onModDownloadFailed(QString reason)
+{
+ m_net_job.reset();
+ emitFailed(reason);
+}
+
+} // namespace ModpacksCH
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h
index ff59b695..e63ca0df 100644
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h
+++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.h
@@ -1,18 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#pragma once
@@ -20,44 +40,60 @@
#include "FTBPackManifest.h"
#include "InstanceTask.h"
+#include "QObjectPtr.h"
+#include "modplatform/flame/FileResolvingTask.h"
#include "net/NetJob.h"
+#include <QWidget>
+
namespace ModpacksCH {
-class PackInstallTask : public InstanceTask
+class PackInstallTask final : public InstanceTask
{
Q_OBJECT
public:
- explicit PackInstallTask(Modpack pack, QString version);
- virtual ~PackInstallTask(){}
+ explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr);
+ ~PackInstallTask() override = default;
- bool canAbort() const override { return true; }
+ bool canAbort() const override { return m_abortable; }
bool abort() override;
protected:
- virtual void executeTask() override;
+ void executeTask() override;
private slots:
- void onDownloadSucceeded();
- void onDownloadFailed(QString reason);
+ void onManifestDownloadSucceeded();
+ void onResolveModsSucceeded();
+ void onModDownloadSucceeded();
+
+ void onManifestDownloadFailed(QString reason);
+ void onResolveModsFailed(QString reason);
+ void onModDownloadFailed(QString reason);
private:
+ void resolveMods();
void downloadPack();
void install();
private:
- bool abortable = false;
+ bool m_abortable = true;
+
+ NetJob::Ptr m_net_job = nullptr;
+ shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver_task = nullptr;
+
+ QList<int> m_file_id_map;
- NetJob::Ptr jobPtr;
- QByteArray response;
+ QByteArray m_response;
Modpack m_pack;
QString m_version_name;
Version m_version;
- QMap<QString, QString> filesToCopy;
+ QMap<QString, QString> m_files_to_copy;
+ //FIXME: nuke
+ QWidget* m_parent;
};
}
diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.cpp b/launcher/modplatform/modpacksch/FTBPackManifest.cpp
index e2d47a5b..421527ae 100644
--- a/launcher/modplatform/modpacksch/FTBPackManifest.cpp
+++ b/launcher/modplatform/modpacksch/FTBPackManifest.cpp
@@ -1,18 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#include "FTBPackManifest.h"
@@ -127,13 +146,16 @@ static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj)
a.path = Json::requireString(obj, "path");
a.name = Json::requireString(obj, "name");
a.version = Json::requireString(obj, "version");
- a.url = Json::requireString(obj, "url");
+ a.url = Json::ensureString(obj, "url"); // optional
a.sha1 = Json::requireString(obj, "sha1");
a.size = Json::requireInteger(obj, "size");
a.clientOnly = Json::requireBoolean(obj, "clientonly");
a.serverOnly = Json::requireBoolean(obj, "serveronly");
a.optional = Json::requireBoolean(obj, "optional");
a.updated = Json::requireInteger(obj, "updated");
+ auto curseforgeObj = Json::ensureObject(obj, "curseforge"); // optional
+ a.curseforge.project_id = Json::ensureInteger(curseforgeObj, "project");
+ a.curseforge.file_id = Json::ensureInteger(curseforgeObj, "file");
}
void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj)
diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.h b/launcher/modplatform/modpacksch/FTBPackManifest.h
index da45d8ac..a8b6f35e 100644
--- a/launcher/modplatform/modpacksch/FTBPackManifest.h
+++ b/launcher/modplatform/modpacksch/FTBPackManifest.h
@@ -1,18 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2020 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2020 Petr Mrazek <peterix@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#pragma once
@@ -97,6 +116,12 @@ struct VersionTarget
int64_t updated;
};
+struct VersionFileCurseForge
+{
+ int project_id;
+ int file_id;
+};
+
struct VersionFile
{
int id;
@@ -111,6 +136,7 @@ struct VersionFile
bool serverOnly;
bool optional;
int64_t updated;
+ VersionFileCurseForge curseforge;
};
struct Version
diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.h b/launcher/modplatform/technic/SingleZipPackInstallTask.h
index 4d1fcbff..981ccf8a 100644
--- a/launcher/modplatform/technic/SingleZipPackInstallTask.h
+++ b/launcher/modplatform/technic/SingleZipPackInstallTask.h
@@ -24,7 +24,7 @@
#include <QStringList>
#include <QUrl>
-#include <nonstd/optional>
+#include <optional>
namespace Technic {
@@ -57,8 +57,8 @@ private:
QString m_archivePath;
NetJob::Ptr m_filesNetJob;
std::unique_ptr<QuaZip> m_packZip;
- QFuture<nonstd::optional<QStringList>> m_extractFuture;
- QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
+ QFuture<std::optional<QStringList>> m_extractFuture;
+ QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
};
} // namespace Technic
diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp
index 4d86c0b8..deb2780b 100644
--- a/launcher/net/HttpMetaCache.cpp
+++ b/launcher/net/HttpMetaCache.cpp
@@ -121,6 +121,14 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
SaveEventually();
}
+ // Get rid of old entries, to prevent cache problems
+ auto current_time = QDateTime::currentSecsSinceEpoch();
+ if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) {
+ qWarning() << "Removing cache entry because of old age!";
+ selected_base.entry_list.remove(resource_path);
+ return staleEntry(base, resource_path);
+ }
+
// entry passed all the checks we cared about.
entry->basePath = getBasePath(base);
return entry;
@@ -221,6 +229,8 @@ void HttpMetaCache::Load()
foo->etag = Json::ensureString(element_obj, "etag");
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
+ foo->current_age = Json::ensureDouble(element_obj, "current_age");
+ foo->max_age = Json::ensureDouble(element_obj, "max_age");
// presumed innocent until closer examination
foo->stale = false;
@@ -240,6 +250,8 @@ void HttpMetaCache::SaveNow()
if (m_index_file.isNull())
return;
+ qDebug() << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries";
+
QJsonObject toplevel;
Json::writeString(toplevel, "version", "1");
@@ -259,6 +271,8 @@ void HttpMetaCache::SaveNow()
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
if (!entry->remote_changed_timestamp.isEmpty())
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
+ entryObj.insert("current_age", QJsonValue(double(entry->current_age)));
+ entryObj.insert("max_age", QJsonValue(double(entry->max_age)));
entriesArr.append(entryObj);
}
}
diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h
index e944b3d5..df3549e8 100644
--- a/launcher/net/HttpMetaCache.h
+++ b/launcher/net/HttpMetaCache.h
@@ -64,6 +64,14 @@ class MetaEntry {
auto getMD5Sum() -> QString { return md5sum; }
void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
+ auto getCurrentAge() -> qint64 { return current_age; }
+ void setCurrentAge(qint64 age) { current_age = age; }
+
+ auto getMaximumAge() -> qint64 { return max_age; }
+ void setMaximumAge(qint64 age) { max_age = age; }
+
+ bool isExpired(qint64 offset) { return current_age >= max_age - offset; };
+
protected:
QString baseId;
QString basePath;
@@ -72,6 +80,8 @@ class MetaEntry {
QString etag;
qint64 local_changed_timestamp = 0;
QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
+ qint64 current_age = 0;
+ qint64 max_age = 0;
bool stale = true;
};
diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp
index f86dd870..ab0c9fcb 100644
--- a/launcher/net/MetaCacheSink.cpp
+++ b/launcher/net/MetaCacheSink.cpp
@@ -36,11 +36,16 @@
#include "MetaCacheSink.h"
#include <QFile>
#include <QFileInfo>
-#include "FileSystem.h"
#include "Application.h"
namespace Net {
+/** Maximum time to hold a cache entry
+ * = 1 week in seconds
+ */
+#define MAX_TIME_TO_EXPIRE 1*7*24*60*60
+
+
MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum)
:Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum)
{
@@ -88,6 +93,37 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
}
m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
+
+ { // Cache lifetime
+ if (reply.hasRawHeader("Cache-Control")) {
+ auto cache_control_header = reply.rawHeader("Cache-Control");
+ // qDebug() << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header;
+
+ QRegularExpression max_age_expr("max-age=([0-9]+)");
+ qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong();
+ m_entry->setMaximumAge(max_age);
+
+ } else if (reply.hasRawHeader("Expires")) {
+ auto expires_header = reply.rawHeader("Expires");
+ // qDebug() << "[MetaCache] Parsing 'Expires' header with" << expires_header;
+
+ qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch();
+ m_entry->setMaximumAge(max_age);
+ } else {
+ m_entry->setMaximumAge(MAX_TIME_TO_EXPIRE);
+ }
+
+ if (reply.hasRawHeader("Age")) {
+ auto age_header = reply.rawHeader("Age");
+ // qDebug() << "[MetaCache] Parsing 'Age' header with" << age_header;
+
+ qint64 current_age = age_header.toLongLong();
+ m_entry->setCurrentAge(current_age);
+ } else {
+ m_entry->setCurrentAge(0);
+ }
+ }
+
m_entry->setStale(false);
APPLICATION->metacache()->updateEntry(m_entry);
diff --git a/launcher/settings/SettingsObject.h b/launcher/settings/SettingsObject.h
index 3d61e707..6200bc3a 100644
--- a/launcher/settings/SettingsObject.h
+++ b/launcher/settings/SettingsObject.h
@@ -25,6 +25,7 @@ class Setting;
class SettingsObject;
typedef std::shared_ptr<SettingsObject> SettingsObjectPtr;
+typedef std::weak_ptr<SettingsObject> SettingsObjectWeakPtr;
/*!
* \brief The SettingsObject handles communicating settings between the application and a
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index d58f158e..299401f5 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -252,6 +252,9 @@ public:
TranslatedAction actionViewInstanceFolder;
TranslatedAction actionViewCentralModsFolder;
+ QMenu * editMenu = nullptr;
+ TranslatedAction actionUndoTrashInstance;
+
QMenu * helpMenu = nullptr;
TranslatedToolButton helpMenuButton;
TranslatedAction actionReportBug;
@@ -335,6 +338,14 @@ public:
actionSettings->setShortcut(QKeySequence::Preferences);
all_actions.append(&actionSettings);
+ actionUndoTrashInstance = TranslatedAction(MainWindow);
+ connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), MainWindow, SLOT(undoTrashInstance()));
+ actionUndoTrashInstance->setObjectName(QStringLiteral("actionUndoTrashInstance"));
+ actionUndoTrashInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Undo Last Instance Deletion"));
+ actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
+ actionUndoTrashInstance->setShortcut(QKeySequence("Ctrl+Z"));
+ all_actions.append(&actionUndoTrashInstance);
+
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
actionReportBug = TranslatedAction(MainWindow);
actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
@@ -508,6 +519,9 @@ public:
fileMenu->addSeparator();
fileMenu->addAction(actionSettings);
+ editMenu = menuBar->addMenu(tr("&Edit"));
+ editMenu->addAction(actionUndoTrashInstance);
+
viewMenu = menuBar->addMenu(tr("&View"));
viewMenu->setSeparatorsCollapsible(false);
viewMenu->addAction(actionCAT);
@@ -732,9 +746,10 @@ public:
actionDeleteInstance = TranslatedAction(MainWindow);
actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance"));
- actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance..."));
+ actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance"));
actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance."));
actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete});
+ actionDeleteInstance->setAutoRepeat(false);
all_actions.append(&actionDeleteInstance);
actionCopyInstance = TranslatedAction(MainWindow);
@@ -1150,6 +1165,11 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
actions.append(actionDeleteGroup);
}
+
+ QAction *actionUndoTrashInstance = new QAction("Undo last trash instance", this);
+ connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), SLOT(undoTrashInstance()));
+ actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
+ actions.append(actionUndoTrashInstance);
}
QMenu myMenu;
myMenu.addActions(actions);
@@ -1445,6 +1465,7 @@ void MainWindow::updateNewsLabel()
{
newsLabel->setText(tr("Loading news..."));
newsLabel->setEnabled(false);
+ ui->actionMoreNews->setVisible(false);
}
else
{
@@ -1453,11 +1474,13 @@ void MainWindow::updateNewsLabel()
{
newsLabel->setText(entries[0]->title);
newsLabel->setEnabled(true);
+ ui->actionMoreNews->setVisible(true);
}
else
{
newsLabel->setText(tr("No news available."));
newsLabel->setEnabled(false);
+ ui->actionMoreNews->setVisible(false);
}
}
}
@@ -1832,6 +1855,11 @@ void MainWindow::deleteGroup()
}
}
+void MainWindow::undoTrashInstance()
+{
+ APPLICATION->instances()->undoTrashInstance();
+}
+
void MainWindow::on_actionViewInstanceFolder_triggered()
{
QString str = APPLICATION->settings()->get("InstanceDir").toString();
@@ -1957,7 +1985,12 @@ void MainWindow::on_actionDeleteInstance_triggered()
{
return;
}
+
auto id = m_selectedInstance->id();
+ if (APPLICATION->instances()->trashInstance(id)) {
+ return;
+ }
+
auto response = CustomMessageBox::selectable(
this,
tr("CAREFUL!"),
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index d7930b5a..dde3d02c 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -145,6 +145,7 @@ private slots:
void on_actionDeleteInstance_triggered();
void deleteGroup();
+ void undoTrashInstance();
void on_actionExportInstance_triggered();
diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp
index c5367d5b..743c34f1 100644
--- a/launcher/ui/dialogs/AboutDialog.cpp
+++ b/launcher/ui/dialogs/AboutDialog.cpp
@@ -147,10 +147,15 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
else
ui->platformLabel->setVisible(false);
- if (BuildConfig.VERSION_BUILD >= 0)
- ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD));
+ if (!BuildConfig.GIT_COMMIT.isEmpty())
+ ui->commitLabel->setText(tr("Commit: %1").arg(BuildConfig.GIT_COMMIT));
else
- ui->buildNumLabel->setVisible(false);
+ ui->commitLabel->setVisible(false);
+
+ if (!BuildConfig.BUILD_DATE.isEmpty())
+ ui->buildDateLabel->setText(tr("Build date: %1").arg(BuildConfig.BUILD_DATE));
+ else
+ ui->buildDateLabel->setVisible(false);
if (!BuildConfig.VERSION_CHANNEL.isEmpty())
ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL);
diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui
index 6323992b..6eaa0c4e 100644
--- a/launcher/ui/dialogs/AboutDialog.ui
+++ b/launcher/ui/dialogs/AboutDialog.ui
@@ -184,12 +184,28 @@
</widget>
</item>
<item>
- <widget class="QLabel" name="buildNumLabel">
+ <widget class="QLabel" name="buildDateLabel">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
- <string>Build Number:</string>
+ <string>Build Date:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="commitLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>Commit:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
new file mode 100644
index 00000000..fe87b517
--- /dev/null
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -0,0 +1,28 @@
+#include "BlockedModsDialog.h"
+#include "ui_BlockedModsDialog.h"
+#include <QPushButton>
+#include <QDialogButtonBox>
+#include <QDesktopServices>
+
+
+BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls) :
+ QDialog(parent), ui(new Ui::BlockedModsDialog), urls(urls) {
+ ui->setupUi(this);
+
+ auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole);
+ connect(openAllButton, &QPushButton::clicked, this, &BlockedModsDialog::openAll);
+
+ this->setWindowTitle(title);
+ ui->label->setText(text);
+ ui->textBrowser->setText(body);
+}
+
+BlockedModsDialog::~BlockedModsDialog() {
+ delete ui;
+}
+
+void BlockedModsDialog::openAll() {
+ for(auto &url : urls) {
+ QDesktopServices::openUrl(url);
+ }
+}
diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h
new file mode 100644
index 00000000..5f5bd61b
--- /dev/null
+++ b/launcher/ui/dialogs/BlockedModsDialog.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <QDialog>
+
+
+QT_BEGIN_NAMESPACE
+namespace Ui { class BlockedModsDialog; }
+QT_END_NAMESPACE
+
+class BlockedModsDialog : public QDialog {
+Q_OBJECT
+
+public:
+ BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls);
+
+ ~BlockedModsDialog() override;
+
+private:
+ Ui::BlockedModsDialog *ui;
+ const QList<QUrl> &urls;
+ void openAll();
+};
diff --git a/launcher/ui/dialogs/BlockedModsDialog.ui b/launcher/ui/dialogs/BlockedModsDialog.ui
new file mode 100644
index 00000000..f4ae95b6
--- /dev/null
+++ b/launcher/ui/dialogs/BlockedModsDialog.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BlockedModsDialog</class>
+ <widget class="QDialog" name="BlockedModsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>455</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string notr="true">BlockedModsDialog</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QTextBrowser" name="textBrowser">
+ <property name="acceptRichText">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>BlockedModsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>199</x>
+ <y>425</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>BlockedModsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>199</x>
+ <y>425</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/launcher/ui/dialogs/LoginDialog.cpp b/launcher/ui/dialogs/LoginDialog.cpp
index 194315a7..30394b72 100644
--- a/launcher/ui/dialogs/LoginDialog.cpp
+++ b/launcher/ui/dialogs/LoginDialog.cpp
@@ -115,5 +115,5 @@ MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg)
{
return dlg.m_account;
}
- return 0;
+ return nullptr;
}
diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp
index b11b6980..be49babb 100644
--- a/launcher/ui/dialogs/MSALoginDialog.cpp
+++ b/launcher/ui/dialogs/MSALoginDialog.cpp
@@ -169,5 +169,5 @@ MinecraftAccountPtr MSALoginDialog::newAccount(QWidget *parent, QString msg)
{
return dlg.m_account;
}
- return 0;
+ return nullptr;
}
diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp
index 4f3d8be4..a69537ab 100644
--- a/launcher/ui/dialogs/OfflineLoginDialog.cpp
+++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp
@@ -103,5 +103,5 @@ MinecraftAccountPtr OfflineLoginDialog::newAccount(QWidget *parent, QString msg)
{
return dlg.m_account;
}
- return 0;
+ return nullptr;
}
diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp
index a79bc837..3c7f53d3 100644
--- a/launcher/ui/dialogs/ProgressDialog.cpp
+++ b/launcher/ui/dialogs/ProgressDialog.cpp
@@ -43,8 +43,8 @@ void ProgressDialog::setSkipButton(bool present, QString label)
void ProgressDialog::on_skipButton_clicked(bool checked)
{
Q_UNUSED(checked);
- task->abort();
- QDialog::reject();
+ if (task->abort())
+ QDialog::reject();
}
ProgressDialog::~ProgressDialog()
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
index d06f412b..39fbe3e2 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -32,13 +32,13 @@ class SortProxy : public QSortFilterProxyModel {
const auto& mod = model->at(source_row);
- if (mod.name().contains(filterRegularExpression()))
+ if (filterRegularExpression().match(mod.name()).hasMatch())
return true;
- if (mod.description().contains(filterRegularExpression()))
+ if (filterRegularExpression().match(mod.description()).hasMatch())
return true;
for (auto& author : mod.authors()) {
- if (author.contains(filterRegularExpression())) {
+ if (filterRegularExpression().match(author).hasMatch()) {
return true;
}
}
@@ -101,7 +101,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
{
ui->setupUi(this);
- runningStateChanged(m_instance && m_instance->isRunning());
+ ExternalResourcesPage::runningStateChanged(m_instance && m_instance->isRunning());
ui->actionsToolbar->insertSpacer(ui->actionViewConfigs);
@@ -182,7 +182,7 @@ void ExternalResourcesPage::retranslate()
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
{
m_viewFilter = newContents;
- m_filterModel->setFilterFixedString(m_viewFilter);
+ m_filterModel->setFilterRegularExpression(m_viewFilter);
}
void ExternalResourcesPage::runningStateChanged(bool running)
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h
index 41237139..ff294678 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.h
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.h
@@ -46,7 +46,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
protected slots:
void itemActivated(const QModelIndex& index);
void filterTextChanged(const QString& newContents);
- void runningStateChanged(bool running);
+ virtual void runningStateChanged(bool running);
virtual void addItem();
virtual void removeItem();
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 14e1f1e5..45678db1 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -84,49 +84,44 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem);
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
- connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
- [this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); });
+ auto check_allow_update = [this] {
+ return (!m_instance || !m_instance->isRunning()) &&
+ (ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
+ };
- connect(mods.get(), &ModFolderModel::rowsInserted, this,
- [this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); });
-
- connect(mods.get(), &ModFolderModel::updateFinished, this, [this, mods] {
- ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
+ connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
+ ui->actionUpdateItem->setEnabled(check_allow_update());
+ });
+
+ connect(mods.get(), &ModFolderModel::rowsInserted, this, [this, check_allow_update] {
+ ui->actionUpdateItem->setEnabled(check_allow_update());
+ });
+
+ connect(mods.get(), &ModFolderModel::rowsRemoved, this, [this, check_allow_update] {
+ ui->actionUpdateItem->setEnabled(check_allow_update());
+ });
+
+ connect(mods.get(), &ModFolderModel::updateFinished, this, [this, check_allow_update, mods] {
+ ui->actionUpdateItem->setEnabled(check_allow_update());
// Prevent a weird crash when trying to open the mods page twice in a session o.O
disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0);
});
+
+ ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning());
}
}
-CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
- : ModFolderPage(inst, mods, parent)
-{}
-
-bool ModFolderPage::shouldDisplay() const
+void ModFolderPage::runningStateChanged(bool running)
{
- return true;
+ ExternalResourcesPage::runningStateChanged(running);
+ ui->actionDownloadItem->setEnabled(!running);
+ ui->actionUpdateItem->setEnabled(!running);
}
-bool CoreModFolderPage::shouldDisplay() const
+bool ModFolderPage::shouldDisplay() const
{
- if (ModFolderPage::shouldDisplay()) {
- auto inst = dynamic_cast<MinecraftInstance*>(m_instance);
- if (!inst)
- return true;
-
- auto version = inst->getPackProfile();
-
- if (!version)
- return true;
- if (!version->getComponent("net.minecraftforge"))
- return false;
- if (!version->getComponent("net.minecraft"))
- return false;
- if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
- return true;
- }
- return false;
+ return true;
}
void ModFolderPage::installMods()
@@ -232,3 +227,28 @@ void ModFolderPage::updateMods()
m_model->update();
}
}
+
+CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
+ : ModFolderPage(inst, mods, parent)
+{}
+
+bool CoreModFolderPage::shouldDisplay() const
+{
+ if (ModFolderPage::shouldDisplay()) {
+ auto inst = dynamic_cast<MinecraftInstance*>(m_instance);
+ if (!inst)
+ return true;
+
+ auto version = inst->getPackProfile();
+
+ if (!version)
+ return true;
+ if (!version->getComponent("net.minecraftforge"))
+ return false;
+ if (!version->getComponent("net.minecraft"))
+ return false;
+ if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
+ return true;
+ }
+ return false;
+}
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index 0a7fc9fa..7e305951 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -53,6 +53,7 @@ class ModFolderPage : public ExternalResourcesPage {
virtual QString helpPage() const override { return "Loader-mods"; }
virtual bool shouldDisplay() const override;
+ void runningStateChanged(bool running) override;
private slots:
void installMods();
@@ -63,5 +64,11 @@ class CoreModFolderPage : public ModFolderPage {
public:
explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = 0);
virtual ~CoreModFolderPage() = default;
- virtual bool shouldDisplay() const;
+
+ virtual QString displayName() const override { return tr("Core mods"); }
+ virtual QIcon icon() const override { return APPLICATION->getThemedIcon("coremods"); }
+ virtual QString id() const override { return "coremods"; }
+ virtual QString helpPage() const override { return "Core-mods"; }
+
+ virtual bool shouldDisplay() const override;
};
diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp
index 647b04a7..85cc01ff 100644
--- a/launcher/ui/pages/instance/WorldListPage.cpp
+++ b/launcher/ui/pages/instance/WorldListPage.cpp
@@ -211,7 +211,7 @@ void WorldListPage::on_actionDatapacks_triggered()
return;
}
- if(!worldSafetyNagQuestion())
+ if(!worldSafetyNagQuestion(tr("Open World Datapacks Folder")))
return;
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
@@ -269,7 +269,7 @@ void WorldListPage::on_actionMCEdit_triggered()
return;
}
- if(!worldSafetyNagQuestion())
+ if(!worldSafetyNagQuestion(tr("Open World in MCEdit")))
return;
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
@@ -373,11 +373,11 @@ bool WorldListPage::isWorldSafe(QModelIndex)
return !m_inst->isRunning();
}
-bool WorldListPage::worldSafetyNagQuestion()
+bool WorldListPage::worldSafetyNagQuestion(const QString &actionType)
{
if(!isWorldSafe(getSelectedWorld()))
{
- auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?"));
+ auto result = QMessageBox::question(this, actionType, tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?"));
if(result == QMessageBox::No)
{
return false;
@@ -395,7 +395,7 @@ void WorldListPage::on_actionCopy_triggered()
return;
}
- if(!worldSafetyNagQuestion())
+ if(!worldSafetyNagQuestion(tr("Copy World")))
return;
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
@@ -417,7 +417,7 @@ void WorldListPage::on_actionRename_triggered()
return;
}
- if(!worldSafetyNagQuestion())
+ if(!worldSafetyNagQuestion(tr("Rename World")))
return;
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h
index 17e36a08..1dc9e53e 100644
--- a/launcher/ui/pages/instance/WorldListPage.h
+++ b/launcher/ui/pages/instance/WorldListPage.h
@@ -93,7 +93,7 @@ protected:
private:
QModelIndex getSelectedWorld();
bool isWorldSafe(QModelIndex index);
- bool worldSafetyNagQuestion();
+ bool worldSafetyNagQuestion(const QString &actionType);
void mceditError();
private:
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
index 8de5211c..7901b90b 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
@@ -37,13 +37,12 @@
#include "AtlPage.h"
#include "ui_AtlPage.h"
-#include "modplatform/atlauncher/ATLPackInstallTask.h"
+#include "BuildConfig.h"
#include "AtlOptionalModDialog.h"
+#include "AtlUserInteractionSupportImpl.h"
+#include "modplatform/atlauncher/ATLPackInstallTask.h"
#include "ui/dialogs/NewInstanceDialog.h"
-#include "ui/dialogs/VersionSelectDialog.h"
-
-#include <BuildConfig.h>
#include <QMessageBox>
@@ -117,7 +116,9 @@ void AtlPage::suggestCurrent()
return;
}
- dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.name, selectedVersion));
+ auto uiSupport = new AtlUserInteractionSupportImpl(this);
+ dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion));
+
auto editedLogoName = selected.safeName;
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());
listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo)
@@ -172,51 +173,3 @@ void AtlPage::onVersionSelectionChanged(QString data)
selectedVersion = data;
suggestCurrent();
}
-
-QVector<QString> AtlPage::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
-{
- AtlOptionalModDialog optionalModDialog(this, version, mods);
- optionalModDialog.exec();
- return optionalModDialog.getResult();
-}
-
-QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) {
- VersionSelectDialog vselect(vlist.get(), "Choose Version", APPLICATION->activeWindow(), false);
- if (minecraftVersion != Q_NULLPTR) {
- vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
- vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion));
- }
- else {
- vselect.setEmptyString(tr("No versions are currently available"));
- }
- vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!"));
-
- // select recommended build
- for (int i = 0; i < vlist->versions().size(); i++) {
- auto version = vlist->versions().at(i);
- auto reqs = version->requires();
-
- // filter by minecraft version, if the loader depends on a certain version.
- if (minecraftVersion != Q_NULLPTR) {
- auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) {
- return req.uid == "net.minecraft";
- });
- if (iter == reqs.end()) continue;
- if (iter->equalsVersion != minecraftVersion) continue;
- }
-
- // first recommended build we find, we use.
- if (version->isRecommended()) {
- vselect.setCurrentVersion(version->descriptor());
- break;
- }
- }
-
- vselect.exec();
- return vselect.selectedVersion()->descriptor();
-}
-
-void AtlPage::displayMessage(QString message)
-{
- QMessageBox::information(this, tr("Installing"), message);
-}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
index aa6d5da1..1b3b15c1 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
@@ -52,7 +52,7 @@ namespace Ui
class NewInstanceDialog;
-class AtlPage : public QWidget, public BasePage, public ATLauncher::UserInteractionSupport
+class AtlPage : public QWidget, public BasePage
{
Q_OBJECT
@@ -83,10 +83,6 @@ public:
private:
void suggestCurrent();
- QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override;
- QVector<QString> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
- void displayMessage(QString message) override;
-
private slots:
void triggerSearch();
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
new file mode 100644
index 00000000..03196685
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QMessageBox>
+#include "AtlUserInteractionSupportImpl.h"
+
+#include "AtlOptionalModDialog.h"
+#include "ui/dialogs/VersionSelectDialog.h"
+
+AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget *parent) : m_parent(parent)
+{
+}
+
+QVector<QString> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
+{
+ AtlOptionalModDialog optionalModDialog(m_parent, version, mods);
+ optionalModDialog.exec();
+ return optionalModDialog.getResult();
+}
+
+QString AtlUserInteractionSupportImpl::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion)
+{
+ VersionSelectDialog vselect(vlist.get(), "Choose Version", m_parent, false);
+ if (minecraftVersion != nullptr) {
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
+ vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion));
+ }
+ else {
+ vselect.setEmptyString(tr("No versions are currently available"));
+ }
+ vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!"));
+
+ // select recommended build
+ for (int i = 0; i < vlist->versions().size(); i++) {
+ auto version = vlist->versions().at(i);
+ auto reqs = version->requires();
+
+ // filter by minecraft version, if the loader depends on a certain version.
+ if (minecraftVersion != nullptr) {
+ auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require& req) {
+ return req.uid == "net.minecraft";
+ });
+ if (iter == reqs.end())
+ continue;
+ if (iter->equalsVersion != minecraftVersion)
+ continue;
+ }
+
+ // first recommended build we find, we use.
+ if (version->isRecommended()) {
+ vselect.setCurrentVersion(version->descriptor());
+ break;
+ }
+ }
+
+ vselect.exec();
+ return vselect.selectedVersion()->descriptor();
+}
+
+void AtlUserInteractionSupportImpl::displayMessage(QString message)
+{
+ QMessageBox::information(m_parent, tr("Installing"), message);
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
new file mode 100644
index 00000000..aa22fc73
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QObject>
+
+#include "modplatform/atlauncher/ATLPackInstallTask.h"
+
+class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInteractionSupport {
+ Q_OBJECT
+
+public:
+ AtlUserInteractionSupportImpl(QWidget* parent);
+
+private:
+ QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override;
+ QVector<QString> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
+ void displayMessage(QString message) override;
+
+private:
+ QWidget* m_parent;
+
+};
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp
index 8a93bc2e..504d7f7b 100644
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp
+++ b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -126,7 +127,7 @@ void FtbPage::suggestCurrent()
return;
}
- dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion));
+ dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this));
for(auto art : selected.art) {
if(art.type == "square") {
QString editedLogoName;
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
index f4231d8d..ad08dc25 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
@@ -35,7 +35,11 @@
</widget>
</item>
<item row="0" column="1">
- <widget class="QTextBrowser" name="publicPackDescription"/>
+ <widget class="QTextBrowser" name="publicPackDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</widget>
@@ -45,7 +49,11 @@
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
- <widget class="QTextBrowser" name="thirdPartyPackDescription"/>
+ <widget class="QTextBrowser" name="thirdPartyPackDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
<item row="0" column="0">
<widget class="QTreeView" name="thirdPartyPackList">
@@ -95,7 +103,11 @@
</widget>
</item>
<item row="0" column="1" rowspan="3">
- <widget class="QTextBrowser" name="privatePackDescription"/>
+ <widget class="QTextBrowser" name="privatePackDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</widget>
diff --git a/launcher/updater/UpdateChecker.cpp b/launcher/updater/UpdateChecker.cpp
index fa6e5a97..78d979ff 100644
--- a/launcher/updater/UpdateChecker.cpp
+++ b/launcher/updater/UpdateChecker.cpp
@@ -25,12 +25,11 @@
#include "BuildConfig.h"
-UpdateChecker::UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild)
+UpdateChecker::UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel)
{
m_network = nam;
m_channelUrl = channelUrl;
m_currentChannel = currentChannel;
- m_currentBuild = currentBuild;
#ifdef Q_OS_MAC
m_externalUpdater = new MacSparkleUpdater();
diff --git a/launcher/updater/UpdateChecker.h b/launcher/updater/UpdateChecker.h
index 94e4312b..42ef318b 100644
--- a/launcher/updater/UpdateChecker.h
+++ b/launcher/updater/UpdateChecker.h
@@ -28,7 +28,7 @@ class UpdateChecker : public QObject
Q_OBJECT
public:
- UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild);
+ UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel);
void checkForUpdate(const QString& updateChannel, bool notifyNoUpdate);
/*!