diff options
author | Petr Mrázek <peterix@gmail.com> | 2021-07-25 19:11:59 +0200 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2021-07-25 19:50:44 +0200 |
commit | 20b9f2b42a3b58b6081af271774fbcc34025dccb (patch) | |
tree | 064fa59facb3357139b47bd4e60bfc8edb35ca11 /api | |
parent | dd133680858351e3e07690e286882327a4f42ba5 (diff) | |
download | PrismLauncher-20b9f2b42a3b58b6081af271774fbcc34025dccb.tar.gz PrismLauncher-20b9f2b42a3b58b6081af271774fbcc34025dccb.tar.bz2 PrismLauncher-20b9f2b42a3b58b6081af271774fbcc34025dccb.zip |
NOISSUE Flatten gui and logic libraries into MultiMC
Diffstat (limited to 'api')
370 files changed, 0 insertions, 43511 deletions
diff --git a/api/gui/CMakeLists.txt b/api/gui/CMakeLists.txt deleted file mode 100644 index ad116a43..00000000 --- a/api/gui/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -project(MultiMC_gui LANGUAGES CXX) - -set(GUI_SOURCES - DesktopServices.h - DesktopServices.cpp - - # Icons - icons/MMCIcon.h - icons/MMCIcon.cpp - icons/IconList.h - icons/IconList.cpp - - SkinUtils.cpp - SkinUtils.h -) -################################ COMPILE ################################ - -add_library(MultiMC_gui SHARED ${GUI_SOURCES}) -set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1) - -generate_export_header(MultiMC_gui) - -# Link -target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic Qt5::Gui) - -# Mark and export headers -target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") - -# Install it -install( - TARGETS MultiMC_gui - RUNTIME DESTINATION ${LIBRARY_DEST_DIR} - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} -)
\ No newline at end of file diff --git a/api/gui/DesktopServices.cpp b/api/gui/DesktopServices.cpp deleted file mode 100644 index 5368ddc8..00000000 --- a/api/gui/DesktopServices.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "DesktopServices.h" -#include <QDir> -#include <QDesktopServices> -#include <QProcess> -#include <QDebug> - -/** - * This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing. - */ -#if defined(Q_OS_LINUX) - -#include <unistd.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/wait.h> - -template <typename T> -bool IndirectOpen(T callable, qint64 *pid_forked = nullptr) -{ - auto pid = fork(); - if(pid_forked) - { - if(pid > 0) - *pid_forked = pid; - else - *pid_forked = 0; - } - if(pid == -1) - { - qWarning() << "IndirectOpen failed to fork: " << errno; - return false; - } - // child - do the stuff - if(pid == 0) - { - // unset all this garbage so it doesn't get passed to the child process - qunsetenv("LD_PRELOAD"); - qunsetenv("LD_LIBRARY_PATH"); - qunsetenv("LD_DEBUG"); - qunsetenv("QT_PLUGIN_PATH"); - qunsetenv("QT_FONTPATH"); - - // open the URL - auto status = callable(); - - // detach from the parent process group. - setsid(); - - // die. now. do not clean up anything, it would just hang forever. - _exit(status ? 0 : 1); - } - else - { - //parent - assume it worked. - int status; - while (waitpid(pid, &status, 0)) - { - if(WIFEXITED(status)) - { - return WEXITSTATUS(status) == 0; - } - if(WIFSIGNALED(status)) - { - return false; - } - } - return true; - } -} -#endif - -namespace DesktopServices { -bool openDirectory(const QString &path, bool ensureExists) -{ - qDebug() << "Opening directory" << path; - QDir parentPath; - QDir dir(path); - if (!dir.exists()) - { - parentPath.mkpath(dir.absolutePath()); - } - auto f = [&]() - { - return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); - }; -#if defined(Q_OS_LINUX) - return IndirectOpen(f); -#else - return f(); -#endif -} - -bool openFile(const QString &path) -{ - qDebug() << "Opening file" << path; - auto f = [&]() - { - return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); - }; -#if defined(Q_OS_LINUX) - return IndirectOpen(f); -#else - return f(); -#endif -} - -bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid) -{ - qDebug() << "Opening file" << path << "using" << application; -#if defined(Q_OS_LINUX) - // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave - return IndirectOpen([&]() - { - return QProcess::startDetached(application, QStringList() << path, workingDirectory); - }, pid); -#else - return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid); -#endif -} - -bool run(const QString &application, const QStringList &args, const QString &workingDirectory, qint64 *pid) -{ - qDebug() << "Running" << application << "with args" << args.join(' '); -#if defined(Q_OS_LINUX) - // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave - return IndirectOpen([&]() - { - return QProcess::startDetached(application, args, workingDirectory); - }, pid); -#else - return QProcess::startDetached(application, args, workingDirectory, pid); -#endif -} - -bool openUrl(const QUrl &url) -{ - qDebug() << "Opening URL" << url.toString(); - auto f = [&]() - { - return QDesktopServices::openUrl(url); - }; -#if defined(Q_OS_LINUX) - return IndirectOpen(f); -#else - return f(); -#endif -} - -} diff --git a/api/gui/DesktopServices.h b/api/gui/DesktopServices.h deleted file mode 100644 index 606fa52c..00000000 --- a/api/gui/DesktopServices.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include <QUrl> -#include <QString> -#include "multimc_gui_export.h" - -/** - * This wraps around QDesktopServices and adds workarounds where needed - * Use this instead of QDesktopServices! - */ -namespace DesktopServices -{ - /** - * Open a file in whatever application is applicable - */ - MULTIMC_GUI_EXPORT bool openFile(const QString &path); - - /** - * Open a file in the specified application - */ - MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0); - - /** - * Run an application - */ - MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0); - - /** - * Open a directory - */ - MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false); - - /** - * Open the URL, most likely in a browser. Maybe. - */ - MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url); -} diff --git a/api/gui/SkinUtils.cpp b/api/gui/SkinUtils.cpp deleted file mode 100644 index ec969889..00000000 --- a/api/gui/SkinUtils.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "SkinUtils.h" -#include "net/HttpMetaCache.h" -#include "Env.h" - -#include <QFile> -#include <QPainter> -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> - -namespace SkinUtils -{ -/* - * Given a username, return a pixmap of the cached skin (if it exists), QPixmap() otherwise - */ -QPixmap getFaceFromCache(QString username, int height, int width) -{ - QFile fskin(ENV.metacache() - ->resolveEntry("skins", username + ".png") - ->getFullPath()); - - if (fskin.exists()) - { - QPixmap skinTexture(fskin.fileName()); - if(!skinTexture.isNull()) - { - QPixmap skin = QPixmap(8, 8); - QPainter painter(&skin); - painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8)); - painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8)); - return skin.scaled(height, width, Qt::KeepAspectRatio); - } - } - - return QPixmap(); -} -} diff --git a/api/gui/SkinUtils.h b/api/gui/SkinUtils.h deleted file mode 100644 index b44f4228..00000000 --- a/api/gui/SkinUtils.h +++ /dev/null @@ -1,25 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QPixmap> - -#include "multimc_gui_export.h" - -namespace SkinUtils -{ -QPixmap MULTIMC_GUI_EXPORT getFaceFromCache(QString id, int height = 64, int width = 64); -} diff --git a/api/gui/icons/IconList.cpp b/api/gui/icons/IconList.cpp deleted file mode 100644 index 70350534..00000000 --- a/api/gui/icons/IconList.cpp +++ /dev/null @@ -1,419 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "IconList.h" -#include <FileSystem.h> -#include <QMap> -#include <QEventLoop> -#include <QMimeData> -#include <QUrl> -#include <QFileSystemWatcher> -#include <QSet> -#include <QDebug> - -#define MAX_SIZE 1024 - -IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent) -{ - QSet<QString> builtinNames; - - // add builtin icons - for(auto & builtinPath: builtinPaths) - { - QDir instance_icons(builtinPath); - auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name); - for (auto file_info : file_info_list) - { - builtinNames.insert(file_info.baseName()); - } - } - for(auto & builtinName : builtinNames) - { - addThemeIcon(builtinName); - } - - m_watcher.reset(new QFileSystemWatcher()); - is_watching = false; - connect(m_watcher.get(), SIGNAL(directoryChanged(QString)), - SLOT(directoryChanged(QString))); - connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); - - directoryChanged(path); -} - -void IconList::directoryChanged(const QString &path) -{ - QDir new_dir (path); - if(m_dir.absolutePath() != new_dir.absolutePath()) - { - m_dir.setPath(path); - m_dir.refresh(); - if(is_watching) - stopWatching(); - startWatching(); - } - if(!m_dir.exists()) - if(!FS::ensureFolderPathExists(m_dir.absolutePath())) - return; - m_dir.refresh(); - auto new_list = m_dir.entryList(QDir::Files, QDir::Name); - for (auto it = new_list.begin(); it != new_list.end(); it++) - { - QString &foo = (*it); - foo = m_dir.filePath(foo); - } - auto new_set = new_list.toSet(); - QList<QString> current_list; - for (auto &it : icons) - { - if (!it.has(IconType::FileBased)) - continue; - current_list.push_back(it.m_images[IconType::FileBased].filename); - } - QSet<QString> current_set = current_list.toSet(); - - QSet<QString> to_remove = current_set; - to_remove -= new_set; - - QSet<QString> to_add = new_set; - to_add -= current_set; - - for (auto remove : to_remove) - { - qDebug() << "Removing " << remove; - QFileInfo rmfile(remove); - QString key = rmfile.baseName(); - int idx = getIconIndex(key); - if (idx == -1) - continue; - icons[idx].remove(IconType::FileBased); - if (icons[idx].type() == IconType::ToBeDeleted) - { - beginRemoveRows(QModelIndex(), idx, idx); - icons.remove(idx); - reindex(); - endRemoveRows(); - } - else - { - dataChanged(index(idx), index(idx)); - } - m_watcher->removePath(remove); - emit iconUpdated(key); - } - - for (auto add : to_add) - { - qDebug() << "Adding " << add; - QFileInfo addfile(add); - QString key = addfile.baseName(); - if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) - { - m_watcher->addPath(add); - emit iconUpdated(key); - } - } -} - -void IconList::fileChanged(const QString &path) -{ - qDebug() << "Checking " << path; - QFileInfo checkfile(path); - if (!checkfile.exists()) - return; - QString key = checkfile.baseName(); - int idx = getIconIndex(key); - if (idx == -1) - return; - QIcon icon(path); - if (!icon.availableSizes().size()) - return; - - icons[idx].m_images[IconType::FileBased].icon = icon; - dataChanged(index(idx), index(idx)); - emit iconUpdated(key); -} - -void IconList::SettingChanged(const Setting &setting, QVariant value) -{ - if(setting.id() != "IconsDir") - return; - - directoryChanged(value.toString()); -} - -void IconList::startWatching() -{ - auto abs_path = m_dir.absolutePath(); - FS::ensureFolderPathExists(abs_path); - is_watching = m_watcher->addPath(abs_path); - if (is_watching) - { - qDebug() << "Started watching " << abs_path; - } - else - { - qDebug() << "Failed to start watching " << abs_path; - } -} - -void IconList::stopWatching() -{ - m_watcher->removePaths(m_watcher->files()); - m_watcher->removePaths(m_watcher->directories()); - is_watching = false; -} - -QStringList IconList::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - return types; -} -Qt::DropActions IconList::supportedDropActions() const -{ - return Qt::CopyAction; -} - -bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) - return true; - // check if the action is supported - if (!data || !(action & supportedDropActions())) - return false; - - // files dropped from outside? - if (data->hasUrls()) - { - auto urls = data->urls(); - QStringList iconFiles; - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - continue; - iconFiles += url.toLocalFile(); - } - installIcons(iconFiles); - return true; - } - return false; -} - -Qt::ItemFlags IconList::flags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - if (index.isValid()) - return Qt::ItemIsDropEnabled | defaultFlags; - else - return Qt::ItemIsDropEnabled | defaultFlags; -} - -QVariant IconList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - - if (row < 0 || row >= icons.size()) - return QVariant(); - - switch (role) - { - case Qt::DecorationRole: - return icons[row].icon(); - case Qt::DisplayRole: - return icons[row].name(); - case Qt::UserRole: - return icons[row].m_key; - default: - return QVariant(); - } -} - -int IconList::rowCount(const QModelIndex &parent) const -{ - return icons.size(); -} - -void IconList::installIcons(const QStringList &iconFiles) -{ - for (QString file : iconFiles) - { - QFileInfo fileinfo(file); - if (!fileinfo.isReadable() || !fileinfo.isFile()) - continue; - QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName()); - - QString suffix = fileinfo.suffix(); - if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif") - continue; - - if (!QFile::copy(file, target)) - continue; - } -} - -void IconList::installIcon(const QString &file, const QString &name) -{ - QFileInfo fileinfo(file); - if(!fileinfo.isReadable() || !fileinfo.isFile()) - return; - - QString target = FS::PathCombine(m_dir.dirName(), name); - - QFile::copy(file, target); -} - -bool IconList::iconFileExists(const QString &key) const -{ - auto iconEntry = icon(key); - if(!iconEntry) - { - return false; - } - return iconEntry->has(IconType::FileBased); -} - -const MMCIcon *IconList::icon(const QString &key) const -{ - int iconIdx = getIconIndex(key); - if (iconIdx == -1) - return nullptr; - return &icons[iconIdx]; -} - -bool IconList::deleteIcon(const QString &key) -{ - int iconIdx = getIconIndex(key); - if (iconIdx == -1) - return false; - auto &iconEntry = icons[iconIdx]; - if (iconEntry.has(IconType::FileBased)) - { - return QFile::remove(iconEntry.m_images[IconType::FileBased].filename); - } - return false; -} - -bool IconList::addThemeIcon(const QString& key) -{ - auto iter = name_index.find(key); - if (iter != name_index.end()) - { - auto &oldOne = icons[*iter]; - oldOne.replace(Builtin, key); - dataChanged(index(*iter), index(*iter)); - return true; - } - else - { - // add a new icon - beginInsertRows(QModelIndex(), icons.size(), icons.size()); - { - MMCIcon mmc_icon; - mmc_icon.m_name = key; - mmc_icon.m_key = key; - mmc_icon.replace(Builtin, key); - icons.push_back(mmc_icon); - name_index[key] = icons.size() - 1; - } - endInsertRows(); - return true; - } -} - -bool IconList::addIcon(const QString &key, const QString &name, const QString &path, const IconType type) -{ - // replace the icon even? is the input valid? - QIcon icon(path); - if (icon.isNull()) - return false; - auto iter = name_index.find(key); - if (iter != name_index.end()) - { - auto &oldOne = icons[*iter]; - oldOne.replace(type, icon, path); - dataChanged(index(*iter), index(*iter)); - return true; - } - else - { - // add a new icon - beginInsertRows(QModelIndex(), icons.size(), icons.size()); - { - MMCIcon mmc_icon; - mmc_icon.m_name = name; - mmc_icon.m_key = key; - mmc_icon.replace(type, icon, path); - icons.push_back(mmc_icon); - name_index[key] = icons.size() - 1; - } - endInsertRows(); - return true; - } -} - -void IconList::saveIcon(const QString &key, const QString &path, const char * format) const -{ - auto icon = getIcon(key); - auto pixmap = icon.pixmap(128, 128); - pixmap.save(path, format); -} - - -void IconList::reindex() -{ - name_index.clear(); - int i = 0; - for (auto &iter : icons) - { - name_index[iter.m_key] = i; - i++; - } -} - -QIcon IconList::getIcon(const QString &key) const -{ - int icon_index = getIconIndex(key); - - if (icon_index != -1) - return icons[icon_index].icon(); - - // Fallback for icons that don't exist. - icon_index = getIconIndex("infinity"); - - if (icon_index != -1) - return icons[icon_index].icon(); - return QIcon(); -} - -int IconList::getIconIndex(const QString &key) const -{ - auto iter = name_index.find(key == "default" ? "infinity" : key); - if (iter != name_index.end()) - return *iter; - - return -1; -} - -QString IconList::getDirectory() const -{ - return m_dir.absolutePath(); -} - -//#include "IconList.moc" diff --git a/api/gui/icons/IconList.h b/api/gui/icons/IconList.h deleted file mode 100644 index f07415fa..00000000 --- a/api/gui/icons/IconList.h +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QMutex> -#include <QAbstractListModel> -#include <QFile> -#include <QDir> -#include <QtGui/QIcon> -#include <memory> -#include "MMCIcon.h" -#include "settings/Setting.h" -#include "Env.h" // there is a global icon list inside Env. -#include <icons/IIconList.h> - -#include "multimc_gui_export.h" - -class QFileSystemWatcher; - -class MULTIMC_GUI_EXPORT IconList : public QAbstractListModel, public IIconList -{ - Q_OBJECT -public: - explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0); - virtual ~IconList() {}; - - QIcon getIcon(const QString &key) const; - int getIconIndex(const QString &key) const; - QString getDirectory() const; - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - bool addThemeIcon(const QString &key); - bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) override; - void saveIcon(const QString &key, const QString &path, const char * format) const override; - bool deleteIcon(const QString &key) override; - bool iconFileExists(const QString &key) const override; - - virtual QStringList mimeTypes() const override; - virtual Qt::DropActions supportedDropActions() const override; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - - void installIcons(const QStringList &iconFiles) override; - void installIcon(const QString &file, const QString &name) override; - - const MMCIcon * icon(const QString &key) const; - - void startWatching(); - void stopWatching(); - -signals: - void iconUpdated(QString key); - -private: - // hide copy constructor - IconList(const IconList &) = delete; - // hide assign op - IconList &operator=(const IconList &) = delete; - void reindex(); - -public slots: - void directoryChanged(const QString &path); - -protected slots: - void fileChanged(const QString &path); - void SettingChanged(const Setting & setting, QVariant value); -private: - shared_qobject_ptr<QFileSystemWatcher> m_watcher; - bool is_watching; - QMap<QString, int> name_index; - QVector<MMCIcon> icons; - QDir m_dir; -}; diff --git a/api/gui/icons/MMCIcon.cpp b/api/gui/icons/MMCIcon.cpp deleted file mode 100644 index f0b82ec9..00000000 --- a/api/gui/icons/MMCIcon.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "MMCIcon.h" -#include <QFileInfo> -#include <xdgicon.h> - -IconType operator--(IconType &t, int) -{ - IconType temp = t; - switch (t) - { - case IconType::Builtin: - t = IconType::ToBeDeleted; - break; - case IconType::Transient: - t = IconType::Builtin; - break; - case IconType::FileBased: - t = IconType::Transient; - break; - default: - { - } - } - return temp; -} - -IconType MMCIcon::type() const -{ - return m_current_type; -} - -QString MMCIcon::name() const -{ - if (m_name.size()) - return m_name; - return m_key; -} - -bool MMCIcon::has(IconType _type) const -{ - return m_images[_type].present(); -} - -QIcon MMCIcon::icon() const -{ - if (m_current_type == IconType::ToBeDeleted) - return QIcon(); - auto & icon = m_images[m_current_type].icon; - if(!icon.isNull()) - return icon; - // FIXME: inject this. - return XdgIcon::fromTheme(m_images[m_current_type].key); -} - -void MMCIcon::remove(IconType rm_type) -{ - m_images[rm_type].filename = QString(); - m_images[rm_type].icon = QIcon(); - for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--) - { - if (m_images[iter].present()) - { - m_current_type = iter; - return; - } - } - m_current_type = IconType::ToBeDeleted; -} - -void MMCIcon::replace(IconType new_type, QIcon icon, QString path) -{ - if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) - { - m_current_type = new_type; - } - m_images[new_type].icon = icon; - m_images[new_type].filename = path; - m_images[new_type].key = QString(); -} - -void MMCIcon::replace(IconType new_type, const QString& key) -{ - if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) - { - m_current_type = new_type; - } - m_images[new_type].icon = QIcon(); - m_images[new_type].filename = QString(); - m_images[new_type].key = key; -} - -QString MMCIcon::getFilePath() const -{ - if(m_current_type == IconType::ToBeDeleted){ - return QString(); - } - return m_images[m_current_type].filename; -} - - -bool MMCIcon::isBuiltIn() const -{ - return m_current_type == IconType::Builtin; -} diff --git a/api/gui/icons/MMCIcon.h b/api/gui/icons/MMCIcon.h deleted file mode 100644 index fd14b5b7..00000000 --- a/api/gui/icons/MMCIcon.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QDateTime> -#include <QIcon> -#include <icons/IIconList.h> - -#include "multimc_gui_export.h" - -struct MULTIMC_GUI_EXPORT MMCImage -{ - QIcon icon; - QString key; - QString filename; - bool present() const - { - return !icon.isNull() || !key.isEmpty(); - } -}; - -struct MULTIMC_GUI_EXPORT MMCIcon -{ - QString m_key; - QString m_name; - MMCImage m_images[ICONS_TOTAL]; - IconType m_current_type = ToBeDeleted; - - IconType type() const; - QString name() const; - bool has(IconType _type) const; - QIcon icon() const; - void remove(IconType rm_type); - void replace(IconType new_type, QIcon icon, QString path = QString()); - void replace(IconType new_type, const QString &key); - bool isBuiltIn() const; - QString getFilePath() const; -}; diff --git a/api/logic/BaseInstaller.cpp b/api/logic/BaseInstaller.cpp deleted file mode 100644 index d61c3fe9..00000000 --- a/api/logic/BaseInstaller.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QFile> - -#include "BaseInstaller.h" -#include "minecraft/MinecraftInstance.h" - -BaseInstaller::BaseInstaller() -{ - -} - -bool BaseInstaller::isApplied(MinecraftInstance *on) -{ - return QFile::exists(filename(on->instanceRoot())); -} - -bool BaseInstaller::add(MinecraftInstance *to) -{ - if (!patchesDir(to->instanceRoot()).exists()) - { - QDir(to->instanceRoot()).mkdir("patches"); - } - - if (isApplied(to)) - { - if (!remove(to)) - { - return false; - } - } - - return true; -} - -bool BaseInstaller::remove(MinecraftInstance *from) -{ - return QFile::remove(filename(from->instanceRoot())); -} - -QString BaseInstaller::filename(const QString &root) const -{ - return patchesDir(root).absoluteFilePath(id() + ".json"); -} -QDir BaseInstaller::patchesDir(const QString &root) const -{ - return QDir(root + "/patches/"); -} diff --git a/api/logic/BaseInstaller.h b/api/logic/BaseInstaller.h deleted file mode 100644 index 3e40b355..00000000 --- a/api/logic/BaseInstaller.h +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <memory> - -#include "multimc_logic_export.h" - -class MinecraftInstance; -class QDir; -class QString; -class QObject; -class Task; -class BaseVersion; -typedef std::shared_ptr<BaseVersion> BaseVersionPtr; - -class MULTIMC_LOGIC_EXPORT BaseInstaller -{ -public: - BaseInstaller(); - virtual ~BaseInstaller(){}; - bool isApplied(MinecraftInstance *on); - - virtual bool add(MinecraftInstance *to); - virtual bool remove(MinecraftInstance *from); - - virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0; - -protected: - virtual QString id() const = 0; - QString filename(const QString &root) const; - QDir patchesDir(const QString &root) const; -}; diff --git a/api/logic/BaseInstance.cpp b/api/logic/BaseInstance.cpp deleted file mode 100644 index 46b45827..00000000 --- a/api/logic/BaseInstance.cpp +++ /dev/null @@ -1,275 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "BaseInstance.h" - -#include <QFileInfo> -#include <QDir> -#include <QDebug> - -#include "settings/INISettingsObject.h" -#include "settings/Setting.h" -#include "settings/OverrideSetting.h" - -#include "FileSystem.h" -#include "Commandline.h" - -BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : QObject() -{ - m_settings = settings; - 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); - - // Custom Commands - auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); - m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting); - m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting); - m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting); - - // Console - auto consoleSetting = m_settings->registerSetting("OverrideConsole", false); - m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting); - m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting); - m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting); - m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting); - - m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr); - m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); -} - -QString BaseInstance::getPreLaunchCommand() -{ - return settings()->get("PreLaunchCommand").toString(); -} - -QString BaseInstance::getWrapperCommand() -{ - return settings()->get("WrapperCommand").toString(); -} - -QString BaseInstance::getPostExitCommand() -{ - return settings()->get("PostExitCommand").toString(); -} - -int BaseInstance::getConsoleMaxLines() const -{ - auto lineSetting = settings()->getSetting("ConsoleMaxLines"); - bool conversionOk = false; - int maxLines = lineSetting->get().toInt(&conversionOk); - if(!conversionOk) - { - maxLines = lineSetting->defValue().toInt(); - qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; - } - return maxLines; -} - -bool BaseInstance::shouldStopOnConsoleOverflow() const -{ - return settings()->get("ConsoleOverflowStop").toBool(); -} - -void BaseInstance::iconUpdated(QString key) -{ - if(iconKey() == key) - { - emit propertiesChanged(this); - } -} - -void BaseInstance::invalidate() -{ - changeStatus(Status::Gone); - qDebug() << "Instance" << id() << "has been invalidated."; -} - -void BaseInstance::changeStatus(BaseInstance::Status newStatus) -{ - Status status = currentStatus(); - if(status != newStatus) - { - m_status = newStatus; - emit statusChanged(status, newStatus); - } -} - -BaseInstance::Status BaseInstance::currentStatus() const -{ - return m_status; -} - -QString BaseInstance::id() const -{ - return QFileInfo(instanceRoot()).fileName(); -} - -bool BaseInstance::isRunning() const -{ - return m_isRunning; -} - -void BaseInstance::setRunning(bool running) -{ - if(running == m_isRunning) - return; - - m_isRunning = running; - - if(!m_settings->get("RecordGameTime").toBool()) - { - emit runningStatusChanged(running); - return; - } - - if(running) - { - m_timeStarted = QDateTime::currentDateTime(); - } - else - { - QDateTime timeEnded = QDateTime::currentDateTime(); - - qint64 current = settings()->get("totalTimePlayed").toLongLong(); - settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded)); - settings()->set("lastTimePlayed", m_timeStarted.secsTo(timeEnded)); - - emit propertiesChanged(this); - } - - emit runningStatusChanged(running); -} - -int64_t BaseInstance::totalTimePlayed() const -{ - qint64 current = settings()->get("totalTimePlayed").toLongLong(); - if(m_isRunning) - { - QDateTime timeNow = QDateTime::currentDateTime(); - return current + m_timeStarted.secsTo(timeNow); - } - return current; -} - -int64_t BaseInstance::lastTimePlayed() const -{ - if(m_isRunning) - { - QDateTime timeNow = QDateTime::currentDateTime(); - return m_timeStarted.secsTo(timeNow); - } - return settings()->get("lastTimePlayed").toLongLong(); -} - -void BaseInstance::resetTimePlayed() -{ - settings()->reset("totalTimePlayed"); - settings()->reset("lastTimePlayed"); -} - -QString BaseInstance::instanceType() const -{ - return m_settings->get("InstanceType").toString(); -} - -QString BaseInstance::instanceRoot() const -{ - return m_rootDir; -} - -SettingsObjectPtr BaseInstance::settings() const -{ - return m_settings; -} - -bool BaseInstance::canLaunch() const -{ - return (!hasVersionBroken() && !isRunning()); -} - -bool BaseInstance::reloadSettings() -{ - return m_settings->reload(); -} - -qint64 BaseInstance::lastLaunch() const -{ - return m_settings->get("lastLaunchTime").value<qint64>(); -} - -void BaseInstance::setLastLaunch(qint64 val) -{ - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("lastLaunchTime", val); - emit propertiesChanged(this); -} - -void BaseInstance::setNotes(QString val) -{ - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("notes", val); -} - -QString BaseInstance::notes() const -{ - return m_settings->get("notes").toString(); -} - -void BaseInstance::setIconKey(QString val) -{ - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("iconKey", val); - emit propertiesChanged(this); -} - -QString BaseInstance::iconKey() const -{ - return m_settings->get("iconKey").toString(); -} - -void BaseInstance::setName(QString val) -{ - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("name", val); - emit propertiesChanged(this); -} - -QString BaseInstance::name() const -{ - return m_settings->get("name").toString(); -} - -QString BaseInstance::windowTitle() const -{ - return "MultiMC: " + name().replace(QRegExp("[ \n\r\t]+"), " "); -} - -// FIXME: why is this here? move it to MinecraftInstance!!! -QStringList BaseInstance::extraArguments() const -{ - return Commandline::splitArgs(settings()->get("JvmArgs").toString()); -} - -shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask() -{ - return m_launchProcess; -} diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h deleted file mode 100644 index d250e03e..00000000 --- a/api/logic/BaseInstance.h +++ /dev/null @@ -1,272 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <cassert> - -#include <QObject> -#include "QObjectPtr.h" -#include <QDateTime> -#include <QSet> -#include <QProcess> - -#include "settings/SettingsObject.h" - -#include "settings/INIFile.h" -#include "BaseVersionList.h" -#include "minecraft/auth/MojangAccount.h" -#include "MessageLevel.h" -#include "pathmatcher/IPathMatcher.h" - -#include "net/Mode.h" - -#include "multimc_logic_export.h" - -#include "minecraft/launch/MinecraftServerTarget.h" - -class QDir; -class Task; -class LaunchTask; -class BaseInstance; - -// pointer for lazy people -typedef std::shared_ptr<BaseInstance> InstancePtr; - -/*! - * \brief Base class for instances. - * This class implements many functions that are common between instances and - * provides a standard interface for all instances. - * - * To create a new instance type, create a new class inheriting from this class - * and implement the pure virtual functions. - */ -class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance> -{ - Q_OBJECT -protected: - /// no-touchy! - BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - -public: /* types */ - enum class Status - { - Present, - Gone // either nuked or invalidated - }; - -public: - /// virtual destructor to make sure the destruction is COMPLETE - virtual ~BaseInstance() {}; - - virtual void saveNow() = 0; - - /*** - * the instance has been invalidated - it is no longer tracked by MultiMC for some reason, - * but it has not necessarily been deleted. - * - * Happens when the instance folder changes to some other location, or the instance is removed by external means. - */ - void invalidate(); - - /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to - /// be unique. - virtual QString id() const; - - void setRunning(bool running); - bool isRunning() const; - int64_t totalTimePlayed() const; - int64_t lastTimePlayed() const; - void resetTimePlayed(); - - /// get the type of this instance - QString instanceType() const; - - /// Path to the instance's root directory. - QString instanceRoot() const; - - /// Path to the instance's game root directory. - virtual QString gameRoot() const - { - return instanceRoot(); - } - - QString name() const; - void setName(QString val); - - /// Value used for instance window titles - QString windowTitle() const; - - QString iconKey() const; - void setIconKey(QString val); - - QString notes() const; - void setNotes(QString val); - - QString getPreLaunchCommand(); - QString getPostExitCommand(); - QString getWrapperCommand(); - - /// guess log level from a line of game log - virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) - { - return level; - }; - - virtual QStringList extraArguments() const; - - /// Traits. Normally inside the version, depends on instance implementation. - virtual QSet <QString> traits() const = 0; - - /** - * Gets the time that the instance was last launched. - * Stored in milliseconds since epoch. - */ - qint64 lastLaunch() const; - /// Sets the last launched time to 'val' milliseconds since epoch - void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch()); - - /*! - * \brief Gets this instance's settings object. - * This settings object stores instance-specific settings. - * \return A pointer to this instance's settings object. - */ - virtual SettingsObjectPtr settings() const; - - /// returns a valid update task - virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0; - - /// returns a valid launcher (task container) - virtual shared_qobject_ptr<LaunchTask> createLaunchTask( - AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0; - - /// returns the current launch task (if any) - shared_qobject_ptr<LaunchTask> getLaunchTask(); - - /*! - * Create envrironment variables for running the instance - */ - virtual QProcessEnvironment createEnvironment() = 0; - - /*! - * Returns a matcher that can maps relative paths within the instance to whether they are 'log files' - */ - virtual IPathMatcher::Ptr getLogFileMatcher() = 0; - - /*! - * Returns the root folder to use for looking up log files - */ - virtual QString getLogFileRoot() = 0; - - virtual QString getStatusbarDescription() = 0; - - /// FIXME: this really should be elsewhere... - virtual QString instanceConfigFolder() const = 0; - - /// get variables this instance exports - virtual QMap<QString, QString> getVariables() const = 0; - - virtual QString typeName() const = 0; - - bool hasVersionBroken() const - { - return m_hasBrokenVersion; - } - void setVersionBroken(bool value) - { - if(m_hasBrokenVersion != value) - { - m_hasBrokenVersion = value; - emit propertiesChanged(this); - } - } - - bool hasUpdateAvailable() const - { - return m_hasUpdate; - } - void setUpdateAvailable(bool value) - { - if(m_hasUpdate != value) - { - m_hasUpdate = value; - emit propertiesChanged(this); - } - } - - bool hasCrashed() const - { - return m_crashed; - } - void setCrashed(bool value) - { - if(m_crashed != value) - { - m_crashed = value; - emit propertiesChanged(this); - } - } - - virtual bool canLaunch() const; - virtual bool canEdit() const = 0; - virtual bool canExport() const = 0; - - bool reloadSettings(); - - /** - * 'print' a verbose description of the instance into a QStringList - */ - virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) = 0; - - Status currentStatus() const; - - int getConsoleMaxLines() const; - bool shouldStopOnConsoleOverflow() const; - -protected: - void changeStatus(Status newStatus); - -signals: - /*! - * \brief Signal emitted when properties relevant to the instance view change - */ - void propertiesChanged(BaseInstance *inst); - - void launchTaskChanged(shared_qobject_ptr<LaunchTask>); - - void runningStatusChanged(bool running); - - void statusChanged(Status from, Status to); - -protected slots: - void iconUpdated(QString key); - -protected: /* data */ - QString m_rootDir; - SettingsObjectPtr m_settings; - // InstanceFlags m_flags; - bool m_isRunning = false; - shared_qobject_ptr<LaunchTask> m_launchProcess; - QDateTime m_timeStarted; - -private: /* data */ - Status m_status = Status::Present; - bool m_crashed = false; - bool m_hasUpdate = false; - bool m_hasBrokenVersion = false; -}; - -Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>) -//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag) -//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags) diff --git a/api/logic/BaseVersion.h b/api/logic/BaseVersion.h deleted file mode 100644 index b88105fb..00000000 --- a/api/logic/BaseVersion.h +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <memory> -#include <QString> -#include <QMetaType> - -/*! - * An abstract base class for versions. - */ -class BaseVersion -{ -public: - virtual ~BaseVersion() {} - /*! - * A string used to identify this version in config files. - * This should be unique within the version list or shenanigans will occur. - */ - virtual QString descriptor() = 0; - - /*! - * The name of this version as it is displayed to the user. - * For example: "1.5.1" - */ - virtual QString name() = 0; - - /*! - * This should return a string that describes - * the kind of version this is (Stable, Beta, Snapshot, whatever) - */ - virtual QString typeString() const = 0; - - virtual bool operator<(BaseVersion &a) - { - return name() < a.name(); - }; - virtual bool operator>(BaseVersion &a) - { - return name() > a.name(); - }; -}; - -typedef std::shared_ptr<BaseVersion> BaseVersionPtr; - -Q_DECLARE_METATYPE(BaseVersionPtr) diff --git a/api/logic/BaseVersionList.cpp b/api/logic/BaseVersionList.cpp deleted file mode 100644 index aa9cb6cf..00000000 --- a/api/logic/BaseVersionList.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "BaseVersionList.h" -#include "BaseVersion.h" - -BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent) -{ -} - -BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor) -{ - for (int i = 0; i < count(); i++) - { - if (at(i)->descriptor() == descriptor) - return at(i); - } - return BaseVersionPtr(); -} - -BaseVersionPtr BaseVersionList::getRecommended() const -{ - if (count() <= 0) - return BaseVersionPtr(); - else - return at(0); -} - -QVariant BaseVersionList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - BaseVersionPtr version = at(index.row()); - - switch (role) - { - case VersionPointerRole: - return qVariantFromValue(version); - - case VersionRole: - return version->name(); - - case VersionIdRole: - return version->descriptor(); - - case TypeRole: - return version->typeString(); - - default: - return QVariant(); - } -} - -BaseVersionList::RoleList BaseVersionList::providesRoles() const -{ - return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole}; -} - -int BaseVersionList::rowCount(const QModelIndex &parent) const -{ - // Return count - return count(); -} - -int BaseVersionList::columnCount(const QModelIndex &parent) const -{ - return 1; -} - -QHash<int, QByteArray> BaseVersionList::roleNames() const -{ - QHash<int, QByteArray> roles = QAbstractListModel::roleNames(); - roles.insert(VersionRole, "version"); - roles.insert(VersionIdRole, "versionId"); - roles.insert(ParentVersionRole, "parentGameVersion"); - roles.insert(RecommendedRole, "recommended"); - roles.insert(LatestRole, "latest"); - roles.insert(TypeRole, "type"); - roles.insert(BranchRole, "branch"); - roles.insert(PathRole, "path"); - roles.insert(ArchitectureRole, "architecture"); - return roles; -} diff --git a/api/logic/BaseVersionList.h b/api/logic/BaseVersionList.h deleted file mode 100644 index 29e21bdb..00000000 --- a/api/logic/BaseVersionList.h +++ /dev/null @@ -1,122 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QVariant> -#include <QAbstractListModel> - -#include "BaseVersion.h" -#include "tasks/Task.h" -#include "multimc_logic_export.h" -#include "QObjectPtr.h" - -/*! - * \brief Class that each instance type's version list derives from. - * Version lists are the lists that keep track of the available game versions - * for that instance. This list will not be loaded on startup. It will be loaded - * when the list's load function is called. Before using the version list, you - * should check to see if it has been loaded yet and if not, load the list. - * - * Note that this class also inherits from QAbstractListModel. Methods from that - * class determine how this version list shows up in a list view. Said methods - * all have a default implementation, but they can be overridden by plugins to - * change the behavior of the list. - */ -class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel -{ - Q_OBJECT -public: - enum ModelRoles - { - VersionPointerRole = Qt::UserRole, - VersionRole, - VersionIdRole, - ParentVersionRole, - RecommendedRole, - LatestRole, - TypeRole, - BranchRole, - PathRole, - ArchitectureRole, - SortRole - }; - typedef QList<int> RoleList; - - explicit BaseVersionList(QObject *parent = 0); - - /*! - * \brief Gets a task that will reload the version list. - * Simply execute the task to load the list. - * The task returned by this function should reset the model when it's done. - * \return A pointer to a task that reloads the version list. - */ - virtual shared_qobject_ptr<Task> getLoadTask() = 0; - - //! Checks whether or not the list is loaded. If this returns false, the list should be - //loaded. - virtual bool isLoaded() = 0; - - //! Gets the version at the given index. - virtual const BaseVersionPtr at(int i) const = 0; - - //! Returns the number of versions in the list. - virtual int count() const = 0; - - //////// List Model Functions //////// - QVariant data(const QModelIndex &index, int role) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QHash<int, QByteArray> roleNames() const override; - - //! which roles are provided by this version list? - virtual RoleList providesRoles() const; - - /*! - * \brief Finds a version by its descriptor. - * \param descriptor The descriptor of the version to find. - * \return A const pointer to the version with the given descriptor. NULL if - * one doesn't exist. - */ - virtual BaseVersionPtr findVersion(const QString &descriptor); - - /*! - * \brief Gets the recommended version from this list - * If the list doesn't support recommended versions, this works exactly as getLatestStable - */ - virtual BaseVersionPtr getRecommended() const; - - /*! - * Sorts the version list. - */ - virtual void sortVersions() = 0; - -protected -slots: - /*! - * Updates this list with the given list of versions. - * This is done by copying each version in the given list and inserting it - * into this one. - * We need to do this so that we can set the parents of the versions are set to this - * version list. This can't be done in the load task, because the versions the load - * task creates are on the load task's thread and Qt won't allow their parents - * to be set to something created on another thread. - * To get around that problem, we invoke this method on the GUI thread, which - * then copies the versions and sets their parents correctly. - * \param versions List of versions whose parents should be set. - */ - virtual void updateListData(QList<BaseVersionPtr> versions) = 0; -}; diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt deleted file mode 100644 index 6d269714..00000000 --- a/api/logic/CMakeLists.txt +++ /dev/null @@ -1,564 +0,0 @@ -project(MultiMC_logic) - -include (UnitTest) - -set(CORE_SOURCES - # LOGIC - Base classes and infrastructure - BaseInstaller.h - BaseInstaller.cpp - BaseVersionList.h - BaseVersionList.cpp - InstanceList.h - InstanceList.cpp - InstanceTask.h - InstanceTask.cpp - LoggedProcess.h - LoggedProcess.cpp - MessageLevel.cpp - MessageLevel.h - BaseVersion.h - BaseInstance.h - BaseInstance.cpp - NullInstance.h - MMCZip.h - MMCZip.cpp - MMCStrings.h - MMCStrings.cpp - - # Basic instance manipulation tasks (derived from InstanceTask) - InstanceCreationTask.h - InstanceCreationTask.cpp - InstanceCopyTask.h - InstanceCopyTask.cpp - InstanceImportTask.h - InstanceImportTask.cpp - - # Use tracking separate from memory management - Usable.h - - # Prefix tree where node names are strings between separators - SeparatorPrefixTree.h - - # WARNING: globals live here - Env.h - Env.cpp - - # String filters - Filter.h - Filter.cpp - - # JSON parsing helpers - Json.h - Json.cpp - - FileSystem.h - FileSystem.cpp - - Exception.h - - # RW lock protected map - RWStorage.h - - # A variable that has an implicit default value and keeps track of changes - DefaultVariable.h - - # a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms - QObjectPtr.h - - # Compression support - GZip.h - GZip.cpp - - # Command line parameter parsing - Commandline.h - Commandline.cpp - - # Version number string support - Version.h - Version.cpp - - # A Recursive file system watcher - RecursiveFileSystemWatcher.h - RecursiveFileSystemWatcher.cpp -) - -add_unit_test(FileSystem - SOURCES FileSystem_test.cpp - LIBS MultiMC_logic - DATA testdata - ) - -add_unit_test(GZip - SOURCES GZip_test.cpp - LIBS MultiMC_logic - ) - -set(PATHMATCHER_SOURCES - # Path matchers - pathmatcher/FSTreeMatcher.h - pathmatcher/IPathMatcher.h - pathmatcher/MultiMatcher.h - pathmatcher/RegexpMatcher.h -) - -set(NET_SOURCES - # network stuffs - net/ByteArraySink.h - net/ChecksumValidator.h - net/Download.cpp - net/Download.h - net/FileSink.cpp - net/FileSink.h - net/HttpMetaCache.cpp - net/HttpMetaCache.h - net/MetaCacheSink.cpp - net/MetaCacheSink.h - net/NetAction.h - net/NetJob.cpp - net/NetJob.h - net/PasteUpload.cpp - net/PasteUpload.h - net/Sink.h - net/Validator.h -) - -# Game launch logic -set(LAUNCH_SOURCES - launch/steps/LookupServerAddress.cpp - launch/steps/LookupServerAddress.h - launch/steps/PostLaunchCommand.cpp - launch/steps/PostLaunchCommand.h - launch/steps/PreLaunchCommand.cpp - launch/steps/PreLaunchCommand.h - launch/steps/TextPrint.cpp - launch/steps/TextPrint.h - launch/steps/Update.cpp - launch/steps/Update.h - launch/LaunchStep.cpp - launch/LaunchStep.h - launch/LaunchTask.cpp - launch/LaunchTask.h - launch/LogModel.cpp - launch/LogModel.h -) - -# Old update system -set(UPDATE_SOURCES - updater/GoUpdate.h - updater/GoUpdate.cpp - updater/UpdateChecker.h - updater/UpdateChecker.cpp - updater/DownloadTask.h - updater/DownloadTask.cpp -) - -add_unit_test(UpdateChecker - SOURCES updater/UpdateChecker_test.cpp - LIBS MultiMC_logic - DATA updater/testdata - ) - -add_unit_test(DownloadTask - SOURCES updater/DownloadTask_test.cpp - LIBS MultiMC_logic - DATA updater/testdata - ) - -# Rarely used notifications -set(NOTIFICATIONS_SOURCES - # Notifications - short warning messages - notifications/NotificationChecker.h - notifications/NotificationChecker.cpp -) - -# Backend for the news bar... there's usually no news. -set(NEWS_SOURCES - # News System - news/NewsChecker.h - news/NewsChecker.cpp - news/NewsEntry.h - news/NewsEntry.cpp -) - -# Icon interface -set(ICONS_SOURCES - # Icons System and related code - icons/IIconList.h - icons/IIconList.cpp - icons/IconUtils.h - icons/IconUtils.cpp -) - -# Minecraft services status checker -set(STATUS_SOURCES - # Status system - status/StatusChecker.h - status/StatusChecker.cpp -) - -# Support for Minecraft instances and launch -set(MINECRAFT_SOURCES - # Minecraft support - minecraft/auth/AuthSession.h - minecraft/auth/AuthSession.cpp - minecraft/auth/MojangAccountList.h - minecraft/auth/MojangAccountList.cpp - minecraft/auth/MojangAccount.h - minecraft/auth/MojangAccount.cpp - minecraft/auth/YggdrasilTask.h - minecraft/auth/YggdrasilTask.cpp - minecraft/auth/flows/AuthenticateTask.h - minecraft/auth/flows/AuthenticateTask.cpp - minecraft/auth/flows/RefreshTask.cpp - minecraft/auth/flows/RefreshTask.cpp - minecraft/auth/flows/ValidateTask.h - minecraft/auth/flows/ValidateTask.cpp - - minecraft/gameoptions/GameOptions.h - minecraft/gameoptions/GameOptions.cpp - - minecraft/update/AssetUpdateTask.h - minecraft/update/AssetUpdateTask.cpp - minecraft/update/FMLLibrariesTask.cpp - minecraft/update/FMLLibrariesTask.h - minecraft/update/FoldersTask.cpp - minecraft/update/FoldersTask.h - minecraft/update/LibrariesTask.cpp - minecraft/update/LibrariesTask.h - - minecraft/launch/ClaimAccount.cpp - minecraft/launch/ClaimAccount.h - minecraft/launch/CreateGameFolders.cpp - minecraft/launch/CreateGameFolders.h - minecraft/launch/ModMinecraftJar.cpp - minecraft/launch/ModMinecraftJar.h - minecraft/launch/DirectJavaLaunch.cpp - minecraft/launch/DirectJavaLaunch.h - minecraft/launch/ExtractNatives.cpp - minecraft/launch/ExtractNatives.h - minecraft/launch/LauncherPartLaunch.cpp - minecraft/launch/LauncherPartLaunch.h - minecraft/launch/MinecraftServerTarget.cpp - minecraft/launch/MinecraftServerTarget.h - minecraft/launch/PrintInstanceInfo.cpp - minecraft/launch/PrintInstanceInfo.h - minecraft/launch/ReconstructAssets.cpp - minecraft/launch/ReconstructAssets.h - minecraft/launch/ScanModFolders.cpp - minecraft/launch/ScanModFolders.h - minecraft/launch/VerifyJavaInstall.cpp - minecraft/launch/VerifyJavaInstall.h - - minecraft/legacy/LegacyModList.h - minecraft/legacy/LegacyModList.cpp - minecraft/legacy/LegacyInstance.h - minecraft/legacy/LegacyInstance.cpp - minecraft/legacy/LegacyUpgradeTask.h - minecraft/legacy/LegacyUpgradeTask.cpp - - minecraft/GradleSpecifier.h - minecraft/MinecraftInstance.cpp - minecraft/MinecraftInstance.h - minecraft/LaunchProfile.cpp - minecraft/LaunchProfile.h - minecraft/Component.cpp - minecraft/Component.h - minecraft/PackProfile.cpp - minecraft/PackProfile.h - minecraft/ComponentUpdateTask.cpp - minecraft/ComponentUpdateTask.h - minecraft/MinecraftLoadAndCheck.h - minecraft/MinecraftLoadAndCheck.cpp - minecraft/MinecraftUpdate.h - minecraft/MinecraftUpdate.cpp - minecraft/MojangVersionFormat.cpp - minecraft/MojangVersionFormat.h - minecraft/Rule.cpp - minecraft/Rule.h - minecraft/OneSixVersionFormat.cpp - minecraft/OneSixVersionFormat.h - minecraft/OpSys.cpp - minecraft/OpSys.h - minecraft/ParseUtils.cpp - minecraft/ParseUtils.h - minecraft/ProfileUtils.cpp - minecraft/ProfileUtils.h - minecraft/Library.cpp - minecraft/Library.h - minecraft/MojangDownloadInfo.h - minecraft/VersionFile.cpp - minecraft/VersionFile.h - minecraft/VersionFilterData.h - minecraft/VersionFilterData.cpp - minecraft/World.h - minecraft/World.cpp - minecraft/WorldList.h - minecraft/WorldList.cpp - - minecraft/mod/Mod.h - minecraft/mod/Mod.cpp - minecraft/mod/ModDetails.h - minecraft/mod/ModFolderModel.h - minecraft/mod/ModFolderModel.cpp - minecraft/mod/ModFolderLoadTask.h - minecraft/mod/ModFolderLoadTask.cpp - minecraft/mod/LocalModParseTask.h - minecraft/mod/LocalModParseTask.cpp - minecraft/mod/ResourcePackFolderModel.h - minecraft/mod/ResourcePackFolderModel.cpp - minecraft/mod/TexturePackFolderModel.h - minecraft/mod/TexturePackFolderModel.cpp - - # Assets - minecraft/AssetsUtils.h - minecraft/AssetsUtils.cpp - - # Minecraft services - minecraft/services/SkinUpload.cpp - minecraft/services/SkinUpload.h - minecraft/services/SkinDelete.cpp - minecraft/services/SkinDelete.h - - mojang/PackageManifest.h - mojang/PackageManifest.cpp - ) - -add_unit_test(GradleSpecifier - SOURCES minecraft/GradleSpecifier_test.cpp - LIBS MultiMC_logic - ) - -add_executable(PackageManifest - mojang/PackageManifest_test.cpp -) -target_link_libraries(PackageManifest - MultiMC_logic - Qt5::Test -) -target_include_directories(PackageManifest - PRIVATE ../../cmake/UnitTest/ -) -add_test( - NAME PackageManifest - COMMAND PackageManifest - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) - -add_unit_test(MojangVersionFormat - SOURCES minecraft/MojangVersionFormat_test.cpp - LIBS MultiMC_logic - DATA minecraft/testdata - ) - -add_unit_test(Library - SOURCES minecraft/Library_test.cpp - LIBS MultiMC_logic - ) - -# FIXME: shares data with FileSystem test -add_unit_test(ModFolderModel - SOURCES minecraft/mod/ModFolderModel_test.cpp - DATA testdata - LIBS MultiMC_logic - ) - -add_unit_test(ParseUtils - SOURCES minecraft/ParseUtils_test.cpp - LIBS MultiMC_logic - ) - -# the screenshots feature -set(SCREENSHOTS_SOURCES - screenshots/Screenshot.h - screenshots/ImgurUpload.h - screenshots/ImgurUpload.cpp - screenshots/ImgurAlbumCreation.h - screenshots/ImgurAlbumCreation.cpp -) - -set(TASKS_SOURCES - # Tasks - tasks/Task.h - tasks/Task.cpp - tasks/SequentialTask.h - tasks/SequentialTask.cpp -) - -set(SETTINGS_SOURCES - # Settings - settings/INIFile.cpp - settings/INIFile.h - settings/INISettingsObject.cpp - settings/INISettingsObject.h - settings/OverrideSetting.cpp - settings/OverrideSetting.h - settings/PassthroughSetting.cpp - settings/PassthroughSetting.h - settings/Setting.cpp - settings/Setting.h - settings/SettingsObject.cpp - settings/SettingsObject.h -) - -add_unit_test(INIFile - SOURCES settings/INIFile_test.cpp - LIBS MultiMC_logic - ) - -set(JAVA_SOURCES - # Java related code - java/launch/CheckJava.cpp - java/launch/CheckJava.h - java/JavaChecker.h - java/JavaChecker.cpp - java/JavaCheckerJob.h - java/JavaCheckerJob.cpp - java/JavaInstall.h - java/JavaInstall.cpp - java/JavaInstallList.h - java/JavaInstallList.cpp - java/JavaUtils.h - java/JavaUtils.cpp - java/JavaVersion.h - java/JavaVersion.cpp -) - -add_unit_test(JavaVersion - SOURCES java/JavaVersion_test.cpp - LIBS MultiMC_logic - ) - -set(TRANSLATIONS_SOURCES - translations/TranslationsModel.h - translations/TranslationsModel.cpp - translations/POTranslator.h - translations/POTranslator.cpp -) - -set(TOOLS_SOURCES - # Tools - tools/BaseExternalTool.cpp - tools/BaseExternalTool.h - tools/BaseProfiler.cpp - tools/BaseProfiler.h - tools/JProfiler.cpp - tools/JProfiler.h - tools/JVisualVM.cpp - tools/JVisualVM.h - tools/MCEditTool.cpp - tools/MCEditTool.h -) - -set(META_SOURCES - # Metadata sources - meta/JsonFormat.cpp - meta/JsonFormat.h - meta/BaseEntity.cpp - meta/BaseEntity.h - meta/VersionList.cpp - meta/VersionList.h - meta/Version.cpp - meta/Version.h - meta/Index.cpp - meta/Index.h -) - -set(FTB_SOURCES - modplatform/legacy_ftb/PackFetchTask.h - modplatform/legacy_ftb/PackFetchTask.cpp - modplatform/legacy_ftb/PackInstallTask.h - modplatform/legacy_ftb/PackInstallTask.cpp - modplatform/legacy_ftb/PrivatePackManager.h - modplatform/legacy_ftb/PrivatePackManager.cpp - - modplatform/legacy_ftb/PackHelpers.h -) - -set(FLAME_SOURCES - # Flame - modplatform/flame/FlamePackIndex.cpp - modplatform/flame/FlamePackIndex.h - modplatform/flame/PackManifest.h - modplatform/flame/PackManifest.cpp - modplatform/flame/FileResolvingTask.h - modplatform/flame/FileResolvingTask.cpp -) - -set(MODPACKSCH_SOURCES - modplatform/modpacksch/FTBPackInstallTask.h - modplatform/modpacksch/FTBPackInstallTask.cpp - modplatform/modpacksch/FTBPackManifest.h - modplatform/modpacksch/FTBPackManifest.cpp -) - -set(TECHNIC_SOURCES - modplatform/technic/SingleZipPackInstallTask.h - modplatform/technic/SingleZipPackInstallTask.cpp - modplatform/technic/SolderPackInstallTask.h - modplatform/technic/SolderPackInstallTask.cpp - modplatform/technic/TechnicPackProcessor.h - modplatform/technic/TechnicPackProcessor.cpp -) - -set(ATLAUNCHER_SOURCES - modplatform/atlauncher/ATLPackIndex.cpp - modplatform/atlauncher/ATLPackIndex.h - modplatform/atlauncher/ATLPackInstallTask.cpp - modplatform/atlauncher/ATLPackInstallTask.h - modplatform/atlauncher/ATLPackManifest.cpp - modplatform/atlauncher/ATLPackManifest.h -) - -add_unit_test(Index - SOURCES meta/Index_test.cpp - LIBS MultiMC_logic - ) - -################################ COMPILE ################################ - -# we need zlib -find_package(ZLIB REQUIRED) - -set(LOGIC_SOURCES - ${CORE_SOURCES} - ${PATHMATCHER_SOURCES} - ${NET_SOURCES} - ${LAUNCH_SOURCES} - ${UPDATE_SOURCES} - ${NOTIFICATIONS_SOURCES} - ${NEWS_SOURCES} - ${STATUS_SOURCES} - ${MINECRAFT_SOURCES} - ${SCREENSHOTS_SOURCES} - ${TASKS_SOURCES} - ${SETTINGS_SOURCES} - ${JAVA_SOURCES} - ${TRANSLATIONS_SOURCES} - ${TOOLS_SOURCES} - ${META_SOURCES} - ${ICONS_SOURCES} - ${FTB_SOURCES} - ${FLAME_SOURCES} - ${MODPACKSCH_SOURCES} - ${TECHNIC_SOURCES} - ${ATLAUNCHER_SOURCES} -) - -add_library(MultiMC_logic SHARED ${LOGIC_SOURCES}) -set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1) - -generate_export_header(MultiMC_logic) - -# Link -target_link_libraries(MultiMC_logic systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES} optional-bare tomlc99 BuildConfig) -target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent) - -# Mark and export headers -target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}") - -# Install it -install( - TARGETS MultiMC_logic - RUNTIME DESTINATION ${LIBRARY_DEST_DIR} - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} -) diff --git a/api/logic/Commandline.cpp b/api/logic/Commandline.cpp deleted file mode 100644 index 2c0fde64..00000000 --- a/api/logic/Commandline.cpp +++ /dev/null @@ -1,483 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Authors: Orochimarufan <orochimarufan.x3@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 "Commandline.h" - -/** - * @file libutil/src/cmdutils.cpp - */ - -namespace Commandline -{ - -// commandline splitter -QStringList splitArgs(QString args) -{ - QStringList argv; - QString current; - bool escape = false; - QChar inquotes; - for (int i = 0; i < args.length(); i++) - { - QChar cchar = args.at(i); - - // \ escaped - if (escape) - { - current += cchar; - escape = false; - // in "quotes" - } - else if (!inquotes.isNull()) - { - if (cchar == '\\') - escape = true; - else if (cchar == inquotes) - inquotes = 0; - else - current += cchar; - // otherwise - } - else - { - if (cchar == ' ') - { - if (!current.isEmpty()) - { - argv << current; - current.clear(); - } - } - else if (cchar == '"' || cchar == '\'') - inquotes = cchar; - else - current += cchar; - } - } - if (!current.isEmpty()) - argv << current; - return argv; -} - -Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle) -{ - m_flagStyle = flagStyle; - m_argStyle = argStyle; -} - -// styles setter/getter -void Parser::setArgumentStyle(ArgumentStyle::Enum style) -{ - m_argStyle = style; -} -ArgumentStyle::Enum Parser::argumentStyle() -{ - return m_argStyle; -} - -void Parser::setFlagStyle(FlagStyle::Enum style) -{ - m_flagStyle = style; -} -FlagStyle::Enum Parser::flagStyle() -{ - return m_flagStyle; -} - -// setup methods -void Parser::addSwitch(QString name, bool def) -{ - if (m_params.contains(name)) - throw "Name not unique"; - - OptionDef *param = new OptionDef; - param->type = otSwitch; - param->name = name; - param->metavar = QString("<%1>").arg(name); - param->def = def; - - m_options[name] = param; - m_params[name] = (CommonDef *)param; - m_optionList.append(param); -} - -void Parser::addOption(QString name, QVariant def) -{ - if (m_params.contains(name)) - throw "Name not unique"; - - OptionDef *param = new OptionDef; - param->type = otOption; - param->name = name; - param->metavar = QString("<%1>").arg(name); - param->def = def; - - m_options[name] = param; - m_params[name] = (CommonDef *)param; - m_optionList.append(param); -} - -void Parser::addArgument(QString name, bool required, QVariant def) -{ - if (m_params.contains(name)) - throw "Name not unique"; - - PositionalDef *param = new PositionalDef; - param->name = name; - param->def = def; - param->required = required; - param->metavar = name; - - m_positionals.append(param); - m_params[name] = (CommonDef *)param; -} - -void Parser::addDocumentation(QString name, QString doc, QString metavar) -{ - if (!m_params.contains(name)) - throw "Name does not exist"; - - CommonDef *param = m_params[name]; - param->doc = doc; - if (!metavar.isNull()) - param->metavar = metavar; -} - -void Parser::addShortOpt(QString name, QChar flag) -{ - if (!m_params.contains(name)) - throw "Name does not exist"; - if (!m_options.contains(name)) - throw "Name is not an Option or Swtich"; - - OptionDef *param = m_options[name]; - m_flags[flag] = param; - param->flag = flag; -} - -// help methods -QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags) -{ - QStringList help; - help << compileUsage(progName, useFlags) << "\r\n"; - - // positionals - if (!m_positionals.isEmpty()) - { - help << "\r\n"; - help << "Positional arguments:\r\n"; - QListIterator<PositionalDef *> it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *param = it2.next(); - help << " " << param->metavar; - help << " " << QString(helpIndent - param->metavar.length() - 1, ' '); - help << param->doc << "\r\n"; - } - } - - // Options - if (!m_optionList.isEmpty()) - { - help << "\r\n"; - QString optPrefix, flagPrefix; - getPrefix(optPrefix, flagPrefix); - - help << "Options & Switches:\r\n"; - QListIterator<OptionDef *> it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - help << " "; - int nameLength = optPrefix.length() + option->name.length(); - if (!option->flag.isNull()) - { - nameLength += 3 + flagPrefix.length(); - help << flagPrefix << option->flag << ", "; - } - help << optPrefix << option->name; - if (option->type == otOption) - { - QString arg = QString("%1%2").arg( - ((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar); - nameLength += arg.length(); - help << arg; - } - help << " " << QString(helpIndent - nameLength - 1, ' '); - help << option->doc << "\r\n"; - } - } - - return help.join(""); -} - -QString Parser::compileUsage(QString progName, bool useFlags) -{ - QStringList usage; - usage << "Usage: " << progName; - - QString optPrefix, flagPrefix; - getPrefix(optPrefix, flagPrefix); - - // options - QListIterator<OptionDef *> it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - usage << " ["; - if (!option->flag.isNull() && useFlags) - usage << flagPrefix << option->flag; - else - usage << optPrefix << option->name; - if (option->type == otOption) - usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar; - usage << "]"; - } - - // arguments - QListIterator<PositionalDef *> it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *param = it2.next(); - usage << " " << (param->required ? "<" : "["); - usage << param->metavar; - usage << (param->required ? ">" : "]"); - } - - return usage.join(""); -} - -// parsing -QHash<QString, QVariant> Parser::parse(QStringList argv) -{ - QHash<QString, QVariant> map; - - QStringListIterator it(argv); - QString programName = it.next(); - - QString optionPrefix; - QString flagPrefix; - QListIterator<PositionalDef *> positionals(m_positionals); - QStringList expecting; - - getPrefix(optionPrefix, flagPrefix); - - while (it.hasNext()) - { - QString arg = it.next(); - - if (!expecting.isEmpty()) - // we were expecting an argument - { - QString name = expecting.first(); -/* - if (map.contains(name)) - throw ParsingError( - QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); -*/ - map[name] = QVariant(arg); - - expecting.removeFirst(); - continue; - } - - if (arg.startsWith(optionPrefix)) - // we have an option - { - // qDebug("Found option %s", qPrintable(arg)); - - QString name = arg.mid(optionPrefix.length()); - QString equals; - - if ((m_argStyle == ArgumentStyle::Equals || - m_argStyle == ArgumentStyle::SpaceAndEquals) && - name.contains("=")) - { - int i = name.indexOf("="); - equals = name.mid(i + 1); - name = name.left(i); - } - - if (m_options.contains(name)) - { - /* - if (map.contains(name)) - throw ParsingError(QString("Option %2%1 was given multiple times") - .arg(name, optionPrefix)); -*/ - OptionDef *option = m_options[name]; - if (option->type == otSwitch) - map[name] = true; - else // if (option->type == otOption) - { - if (m_argStyle == ArgumentStyle::Space) - expecting.append(name); - else if (!equals.isNull()) - map[name] = equals; - else if (m_argStyle == ArgumentStyle::SpaceAndEquals) - expecting.append(name); - else - throw ParsingError(QString("Option %2%1 reqires an argument.") - .arg(name, optionPrefix)); - } - - continue; - } - - throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix)); - } - - if (arg.startsWith(flagPrefix)) - // we have (a) flag(s) - { - // qDebug("Found flags %s", qPrintable(arg)); - - QString flags = arg.mid(flagPrefix.length()); - QString equals; - - if ((m_argStyle == ArgumentStyle::Equals || - m_argStyle == ArgumentStyle::SpaceAndEquals) && - flags.contains("=")) - { - int i = flags.indexOf("="); - equals = flags.mid(i + 1); - flags = flags.left(i); - } - - for (int i = 0; i < flags.length(); i++) - { - QChar flag = flags.at(i); - - if (!m_flags.contains(flag)) - throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix)); - - OptionDef *option = m_flags[flag]; -/* - if (map.contains(option->name)) - throw ParsingError(QString("Option %2%1 was given multiple times") - .arg(option->name, optionPrefix)); -*/ - if (option->type == otSwitch) - map[option->name] = true; - else // if (option->type == otOption) - { - if (m_argStyle == ArgumentStyle::Space) - expecting.append(option->name); - else if (!equals.isNull()) - if (i == flags.length() - 1) - map[option->name] = equals; - else - throw ParsingError(QString("Flag %4%2 of Argument-requiring Option " - "%1 not last flag in %4%3") - .arg(option->name, flag, flags, flagPrefix)); - else if (m_argStyle == ArgumentStyle::SpaceAndEquals) - expecting.append(option->name); - else - throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)") - .arg(option->name, flag, flagPrefix)); - } - } - - continue; - } - - // must be a positional argument - if (!positionals.hasNext()) - throw ParsingError(QString("Don't know what to do with '%1'").arg(arg)); - - PositionalDef *param = positionals.next(); - - map[param->name] = arg; - } - - // check if we're missing something - if (!expecting.isEmpty()) - throw ParsingError(QString("Was still expecting arguments for %2%1").arg( - expecting.join(QString(", ") + optionPrefix), optionPrefix)); - - while (positionals.hasNext()) - { - PositionalDef *param = positionals.next(); - if (param->required) - throw ParsingError( - QString("Missing required positional argument '%1'").arg(param->name)); - else - map[param->name] = param->def; - } - - // fill out gaps - QListIterator<OptionDef *> iter(m_optionList); - while (iter.hasNext()) - { - OptionDef *option = iter.next(); - if (!map.contains(option->name)) - map[option->name] = option->def; - } - - return map; -} - -// clear defs -void Parser::clear() -{ - m_flags.clear(); - m_params.clear(); - m_options.clear(); - - QMutableListIterator<OptionDef *> it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - it.remove(); - delete option; - } - - QMutableListIterator<PositionalDef *> it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *arg = it2.next(); - it2.remove(); - delete arg; - } -} - -// Destructor -Parser::~Parser() -{ - clear(); -} - -// getPrefix -void Parser::getPrefix(QString &opt, QString &flag) -{ - if (m_flagStyle == FlagStyle::Windows) - opt = flag = "/"; - else if (m_flagStyle == FlagStyle::Unix) - opt = flag = "-"; - // else if (m_flagStyle == FlagStyle::GNU) - else - { - opt = "--"; - flag = "-"; - } -} - -// ParsingError -ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdString()) -{ -} -}
\ No newline at end of file diff --git a/api/logic/Commandline.h b/api/logic/Commandline.h deleted file mode 100644 index 09c1707e..00000000 --- a/api/logic/Commandline.h +++ /dev/null @@ -1,252 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Authors: Orochimarufan <orochimarufan.x3@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 - -#include <exception> -#include <stdexcept> - -#include <QString> -#include <QVariant> -#include <QHash> -#include <QStringList> - -#include "multimc_logic_export.h" - -/** - * @file libutil/include/cmdutils.h - * @brief commandline parsing and processing utilities - */ - -namespace Commandline -{ - -/** - * @brief split a string into argv items like a shell would do - * @param args the argument string - * @return a QStringList containing all arguments - */ -MULTIMC_LOGIC_EXPORT QStringList splitArgs(QString args); - -/** - * @brief The FlagStyle enum - * Specifies how flags are decorated - */ - -namespace FlagStyle -{ -enum Enum -{ - GNU, /**< --option and -o (GNU Style) */ - Unix, /**< -option and -o (Unix Style) */ - Windows, /**< /option and /o (Windows Style) */ -#ifdef Q_OS_WIN32 - Default = Windows -#else - Default = GNU -#endif -}; -} - -/** - * @brief The ArgumentStyle enum - */ -namespace ArgumentStyle -{ -enum Enum -{ - Space, /**< --option value */ - Equals, /**< --option=value */ - SpaceAndEquals, /**< --option[= ]value */ -#ifdef Q_OS_WIN32 - Default = Equals -#else - Default = SpaceAndEquals -#endif -}; -} - -/** - * @brief The ParsingError class - */ -class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error -{ -public: - ParsingError(const QString &what); -}; - -/** - * @brief The Parser class - */ -class MULTIMC_LOGIC_EXPORT Parser -{ -public: - /** - * @brief Parser constructor - * @param flagStyle the FlagStyle to use in this Parser - * @param argStyle the ArgumentStyle to use in this Parser - */ - Parser(FlagStyle::Enum flagStyle = FlagStyle::Default, - ArgumentStyle::Enum argStyle = ArgumentStyle::Default); - - /** - * @brief set the flag style - * @param style - */ - void setFlagStyle(FlagStyle::Enum style); - - /** - * @brief get the flag style - * @return - */ - FlagStyle::Enum flagStyle(); - - /** - * @brief set the argument style - * @param style - */ - void setArgumentStyle(ArgumentStyle::Enum style); - - /** - * @brief get the argument style - * @return - */ - ArgumentStyle::Enum argumentStyle(); - - /** - * @brief define a boolean switch - * @param name the parameter name - * @param def the default value - */ - void addSwitch(QString name, bool def = false); - - /** - * @brief define an option that takes an additional argument - * @param name the parameter name - * @param def the default value - */ - void addOption(QString name, QVariant def = QVariant()); - - /** - * @brief define a positional argument - * @param name the parameter name - * @param required wether this argument is required - * @param def the default value - */ - void addArgument(QString name, bool required = true, QVariant def = QVariant()); - - /** - * @brief adds a flag to an existing parameter - * @param name the (existing) parameter name - * @param flag the flag character - * @see addSwitch addArgument addOption - * Note: any one parameter can only have one flag - */ - void addShortOpt(QString name, QChar flag); - - /** - * @brief adds documentation to a Parameter - * @param name the parameter name - * @param metavar a string to be displayed as placeholder for the value - * @param doc a QString containing the documentation - * Note: on positional arguments, metavar replaces the name as displayed. - * on options , metavar replaces the value placeholder - */ - void addDocumentation(QString name, QString doc, QString metavar = QString()); - - /** - * @brief generate a help message - * @param progName the program name to use in the help message - * @param helpIndent how much the parameter documentation should be indented - * @param flagsInUsage whether we should use flags instead of options in the usage - * @return a help message - */ - QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true); - - /** - * @brief generate a short usage message - * @param progName the program name to use in the usage message - * @param useFlags whether we should use flags instead of options - * @return a usage message - */ - QString compileUsage(QString progName, bool useFlags = true); - - /** - * @brief parse - * @param argv a QStringList containing the program ARGV - * @return a QHash mapping argument names to their values - */ - QHash<QString, QVariant> parse(QStringList argv); - - /** - * @brief clear all definitions - */ - void clear(); - - ~Parser(); - -private: - FlagStyle::Enum m_flagStyle; - ArgumentStyle::Enum m_argStyle; - - enum OptionType - { - otSwitch, - otOption - }; - - // Important: the common part MUST BE COMMON ON ALL THREE structs - struct CommonDef - { - QString name; - QString doc; - QString metavar; - QVariant def; - }; - - struct OptionDef - { - // common - QString name; - QString doc; - QString metavar; - QVariant def; - // option - OptionType type; - QChar flag; - }; - - struct PositionalDef - { - // common - QString name; - QString doc; - QString metavar; - QVariant def; - // positional - bool required; - }; - - QHash<QString, OptionDef *> m_options; - QHash<QChar, OptionDef *> m_flags; - QHash<QString, CommonDef *> m_params; - QList<PositionalDef *> m_positionals; - QList<OptionDef *> m_optionList; - - void getPrefix(QString &opt, QString &flag); -}; -} diff --git a/api/logic/DefaultVariable.h b/api/logic/DefaultVariable.h deleted file mode 100644 index 5c069bd3..00000000 --- a/api/logic/DefaultVariable.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -template <typename T> -class DefaultVariable -{ -public: - DefaultVariable(const T & value) - { - defaultValue = value; - } - DefaultVariable<T> & operator =(const T & value) - { - currentValue = value; - is_default = currentValue == defaultValue; - is_explicit = true; - return *this; - } - operator const T &() const - { - return is_default ? defaultValue : currentValue; - } - bool isDefault() const - { - return is_default; - } - bool isExplicit() const - { - return is_explicit; - } -private: - T currentValue; - T defaultValue; - bool is_default = true; - bool is_explicit = false; -}; diff --git a/api/logic/Env.cpp b/api/logic/Env.cpp deleted file mode 100644 index 71b49d95..00000000 --- a/api/logic/Env.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "Env.h" -#include "net/HttpMetaCache.h" -#include "BaseVersion.h" -#include "BaseVersionList.h" -#include <QDir> -#include <QCoreApplication> -#include <QNetworkProxy> -#include <QNetworkAccessManager> -#include <QDebug> -#include "tasks/Task.h" -#include "meta/Index.h" -#include "FileSystem.h" -#include <QDebug> - - -struct Env::Private -{ - QNetworkAccessManager m_qnam; - shared_qobject_ptr<HttpMetaCache> m_metacache; - std::shared_ptr<IIconList> m_iconlist; - shared_qobject_ptr<Meta::Index> m_metadataIndex; - QString m_jarsPath; - QSet<QString> m_features; -}; - -static Env * instance; - -/* - * The *NEW* global rat nest of an object. Handle with care. - */ - -Env::Env() -{ - d = new Private(); -} - -Env::~Env() -{ - delete d; -} - -Env& Env::Env::getInstance() -{ - if(!instance) - { - instance = new Env(); - } - return *instance; -} - -void Env::dispose() -{ - delete instance; - instance = nullptr; -} - -shared_qobject_ptr< HttpMetaCache > Env::metacache() -{ - return d->m_metacache; -} - -QNetworkAccessManager& Env::qnam() const -{ - return d->m_qnam; -} - -std::shared_ptr<IIconList> Env::icons() -{ - return d->m_iconlist; -} - -void Env::registerIconList(std::shared_ptr<IIconList> iconlist) -{ - d->m_iconlist = iconlist; -} - -shared_qobject_ptr<Meta::Index> Env::metadataIndex() -{ - if (!d->m_metadataIndex) - { - d->m_metadataIndex.reset(new Meta::Index()); - } - return d->m_metadataIndex; -} - - -void Env::initHttpMetaCache() -{ - auto &m_metacache = d->m_metacache; - m_metacache.reset(new HttpMetaCache("metacache")); - m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath()); - m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath()); - m_metacache->addBase("versions", QDir("versions").absolutePath()); - m_metacache->addBase("libraries", QDir("libraries").absolutePath()); - m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath()); - m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath()); - m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath()); - m_metacache->addBase("general", QDir("cache").absolutePath()); - m_metacache->addBase("ATLauncherPacks", QDir("cache/ATLauncherPacks").absolutePath()); - m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath()); - 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("skins", QDir("accounts/skins").absolutePath()); - m_metacache->addBase("root", QDir::currentPath()); - m_metacache->addBase("translations", QDir("translations").absolutePath()); - m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); - m_metacache->addBase("meta", QDir("meta").absolutePath()); - m_metacache->Load(); -} - -void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password) -{ - // Set the application proxy settings. - if (proxyTypeStr == "SOCKS5") - { - QNetworkProxy::setApplicationProxy( - QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password)); - } - else if (proxyTypeStr == "HTTP") - { - QNetworkProxy::setApplicationProxy( - QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password)); - } - else if (proxyTypeStr == "None") - { - // If we have no proxy set, set no proxy and return. - QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy)); - } - else - { - // If we have "Default" selected, set Qt to use the system proxy settings. - QNetworkProxyFactory::setUseSystemConfiguration(true); - } - - qDebug() << "Detecting proxy settings..."; - QNetworkProxy proxy = QNetworkProxy::applicationProxy(); - d->m_qnam.setProxy(proxy); - QString proxyDesc; - if (proxy.type() == QNetworkProxy::NoProxy) - { - qDebug() << "Using no proxy is an option!"; - return; - } - switch (proxy.type()) - { - case QNetworkProxy::DefaultProxy: - proxyDesc = "Default proxy: "; - break; - case QNetworkProxy::Socks5Proxy: - proxyDesc = "Socks5 proxy: "; - break; - case QNetworkProxy::HttpProxy: - proxyDesc = "HTTP proxy: "; - break; - case QNetworkProxy::HttpCachingProxy: - proxyDesc = "HTTP caching: "; - break; - case QNetworkProxy::FtpCachingProxy: - proxyDesc = "FTP caching: "; - break; - default: - proxyDesc = "DERP proxy: "; - break; - } - proxyDesc += QString("%1:%2") - .arg(proxy.hostName()) - .arg(proxy.port()); - qDebug() << proxyDesc; -} - -QString Env::getJarsPath() -{ - if(d->m_jarsPath.isEmpty()) - { - return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars"); - } - return d->m_jarsPath; -} - -void Env::setJarsPath(const QString& path) -{ - d->m_jarsPath = path; -} - -void Env::enableFeature(const QString& featureName, bool state) -{ - if(state) - { - d->m_features.insert(featureName); - } - else - { - d->m_features.remove(featureName); - } -} - -bool Env::isFeatureEnabled(const QString& featureName) const -{ - return d->m_features.contains(featureName); -} - -void Env::getEnabledFeatures(QSet<QString>& features) const -{ - features = d->m_features; -} - -void Env::setEnabledFeatures(const QSet<QString>& features) const -{ - d->m_features = features; -} diff --git a/api/logic/Env.h b/api/logic/Env.h deleted file mode 100644 index 8b9b827e..00000000 --- a/api/logic/Env.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include <memory> -#include "icons/IIconList.h" -#include <QString> -#include <QMap> - -#include "multimc_logic_export.h" - -#include "QObjectPtr.h" - -class QNetworkAccessManager; -class HttpMetaCache; -class BaseVersionList; -class BaseVersion; - -namespace Meta -{ -class Index; -} - -#if defined(ENV) - #undef ENV -#endif -#define ENV (Env::getInstance()) - - -class MULTIMC_LOGIC_EXPORT Env -{ - friend class MultiMC; -private: - struct Private; - Env(); - ~Env(); - static void dispose(); -public: - static Env& getInstance(); - - QNetworkAccessManager &qnam() const; - - shared_qobject_ptr<HttpMetaCache> metacache(); - - std::shared_ptr<IIconList> icons(); - - /// init the cache. FIXME: possible future hook point - void initHttpMetaCache(); - - /// Updates the application proxy settings from the settings object. - void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password); - - void registerIconList(std::shared_ptr<IIconList> iconlist); - - shared_qobject_ptr<Meta::Index> metadataIndex(); - - QString getJarsPath(); - void setJarsPath(const QString & path); - - bool isFeatureEnabled(const QString & featureName) const; - void enableFeature(const QString & featureName, bool state = true); - void getEnabledFeatures(QSet<QString> & features) const; - void setEnabledFeatures(const QSet<QString> & features) const; - -protected: - Private * d; -}; diff --git a/api/logic/Exception.h b/api/logic/Exception.h deleted file mode 100644 index 9400b3f8..00000000 --- a/api/logic/Exception.h +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed under the Apache-2.0 license. See README.md for details. - -#pragma once - -#include <QString> -#include <QDebug> -#include <exception> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT Exception : public std::exception -{ -public: - Exception(const QString &message) : std::exception(), m_message(message) - { - qCritical() << "Exception:" << message; - } - Exception(const Exception &other) - : std::exception(), m_message(other.cause()) - { - } - virtual ~Exception() noexcept {} - const char *what() const noexcept - { - return m_message.toLatin1().constData(); - } - QString cause() const - { - return m_message; - } - -private: - QString m_message; -}; diff --git a/api/logic/ExponentialSeries.h b/api/logic/ExponentialSeries.h deleted file mode 100644 index a9487f0a..00000000 --- a/api/logic/ExponentialSeries.h +++ /dev/null @@ -1,43 +0,0 @@ - -#pragma once - -template <typename T> -inline void clamp(T& current, T min, T max) -{ - if (current < min) - { - current = min; - } - else if(current > max) - { - current = max; - } -} - -// List of numbers from min to max. Next is exponent times bigger than previous. - -class ExponentialSeries -{ -public: - ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2) - { - m_current = m_min = min; - m_max = max; - m_exponent = exponent; - } - void reset() - { - m_current = m_min; - } - unsigned operator()() - { - unsigned retval = m_current; - m_current *= m_exponent; - clamp(m_current, m_min, m_max); - return retval; - } - unsigned m_current; - unsigned m_min; - unsigned m_max; - unsigned m_exponent; -}; diff --git a/api/logic/FileSystem.cpp b/api/logic/FileSystem.cpp deleted file mode 100644 index 13f05b86..00000000 --- a/api/logic/FileSystem.cpp +++ /dev/null @@ -1,457 +0,0 @@ -// Licensed under the Apache-2.0 license. See README.md for details. - -#include "FileSystem.h" - -#include <QDir> -#include <QFile> -#include <QSaveFile> -#include <QFileInfo> -#include <QDebug> -#include <QUrl> -#include <QStandardPaths> -#include <QTextStream> - -#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> -#else - #include <utime.h> -#endif - -namespace FS { - -void ensureExists(const QDir &dir) -{ - if (!QDir().mkpath(dir.absolutePath())) - { - throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + - dir.absolutePath() + ")"); - } -} - -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 (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()); - } -} - -QByteArray read(const QString &filename) -{ - QFile file(filename); - 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()); - } - return data; -} - -bool updateTimestamp(const QString& filename) -{ -#ifdef Q_OS_WIN32 - std::wstring filename_utf_16 = filename.toStdWString(); - return (_wutime64(filename_utf_16.c_str(), nullptr) == 0); -#else - QByteArray filenameBA = QFile::encodeName(filename); - return (utime(filenameBA.data(), nullptr) == 0); -#endif -} - -bool ensureFilePathExists(QString filenamepath) -{ - QFileInfo a(filenamepath); - QDir dir; - QString ensuredPath = a.path(); - bool success = dir.mkpath(ensuredPath); - return success; -} - -bool ensureFolderPathExists(QString foldernamepath) -{ - QFileInfo a(foldernamepath); - QDir dir; - QString ensuredPath = a.filePath(); - bool success = dir.mkpath(ensuredPath); - return success; -} - -bool copy::operator()(const QString &offset) -{ - //NOTE always deep copy on windows. the alternatives are too messy. - #if defined Q_OS_WIN32 - m_followSymlinks = true; - #endif - - auto src = PathCombine(m_src.absolutePath(), offset); - auto dst = PathCombine(m_dst.absolutePath(), offset); - - QFileInfo currentSrc(src); - if (!currentSrc.exists()) - return false; - - if(!m_followSymlinks && currentSrc.isSymLink()) - { - qDebug() << "creating symlink" << src << " - " << dst; - if (!ensureFilePathExists(dst)) - { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::link(currentSrc.symLinkTarget(), dst); - } - else if(currentSrc.isFile()) - { - qDebug() << "copying file" << src << " - " << dst; - if (!ensureFilePathExists(dst)) - { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::copy(src, dst); - } - else if(currentSrc.isDir()) - { - qDebug() << "recursing" << offset; - 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)) - { - auto inner_offset = PathCombine(offset, f); - // ignore and skip stuff that matches the blacklist. - if(m_blacklist && m_blacklist->matches(inner_offset)) - { - continue; - } - if(!operator()(inner_offset)) - { - qWarning() << "Failed to copy" << inner_offset; - return false; - } - } - } - else - { - qCritical() << "Copy ERROR: Unknown filesystem object:" << src; - return false; - } - return true; -} - -bool deletePath(QString path) -{ - bool OK = true; - QFileInfo finfo(path); - if(finfo.isFile()) { - return QFile::remove(path); - } - - QDir dir(path); - - if (!dir.exists()) - { - return OK; - } - auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | - QDir::AllDirs | QDir::Files, - QDir::DirsFirst); - - 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()) - { - OK &= QFile::remove(info.absoluteFilePath()); - } - 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()) - { - OK &= QFile::remove(info.absoluteFilePath()); - } -#endif - else if (info.isDir()) - { - OK &= deletePath(info.absoluteFilePath()); - } - else if (info.isFile()) - { - OK &= QFile::remove(info.absoluteFilePath()); - } - else - { - OK = false; - qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); - } - } - OK &= dir.rmdir(dir.absolutePath()); - return OK; -} - - -QString PathCombine(const QString & path1, const QString & path2) -{ - if(!path1.size()) - return path2; - if(!path2.size()) - return path1; - return QDir::cleanPath(path1 + QDir::separator() + path2); -} - -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) -{ - return PathCombine(PathCombine(path1, path2, path3), path4); -} - -QString AbsolutePath(QString path) -{ - return QFileInfo(path).absolutePath(); -} - -QString ResolveExecutable(QString path) -{ - if (path.isEmpty()) - { - return QString(); - } - if(!path.contains('/')) - { - path = QStandardPaths::findExecutable(path); - } - QFileInfo pathInfo(path); - if(!pathInfo.exists() || !pathInfo.isExecutable()) - { - return QString(); - } - return pathInfo.absoluteFilePath(); -} - -/** - * Normalize path - * - * Any paths inside the current folder will be normalized to relative paths (to current) - * Other paths will be made absolute - */ -QString NormalizePath(QString path) -{ - QDir a = QDir::currentPath(); - QString currentAbsolute = a.absolutePath(); - - QDir b(path); - QString newAbsolute = b.absolutePath(); - - if (newAbsolute.startsWith(currentAbsolute)) - { - return a.relativeFilePath(newAbsolute); - } - else - { - return newAbsolute; - } -} - -QString badFilenameChars = "\"\\/?<>:;*|!+\r\n"; - -QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) -{ - for (int i = 0; i < string.length(); i++) - { - if (badFilenameChars.contains(string[i])) - { - string[i] = replaceWith; - } - } - return string; -} - -QString DirNameFromString(QString string, QString inDir) -{ - int num = 0; - QString baseName = RemoveInvalidFilenameChars(string, '-'); - QString dirName; - do - { - if(num == 0) - { - dirName = baseName; - } - else - { - dirName = baseName + QString::number(num);; - } - - // If it's over 9000 - if (num > 9000) - return ""; - num++; - } while (QFileInfo(PathCombine(inDir, dirName)).exists()); - return dirName; -} - -// Does the folder path contain any '!'? If yes, return true, otherwise false. -// (This is a problem for Java) -bool checkProblemticPathJava(QDir folder) -{ - QString pathfoldername = folder.absolutePath(); - return pathfoldername.contains("!", Qt::CaseInsensitive); -} - -// Win32 crap -#if defined Q_OS_WIN - -bool called_coinit = false; - -HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) -{ - HRESULT hres; - - if (!called_coinit) - { - hres = CoInitialize(NULL); - called_coinit = true; - - if (!SUCCEEDED(hres)) - { - qWarning("Failed to initialize COM. Error 0x%08lX", hres); - return hres; - } - } - - IShellLink *link; - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, - (LPVOID *)&link); - - if (SUCCEEDED(hres)) - { - IPersistFile *persistFile; - - link->SetPath(targetPath); - link->SetArguments(args); - - hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); - if (SUCCEEDED(hres)) - { - WCHAR wstr[MAX_PATH]; - - MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); - - hres = persistFile->Save(wstr, TRUE); - persistFile->Release(); - } - link->Release(); - } - return hres; -} - -#endif - -QString getDesktopDir() -{ - return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); -} - -// Cross-platform Shortcut creation -bool createShortCut(QString location, QString dest, QStringList args, QString name, - QString icon) -{ -#if defined Q_OS_LINUX - location = PathCombine(location, name + ".desktop"); - - QFile f(location); - f.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream stream(&f); - - QString argstring; - if (!args.empty()) - argstring = " '" + args.join("' '") + "'"; - - stream << "[Desktop Entry]" - << "\n"; - stream << "Type=Application" - << "\n"; - stream << "TryExec=" << dest.toLocal8Bit() << "\n"; - stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n"; - stream << "Name=" << name.toLocal8Bit() << "\n"; - stream << "Icon=" << icon.toLocal8Bit() << "\n"; - - stream.flush(); - f.close(); - - f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | - QFileDevice::ExeOther); - - return true; -#elif defined Q_OS_WIN - // TODO: Fix - // QFile file(PathCombine(location, name + ".lnk")); - // WCHAR *file_w; - // WCHAR *dest_w; - // WCHAR *args_w; - // file.fileName().toWCharArray(file_w); - // dest.toWCharArray(dest_w); - - // QString argStr; - // for (int i = 0; i < args.count(); i++) - // { - // argStr.append(args[i]); - // argStr.append(" "); - // } - // argStr.toWCharArray(args_w); - - // return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); - return false; -#else - qWarning("Desktop Shortcuts not supported on your platform!"); - return false; -#endif -} -} diff --git a/api/logic/FileSystem.h b/api/logic/FileSystem.h deleted file mode 100644 index 55ec6a58..00000000 --- a/api/logic/FileSystem.h +++ /dev/null @@ -1,128 +0,0 @@ -// Licensed under the Apache-2.0 license. See README.md for details. - -#pragma once - -#include "Exception.h" -#include "pathmatcher/IPathMatcher.h" - -#include "multimc_logic_export.h" -#include <QDir> -#include <QFlags> - -namespace FS -{ - -class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception -{ -public: - FileSystemException(const QString &message) : Exception(message) {} -}; - -/** - * write data to a file safely - */ -MULTIMC_LOGIC_EXPORT void write(const QString &filename, const QByteArray &data); - -/** - * read data from a file safely\ - */ -MULTIMC_LOGIC_EXPORT QByteArray read(const QString &filename); - -/** - * Update the last changed timestamp of an existing file - */ -MULTIMC_LOGIC_EXPORT bool updateTimestamp(const QString & filename); - -/** - * Creates all the folders in a path for the specified path - * last segment of the path is treated as a file name and is ignored! - */ -MULTIMC_LOGIC_EXPORT bool ensureFilePathExists(QString filenamepath); - -/** - * Creates all the folders in a path for the specified path - * last segment of the path is treated as a folder name and is created! - */ -MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath); - -class MULTIMC_LOGIC_EXPORT copy -{ -public: - copy(const QString & src, const QString & dst) - { - m_src = src; - m_dst = dst; - } - copy & followSymlinks(const bool follow) - { - m_followSymlinks = follow; - return *this; - } - copy & blacklist(const IPathMatcher * filter) - { - m_blacklist = filter; - return *this; - } - bool operator()() - { - return operator()(QString()); - } - -private: - bool operator()(const QString &offset); - -private: - bool m_followSymlinks = true; - const IPathMatcher * m_blacklist = nullptr; - QDir m_src; - QDir m_dst; -}; - -/** - * Delete a folder recursively - */ -MULTIMC_LOGIC_EXPORT bool deletePath(QString path); - -MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2); -MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3); -MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4); - -MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path); - -/** - * Resolve an executable - * - * Will resolve: - * single executable (by name) - * relative path - * absolute path - * - * @return absolute path to executable or null string - */ -MULTIMC_LOGIC_EXPORT QString ResolveExecutable(QString path); - -/** - * Normalize path - * - * Any paths inside the current directory will be normalized to relative paths (to current) - * Other paths will be made absolute - * - * Returns false if the path logic somehow filed (and normalizedPath in invalid) - */ -MULTIMC_LOGIC_EXPORT QString NormalizePath(QString path); - -MULTIMC_LOGIC_EXPORT QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-'); - -MULTIMC_LOGIC_EXPORT QString DirNameFromString(QString string, QString inDir = "."); - -/// Checks if the a given Path contains "!" -MULTIMC_LOGIC_EXPORT bool checkProblemticPathJava(QDir folder); - -// Get the Directory representing the User's Desktop -MULTIMC_LOGIC_EXPORT QString getDesktopDir(); - -// Create a shortcut at *location*, pointing to *dest* called with the arguments *args* -// call it *name* and assign it the icon *icon* -// return true if operation succeeded -MULTIMC_LOGIC_EXPORT bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation); -} diff --git a/api/logic/FileSystem_test.cpp b/api/logic/FileSystem_test.cpp deleted file mode 100644 index df653ea1..00000000 --- a/api/logic/FileSystem_test.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include <QTest> -#include <QTemporaryDir> -#include <QStandardPaths> -#include "TestUtil.h" - -#include "FileSystem.h" - -class FileSystemTest : public QObject -{ - Q_OBJECT - - const QString bothSlash = "/foo/"; - const QString trailingSlash = "foo/"; - const QString leadingSlash = "/foo"; - -private -slots: - void test_pathCombine() - { - QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash)); - QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash)); - QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash)); - - QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash)); - QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash)); - QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash)); - } - - void test_PathCombine1_data() - { - QTest::addColumn<QString>("result"); - QTest::addColumn<QString>("path1"); - QTest::addColumn<QString>("path2"); - - QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl"; - QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl"; -#if defined(Q_OS_WIN) - QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc"; - QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl"; - QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl"; -#endif - } - - void test_PathCombine1() - { - QFETCH(QString, result); - QFETCH(QString, path1); - QFETCH(QString, path2); - - QCOMPARE(FS::PathCombine(path1, path2), result); - } - - void test_PathCombine2_data() - { - QTest::addColumn<QString>("result"); - QTest::addColumn<QString>("path1"); - QTest::addColumn<QString>("path2"); - QTest::addColumn<QString>("path3"); - - QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl"; - QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl"; - QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl"; - QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl"; -#if defined(Q_OS_WIN) - QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl"; - QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; - QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl"; - QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; -#endif - } - - void test_PathCombine2() - { - QFETCH(QString, result); - QFETCH(QString, path1); - QFETCH(QString, path2); - QFETCH(QString, path3); - - QCOMPARE(FS::PathCombine(path1, path2, path3), result); - } - - void test_copy() - { - QString folder = QFINDTESTDATA("data/test_folder"); - auto f = [&folder]() - { - QTemporaryDir tempDir; - tempDir.setAutoRemove(true); - qDebug() << "From:" << folder << "To:" << tempDir.path(); - - QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); - qDebug() << tempDir.path(); - qDebug() << target_dir.path(); - FS::copy c(folder, target_dir.path()); - c(); - - for(auto entry: target_dir.entryList()) - { - qDebug() << entry; - } - QVERIFY(target_dir.entryList().contains("pack.mcmeta")); - QVERIFY(target_dir.entryList().contains("assets")); - }; - - // first try variant without trailing / - QVERIFY(!folder.endsWith('/')); - f(); - - // then variant with trailing / - folder.append('/'); - QVERIFY(folder.endsWith('/')); - f(); - } - - void test_getDesktop() - { - QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); - } - -// this is only valid on linux -// FIXME: implement on windows, OSX, then test. -#if defined(Q_OS_LINUX) - void test_createShortcut_data() - { - QTest::addColumn<QString>("location"); - QTest::addColumn<QString>("dest"); - QTest::addColumn<QStringList>("args"); - QTest::addColumn<QString>("name"); - QTest::addColumn<QString>("iconLocation"); - QTest::addColumn<QByteArray>("result"); - - QTest::newRow("unix") << QDir::currentPath() - << "asdfDest" - << (QStringList() << "arg1" << "arg2") - << "asdf" - << QString() - #if defined(Q_OS_LINUX) - << MULTIMC_GET_TEST_FILE("data/FileSystem-test_createShortcut-unix") - #elif defined(Q_OS_WIN) - << QByteArray() - #endif - ; - } - - void test_createShortcut() - { - QFETCH(QString, location); - QFETCH(QString, dest); - QFETCH(QStringList, args); - QFETCH(QString, name); - QFETCH(QString, iconLocation); - QFETCH(QByteArray, result); - - QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation)); - QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result)); - - //QDir().remove(location); - } -#endif -}; - -QTEST_GUILESS_MAIN(FileSystemTest) - -#include "FileSystem_test.moc" diff --git a/api/logic/Filter.cpp b/api/logic/Filter.cpp deleted file mode 100644 index c65ca0ce..00000000 --- a/api/logic/Filter.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "Filter.h" - -Filter::~Filter(){} - -ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern){} -ContainsFilter::~ContainsFilter(){} -bool ContainsFilter::accepts(const QString& value) -{ - return value.contains(pattern); -} - -ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern){} -ExactFilter::~ExactFilter(){} -bool ExactFilter::accepts(const QString& value) -{ - return value == pattern; -} - -RegexpFilter::RegexpFilter(const QString& regexp, bool invert) - :invert(invert) -{ - pattern.setPattern(regexp); - pattern.optimize(); -} -RegexpFilter::~RegexpFilter(){} -bool RegexpFilter::accepts(const QString& value) -{ - auto match = pattern.match(value); - bool matched = match.hasMatch(); - return invert ? (!matched) : (matched); -} diff --git a/api/logic/Filter.h b/api/logic/Filter.h deleted file mode 100644 index 1ba48e48..00000000 --- a/api/logic/Filter.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include <QString> -#include <QRegularExpression> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT Filter -{ -public: - virtual ~Filter(); - virtual bool accepts(const QString & value) = 0; -}; - -class MULTIMC_LOGIC_EXPORT ContainsFilter: public Filter -{ -public: - ContainsFilter(const QString &pattern); - virtual ~ContainsFilter(); - bool accepts(const QString & value) override; -private: - QString pattern; -}; - -class MULTIMC_LOGIC_EXPORT ExactFilter: public Filter -{ -public: - ExactFilter(const QString &pattern); - virtual ~ExactFilter(); - bool accepts(const QString & value) override; -private: - QString pattern; -}; - -class MULTIMC_LOGIC_EXPORT RegexpFilter: public Filter -{ -public: - RegexpFilter(const QString ®exp, bool invert); - virtual ~RegexpFilter(); - bool accepts(const QString & value) override; -private: - QRegularExpression pattern; - bool invert = false; -}; diff --git a/api/logic/GZip.cpp b/api/logic/GZip.cpp deleted file mode 100644 index 0368c32d..00000000 --- a/api/logic/GZip.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "GZip.h" -#include <zlib.h> -#include <QByteArray> - -bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes) -{ - if (compressedBytes.size() == 0) - { - uncompressedBytes = compressedBytes; - return true; - } - - unsigned uncompLength = compressedBytes.size(); - uncompressedBytes.clear(); - uncompressedBytes.resize(uncompLength); - - z_stream strm; - memset(&strm, 0, sizeof(strm)); - strm.next_in = (Bytef *)compressedBytes.data(); - strm.avail_in = compressedBytes.size(); - - bool done = false; - - if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) - { - return false; - } - - int err = Z_OK; - - while (!done) - { - // If our output buffer is too small - if (strm.total_out >= uncompLength) - { - uncompressedBytes.resize(uncompLength * 2); - uncompLength *= 2; - } - - strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out); - strm.avail_out = uncompLength - strm.total_out; - - // Inflate another chunk. - err = inflate(&strm, Z_SYNC_FLUSH); - if (err == Z_STREAM_END) - done = true; - else if (err != Z_OK) - { - break; - } - } - - if (inflateEnd(&strm) != Z_OK || !done) - { - return false; - } - - uncompressedBytes.resize(strm.total_out); - return true; -} - -bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes) -{ - if (uncompressedBytes.size() == 0) - { - compressedBytes = uncompressedBytes; - return true; - } - - unsigned compLength = std::min(uncompressedBytes.size(), 16); - compressedBytes.clear(); - compressedBytes.resize(compLength); - - z_stream zs; - memset(&zs, 0, sizeof(zs)); - - if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK) - { - return false; - } - - zs.next_in = (Bytef*)uncompressedBytes.data(); - zs.avail_in = uncompressedBytes.size(); - - int ret; - compressedBytes.resize(uncompressedBytes.size()); - - unsigned offset = 0; - unsigned temp = 0; - do - { - auto remaining = compressedBytes.size() - offset; - if(remaining < 1) - { - compressedBytes.resize(compressedBytes.size() * 2); - } - zs.next_out = (Bytef *) (compressedBytes.data() + offset); - temp = zs.avail_out = compressedBytes.size() - offset; - ret = deflate(&zs, Z_FINISH); - offset += temp - zs.avail_out; - } while (ret == Z_OK); - - compressedBytes.resize(offset); - - if (deflateEnd(&zs) != Z_OK) - { - return false; - } - - if (ret != Z_STREAM_END) - { - return false; - } - return true; -}
\ No newline at end of file diff --git a/api/logic/GZip.h b/api/logic/GZip.h deleted file mode 100644 index c7eddbb3..00000000 --- a/api/logic/GZip.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include <QByteArray> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT GZip -{ -public: - static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes); - static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes); -}; - diff --git a/api/logic/GZip_test.cpp b/api/logic/GZip_test.cpp deleted file mode 100644 index 3f4d181c..00000000 --- a/api/logic/GZip_test.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "GZip.h" -#include <random> - -void fib(int &prev, int &cur) -{ - auto ret = prev + cur; - prev = cur; - cur = ret; -} - -class GZipTest : public QObject -{ - Q_OBJECT -private -slots: - - void test_Through() - { - // test up to 10 MB - static const int size = 10 * 1024 * 1024; - QByteArray random; - QByteArray compressed; - QByteArray decompressed; - std::default_random_engine eng((std::random_device())()); - std::uniform_int_distribution<uint8_t> idis(0, std::numeric_limits<uint8_t>::max()); - - // initialize random buffer - for(int i = 0; i < size; i++) - { - random.append((char)idis(eng)); - } - - // initialize fibonacci - int prev = 1; - int cur = 1; - - // test if fibonacci long random buffers pass through GZip - do - { - QByteArray copy = random; - copy.resize(cur); - compressed.clear(); - decompressed.clear(); - QVERIFY(GZip::zip(copy, compressed)); - QVERIFY(GZip::unzip(compressed, decompressed)); - QCOMPARE(decompressed, copy); - fib(prev, cur); - } while (cur < size); - } -}; - -QTEST_GUILESS_MAIN(GZipTest) - -#include "GZip_test.moc" diff --git a/api/logic/InstanceCopyTask.cpp b/api/logic/InstanceCopyTask.cpp deleted file mode 100644 index 35adeaf9..00000000 --- a/api/logic/InstanceCopyTask.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "InstanceCopyTask.h" -#include "settings/INISettingsObject.h" -#include "FileSystem.h" -#include "NullInstance.h" -#include "pathmatcher/RegexpMatcher.h" -#include <QtConcurrentRun> - -InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime) -{ - m_origInstance = origInstance; - m_keepPlaytime = keepPlaytime; - - if(!copySaves) - { - // FIXME: get this from the original instance type... - auto matcherReal = new RegexpMatcher("[.]?minecraft/saves"); - matcherReal->caseSensitive(false); - m_matcher.reset(matcherReal); - } -} - -void InstanceCopyTask::executeTask() -{ - setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(false).blacklist(m_matcher.get()); - - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); - connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished); - connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted); - m_copyFutureWatcher.setFuture(m_copyFuture); -} - -void InstanceCopyTask::copyFinished() -{ - auto successful = m_copyFuture.result(); - if(!successful) - { - emitFailed(tr("Instance folder copy failed.")); - return; - } - // FIXME: shouldn't this be able to report errors? - auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->registerSetting("InstanceType", "Legacy"); - - InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); - inst->setName(m_instName); - inst->setIconKey(m_instIcon); - if(!m_keepPlaytime) { - inst->resetTimePlayed(); - } - emitSucceeded(); -} - -void InstanceCopyTask::copyAborted() -{ - emitFailed(tr("Instance folder copy has been aborted.")); - return; -} diff --git a/api/logic/InstanceCopyTask.h b/api/logic/InstanceCopyTask.h deleted file mode 100644 index 6465e92d..00000000 --- a/api/logic/InstanceCopyTask.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "tasks/Task.h" -#include "multimc_logic_export.h" -#include "net/NetJob.h" -#include <QUrl> -#include <QFuture> -#include <QFutureWatcher> -#include "settings/SettingsObject.h" -#include "BaseVersion.h" -#include "BaseInstance.h" -#include "InstanceTask.h" - -class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask -{ - Q_OBJECT -public: - explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime); - -protected: - //! Entry point for tasks. - virtual void executeTask() override; - void copyFinished(); - void copyAborted(); - -private: /* data */ - InstancePtr m_origInstance; - QFuture<bool> m_copyFuture; - QFutureWatcher<bool> m_copyFutureWatcher; - std::unique_ptr<IPathMatcher> m_matcher; - bool m_keepPlaytime; -}; diff --git a/api/logic/InstanceCreationTask.cpp b/api/logic/InstanceCreationTask.cpp deleted file mode 100644 index eafc5126..00000000 --- a/api/logic/InstanceCreationTask.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "InstanceCreationTask.h" -#include "settings/INISettingsObject.h" -#include "FileSystem.h" - -//FIXME: remove this -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" - -InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version) -{ - m_version = version; -} - -void InstanceCreationTask::executeTask() -{ - setStatus(tr("Creating instance from version %1").arg(m_version->name())); - { - auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->suspendSave(); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); - auto components = inst.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_version->descriptor(), true); - inst.setName(m_instName); - inst.setIconKey(m_instIcon); - instanceSettings->resumeSave(); - } - emitSucceeded(); -} diff --git a/api/logic/InstanceCreationTask.h b/api/logic/InstanceCreationTask.h deleted file mode 100644 index 154a854f..00000000 --- a/api/logic/InstanceCreationTask.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "tasks/Task.h" -#include "multimc_logic_export.h" -#include "net/NetJob.h" -#include <QUrl> -#include "settings/SettingsObject.h" -#include "BaseVersion.h" -#include "InstanceTask.h" - -class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public InstanceTask -{ - Q_OBJECT -public: - explicit InstanceCreationTask(BaseVersionPtr version); - -protected: - //! Entry point for tasks. - virtual void executeTask() override; - -private: /* data */ - BaseVersionPtr m_version; -}; diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp deleted file mode 100644 index 3eac4d57..00000000 --- a/api/logic/InstanceImportTask.cpp +++ /dev/null @@ -1,456 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "InstanceImportTask.h" -#include "BaseInstance.h" -#include "FileSystem.h" -#include "Env.h" -#include "MMCZip.h" -#include "NullInstance.h" -#include "settings/INISettingsObject.h" -#include "icons/IIconList.h" -#include "icons/IconUtils.h" -#include <QtConcurrentRun> - -// FIXME: this does not belong here, it's Minecraft/Flame specific -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "modplatform/flame/FileResolvingTask.h" -#include "modplatform/flame/PackManifest.h" -#include "Json.h" -#include <quazipdir.h> -#include "modplatform/technic/TechnicPackProcessor.h" - -InstanceImportTask::InstanceImportTask(const QUrl sourceUrl) -{ - m_sourceUrl = sourceUrl; -} - -void InstanceImportTask::executeTask() -{ - if (m_sourceUrl.isLocalFile()) - { - m_archivePath = m_sourceUrl.toLocalFile(); - processZipPack(); - } - else - { - setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); - m_downloadRequired = true; - - const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path(); - auto entry = ENV.metacache()->resolveEntry("general", path); - entry->setStale(true); - m_filesNetJob.reset(new NetJob(tr("Modpack download"))); - m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); - m_archivePath = entry->getFullPath(); - auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); - connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); - connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed); - m_filesNetJob->start(); - } -} - -void InstanceImportTask::downloadSucceeded() -{ - processZipPack(); - m_filesNetJob.reset(); -} - -void InstanceImportTask::downloadFailed(QString reason) -{ - emitFailed(reason); - m_filesNetJob.reset(); -} - -void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) -{ - setProgress(current / 2, total); -} - -void InstanceImportTask::processZipPack() -{ - setStatus(tr("Extracting modpack")); - QDir extractDir(m_stagingPath); - qDebug() << "Attempting to create instance from" << m_archivePath; - - // open the zip and find relevant files in it - m_packZip.reset(new QuaZip(m_archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) - { - emitFailed(tr("Unable to open supplied modpack zip file.")); - return; - } - - QStringList blacklist = {"instance.cfg", "manifest.json"}; - QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); - bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json"); - QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); - QString root; - if(!mmcFound.isNull()) - { - // process as MultiMC instance/pack - qDebug() << "MultiMC:" << mmcFound; - root = mmcFound; - m_modpackType = ModpackType::MultiMC; - } - else if (technicFound) - { - // process as Technic pack - qDebug() << "Technic:" << technicFound; - extractDir.mkpath(".minecraft"); - extractDir.cd(".minecraft"); - m_modpackType = ModpackType::Technic; - } - else if(!flameFound.isNull()) - { - // process as Flame pack - qDebug() << "Flame:" << flameFound; - root = flameFound; - m_modpackType = ModpackType::Flame; - } - if(m_modpackType == ModpackType::Unknown) - { - emitFailed(tr("Archive does not contain a recognized modpack type.")); - return; - } - - // make sure we extract just the pack - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath()); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted); - m_extractFutureWatcher.setFuture(m_extractFuture); -} - -void InstanceImportTask::extractFinished() -{ - m_packZip.reset(); - if (!m_extractFuture.result()) - { - emitFailed(tr("Failed to extract modpack")); - return; - } - QDir extractDir(m_stagingPath); - - qDebug() << "Fixing permissions for extracted pack files..."; - QDirIterator it(extractDir, QDirIterator::Subdirectories); - while (it.hasNext()) - { - auto filepath = it.next(); - QFileInfo file(filepath); - auto permissions = QFile::permissions(filepath); - auto origPermissions = permissions; - if(file.isDir()) - { - // Folder +rwx for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; - } - else - { - // File +rw for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; - } - if(origPermissions != permissions) - { - if(!QFile::setPermissions(filepath, permissions)) - { - logWarning(tr("Could not fix permissions for %1").arg(filepath)); - } - else - { - qDebug() << "Fixed" << filepath; - } - } - } - - switch(m_modpackType) - { - case ModpackType::Flame: - processFlame(); - return; - case ModpackType::MultiMC: - processMultiMC(); - return; - case ModpackType::Technic: - processTechnic(); - return; - case ModpackType::Unknown: - emitFailed(tr("Archive does not contain a recognized modpack type.")); - return; - } -} - -void InstanceImportTask::extractAborted() -{ - emitFailed(tr("Instance import has been aborted.")); - return; -} - -void InstanceImportTask::processFlame() -{ - const static QMap<QString,QString> forgemap = { - {"1.2.5", "3.4.9.171"}, - {"1.4.2", "6.0.1.355"}, - {"1.4.7", "6.6.2.534"}, - {"1.5.2", "7.8.1.737"} - }; - Flame::Manifest pack; - try - { - QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); - Flame::loadManifest(pack, configPath); - QFile::remove(configPath); - } - catch (const JSONValidationError &e) - { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - if(!pack.overrides.isEmpty()) - { - QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); - if (QFile::exists(overridePath)) - { - QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); - if (!QFile::rename(overridePath, mcPath)) - { - emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides); - return; - } - } - else - { - logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); - } - } - - QString forgeVersion; - QString fabricVersion; - for(auto &loader: pack.minecraft.modLoaders) - { - auto id = loader.id; - if(id.startsWith("forge-")) - { - id.remove("forge-"); - forgeVersion = id; - continue; - } - if(id.startsWith("fabric-")) - { - id.remove("fabric-"); - fabricVersion = id; - continue; - } - logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); - } - - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared<INISettingsObject>(configPath); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto mcVersion = pack.minecraft.version; - // Hack to correct some 'special sauce'... - if(mcVersion.endsWith('.')) - { - mcVersion.remove(QRegExp("[.]+$")); - logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); - } - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", mcVersion, true); - if(!forgeVersion.isEmpty()) - { - // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. - if(forgeVersion == "recommended") - { - if(forgemap.contains(mcVersion)) - { - forgeVersion = forgemap[mcVersion]; - } - else - { - logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion)); - } - } - components->setComponentVersion("net.minecraftforge", forgeVersion); - } - if(!fabricVersion.isEmpty()) - { - components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); - } - if (m_instIcon != "default") - { - instance.setIconKey(m_instIcon); - } - else - { - if(pack.name.contains("Direwolf20")) - { - instance.setIconKey("steve"); - } - else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) - { - instance.setIconKey("ftb_logo"); - } - else - { - // default to something other than the MultiMC default to distinguish these - instance.setIconKey("flame"); - } - } - QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); - QFileInfo jarmodsInfo(jarmodsPath); - if(jarmodsInfo.isDir()) - { - // install all the jar mods - qDebug() << "Found jarmods:"; - QDir jarmodsDir(jarmodsPath); - QStringList jarMods; - for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) - { - qDebug() << info.fileName(); - jarMods.push_back(info.absoluteFilePath()); - } - auto profile = instance.getPackProfile(); - profile->installJarMods(jarMods); - // nuke the original files - FS::deletePath(jarmodsPath); - } - instance.setName(m_instName); - m_modIdResolver.reset(new Flame::FileResolvingTask(pack)); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() - { - auto results = m_modIdResolver->getResults(); - m_filesNetJob.reset(new NetJob(tr("Mod download"))); - for(auto result: results.files) - { - QString filename = result.fileName; - if(!result.required) - { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath , relpath); - - switch(result.type) - { - case Flame::File::Type::Folder: - { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: - { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_filesNetJob->addNetAction(dl); - break; - } - case Flame::File::Type::Modpack: - logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - m_modIdResolver.reset(); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() - { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) - { - m_filesNetJob.reset(); - emitFailed(reason); - }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); - } - ); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) - { - m_modIdResolver.reset(); - emitFailed(tr("Unable to resolve mod IDs:\n") + reason); - }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status) - { - setStatus(status); - }); - m_modIdResolver->start(); -} - -void InstanceImportTask::processTechnic() -{ - shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor(); - connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded); - connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed); - packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath); -} - -void InstanceImportTask::processMultiMC() -{ - // FIXME: copy from FolderInstanceProvider!!! FIX IT!!! - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared<INISettingsObject>(configPath); - instanceSettings->registerSetting("InstanceType", "Legacy"); - - NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - - // reset time played on import... because packs. - instance.resetTimePlayed(); - - // set a new nice name - instance.setName(m_instName); - - // if the icon was specified by user, use that. otherwise pull icon from the pack - if (m_instIcon != "default") - { - instance.setIconKey(m_instIcon); - } - else - { - m_instIcon = instance.iconKey(); - - auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); - if (!importIconPath.isNull() && QFile::exists(importIconPath)) - { - // import icon - auto iconList = ENV.icons(); - if (iconList->iconFileExists(m_instIcon)) - { - iconList->deleteIcon(m_instIcon); - } - iconList->installIcons({importIconPath}); - } - } - emitSucceeded(); -} diff --git a/api/logic/InstanceImportTask.h b/api/logic/InstanceImportTask.h deleted file mode 100644 index 7291324d..00000000 --- a/api/logic/InstanceImportTask.h +++ /dev/null @@ -1,73 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "InstanceTask.h" -#include "multimc_logic_export.h" -#include "net/NetJob.h" -#include <QUrl> -#include <QFuture> -#include <QFutureWatcher> -#include "settings/SettingsObject.h" -#include "QObjectPtr.h" - -#include <nonstd/optional> - -class QuaZip; -namespace Flame -{ - class FileResolvingTask; -} - -class MULTIMC_LOGIC_EXPORT InstanceImportTask : public InstanceTask -{ - Q_OBJECT -public: - explicit InstanceImportTask(const QUrl sourceUrl); - -protected: - //! Entry point for tasks. - virtual void executeTask() override; - -private: - void processZipPack(); - void processMultiMC(); - void processFlame(); - void processTechnic(); - -private slots: - void downloadSucceeded(); - void downloadFailed(QString reason); - void downloadProgressChanged(qint64 current, qint64 total); - void extractFinished(); - void extractAborted(); - -private: /* data */ - NetJobPtr m_filesNetJob; - shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver; - QUrl m_sourceUrl; - 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; - enum class ModpackType{ - Unknown, - MultiMC, - Flame, - Technic - } m_modpackType = ModpackType::Unknown; -}; diff --git a/api/logic/InstanceList.cpp b/api/logic/InstanceList.cpp deleted file mode 100644 index cb38853b..00000000 --- a/api/logic/InstanceList.cpp +++ /dev/null @@ -1,867 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <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 "InstanceList.h" -#include "BaseInstance.h" -#include "InstanceTask.h" -#include "settings/INISettingsObject.h" -#include "minecraft/legacy/LegacyInstance.h" -#include "NullInstance.h" -#include "minecraft/MinecraftInstance.h" -#include "FileSystem.h" -#include "ExponentialSeries.h" -#include "WatchLock.h" - -const static int GROUP_FILE_FORMAT_VERSION = 1; - -InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent) - : QAbstractListModel(parent), m_globalSettings(settings) -{ - resumeWatch(); - // Create aand normalize path - if (!QDir::current().exists(instDir)) - { - QDir::current().mkpath(instDir); - } - - connect(this, &InstanceList::instancesChanged, this, &InstanceList::providerUpdated); - - // NOTE: canonicalPath requires the path to exist. Do not move this above the creation block! - m_instDir = QDir(instDir).canonicalPath(); - m_watcher = new QFileSystemWatcher(this); - connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &InstanceList::instanceDirContentsChanged); - m_watcher->addPath(m_instDir); -} - -InstanceList::~InstanceList() -{ -} - -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 -{ - Q_UNUSED(parent); - if (row < 0 || row >= m_instances.size()) - return QModelIndex(); - return createIndex(row, column, (void *)m_instances.at(row).get()); -} - -QVariant InstanceList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - { - return QVariant(); - } - BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer()); - switch (role) - { - case InstancePointerRole: - { - QVariant v = qVariantFromValue((void *)pdata); - return v; - } - case InstanceIDRole: - { - return pdata->id(); - } - case Qt::EditRole: - case Qt::DisplayRole: - { - return pdata->name(); - } - case Qt::AccessibleTextRole: - { - return tr("%1 Instance").arg(pdata->name()); - } - case Qt::ToolTipRole: - { - return pdata->instanceRoot(); - } - case Qt::DecorationRole: - { - return pdata->iconKey(); - } - // HACK: see GroupView.h in gui! - case GroupRole: - { - return getInstanceGroup(pdata->id()); - } - default: - break; - } - return QVariant(); -} - -bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role) -{ - if (!index.isValid()) - { - return false; - } - if(role != Qt::EditRole) - { - return false; - } - BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer()); - auto newName = value.toString(); - if(pdata->name() == newName) - { - return true; - } - pdata->setName(newName); - return true; -} - -Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const -{ - Qt::ItemFlags f; - if (index.isValid()) - { - f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); - } - return f; -} - -GroupId InstanceList::getInstanceGroup(const InstanceId& id) const -{ - auto inst = getInstanceById(id); - if(!inst) - { - return GroupId(); - } - auto iter = m_instanceGroupIndex.find(inst->id()); - if(iter != m_instanceGroupIndex.end()) - { - return *iter; - } - return GroupId(); -} - -void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name) -{ - auto inst = getInstanceById(id); - 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) - { - *iter = name; - changed = true; - } - } - else - { - changed = true; - m_instanceGroupIndex[id] = name; - } - - if(changed) - { - m_groupNameCache.insert(name); - auto idx = getInstIndex(inst.get()); - emit dataChanged(index(idx), index(idx), {GroupRole}); - saveGroupList(); - } -} - -QStringList InstanceList::getGroups() -{ - return m_groupNameCache.toList(); -} - -void InstanceList::deleteGroup(const QString& name) -{ - bool removed = false; - qDebug() << "Delete group" << name; - for(auto & instance: m_instances) - { - const auto & instID = instance->id(); - auto instGroupName = getInstanceGroup(instID); - 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(removed) - { - saveGroupList(); - } -} - -bool InstanceList::isGroupCollapsed(const QString& group) -{ - return m_collapsedGroups.contains(group); -} - -void InstanceList::deleteInstance(const InstanceId& id) -{ - auto inst = getInstanceById(id); - if(!inst) - { - qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?)."; - return; - } - - if(m_instanceGroupIndex.remove(id)) - { - saveGroupList(); - } - - qDebug() << "Will delete instance" << id; - if(!FS::deletePath(inst->instanceRoot())) - { - qWarning() << "Deletion of instance" << id << "has not been completely successful ..."; - return; - } - - qDebug() << "Instance" << id << "has been deleted by MultiMC."; -} - -static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list) -{ - QMap<InstanceId, InstanceLocator> out; - int i = 0; - for(auto & item: list) - { - auto id = item->id(); - if(out.contains(id)) - { - qWarning() << "Duplicate ID" << id << "in instance list"; - } - out[id] = std::make_pair(item, i); - i++; - } - return out; -} - -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()) - { - 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()) - { - QFileInfo targetInfo(dirInfo.symLinkTarget()); - QFileInfo instDirInfo(m_instDir); - if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) - { - qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder"; - continue; - } - } - auto id = dirInfo.fileName(); - out.append(id); - qDebug() << "Found instance ID" << id; - } - instanceSet = out.toSet(); - m_instancesProbed = true; - return out; -} - -InstanceList::InstListError InstanceList::loadList() -{ - auto existingIds = getIdMapping(m_instances); - - QList<InstancePtr> newList; - - for(auto & id: discoverInstances()) - { - if(existingIds.contains(id)) - { - auto instPair = existingIds[id]; - existingIds.remove(id); - qDebug() << "Should keep and soft-reload" << id; - } - else - { - InstancePtr instPtr = loadInstance(id); - if(instPtr) - { - newList.append(instPtr); - } - } - } - - // TODO: looks like a general algorithm with a few specifics inserted. Do something about it. - 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; - }; - 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 = [&]() - { - 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) - { - auto instPtr = removedItem.first; - instPtr->invalidate(); - currentItem = removedItem.second; - if(back_bookmark == -1) - { - // no bookmark yet - back_bookmark = currentItem; - } - else if(currentItem == front_bookmark - 1) - { - // part of contiguous sequence, continue - } - else - { - // seam between previous and current item - removeNow(); - } - front_bookmark = currentItem; - } - if(back_bookmark != -1) - { - removeNow(); - } - } - if(newList.size()) - { - add(newList); - } - m_dirty = false; - updateTotalPlayTime(); - return NoError; -} - -void InstanceList::updateTotalPlayTime() -{ - totalPlayTime = 0; - for(auto const& itr : m_instances) - { - totalPlayTime += itr.get()->totalTimePlayed(); - } -} - -void InstanceList::saveNow() -{ - for(auto & item: m_instances) - { - item->saveNow(); - } -} - -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) - { - connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged); - } - endInsertRows(); -} - -void InstanceList::resumeWatch() -{ - if(m_watchLevel > 0) - { - qWarning() << "Bad suspend level resume in instance list"; - return; - } - m_watchLevel++; - if(m_watchLevel > 0 && m_dirty) - { - loadList(); - } -} - -void InstanceList::suspendWatch() -{ - m_watchLevel --; -} - -void InstanceList::providerUpdated() -{ - m_dirty = true; - if(m_watchLevel == 1) - { - loadList(); - } -} - -InstancePtr InstanceList::getInstanceById(QString instId) const -{ - if(instId.isEmpty()) - return InstancePtr(); - for(auto & inst: m_instances) - { - if (inst->id() == instId) - { - return inst; - } - } - return InstancePtr(); -} - -QModelIndex InstanceList::getInstanceIndexById(const QString &id) const -{ - return index(getInstIndex(getInstanceById(id).get())); -} - -int InstanceList::getInstIndex(BaseInstance *inst) const -{ - int count = m_instances.count(); - for (int i = 0; i < count; i++) - { - if (inst == m_instances[i].get()) - { - return i; - } - } - return -1; -} - -void InstanceList::propertiesChanged(BaseInstance *inst) -{ - int i = getInstIndex(inst); - if (i != -1) - { - emit dataChanged(index(i), index(i)); - updateTotalPlayTime(); - } -} - -InstancePtr InstanceList::loadInstance(const InstanceId& id) -{ - if(!m_groupsLoaded) - { - loadGroupList(); - } - - auto instanceRoot = FS::PathCombine(m_instDir, id); - auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg")); - InstancePtr inst; - - instanceSettings->registerSetting("InstanceType", "Legacy"); - - QString inst_type = instanceSettings->get("InstanceType").toString(); - - if (inst_type == "OneSix" || inst_type == "Nostalgia") - { - inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); - } - else if (inst_type == "Legacy") - { - inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot)); - } - else - { - inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); - } - qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); - return inst; -} - -void InstanceList::saveGroupList() -{ - qDebug() << "Will save group list now."; - 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++) - { - QString id = iter.key(); - QString group = iter.value(); - if (group.isEmpty()) - continue; - if(!instanceSet.contains(id)) - { - qDebug() << "Skipping saving missing instance" << id << "to groups list."; - continue; - } - - if (!reverseGroupMap.count(group)) - { - QSet<QString> set; - set.insert(id); - reverseGroupMap[group] = set; - } - 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++) - { - auto list = iter.value(); - auto name = iter.key(); - QJsonObject groupObj; - QJsonArray instanceArr; - groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name))); - for (auto item : list) - { - instanceArr.append(QJsonValue(item)); - } - groupObj.insert("instances", instanceArr); - groupsArr.insert(name, groupObj); - } - toplevel.insert("groups", groupsArr); - QJsonDocument doc(toplevel); - try - { - FS::write(groupFileName, doc.toJson()); - qDebug() << "Group list saved."; - } - catch (const FS::FileSystemException &e) - { - qCritical() << "Failed to write instance group file :" << e.cause(); - } -} - -void InstanceList::loadGroupList() -{ - qDebug() << "Will load group list now."; - - QString groupFileName = m_instDir + "/instgroups.json"; - - // if there's no group file, fail - if (!QFileInfo(groupFileName).exists()) - return; - - QByteArray jsonData; - try - { - jsonData = FS::read(groupFileName); - } - catch (const FS::FileSystemException &e) - { - qCritical() << "Failed to read instance group file :" << e.cause(); - return; - } - - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); - - // if the json was bad, fail - 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(); - return; - } - - // if the root of the json wasn't an object, fail - if (!jsonDoc.isObject()) - { - qWarning() << "Invalid group file. Root entry should be an object."; - return; - } - - QJsonObject rootObj = jsonDoc.object(); - - // Make sure the format version matches, otherwise fail. - if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION) - return; - - // Get the groups. if it's not an object, fail - if (!rootObj.value("groups").isObject()) - { - qWarning() << "Invalid group list JSON: 'groups' should be an object."; - return; - } - - QSet<QString> groupSet; - m_instanceGroupIndex.clear(); - - // Iterate through all the groups. - QJsonObject groupMapping = rootObj.value("groups").toObject(); - 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()) - { - 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(); - continue; - } - - // keep a list/set of groups for choosing - groupSet.insert(groupName); - - auto hidden = groupObj.value("hidden").toBool(false); - 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++) - { - m_instanceGroupIndex[(*iter2).toString()] = groupName; - } - } - m_groupsLoaded = true; - m_groupNameCache.unite(groupSet); - qDebug() << "Group list loaded."; -} - -void InstanceList::instanceDirContentsChanged(const QString& path) -{ - Q_UNUSED(path); - emit instancesChanged(); -} - -void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value) -{ - QString newInstDir = QDir(value.toString()).canonicalPath(); - if(newInstDir != m_instDir) - { - if(m_groupsLoaded) - { - saveGroupList(); - } - m_instDir = newInstDir; - m_groupsLoaded = false; - emit instancesChanged(); - } -} - -void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed) -{ - qDebug() << "Group" << group << (collapsed ? "collapsed" : "expanded"); - if(collapsed) { - m_collapsedGroups.insert(group); - } else { - m_collapsedGroups.remove(group); - } - saveGroupList(); -} - -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) - { - m_parent = parent; - m_child.reset(child); - connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); - connect(child, &Task::failed, this, &InstanceStaging::childFailed); - connect(child, &Task::status, this, &InstanceStaging::setStatus); - connect(child, &Task::progress, this, &InstanceStaging::setProgress); - m_instanceName = instanceName; - m_groupName = groupName; - m_stagingPath = stagingPath; - m_backoffTimer.setSingleShot(true); - connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); - } - - virtual ~InstanceStaging() {}; - - - // FIXME/TODO: add ability to abort during instance commit retries - bool abort() override - { - if(m_child && m_child->canAbort()) - { - return m_child->abort(); - } - return false; - } - bool canAbort() const override - { - 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(); - } - -private slots: - void childSucceded() - { - unsigned sleepTime = backoff(); - if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) - { - emitSucceeded(); - return; - } - // we actually failed, retry? - 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) - { - m_parent->destroyStagingPath(m_stagingPath); - emitFailed(reason); - } - -private: - /* - * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. - * Basically, it starts messing things up while MultiMC is extracting/creating instances - * and causes that horrible failure that is NTFS to lock files in place because they are open. - */ - ExponentialSeries backoff; - QString m_stagingPath; - InstanceList * m_parent; - unique_qobject_ptr<Task> m_child; - QString m_instanceName; - QString m_groupName; - QTimer m_backoffTimer; -}; - -Task * InstanceList::wrapInstanceTask(InstanceTask * task) -{ - auto stagingPath = getStagedInstancePath(); - task->setStagingPath(stagingPath); - task->setParentSettings(m_globalSettings); - return new InstanceStaging(this, task, stagingPath, task->name(), task->group()); -} - -QString InstanceList::getStagedInstancePath() -{ - QString key = QUuid::createUuid().toString(); - QString relPath = FS::PathCombine("_MMC_TEMP/" , key); - QDir rootPath(m_instDir); - auto path = FS::PathCombine(m_instDir, relPath); - if(!rootPath.mkpath(relPath)) - { - return QString(); - } - return path; -} - -bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName) -{ - QDir dir; - QString instID = FS::DirNameFromString(instanceName, m_instDir); - { - WatchLock lock(m_watcher, m_instDir); - QString destination = FS::PathCombine(m_instDir, instID); - if(!dir.rename(path, destination)) - { - qWarning() << "Failed to move" << path << "to" << destination; - return false; - } - m_instanceGroupIndex[instID] = groupName; - instanceSet.insert(instID); - m_groupNameCache.insert(groupName); - emit instancesChanged(); - emit instanceSelectRequest(instID); - } - saveGroupList(); - return true; -} - -bool InstanceList::destroyStagingPath(const QString& keyPath) -{ - return FS::deletePath(keyPath); -} - -int InstanceList::getTotalPlayTime() { - updateTotalPlayTime(); - return totalPlayTime; -} - -#include "InstanceList.moc" diff --git a/api/logic/InstanceList.h b/api/logic/InstanceList.h deleted file mode 100644 index 56ee3be4..00000000 --- a/api/logic/InstanceList.h +++ /dev/null @@ -1,175 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QAbstractListModel> -#include <QSet> -#include <QList> - -#include "BaseInstance.h" - -#include "multimc_logic_export.h" - -#include "QObjectPtr.h" - -class QFileSystemWatcher; -class InstanceTask; -using InstanceId = QString; -using GroupId = QString; -using InstanceLocator = std::pair<InstancePtr, int>; - -enum class InstCreateError -{ - NoCreateError = 0, - NoSuchVersion, - UnknownCreateError, - InstExists, - CantCreateDir -}; - -enum class GroupsState -{ - NotLoaded, - Steady, - Dirty -}; - - -class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel -{ - Q_OBJECT - -public: - explicit InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent = 0); - virtual ~InstanceList(); - -public: - QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - - bool setData(const QModelIndex & index, const QVariant & value, int role) override; - - enum AdditionalRoles - { - GroupRole = Qt::UserRole, - InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance - InstanceIDRole = 0x34B1CB49 ///< Return id if the instance - }; - /*! - * \brief Error codes returned by functions in the InstanceList class. - * NoError Indicates that no error occurred. - * UnknownError indicates that an unspecified error occurred. - */ - enum InstListError - { - NoError = 0, - UnknownError - }; - - InstancePtr at(int i) const - { - return m_instances.at(i); - } - - int count() const - { - return m_instances.count(); - } - - InstListError loadList(); - void saveNow(); - - - InstancePtr getInstanceById(QString id) const; - QModelIndex getInstanceIndexById(const QString &id) const; - QStringList getGroups(); - bool isGroupCollapsed(const QString &groupName); - - GroupId getInstanceGroup(const InstanceId & id) const; - void setInstanceGroup(const InstanceId & id, const GroupId& name); - - void deleteGroup(const GroupId & name); - void deleteInstance(const InstanceId & id); - - // Wrap an instance creation task in some more task machinery and make it ready to be used - Task * wrapInstanceTask(InstanceTask * task); - - /** - * Create a new empty staging area for instance creation and @return a path/key top commit it later. - * Used by instance manipulation tasks. - */ - QString getStagedInstancePath(); - - /** - * Commit the staging area given by @keyPath to the provider - used when creation succeeds. - * Used by instance manipulation tasks. - */ - bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName); - - /** - * Destroy a previously created staging area given by @keyPath - used when creation fails. - * Used by instance manipulation tasks. - */ - bool destroyStagingPath(const QString & keyPath); - - int getTotalPlayTime(); - -signals: - void dataIsInvalid(); - void instancesChanged(); - void instanceSelectRequest(QString instanceId); - void groupsChanged(QSet<QString> groups); - -public slots: - void on_InstFolderChanged(const Setting &setting, QVariant value); - void on_GroupStateChanged(const QString &group, bool collapsed); - -private slots: - void propertiesChanged(BaseInstance *inst); - void providerUpdated(); - void instanceDirContentsChanged(const QString &path); - -private: - int getInstIndex(BaseInstance *inst) const; - void updateTotalPlayTime(); - void suspendWatch(); - void resumeWatch(); - void add(const QList<InstancePtr> &list); - void loadGroupList(); - void saveGroupList(); - QList<InstanceId> discoverInstances(); - InstancePtr loadInstance(const InstanceId& id); - -private: - int m_watchLevel = 0; - int totalPlayTime = 0; - bool m_dirty = false; - QList<InstancePtr> m_instances; - QSet<QString> m_groupNameCache; - - SettingsObjectPtr m_globalSettings; - QString m_instDir; - QFileSystemWatcher * m_watcher; - // FIXME: this is so inefficient that looking at it is almost painful. - QSet<QString> m_collapsedGroups; - QMap<InstanceId, GroupId> m_instanceGroupIndex; - QSet<InstanceId> instanceSet; - bool m_groupsLoaded = false; - bool m_instancesProbed = false; -}; diff --git a/api/logic/InstanceTask.cpp b/api/logic/InstanceTask.cpp deleted file mode 100644 index dd132877..00000000 --- a/api/logic/InstanceTask.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "InstanceTask.h" - -InstanceTask::InstanceTask() -{ -} - -InstanceTask::~InstanceTask() -{ -} diff --git a/api/logic/InstanceTask.h b/api/logic/InstanceTask.h deleted file mode 100644 index c5f6c7fd..00000000 --- a/api/logic/InstanceTask.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "tasks/Task.h" -#include "multimc_logic_export.h" -#include "settings/SettingsObject.h" - -class MULTIMC_LOGIC_EXPORT InstanceTask : public Task -{ - Q_OBJECT -public: - explicit InstanceTask(); - virtual ~InstanceTask(); - - void setParentSettings(SettingsObjectPtr settings) - { - m_globalSettings = settings; - } - - void setStagingPath(const QString &stagingPath) - { - m_stagingPath = stagingPath; - } - - void setName(const QString &name) - { - m_instName = name; - } - QString name() const - { - return m_instName; - } - - void setIcon(const QString &icon) - { - m_instIcon = icon; - } - - void setGroup(const QString &group) - { - m_instGroup = group; - } - QString group() const - { - return m_instGroup; - } - -protected: /* data */ - SettingsObjectPtr m_globalSettings; - QString m_instName; - QString m_instIcon; - QString m_instGroup; - QString m_stagingPath; -}; diff --git a/api/logic/Json.cpp b/api/logic/Json.cpp deleted file mode 100644 index 37ada1aa..00000000 --- a/api/logic/Json.cpp +++ /dev/null @@ -1,272 +0,0 @@ -// Licensed under the Apache-2.0 license. See README.md for details. - -#include "Json.h" - -#include <QFile> - -#include "FileSystem.h" -#include <math.h> - -namespace Json -{ -void write(const QJsonDocument &doc, const QString &filename) -{ - FS::write(filename, doc.toJson()); -} -void write(const QJsonObject &object, const QString &filename) -{ - write(QJsonDocument(object), filename); -} -void write(const QJsonArray &array, const QString &filename) -{ - write(QJsonDocument(array), filename); -} - -QByteArray toBinary(const QJsonObject &obj) -{ - return QJsonDocument(obj).toBinaryData(); -} -QByteArray toBinary(const QJsonArray &array) -{ - return QJsonDocument(array).toBinaryData(); -} -QByteArray toText(const QJsonObject &obj) -{ - return QJsonDocument(obj).toJson(QJsonDocument::Compact); -} -QByteArray toText(const QJsonArray &array) -{ - return QJsonDocument(array).toJson(QJsonDocument::Compact); -} - -static bool isBinaryJson(const QByteArray &data) -{ - decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; - return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; -} -QJsonDocument requireDocument(const QByteArray &data, const QString &what) -{ - if (isBinaryJson(data)) - { - QJsonDocument doc = QJsonDocument::fromBinaryData(data); - if (doc.isNull()) - { - throw JsonException(what + ": Invalid JSON (binary JSON detected)"); - } - return doc; - } - else - { - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) - { - throw JsonException(what + ": Error parsing JSON: " + error.errorString()); - } - return doc; - } -} -QJsonDocument requireDocument(const QString &filename, const QString &what) -{ - return requireDocument(FS::read(filename), what); -} -QJsonObject requireObject(const QJsonDocument &doc, const QString &what) -{ - if (!doc.isObject()) - { - throw JsonException(what + " is not an object"); - } - return doc.object(); -} -QJsonArray requireArray(const QJsonDocument &doc, const QString &what) -{ - if (!doc.isArray()) - { - throw JsonException(what + " is not an array"); - } - return doc.array(); -} - -void writeString(QJsonObject &to, const QString &key, const QString &value) -{ - if (!value.isEmpty()) - { - to.insert(key, value); - } -} - -void writeStringList(QJsonObject &to, const QString &key, const QStringList &values) -{ - if (!values.isEmpty()) - { - QJsonArray array; - for(auto value: values) - { - array.append(value); - } - to.insert(key, array); - } -} - -template<> -QJsonValue toJson<QUrl>(const QUrl &url) -{ - return QJsonValue(url.toString(QUrl::FullyEncoded)); -} -template<> -QJsonValue toJson<QByteArray>(const QByteArray &data) -{ - return QJsonValue(QString::fromLatin1(data.toHex())); -} -template<> -QJsonValue toJson<QDateTime>(const QDateTime &datetime) -{ - return QJsonValue(datetime.toString(Qt::ISODate)); -} -template<> -QJsonValue toJson<QDir>(const QDir &dir) -{ - return QDir::current().relativeFilePath(dir.absolutePath()); -} -template<> -QJsonValue toJson<QUuid>(const QUuid &uuid) -{ - return uuid.toString(); -} -template<> -QJsonValue toJson<QVariant>(const QVariant &variant) -{ - return QJsonValue::fromVariant(variant); -} - - -template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what) -{ - const QString string = ensureIsType<QString>(value, what); - // ensure that the string can be safely cast to Latin1 - if (string != QString::fromLatin1(string.toLatin1())) - { - throw JsonException(what + " is not encodable as Latin1"); - } - return QByteArray::fromHex(string.toLatin1()); -} - -template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what) -{ - if (!value.isArray()) - { - throw JsonException(what + " is not an array"); - } - return value.toArray(); -} - - -template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what) -{ - if (!value.isString()) - { - throw JsonException(what + " is not a string"); - } - return value.toString(); -} - -template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what) -{ - if (!value.isBool()) - { - throw JsonException(what + " is not a bool"); - } - return value.toBool(); -} - -template<> double requireIsType<double>(const QJsonValue &value, const QString &what) -{ - if (!value.isDouble()) - { - throw JsonException(what + " is not a double"); - } - return value.toDouble(); -} - -template<> int requireIsType<int>(const QJsonValue &value, const QString &what) -{ - const double doubl = requireIsType<double>(value, what); - if (fmod(doubl, 1) != 0) - { - throw JsonException(what + " is not an integer"); - } - return int(doubl); -} - -template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what) -{ - const QString string = requireIsType<QString>(value, what); - const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate); - if (!datetime.isValid()) - { - throw JsonException(what + " is not a ISO formatted date/time value"); - } - return datetime; -} - -template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what) -{ - const QString string = ensureIsType<QString>(value, what); - if (string.isEmpty()) - { - return QUrl(); - } - const QUrl url = QUrl(string, QUrl::StrictMode); - if (!url.isValid()) - { - throw JsonException(what + " is not a correctly formatted URL"); - } - return url; -} - -template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what) -{ - const QString string = requireIsType<QString>(value, what); - // FIXME: does not handle invalid characters! - return QDir::current().absoluteFilePath(string); -} - -template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what) -{ - const QString string = requireIsType<QString>(value, what); - const QUuid uuid = QUuid(string); - if (uuid.toString() != string) // converts back => valid - { - throw JsonException(what + " is not a valid UUID"); - } - return uuid; -} - -template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what) -{ - if (!value.isObject()) - { - throw JsonException(what + " is not an object"); - } - return value.toObject(); -} - -template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what) -{ - if (value.isNull() || value.isUndefined()) - { - throw JsonException(what + " is null or undefined"); - } - return value.toVariant(); -} - -template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what) -{ - if (value.isNull() || value.isUndefined()) - { - throw JsonException(what + " is null or undefined"); - } - return value; -} - -} diff --git a/api/logic/Json.h b/api/logic/Json.h deleted file mode 100644 index 34ff6fe2..00000000 --- a/api/logic/Json.h +++ /dev/null @@ -1,249 +0,0 @@ -// Licensed under the Apache-2.0 license. See README.md for details. - -#pragma once - -#include <QJsonDocument> -#include <QJsonArray> -#include <QJsonObject> -#include <QDateTime> -#include <QUrl> -#include <QDir> -#include <QUuid> -#include <QVariant> -#include <memory> - -#include "Exception.h" - -namespace Json -{ -class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception -{ -public: - JsonException(const QString &message) : Exception(message) {} -}; - -/// @throw FileSystemException -MULTIMC_LOGIC_EXPORT void write(const QJsonDocument &doc, const QString &filename); -/// @throw FileSystemException -MULTIMC_LOGIC_EXPORT void write(const QJsonObject &object, const QString &filename); -/// @throw FileSystemException -MULTIMC_LOGIC_EXPORT void write(const QJsonArray &array, const QString &filename); - -MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonObject &obj); -MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonArray &array); -MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonObject &obj); -MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonArray &array); - -/// @throw JsonException -MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document"); -/// @throw JsonException -MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QString &filename, const QString &what = "Document"); -/// @throw JsonException -MULTIMC_LOGIC_EXPORT QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document"); -/// @throw JsonException -MULTIMC_LOGIC_EXPORT QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document"); - -/////////////////// WRITING //////////////////// - -void writeString(QJsonObject & to, const QString &key, const QString &value); -void writeStringList(QJsonObject & to, const QString &key, const QStringList &values); - -template<typename T> -QJsonValue toJson(const T &t) -{ - return QJsonValue(t); -} -template<> -QJsonValue toJson<QUrl>(const QUrl &url); -template<> -QJsonValue toJson<QByteArray>(const QByteArray &data); -template<> -QJsonValue toJson<QDateTime>(const QDateTime &datetime); -template<> -QJsonValue toJson<QDir>(const QDir &dir); -template<> -QJsonValue toJson<QUuid>(const QUuid &uuid); -template<> -QJsonValue toJson<QVariant>(const QVariant &variant); - -template<typename T> -QJsonArray toJsonArray(const QList<T> &container) -{ - QJsonArray array; - for (const T item : container) - { - array.append(toJson<T>(item)); - } - return array; -} - -////////////////// READING //////////////////// - -/// @throw JsonException -template <typename T> -T requireIsType(const QJsonValue &value, const QString &what = "Value"); - -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT double requireIsType<double>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT bool requireIsType<bool>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT int requireIsType<int>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QString requireIsType<QString>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QDir requireIsType<QDir>(const QJsonValue &value, const QString &what); -/// @throw JsonException -template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what); - -// the following functions are higher level functions, that make use of the above functions for -// type conversion -template <typename T> -T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value") -{ - if (value.isUndefined() || value.isNull()) - { - return default_; - } - try - { - return requireIsType<T>(value, what); - } - catch (const JsonException &) - { - return default_; - } -} - -/// @throw JsonException -template <typename T> -T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") -{ - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - throw JsonException(localWhat + "s parent does not contain " + localWhat); - } - return requireIsType<T>(parent.value(key), localWhat); -} - -template <typename T> -T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__") -{ - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - return default_; - } - return ensureIsType<T>(parent.value(key), default_, localWhat); -} - -template <typename T> -QVector<T> requireIsArrayOf(const QJsonDocument &doc) -{ - const QJsonArray array = requireArray(doc); - QVector<T> out; - for (const QJsonValue val : array) - { - out.append(requireIsType<T>(val, "Document")); - } - return out; -} - -template <typename T> -QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value") -{ - const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what); - QVector<T> out; - for (const QJsonValue val : array) - { - out.append(requireIsType<T>(val, what)); - } - return out; -} - -template <typename T> -QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value") -{ - if (value.isUndefined()) - { - return default_; - } - return ensureIsArrayOf<T>(value, what); -} - -/// @throw JsonException -template <typename T> -QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") -{ - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - throw JsonException(localWhat + "s parent does not contain " + localWhat); - } - return ensureIsArrayOf<T>(parent.value(key), localWhat); -} - -template <typename T> -QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key, - const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__") -{ - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - return default_; - } - return ensureIsArrayOf<T>(parent.value(key), default_, localWhat); -} - -// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers -#define JSON_HELPERFUNCTIONS(NAME, TYPE) \ - inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \ - { \ - return requireIsType<TYPE>(value, what); \ - } \ - inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \ - { \ - return ensureIsType<TYPE>(value, default_, what); \ - } \ - inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \ - { \ - return requireIsType<TYPE>(parent, key, what); \ - } \ - inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \ - { \ - return ensureIsType<TYPE>(parent, key, default_, what); \ - } - -JSON_HELPERFUNCTIONS(Array, QJsonArray) -JSON_HELPERFUNCTIONS(Object, QJsonObject) -JSON_HELPERFUNCTIONS(JsonValue, QJsonValue) -JSON_HELPERFUNCTIONS(String, QString) -JSON_HELPERFUNCTIONS(Boolean, bool) -JSON_HELPERFUNCTIONS(Double, double) -JSON_HELPERFUNCTIONS(Integer, int) -JSON_HELPERFUNCTIONS(DateTime, QDateTime) -JSON_HELPERFUNCTIONS(Url, QUrl) -JSON_HELPERFUNCTIONS(ByteArray, QByteArray) -JSON_HELPERFUNCTIONS(Dir, QDir) -JSON_HELPERFUNCTIONS(Uuid, QUuid) -JSON_HELPERFUNCTIONS(Variant, QVariant) - -#undef JSON_HELPERFUNCTIONS - -} -using JSONValidationError = Json::JsonException; diff --git a/api/logic/LoggedProcess.cpp b/api/logic/LoggedProcess.cpp deleted file mode 100644 index 822c0f04..00000000 --- a/api/logic/LoggedProcess.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include "LoggedProcess.h" -#include "MessageLevel.h" -#include <QDebug> - -LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) -{ - // QProcess has a strange interface... let's map a lot of those into a few. - connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); - connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); - connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); - connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); - connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); -} - -LoggedProcess::~LoggedProcess() -{ - if(m_is_detachable) - { - setProcessState(QProcess::NotRunning); - } -} - -QStringList reprocess(const QByteArray & data, QString & leftover) -{ - QString str = leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - leftover = lines.takeLast(); - return lines; -} - -void LoggedProcess::on_stdErr() -{ - auto lines = reprocess(readAllStandardError(), m_err_leftover); - emit log(lines, MessageLevel::StdErr); -} - -void LoggedProcess::on_stdOut() -{ - auto lines = reprocess(readAllStandardOutput(), m_out_leftover); - emit log(lines, MessageLevel::StdOut); -} - -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) - { - if (status == QProcess::NormalExit) - { - //: Message displayed on instance exit - emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC); - changeState(LoggedProcess::Finished); - } - else - { - //: Message displayed on instance crashed - if(exit_code == -1) - emit log({tr("Process crashed.")}, MessageLevel::MultiMC); - else - emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC); - changeState(LoggedProcess::Crashed); - } - } - else - { - //: Message displayed after the instance exits due to kill request - emit log({tr("Process was killed by user.")}, MessageLevel::Error); - changeState(LoggedProcess::Aborted); - } -} - -void LoggedProcess::on_error(QProcess::ProcessError error) -{ - switch(error) - { - case QProcess::FailedToStart: - { - emit log({tr("The process failed to start.")}, MessageLevel::Fatal); - changeState(LoggedProcess::FailedToStart); - break; - } - // we'll just ignore those... never needed them - case QProcess::Crashed: - case QProcess::ReadError: - case QProcess::Timedout: - case QProcess::UnknownError: - case QProcess::WriteError: - break; - } -} - -void LoggedProcess::kill() -{ - m_is_aborting = true; - QProcess::kill(); -} - -int LoggedProcess::exitCode() const -{ - return m_exit_code; -} - -void LoggedProcess::changeState(LoggedProcess::State state) -{ - if(state == m_state) - return; - m_state = state; - emit stateChanged(m_state); -} - -LoggedProcess::State LoggedProcess::state() const -{ - return m_state; -} - -void LoggedProcess::on_stateChange(QProcess::ProcessState state) -{ - switch(state) - { - case QProcess::NotRunning: - break; // let's not - there are too many that handle this already. - case QProcess::Starting: - { - if(m_state != LoggedProcess::NotRunning) - { - qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting; - } - changeState(LoggedProcess::Starting); - return; - } - case QProcess::Running: - { - if(m_state != LoggedProcess::Starting) - { - qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running; - } - changeState(LoggedProcess::Running); - return; - } - } -} - -#if defined Q_OS_WIN32 -#include <windows.h> -#endif - -qint64 LoggedProcess::processId() const -{ -#ifdef Q_OS_WIN - return pid() ? pid()->dwProcessId : 0; -#else - return pid(); -#endif -} - -void LoggedProcess::setDetachable(bool detachable) -{ - m_is_detachable = detachable; -} diff --git a/api/logic/LoggedProcess.h b/api/logic/LoggedProcess.h deleted file mode 100644 index 327cdc6a..00000000 --- a/api/logic/LoggedProcess.h +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QProcess> -#include "MessageLevel.h" -#include "multimc_logic_export.h" - -/* - * This is a basic process. - * It has line-based logging support and hides some of the nasty bits. - */ -class MULTIMC_LOGIC_EXPORT LoggedProcess : public QProcess -{ -Q_OBJECT -public: - enum State - { - NotRunning, - Starting, - FailedToStart, - Running, - Finished, - Crashed, - Aborted - }; - -public: - explicit LoggedProcess(QObject* parent = 0); - virtual ~LoggedProcess(); - - State state() const; - int exitCode() const; - qint64 processId() const; - - void setDetachable(bool detachable); - -signals: - void log(QStringList lines, MessageLevel::Enum level); - void stateChanged(LoggedProcess::State state); - -public slots: - /** - * @brief kill the process - equivalent to kill -9 - */ - void kill(); - - -private slots: - void on_stdErr(); - void on_stdOut(); - void on_exit(int exit_code, QProcess::ExitStatus status); - void on_error(QProcess::ProcessError error); - void on_stateChange(QProcess::ProcessState); - -private: - void changeState(LoggedProcess::State state); - -private: - QString m_err_leftover; - QString m_out_leftover; - bool m_killed = false; - State m_state = NotRunning; - int m_exit_code = 0; - bool m_is_aborting = false; - bool m_is_detachable = false; -}; diff --git a/api/logic/MMCStrings.cpp b/api/logic/MMCStrings.cpp deleted file mode 100644 index dc91c8d6..00000000 --- a/api/logic/MMCStrings.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "MMCStrings.h" - -/// TAKEN FROM Qt, because it doesn't expose it intelligently -static inline QChar getNextChar(const QString &s, int location) -{ - return (location < s.length()) ? s.at(location) : QChar(); -} - -/// TAKEN FROM Qt, because it doesn't expose it intelligently -int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs) -{ - for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) - { - // skip spaces, tabs and 0's - QChar c1 = getNextChar(s1, l1); - while (c1.isSpace()) - c1 = getNextChar(s1, ++l1); - QChar c2 = getNextChar(s2, l2); - while (c2.isSpace()) - c2 = getNextChar(s2, ++l2); - - if (c1.isDigit() && c2.isDigit()) - { - while (c1.digitValue() == 0) - c1 = getNextChar(s1, ++l1); - while (c2.digitValue() == 0) - c2 = getNextChar(s2, ++l2); - - int lookAheadLocation1 = l1; - int lookAheadLocation2 = l2; - int currentReturnValue = 0; - // find the last digit, setting currentReturnValue as we go if it isn't equal - for (QChar lookAhead1 = c1, lookAhead2 = c2; - (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); - lookAhead1 = getNextChar(s1, ++lookAheadLocation1), - lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) - { - bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); - bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); - if (!is1ADigit && !is2ADigit) - break; - if (!is1ADigit) - return -1; - if (!is2ADigit) - return 1; - if (currentReturnValue == 0) - { - if (lookAhead1 < lookAhead2) - { - currentReturnValue = -1; - } - else if (lookAhead1 > lookAhead2) - { - currentReturnValue = 1; - } - } - } - if (currentReturnValue != 0) - return currentReturnValue; - } - if (cs == Qt::CaseInsensitive) - { - if (!c1.isLower()) - c1 = c1.toLower(); - if (!c2.isLower()) - c2 = c2.toLower(); - } - int r = QString::localeAwareCompare(c1, c2); - if (r < 0) - return -1; - if (r > 0) - return 1; - } - // The two strings are the same (02 == 2) so fall back to the normal sort - return QString::compare(s1, s2, cs); -} diff --git a/api/logic/MMCStrings.h b/api/logic/MMCStrings.h deleted file mode 100644 index 493ba3d2..00000000 --- a/api/logic/MMCStrings.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include <QString> - -#include "multimc_logic_export.h" - -namespace Strings -{ - int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs); -} diff --git a/api/logic/MMCZip.cpp b/api/logic/MMCZip.cpp deleted file mode 100644 index b25c61e7..00000000 --- a/api/logic/MMCZip.cpp +++ /dev/null @@ -1,312 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <quazip.h> -#include <quazipdir.h> -#include <quazipfile.h> -#include <JlCompress.h> -#include "MMCZip.h" -#include "FileSystem.h" - -#include <QDebug> - -// ours -bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const JlCompress::FilterFunction filter) -{ - QuaZip modZip(from.filePath()); - modZip.open(QuaZip::mdUnzip); - - QuaZipFile fileInsideMod(&modZip); - QuaZipFile zipOutFile(into); - for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) - { - QString filename = modZip.getCurrentFileName(); - if (filter && !filter(filename)) - { - qDebug() << "Skipping file " << filename << " from " - << from.fileName() << " - filtered"; - continue; - } - if (contained.contains(filename)) - { - qDebug() << "Skipping already contained file " << filename << " from " - << from.fileName(); - continue; - } - contained.insert(filename); - - if (!fileInsideMod.open(QIODevice::ReadOnly)) - { - qCritical() << "Failed to open " << filename << " from " << from.fileName(); - return false; - } - - QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); - - if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) - { - qCritical() << "Failed to open " << filename << " in the jar"; - fileInsideMod.close(); - return false; - } - if (!JlCompress::copyData(fileInsideMod, zipOutFile)) - { - zipOutFile.close(); - fileInsideMod.close(); - qCritical() << "Failed to copy data of " << filename << " into the jar"; - return false; - } - zipOutFile.close(); - fileInsideMod.close(); - } - return true; -} - -// ours -bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods) -{ - QuaZip zipOut(targetJarPath); - if (!zipOut.open(QuaZip::mdCreate)) - { - QFile::remove(targetJarPath); - qCritical() << "Failed to open the minecraft.jar for modding"; - return false; - } - // Files already added to the jar. - // These files will be skipped. - QSet<QString> addedFiles; - - // Modify the jar - QListIterator<Mod> i(mods); - i.toBack(); - while (i.hasPrevious()) - { - const Mod &mod = i.previous(); - // do not merge disabled mods. - if (!mod.enabled()) - continue; - if (mod.type() == Mod::MOD_ZIPFILE) - { - if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles)) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; - return false; - } - } - else if (mod.type() == Mod::MOD_SINGLEFILE) - { - // FIXME: buggy - does not work with addedFiles - auto filename = mod.filename(); - if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; - return false; - } - addedFiles.insert(filename.fileName()); - } - else if (mod.type() == Mod::MOD_FOLDER) - { - // FIXME: buggy - does not work with addedFiles - auto filename = mod.filename(); - QString what_to_zip = filename.absoluteFilePath(); - QDir dir(what_to_zip); - dir.cdUp(); - QString parent_dir = dir.absolutePath(); - if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles)) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; - return false; - } - qDebug() << "Adding folder " << filename.fileName() << " from " - << filename.absoluteFilePath(); - } - else - { - // Make sure we do not continue launching when something is missing or undefined... - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar."; - return false; - } - } - - if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");})) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to insert minecraft.jar contents."; - return false; - } - - // Recompress the jar - zipOut.close(); - if (zipOut.getZipError() != 0) - { - QFile::remove(targetJarPath); - qCritical() << "Failed to finalize minecraft.jar!"; - return false; - } - return true; -} - -// ours -QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root) -{ - QuaZipDir rootDir(zip, root); - for(auto fileName: rootDir.entryList(QDir::Files)) - { - if(fileName == what) - return root; - } - for(auto fileName: rootDir.entryList(QDir::Dirs)) - { - QString result = findFolderOfFileInZip(zip, what, root + fileName); - if(!result.isEmpty()) - { - return result; - } - } - return QString(); -} - -// ours -bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root) -{ - QuaZipDir rootDir(zip, root); - for(auto fileName: rootDir.entryList(QDir::Files)) - { - if(fileName == what) - { - result.append(root); - return true; - } - } - for(auto fileName: rootDir.entryList(QDir::Dirs)) - { - findFilesInZip(zip, what, result, root + fileName); - } - return !result.isEmpty(); -} - - -// ours -nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) -{ - QDir directory(target); - QStringList extracted; - - qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; - auto numEntries = zip->getEntriesCount(); - if(numEntries < 0) { - qWarning() << "Failed to enumerate files in archive"; - return nonstd::nullopt; - } - else if(numEntries == 0) { - qDebug() << "Extracting empty archives seems odd..."; - return extracted; - } - else if (!zip->goToFirstFile()) - { - qWarning() << "Failed to seek to first file in zip"; - return nonstd::nullopt; - } - - do - { - QString name = zip->getCurrentFileName(); - if(!name.startsWith(subdir)) - { - continue; - } - name.remove(0, subdir.size()); - QString absFilePath = directory.absoluteFilePath(name); - if(name.isEmpty()) - { - absFilePath += "/"; - } - if (!JlCompress::extractFile(zip, "", absFilePath)) - { - qWarning() << "Failed to extract file" << name << "to" << absFilePath; - JlCompress::removeFile(extracted); - return nonstd::nullopt; - } - extracted.append(absFilePath); - qDebug() << "Extracted file" << name; - } while (zip->goToNextFile()); - return extracted; -} - -// ours -bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &target) -{ - return JlCompress::extractFile(zip, file, target); -} - -// ours -nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir) -{ - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) - { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if(fileInfo.size() == 22) { - return QStringList(); - } - qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; - return nonstd::nullopt; - } - return MMCZip::extractSubDir(&zip, "", dir); -} - -// ours -nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir) -{ - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) - { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if(fileInfo.size() == 22) { - return QStringList(); - } - qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; - return nonstd::nullopt; - } - return MMCZip::extractSubDir(&zip, subdir, dir); -} - -// ours -bool MMCZip::extractFile(QString fileCompressed, QString file, QString target) -{ - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) - { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if(fileInfo.size() == 22) { - return true; - } - qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); - return false; - } - return MMCZip::extractRelFile(&zip, file, target); -} diff --git a/api/logic/MMCZip.h b/api/logic/MMCZip.h deleted file mode 100644 index 98d9cd5b..00000000 --- a/api/logic/MMCZip.h +++ /dev/null @@ -1,94 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QFileInfo> -#include <QSet> -#include "minecraft/mod/Mod.h" -#include <functional> - -#include "multimc_logic_export.h" - -#include <JlCompress.h> -#include <nonstd/optional> - -namespace MMCZip -{ - - /** - * Merge two zip files, using a filter function - */ - bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, - const JlCompress::FilterFunction filter = nullptr); - - /** - * take a source jar, add mods to it, resulting in target jar - */ - bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods); - - /** - * Find a single file in archive by file name (not path) - * - * \return the path prefix where the file is - */ - QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString("")); - - /** - * Find a multiple files of the same name in archive by file name - * If a file is found in a path, no deeper paths are searched - * - * \return true if anything was found - */ - bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString()); - - /** - * Extract a subdirectory from an archive - */ - nonstd::optional<QStringList> MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); - - bool MULTIMC_LOGIC_EXPORT extractRelFile(QuaZip *zip, const QString & file, const QString &target); - - /** - * Extract a whole archive. - * - * \param fileCompressed The name of the archive. - * \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> MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir); - - /** - * Extract a subdirectory from an archive - * - * \param fileCompressed The name of the archive. - * \param subdir The directory within the archive to extract - * \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> MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString subdir, QString dir); - - /** - * Extract a single file from an archive into a directory - * - * \param fileCompressed The name of the archive. - * \param file The file within the archive to extract - * \param dir The directory to extract to, the current directory if left empty. - * \return true for success or false for failure - */ - bool MULTIMC_LOGIC_EXPORT extractFile(QString fileCompressed, QString file, QString dir); - -} diff --git a/api/logic/MessageLevel.cpp b/api/logic/MessageLevel.cpp deleted file mode 100644 index 0a2cd23d..00000000 --- a/api/logic/MessageLevel.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "MessageLevel.h" - -MessageLevel::Enum MessageLevel::getLevel(const QString& levelName) -{ - if (levelName == "MultiMC") - return MessageLevel::MultiMC; - else if (levelName == "Debug") - return MessageLevel::Debug; - else if (levelName == "Info") - return MessageLevel::Info; - else if (levelName == "Message") - return MessageLevel::Message; - else if (levelName == "Warning") - return MessageLevel::Warning; - else if (levelName == "Error") - return MessageLevel::Error; - else if (levelName == "Fatal") - return MessageLevel::Fatal; - // Skip PrePost, it's not exposed to !![]! - // Also skip StdErr and StdOut - else - return MessageLevel::Unknown; -} - -MessageLevel::Enum MessageLevel::fromLine(QString &line) -{ - // Level prefix - int endmark = line.indexOf("]!"); - if (line.startsWith("!![") && endmark != -1) - { - auto level = MessageLevel::getLevel(line.left(endmark).mid(3)); - line = line.mid(endmark + 2); - return level; - } - return MessageLevel::Unknown; -} diff --git a/api/logic/MessageLevel.h b/api/logic/MessageLevel.h deleted file mode 100644 index f19048e3..00000000 --- a/api/logic/MessageLevel.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include <QString> - -/** - * @brief the MessageLevel Enum - * defines what level a log message is - */ -namespace MessageLevel -{ -enum Enum -{ - Unknown, /**< No idea what this is or where it came from */ - StdOut, /**< Undetermined stderr messages */ - StdErr, /**< Undetermined stdout messages */ - MultiMC, /**< MultiMC Messages */ - Debug, /**< Debug Messages */ - Info, /**< Info Messages */ - Message, /**< Standard Messages */ - Warning, /**< Warnings */ - Error, /**< Errors */ - Fatal, /**< Fatal Errors */ -}; -MessageLevel::Enum getLevel(const QString &levelName); - -/* Get message level from a line. Line is modified if it was successful. */ -MessageLevel::Enum fromLine(QString &line); -} diff --git a/api/logic/NullInstance.h b/api/logic/NullInstance.h deleted file mode 100644 index 94ed6c3a..00000000 --- a/api/logic/NullInstance.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once -#include "BaseInstance.h" -#include "launch/LaunchTask.h" - -class NullInstance: public BaseInstance -{ - Q_OBJECT -public: - NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) - :BaseInstance(globalSettings, settings, rootDir) - { - setVersionBroken(true); - } - virtual ~NullInstance() {}; - void saveNow() override - { - } - QString getStatusbarDescription() override - { - return tr("Unknown instance type"); - }; - QSet< QString > traits() const override - { - return {}; - }; - QString instanceConfigFolder() const override - { - return instanceRoot(); - }; - shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override - { - return nullptr; - } - shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override - { - return nullptr; - } - QProcessEnvironment createEnvironment() override - { - return QProcessEnvironment(); - } - QMap<QString, QString> getVariables() const override - { - return QMap<QString, QString>(); - } - IPathMatcher::Ptr getLogFileMatcher() override - { - return nullptr; - } - QString getLogFileRoot() override - { - return instanceRoot(); - } - QString typeName() const override - { - return "Null"; - } - bool canExport() const override - { - return false; - } - bool canEdit() const override - { - return false; - } - bool canLaunch() const override - { - return false; - } - QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override - { - QStringList out; - out << "Null instance - placeholder."; - return out; - } -}; diff --git a/api/logic/ProblemProvider.h b/api/logic/ProblemProvider.h deleted file mode 100644 index 33c9d364..00000000 --- a/api/logic/ProblemProvider.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "multimc_logic_export.h" - -enum class ProblemSeverity -{ - None, - Warning, - Error -}; - -struct PatchProblem -{ - ProblemSeverity m_severity; - QString m_description; -}; - -class MULTIMC_LOGIC_EXPORT ProblemProvider -{ -public: - virtual ~ProblemProvider() {}; - virtual const QList<PatchProblem> getProblems() const = 0; - virtual ProblemSeverity getProblemSeverity() const = 0; -}; - -class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider -{ -public: - const QList<PatchProblem> getProblems() const override - { - return m_problems; - } - ProblemSeverity getProblemSeverity() const override - { - return m_problemSeverity; - } - virtual void addProblem(ProblemSeverity severity, const QString &description) - { - if(severity > m_problemSeverity) - { - m_problemSeverity = severity; - } - m_problems.append({severity, description}); - } - -private: - QList<PatchProblem> m_problems; - ProblemSeverity m_problemSeverity = ProblemSeverity::None; -}; diff --git a/api/logic/QObjectPtr.h b/api/logic/QObjectPtr.h deleted file mode 100644 index 0ff51136..00000000 --- a/api/logic/QObjectPtr.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#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>; - -/** - * 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(); - } - -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 - { - return m_ptr; - } - -private: - std::shared_ptr <T> m_ptr; -}; diff --git a/api/logic/RWStorage.h b/api/logic/RWStorage.h deleted file mode 100644 index 3028388e..00000000 --- a/api/logic/RWStorage.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once -#include <QWriteLocker> -#include <QReadLocker> -#include <QMap> -#include <QSet> - -template <typename K, typename V> -class RWStorage -{ -public: - void add(K key, V value) - { - QWriteLocker l(&lock); - cache[key] = value; - stale_entries.remove(key); - } - V get(K key) - { - QReadLocker l(&lock); - if(cache.contains(key)) - { - return cache[key]; - } - else return V(); - } - bool get(K key, V& value) - { - QReadLocker l(&lock); - if(cache.contains(key)) - { - value = cache[key]; - return true; - } - else return false; - } - bool has(K key) - { - QReadLocker l(&lock); - return cache.contains(key); - } - bool stale(K key) - { - QReadLocker l(&lock); - if(!cache.contains(key)) - return true; - return stale_entries.contains(key); - } - void setStale(K key) - { - QWriteLocker l(&lock); - if(cache.contains(key)) - { - stale_entries.insert(key); - } - } - void clear() - { - QWriteLocker l(&lock); - cache.clear(); - stale_entries.clear(); - } -private: - QReadWriteLock lock; - QMap<K, V> cache; - QSet<K> stale_entries; -}; diff --git a/api/logic/RecursiveFileSystemWatcher.cpp b/api/logic/RecursiveFileSystemWatcher.cpp deleted file mode 100644 index b7417cdf..00000000 --- a/api/logic/RecursiveFileSystemWatcher.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "RecursiveFileSystemWatcher.h" - -#include <QRegularExpression> -#include <QDebug> - -RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject *parent) - : QObject(parent), m_watcher(new QFileSystemWatcher(this)) -{ - connect(m_watcher, &QFileSystemWatcher::fileChanged, this, - &RecursiveFileSystemWatcher::fileChange); - connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, - &RecursiveFileSystemWatcher::directoryChange); -} - -void RecursiveFileSystemWatcher::setRootDir(const QDir &root) -{ - bool wasEnabled = m_isEnabled; - disable(); - m_root = root; - setFiles(scanRecursive(m_root)); - if (wasEnabled) - { - enable(); - } -} -void RecursiveFileSystemWatcher::setWatchFiles(const bool watchFiles) -{ - bool wasEnabled = m_isEnabled; - disable(); - m_watchFiles = watchFiles; - if (wasEnabled) - { - enable(); - } -} - -void RecursiveFileSystemWatcher::enable() -{ - if (m_isEnabled) - { - return; - } - Q_ASSERT(m_root != QDir::root()); - addFilesToWatcherRecursive(m_root); - m_isEnabled = true; -} -void RecursiveFileSystemWatcher::disable() -{ - if (!m_isEnabled) - { - return; - } - m_isEnabled = false; - m_watcher->removePaths(m_watcher->files()); - m_watcher->removePaths(m_watcher->directories()); -} - -void RecursiveFileSystemWatcher::setFiles(const QStringList &files) -{ - if (files != m_files) - { - m_files = files; - emit filesChanged(); - } -} - -void RecursiveFileSystemWatcher::addFilesToWatcherRecursive(const QDir &dir) -{ - m_watcher->addPath(dir.absolutePath()); - for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) - { - addFilesToWatcherRecursive(dir.absoluteFilePath(directory)); - } - if (m_watchFiles) - { - for (const QFileInfo &info : dir.entryInfoList(QDir::Files)) - { - m_watcher->addPath(info.absoluteFilePath()); - } - } -} -QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir &directory) -{ - QStringList ret; - if(!m_matcher) - { - return {}; - } - for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden)) - { - ret.append(scanRecursive(directory.absoluteFilePath(dir))); - } - for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden)) - { - auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file)); - if (m_matcher->matches(relPath)) - { - ret.append(relPath); - } - } - return ret; -} - -void RecursiveFileSystemWatcher::fileChange(const QString &path) -{ - emit fileChanged(path); -} -void RecursiveFileSystemWatcher::directoryChange(const QString &path) -{ - setFiles(scanRecursive(m_root)); -} diff --git a/api/logic/RecursiveFileSystemWatcher.h b/api/logic/RecursiveFileSystemWatcher.h deleted file mode 100644 index c9c39f49..00000000 --- a/api/logic/RecursiveFileSystemWatcher.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include <QFileSystemWatcher> -#include <QDir> -#include "pathmatcher/IPathMatcher.h" - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject -{ - Q_OBJECT -public: - RecursiveFileSystemWatcher(QObject *parent); - - void setRootDir(const QDir &root); - QDir rootDir() const - { - return m_root; - } - - // WARNING: setting this to true may be bad for performance - void setWatchFiles(const bool watchFiles); - bool watchFiles() const - { - return m_watchFiles; - } - - void setMatcher(IPathMatcher::Ptr matcher) - { - m_matcher = matcher; - } - - QStringList files() const - { - return m_files; - } - -signals: - void filesChanged(); - void fileChanged(const QString &path); - -public slots: - void enable(); - void disable(); - -private: - QDir m_root; - bool m_watchFiles = false; - bool m_isEnabled = false; - IPathMatcher::Ptr m_matcher; - - QFileSystemWatcher *m_watcher; - - QStringList m_files; - void setFiles(const QStringList &files); - - void addFilesToWatcherRecursive(const QDir &dir); - QStringList scanRecursive(const QDir &dir); - -private slots: - void fileChange(const QString &path); - void directoryChange(const QString &path); -}; diff --git a/api/logic/SeparatorPrefixTree.h b/api/logic/SeparatorPrefixTree.h deleted file mode 100644 index 7a841cb7..00000000 --- a/api/logic/SeparatorPrefixTree.h +++ /dev/null @@ -1,298 +0,0 @@ -#pragma once -#include <QString> -#include <QMap> -#include <QStringList> - -template <char Tseparator> -class SeparatorPrefixTree -{ -public: - SeparatorPrefixTree(QStringList paths) - { - insert(paths); - } - - SeparatorPrefixTree(bool contained = false) - { - m_contained = contained; - } - - void insert(QStringList paths) - { - for(auto &path: paths) - { - insert(path); - } - } - - /// insert an exact path into the tree - SeparatorPrefixTree & insert(QString path) - { - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - children[path] = SeparatorPrefixTree(true); - return children[path]; - } - else - { - auto prefix = path.left(sepIndex); - if(!children.contains(prefix)) - { - children[prefix] = SeparatorPrefixTree(false); - } - return children[prefix].insert(path.mid(sepIndex + 1)); - } - } - - /// is the path fully contained in the tree? - bool contains(QString path) const - { - auto node = find(path); - return node != nullptr; - } - - /// does the tree cover a path? That means the prefix of the path is contained in the tree - bool covers(QString path) const - { - // if we found some valid node, it's good enough. the tree covers the path - if(m_contained) - { - return true; - } - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return false; - } - return (*found).covers(QString()); - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return false; - } - return (*found).covers(path.mid(sepIndex + 1)); - } - } - - /// return the contained path that covers the path specified - QString cover(QString path) const - { - // if we found some valid node, it's good enough. the tree covers the path - if(m_contained) - { - return QString(""); - } - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return QString(); - } - auto nested = (*found).cover(QString()); - if(nested.isNull()) - { - return nested; - } - if(nested.isEmpty()) - return path; - return path + Tseparator + nested; - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return QString(); - } - auto nested = (*found).cover(path.mid(sepIndex + 1)); - if(nested.isNull()) - { - return nested; - } - if(nested.isEmpty()) - return prefix; - return prefix + Tseparator + nested; - } - } - - /// Does the path-specified node exist in the tree? It does not have to be contained. - bool exists(QString path) const - { - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return false; - } - return true; - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return false; - } - return (*found).exists(path.mid(sepIndex + 1)); - } - } - - /// find a node in the tree by name - const SeparatorPrefixTree * find(QString path) const - { - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return nullptr; - } - return &(*found); - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return nullptr; - } - return (*found).find(path.mid(sepIndex + 1)); - } - } - - /// is this a leaf node? - bool leaf() const - { - return children.isEmpty(); - } - - /// is this node actually contained in the tree, or is it purely structural? - bool contained() const - { - return m_contained; - } - - /// Remove a path from the tree - bool remove(QString path) - { - return removeInternal(path) != Failed; - } - - /// Clear all children of this node tree node - void clear() - { - children.clear(); - } - - QStringList toStringList() const - { - QStringList collected; - // collecting these is more expensive. - auto iter = children.begin(); - while(iter != children.end()) - { - QStringList list = iter.value().toStringList(); - for(int i = 0; i < list.size(); i++) - { - list[i] = iter.key() + Tseparator + list[i]; - } - collected.append(list); - if((*iter).m_contained) - { - collected.append(iter.key()); - } - iter++; - } - return collected; - } -private: - enum Removal - { - Failed, - Succeeded, - HasChildren - }; - Removal removeInternal(QString path = QString()) - { - if(path.isEmpty()) - { - if(!m_contained) - { - // remove all children - we are removing a prefix - clear(); - return Succeeded; - } - m_contained = false; - if(children.size()) - { - return HasChildren; - } - return Succeeded; - } - Removal remStatus = Failed; - QString childToRemove; - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - childToRemove = path; - auto found = children.find(childToRemove); - if(found == children.end()) - { - return Failed; - } - remStatus = (*found).removeInternal(); - } - else - { - childToRemove = path.left(sepIndex); - auto found = children.find(childToRemove); - if(found == children.end()) - { - return Failed; - } - remStatus = (*found).removeInternal(path.mid(sepIndex + 1)); - } - switch (remStatus) - { - case Failed: - case HasChildren: - { - return remStatus; - } - case Succeeded: - { - children.remove(childToRemove); - if(m_contained) - { - return HasChildren; - } - if(children.size()) - { - return HasChildren; - } - return Succeeded; - } - } - return Failed; - } - -private: - QMap<QString,SeparatorPrefixTree<Tseparator>> children; - bool m_contained = false; -}; diff --git a/api/logic/Usable.h b/api/logic/Usable.h deleted file mode 100644 index 83dd083d..00000000 --- a/api/logic/Usable.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include <cstddef> -#include <memory> - -class Usable; - -/** - * Base class for things that can be used by multiple other things and we want to track the use count. - * - * @see UseLock - */ -class Usable -{ - friend class UseLock; -public: - std::size_t useCount() - { - return m_useCount; - } - bool isInUse() - { - return m_useCount > 0; - } -protected: - virtual void decrementUses() - { - m_useCount--; - } - virtual void incrementUses() - { - m_useCount++; - } -private: - std::size_t m_useCount = 0; -}; - -/** - * Lock class to use for keeping track of uses of other things derived from Usable - * - * @see Usable - */ -class UseLock -{ -public: - UseLock(std::shared_ptr<Usable> usable) - : m_usable(usable) - { - // this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate. - m_usable->incrementUses(); - } - ~UseLock() - { - m_usable->decrementUses(); - } -private: - std::shared_ptr<Usable> m_usable; -}; diff --git a/api/logic/Version.cpp b/api/logic/Version.cpp deleted file mode 100644 index 6392a50f..00000000 --- a/api/logic/Version.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "Version.h" - -#include <QStringList> -#include <QUrl> -#include <QRegularExpression> -#include <QRegularExpressionMatch> - -Version::Version(const QString &str) : m_string(str) -{ - parse(); -} - -bool Version::operator<(const Version &other) const -{ - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return sec1 < sec2; - } - } - - return false; -} -bool Version::operator<=(const Version &other) const -{ - return *this < other || *this == other; -} -bool Version::operator>(const Version &other) const -{ - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return sec1 > sec2; - } - } - - return false; -} -bool Version::operator>=(const Version &other) const -{ - return *this > other || *this == other; -} -bool Version::operator==(const Version &other) const -{ - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return false; - } - } - - return true; -} -bool Version::operator!=(const Version &other) const -{ - return !operator==(other); -} - -void Version::parse() -{ - m_sections.clear(); - - // FIXME: this is bad. versions can contain a lot more separators... - QStringList parts = m_string.split('.'); - - for (const auto &part : parts) - { - m_sections.append(Section(part)); - } -} diff --git a/api/logic/Version.h b/api/logic/Version.h deleted file mode 100644 index c5d93081..00000000 --- a/api/logic/Version.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include <QString> -#include <QList> - -#include "multimc_logic_export.h" - -class QUrl; - -class MULTIMC_LOGIC_EXPORT Version -{ -public: - Version(const QString &str); - Version() {} - - bool operator<(const Version &other) const; - bool operator<=(const Version &other) const; - bool operator>(const Version &other) const; - bool operator>=(const Version &other) const; - bool operator==(const Version &other) const; - bool operator!=(const Version &other) const; - - QString toString() const - { - return m_string; - } - -private: - QString m_string; - struct Section - { - explicit Section(const QString &fullString) - { - m_fullString = fullString; - int cutoff = m_fullString.size(); - for(int i = 0; i < m_fullString.size(); i++) - { - if(!m_fullString[i].isDigit()) - { - cutoff = i; - break; - } - } - auto numPart = m_fullString.leftRef(cutoff); - if(numPart.size()) - { - numValid = true; - m_numPart = numPart.toInt(); - } - auto stringPart = m_fullString.midRef(cutoff); - if(stringPart.size()) - { - m_stringPart = stringPart.toString(); - } - } - explicit Section() {} - bool numValid = false; - int m_numPart = 0; - QString m_stringPart; - QString m_fullString; - - inline bool operator!=(const Section &other) const - { - if(numValid && other.numValid) - { - return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart; - } - else - { - return m_fullString != other.m_fullString; - } - } - inline bool operator<(const Section &other) const - { - if(numValid && other.numValid) - { - if(m_numPart < other.m_numPart) - return true; - if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) - return true; - return false; - } - else - { - return m_fullString < other.m_fullString; - } - } - inline bool operator>(const Section &other) const - { - if(numValid && other.numValid) - { - if(m_numPart > other.m_numPart) - return true; - if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart) - return true; - return false; - } - else - { - return m_fullString > other.m_fullString; - } - } - }; - QList<Section> m_sections; - - void parse(); -}; diff --git a/api/logic/Version_test.cpp b/api/logic/Version_test.cpp deleted file mode 100644 index b2d657a6..00000000 --- a/api/logic/Version_test.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QTest> - -#include "TestUtil.h" -#include <Version.h> - -class ModUtilsTest : public QObject -{ - Q_OBJECT - void setupVersions() - { - QTest::addColumn<QString>("first"); - QTest::addColumn<QString>("second"); - QTest::addColumn<bool>("lessThan"); - QTest::addColumn<bool>("equal"); - - QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true; - QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true; - QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true; - QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true; - - QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false; - QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false; - QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false; - QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false; - QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false; - QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false; - QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false; - - QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false; - QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false; - QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false; - QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false; - QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false; - QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false; - QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false; - } - -private slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void test_versionCompare_data() - { - setupVersions(); - } - void test_versionCompare() - { - QFETCH(QString, first); - QFETCH(QString, second); - QFETCH(bool, lessThan); - QFETCH(bool, equal); - - const auto v1 = Version(first); - const auto v2 = Version(second); - - QCOMPARE(v1 < v2, lessThan); - QCOMPARE(v1 > v2, !lessThan && !equal); - QCOMPARE(v1 == v2, equal); - } -}; - -QTEST_GUILESS_MAIN(ModUtilsTest) - -#include "Version_test.moc" diff --git a/api/logic/WatchLock.h b/api/logic/WatchLock.h deleted file mode 100644 index 3e08b413..00000000 --- a/api/logic/WatchLock.h +++ /dev/null @@ -1,20 +0,0 @@ - -#pragma once - -#include <QString> -#include <QFileSystemWatcher> - -struct WatchLock -{ - WatchLock(QFileSystemWatcher * watcher, const QString& directory) - : m_watcher(watcher), m_directory(directory) - { - m_watcher->removePath(m_directory); - } - ~WatchLock() - { - m_watcher->addPath(m_directory); - } - QFileSystemWatcher * m_watcher; - QString m_directory; -}; diff --git a/api/logic/icons/IIconList.cpp b/api/logic/icons/IIconList.cpp deleted file mode 100644 index b3a8fb43..00000000 --- a/api/logic/icons/IIconList.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "IIconList.h" - -// blargh -IIconList::~IIconList() -{ -} - diff --git a/api/logic/icons/IIconList.h b/api/logic/icons/IIconList.h deleted file mode 100644 index 9a3fe022..00000000 --- a/api/logic/icons/IIconList.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include <QString> -#include <QStringList> -#include "multimc_logic_export.h" - -enum IconType : unsigned -{ - Builtin, - Transient, - FileBased, - ICONS_TOTAL, - ToBeDeleted -}; - -class MULTIMC_LOGIC_EXPORT IIconList -{ -public: - virtual ~IIconList(); - virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0; - virtual bool deleteIcon(const QString &key) = 0; - virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0; - virtual bool iconFileExists(const QString &key) const = 0; - virtual void installIcons(const QStringList &iconFiles) = 0; - virtual void installIcon(const QString &file, const QString &name) = 0; -}; diff --git a/api/logic/icons/IconUtils.cpp b/api/logic/icons/IconUtils.cpp deleted file mode 100644 index bf530c16..00000000 --- a/api/logic/icons/IconUtils.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "IconUtils.h" - -#include "FileSystem.h" -#include <QDirIterator> - -#include <array> - -namespace { -std::array<const char *, 6> validIconExtensions = {{ - "svg", - "png", - "ico", - "gif", - "jpg", - "jpeg" -}}; -} - -namespace IconUtils{ - -QString findBestIconIn(const QString &folder, const QString & iconKey) { - int best_found = validIconExtensions.size(); - QString best_filename; - - QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags); - while (it.hasNext()) { - it.next(); - auto fileInfo = it.fileInfo(); - - if(fileInfo.completeBaseName() != iconKey) - continue; - - auto extension = fileInfo.suffix(); - - for(int i = 0; i < best_found; i++) { - if(extension == validIconExtensions[i]) { - best_found = i; - qDebug() << i << " : " << fileInfo.fileName(); - best_filename = fileInfo.fileName(); - } - } - } - return FS::PathCombine(folder, best_filename); -} - -QString getIconFilter() { - QString out; - QTextStream stream(&out); - stream << '('; - for(size_t i = 0; i < validIconExtensions.size() - 1; i++) { - if(i > 0) { - stream << " "; - } - stream << "*." << validIconExtensions[i]; - } - stream << " *." << validIconExtensions[validIconExtensions.size() - 1]; - stream << ')'; - return out; -} - -} - diff --git a/api/logic/icons/IconUtils.h b/api/logic/icons/IconUtils.h deleted file mode 100644 index ce236946..00000000 --- a/api/logic/icons/IconUtils.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include <QString> -#include "multimc_logic_export.h" - -namespace IconUtils { - -// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path -MULTIMC_LOGIC_EXPORT QString findBestIconIn(const QString &folder, const QString & iconKey); - -// Get icon file type filter for file browser dialogs -MULTIMC_LOGIC_EXPORT QString getIconFilter(); - -} diff --git a/api/logic/java/JavaChecker.cpp b/api/logic/java/JavaChecker.cpp deleted file mode 100644 index d78d6505..00000000 --- a/api/logic/java/JavaChecker.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "JavaChecker.h" -#include "JavaUtils.h" -#include <FileSystem.h> -#include <Commandline.h> -#include <QFile> -#include <QProcess> -#include <QMap> -#include <QCoreApplication> -#include <QDebug> - -#include "Env.h" - -JavaChecker::JavaChecker(QObject *parent) : QObject(parent) -{ -} - -void JavaChecker::performCheck() -{ - QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar"); - - QStringList args; - - process.reset(new QProcess()); - if(m_args.size()) - { - auto extraArgs = Commandline::splitArgs(m_args); - args.append(extraArgs); - } - if(m_minMem != 0) - { - args << QString("-Xms%1m").arg(m_minMem); - } - if(m_maxMem != 0) - { - args << QString("-Xmx%1m").arg(m_maxMem); - } - if(m_permGen != 64) - { - args << QString("-XX:PermSize=%1m").arg(m_permGen); - } - - args.append({"-jar", checkerJar}); - process->setArguments(args); - process->setProgram(m_path); - process->setProcessChannelMode(QProcess::SeparateChannels); - process->setProcessEnvironment(CleanEnviroment()); - qDebug() << "Running java checker: " + m_path + args.join(" ");; - - connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); - connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); - connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady())); - connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady())); - connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); - killTimer.setSingleShot(true); - killTimer.start(15000); - process->start(); -} - -void JavaChecker::stdoutReady() -{ - QByteArray data = process->readAllStandardOutput(); - QString added = QString::fromLocal8Bit(data); - added.remove('\r'); - m_stdout += added; -} - -void JavaChecker::stderrReady() -{ - QByteArray data = process->readAllStandardError(); - QString added = QString::fromLocal8Bit(data); - added.remove('\r'); - m_stderr += added; -} - -void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) -{ - killTimer.stop(); - QProcessPtr _process = process; - process.reset(); - - JavaCheckResult result; - { - result.path = m_path; - result.id = m_id; - } - result.errorLog = m_stderr; - result.outLog = m_stdout; - qDebug() << "STDOUT" << m_stdout; - qWarning() << "STDERR" << m_stderr; - qDebug() << "Java checker finished with status " << status << " exit code " << exitcode; - - if (status == QProcess::CrashExit || exitcode == 1) - { - result.validity = JavaCheckResult::Validity::Errored; - emit checkFinished(result); - return; - } - - bool success = true; - - QMap<QString, QString> results; - QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts); - for(QString line : lines) - { - line = line.trimmed(); - - auto parts = line.split('=', QString::SkipEmptyParts); - if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) - { - success = false; - } - else - { - results.insert(parts[0], parts[1]); - } - } - - if(!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success) - { - result.validity = JavaCheckResult::Validity::ReturnedInvalidData; - emit checkFinished(result); - return; - } - - auto os_arch = results["os.arch"]; - auto java_version = results["java.version"]; - auto java_vendor = results["java.vendor"]; - bool is_64 = os_arch == "x86_64" || os_arch == "amd64"; - - - result.validity = JavaCheckResult::Validity::Valid; - result.is_64bit = is_64; - result.mojangPlatform = is_64 ? "64" : "32"; - result.realPlatform = os_arch; - result.javaVersion = java_version; - result.javaVendor = java_vendor; - qDebug() << "Java checker succeeded."; - emit checkFinished(result); -} - -void JavaChecker::error(QProcess::ProcessError err) -{ - if(err == QProcess::FailedToStart) - { - killTimer.stop(); - qDebug() << "Java checker has failed to start."; - JavaCheckResult result; - { - result.path = m_path; - result.id = m_id; - } - - emit checkFinished(result); - return; - } -} - -void JavaChecker::timeout() -{ - // NO MERCY. NO ABUSE. - if(process) - { - qDebug() << "Java checker has been killed by timeout."; - process->kill(); - } -} diff --git a/api/logic/java/JavaChecker.h b/api/logic/java/JavaChecker.h deleted file mode 100644 index 0a96249a..00000000 --- a/api/logic/java/JavaChecker.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once -#include <QProcess> -#include <QTimer> -#include <memory> - -#include "QObjectPtr.h" - -#include "multimc_logic_export.h" - -#include "JavaVersion.h" - -class JavaChecker; - -struct MULTIMC_LOGIC_EXPORT JavaCheckResult -{ - QString path; - QString mojangPlatform; - QString realPlatform; - JavaVersion javaVersion; - QString javaVendor; - QString outLog; - QString errorLog; - bool is_64bit = false; - int id; - enum class Validity - { - Errored, - ReturnedInvalidData, - Valid - } validity = Validity::Errored; -}; - -typedef shared_qobject_ptr<QProcess> QProcessPtr; -typedef shared_qobject_ptr<JavaChecker> JavaCheckerPtr; -class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject -{ - Q_OBJECT -public: - explicit JavaChecker(QObject *parent = 0); - void performCheck(); - - QString m_path; - QString m_args; - int m_id = 0; - int m_minMem = 0; - int m_maxMem = 0; - int m_permGen = 64; - -signals: - void checkFinished(JavaCheckResult result); -private: - QProcessPtr process; - QTimer killTimer; - QString m_stdout; - QString m_stderr; -public -slots: - void timeout(); - void finished(int exitcode, QProcess::ExitStatus); - void error(QProcess::ProcessError); - void stdoutReady(); - void stderrReady(); -}; diff --git a/api/logic/java/JavaCheckerJob.cpp b/api/logic/java/JavaCheckerJob.cpp deleted file mode 100644 index 67d70066..00000000 --- a/api/logic/java/JavaCheckerJob.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "JavaCheckerJob.h" - -#include <QDebug> - -void JavaCheckerJob::partFinished(JavaCheckResult result) -{ - num_finished++; - qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" - << javacheckers.size(); - setProgress(num_finished, javacheckers.size()); - - javaresults.replace(result.id, result); - - if (num_finished == javacheckers.size()) - { - emitSucceeded(); - } -} - -void JavaCheckerJob::executeTask() -{ - qDebug() << m_job_name.toLocal8Bit() << " started."; - for (auto iter : javacheckers) - { - javaresults.append(JavaCheckResult()); - connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); - iter->performCheck(); - } -} diff --git a/api/logic/java/JavaCheckerJob.h b/api/logic/java/JavaCheckerJob.h deleted file mode 100644 index c0986420..00000000 --- a/api/logic/java/JavaCheckerJob.h +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QtNetwork> -#include "JavaChecker.h" -#include "tasks/Task.h" - -class JavaCheckerJob; -typedef shared_qobject_ptr<JavaCheckerJob> JavaCheckerJobPtr; - -// FIXME: this just seems horribly redundant -class JavaCheckerJob : public Task -{ - Q_OBJECT -public: - explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {}; - virtual ~JavaCheckerJob() {}; - - bool addJavaCheckerAction(JavaCheckerPtr base) - { - javacheckers.append(base); - // if this is already running, the action needs to be started right away! - if (isRunning()) - { - setProgress(num_finished, javacheckers.size()); - connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); - base->performCheck(); - } - return true; - } - QList<JavaCheckResult> getResults() - { - return javaresults; - } - -private slots: - void partFinished(JavaCheckResult result); - -protected: - virtual void executeTask() override; - -private: - QString m_job_name; - QList<JavaCheckerPtr> javacheckers; - QList<JavaCheckResult> javaresults; - int num_finished = 0; -}; diff --git a/api/logic/java/JavaInstall.cpp b/api/logic/java/JavaInstall.cpp deleted file mode 100644 index 5bcf7bcb..00000000 --- a/api/logic/java/JavaInstall.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "JavaInstall.h" -#include <MMCStrings.h> - -bool JavaInstall::operator<(const JavaInstall &rhs) -{ - auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); - if(archCompare != 0) - return archCompare < 0; - if(id < rhs.id) - { - return true; - } - if(id > rhs.id) - { - return false; - } - return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; -} - -bool JavaInstall::operator==(const JavaInstall &rhs) -{ - return arch == rhs.arch && id == rhs.id && path == rhs.path; -} - -bool JavaInstall::operator>(const JavaInstall &rhs) -{ - return (!operator<(rhs)) && (!operator==(rhs)); -} diff --git a/api/logic/java/JavaInstall.h b/api/logic/java/JavaInstall.h deleted file mode 100644 index 64be40d1..00000000 --- a/api/logic/java/JavaInstall.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "BaseVersion.h" -#include "JavaVersion.h" - -struct JavaInstall : public BaseVersion -{ - JavaInstall(){} - JavaInstall(QString id, QString arch, QString path) - : id(id), arch(arch), path(path) - { - } - virtual QString descriptor() - { - return id.toString(); - } - - virtual QString name() - { - return id.toString(); - } - - virtual QString typeString() const - { - return arch; - } - - bool operator<(const JavaInstall & rhs); - bool operator==(const JavaInstall & rhs); - bool operator>(const JavaInstall & rhs); - - JavaVersion id; - QString arch; - QString path; - bool recommended = false; -}; - -typedef std::shared_ptr<JavaInstall> JavaInstallPtr; diff --git a/api/logic/java/JavaInstallList.cpp b/api/logic/java/JavaInstallList.cpp deleted file mode 100644 index 0bded03c..00000000 --- a/api/logic/java/JavaInstallList.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QtNetwork> -#include <QtXml> -#include <QRegExp> - -#include <QDebug> - -#include "java/JavaInstallList.h" -#include "java/JavaCheckerJob.h" -#include "java/JavaUtils.h" -#include "MMCStrings.h" -#include "minecraft/VersionFilterData.h" - -JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent) -{ -} - -shared_qobject_ptr<Task> JavaInstallList::getLoadTask() -{ - load(); - return getCurrentTask(); -} - -shared_qobject_ptr<Task> JavaInstallList::getCurrentTask() -{ - if(m_status == Status::InProgress) - { - return m_loadTask; - } - return nullptr; -} - -void JavaInstallList::load() -{ - if(m_status != Status::InProgress) - { - m_status = Status::InProgress; - m_loadTask = new JavaListLoadTask(this); - m_loadTask->start(); - } -} - -const BaseVersionPtr JavaInstallList::at(int i) const -{ - return m_vlist.at(i); -} - -bool JavaInstallList::isLoaded() -{ - return m_status == JavaInstallList::Status::Done; -} - -int JavaInstallList::count() const -{ - return m_vlist.count(); -} - -QVariant JavaInstallList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]); - switch (role) - { - case VersionPointerRole: - return qVariantFromValue(m_vlist[index.row()]); - case VersionIdRole: - return version->descriptor(); - case VersionRole: - return version->id.toString(); - case RecommendedRole: - return version->recommended; - case PathRole: - return version->path; - case ArchitectureRole: - return version->arch; - default: - return QVariant(); - } -} - -BaseVersionList::RoleList JavaInstallList::providesRoles() const -{ - return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole}; -} - - -void JavaInstallList::updateListData(QList<BaseVersionPtr> versions) -{ - beginResetModel(); - m_vlist = versions; - sortVersions(); - if(m_vlist.size()) - { - auto best = std::dynamic_pointer_cast<JavaInstall>(m_vlist[0]); - best->recommended = true; - } - endResetModel(); - m_status = Status::Done; - m_loadTask.reset(); -} - -bool sortJavas(BaseVersionPtr left, BaseVersionPtr right) -{ - auto rleft = std::dynamic_pointer_cast<JavaInstall>(left); - auto rright = std::dynamic_pointer_cast<JavaInstall>(right); - return (*rleft) > (*rright); -} - -void JavaInstallList::sortVersions() -{ - beginResetModel(); - std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); - endResetModel(); -} - -JavaListLoadTask::JavaListLoadTask(JavaInstallList *vlist) : Task() -{ - m_list = vlist; - m_currentRecommended = NULL; -} - -JavaListLoadTask::~JavaListLoadTask() -{ -} - -void JavaListLoadTask::executeTask() -{ - setStatus(tr("Detecting Java installations...")); - - JavaUtils ju; - QList<QString> candidate_paths = ju.FindJavaPaths(); - - m_job = new JavaCheckerJob("Java detection"); - connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); - connect(m_job.get(), &Task::progress, this, &Task::setProgress); - - qDebug() << "Probing the following Java paths: "; - int id = 0; - for(QString candidate : candidate_paths) - { - qDebug() << " " << candidate; - - auto candidate_checker = new JavaChecker(); - candidate_checker->m_path = candidate; - candidate_checker->m_id = id; - m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); - - id++; - } - - m_job->start(); -} - -void JavaListLoadTask::javaCheckerFinished() -{ - QList<JavaInstallPtr> candidates; - auto results = m_job->getResults(); - - qDebug() << "Found the following valid Java installations:"; - for(JavaCheckResult result : results) - { - if(result.validity == JavaCheckResult::Validity::Valid) - { - JavaInstallPtr javaVersion(new JavaInstall()); - - javaVersion->id = result.javaVersion; - javaVersion->arch = result.mojangPlatform; - javaVersion->path = result.path; - candidates.append(javaVersion); - - qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; - } - } - - QList<BaseVersionPtr> javas_bvp; - for (auto java : candidates) - { - //qDebug() << java->id << java->arch << " at " << java->path; - BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java); - - if (bp_java) - { - javas_bvp.append(java); - } - } - - m_list->updateListData(javas_bvp); - emitSucceeded(); -} diff --git a/api/logic/java/JavaInstallList.h b/api/logic/java/JavaInstallList.h deleted file mode 100644 index 1785a7b6..00000000 --- a/api/logic/java/JavaInstallList.h +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QAbstractListModel> - -#include "BaseVersionList.h" -#include "tasks/Task.h" - -#include "JavaCheckerJob.h" -#include "JavaInstall.h" - -#include "QObjectPtr.h" - -#include "multimc_logic_export.h" - -class JavaListLoadTask; - -class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList -{ - Q_OBJECT - enum class Status - { - NotDone, - InProgress, - Done - }; -public: - explicit JavaInstallList(QObject *parent = 0); - - shared_qobject_ptr<Task> getLoadTask() override; - bool isLoaded() override; - const BaseVersionPtr at(int i) const override; - int count() const override; - void sortVersions() override; - - QVariant data(const QModelIndex &index, int role) const override; - RoleList providesRoles() const override; - -public slots: - void updateListData(QList<BaseVersionPtr> versions) override; - -protected: - void load(); - shared_qobject_ptr<Task> getCurrentTask(); - -protected: - Status m_status = Status::NotDone; - shared_qobject_ptr<JavaListLoadTask> m_loadTask; - QList<BaseVersionPtr> m_vlist; -}; - -class JavaListLoadTask : public Task -{ - Q_OBJECT - -public: - explicit JavaListLoadTask(JavaInstallList *vlist); - virtual ~JavaListLoadTask(); - - void executeTask() override; -public slots: - void javaCheckerFinished(); - -protected: - shared_qobject_ptr<JavaCheckerJob> m_job; - JavaInstallList *m_list; - JavaInstall *m_currentRecommended; -}; diff --git a/api/logic/java/JavaUtils.cpp b/api/logic/java/JavaUtils.cpp deleted file mode 100644 index 4b231e6a..00000000 --- a/api/logic/java/JavaUtils.cpp +++ /dev/null @@ -1,399 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QStringList> -#include <QString> -#include <QDir> -#include <QStringList> - -#include <settings/Setting.h> - -#include <QDebug> -#include "java/JavaUtils.h" -#include "java/JavaInstallList.h" -#include "FileSystem.h" - -#define IBUS "@im=ibus" - -JavaUtils::JavaUtils() -{ -} - -#ifdef Q_OS_LINUX -static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH) -{ - QDir mmcBin(QCoreApplication::applicationDirPath()); - auto items = LD_LIBRARY_PATH.split(':'); - QStringList final; - for(auto & item: items) - { - QDir test(item); - if(test == mmcBin) - { - qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; - continue; - } - final.append(item); - } - return final.join(':'); -} -#endif - -QProcessEnvironment CleanEnviroment() -{ - // prepare the process environment - QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); - QProcessEnvironment env; - - QStringList ignored = - { - "JAVA_ARGS", - "CLASSPATH", - "CONFIGPATH", - "JAVA_HOME", - "JRE_HOME", - "_JAVA_OPTIONS", - "JAVA_OPTIONS", - "JAVA_TOOL_OPTIONS" - }; - for(auto key: rawenv.keys()) - { - auto value = rawenv.value(key); - // filter out dangerous java crap - if(ignored.contains(key)) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } - // filter MultiMC-related things - if(key.startsWith("QT_")) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } -#ifdef Q_OS_LINUX - // Do not pass LD_* variables to java. They were intended for MultiMC - if(key.startsWith("LD_")) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } - // Strip IBus - // IBus is a Linux IME framework. For some reason, it breaks MC? - if (key == "XMODIFIERS" && value.contains(IBUS)) - { - QString save = value; - value.replace(IBUS, ""); - qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; - } - if(key == "GAME_PRELOAD") - { - env.insert("LD_PRELOAD", value); - continue; - } - if(key == "GAME_LIBRARY_PATH") - { - env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); - continue; - } -#endif - // qDebug() << "Env: " << key << value; - env.insert(key, value); - } -#ifdef Q_OS_LINUX - // HACK: Workaround for QTBUG42500 - if(!env.contains("LD_LIBRARY_PATH")) - { - env.insert("LD_LIBRARY_PATH", ""); - } -#endif - - return env; -} - -JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch) -{ - JavaInstallPtr javaVersion(new JavaInstall()); - - javaVersion->id = id; - javaVersion->arch = arch; - javaVersion->path = path; - - return javaVersion; -} - -JavaInstallPtr JavaUtils::GetDefaultJava() -{ - JavaInstallPtr javaVersion(new JavaInstall()); - - javaVersion->id = "java"; - javaVersion->arch = "unknown"; -#if defined(Q_OS_WIN32) - javaVersion->path = "javaw"; -#else - javaVersion->path = "java"; -#endif - - return javaVersion; -} - -#if defined(Q_OS_WIN32) -QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName, QString keyJavaDir, QString subkeySuffix) -{ - QList<JavaInstallPtr> javas; - - QString archType = "unknown"; - if (keyType == KEY_WOW64_64KEY) - archType = "64"; - else if (keyType == KEY_WOW64_32KEY) - archType = "32"; - - HKEY jreKey; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0, - KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS) - { - // Read the current type version from the registry. - // This will be used to find any key that contains the JavaHome value. - char *value = new char[0]; - DWORD valueSz = 0; - if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) == - ERROR_MORE_DATA) - { - value = new char[valueSz]; - RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz); - } - - TCHAR subKeyName[255]; - DWORD subKeyNameSize, numSubKeys, retCode; - - // Get the number of subkeys - RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, - NULL, NULL); - - // Iterate until RegEnumKeyEx fails - if (numSubKeys > 0) - { - for (DWORD i = 0; i < numSubKeys; i++) - { - subKeyNameSize = 255; - retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, - NULL); - if (retCode == ERROR_SUCCESS) - { - // Now open the registry key for the version that we just got. - QString newKeyName = keyName + "\\" + subKeyName + subkeySuffix; - - HKEY newKey; - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, - KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) - { - // Read the JavaHome value to find where Java is installed. - value = new char[0]; - valueSz = 0; - if (RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value, - &valueSz) == ERROR_MORE_DATA) - { - value = new char[valueSz]; - RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value, - &valueSz); - - // Now, we construct the version object and add it to the list. - JavaInstallPtr javaVersion(new JavaInstall()); - - javaVersion->id = subKeyName; - javaVersion->arch = archType; - javaVersion->path = - QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe"); - javas.append(javaVersion); - } - - RegCloseKey(newKey); - } - } - } - } - - RegCloseKey(jreKey); - } - - return javas; -} - -QList<QString> JavaUtils::FindJavaPaths() -{ - QList<JavaInstallPtr> java_candidates; - - // Oracle - QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment", "JavaHome"); - QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit", "JavaHome"); - QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment", "JavaHome"); - QList<JavaInstallPtr> JDK32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit", "JavaHome"); - - // Oracle for Java 9 and newer - QList<JavaInstallPtr> NEWJRE64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\JRE", "JavaHome"); - QList<JavaInstallPtr> NEWJDK64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\JDK", "JavaHome"); - QList<JavaInstallPtr> NEWJRE32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\JRE", "JavaHome"); - QList<JavaInstallPtr> NEWJDK32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\JDK", "JavaHome"); - - // AdoptOpenJDK - QList<JavaInstallPtr> ADOPTOPENJRE32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\AdoptOpenJDK\\JRE", "Path", "\\hotspot\\MSI"); - QList<JavaInstallPtr> ADOPTOPENJRE64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\AdoptOpenJDK\\JRE", "Path", "\\hotspot\\MSI"); - QList<JavaInstallPtr> ADOPTOPENJDK32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\AdoptOpenJDK\\JDK", "Path", "\\hotspot\\MSI"); - QList<JavaInstallPtr> ADOPTOPENJDK64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\AdoptOpenJDK\\JDK", "Path", "\\hotspot\\MSI"); - - // Microsoft - QList<JavaInstallPtr> MICROSOFTJDK64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\Microsoft\\JDK", "Path", "\\hotspot\\MSI"); - - // Azul Zulu - QList<JavaInstallPtr> ZULU64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\Azul Systems\\Zulu", "InstallationPath"); - QList<JavaInstallPtr> ZULU32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\Azul Systems\\Zulu", "InstallationPath"); - - // BellSoft Liberica - QList<JavaInstallPtr> LIBERICA64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\BellSoft\\Liberica", "InstallationPath"); - QList<JavaInstallPtr> LIBERICA32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\BellSoft\\Liberica", "InstallationPath"); - - // List x64 before x86 - java_candidates.append(JRE64s); - java_candidates.append(NEWJRE64s); - java_candidates.append(ADOPTOPENJRE64s); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe")); - java_candidates.append(JDK64s); - java_candidates.append(NEWJDK64s); - java_candidates.append(ADOPTOPENJDK64s); - java_candidates.append(MICROSOFTJDK64s); - java_candidates.append(ZULU64s); - java_candidates.append(LIBERICA64s); - - java_candidates.append(JRE32s); - java_candidates.append(NEWJRE32s); - java_candidates.append(ADOPTOPENJRE32s); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe")); - java_candidates.append(JDK32s); - java_candidates.append(NEWJDK32s); - java_candidates.append(ADOPTOPENJDK32s); - java_candidates.append(ZULU32s); - java_candidates.append(LIBERICA32s); - - java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); - - QList<QString> candidates; - for(JavaInstallPtr java_candidate : java_candidates) - { - if(!candidates.contains(java_candidate->path)) - { - candidates.append(java_candidate->path); - } - } - - return candidates; -} - -#elif defined(Q_OS_MAC) -QList<QString> JavaUtils::FindJavaPaths() -{ - QList<QString> javas; - javas.append(this->GetDefaultJava()->path); - javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"); - javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"); - javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java"); - QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/"); - QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString &java, libraryJVMJavas) { - javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); - javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java"); - } - QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/"); - QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString &java, systemLibraryJVMJavas) { - javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); - javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); - } - return javas; -} - -#elif defined(Q_OS_LINUX) -QList<QString> JavaUtils::FindJavaPaths() -{ - qDebug() << "Linux Java detection incomplete - defaulting to \"java\""; - - QList<QString> javas; - javas.append(this->GetDefaultJava()->path); - auto scanJavaDir = [&](const QString & dirPath) - { - QDir dir(dirPath); - if(!dir.exists()) - return; - auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); - for(auto & entry: entries) - { - - QString prefix; - if(entry.isAbsolute()) - { - prefix = entry.absoluteFilePath(); - } - else - { - prefix = entry.filePath(); - } - - javas.append(FS::PathCombine(prefix, "jre/bin/java")); - javas.append(FS::PathCombine(prefix, "bin/java")); - } - }; - // oracle RPMs - scanJavaDir("/usr/java"); - // general locations used by distro packaging - scanJavaDir("/usr/lib/jvm"); - scanJavaDir("/usr/lib32/jvm"); - // javas stored in MultiMC's folder - scanJavaDir("java"); - // manually installed JDKs in /opt - scanJavaDir("/opt/jdk"); - scanJavaDir("/opt/jdks"); - return javas; -} -#else -QList<QString> JavaUtils::FindJavaPaths() -{ - qDebug() << "Unknown operating system build - defaulting to \"java\""; - - QList<QString> javas; - javas.append(this->GetDefaultJava()->path); - - return javas; -} -#endif diff --git a/api/logic/java/JavaUtils.h b/api/logic/java/JavaUtils.h deleted file mode 100644 index 206acf89..00000000 --- a/api/logic/java/JavaUtils.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QStringList> - -#include "JavaChecker.h" -#include "JavaInstallList.h" - -#ifdef Q_OS_WIN -#include <windows.h> -#endif - -#include "multimc_logic_export.h" - -QProcessEnvironment CleanEnviroment(); - -class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject -{ - Q_OBJECT -public: - JavaUtils(); - - JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown"); - QList<QString> FindJavaPaths(); - JavaInstallPtr GetDefaultJava(); - -#ifdef Q_OS_WIN - QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName, QString keyJavaDir, QString subkeySuffix = ""); -#endif -}; diff --git a/api/logic/java/JavaVersion.cpp b/api/logic/java/JavaVersion.cpp deleted file mode 100644 index 179ccd8d..00000000 --- a/api/logic/java/JavaVersion.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "JavaVersion.h" -#include <MMCStrings.h> - -#include <QRegularExpression> -#include <QString> - -JavaVersion & JavaVersion::operator=(const QString & javaVersionString) -{ - m_string = javaVersionString; - - auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int - { - auto str = match.captured(what); - if(str.isEmpty()) - { - return 0; - } - return str.toInt(); - }; - - QRegularExpression pattern; - if(javaVersionString.startsWith("1.")) - { - pattern = QRegularExpression ("1[.](?<major>[0-9]+)([.](?<minor>[0-9]+))?(_(?<security>[0-9]+)?)?(-(?<prerelease>[a-zA-Z0-9]+))?"); - } - else - { - pattern = QRegularExpression("(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?"); - } - - auto match = pattern.match(m_string); - m_parseable = match.hasMatch(); - m_major = getCapturedInteger(match, "major"); - m_minor = getCapturedInteger(match, "minor"); - m_security = getCapturedInteger(match, "security"); - m_prerelease = match.captured("prerelease"); - return *this; -} - -JavaVersion::JavaVersion(const QString &rhs) -{ - operator=(rhs); -} - -QString JavaVersion::toString() -{ - return m_string; -} - -bool JavaVersion::requiresPermGen() -{ - if(m_parseable) - { - return m_major < 8; - } - return true; -} - -bool JavaVersion::operator<(const JavaVersion &rhs) -{ - if(m_parseable && rhs.m_parseable) - { - auto major = m_major; - auto rmajor = rhs.m_major; - - // HACK: discourage using java 9 - if(major > 8) - major = -major; - if(rmajor > 8) - rmajor = -rmajor; - - if(major < rmajor) - return true; - if(major > rmajor) - return false; - if(m_minor < rhs.m_minor) - return true; - if(m_minor > rhs.m_minor) - return false; - if(m_security < rhs.m_security) - return true; - if(m_security > rhs.m_security) - return false; - - // everything else being equal, consider prerelease status - bool thisPre = !m_prerelease.isEmpty(); - bool rhsPre = !rhs.m_prerelease.isEmpty(); - if(thisPre && !rhsPre) - { - // this is a prerelease and the other one isn't -> lesser - return true; - } - else if(!thisPre && rhsPre) - { - // this isn't a prerelease and the other one is -> greater - return false; - } - else if(thisPre && rhsPre) - { - // both are prereleases - use natural compare... - return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0; - } - // neither is prerelease, so they are the same -> this cannot be less than rhs - return false; - } - else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; -} - -bool JavaVersion::operator==(const JavaVersion &rhs) -{ - if(m_parseable && rhs.m_parseable) - { - return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease; - } - return m_string == rhs.m_string; -} - -bool JavaVersion::operator>(const JavaVersion &rhs) -{ - return (!operator<(rhs)) && (!operator==(rhs)); -} diff --git a/api/logic/java/JavaVersion.h b/api/logic/java/JavaVersion.h deleted file mode 100644 index 8589c21a..00000000 --- a/api/logic/java/JavaVersion.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include "multimc_logic_export.h" -#include <QString> - -// NOTE: apparently the GNU C library pollutes the global namespace with these... undef them. -#ifdef major - #undef major -#endif -#ifdef minor - #undef minor -#endif - -class MULTIMC_LOGIC_EXPORT JavaVersion -{ - friend class JavaVersionTest; -public: - JavaVersion() {}; - JavaVersion(const QString & rhs); - - JavaVersion & operator=(const QString & rhs); - - bool operator<(const JavaVersion & rhs); - bool operator==(const JavaVersion & rhs); - bool operator>(const JavaVersion & rhs); - - bool requiresPermGen(); - - QString toString(); - - int major() - { - return m_major; - } - int minor() - { - return m_minor; - } - int security() - { - return m_security; - } -private: - QString m_string; - int m_major = 0; - int m_minor = 0; - int m_security = 0; - bool m_parseable = false; - QString m_prerelease; -}; diff --git a/api/logic/java/JavaVersion_test.cpp b/api/logic/java/JavaVersion_test.cpp deleted file mode 100644 index 10ae13a7..00000000 --- a/api/logic/java/JavaVersion_test.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "java/JavaVersion.h" - -class JavaVersionTest : public QObject -{ - Q_OBJECT -private -slots: - void test_Parse_data() - { - QTest::addColumn<QString>("string"); - QTest::addColumn<int>("major"); - QTest::addColumn<int>("minor"); - QTest::addColumn<int>("security"); - QTest::addColumn<QString>("prerelease"); - - QTest::newRow("old format") << "1.6.0_33" << 6 << 0 << 33 << QString(); - QTest::newRow("old format prerelease") << "1.9.0_1-ea" << 9 << 0 << 1 << "ea"; - - QTest::newRow("new format major") << "9" << 9 << 0 << 0 << QString(); - QTest::newRow("new format minor") << "9.1" << 9 << 1 << 0 << QString(); - QTest::newRow("new format security") << "9.0.1" << 9 << 0 << 1 << QString(); - QTest::newRow("new format prerelease") << "9-ea" << 9 << 0 << 0 << "ea"; - QTest::newRow("new format long prerelease") << "9.0.1-ea" << 9 << 0 << 1 << "ea"; - } - void test_Parse() - { - QFETCH(QString, string); - QFETCH(int, major); - QFETCH(int, minor); - QFETCH(int, security); - QFETCH(QString, prerelease); - - JavaVersion test(string); - QCOMPARE(test.m_string, string); - QCOMPARE(test.toString(), string); - QCOMPARE(test.m_major, major); - QCOMPARE(test.m_minor, minor); - QCOMPARE(test.m_security, security); - QCOMPARE(test.m_prerelease, prerelease); - } - - void test_Sort_data() - { - QTest::addColumn<QString>("lhs"); - QTest::addColumn<QString>("rhs"); - QTest::addColumn<bool>("smaller"); - QTest::addColumn<bool>("equal"); - QTest::addColumn<bool>("bigger"); - - // old format and new format equivalence - QTest::newRow("1.6.0_33 == 6.0.33") << "1.6.0_33" << "6.0.33" << false << true << false; - // old format major version - QTest::newRow("1.5.0_33 < 1.6.0_33") << "1.5.0_33" << "1.6.0_33" << true << false << false; - // new format - first release vs first security patch - QTest::newRow("9 < 9.0.1") << "9" << "9.0.1" << true << false << false; - QTest::newRow("9.0.1 > 9") << "9.0.1" << "9" << false << false << true; - // new format - first minor vs first release/security patch - QTest::newRow("9.1 > 9.0.1") << "9.1" << "9.0.1" << false << false << true; - QTest::newRow("9.0.1 < 9.1") << "9.0.1" << "9.1" << true << false << false; - QTest::newRow("9.1 > 9") << "9.1" << "9" << false << false << true; - QTest::newRow("9 > 9.1") << "9" << "9.1" << true << false << false; - // new format - omitted numbers - QTest::newRow("9 == 9.0") << "9" << "9.0" << false << true << false; - QTest::newRow("9 == 9.0.0") << "9" << "9.0.0" << false << true << false; - QTest::newRow("9.0 == 9.0.0") << "9.0" << "9.0.0" << false << true << false; - // early access and prereleases compared to final release - QTest::newRow("9-ea < 9") << "9-ea" << "9" << true << false << false; - QTest::newRow("9 < 9.0.1-ea") << "9" << "9.0.1-ea" << true << false << false; - QTest::newRow("9.0.1-ea > 9") << "9.0.1-ea" << "9" << false << false << true; - // prerelease difference only testing - QTest::newRow("9-1 == 9-1") << "9-1" << "9-1" << false << true << false; - QTest::newRow("9-1 < 9-2") << "9-1" << "9-2" << true << false << false; - QTest::newRow("9-5 < 9-20") << "9-5" << "9-20" << true << false << false; - QTest::newRow("9-rc1 < 9-rc2") << "9-rc1" << "9-rc2" << true << false << false; - QTest::newRow("9-rc5 < 9-rc20") << "9-rc5" << "9-rc20" << true << false << false; - QTest::newRow("9-rc < 9-rc2") << "9-rc" << "9-rc2" << true << false << false; - QTest::newRow("9-ea < 9-rc") << "9-ea" << "9-rc" << true << false << false; - } - void test_Sort() - { - QFETCH(QString, lhs); - QFETCH(QString, rhs); - QFETCH(bool, smaller); - QFETCH(bool, equal); - QFETCH(bool, bigger); - JavaVersion lver(lhs); - JavaVersion rver(rhs); - QCOMPARE(lver < rver, smaller); - QCOMPARE(lver == rver, equal); - QCOMPARE(lver > rver, bigger); - } - void test_PermGen_data() - { - QTest::addColumn<QString>("version"); - QTest::addColumn<bool>("needs_permgen"); - QTest::newRow("1.6.0_33") << "1.6.0_33" << true; - QTest::newRow("1.7.0_60") << "1.7.0_60" << true; - QTest::newRow("1.8.0_22") << "1.8.0_22" << false; - QTest::newRow("9-ea") << "9-ea" << false; - QTest::newRow("9.2.4") << "9.2.4" << false; - } - void test_PermGen() - { - QFETCH(QString, version); - QFETCH(bool, needs_permgen); - JavaVersion v(version); - QCOMPARE(needs_permgen, v.requiresPermGen()); - } -}; - -QTEST_GUILESS_MAIN(JavaVersionTest) - -#include "JavaVersion_test.moc" diff --git a/api/logic/java/launch/CheckJava.cpp b/api/logic/java/launch/CheckJava.cpp deleted file mode 100644 index f58602f0..00000000 --- a/api/logic/java/launch/CheckJava.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "CheckJava.h" -#include <launch/LaunchTask.h> -#include <FileSystem.h> -#include <QStandardPaths> -#include <QFileInfo> -#include <sys.h> - -void CheckJava::executeTask() -{ - auto instance = m_parent->instance(); - auto settings = instance->settings(); - m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString()); - bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool(); - - auto realJavaPath = QStandardPaths::findExecutable(m_javaPath); - if (realJavaPath.isEmpty()) - { - if (perInstance) - { - emit logLine( - QString("The java binary \"%1\" couldn't be found. Please fix the java path " - "override in the instance's settings or disable it.").arg(m_javaPath), - MessageLevel::Warning); - } - else - { - emit logLine(QString("The java binary \"%1\" couldn't be found. Please set up java in " - "the settings.").arg(m_javaPath), - MessageLevel::Warning); - } - emitFailed(QString("Java path is not valid.")); - return; - } - else - { - emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC); - } - - QFileInfo javaInfo(realJavaPath); - qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); - auto storedUnixTime = settings->get("JavaTimestamp").toLongLong(); - auto storedArchitecture = settings->get("JavaArchitecture").toString(); - auto storedVersion = settings->get("JavaVersion").toString(); - auto storedVendor = settings->get("JavaVendor").toString(); - m_javaUnixTime = javaUnixTime; - // if timestamps are not the same, or something is missing, check! - if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0 || storedVendor.size() == 0) - { - m_JavaChecker = new JavaChecker(); - emit logLine(QString("Checking Java version..."), MessageLevel::MultiMC); - connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); - m_JavaChecker->m_path = realJavaPath; - m_JavaChecker->performCheck(); - return; - } - else - { - auto verString = instance->settings()->get("JavaVersion").toString(); - auto archString = instance->settings()->get("JavaArchitecture").toString(); - auto vendorString = instance->settings()->get("JavaVendor").toString(); - printJavaInfo(verString, archString, vendorString); - } - emitSucceeded(); -} - -void CheckJava::checkJavaFinished(JavaCheckResult result) -{ - switch (result.validity) - { - case JavaCheckResult::Validity::Errored: - { - // Error message displayed if java can't start - emit logLine(QString("Could not start java:"), MessageLevel::Error); - emit logLines(result.errorLog.split('\n'), MessageLevel::Error); - emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC); - printSystemInfo(false, false); - emitFailed(QString("Could not start java!")); - return; - } - case JavaCheckResult::Validity::ReturnedInvalidData: - { - emit logLine(QString("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error); - emit logLines(result.outLog.split('\n'), MessageLevel::Warning); - emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC); - printSystemInfo(false, false); - emitSucceeded(); - return; - } - case JavaCheckResult::Validity::Valid: - { - auto instance = m_parent->instance(); - printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.javaVendor); - instance->settings()->set("JavaVersion", result.javaVersion.toString()); - instance->settings()->set("JavaArchitecture", result.mojangPlatform); - instance->settings()->set("JavaVendor", result.javaVendor); - instance->settings()->set("JavaTimestamp", m_javaUnixTime); - emitSucceeded(); - return; - } - } -} - -void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString & vendor) -{ - emit logLine(QString("Java is version %1, using %2-bit architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::MultiMC); - printSystemInfo(true, architecture == "64"); -} - -void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit) -{ - auto cpu64 = Sys::isCPU64bit(); - auto system64 = Sys::isSystem64bit(); - if(cpu64 != system64) - { - emit logLine(QString("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error); - } - if(javaIsKnown) - { - if(javaIs64bit != system64) - { - emit logLine(QString("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error); - } - } -} diff --git a/api/logic/java/launch/CheckJava.h b/api/logic/java/launch/CheckJava.h deleted file mode 100644 index 68cd618b..00000000 --- a/api/logic/java/launch/CheckJava.h +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <LoggedProcess.h> -#include <java/JavaChecker.h> - -class CheckJava: public LaunchStep -{ - Q_OBJECT -public: - explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){}; - virtual ~CheckJava() {}; - - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } -private slots: - void checkJavaFinished(JavaCheckResult result); - -private: - void printJavaInfo(const QString & version, const QString & architecture, const QString & vendor); - void printSystemInfo(bool javaIsKnown, bool javaIs64bit); - -private: - QString m_javaPath; - qlonglong m_javaUnixTime; - JavaCheckerPtr m_JavaChecker; -}; diff --git a/api/logic/launch/LaunchStep.cpp b/api/logic/launch/LaunchStep.cpp deleted file mode 100644 index d6bb6e88..00000000 --- a/api/logic/launch/LaunchStep.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "LaunchStep.h" -#include "LaunchTask.h" - -void LaunchStep::bind(LaunchTask *parent) -{ - m_parent = parent; - connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); - connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); - connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines); - connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished); - connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested); -} diff --git a/api/logic/launch/LaunchStep.h b/api/logic/launch/LaunchStep.h deleted file mode 100644 index 3939f960..00000000 --- a/api/logic/launch/LaunchStep.h +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "tasks/Task.h" -#include "MessageLevel.h" - -#include <QStringList> - -class LaunchTask; -class LaunchStep: public Task -{ - Q_OBJECT -public: /* methods */ - explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent) - { - bind(parent); - }; - virtual ~LaunchStep() {}; - -private: /* methods */ - void bind(LaunchTask *parent); - -signals: - void logLines(QStringList lines, MessageLevel::Enum level); - void logLine(QString line, MessageLevel::Enum level); - void readyForLaunch(); - void progressReportingRequest(); - -public slots: - virtual void proceed() {}; - // called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends - virtual void finalize() {}; - -protected: /* data */ - LaunchTask *m_parent; -}; diff --git a/api/logic/launch/LaunchTask.cpp b/api/logic/launch/LaunchTask.cpp deleted file mode 100644 index e6f6bbac..00000000 --- a/api/logic/launch/LaunchTask.cpp +++ /dev/null @@ -1,280 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Authors: Orochimarufan <orochimarufan.x3@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 "launch/LaunchTask.h" -#include "MessageLevel.h" -#include "MMCStrings.h" -#include "java/JavaChecker.h" -#include "tasks/Task.h" -#include <QDebug> -#include <QDir> -#include <QEventLoop> -#include <QRegularExpression> -#include <QCoreApplication> -#include <QStandardPaths> -#include <assert.h> - -void LaunchTask::init() -{ - m_instance->setRunning(true); -} - -shared_qobject_ptr<LaunchTask> LaunchTask::create(InstancePtr inst) -{ - shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst)); - proc->init(); - return proc; -} - -LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance) -{ -} - -void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step) -{ - m_steps.append(step); -} - -void LaunchTask::prependStep(shared_qobject_ptr<LaunchStep> step) -{ - m_steps.prepend(step); -} - -void LaunchTask::executeTask() -{ - m_instance->setCrashed(false); - if(!m_steps.size()) - { - state = LaunchTask::Finished; - emitSucceeded(); - } - state = LaunchTask::Running; - onStepFinished(); -} - -void LaunchTask::onReadyForLaunch() -{ - state = LaunchTask::Waiting; - emit readyForLaunch(); -} - -void LaunchTask::onStepFinished() -{ - // initial -> just start the first step - if(currentStep == -1) - { - currentStep ++; - m_steps[currentStep]->start(); - return; - } - - auto step = m_steps[currentStep]; - if(step->wasSuccessful()) - { - // end? - if(currentStep == m_steps.size() - 1) - { - finalizeSteps(true, QString()); - } - else - { - currentStep ++; - step = m_steps[currentStep]; - step->start(); - } - } - else - { - finalizeSteps(false, step->failReason()); - } -} - -void LaunchTask::finalizeSteps(bool successful, const QString& error) -{ - for(auto step = currentStep; step >= 0; step--) - { - m_steps[step]->finalize(); - } - if(successful) - { - emitSucceeded(); - } - else - { - emitFailed(error); - } -} - -void LaunchTask::onProgressReportingRequested() -{ - state = LaunchTask::Waiting; - emit requestProgress(m_steps[currentStep].get()); -} - -void LaunchTask::setCensorFilter(QMap<QString, QString> filter) -{ - m_censorFilter = filter; -} - -QString LaunchTask::censorPrivateInfo(QString in) -{ - auto iter = m_censorFilter.begin(); - while (iter != m_censorFilter.end()) - { - in.replace(iter.key(), iter.value()); - iter++; - } - return in; -} - -void LaunchTask::proceed() -{ - if(state != LaunchTask::Waiting) - { - return; - } - m_steps[currentStep]->proceed(); -} - -bool LaunchTask::canAbort() const -{ - switch(state) - { - case LaunchTask::Aborted: - case LaunchTask::Failed: - case LaunchTask::Finished: - return false; - case LaunchTask::NotStarted: - return true; - case LaunchTask::Running: - case LaunchTask::Waiting: - { - auto step = m_steps[currentStep]; - return step->canAbort(); - } - } - return false; -} - -bool LaunchTask::abort() -{ - switch(state) - { - case LaunchTask::Aborted: - case LaunchTask::Failed: - case LaunchTask::Finished: - return true; - case LaunchTask::NotStarted: - { - state = LaunchTask::Aborted; - emitFailed("Aborted"); - return true; - } - case LaunchTask::Running: - case LaunchTask::Waiting: - { - auto step = m_steps[currentStep]; - if(!step->canAbort()) - { - return false; - } - if(step->abort()) - { - state = LaunchTask::Aborted; - return true; - } - } - default: - break; - } - return false; -} - -shared_qobject_ptr<LogModel> LaunchTask::getLogModel() -{ - if(!m_logModel) - { - m_logModel.reset(new LogModel()); - m_logModel->setMaxLines(m_instance->getConsoleMaxLines()); - m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow()); - // FIXME: should this really be here? - m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n" - "You may have to fix your mods because the game is still logging to files and" - " likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines())); - } - return m_logModel; -} - -void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel) -{ - for (auto & line: lines) - { - onLogLine(line, defaultLevel); - } -} - -void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) -{ - // if the launcher part set a log level, use it - auto innerLevel = MessageLevel::fromLine(line); - if(innerLevel != MessageLevel::Unknown) - { - level = innerLevel; - } - - // If the level is still undetermined, guess level - if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) - { - level = m_instance->guessLevel(line, level); - } - - // censor private user info - line = censorPrivateInfo(line); - - auto &model = *getLogModel(); - model.append(level, line); -} - -void LaunchTask::emitSucceeded() -{ - m_instance->setRunning(false); - Task::emitSucceeded(); -} - -void LaunchTask::emitFailed(QString reason) -{ - m_instance->setRunning(false); - m_instance->setCrashed(true); - Task::emitFailed(reason); -} - -QString LaunchTask::substituteVariables(const QString &cmd) const -{ - QString out = cmd; - auto variables = m_instance->getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - out.replace("$" + it.key(), it.value()); - } - auto env = QProcessEnvironment::systemEnvironment(); - for (auto var : env.keys()) - { - out.replace("$" + var, env.value(var)); - } - return out; -} - diff --git a/api/logic/launch/LaunchTask.h b/api/logic/launch/LaunchTask.h deleted file mode 100644 index 2be59c83..00000000 --- a/api/logic/launch/LaunchTask.h +++ /dev/null @@ -1,125 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Authors: Orochimarufan <orochimarufan.x3@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 -#include <QProcess> -#include <QObjectPtr.h> -#include "LogModel.h" -#include "BaseInstance.h" -#include "MessageLevel.h" -#include "LoggedProcess.h" -#include "LaunchStep.h" - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT LaunchTask: public Task -{ - Q_OBJECT -protected: - explicit LaunchTask(InstancePtr instance); - void init(); - -public: - enum State - { - NotStarted, - Running, - Waiting, - Failed, - Aborted, - Finished - }; - -public: /* methods */ - static shared_qobject_ptr<LaunchTask> create(InstancePtr inst); - virtual ~LaunchTask() {}; - - void appendStep(shared_qobject_ptr<LaunchStep> step); - void prependStep(shared_qobject_ptr<LaunchStep> step); - void setCensorFilter(QMap<QString, QString> filter); - - InstancePtr instance() - { - return m_instance; - } - - void setPid(qint64 pid) - { - m_pid = pid; - } - - qint64 pid() - { - return m_pid; - } - - /** - * @brief prepare the process for launch (for multi-stage launch) - */ - virtual void executeTask() override; - - /** - * @brief launch the armed instance - */ - void proceed(); - - /** - * @brief abort launch - */ - bool abort() override; - - bool canAbort() const override; - - shared_qobject_ptr<LogModel> getLogModel(); - -public: - QString substituteVariables(const QString &cmd) const; - QString censorPrivateInfo(QString in); - -protected: /* methods */ - virtual void emitFailed(QString reason) override; - virtual void emitSucceeded() override; - -signals: - /** - * @brief emitted when the launch preparations are done - */ - void readyForLaunch(); - - void requestProgress(Task *task); - - void requestLogging(); - -public slots: - void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); - void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); - void onReadyForLaunch(); - void onStepFinished(); - void onProgressReportingRequested(); - -private: /*methods */ - void finalizeSteps(bool successful, const QString & error); - -protected: /* data */ - InstancePtr m_instance; - shared_qobject_ptr<LogModel> m_logModel; - QList <shared_qobject_ptr<LaunchStep>> m_steps; - QMap<QString, QString> m_censorFilter; - int currentStep = -1; - State state = NotStarted; - qint64 m_pid = -1; -}; diff --git a/api/logic/launch/LogModel.cpp b/api/logic/launch/LogModel.cpp deleted file mode 100644 index 92f9487a..00000000 --- a/api/logic/launch/LogModel.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "LogModel.h" - -LogModel::LogModel(QObject *parent):QAbstractListModel(parent) -{ - m_content.resize(m_maxLines); -} - -int LogModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - - return m_numLines; -} - -QVariant LogModel::data(const QModelIndex &index, int role) const -{ - if (index.row() < 0 || index.row() >= m_numLines) - return QVariant(); - - auto row = index.row(); - auto realRow = (row + m_firstLine) % m_maxLines; - if (role == Qt::DisplayRole || role == Qt::EditRole) - { - return m_content[realRow].line; - } - if(role == LevelRole) - { - return m_content[realRow].level; - } - - return QVariant(); -} - -void LogModel::append(MessageLevel::Enum level, QString line) -{ - if(m_suspended) - { - return; - } - int lineNum = (m_firstLine + m_numLines) % m_maxLines; - // overflow - if(m_numLines == m_maxLines) - { - if(m_stopOnOverflow) - { - // nothing more to do, the buffer is full - return; - } - beginRemoveRows(QModelIndex(), 0, 0); - m_firstLine = (m_firstLine + 1) % m_maxLines; - m_numLines --; - endRemoveRows(); - } - else if (m_numLines == m_maxLines - 1 && m_stopOnOverflow) - { - level = MessageLevel::Fatal; - line = m_overflowMessage; - } - beginInsertRows(QModelIndex(), m_numLines, m_numLines); - m_numLines ++; - m_content[lineNum].level = level; - m_content[lineNum].line = line; - endInsertRows(); -} - -void LogModel::suspend(bool suspend) -{ - m_suspended = suspend; -} - -bool LogModel::suspended() -{ - return m_suspended; -} - -void LogModel::clear() -{ - beginResetModel(); - m_firstLine = 0; - m_numLines = 0; - endResetModel(); -} - -QString LogModel::toPlainText() -{ - QString out; - out.reserve(m_numLines * 80); - for(int i = 0; i < m_numLines; i++) - { - QString & line = m_content[(m_firstLine + i) % m_maxLines].line; - out.append(line + '\n'); - } - out.squeeze(); - return out; -} - -void LogModel::setMaxLines(int maxLines) -{ - // no-op - if(maxLines == m_maxLines) - { - return; - } - // if it all still fits in the buffer, just resize it - if(m_firstLine + m_numLines < m_maxLines) - { - m_maxLines = maxLines; - m_content.resize(maxLines); - return; - } - // otherwise, we need to reorganize the data because it crosses the wrap boundary - QVector<entry> newContent; - newContent.resize(maxLines); - if(m_numLines <= maxLines) - { - // if it all fits in the new buffer, just copy it over - for(int i = 0; i < m_numLines; i++) - { - newContent[i] = m_content[(m_firstLine + i) % m_maxLines]; - } - m_content.swap(newContent); - } - else - { - // if it doesn't fit, part of the data needs to be thrown away (the oldest log messages) - int lead = m_numLines - maxLines; - beginRemoveRows(QModelIndex(), 0, lead - 1); - for(int i = 0; i < maxLines; i++) - { - newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines]; - } - m_numLines = m_maxLines; - m_content.swap(newContent); - endRemoveRows(); - } - m_firstLine = 0; - m_maxLines = maxLines; -} - -int LogModel::getMaxLines() -{ - return m_maxLines; -} - -void LogModel::setStopOnOverflow(bool stop) -{ - m_stopOnOverflow = stop; -} - -void LogModel::setOverflowMessage(const QString& overflowMessage) -{ - m_overflowMessage = overflowMessage; -} - -void LogModel::setLineWrap(bool state) -{ - if(m_lineWrap != state) - { - m_lineWrap = state; - } -} - -bool LogModel::wrapLines() const -{ - return m_lineWrap; -} diff --git a/api/logic/launch/LogModel.h b/api/logic/launch/LogModel.h deleted file mode 100644 index bccceaef..00000000 --- a/api/logic/launch/LogModel.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include <QAbstractListModel> -#include <QString> -#include "MessageLevel.h" - -#include <multimc_logic_export.h> - -class MULTIMC_LOGIC_EXPORT LogModel : public QAbstractListModel -{ - Q_OBJECT -public: - explicit LogModel(QObject *parent = 0); - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - - void append(MessageLevel::Enum, QString line); - void clear(); - - void suspend(bool suspend); - bool suspended(); - - QString toPlainText(); - - int getMaxLines(); - void setMaxLines(int maxLines); - void setStopOnOverflow(bool stop); - void setOverflowMessage(const QString & overflowMessage); - - void setLineWrap(bool state); - bool wrapLines() const; - - enum Roles - { - LevelRole = Qt::UserRole - }; - -private /* types */: - struct entry - { - MessageLevel::Enum level; - QString line; - }; - -private: /* data */ - QVector <entry> m_content; - int m_maxLines = 1000; - // first line in the circular buffer - int m_firstLine = 0; - // number of lines occupied in the circular buffer - int m_numLines = 0; - bool m_stopOnOverflow = false; - QString m_overflowMessage = "OVERFLOW"; - bool m_suspended = false; - bool m_lineWrap = true; - -private: - Q_DISABLE_COPY(LogModel) -}; diff --git a/api/logic/launch/steps/LookupServerAddress.cpp b/api/logic/launch/steps/LookupServerAddress.cpp deleted file mode 100644 index de56c28a..00000000 --- a/api/logic/launch/steps/LookupServerAddress.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "LookupServerAddress.h" - -#include <launch/LaunchTask.h> - -LookupServerAddress::LookupServerAddress(LaunchTask *parent) : - LaunchStep(parent), m_dnsLookup(new QDnsLookup(this)) -{ - connect(m_dnsLookup, &QDnsLookup::finished, this, &LookupServerAddress::on_dnsLookupFinished); - - m_dnsLookup->setType(QDnsLookup::SRV); -} - -void LookupServerAddress::setLookupAddress(const QString &lookupAddress) -{ - m_lookupAddress = lookupAddress; - m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress)); -} - -void LookupServerAddress::setOutputAddressPtr(MinecraftServerTargetPtr output) -{ - m_output = std::move(output); -} - -bool LookupServerAddress::abort() -{ - m_dnsLookup->abort(); - emitFailed("Aborted"); - return true; -} - -void LookupServerAddress::executeTask() -{ - m_dnsLookup->lookup(); -} - -void LookupServerAddress::on_dnsLookupFinished() -{ - if (isFinished()) - { - // Aborted - return; - } - - if (m_dnsLookup->error() != QDnsLookup::NoError) - { - emit logLine(QString("Failed to resolve server address (this is NOT an error!) %1: %2\n") - .arg(m_dnsLookup->name(), m_dnsLookup->errorString()), MessageLevel::MultiMC); - resolve(m_lookupAddress, 25565); // Technically the task failed, however, we don't abort the launch - // and leave it up to minecraft to fail (or maybe not) when connecting - return; - } - - const auto records = m_dnsLookup->serviceRecords(); - if (records.empty()) - { - emit logLine( - QString("Failed to resolve server address %1: the DNS lookup succeeded, but no records were returned.\n") - .arg(m_dnsLookup->name()), MessageLevel::Warning); - resolve(m_lookupAddress, 25565); // Technically the task failed, however, we don't abort the launch - // and leave it up to minecraft to fail (or maybe not) when connecting - return; - } - - const auto &firstRecord = records.at(0); - quint16 port = firstRecord.port(); - - emit logLine(QString("Resolved server address %1 to %2 with port %3\n").arg( - m_dnsLookup->name(), firstRecord.target(), QString::number(port)),MessageLevel::MultiMC); - resolve(firstRecord.target(), port); -} - -void LookupServerAddress::resolve(const QString &address, quint16 port) -{ - m_output->address = address; - m_output->port = port; - - emitSucceeded(); - m_dnsLookup->deleteLater(); -} diff --git a/api/logic/launch/steps/LookupServerAddress.h b/api/logic/launch/steps/LookupServerAddress.h deleted file mode 100644 index 5a5c3de1..00000000 --- a/api/logic/launch/steps/LookupServerAddress.h +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <QObjectPtr.h> -#include <QDnsLookup> - -#include "minecraft/launch/MinecraftServerTarget.h" - -class LookupServerAddress: public LaunchStep { -Q_OBJECT -public: - explicit LookupServerAddress(LaunchTask *parent); - virtual ~LookupServerAddress() {}; - - virtual void executeTask(); - virtual bool abort(); - virtual bool canAbort() const - { - return true; - } - - void setLookupAddress(const QString &lookupAddress); - void setOutputAddressPtr(MinecraftServerTargetPtr output); - -private slots: - void on_dnsLookupFinished(); - -private: - void resolve(const QString &address, quint16 port); - - QDnsLookup *m_dnsLookup; - QString m_lookupAddress; - MinecraftServerTargetPtr m_output; -}; diff --git a/api/logic/launch/steps/PostLaunchCommand.cpp b/api/logic/launch/steps/PostLaunchCommand.cpp deleted file mode 100644 index d48d03d1..00000000 --- a/api/logic/launch/steps/PostLaunchCommand.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "PostLaunchCommand.h" -#include <launch/LaunchTask.h> - -PostLaunchCommand::PostLaunchCommand(LaunchTask *parent) : LaunchStep(parent) -{ - auto instance = m_parent->instance(); - m_command = instance->getPostExitCommand(); - m_process.setProcessEnvironment(instance->createEnvironment()); - connect(&m_process, &LoggedProcess::log, this, &PostLaunchCommand::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &PostLaunchCommand::on_state); -} - -void PostLaunchCommand::executeTask() -{ - QString postlaunch_cmd = m_parent->substituteVariables(m_command); - emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::MultiMC); - m_process.start(postlaunch_cmd); -} - -void PostLaunchCommand::on_state(LoggedProcess::State state) -{ - auto getError = [&]() - { - return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); - }; - switch(state) - { - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - case LoggedProcess::FailedToStart: - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - return; - } - case LoggedProcess::Finished: - { - if(m_process.exitCode() != 0) - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - } - else - { - emit logLine(tr("Post-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); - emitSucceeded(); - } - } - default: - break; - } -} - -void PostLaunchCommand::setWorkingDirectory(const QString &wd) -{ - m_process.setWorkingDirectory(wd); -} - -bool PostLaunchCommand::abort() -{ - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; -} diff --git a/api/logic/launch/steps/PostLaunchCommand.h b/api/logic/launch/steps/PostLaunchCommand.h deleted file mode 100644 index ab4c494f..00000000 --- a/api/logic/launch/steps/PostLaunchCommand.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <LoggedProcess.h> - -class PostLaunchCommand: public LaunchStep -{ - Q_OBJECT -public: - explicit PostLaunchCommand(LaunchTask *parent); - virtual ~PostLaunchCommand() {}; - - virtual void executeTask(); - virtual bool abort(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); -private slots: - void on_state(LoggedProcess::State state); - -private: - LoggedProcess m_process; - QString m_command; -}; diff --git a/api/logic/launch/steps/PreLaunchCommand.cpp b/api/logic/launch/steps/PreLaunchCommand.cpp deleted file mode 100644 index 20e089e2..00000000 --- a/api/logic/launch/steps/PreLaunchCommand.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "PreLaunchCommand.h" -#include <launch/LaunchTask.h> - -PreLaunchCommand::PreLaunchCommand(LaunchTask *parent) : LaunchStep(parent) -{ - auto instance = m_parent->instance(); - m_command = instance->getPreLaunchCommand(); - m_process.setProcessEnvironment(instance->createEnvironment()); - connect(&m_process, &LoggedProcess::log, this, &PreLaunchCommand::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &PreLaunchCommand::on_state); -} - -void PreLaunchCommand::executeTask() -{ - //FIXME: where to put this? - QString prelaunch_cmd = m_parent->substituteVariables(m_command); - emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::MultiMC); - m_process.start(prelaunch_cmd); -} - -void PreLaunchCommand::on_state(LoggedProcess::State state) -{ - auto getError = [&]() - { - return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); - }; - switch(state) - { - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - case LoggedProcess::FailedToStart: - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - return; - } - case LoggedProcess::Finished: - { - if(m_process.exitCode() != 0) - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - } - else - { - emit logLine(tr("Pre-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); - emitSucceeded(); - } - } - default: - break; - } -} - -void PreLaunchCommand::setWorkingDirectory(const QString &wd) -{ - m_process.setWorkingDirectory(wd); -} - -bool PreLaunchCommand::abort() -{ - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; -} diff --git a/api/logic/launch/steps/PreLaunchCommand.h b/api/logic/launch/steps/PreLaunchCommand.h deleted file mode 100644 index dc069f71..00000000 --- a/api/logic/launch/steps/PreLaunchCommand.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "launch/LaunchStep.h" -#include "LoggedProcess.h" - -class PreLaunchCommand: public LaunchStep -{ - Q_OBJECT -public: - explicit PreLaunchCommand(LaunchTask *parent); - virtual ~PreLaunchCommand() {}; - - virtual void executeTask(); - virtual bool abort(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); -private slots: - void on_state(LoggedProcess::State state); - -private: - LoggedProcess m_process; - QString m_command; -}; diff --git a/api/logic/launch/steps/TextPrint.cpp b/api/logic/launch/steps/TextPrint.cpp deleted file mode 100644 index 0c1f320c..00000000 --- a/api/logic/launch/steps/TextPrint.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "TextPrint.h" - -TextPrint::TextPrint(LaunchTask * parent, const QStringList &lines, MessageLevel::Enum level) : LaunchStep(parent) -{ - m_lines = lines; - m_level = level; -} -TextPrint::TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level) : LaunchStep(parent) -{ - m_lines.append(line); - m_level = level; -} - -void TextPrint::executeTask() -{ - emit logLines(m_lines, m_level); - emitSucceeded(); -} - -bool TextPrint::canAbort() const -{ - return true; -} - -bool TextPrint::abort() -{ - emitFailed("Aborted."); - return true; -} diff --git a/api/logic/launch/steps/TextPrint.h b/api/logic/launch/steps/TextPrint.h deleted file mode 100644 index 2937c64a..00000000 --- a/api/logic/launch/steps/TextPrint.h +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <LoggedProcess.h> -#include <java/JavaChecker.h> - -#include "multimc_logic_export.h" - -/* - * FIXME: maybe do not export - */ - -class MULTIMC_LOGIC_EXPORT TextPrint: public LaunchStep -{ - Q_OBJECT -public: - explicit TextPrint(LaunchTask *parent, const QStringList &lines, MessageLevel::Enum level); - explicit TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level); - virtual ~TextPrint(){}; - - virtual void executeTask(); - virtual bool canAbort() const; - virtual bool abort(); - -private: - QStringList m_lines; - MessageLevel::Enum m_level; -}; diff --git a/api/logic/launch/steps/Update.cpp b/api/logic/launch/steps/Update.cpp deleted file mode 100644 index 28bd153d..00000000 --- a/api/logic/launch/steps/Update.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "Update.h" -#include <launch/LaunchTask.h> - -void Update::executeTask() -{ - if(m_aborted) - { - emitFailed(tr("Task aborted.")); - return; - } - m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode)); - if(m_updateTask) - { - connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); - connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress); - connect(m_updateTask.get(), &Task::status, this, &Task::setStatus); - emit progressReportingRequest(); - return; - } - emitSucceeded(); -} - -void Update::proceed() -{ - m_updateTask->start(); -} - -void Update::updateFinished() -{ - if(m_updateTask->wasSuccessful()) - { - m_updateTask.reset(); - emitSucceeded(); - } - else - { - QString reason = tr("Instance update failed because: %1\n\n").arg(m_updateTask->failReason()); - m_updateTask.reset(); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - } -} - -bool Update::canAbort() const -{ - if(m_updateTask) - { - return m_updateTask->canAbort(); - } - return true; -} - - -bool Update::abort() -{ - m_aborted = true; - if(m_updateTask) - { - if(m_updateTask->canAbort()) - { - return m_updateTask->abort(); - } - } - return true; -} diff --git a/api/logic/launch/steps/Update.h b/api/logic/launch/steps/Update.h deleted file mode 100644 index 0c9d91e0..00000000 --- a/api/logic/launch/steps/Update.h +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <QObjectPtr.h> -#include <LoggedProcess.h> -#include <java/JavaChecker.h> -#include <net/Mode.h> - -// FIXME: stupid. should be defined by the instance type? or even completely abstracted away... -class Update: public LaunchStep -{ - Q_OBJECT -public: - explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {}; - virtual ~Update() {}; - - void executeTask() override; - bool canAbort() const override; - void proceed() override; -public slots: - bool abort() override; - -private slots: - void updateFinished(); - -private: - shared_qobject_ptr<Task> m_updateTask; - bool m_aborted = false; - Net::Mode m_mode = Net::Mode::Offline; -}; diff --git a/api/logic/meta/BaseEntity.cpp b/api/logic/meta/BaseEntity.cpp deleted file mode 100644 index 5ff7a59a..00000000 --- a/api/logic/meta/BaseEntity.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 "BaseEntity.h" - -#include "Json.h" - -#include "net/Download.h" -#include "net/HttpMetaCache.h" -#include "net/NetJob.h" - -#include "Env.h" -#include "Json.h" - -#include "BuildConfig.h" - -class ParsingValidator : public Net::Validator -{ -public: /* con/des */ - ParsingValidator(Meta::BaseEntity *entity) : m_entity(entity) - { - }; - virtual ~ParsingValidator() - { - }; - -public: /* methods */ - bool init(QNetworkRequest &) override - { - return true; - } - bool write(QByteArray & data) override - { - this->data.append(data); - return true; - } - bool abort() override - { - return true; - } - bool validate(QNetworkReply &) override - { - auto fname = m_entity->localFilename(); - try - { - auto doc = Json::requireDocument(data, fname); - auto obj = Json::requireObject(doc, fname); - m_entity->parse(obj); - return true; - } - catch (const Exception &e) - { - qWarning() << "Unable to parse response:" << e.cause(); - return false; - } - } - -private: /* data */ - QByteArray data; - Meta::BaseEntity *m_entity; -}; - -Meta::BaseEntity::~BaseEntity() -{ -} - -QUrl Meta::BaseEntity::url() const -{ - return QUrl(BuildConfig.META_URL).resolved(localFilename()); -} - -bool Meta::BaseEntity::loadLocalFile() -{ - const QString fname = QDir("meta").absoluteFilePath(localFilename()); - if (!QFile::exists(fname)) - { - return false; - } - // TODO: check if the file has the expected checksum - try - { - auto doc = Json::requireDocument(fname, fname); - auto obj = Json::requireObject(doc, fname); - parse(obj); - return true; - } - catch (const Exception &e) - { - qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); - // just make sure it's gone and we never consider it again. - QFile::remove(fname); - return false; - } -} - -void Meta::BaseEntity::load(Net::Mode loadType) -{ - // load local file if nothing is loaded yet - if(!isLoaded()) - { - if(loadLocalFile()) - { - m_loadStatus = LoadStatus::Local; - } - } - // if we need remote update, run the update task - if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) - { - return; - } - NetJob *job = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename())); - auto url = this->url(); - auto entry = ENV.metacache()->resolveEntry("meta", localFilename()); - entry->setStale(true); - auto dl = Net::Download::makeCached(url, entry); - /* - * The validator parses the file and loads it into the object. - * If that fails, the file is not written to storage. - */ - dl->addValidator(new ParsingValidator(this)); - job->addNetAction(dl); - m_updateStatus = UpdateStatus::InProgress; - m_updateTask.reset(job); - QObject::connect(job, &NetJob::succeeded, [&]() - { - m_loadStatus = LoadStatus::Remote; - m_updateStatus = UpdateStatus::Succeeded; - m_updateTask.reset(); - }); - QObject::connect(job, &NetJob::failed, [&]() - { - m_updateStatus = UpdateStatus::Failed; - m_updateTask.reset(); - }); - m_updateTask->start(); -} - -bool Meta::BaseEntity::isLoaded() const -{ - return m_loadStatus > LoadStatus::NotLoaded; -} - -bool Meta::BaseEntity::shouldStartRemoteUpdate() const -{ - // TODO: version-locks and offline mode? - return m_updateStatus != UpdateStatus::InProgress; -} - -shared_qobject_ptr<Task> Meta::BaseEntity::getCurrentTask() -{ - if(m_updateStatus == UpdateStatus::InProgress) - { - return m_updateTask; - } - return nullptr; -} diff --git a/api/logic/meta/BaseEntity.h b/api/logic/meta/BaseEntity.h deleted file mode 100644 index 04a37420..00000000 --- a/api/logic/meta/BaseEntity.h +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 <QJsonObject> -#include <QObject> -#include "QObjectPtr.h" - -#include "multimc_logic_export.h" -#include "net/Mode.h" - -class Task; -namespace Meta -{ -class MULTIMC_LOGIC_EXPORT BaseEntity -{ -public: /* types */ - using Ptr = std::shared_ptr<BaseEntity>; - enum class LoadStatus - { - NotLoaded, - Local, - Remote - }; - enum class UpdateStatus - { - NotDone, - InProgress, - Failed, - Succeeded - }; - -public: - virtual ~BaseEntity(); - - virtual void parse(const QJsonObject &obj) = 0; - - virtual QString localFilename() const = 0; - virtual QUrl url() const; - - bool isLoaded() const; - bool shouldStartRemoteUpdate() const; - - void load(Net::Mode loadType); - shared_qobject_ptr<Task> getCurrentTask(); - -protected: /* methods */ - bool loadLocalFile(); - -private: - LoadStatus m_loadStatus = LoadStatus::NotLoaded; - UpdateStatus m_updateStatus = UpdateStatus::NotDone; - shared_qobject_ptr<Task> m_updateTask; -}; -} diff --git a/api/logic/meta/Index.cpp b/api/logic/meta/Index.cpp deleted file mode 100644 index 6802470d..00000000 --- a/api/logic/meta/Index.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 "Index.h" - -#include "VersionList.h" -#include "JsonFormat.h" - -namespace Meta -{ -Index::Index(QObject *parent) - : QAbstractListModel(parent) -{ -} -Index::Index(const QVector<VersionListPtr> &lists, QObject *parent) - : QAbstractListModel(parent), m_lists(lists) -{ - for (int i = 0; i < m_lists.size(); ++i) - { - m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i)); - connectVersionList(i, m_lists.at(i)); - } -} - -QVariant Index::data(const QModelIndex &index, int role) const -{ - if (index.parent().isValid() || index.row() < 0 || index.row() >= m_lists.size()) - { - return QVariant(); - } - - VersionListPtr list = m_lists.at(index.row()); - switch (role) - { - case Qt::DisplayRole: - switch (index.column()) - { - case 0: return list->humanReadable(); - default: break; - } - case UidRole: return list->uid(); - case NameRole: return list->name(); - case ListPtrRole: return QVariant::fromValue(list); - } - return QVariant(); -} -int Index::rowCount(const QModelIndex &parent) const -{ - return m_lists.size(); -} -int Index::columnCount(const QModelIndex &parent) const -{ - return 1; -} -QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) - { - return tr("Name"); - } - else - { - return QVariant(); - } -} - -bool Index::hasUid(const QString &uid) const -{ - return m_uids.contains(uid); -} - -VersionListPtr Index::get(const QString &uid) -{ - VersionListPtr out = m_uids.value(uid, nullptr); - if(!out) - { - out = std::make_shared<VersionList>(uid); - m_uids[uid] = out; - } - return out; -} - -VersionPtr Index::get(const QString &uid, const QString &version) -{ - auto list = get(uid); - return list->getVersion(version); -} - -void Index::parse(const QJsonObject& obj) -{ - parseIndex(obj, this); -} - -void Index::merge(const std::shared_ptr<Index> &other) -{ - const QVector<VersionListPtr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists; - // initial load, no need to merge - if (m_lists.isEmpty()) - { - beginResetModel(); - m_lists = lists; - for (int i = 0; i < lists.size(); ++i) - { - m_uids.insert(lists.at(i)->uid(), lists.at(i)); - connectVersionList(i, lists.at(i)); - } - endResetModel(); - } - else - { - for (const VersionListPtr &list : lists) - { - if (m_uids.contains(list->uid())) - { - m_uids[list->uid()]->mergeFromIndex(list); - } - else - { - beginInsertRows(QModelIndex(), m_lists.size(), m_lists.size()); - connectVersionList(m_lists.size(), list); - m_lists.append(list); - m_uids.insert(list->uid(), list); - endInsertRows(); - } - } - } -} - -void Index::connectVersionList(const int row, const VersionListPtr &list) -{ - connect(list.get(), &VersionList::nameChanged, this, [this, row]() - { - emit dataChanged(index(row), index(row), QVector<int>() << Qt::DisplayRole); - }); -} -} diff --git a/api/logic/meta/Index.h b/api/logic/meta/Index.h deleted file mode 100644 index e9412e70..00000000 --- a/api/logic/meta/Index.h +++ /dev/null @@ -1,71 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 <QAbstractListModel> -#include <memory> - -#include "BaseEntity.h" - -#include "multimc_logic_export.h" - -class Task; - -namespace Meta -{ -using VersionListPtr = std::shared_ptr<class VersionList>; -using VersionPtr = std::shared_ptr<class Version>; - -class MULTIMC_LOGIC_EXPORT Index : public QAbstractListModel, public BaseEntity -{ - Q_OBJECT -public: - explicit Index(QObject *parent = nullptr); - explicit Index(const QVector<VersionListPtr> &lists, QObject *parent = nullptr); - - enum - { - UidRole = Qt::UserRole, - NameRole, - ListPtrRole - }; - - QVariant data(const QModelIndex &index, int role) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - - QString localFilename() const override { return "index.json"; } - - // queries - VersionListPtr get(const QString &uid); - VersionPtr get(const QString &uid, const QString &version); - bool hasUid(const QString &uid) const; - - QVector<VersionListPtr> lists() const { return m_lists; } - -public: // for usage by parsers only - void merge(const std::shared_ptr<Index> &other); - void parse(const QJsonObject &obj) override; - -private: - QVector<VersionListPtr> m_lists; - QHash<QString, VersionListPtr> m_uids; - - void connectVersionList(const int row, const VersionListPtr &list); -}; -} - diff --git a/api/logic/meta/Index_test.cpp b/api/logic/meta/Index_test.cpp deleted file mode 100644 index b0892070..00000000 --- a/api/logic/meta/Index_test.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "meta/Index.h" -#include "meta/VersionList.h" -#include "Env.h" - -class IndexTest : public QObject -{ - Q_OBJECT -private -slots: - void test_isProvidedByEnv() - { - QVERIFY(ENV.metadataIndex()); - QCOMPARE(ENV.metadataIndex(), ENV.metadataIndex()); - } - - void test_hasUid_and_getList() - { - Meta::Index windex({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")}); - QVERIFY(windex.hasUid("list1")); - QVERIFY(!windex.hasUid("asdf")); - QVERIFY(windex.get("list2") != nullptr); - QCOMPARE(windex.get("list2")->uid(), QString("list2")); - QVERIFY(windex.get("adsf") != nullptr); - } - - void test_merge() - { - Meta::Index windex({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")}); - QCOMPARE(windex.lists().size(), 3); - windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")}))); - QCOMPARE(windex.lists().size(), 3); - windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list4"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list5")}))); - QCOMPARE(windex.lists().size(), 5); - windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list6")}))); - QCOMPARE(windex.lists().size(), 6); - } -}; - -QTEST_GUILESS_MAIN(IndexTest) - -#include "Index_test.moc" diff --git a/api/logic/meta/JsonFormat.cpp b/api/logic/meta/JsonFormat.cpp deleted file mode 100644 index 796da4bb..00000000 --- a/api/logic/meta/JsonFormat.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 "JsonFormat.h" - -// FIXME: remove this from here... somehow -#include "minecraft/OneSixVersionFormat.h" -#include "Json.h" - -#include "Index.h" -#include "Version.h" -#include "VersionList.h" - -using namespace Json; - -namespace Meta -{ - -MetadataVersion currentFormatVersion() -{ - return MetadataVersion::InitialRelease; -} - -// Index -static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj) -{ - const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages"); - QVector<VersionListPtr> lists; - lists.reserve(objects.size()); - std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj) - { - VersionListPtr list = std::make_shared<VersionList>(requireString(obj, "uid")); - list->setName(ensureString(obj, "name", QString())); - return list; - }); - return std::make_shared<Index>(lists); -} - -// Version -static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj) -{ - VersionPtr version = std::make_shared<Version>(uid, requireString(obj, "version")); - version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000); - version->setType(ensureString(obj, "type", QString())); - version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); - version->setVolatile(ensureBoolean(obj, QString("volatile"), false)); - RequireSet requires, conflicts; - parseRequires(obj, &requires, "requires"); - parseRequires(obj, &conflicts, "conflicts"); - version->setRequires(requires, conflicts); - return version; -} - -static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj) -{ - VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj); - - version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj), - QString("%1/%2.json").arg(version->uid(), version->version()), - obj.contains("order"))); - return version; -} - -// Version list / package -static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject &obj) -{ - const QString uid = requireString(obj, "uid"); - - const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions"); - QVector<VersionPtr> versions; - versions.reserve(versionsRaw.size()); - std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj) - { - auto version = parseCommonVersion(uid, vObj); - version->setProvidesRecommendations(); - return version; - }); - - VersionListPtr list = std::make_shared<VersionList>(uid); - list->setName(ensureString(obj, "name", QString())); - list->setVersions(versions); - return list; -} - - -MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required) -{ - if (!obj.contains("formatVersion")) - { - if(required) - { - return MetadataVersion::Invalid; - } - return MetadataVersion::InitialRelease; - } - if (!obj.value("formatVersion").isDouble()) - { - return MetadataVersion::Invalid; - } - switch(obj.value("formatVersion").toInt()) - { - case 0: - case 1: - return MetadataVersion::InitialRelease; - default: - return MetadataVersion::Invalid; - } -} - -void serializeFormatVersion(QJsonObject& obj, Meta::MetadataVersion version) -{ - if(version == MetadataVersion::Invalid) - { - return; - } - obj.insert("formatVersion", int(version)); -} - -void parseIndex(const QJsonObject &obj, Index *ptr) -{ - const MetadataVersion version = parseFormatVersion(obj); - switch (version) - { - case MetadataVersion::InitialRelease: - ptr->merge(parseIndexInternal(obj)); - break; - case MetadataVersion::Invalid: - throw ParseException(QObject::tr("Unknown format version!")); - } -} - -void parseVersionList(const QJsonObject &obj, VersionList *ptr) -{ - const MetadataVersion version = parseFormatVersion(obj); - switch (version) - { - case MetadataVersion::InitialRelease: - ptr->merge(parseVersionListInternal(obj)); - break; - case MetadataVersion::Invalid: - throw ParseException(QObject::tr("Unknown format version!")); - } -} - -void parseVersion(const QJsonObject &obj, Version *ptr) -{ - const MetadataVersion version = parseFormatVersion(obj); - switch (version) - { - case MetadataVersion::InitialRelease: - ptr->merge(parseVersionInternal(obj)); - break; - case MetadataVersion::Invalid: - throw ParseException(QObject::tr("Unknown format version!")); - } -} - -/* -[ -{"uid":"foo", "equals":"version"} -] -*/ -void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName) -{ - if(obj.contains(keyName)) - { - QSet<QString> requires; - auto reqArray = requireArray(obj, keyName); - auto iter = reqArray.begin(); - while(iter != reqArray.end()) - { - auto reqObject = requireObject(*iter); - auto uid = requireString(reqObject, "uid"); - auto equals = ensureString(reqObject, "equals", QString()); - auto suggests = ensureString(reqObject, "suggests", QString()); - ptr->insert({uid, equals, suggests}); - iter++; - } - } -} -void serializeRequires(QJsonObject& obj, RequireSet* ptr, const char * keyName) -{ - if(!ptr || ptr->empty()) - { - return; - } - QJsonArray arrOut; - for(auto &iter: *ptr) - { - QJsonObject reqOut; - reqOut.insert("uid", iter.uid); - if(!iter.equalsVersion.isEmpty()) - { - reqOut.insert("equals", iter.equalsVersion); - } - if(!iter.suggests.isEmpty()) - { - reqOut.insert("suggests", iter.suggests); - } - arrOut.append(reqOut); - } - obj.insert(keyName, arrOut); -} - -} - diff --git a/api/logic/meta/JsonFormat.h b/api/logic/meta/JsonFormat.h deleted file mode 100644 index 93217b7e..00000000 --- a/api/logic/meta/JsonFormat.h +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 <QJsonObject> -#include <memory> - -#include "Exception.h" -#include "meta/BaseEntity.h" -#include <set> - -namespace Meta -{ -class Index; -class Version; -class VersionList; - -enum class MetadataVersion -{ - Invalid = -1, - InitialRelease = 1 -}; - -class ParseException : public Exception -{ -public: - using Exception::Exception; -}; -struct Require -{ - bool operator==(const Require & rhs) const - { - return uid == rhs.uid; - } - bool operator<(const Require & rhs) const - { - return uid < rhs.uid; - } - bool deepEquals(const Require & rhs) const - { - return uid == rhs.uid - && equalsVersion == rhs.equalsVersion - && suggests == rhs.suggests; - } - QString uid; - QString equalsVersion; - QString suggests; -}; - -inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW -{ - return qHash(key.uid, seed); -} - -using RequireSet = std::set<Require>; - -void parseIndex(const QJsonObject &obj, Index *ptr); -void parseVersion(const QJsonObject &obj, Version *ptr); -void parseVersionList(const QJsonObject &obj, VersionList *ptr); - -MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required = true); -void serializeFormatVersion(QJsonObject &obj, MetadataVersion version); - -// FIXME: this has a different shape than the others...FIX IT!? -void parseRequires(const QJsonObject &obj, RequireSet * ptr, const char * keyName = "requires"); -void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyName = "requires"); -MetadataVersion currentFormatVersion(); -} - -Q_DECLARE_METATYPE(std::set<Meta::Require>) diff --git a/api/logic/meta/Version.cpp b/api/logic/meta/Version.cpp deleted file mode 100644 index a8dc3169..00000000 --- a/api/logic/meta/Version.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 "Version.h" - -#include <QDateTime> - -#include "JsonFormat.h" -#include "minecraft/PackProfile.h" - -Meta::Version::Version(const QString &uid, const QString &version) - : BaseVersion(), m_uid(uid), m_version(version) -{ -} - -Meta::Version::~Version() -{ -} - -QString Meta::Version::descriptor() -{ - return m_version; -} -QString Meta::Version::name() -{ - if(m_data) - return m_data->name; - return m_uid; -} -QString Meta::Version::typeString() const -{ - return m_type; -} - -QDateTime Meta::Version::time() const -{ - return QDateTime::fromMSecsSinceEpoch(m_time * 1000, Qt::UTC); -} - -void Meta::Version::parse(const QJsonObject& obj) -{ - parseVersion(obj, this); -} - -void Meta::Version::mergeFromList(const Meta::VersionPtr& other) -{ - if(other->m_providesRecommendations) - { - if(m_recommended != other->m_recommended) - { - setRecommended(other->m_recommended); - } - } - if (m_type != other->m_type) - { - setType(other->m_type); - } - if (m_time != other->m_time) - { - setTime(other->m_time); - } - if (m_requires != other->m_requires) - { - m_requires = other->m_requires; - } - if (m_conflicts != other->m_conflicts) - { - m_conflicts = other->m_conflicts; - } - if(m_volatile != other->m_volatile) - { - setVolatile(other->m_volatile); - } -} - -void Meta::Version::merge(const VersionPtr &other) -{ - mergeFromList(other); - if(other->m_data) - { - setData(other->m_data); - } -} - -QString Meta::Version::localFilename() const -{ - return m_uid + '/' + m_version + ".json"; -} - -void Meta::Version::setType(const QString &type) -{ - m_type = type; - emit typeChanged(); -} - -void Meta::Version::setTime(const qint64 time) -{ - m_time = time; - emit timeChanged(); -} - -void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts) -{ - m_requires = requires; - m_conflicts = conflicts; - emit requiresChanged(); -} - -void Meta::Version::setVolatile(bool volatile_) -{ - m_volatile = volatile_; -} - - -void Meta::Version::setData(const VersionFilePtr &data) -{ - m_data = data; -} - -void Meta::Version::setProvidesRecommendations() -{ - m_providesRecommendations = true; -} - -void Meta::Version::setRecommended(bool recommended) -{ - m_recommended = recommended; -} diff --git a/api/logic/meta/Version.h b/api/logic/meta/Version.h deleted file mode 100644 index a38d7bcd..00000000 --- a/api/logic/meta/Version.h +++ /dev/null @@ -1,118 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 "BaseVersion.h" - -#include <QJsonObject> -#include <QStringList> -#include <QVector> -#include <memory> - -#include "minecraft/VersionFile.h" - -#include "BaseEntity.h" - -#include "multimc_logic_export.h" - -#include "JsonFormat.h" - -namespace Meta -{ -using VersionPtr = std::shared_ptr<class Version>; - -class MULTIMC_LOGIC_EXPORT Version : public QObject, public BaseVersion, public BaseEntity -{ - Q_OBJECT - -public: /* con/des */ - explicit Version(const QString &uid, const QString &version); - virtual ~Version(); - - QString descriptor() override; - QString name() override; - QString typeString() const override; - - QString uid() const - { - return m_uid; - } - QString version() const - { - return m_version; - } - QString type() const - { - return m_type; - } - QDateTime time() const; - qint64 rawTime() const - { - return m_time; - } - const Meta::RequireSet &requires() const - { - return m_requires; - } - VersionFilePtr data() const - { - return m_data; - } - bool isRecommended() const - { - return m_recommended; - } - bool isLoaded() const - { - return m_data != nullptr; - } - - void merge(const VersionPtr &other); - void mergeFromList(const VersionPtr &other); - void parse(const QJsonObject &obj) override; - - QString localFilename() const override; - -public: // for usage by format parsers only - void setType(const QString &type); - void setTime(const qint64 time); - void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts); - void setVolatile(bool volatile_); - void setRecommended(bool recommended); - void setProvidesRecommendations(); - void setData(const VersionFilePtr &data); - -signals: - void typeChanged(); - void timeChanged(); - void requiresChanged(); - -private: - bool m_providesRecommendations = false; - bool m_recommended = false; - QString m_name; - QString m_uid; - QString m_version; - QString m_type; - qint64 m_time = 0; - Meta::RequireSet m_requires; - Meta::RequireSet m_conflicts; - bool m_volatile = false; - VersionFilePtr m_data; -}; -} - -Q_DECLARE_METATYPE(Meta::VersionPtr) diff --git a/api/logic/meta/VersionList.cpp b/api/logic/meta/VersionList.cpp deleted file mode 100644 index 607007eb..00000000 --- a/api/logic/meta/VersionList.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 "VersionList.h" - -#include <QDateTime> - -#include "Version.h" -#include "JsonFormat.h" -#include "Version.h" - -namespace Meta -{ -VersionList::VersionList(const QString &uid, QObject *parent) - : BaseVersionList(parent), m_uid(uid) -{ - setObjectName("Version list: " + uid); -} - -shared_qobject_ptr<Task> VersionList::getLoadTask() -{ - load(Net::Mode::Online); - return getCurrentTask(); -} - -bool VersionList::isLoaded() -{ - return BaseEntity::isLoaded(); -} - -const BaseVersionPtr VersionList::at(int i) const -{ - return m_versions.at(i); -} -int VersionList::count() const -{ - return m_versions.size(); -} - -void VersionList::sortVersions() -{ - beginResetModel(); - std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) - { - return *a.get() < *b.get(); - }); - endResetModel(); -} - -QVariant VersionList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= m_versions.size() || index.parent().isValid()) - { - return QVariant(); - } - - VersionPtr version = m_versions.at(index.row()); - - switch (role) - { - case VersionPointerRole: return QVariant::fromValue(std::dynamic_pointer_cast<BaseVersion>(version)); - case VersionRole: - case VersionIdRole: - return version->version(); - case ParentVersionRole: - { - // FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'. - auto & reqs = version->requires(); - auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req) - { - return req.uid == "net.minecraft"; - }); - if (iter != reqs.end()) - { - return (*iter).equalsVersion; - } - return QVariant(); - } - case TypeRole: return version->type(); - - case UidRole: return version->uid(); - case TimeRole: return version->time(); - case RequiresRole: return QVariant::fromValue(version->requires()); - case SortRole: return version->rawTime(); - case VersionPtrRole: return QVariant::fromValue(version); - case RecommendedRole: return version->isRecommended(); - // FIXME: this should be determined in whatever view/proxy is used... - // case LatestRole: return version == getLatestStable(); - default: return QVariant(); - } -} - -BaseVersionList::RoleList VersionList::providesRoles() const -{ - return {VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, - TypeRole, UidRole, TimeRole, RequiresRole, SortRole, - RecommendedRole, LatestRole, VersionPtrRole}; -} - -QHash<int, QByteArray> VersionList::roleNames() const -{ - QHash<int, QByteArray> roles = BaseVersionList::roleNames(); - roles.insert(UidRole, "uid"); - roles.insert(TimeRole, "time"); - roles.insert(SortRole, "sort"); - roles.insert(RequiresRole, "requires"); - return roles; -} - -QString VersionList::localFilename() const -{ - return m_uid + "/index.json"; -} - -QString VersionList::humanReadable() const -{ - return m_name.isEmpty() ? m_uid : m_name; -} - -VersionPtr VersionList::getVersion(const QString &version) -{ - VersionPtr out = m_lookup.value(version, nullptr); - if(!out) - { - out = std::make_shared<Version>(m_uid, version); - m_lookup[version] = out; - } - return out; -} - -void VersionList::setName(const QString &name) -{ - m_name = name; - emit nameChanged(name); -} - -void VersionList::setVersions(const QVector<VersionPtr> &versions) -{ - beginResetModel(); - m_versions = versions; - std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) - { - return a->rawTime() > b->rawTime(); - }); - for (int i = 0; i < m_versions.size(); ++i) - { - m_lookup.insert(m_versions.at(i)->version(), m_versions.at(i)); - setupAddedVersion(i, m_versions.at(i)); - } - - // FIXME: this is dumb, we have 'recommended' as part of the metadata already... - auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; }); - m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt; - endResetModel(); -} - -void VersionList::parse(const QJsonObject& obj) -{ - parseVersionList(obj, this); -} - -// FIXME: this is dumb, we have 'recommended' as part of the metadata already... -static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b) -{ - if(!a) - return b; - if(!b) - return a; - if(a->type() == b->type()) - { - // newer of same type wins - return (a->rawTime() > b->rawTime() ? a : b); - } - // 'release' type wins - return (a->type() == "release" ? a : b); -} - -void VersionList::mergeFromIndex(const VersionListPtr &other) -{ - if (m_name != other->m_name) - { - setName(other->m_name); - } -} - -void VersionList::merge(const VersionListPtr &other) -{ - if (m_name != other->m_name) - { - setName(other->m_name); - } - - // TODO: do not reset the whole model. maybe? - beginResetModel(); - m_versions.clear(); - if(other->m_versions.isEmpty()) - { - qWarning() << "Empty list loaded ..."; - } - for (const VersionPtr &version : other->m_versions) - { - // we already have the version. merge the contents - if (m_lookup.contains(version->version())) - { - m_lookup.value(version->version())->mergeFromList(version); - } - else - { - m_lookup.insert(version->uid(), version); - } - // connect it. - setupAddedVersion(m_versions.size(), version); - m_versions.append(version); - m_recommended = getBetterVersion(m_recommended, version); - } - endResetModel(); -} - -void VersionList::setupAddedVersion(const int row, const VersionPtr &version) -{ - // FIXME: do not disconnect from everythin, disconnect only the lambdas here - version->disconnect(); - connect(version.get(), &Version::requiresChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); }); - connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TimeRole << SortRole); }); - connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); }); -} - -BaseVersionPtr VersionList::getRecommended() const -{ - return m_recommended; -} - -} diff --git a/api/logic/meta/VersionList.h b/api/logic/meta/VersionList.h deleted file mode 100644 index bba32ca3..00000000 --- a/api/logic/meta/VersionList.h +++ /dev/null @@ -1,101 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 "BaseEntity.h" -#include "BaseVersionList.h" -#include <QJsonObject> -#include <memory> - -namespace Meta -{ -using VersionPtr = std::shared_ptr<class Version>; -using VersionListPtr = std::shared_ptr<class VersionList>; - -class MULTIMC_LOGIC_EXPORT VersionList : public BaseVersionList, public BaseEntity -{ - Q_OBJECT - Q_PROPERTY(QString uid READ uid CONSTANT) - Q_PROPERTY(QString name READ name NOTIFY nameChanged) -public: - explicit VersionList(const QString &uid, QObject *parent = nullptr); - - enum Roles - { - UidRole = Qt::UserRole + 100, - TimeRole, - RequiresRole, - VersionPtrRole - }; - - shared_qobject_ptr<Task> getLoadTask() override; - bool isLoaded() override; - const BaseVersionPtr at(int i) const override; - int count() const override; - void sortVersions() override; - - BaseVersionPtr getRecommended() const override; - - QVariant data(const QModelIndex &index, int role) const override; - RoleList providesRoles() const override; - QHash<int, QByteArray> roleNames() const override; - - QString localFilename() const override; - - QString uid() const - { - return m_uid; - } - QString name() const - { - return m_name; - } - QString humanReadable() const; - - VersionPtr getVersion(const QString &version); - - QVector<VersionPtr> versions() const - { - return m_versions; - } - -public: // for usage only by parsers - void setName(const QString &name); - void setVersions(const QVector<VersionPtr> &versions); - void merge(const VersionListPtr &other); - void mergeFromIndex(const VersionListPtr &other); - void parse(const QJsonObject &obj) override; - -signals: - void nameChanged(const QString &name); - -protected slots: - void updateListData(QList<BaseVersionPtr>) override - { - } - -private: - QVector<VersionPtr> m_versions; - QHash<QString, VersionPtr> m_lookup; - QString m_uid; - QString m_name; - - VersionPtr m_recommended; - - void setupAddedVersion(const int row, const VersionPtr &version); -}; -} -Q_DECLARE_METATYPE(Meta::VersionListPtr) diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp deleted file mode 100644 index c01733b6..00000000 --- a/api/logic/minecraft/AssetsUtils.cpp +++ /dev/null @@ -1,333 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QFileInfo> -#include <QDir> -#include <QDirIterator> -#include <QCryptographicHash> -#include <QJsonParseError> -#include <QJsonDocument> -#include <QJsonObject> -#include <QVariant> -#include <QDebug> - -#include "AssetsUtils.h" -#include "FileSystem.h" -#include "net/Download.h" -#include "net/ChecksumValidator.h" -#include "BuildConfig.h" - -namespace { -QSet<QString> collectPathsFromDir(QString dirPath) -{ - QFileInfo dirInfo(dirPath); - - if (!dirInfo.exists()) - { - return {}; - } - - QSet<QString> out; - - QDirIterator iter(dirPath, QDirIterator::Subdirectories); - while (iter.hasNext()) - { - QString value = iter.next(); - QFileInfo info(value); - if(info.isFile()) - { - out.insert(value); - qDebug() << value; - } - } - return out; -} -} - - -namespace AssetsUtils -{ - -/* - * Returns true on success, with index populated - * index is undefined otherwise - */ -bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsIndex& index) -{ - /* - { - "objects": { - "icons/icon_16x16.png": { - "hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a", - "size": 3665 - }, - ... - } - } - } - */ - - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::ReadOnly)) - { - qCritical() << "Failed to read assets index file" << path; - return false; - } - index.id = assetsId; - - // Read the file and close it. - QByteArray jsonData = file.readAll(); - file.close(); - - QJsonParseError parseError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); - - // Fail if the JSON is invalid. - if (parseError.error != QJsonParseError::NoError) - { - qCritical() << "Failed to parse assets index file:" << parseError.errorString() - << "at offset " << QString::number(parseError.offset); - return false; - } - - // Make sure the root is an object. - if (!jsonDoc.isObject()) - { - qCritical() << "Invalid assets index JSON: Root should be an array."; - return false; - } - - QJsonObject root = jsonDoc.object(); - - QJsonValue isVirtual = root.value("virtual"); - if (!isVirtual.isUndefined()) - { - index.isVirtual = isVirtual.toBool(false); - } - - QJsonValue mapToResources = root.value("map_to_resources"); - if (!mapToResources.isUndefined()) - { - index.mapToResources = mapToResources.toBool(false); - } - - QJsonValue objects = root.value("objects"); - QVariantMap map = objects.toVariant().toMap(); - - for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) - { - // qDebug() << iter.key(); - - QVariant variant = iter.value(); - QVariantMap nested_objects = variant.toMap(); - - AssetObject object; - - for (QVariantMap::const_iterator nested_iter = nested_objects.begin(); - nested_iter != nested_objects.end(); ++nested_iter) - { - // qDebug() << nested_iter.key() << nested_iter.value().toString(); - QString key = nested_iter.key(); - QVariant value = nested_iter.value(); - - if (key == "hash") - { - object.hash = value.toString(); - } - else if (key == "size") - { - object.size = value.toDouble(); - } - } - - index.objects.insert(iter.key(), object); - } - - return true; -} - -// FIXME: ugly code duplication -QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder) -{ - QDir assetsDir = QDir("assets/"); - QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); - QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); - QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); - - QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); - QFile indexFile(indexPath); - QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); - - if (!indexFile.exists()) - { - qCritical() << "No assets index file" << indexPath << "; can't determine assets path!"; - return virtualRoot; - } - - AssetsIndex index; - if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) - { - qCritical() << "Failed to load asset index file" << indexPath << "; can't determine assets path!"; - return virtualRoot; - } - - QString targetPath; - if(index.isVirtual) - { - return virtualRoot; - } - else if(index.mapToResources) - { - return QDir(resourcesFolder); - } - return virtualRoot; -} - -// FIXME: ugly code duplication -bool reconstructAssets(QString assetsId, QString resourcesFolder) -{ - QDir assetsDir = QDir("assets/"); - QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); - QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); - QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); - - QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); - QFile indexFile(indexPath); - QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); - - if (!indexFile.exists()) - { - qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets!"; - return false; - } - - qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path(); - - AssetsIndex index; - if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) - { - qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!"; - return false; - } - - QString targetPath; - bool removeLeftovers = false; - if(index.isVirtual) - { - targetPath = virtualRoot.path(); - removeLeftovers = true; - qDebug() << "Reconstructing virtual assets folder at" << targetPath; - } - else if(index.mapToResources) - { - targetPath = resourcesFolder; - qDebug() << "Reconstructing resources folder at" << targetPath; - } - - if (!targetPath.isNull()) - { - auto presentFiles = collectPathsFromDir(targetPath); - for (QString map : index.objects.keys()) - { - AssetObject asset_object = index.objects.value(map); - QString target_path = FS::PathCombine(targetPath, map); - QFile target(target_path); - - QString tlk = asset_object.hash.left(2); - - QString original_path = FS::PathCombine(objectDir.path(), tlk, asset_object.hash); - QFile original(original_path); - if (!original.exists()) - continue; - - presentFiles.remove(target_path); - - if (!target.exists()) - { - QFileInfo info(target_path); - QDir target_dir = info.dir(); - - qDebug() << target_dir.path(); - FS::ensureFolderPathExists(target_dir.path()); - - bool couldCopy = original.copy(target_path); - qDebug() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy); - } - } - - // TODO: Write last used time to virtualRoot/.lastused - if(removeLeftovers) - { - for(auto & file: presentFiles) - { - qDebug() << "Would remove" << file; - } - } - } - return true; -} - -} - -NetActionPtr AssetObject::getDownloadAction() -{ - QFileInfo objectFile(getLocalPath()); - if ((!objectFile.isFile()) || (objectFile.size() != size)) - { - auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath()); - if(hash.size()) - { - auto rawHash = QByteArray::fromHex(hash.toLatin1()); - objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); - } - objectDL->m_total_progress = size; - return objectDL; - } - return nullptr; -} - -QString AssetObject::getLocalPath() -{ - return "assets/objects/" + getRelPath(); -} - -QUrl AssetObject::getUrl() -{ - return BuildConfig.RESOURCE_BASE + getRelPath(); -} - -QString AssetObject::getRelPath() -{ - return hash.left(2) + "/" + hash; -} - -NetJobPtr AssetsIndex::getDownloadJob() -{ - auto job = new NetJob(QObject::tr("Assets for %1").arg(id)); - for (auto &object : objects.values()) - { - auto dl = object.getDownloadAction(); - if(dl) - { - job->addNetAction(dl); - } - } - if(job->size()) - return job; - return nullptr; -} diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h deleted file mode 100644 index 32e57060..00000000 --- a/api/logic/minecraft/AssetsUtils.h +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QMap> -#include "net/NetAction.h" -#include "net/NetJob.h" - -struct AssetObject -{ - QString getRelPath(); - QUrl getUrl(); - QString getLocalPath(); - NetActionPtr getDownloadAction(); - - QString hash; - qint64 size; -}; - -struct AssetsIndex -{ - NetJobPtr getDownloadJob(); - - QString id; - QMap<QString, AssetObject> objects; - bool isVirtual = false; - bool mapToResources = false; -}; - -/// FIXME: this is absolutely horrendous. REDO!!!! -namespace AssetsUtils -{ -bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index); - -QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder); - -/// Reconstruct a virtual assets folder for the given assets ID and return the folder -bool reconstructAssets(QString assetsId, QString resourcesFolder); -} diff --git a/api/logic/minecraft/Component.cpp b/api/logic/minecraft/Component.cpp deleted file mode 100644 index 92821065..00000000 --- a/api/logic/minecraft/Component.cpp +++ /dev/null @@ -1,439 +0,0 @@ -#include <meta/VersionList.h> -#include <meta/Index.h> -#include <Env.h> -#include "Component.h" - -#include "meta/Version.h" -#include "VersionFile.h" -#include "minecraft/PackProfile.h" -#include <FileSystem.h> -#include <QSaveFile> -#include "OneSixVersionFormat.h" -#include <assert.h> - -Component::Component(PackProfile * parent, const QString& uid) -{ - assert(parent); - m_parent = parent; - - m_uid = uid; -} - -Component::Component(PackProfile * parent, std::shared_ptr<Meta::Version> version) -{ - assert(parent); - m_parent = parent; - - m_metaVersion = version; - m_uid = version->uid(); - m_version = m_cachedVersion = version->version(); - m_cachedName = version->name(); - m_loaded = version->isLoaded(); -} - -Component::Component(PackProfile * parent, const QString& uid, std::shared_ptr<VersionFile> file) -{ - assert(parent); - m_parent = parent; - - m_file = file; - m_uid = uid; - m_cachedVersion = m_file->version; - m_cachedName = m_file->name; - m_loaded = true; -} - -std::shared_ptr<Meta::Version> Component::getMeta() -{ - return m_metaVersion; -} - -void Component::applyTo(LaunchProfile* profile) -{ - // do not apply disabled components - if(!isEnabled()) - { - return; - } - auto vfile = getVersionFile(); - if(vfile) - { - vfile->applyTo(profile); - } - else - { - profile->applyProblemSeverity(getProblemSeverity()); - } -} - -std::shared_ptr<class VersionFile> Component::getVersionFile() const -{ - if(m_metaVersion) - { - if(!m_metaVersion->isLoaded()) - { - m_metaVersion->load(Net::Mode::Online); - } - return m_metaVersion->data(); - } - else - { - return m_file; - } -} - -std::shared_ptr<class Meta::VersionList> Component::getVersionList() const -{ - // FIXME: what if the metadata index isn't loaded yet? - if(ENV.metadataIndex()->hasUid(m_uid)) - { - return ENV.metadataIndex()->get(m_uid); - } - return nullptr; -} - -int Component::getOrder() -{ - if(m_orderOverride) - return m_order; - - auto vfile = getVersionFile(); - if(vfile) - { - return vfile->order; - } - return 0; -} -void Component::setOrder(int order) -{ - m_orderOverride = true; - m_order = order; -} -QString Component::getID() -{ - return m_uid; -} -QString Component::getName() -{ - if (!m_cachedName.isEmpty()) - return m_cachedName; - return m_uid; -} -QString Component::getVersion() -{ - return m_cachedVersion; -} -QString Component::getFilename() -{ - return m_parent->patchFilePathForUid(m_uid); -} -QDateTime Component::getReleaseDateTime() -{ - if(m_metaVersion) - { - return m_metaVersion->time(); - } - auto vfile = getVersionFile(); - if(vfile) - { - return vfile->releaseTime; - } - // FIXME: fake - return QDateTime::currentDateTime(); -} - -bool Component::isEnabled() -{ - return !canBeDisabled() || !m_disabled; -} - -bool Component::canBeDisabled() -{ - return isRemovable() && !m_dependencyOnly; -} - -bool Component::setEnabled(bool state) -{ - bool intendedDisabled = !state; - if (!canBeDisabled()) - { - intendedDisabled = false; - } - if(intendedDisabled != m_disabled) - { - m_disabled = intendedDisabled; - emit dataChanged(); - return true; - } - return false; -} - -bool Component::isCustom() -{ - return m_file != nullptr; -} - -bool Component::isCustomizable() -{ - if(m_metaVersion) - { - if(getVersionFile()) - { - return true; - } - } - return false; -} -bool Component::isRemovable() -{ - return !m_important; -} -bool Component::isRevertible() -{ - if (isCustom()) - { - if(ENV.metadataIndex()->hasUid(m_uid)) - { - return true; - } - } - return false; -} -bool Component::isMoveable() -{ - // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'. - return true; -} -bool Component::isVersionChangeable() -{ - auto list = getVersionList(); - if(list) - { - if(!list->isLoaded()) - { - list->load(Net::Mode::Online); - } - return list->count() != 0; - } - return false; -} - -void Component::setImportant(bool state) -{ - if(m_important != state) - { - m_important = state; - emit dataChanged(); - } -} - -ProblemSeverity Component::getProblemSeverity() const -{ - auto file = getVersionFile(); - if(file) - { - return file->getProblemSeverity(); - } - return ProblemSeverity::Error; -} - -const QList<PatchProblem> Component::getProblems() const -{ - auto file = getVersionFile(); - if(file) - { - return file->getProblems(); - } - return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}}; -} - -void Component::setVersion(const QString& version) -{ - if(version == m_version) - { - return; - } - m_version = version; - if(m_loaded) - { - // we are loaded and potentially have state to invalidate - if(m_file) - { - // we have a file... explicit version has been changed and there is nothing else to do. - } - else - { - // we don't have a file, therefore we are loaded with metadata - m_cachedVersion = version; - // see if the meta version is loaded - auto metaVersion = ENV.metadataIndex()->get(m_uid, version); - if(metaVersion->isLoaded()) - { - // if yes, we can continue with that. - m_metaVersion = metaVersion; - } - else - { - // if not, we need loading - m_metaVersion.reset(); - m_loaded = false; - } - updateCachedData(); - } - } - else - { - // not loaded... assume it will be sorted out later by the update task - } - emit dataChanged(); -} - -bool Component::customize() -{ - if(isCustom()) - { - return false; - } - - auto filename = getFilename(); - if(!FS::ensureFilePathExists(filename)) - { - return false; - } - // FIXME: get rid of this try-catch. - try - { - QSaveFile jsonFile(filename); - if(!jsonFile.open(QIODevice::WriteOnly)) - { - return false; - } - auto vfile = getVersionFile(); - if(!vfile) - { - return false; - } - auto document = OneSixVersionFormat::versionFileToJson(vfile); - jsonFile.write(document.toJson()); - if(!jsonFile.commit()) - { - return false; - } - m_file = vfile; - m_metaVersion.reset(); - emit dataChanged(); - } - catch (const Exception &error) - { - qWarning() << "Version could not be loaded:" << error.cause(); - } - return true; -} - -bool Component::revert() -{ - if(!isCustom()) - { - // already not custom - return true; - } - auto filename = getFilename(); - bool result = true; - // just kill the file and reload - if(QFile::exists(filename)) - { - result = QFile::remove(filename); - } - if(result) - { - // file gone... - m_file.reset(); - - // check local cache for metadata... - auto version = ENV.metadataIndex()->get(m_uid, m_version); - if(version->isLoaded()) - { - m_metaVersion = version; - } - else - { - m_metaVersion.reset(); - m_loaded = false; - } - emit dataChanged(); - } - return result; -} - -/** - * deep inspecting compare for requirement sets - * By default, only uids are compared for set operations. - * This compares all fields of the Require structs in the sets. - */ -static bool deepCompare(const std::set<Meta::Require> & a, const std::set<Meta::Require> & b) -{ - // NOTE: this needs to be rewritten if the type of Meta::RequireSet changes - if(a.size() != b.size()) - { - return false; - } - for(const auto & reqA :a) - { - const auto &iter2 = b.find(reqA); - if(iter2 == b.cend()) - { - return false; - } - const auto & reqB = *iter2; - if(!reqA.deepEquals(reqB)) - { - return false; - } - } - return true; -} - -void Component::updateCachedData() -{ - auto file = getVersionFile(); - if(file) - { - bool changed = false; - if(m_cachedName != file->name) - { - m_cachedName = file->name; - changed = true; - } - if(m_cachedVersion != file->version) - { - m_cachedVersion = file->version; - changed = true; - } - if(m_cachedVolatile != file->m_volatile) - { - m_cachedVolatile = file->m_volatile; - changed = true; - } - if(!deepCompare(m_cachedRequires, file->requires)) - { - m_cachedRequires = file->requires; - changed = true; - } - if(!deepCompare(m_cachedConflicts, file->conflicts)) - { - m_cachedConflicts = file->conflicts; - changed = true; - } - if(changed) - { - emit dataChanged(); - } - } - else - { - // in case we removed all the metadata - m_cachedRequires.clear(); - m_cachedConflicts.clear(); - emit dataChanged(); - } -} diff --git a/api/logic/minecraft/Component.h b/api/logic/minecraft/Component.h deleted file mode 100644 index cb202f7f..00000000 --- a/api/logic/minecraft/Component.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -#include <memory> -#include <QList> -#include <QJsonDocument> -#include <QDateTime> -#include "meta/JsonFormat.h" -#include "ProblemProvider.h" -#include "QObjectPtr.h" -#include "multimc_logic_export.h" - -class PackProfile; -class LaunchProfile; -namespace Meta -{ - class Version; - class VersionList; -} -class VersionFile; - -class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider -{ -Q_OBJECT -public: - Component(PackProfile * parent, const QString &uid); - - // DEPRECATED: remove these constructors? - Component(PackProfile * parent, std::shared_ptr<Meta::Version> version); - Component(PackProfile * parent, const QString & uid, std::shared_ptr<VersionFile> file); - - virtual ~Component(){}; - void applyTo(LaunchProfile *profile); - - bool isEnabled(); - bool setEnabled (bool state); - bool canBeDisabled(); - - bool isMoveable(); - bool isCustomizable(); - bool isRevertible(); - bool isRemovable(); - bool isCustom(); - bool isVersionChangeable(); - - // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code - void setOrder(int order); - int getOrder(); - - QString getID(); - QString getName(); - QString getVersion(); - std::shared_ptr<Meta::Version> getMeta(); - QDateTime getReleaseDateTime(); - - QString getFilename(); - - std::shared_ptr<class VersionFile> getVersionFile() const; - std::shared_ptr<class Meta::VersionList> getVersionList() const; - - void setImportant (bool state); - - - const QList<PatchProblem> getProblems() const override; - ProblemSeverity getProblemSeverity() const override; - - void setVersion(const QString & version); - bool customize(); - bool revert(); - - void updateCachedData(); - -signals: - void dataChanged(); - -public: /* data */ - PackProfile * m_parent; - - // BEGIN: persistent component list properties - /// ID of the component - QString m_uid; - /// version of the component - when there's a custom json override, this is also the version the component reverts to - QString m_version; - /// if true, this has been added automatically to satisfy dependencies and may be automatically removed - bool m_dependencyOnly = false; - /// if true, the component is either the main component of the instance, or otherwise important and cannot be removed. - bool m_important = false; - /// if true, the component is disabled - bool m_disabled = false; - - /// cached name for display purposes, taken from the version file (meta or local override) - QString m_cachedName; - /// cached version for display AND other purposes, taken from the version file (meta or local override) - QString m_cachedVersion; - /// cached set of requirements, taken from the version file (meta or local override) - Meta::RequireSet m_cachedRequires; - Meta::RequireSet m_cachedConflicts; - /// if true, the component is volatile and may be automatically removed when no longer needed - bool m_cachedVolatile = false; - // END: persistent component list properties - - // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code - bool m_orderOverride = false; - int m_order = 0; - - // load state - std::shared_ptr<Meta::Version> m_metaVersion; - std::shared_ptr<VersionFile> m_file; - bool m_loaded = false; -}; - -typedef shared_qobject_ptr<Component> ComponentPtr; diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp deleted file mode 100644 index 241d9a49..00000000 --- a/api/logic/minecraft/ComponentUpdateTask.cpp +++ /dev/null @@ -1,704 +0,0 @@ -#include "ComponentUpdateTask.h" - -#include "PackProfile_p.h" -#include "PackProfile.h" -#include "Component.h" -#include <Env.h> -#include <meta/Index.h> -#include <meta/VersionList.h> -#include <meta/Version.h> -#include "ComponentUpdateTask_p.h" -#include <cassert> -#include <Version.h> -#include "net/Mode.h" -#include "OneSixVersionFormat.h" - -/* - * This is responsible for loading the components of a component list AND resolving dependency issues between them - */ - -/* - * FIXME: the 'one shot async task' nature of this does not fit the intended usage - * Really, it should be a reactor/state machine that receives input from the application - * and dynamically adapts to changing requirements... - * - * The reactor should be the only entry into manipulating the PackProfile. - * See: https://en.wikipedia.org/wiki/Reactor_pattern - */ - -/* - * Or make this operate on a snapshot of the PackProfile state, then merge results in as long as the snapshot and PackProfile didn't change? - * If the component list changes, start over. - */ - -ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) - : Task(parent) -{ - d.reset(new ComponentUpdateTaskData); - d->m_list = list; - d->mode = mode; - d->netmode = netmode; -} - -ComponentUpdateTask::~ComponentUpdateTask() -{ -} - -void ComponentUpdateTask::executeTask() -{ - qDebug() << "Loading components"; - loadComponents(); -} - -namespace -{ -enum class LoadResult -{ - LoadedLocal, - RequiresRemote, - Failed -}; - -LoadResult composeLoadResult(LoadResult a, LoadResult b) -{ - if (a < b) - { - return b; - } - return a; -} - -static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) -{ - if(component->m_loaded) - { - qDebug() << component->getName() << "is already loaded"; - return LoadResult::LoadedLocal; - } - - LoadResult result = LoadResult::Failed; - auto customPatchFilename = component->getFilename(); - if(QFile::exists(customPatchFilename)) - { - // if local file exists... - - // check for uid problems inside... - bool fileChanged = false; - auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false); - if(file->uid != component->m_uid) - { - file->uid = component->m_uid; - fileChanged = true; - } - if(fileChanged) - { - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename); - } - - component->m_file = file; - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version); - component->m_metaVersion = metaVersion; - if(metaVersion->isLoaded()) - { - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - metaVersion->load(netmode); - loadTask = metaVersion->getCurrentTask(); - if(loadTask) - result = LoadResult::RequiresRemote; - else if (metaVersion->isLoaded()) - result = LoadResult::LoadedLocal; - else - result = LoadResult::Failed; - } - } - return result; -} - -// FIXME: dead code. determine if this can still be useful? -/* -static LoadResult loadPackProfile(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) -{ - if(component->m_loaded) - { - qDebug() << component->getName() << "is already loaded"; - return LoadResult::LoadedLocal; - } - - LoadResult result = LoadResult::Failed; - auto metaList = ENV.metadataIndex()->get(component->m_uid); - if(metaList->isLoaded()) - { - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - metaList->load(netmode); - loadTask = metaList->getCurrentTask(); - result = LoadResult::RequiresRemote; - } - return result; -} -*/ - -static LoadResult loadIndex(shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) -{ - // FIXME: DECIDE. do we want to run the update task anyway? - if(ENV.metadataIndex()->isLoaded()) - { - qDebug() << "Index is already loaded"; - return LoadResult::LoadedLocal; - } - ENV.metadataIndex()->load(netmode); - loadTask = ENV.metadataIndex()->getCurrentTask(); - if(loadTask) - { - return LoadResult::RequiresRemote; - } - // FIXME: this is assuming the load succeeded... did it really? - return LoadResult::LoadedLocal; -} -} - -void ComponentUpdateTask::loadComponents() -{ - LoadResult result = LoadResult::LoadedLocal; - size_t taskIndex = 0; - size_t componentIndex = 0; - d->remoteLoadSuccessful = true; - // load the main index (it is needed to determine if components can revert) - { - // FIXME: tear out as a method? or lambda? - shared_qobject_ptr<Task> indexLoadTask; - auto singleResult = loadIndex(indexLoadTask, d->netmode); - result = composeLoadResult(result, singleResult); - if(indexLoadTask) - { - qDebug() << "Remote loading is being run for metadata index"; - RemoteLoadStatus status; - status.type = RemoteLoadStatus::Type::Index; - d->remoteLoadStatusList.append(status); - connect(indexLoadTask.get(), &Task::succeeded, [=]() - { - remoteLoadSucceeded(taskIndex); - }); - connect(indexLoadTask.get(), &Task::failed, [=](const QString & error) - { - remoteLoadFailed(taskIndex, error); - }); - taskIndex++; - } - } - // load all the components OR their lists... - for (auto component: d->m_list->d->components) - { - shared_qobject_ptr<Task> loadTask; - LoadResult singleResult; - RemoteLoadStatus::Type loadType; - // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that... -#if 0 - switch(d->mode) - { - case Mode::Launch: - { - singleResult = loadComponent(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::Version; - break; - } - case Mode::Resolution: - { - singleResult = loadPackProfile(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::List; - break; - } - } -#else - singleResult = loadComponent(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::Version; -#endif - if(singleResult == LoadResult::LoadedLocal) - { - component->updateCachedData(); - } - result = composeLoadResult(result, singleResult); - if (loadTask) - { - qDebug() << "Remote loading is being run for" << component->getName(); - connect(loadTask.get(), &Task::succeeded, [=]() - { - remoteLoadSucceeded(taskIndex); - }); - connect(loadTask.get(), &Task::failed, [=](const QString & error) - { - remoteLoadFailed(taskIndex, error); - }); - RemoteLoadStatus status; - status.type = loadType; - status.PackProfileIndex = componentIndex; - d->remoteLoadStatusList.append(status); - taskIndex++; - } - componentIndex++; - } - d->remoteTasksInProgress = taskIndex; - switch(result) - { - case LoadResult::LoadedLocal: - { - // Everything got loaded. Advance to dependency resolution. - resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); - break; - } - case LoadResult::RequiresRemote: - { - // we wait for signals. - break; - } - case LoadResult::Failed: - { - emitFailed(tr("Some component metadata load tasks failed.")); - break; - } - } -} - -namespace -{ - struct RequireEx : public Meta::Require - { - size_t indexOfFirstDependee = 0; - }; - struct RequireCompositionResult - { - bool ok; - RequireEx outcome; - }; - using RequireExSet = std::set<RequireEx>; -} - -static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b) -{ - assert(a.uid == b.uid); - RequireEx out; - out.uid = a.uid; - out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee); - if(a.equalsVersion.isEmpty()) - { - out.equalsVersion = b.equalsVersion; - } - else if (b.equalsVersion.isEmpty()) - { - out.equalsVersion = a.equalsVersion; - } - else if (a.equalsVersion == b.equalsVersion) - { - out.equalsVersion = a.equalsVersion; - } - else - { - // FIXME: mark error as explicit version conflict - return {false, out}; - } - - if(a.suggests.isEmpty()) - { - out.suggests = b.suggests; - } - else if (b.suggests.isEmpty()) - { - out.suggests = a.suggests; - } - else - { - Version aVer(a.suggests); - Version bVer(b.suggests); - out.suggests = (aVer < bVer ? b.suggests : a.suggests); - } - return {true, out}; -} - -// gather the requirements from all components, finding any obvious conflicts -static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output) -{ - bool succeeded = true; - size_t componentNum = 0; - for(auto component: input) - { - auto &componentRequires = component->m_cachedRequires; - for(const auto & componentRequire: componentRequires) - { - auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){ - return req.uid == componentRequire.uid; - }); - - RequireEx componenRequireEx; - componenRequireEx.uid = componentRequire.uid; - componenRequireEx.suggests = componentRequire.suggests; - componenRequireEx.equalsVersion = componentRequire.equalsVersion; - componenRequireEx.indexOfFirstDependee = componentNum; - - if(found != output.cend()) - { - // found... process it further - auto result = composeRequirement(componenRequireEx, *found); - if(result.ok) - { - output.erase(componenRequireEx); - output.insert(result.outcome); - } - else - { - qCritical() - << "Conflicting requirements:" - << componentRequire.uid - << "versions:" - << componentRequire.equalsVersion - << ";" - << (*found).equalsVersion; - } - succeeded &= result.ok; - } - else - { - // not found, accumulate - output.insert(componenRequireEx); - } - } - componentNum++; - } - return succeeded; -} - -/// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps) -static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove) -{ - for(const auto & component: components) - { - if(!component->m_dependencyOnly) - continue; - if(!component->m_cachedVolatile) - continue; - RequireEx reqNeedle; - reqNeedle.uid = component->m_uid; - const auto iter = reqs.find(reqNeedle); - if(iter == reqs.cend()) - { - toRemove.append(component->m_uid); - } - } -} - -/** - * handles: - * - trivial addition (there is an unmet requirement and it can be trivially met by adding something) - * - trivial version conflict of dependencies == explicit version required and installed is different - * - * toAdd - set of requirements than mean adding a new component - * toChange - set of requirements that mean changing version of an existing component - */ -static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange) -{ - enum class Decision - { - Undetermined, - Met, - Missing, - VersionNotSame, - LockedVersionNotSame - } decision = Decision::Undetermined; - - QString reqStr; - bool succeeded = true; - // list the composed requirements and say if they are met or unmet - for(auto & req: input) - { - do - { - if(req.equalsVersion.isEmpty()) - { - reqStr = QString("Req: %1").arg(req.uid); - if(index.contains(req.uid)) - { - decision = Decision::Met; - } - else - { - toAdd.insert(req); - decision = Decision::Missing; - } - break; - } - else - { - reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion); - const auto & compIter = index.find(req.uid); - if(compIter == index.cend()) - { - toAdd.insert(req); - decision = Decision::Missing; - break; - } - auto & comp = (*compIter); - if(comp->getVersion() != req.equalsVersion) - { - if(comp->isCustom()) { - decision = Decision::LockedVersionNotSame; - } else { - if(comp->m_dependencyOnly) - { - decision = Decision::VersionNotSame; - } - else - { - decision = Decision::LockedVersionNotSame; - } - } - break; - } - decision = Decision::Met; - } - } while(false); - switch(decision) - { - case Decision::Undetermined: - qCritical() << "No decision for" << reqStr; - succeeded = false; - break; - case Decision::Met: - qDebug() << reqStr << "Is met."; - break; - case Decision::Missing: - qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee; - toAdd.insert(req); - break; - case Decision::VersionNotSame: - qDebug() << reqStr << "already has different version that can be changed."; - toChange.insert(req); - break; - case Decision::LockedVersionNotSame: - qDebug() << reqStr << "already has different version that cannot be changed."; - succeeded = false; - break; - } - } - return succeeded; -} - -// FIXME, TODO: decouple dependency resolution from loading -// FIXME: This works directly with the PackProfile internals. It shouldn't! It needs richer data types than PackProfile uses. -// FIXME: throw all this away and use a graph -void ComponentUpdateTask::resolveDependencies(bool checkOnly) -{ - qDebug() << "Resolving dependencies"; - /* - * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways: - * 1. There are conflicting dependencies on the same uid with different exact version numbers - * -> hard error - * 2. A dependency has non-matching exact version number - * -> hard error - * 3. A dependency is entirely missing and needs to be injected before the dependee(s) - * -> requirements are injected - * - * NOTE: this is a placeholder and should eventually be replaced with something 'serious' - */ - auto & components = d->m_list->d->components; - auto & componentIndex = d->m_list->d->componentIndex; - - RequireExSet allRequires; - QStringList toRemove; - do - { - allRequires.clear(); - toRemove.clear(); - if(!gatherRequirementsFromComponents(components, allRequires)) - { - emitFailed(tr("Conflicting requirements detected during dependency checking!")); - return; - } - getTrivialRemovals(components, allRequires, toRemove); - if(!toRemove.isEmpty()) - { - qDebug() << "Removing obsolete components..."; - for(auto & remove : toRemove) - { - qDebug() << "Removing" << remove; - d->m_list->remove(remove); - } - } - } while (!toRemove.isEmpty()); - RequireExSet toAdd; - RequireExSet toChange; - bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange); - if(!succeeded) - { - emitFailed(tr("Instance has conflicting dependencies.")); - return; - } - if(checkOnly) - { - if(toAdd.size() || toChange.size()) - { - emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch.")); - } - else - { - emitSucceeded(); - } - return; - } - - bool recursionNeeded = false; - if(toAdd.size()) - { - // add stuff... - for(auto &add: toAdd) - { - ComponentPtr component = new Component(d->m_list, add.uid); - if(!add.equalsVersion.isEmpty()) - { - // exact version - qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee; - component->m_version = add.equalsVersion; - } - else - { - // version needs to be decided - qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee; -// ############################################################################################################ -// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. - if(!add.suggests.isEmpty()) - { - component->m_version = add.suggests; - } - else - { - if(add.uid == "org.lwjgl") - { - component->m_version = "2.9.1"; - } - else if (add.uid == "org.lwjgl3") - { - component->m_version = "3.1.2"; - } - else if (add.uid == "net.fabricmc.intermediary") - { - auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){ - return cmp->getID() == "net.minecraft"; - }); - if(minecraft != components.end()) { - component->m_version = (*minecraft)->getVersion(); - } - } - } -// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. -// ############################################################################################################ - } - component->m_dependencyOnly = true; - // FIXME: this should not work directly with the component list - d->m_list->insertComponent(add.indexOfFirstDependee, component); - componentIndex[add.uid] = component; - } - recursionNeeded = true; - } - if(toChange.size()) - { - // change a version of something that exists - for(auto &change: toChange) - { - // FIXME: this should not work directly with the component list - qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion; - auto component = componentIndex[change.uid]; - component->setVersion(change.equalsVersion); - } - recursionNeeded = true; - } - - if(recursionNeeded) - { - loadComponents(); - } - else - { - emitSucceeded(); - } -} - -void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) -{ - auto &taskSlot = d->remoteLoadStatusList[taskIndex]; - if(taskSlot.finished) - { - qWarning() << "Got multiple results from remote load task" << taskIndex; - return; - } - qDebug() << "Remote task" << taskIndex << "succeeded"; - taskSlot.succeeded = false; - taskSlot.finished = true; - d->remoteTasksInProgress --; - // update the cached data of the component from the downloaded version file. - if (taskSlot.type == RemoteLoadStatus::Type::Version) - { - auto component = d->m_list->getComponent(taskSlot.PackProfileIndex); - component->m_loaded = true; - component->updateCachedData(); - } - checkIfAllFinished(); -} - - -void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) -{ - auto &taskSlot = d->remoteLoadStatusList[taskIndex]; - if(taskSlot.finished) - { - qWarning() << "Got multiple results from remote load task" << taskIndex; - return; - } - qDebug() << "Remote task" << taskIndex << "failed: " << msg; - d->remoteLoadSuccessful = false; - taskSlot.succeeded = false; - taskSlot.finished = true; - taskSlot.error = msg; - d->remoteTasksInProgress --; - checkIfAllFinished(); -} - -void ComponentUpdateTask::checkIfAllFinished() -{ - if(d->remoteTasksInProgress) - { - // not yet... - return; - } - if(d->remoteLoadSuccessful) - { - // nothing bad happened... clear the temp load status and proceed with looking at dependencies - d->remoteLoadStatusList.clear(); - resolveDependencies(d->mode == Mode::Launch); - } - else - { - // remote load failed... report error and bail - QStringList allErrorsList; - for(auto & item: d->remoteLoadStatusList) - { - if(!item.succeeded) - { - allErrorsList.append(item.error); - } - } - auto allErrors = allErrorsList.join("\n"); - emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors)); - d->remoteLoadStatusList.clear(); - } -} diff --git a/api/logic/minecraft/ComponentUpdateTask.h b/api/logic/minecraft/ComponentUpdateTask.h deleted file mode 100644 index 4274cabb..00000000 --- a/api/logic/minecraft/ComponentUpdateTask.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "tasks/Task.h" -#include "net/Mode.h" - -#include <memory> -class PackProfile; -struct ComponentUpdateTaskData; - -class ComponentUpdateTask : public Task -{ - Q_OBJECT -public: - enum class Mode - { - Launch, - Resolution - }; - -public: - explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile * list, QObject *parent = 0); - virtual ~ComponentUpdateTask(); - -protected: - void executeTask(); - -private: - void loadComponents(); - void resolveDependencies(bool checkOnly); - - void remoteLoadSucceeded(size_t index); - void remoteLoadFailed(size_t index, const QString &msg); - void checkIfAllFinished(); - -private: - std::unique_ptr<ComponentUpdateTaskData> d; -}; diff --git a/api/logic/minecraft/ComponentUpdateTask_p.h b/api/logic/minecraft/ComponentUpdateTask_p.h deleted file mode 100644 index 5b02431b..00000000 --- a/api/logic/minecraft/ComponentUpdateTask_p.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include <cstddef> -#include <QString> -#include <QList> -#include "net/Mode.h" - -class PackProfile; - -struct RemoteLoadStatus -{ - enum class Type - { - Index, - List, - Version - } type = Type::Version; - size_t PackProfileIndex = 0; - bool finished = false; - bool succeeded = false; - QString error; -}; - -struct ComponentUpdateTaskData -{ - PackProfile * m_list = nullptr; - QList<RemoteLoadStatus> remoteLoadStatusList; - bool remoteLoadSuccessful = true; - size_t remoteTasksInProgress = 0; - ComponentUpdateTask::Mode mode; - Net::Mode netmode; -}; diff --git a/api/logic/minecraft/GradleSpecifier.h b/api/logic/minecraft/GradleSpecifier.h deleted file mode 100644 index 60e0a726..00000000 --- a/api/logic/minecraft/GradleSpecifier.h +++ /dev/null @@ -1,151 +0,0 @@ -#pragma once - -#include <QString> -#include <QStringList> -#include "DefaultVariable.h" - -struct GradleSpecifier -{ - GradleSpecifier() - { - m_valid = false; - } - GradleSpecifier(QString value) - { - operator=(value); - } - GradleSpecifier & operator =(const QString & value) - { - /* - org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar - 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar" - 1 "org.gradle.test.classifiers" - 2 "service" - 3 "1.0" - 4 "jdk15" - 5 "jar" - */ - QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?"); - m_valid = matcher.exactMatch(value); - if(!m_valid) { - m_invalidValue = value; - return *this; - } - auto elements = matcher.capturedTexts(); - m_groupId = elements[1]; - m_artifactId = elements[2]; - m_version = elements[3]; - m_classifier = elements[4]; - if(!elements[5].isEmpty()) - { - m_extension = elements[5]; - } - return *this; - } - QString serialize() const - { - if(!m_valid) { - return m_invalidValue; - } - QString retval = m_groupId + ":" + m_artifactId + ":" + m_version; - if(!m_classifier.isEmpty()) - { - retval += ":" + m_classifier; - } - if(m_extension.isExplicit()) - { - retval += "@" + m_extension; - } - return retval; - } - QString getFileName() const - { - if(!m_valid) { - return QString(); - } - QString filename = m_artifactId + '-' + m_version; - if(!m_classifier.isEmpty()) - { - filename += "-" + m_classifier; - } - filename += "." + m_extension; - return filename; - } - QString toPath(const QString & filenameOverride = QString()) const - { - if(!m_valid) { - return QString(); - } - QString filename; - if(filenameOverride.isEmpty()) - { - filename = getFileName(); - } - else - { - filename = filenameOverride; - } - QString path = m_groupId; - path.replace('.', '/'); - path += '/' + m_artifactId + '/' + m_version + '/' + filename; - return path; - } - inline bool valid() const - { - return m_valid; - } - inline QString version() const - { - return m_version; - } - inline QString groupId() const - { - return m_groupId; - } - inline QString artifactId() const - { - return m_artifactId; - } - inline void setClassifier(const QString & classifier) - { - m_classifier = classifier; - } - inline QString classifier() const - { - return m_classifier; - } - inline QString extension() const - { - return m_extension; - } - inline QString artifactPrefix() const - { - return m_groupId + ":" + m_artifactId; - } - bool matchName(const GradleSpecifier & other) const - { - return other.artifactId() == artifactId() && other.groupId() == groupId(); - } - bool operator==(const GradleSpecifier & other) const - { - if(m_groupId != other.m_groupId) - return false; - if(m_artifactId != other.m_artifactId) - return false; - if(m_version != other.m_version) - return false; - if(m_classifier != other.m_classifier) - return false; - if(m_extension != other.m_extension) - return false; - return true; - } -private: - QString m_invalidValue; - QString m_groupId; - QString m_artifactId; - QString m_version; - QString m_classifier; - DefaultVariable<QString> m_extension = DefaultVariable<QString>("jar"); - bool m_valid = false; -}; diff --git a/api/logic/minecraft/GradleSpecifier_test.cpp b/api/logic/minecraft/GradleSpecifier_test.cpp deleted file mode 100644 index 0900c9d8..00000000 --- a/api/logic/minecraft/GradleSpecifier_test.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "minecraft/GradleSpecifier.h" - -class GradleSpecifierTest : public QObject -{ - Q_OBJECT -private -slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void test_Positive_data() - { - QTest::addColumn<QString>("through"); - - QTest::newRow("3 parter") << "org.gradle.test.classifiers:service:1.0"; - QTest::newRow("classifier") << "org.gradle.test.classifiers:service:1.0:jdk15"; - QTest::newRow("jarextension") << "org.gradle.test.classifiers:service:1.0@jar"; - QTest::newRow("jarboth") << "org.gradle.test.classifiers:service:1.0:jdk15@jar"; - QTest::newRow("packxz") << "org.gradle.test.classifiers:service:1.0:jdk15@jar.pack.xz"; - } - void test_Positive() - { - QFETCH(QString, through); - - QString converted = GradleSpecifier(through).serialize(); - - QCOMPARE(converted, through); - } - - void test_Path_data() - { - QTest::addColumn<QString>("spec"); - QTest::addColumn<QString>("expected"); - - QTest::newRow("3 parter") << "group.id:artifact:1.0" << "group/id/artifact/1.0/artifact-1.0.jar"; - QTest::newRow("doom") << "id.software:doom:1.666:demons@wad" << "id/software/doom/1.666/doom-1.666-demons.wad"; - } - void test_Path() - { - QFETCH(QString, spec); - QFETCH(QString, expected); - - QString converted = GradleSpecifier(spec).toPath(); - - QCOMPARE(converted, expected); - } - void test_Negative_data() - { - QTest::addColumn<QString>("input"); - - QTest::newRow("too many :") << "org:gradle.test:class:::ifiers:service:1.0::"; - QTest::newRow("nonsense") << "I like turtles"; - QTest::newRow("empty string") << ""; - QTest::newRow("missing version") << "herp.derp:artifact"; - } - void test_Negative() - { - QFETCH(QString, input); - - GradleSpecifier spec(input); - QVERIFY(!spec.valid()); - QCOMPARE(spec.serialize(), input); - QCOMPARE(spec.toPath(), QString()); - } -}; - -QTEST_GUILESS_MAIN(GradleSpecifierTest) - -#include "GradleSpecifier_test.moc" diff --git a/api/logic/minecraft/LaunchProfile.cpp b/api/logic/minecraft/LaunchProfile.cpp deleted file mode 100644 index 41705187..00000000 --- a/api/logic/minecraft/LaunchProfile.cpp +++ /dev/null @@ -1,319 +0,0 @@ -#include "LaunchProfile.h" -#include <Version.h> - -void LaunchProfile::clear() -{ - m_minecraftVersion.clear(); - m_minecraftVersionType.clear(); - m_minecraftAssets.reset(); - m_minecraftArguments.clear(); - m_tweakers.clear(); - m_mainClass.clear(); - m_appletClass.clear(); - m_libraries.clear(); - m_mavenFiles.clear(); - m_traits.clear(); - m_jarMods.clear(); - m_mainJar.reset(); - m_problemSeverity = ProblemSeverity::None; -} - -static void applyString(const QString & from, QString & to) -{ - if(from.isEmpty()) - return; - to = from; -} - -void LaunchProfile::applyMinecraftVersion(const QString& id) -{ - applyString(id, this->m_minecraftVersion); -} - -void LaunchProfile::applyAppletClass(const QString& appletClass) -{ - applyString(appletClass, this->m_appletClass); -} - -void LaunchProfile::applyMainClass(const QString& mainClass) -{ - applyString(mainClass, this->m_mainClass); -} - -void LaunchProfile::applyMinecraftArguments(const QString& minecraftArguments) -{ - applyString(minecraftArguments, this->m_minecraftArguments); -} - -void LaunchProfile::applyMinecraftVersionType(const QString& type) -{ - applyString(type, this->m_minecraftVersionType); -} - -void LaunchProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets) -{ - if(assets) - { - m_minecraftAssets = assets; - } -} - -void LaunchProfile::applyTraits(const QSet<QString>& traits) -{ - this->m_traits.unite(traits); -} - -void LaunchProfile::applyTweakers(const QStringList& tweakers) -{ - // if the applied tweakers override an existing one, skip it. this effectively moves it later in the sequence - QStringList newTweakers; - for(auto & tweaker: m_tweakers) - { - if (tweakers.contains(tweaker)) - { - continue; - } - newTweakers.append(tweaker); - } - // then just append the new tweakers (or moved original ones) - newTweakers += tweakers; - m_tweakers = newTweakers; -} - -void LaunchProfile::applyJarMods(const QList<LibraryPtr>& jarMods) -{ - this->m_jarMods.append(jarMods); -} - -static int findLibraryByName(QList<LibraryPtr> *haystack, const GradleSpecifier &needle) -{ - int retval = -1; - for (int i = 0; i < haystack->size(); ++i) - { - if (haystack->at(i)->rawName().matchName(needle)) - { - // only one is allowed. - if (retval != -1) - return -1; - retval = i; - } - } - return retval; -} - -void LaunchProfile::applyMods(const QList<LibraryPtr>& mods) -{ - QList<LibraryPtr> * list = &m_mods; - for(auto & mod: mods) - { - auto modCopy = Library::limitedCopy(mod); - - // find the mod by name. - const int index = findLibraryByName(list, mod->rawName()); - // mod not found? just add it. - if (index < 0) - { - list->append(modCopy); - return; - } - - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(mod->version()) > Version(existingLibrary->version())) - { - list->replace(index, modCopy); - } - } -} - -void LaunchProfile::applyLibrary(LibraryPtr library) -{ - if(!library->isActive()) - { - return; - } - - QList<LibraryPtr> * list = &m_libraries; - if(library->isNative()) - { - list = &m_nativeLibraries; - } - - auto libraryCopy = Library::limitedCopy(library); - - // find the library by name. - const int index = findLibraryByName(list, library->rawName()); - // library not found? just add it. - if (index < 0) - { - list->append(libraryCopy); - return; - } - - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(library->version()) > Version(existingLibrary->version())) - { - list->replace(index, libraryCopy); - } -} - -void LaunchProfile::applyMavenFile(LibraryPtr mavenFile) -{ - if(!mavenFile->isActive()) - { - return; - } - - if(mavenFile->isNative()) - { - return; - } - - // unlike libraries, we do not keep only one version or try to dedupe them - m_mavenFiles.append(Library::limitedCopy(mavenFile)); -} - -const LibraryPtr LaunchProfile::getMainJar() const -{ - return m_mainJar; -} - -void LaunchProfile::applyMainJar(LibraryPtr jar) -{ - if(jar) - { - m_mainJar = jar; - } -} - -void LaunchProfile::applyProblemSeverity(ProblemSeverity severity) -{ - if (m_problemSeverity < severity) - { - m_problemSeverity = severity; - } -} - -const QList<PatchProblem> LaunchProfile::getProblems() const -{ - // FIXME: implement something that actually makes sense here - return {}; -} - -QString LaunchProfile::getMinecraftVersion() const -{ - return m_minecraftVersion; -} - -QString LaunchProfile::getAppletClass() const -{ - return m_appletClass; -} - -QString LaunchProfile::getMainClass() const -{ - return m_mainClass; -} - -const QSet<QString> &LaunchProfile::getTraits() const -{ - return m_traits; -} - -const QStringList & LaunchProfile::getTweakers() const -{ - return m_tweakers; -} - -bool LaunchProfile::hasTrait(const QString& trait) const -{ - return m_traits.contains(trait); -} - -ProblemSeverity LaunchProfile::getProblemSeverity() const -{ - return m_problemSeverity; -} - -QString LaunchProfile::getMinecraftVersionType() const -{ - return m_minecraftVersionType; -} - -std::shared_ptr<MojangAssetIndexInfo> LaunchProfile::getMinecraftAssets() const -{ - if(!m_minecraftAssets) - { - return std::make_shared<MojangAssetIndexInfo>("legacy"); - } - return m_minecraftAssets; -} - -QString LaunchProfile::getMinecraftArguments() const -{ - return m_minecraftArguments; -} - -const QList<LibraryPtr> & LaunchProfile::getJarMods() const -{ - return m_jarMods; -} - -const QList<LibraryPtr> & LaunchProfile::getLibraries() const -{ - return m_libraries; -} - -const QList<LibraryPtr> & LaunchProfile::getNativeLibraries() const -{ - return m_nativeLibraries; -} - -const QList<LibraryPtr> & LaunchProfile::getMavenFiles() const -{ - return m_mavenFiles; -} - -void LaunchProfile::getLibraryFiles( - const QString& architecture, - QStringList& jars, - QStringList& nativeJars, - const QString& overridePath, - const QString& tempPath -) const -{ - QStringList native32, native64; - jars.clear(); - nativeJars.clear(); - for (auto lib : getLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - // NOTE: order is important here, add main jar last to the lists - if(m_mainJar) - { - // FIXME: HACK!! jar modding is weird and unsystematic! - if(m_jarMods.size()) - { - QDir tempDir(tempPath); - jars.append(tempDir.absoluteFilePath("minecraft.jar")); - } - else - { - m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - } - for (auto lib : getNativeLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - if(architecture == "32") - { - nativeJars.append(native32); - } - else if(architecture == "64") - { - nativeJars.append(native64); - } -} diff --git a/api/logic/minecraft/LaunchProfile.h b/api/logic/minecraft/LaunchProfile.h deleted file mode 100644 index c1752531..00000000 --- a/api/logic/minecraft/LaunchProfile.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once -#include <QString> -#include "Library.h" -#include <ProblemProvider.h> - -class LaunchProfile: public ProblemProvider -{ -public: - virtual ~LaunchProfile() {}; - -public: /* application of profile variables from patches */ - void applyMinecraftVersion(const QString& id); - void applyMainClass(const QString& mainClass); - void applyAppletClass(const QString& appletClass); - void applyMinecraftArguments(const QString& minecraftArguments); - void applyMinecraftVersionType(const QString& type); - void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); - void applyTraits(const QSet<QString> &traits); - void applyTweakers(const QStringList &tweakers); - void applyJarMods(const QList<LibraryPtr> &jarMods); - void applyMods(const QList<LibraryPtr> &jarMods); - void applyLibrary(LibraryPtr library); - void applyMavenFile(LibraryPtr library); - void applyMainJar(LibraryPtr jar); - void applyProblemSeverity(ProblemSeverity severity); - /// clear the profile - void clear(); - -public: /* getters for profile variables */ - QString getMinecraftVersion() const; - QString getMainClass() const; - QString getAppletClass() const; - QString getMinecraftVersionType() const; - MojangAssetIndexInfo::Ptr getMinecraftAssets() const; - QString getMinecraftArguments() const; - const QSet<QString> & getTraits() const; - const QStringList & getTweakers() const; - const QList<LibraryPtr> & getJarMods() const; - const QList<LibraryPtr> & getLibraries() const; - const QList<LibraryPtr> & getNativeLibraries() const; - const QList<LibraryPtr> & getMavenFiles() const; - const LibraryPtr getMainJar() const; - void getLibraryFiles( - const QString & architecture, - QStringList & jars, - QStringList & nativeJars, - const QString & overridePath, - const QString & tempPath - ) const; - bool hasTrait(const QString & trait) const; - ProblemSeverity getProblemSeverity() const override; - const QList<PatchProblem> getProblems() const override; - -private: - /// the version of Minecraft - jar to use - QString m_minecraftVersion; - - /// Release type - "release" or "snapshot" - QString m_minecraftVersionType; - - /// Assets type - "legacy" or a version ID - MojangAssetIndexInfo::Ptr m_minecraftAssets; - - /** - * arguments that should be used for launching minecraft - * - * ex: "--username ${auth_player_name} --session ${auth_session} - * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" - */ - QString m_minecraftArguments; - - /// A list of all tweaker classes - QStringList m_tweakers; - - /// The main class to load first - QString m_mainClass; - - /// The applet class, for some very old minecraft releases - QString m_appletClass; - - /// the list of libraries - QList<LibraryPtr> m_libraries; - - /// the list of maven files to be placed in the libraries folder, but not acted upon - QList<LibraryPtr> m_mavenFiles; - - /// the main jar - LibraryPtr m_mainJar; - - /// the list of native libraries - QList<LibraryPtr> m_nativeLibraries; - - /// traits, collected from all the version files (version files can only add) - QSet<QString> m_traits; - - /// A list of jar mods. version files can add those. - QList<LibraryPtr> m_jarMods; - - /// the list of mods - QList<LibraryPtr> m_mods; - - ProblemSeverity m_problemSeverity = ProblemSeverity::None; - -}; diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp deleted file mode 100644 index f2293679..00000000 --- a/api/logic/minecraft/Library.cpp +++ /dev/null @@ -1,309 +0,0 @@ -#include "Library.h" -#include "MinecraftInstance.h" - -#include <net/Download.h> -#include <net/ChecksumValidator.h> -#include <Env.h> -#include <FileSystem.h> -#include <BuildConfig.h> - - -void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32, - QStringList& native64, const QString &overridePath) const -{ - bool local = isLocal(); - auto actualPath = [&](QString relPath) - { - QFileInfo out(FS::PathCombine(storagePrefix(), relPath)); - if(local && !overridePath.isEmpty()) - { - QString fileName = out.fileName(); - return QFileInfo(FS::PathCombine(overridePath, fileName)).absoluteFilePath(); - } - return out.absoluteFilePath(); - }; - QString raw_storage = storageSuffix(system); - if(isNative()) - { - if (raw_storage.contains("${arch}")) - { - auto nat32Storage = raw_storage; - nat32Storage.replace("${arch}", "32"); - auto nat64Storage = raw_storage; - nat64Storage.replace("${arch}", "64"); - native32 += actualPath(nat32Storage); - native64 += actualPath(nat64Storage); - } - else - { - native += actualPath(raw_storage); - } - } - else - { - jar += actualPath(raw_storage); - } -} - -QList< std::shared_ptr< NetAction > > Library::getDownloads( - OpSys system, - class HttpMetaCache* cache, - QStringList& failedLocalFiles, - const QString & overridePath -) const -{ - QList<NetActionPtr> out; - bool stale = isAlwaysStale(); - bool local = isLocal(); - - auto check_local_file = [&](QString storage) - { - QFileInfo fileinfo(storage); - QString fileName = fileinfo.fileName(); - auto fullPath = FS::PathCombine(overridePath, fileName); - QFileInfo localFileInfo(fullPath); - if(!localFileInfo.exists()) - { - failedLocalFiles.append(localFileInfo.filePath()); - return false; - } - return true; - }; - - auto add_download = [&](QString storage, QString url, QString sha1) - { - if(local) - { - return check_local_file(storage); - } - auto entry = cache->resolveEntry("libraries", storage); - if(stale) - { - entry->setStale(true); - } - if (!entry->isStale()) - return true; - Net::Download::Options options; - if(stale) - { - options |= Net::Download::Option::AcceptLocalFiles; - } - - if(sha1.size()) - { - auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); - auto dl = Net::Download::makeCached(url, entry, options); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; - out.append(dl); - } - else - { - out.append(Net::Download::makeCached(url, entry, options)); - qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; - } - return true; - }; - - QString raw_storage = storageSuffix(system); - if(m_mojangDownloads) - { - if(isNative()) - { - if(m_nativeClassifiers.contains(system)) - { - auto nativeClassifier = m_nativeClassifiers[system]; - if(nativeClassifier.contains("${arch}")) - { - auto nat32Classifier = nativeClassifier; - nat32Classifier.replace("${arch}", "32"); - auto nat64Classifier = nativeClassifier; - nat64Classifier.replace("${arch}", "64"); - auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier); - if(nat32info) - { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "32"); - add_download(cooked_storage, nat32info->url, nat32info->sha1); - } - auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); - if(nat64info) - { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "64"); - add_download(cooked_storage, nat64info->url, nat64info->sha1); - } - } - else - { - auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); - if(info) - { - add_download(raw_storage, info->url, info->sha1); - } - } - } - else - { - qDebug() << "Ignoring native library" << m_name.serialize() << "because it has no classifier for current OS"; - } - } - else - { - if(m_mojangDownloads->artifact) - { - auto artifact = m_mojangDownloads->artifact; - add_download(raw_storage, artifact->url, artifact->sha1); - } - else - { - qDebug() << "Ignoring java library" << m_name.serialize() << "because it has no artifact"; - } - } - } - else - { - auto raw_dl = [&]() - { - if (!m_absoluteURL.isEmpty()) - { - return m_absoluteURL; - } - - if (m_repositoryURL.isEmpty()) - { - return BuildConfig.LIBRARY_BASE + raw_storage; - } - - if(m_repositoryURL.endsWith('/')) - { - return m_repositoryURL + raw_storage; - } - else - { - return m_repositoryURL + QChar('/') + raw_storage; - } - }(); - if (raw_storage.contains("${arch}")) - { - QString cooked_storage = raw_storage; - QString cooked_dl = raw_dl; - add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"), QString()); - cooked_storage = raw_storage; - cooked_dl = raw_dl; - add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"), QString()); - } - else - { - add_download(raw_storage, raw_dl, QString()); - } - } - return out; -} - -bool Library::isActive() const -{ - bool result = true; - if (m_rules.empty()) - { - result = true; - } - else - { - RuleAction ruleResult = Disallow; - for (auto rule : m_rules) - { - RuleAction temp = rule->apply(this); - if (temp != Defer) - ruleResult = temp; - } - result = result && (ruleResult == Allow); - } - if (isNative()) - { - result = result && m_nativeClassifiers.contains(currentSystem); - } - return result; -} - -bool Library::isLocal() const -{ - return m_hint == "local"; -} - -bool Library::isAlwaysStale() const -{ - return m_hint == "always-stale"; -} - -void Library::setStoragePrefix(QString prefix) -{ - m_storagePrefix = prefix; -} - -QString Library::defaultStoragePrefix() -{ - return "libraries/"; -} - -QString Library::storagePrefix() const -{ - if(m_storagePrefix.isEmpty()) - { - return defaultStoragePrefix(); - } - return m_storagePrefix; -} - -QString Library::filename(OpSys system) const -{ - if(!m_filename.isEmpty()) - { - return m_filename; - } - // non-native? use only the gradle specifier - if (!isNative()) - { - return m_name.getFileName(); - } - - // otherwise native, override classifiers. Mojang HACK! - GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) - { - nativeSpec.setClassifier(m_nativeClassifiers[system]); - } - else - { - nativeSpec.setClassifier("INVALID"); - } - return nativeSpec.getFileName(); -} - -QString Library::displayName(OpSys system) const -{ - if(!m_displayname.isEmpty()) - return m_displayname; - return filename(system); -} - -QString Library::storageSuffix(OpSys system) const -{ - // non-native? use only the gradle specifier - if (!isNative()) - { - return m_name.toPath(m_filename); - } - - // otherwise native, override classifiers. Mojang HACK! - GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) - { - nativeSpec.setClassifier(m_nativeClassifiers[system]); - } - else - { - nativeSpec.setClassifier("INVALID"); - } - return nativeSpec.toPath(m_filename); -} diff --git a/api/logic/minecraft/Library.h b/api/logic/minecraft/Library.h deleted file mode 100644 index acdd6c9c..00000000 --- a/api/logic/minecraft/Library.h +++ /dev/null @@ -1,219 +0,0 @@ -#pragma once -#include <QString> -#include <net/NetAction.h> -#include <QPair> -#include <QList> -#include <QStringList> -#include <QMap> -#include <QDir> -#include <QUrl> -#include <memory> - -#include "Rule.h" -#include "minecraft/OpSys.h" -#include "GradleSpecifier.h" -#include "MojangDownloadInfo.h" - -#include "multimc_logic_export.h" - -class Library; -class MinecraftInstance; - -typedef std::shared_ptr<Library> LibraryPtr; - -class MULTIMC_LOGIC_EXPORT Library -{ - friend class OneSixVersionFormat; - friend class MojangVersionFormat; - friend class LibraryTest; -public: - Library() - { - } - Library(const QString &name) - { - m_name = name; - } - /// limited copy without some data. TODO: why? - static LibraryPtr limitedCopy(LibraryPtr base) - { - auto newlib = std::make_shared<Library>(); - newlib->m_name = base->m_name; - newlib->m_repositoryURL = base->m_repositoryURL; - newlib->m_hint = base->m_hint; - newlib->m_absoluteURL = base->m_absoluteURL; - newlib->m_extractExcludes = base->m_extractExcludes; - newlib->m_nativeClassifiers = base->m_nativeClassifiers; - newlib->m_rules = base->m_rules; - newlib->m_storagePrefix = base->m_storagePrefix; - newlib->m_mojangDownloads = base->m_mojangDownloads; - newlib->m_filename = base->m_filename; - return newlib; - } - -public: /* methods */ - /// Returns the raw name field - const GradleSpecifier & rawName() const - { - return m_name; - } - - void setRawName(const GradleSpecifier & spec) - { - m_name = spec; - } - - void setClassifier(const QString & spec) - { - m_name.setClassifier(spec); - } - - /// returns the full group and artifact prefix - QString artifactPrefix() const - { - return m_name.artifactPrefix(); - } - - /// get the artifact ID - QString artifactId() const - { - return m_name.artifactId(); - } - - /// get the artifact version - QString version() const - { - return m_name.version(); - } - - /// Returns true if the library is native - bool isNative() const - { - return m_nativeClassifiers.size() != 0; - } - - void setStoragePrefix(QString prefix = QString()); - - /// Set the url base for downloads - void setRepositoryURL(const QString &base_url) - { - m_repositoryURL = base_url; - } - - void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native, - QStringList & native32, QStringList & native64, const QString & overridePath) const; - - void setAbsoluteUrl(const QString &absolute_url) - { - m_absoluteURL = absolute_url; - } - - void setFilename(const QString &filename) - { - m_filename = filename; - } - - /// Get the file name of the library - QString filename(OpSys system) const; - - // DEPRECATED: set a display name, used by jar mods only - void setDisplayName(const QString & displayName) - { - m_displayname = displayName; - } - - /// Get the file name of the library - QString displayName(OpSys system) const; - - void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) - { - m_mojangDownloads = info; - } - - void setHint(const QString &hint) - { - m_hint = hint; - } - - /// Set the load rules - void setRules(QList<std::shared_ptr<Rule>> rules) - { - m_rules = rules; - } - - /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive() const; - - /// Returns true if the library is contained in an instance and false if it is shared - bool isLocal() const; - - /// Returns true if the library is to always be checked for updates - bool isAlwaysStale() const; - - /// Return true if the library requires forge XZ hacks - bool isForge() const; - - // Get a list of downloads for this library - QList<NetActionPtr> getDownloads(OpSys system, class HttpMetaCache * cache, - QStringList & failedLocalFiles, const QString & overridePath) const; - -private: /* methods */ - /// the default storage prefix used by MultiMC - static QString defaultStoragePrefix(); - - /// Get the prefix - root of the storage to be used - QString storagePrefix() const; - - /// Get the relative file path where the library should be saved - QString storageSuffix(OpSys system) const; - - QString hint() const - { - return m_hint; - } - -protected: /* data */ - /// the basic gradle dependency specifier. - GradleSpecifier m_name; - - /// DEPRECATED URL prefix of the maven repo where the file can be downloaded - QString m_repositoryURL; - - /// DEPRECATED: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined - QString m_absoluteURL; - - /// MultiMC extension - filename override - QString m_filename; - - /// DEPRECATED MultiMC extension - display name - QString m_displayname; - - /** - * MultiMC-specific type hint - modifies how the library is treated - */ - QString m_hint; - - /** - * storage - by default the local libraries folder in multimc, but could be elsewhere - * MultiMC specific, because of FTB. - */ - QString m_storagePrefix; - - /// true if the library had an extract/excludes section (even empty) - bool m_hasExcludes = false; - - /// a list of files that shouldn't be extracted from the library - QStringList m_extractExcludes; - - /// native suffixes per OS - QMap<OpSys, QString> m_nativeClassifiers; - - /// true if the library had a rules section (even empty) - bool applyRules = false; - - /// rules associated with the library - QList<std::shared_ptr<Rule>> m_rules; - - /// MOJANG: container with Mojang style download info - MojangLibraryDownloadInfo::Ptr m_mojangDownloads; -}; diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp deleted file mode 100644 index 75bb4db1..00000000 --- a/api/logic/minecraft/Library_test.cpp +++ /dev/null @@ -1,272 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "minecraft/MojangVersionFormat.h" -#include "minecraft/OneSixVersionFormat.h" -#include "minecraft/Library.h" -#include "net/HttpMetaCache.h" -#include "FileSystem.h" - -class LibraryTest : public QObject -{ - Q_OBJECT -private: - LibraryPtr readMojangJson(const char *file) - { - auto path = QFINDTESTDATA(file); - QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); - auto data = jsonFile.readAll(); - jsonFile.close(); - ProblemContainer problems; - return MojangVersionFormat::libraryFromJson(problems, QJsonDocument::fromJson(data).object(), file); - } - // get absolute path to expected storage, assuming default cache prefix - QStringList getStorage(QString relative) - { - return {FS::PathCombine(cache->getBasePath("libraries"), relative)}; - } -private -slots: - void initTestCase() - { - cache.reset(new HttpMetaCache()); - cache->addBase("libraries", QDir("libraries").absolutePath()); - dataDir = QDir("data").absolutePath(); - } - void test_legacy() - { - Library test("test.package:testname:testversion"); - QCOMPARE(test.artifactPrefix(), QString("test.package:testname")); - QCOMPARE(test.isNative(), false); - - QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString()); - QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar")); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - void test_legacy_url() - { - QStringList failedFiles; - Library test("test.package:testname:testversion"); - test.setRepositoryURL("file://foo/bar"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); - QCOMPARE(downloads.size(), 1); - QCOMPARE(failedFiles, {}); - NetActionPtr dl = downloads[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar")); - } - void test_legacy_url_local_broken() - { - Library test("test.package:testname:testversion"); - QCOMPARE(test.isNative(), false); - QStringList failedFiles; - test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); - QCOMPARE(downloads.size(), 0); - QCOMPARE(failedFiles, {"testname-testversion.jar"}); - } - void test_legacy_url_local_override() - { - Library test("com.paulscode:codecwav:20101023"); - QCOMPARE(test.isNative(), false); - QStringList failedFiles; - test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data")); - QCOMPARE(downloads.size(), 0); - qDebug() << failedFiles; - QCOMPARE(failedFiles.size(), 0); - - QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - void test_legacy_native() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux"; - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar")); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - auto dl = dls[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar")); - } - } - void test_legacy_native_arch() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; - test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}"; - test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}"; - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); - } - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); - } - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar")); - } - } - void test_legacy_native_arch_local_override() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; - test.setHint("local"); - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()}); - QCOMPARE(native64, {QFileInfo("data/testname-testversion-linux-64.jar").absoluteFilePath()}); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {"data/testname-testversion-linux-64.jar"}); - } - } - void test_onenine() - { - auto test = readMojangJson("data/lib-simple.json"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar")); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); - } - test->setHint("local"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {}); - } - } - void test_onenine_local_override() - { - auto test = readMojangJson("data/lib-simple.json"); - test->setHint("local"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {}); - } - } - void test_onenine_native() - { - auto test = readMojangJson("data/lib-native.json"); - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, QStringList()); - QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - QStringList failedFiles; - auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); - } - void test_onenine_native_arch() - { - auto test = readMojangJson("data/lib-native-arch.json"); - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); - QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); - QStringList failedFiles; - auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); - } -private: - std::unique_ptr<HttpMetaCache> cache; - QString dataDir; -}; - -QTEST_GUILESS_MAIN(LibraryTest) - -#include "Library_test.moc" diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp deleted file mode 100644 index dbf9f816..00000000 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ /dev/null @@ -1,1054 +0,0 @@ -#include "MinecraftInstance.h" -#include <minecraft/launch/CreateGameFolders.h> -#include <minecraft/launch/ExtractNatives.h> -#include <minecraft/launch/PrintInstanceInfo.h> -#include <settings/Setting.h> -#include "settings/SettingsObject.h" -#include "Env.h" -#include <MMCStrings.h> -#include <pathmatcher/RegexpMatcher.h> -#include <pathmatcher/MultiMatcher.h> -#include <FileSystem.h> -#include <java/JavaVersion.h> - -#include "launch/LaunchTask.h" -#include "launch/steps/LookupServerAddress.h" -#include "launch/steps/PostLaunchCommand.h" -#include "launch/steps/Update.h" -#include "launch/steps/PreLaunchCommand.h" -#include "launch/steps/TextPrint.h" -#include "minecraft/launch/LauncherPartLaunch.h" -#include "minecraft/launch/DirectJavaLaunch.h" -#include "minecraft/launch/ModMinecraftJar.h" -#include "minecraft/launch/ClaimAccount.h" -#include "minecraft/launch/ReconstructAssets.h" -#include "minecraft/launch/ScanModFolders.h" -#include "minecraft/launch/VerifyJavaInstall.h" -#include "java/launch/CheckJava.h" -#include "java/JavaUtils.h" -#include "meta/Index.h" -#include "meta/VersionList.h" - -#include "mod/ModFolderModel.h" -#include "mod/ResourcePackFolderModel.h" -#include "mod/TexturePackFolderModel.h" -#include "WorldList.h" - -#include "icons/IIconList.h" - -#include <QCoreApplication> -#include "PackProfile.h" -#include "AssetsUtils.h" -#include "MinecraftUpdate.h" -#include "MinecraftLoadAndCheck.h" -#include <minecraft/gameoptions/GameOptions.h> -#include <minecraft/update/FoldersTask.h> - -#define IBUS "@im=ibus" - -// all of this because keeping things compatible with deprecated old settings -// if either of the settings {a, b} is true, this also resolves to true -class OrSetting : public Setting -{ - Q_OBJECT -public: - OrSetting(QString id, std::shared_ptr<Setting> a, std::shared_ptr<Setting> b) - :Setting({id}, false), m_a(a), m_b(b) - { - } - virtual QVariant get() const - { - bool a = m_a->get().toBool(); - bool b = m_b->get().toBool(); - return a || b; - } - virtual void reset() {} - virtual void set(QVariant value) {} -private: - std::shared_ptr<Setting> m_a; - std::shared_ptr<Setting> m_b; -}; - -MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : BaseInstance(globalSettings, settings, rootDir) -{ - // Java Settings - auto javaOverride = m_settings->registerSetting("OverrideJava", false); - auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false); - auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false); - - // combinations - 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); - - // 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); - - // Game time - auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); - m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); - m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride); - - // Join server on launch, this does not have a global override - m_settings->registerSetting("JoinServerOnLaunch", false); - m_settings->registerSetting("JoinServerOnLaunchAddress", ""); - - // DEPRECATED: Read what versions the user configuration thinks should be used - m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); - m_settings->registerSetting("LWJGLVersion", ""); - m_settings->registerSetting("ForgeVersion", ""); - m_settings->registerSetting("LiteloaderVersion", ""); - - m_components.reset(new PackProfile(this)); - m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString()); - auto setting = m_settings->getSetting("LWJGLVersion"); - m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString()); - m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString()); - m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString()); -} - -void MinecraftInstance::saveNow() -{ - m_components->saveNow(); -} - -QString MinecraftInstance::typeName() const -{ - return "Minecraft"; -} - -std::shared_ptr<PackProfile> MinecraftInstance::getPackProfile() const -{ - return m_components; -} - -QSet<QString> MinecraftInstance::traits() const -{ - auto components = getPackProfile(); - if (!components) - { - return {"version-incomplete"}; - } - auto profile = components->getProfile(); - if (!profile) - { - return {"version-incomplete"}; - } - return profile->getTraits(); -} - -QString MinecraftInstance::gameRoot() const -{ - QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - - if (mcDir.exists() && !dotMCDir.exists()) - return mcDir.filePath(); - else - return dotMCDir.filePath(); -} - -QString MinecraftInstance::binRoot() const -{ - return FS::PathCombine(gameRoot(), "bin"); -} - -QString MinecraftInstance::getNativePath() const -{ - QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); - return natives_dir.absolutePath(); -} - -QString MinecraftInstance::getLocalLibraryPath() const -{ - QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/")); - return libraries_dir.absolutePath(); -} - -QString MinecraftInstance::jarModsDir() const -{ - QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/")); - return jarmods_dir.absolutePath(); -} - -QString MinecraftInstance::loaderModsDir() const -{ - return FS::PathCombine(gameRoot(), "mods"); -} - -QString MinecraftInstance::modsCacheLocation() const -{ - return FS::PathCombine(instanceRoot(), "mods.cache"); -} - -QString MinecraftInstance::coreModsDir() const -{ - return FS::PathCombine(gameRoot(), "coremods"); -} - -QString MinecraftInstance::resourcePacksDir() const -{ - return FS::PathCombine(gameRoot(), "resourcepacks"); -} - -QString MinecraftInstance::texturePacksDir() const -{ - return FS::PathCombine(gameRoot(), "texturepacks"); -} - -QString MinecraftInstance::instanceConfigFolder() const -{ - return FS::PathCombine(gameRoot(), "config"); -} - -QString MinecraftInstance::libDir() const -{ - return FS::PathCombine(gameRoot(), "lib"); -} - -QString MinecraftInstance::worldDir() const -{ - return FS::PathCombine(gameRoot(), "saves"); -} - -QString MinecraftInstance::resourcesDir() const -{ - return FS::PathCombine(gameRoot(), "resources"); -} - -QDir MinecraftInstance::librariesPath() const -{ - return QDir::current().absoluteFilePath("libraries"); -} - -QDir MinecraftInstance::jarmodsPath() const -{ - return QDir(jarModsDir()); -} - -QDir MinecraftInstance::versionsPath() const -{ - return QDir::current().absoluteFilePath("versions"); -} - -QStringList MinecraftInstance::getClassPath() const -{ - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - auto profile = m_components->getProfile(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return jars; -} - -QString MinecraftInstance::getMainClass() const -{ - auto profile = m_components->getProfile(); - return profile->getMainClass(); -} - -QStringList MinecraftInstance::getNativeJars() const -{ - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - auto profile = m_components->getProfile(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return nativeJars; -} - -QStringList MinecraftInstance::extraArguments() const -{ - auto list = BaseInstance::extraArguments(); - auto version = getPackProfile(); - if (!version) - return list; - auto jarMods = getJarMods(); - if (!jarMods.isEmpty()) - { - list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", - "-Dfml.ignorePatchDiscrepancies=true"}); - } - return list; -} - -QStringList MinecraftInstance::javaArguments() const -{ - QStringList args; - - // custom args go first. we want to override them if we have our own here. - args.append(extraArguments()); - - // OSX dock icon and name -#ifdef Q_OS_MAC - args << "-Xdock:icon=icon.png"; - args << QString("-Xdock:name=\"%1\"").arg(windowTitle()); -#endif - auto traits_ = traits(); - // HACK: fix issues on macOS with 1.13 snapshots - // NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them -#ifdef Q_OS_MAC - if(traits_.contains("FirstThreadOnMacOS")) - { - args << QString("-XstartOnFirstThread"); - } -#endif - - // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 -#ifdef Q_OS_WIN32 - args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" - "minecraft.exe.heapdump"); -#endif - - int min = settings()->get("MinMemAlloc").toInt(); - int max = settings()->get("MaxMemAlloc").toInt(); - if(min < max) - { - args << QString("-Xms%1m").arg(min); - args << QString("-Xmx%1m").arg(max); - } - else - { - args << QString("-Xms%1m").arg(max); - args << QString("-Xmx%1m").arg(min); - } - - // No PermGen in newer java. - JavaVersion javaVersion = getJavaVersion(); - if(javaVersion.requiresPermGen()) - { - auto permgen = settings()->get("PermGen").toInt(); - if (permgen != 64) - { - args << QString("-XX:PermSize=%1m").arg(permgen); - } - } - - args << "-Duser.language=en"; - - return args; -} - -QMap<QString, QString> MinecraftInstance::getVariables() const -{ - QMap<QString, QString> out; - out.insert("INST_NAME", name()); - out.insert("INST_ID", id()); - out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); - out.insert("INST_MC_DIR", QDir(gameRoot()).absolutePath()); - out.insert("INST_JAVA", settings()->get("JavaPath").toString()); - out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); - return out; -} - -QProcessEnvironment MinecraftInstance::createEnvironment() -{ - // prepare the process environment - QProcessEnvironment env = CleanEnviroment(); - - // export some infos - auto variables = getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - env.insert(it.key(), it.value()); - } - return env; -} - -static QString replaceTokensIn(QString text, QMap<QString, QString> with) -{ - QString result; - QRegExp token_regexp("\\$\\{(.+)\\}"); - token_regexp.setMinimal(true); - QStringList list; - int tail = 0; - int head = 0; - while ((head = token_regexp.indexIn(text, head)) != -1) - { - result.append(text.mid(tail, head - tail)); - QString key = token_regexp.cap(1); - auto iter = with.find(key); - if (iter != with.end()) - { - result.append(*iter); - } - head += token_regexp.matchedLength(); - tail = head; - } - result.append(text.mid(tail)); - return result; -} - -QStringList MinecraftInstance::processMinecraftArgs( - AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const -{ - auto profile = m_components->getProfile(); - QString args_pattern = profile->getMinecraftArguments(); - for (auto tweaker : profile->getTweakers()) - { - args_pattern += " --tweakClass " + tweaker; - } - - if (serverToJoin && !serverToJoin->address.isEmpty()) - { - args_pattern += " --server " + serverToJoin->address; - args_pattern += " --port " + QString::number(serverToJoin->port); - } - - QMap<QString, QString> token_mapping; - // yggdrasil! - if(session) - { - token_mapping["auth_username"] = session->username; - token_mapping["auth_session"] = session->session; - token_mapping["auth_access_token"] = session->access_token; - token_mapping["auth_player_name"] = session->player_name; - token_mapping["auth_uuid"] = session->uuid; - token_mapping["user_properties"] = session->serializeUserProperties(); - token_mapping["user_type"] = session->user_type; - } - - // blatant self-promotion. - token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; - - token_mapping["version_type"] = profile->getMinecraftVersionType(); - - QString absRootDir = QDir(gameRoot()).absolutePath(); - token_mapping["game_directory"] = absRootDir; - QString absAssetsDir = QDir("assets/").absolutePath(); - auto assets = profile->getMinecraftAssets(); - token_mapping["game_assets"] = AssetsUtils::getAssetsDir(assets->id, resourcesDir()).absolutePath(); - - // 1.7.3+ assets tokens - token_mapping["assets_root"] = absAssetsDir; - token_mapping["assets_index_name"] = assets->id; - - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); - for (int i = 0; i < parts.length(); i++) - { - parts[i] = replaceTokensIn(parts[i], token_mapping); - } - return parts; -} - -QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) -{ - QString launchScript; - - if (!m_components) - return QString(); - auto profile = m_components->getProfile(); - if(!profile) - return QString(); - - auto mainClass = getMainClass(); - if (!mainClass.isEmpty()) - { - launchScript += "mainClass " + mainClass + "\n"; - } - auto appletClass = profile->getAppletClass(); - if (!appletClass.isEmpty()) - { - launchScript += "appletClass " + appletClass + "\n"; - } - - if (serverToJoin && !serverToJoin->address.isEmpty()) - { - launchScript += "serverAddress " + serverToJoin->address + "\n"; - launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n"; - } - - // generic minecraft params - for (auto param : processMinecraftArgs( - session, - nullptr /* When using a launch script, the server parameters are handled by it*/ - )) - { - launchScript += "param " + param + "\n"; - } - - // window size, title and state, legacy - { - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - windowParams = "max"; - else - windowParams = QString("%1x%2") - .arg(settings()->get("MinecraftWinWidth").toInt()) - .arg(settings()->get("MinecraftWinHeight").toInt()); - launchScript += "windowTitle " + windowTitle() + "\n"; - launchScript += "windowParams " + windowParams + "\n"; - } - - // legacy auth - if(session) - { - launchScript += "userName " + session->player_name + "\n"; - launchScript += "sessionId " + session->session + "\n"; - } - - // libraries and class path. - { - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - for(auto file: jars) - { - launchScript += "cp " + file + "\n"; - } - for(auto file: nativeJars) - { - launchScript += "ext " + file + "\n"; - } - launchScript += "natives " + getNativePath() + "\n"; - } - - for (auto trait : profile->getTraits()) - { - launchScript += "traits " + trait + "\n"; - } - launchScript += "launcher onesix\n"; - // qDebug() << "Generated launch script:" << launchScript; - return launchScript; -} - -QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) -{ - QStringList out; - out << "Main Class:" << " " + getMainClass() << ""; - out << "Native path:" << " " + getNativePath() << ""; - - auto profile = m_components->getProfile(); - - auto alltraits = traits(); - if(alltraits.size()) - { - out << "Traits:"; - for (auto trait : alltraits) - { - out << "traits " + trait; - } - out << ""; - } - - auto settings = this->settings(); - bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); - bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); - if (nativeOpenAL || nativeGLFW) - { - if (nativeOpenAL) - out << "Using system OpenAL."; - if (nativeGLFW) - out << "Using system GLFW."; - out << ""; - } - - // libraries and class path. - { - out << "Libraries:"; - QStringList jars, nativeJars; - auto javaArchitecture = settings->get("JavaArchitecture").toString(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - auto printLibFile = [&](const QString & path) - { - QFileInfo info(path); - if(info.exists()) - { - out << " " + path; - } - else - { - out << " " + path + " (missing)"; - } - }; - for(auto file: jars) - { - printLibFile(file); - } - out << ""; - out << "Native libraries:"; - for(auto file: nativeJars) - { - printLibFile(file); - } - out << ""; - } - - auto printModList = [&](const QString & label, ModFolderModel & model) { - if(model.size()) - { - out << QString("%1:").arg(label); - auto modList = model.allMods(); - std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) { - auto aName = a.filename().completeBaseName(); - auto bName = b.filename().completeBaseName(); - return aName.localeAwareCompare(bName) < 0; - }); - for(auto & mod: modList) - { - if(mod.type() == Mod::MOD_FOLDER) - { - out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)"; - continue; - } - - if(mod.enabled()) { - out << u8" [✔️] " + mod.filename().completeBaseName(); - } - else { - out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)"; - } - - } - out << ""; - } - }; - - printModList("Mods", *(loaderModList().get())); - printModList("Core Mods", *(coreModList().get())); - - auto & jarMods = profile->getJarMods(); - if(jarMods.size()) - { - out << "Jar Mods:"; - for(auto & jarmod: jarMods) - { - auto displayname = jarmod->displayName(currentSystem); - auto realname = jarmod->filename(currentSystem); - if(displayname != realname) - { - out << " " + displayname + " (" + realname + ")"; - } - else - { - out << " " + realname; - } - } - out << ""; - } - - auto params = processMinecraftArgs(nullptr, serverToJoin); - out << "Params:"; - out << " " + params.join(' '); - out << ""; - - QString windowParams; - if (settings->get("LaunchMaximized").toBool()) - { - out << "Window size: max (if available)"; - } - else - { - auto width = settings->get("MinecraftWinWidth").toInt(); - auto height = settings->get("MinecraftWinHeight").toInt(); - out << "Window size: " + QString::number(width) + " x " + QString::number(height); - } - out << ""; - return out; -} - -QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session) -{ - if(!session) - { - return QMap<QString, QString>(); - } - auto & sessionRef = *session.get(); - QMap<QString, QString> filter; - auto addToFilter = [&filter](QString key, QString value) - { - if(key.trimmed().size()) - { - filter[key] = value; - } - }; - if (sessionRef.session != "-") - { - addToFilter(sessionRef.session, tr("<SESSION ID>")); - } - addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>")); - addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>")); - addToFilter(sessionRef.uuid, tr("<PROFILE ID>")); - - auto i = sessionRef.u.properties.begin(); - while (i != sessionRef.u.properties.end()) - { - if(i.value().length() <= 3) { - ++i; - continue; - } - addToFilter(i.value(), "<" + i.key().toUpper() + ">"); - ++i; - } - return filter; -} - -MessageLevel::Enum MinecraftInstance::guessLevel(const QString &line, MessageLevel::Enum level) -{ - QRegularExpression re("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]"); - auto match = re.match(line); - if(match.hasMatch()) - { - // New style logs from log4j - QString timestamp = match.captured("timestamp"); - QString levelStr = match.captured("level"); - if(levelStr == "INFO") - level = MessageLevel::Message; - if(levelStr == "WARN") - level = MessageLevel::Warning; - if(levelStr == "ERROR") - level = MessageLevel::Error; - if(levelStr == "FATAL") - level = MessageLevel::Fatal; - if(levelStr == "TRACE" || levelStr == "DEBUG") - level = MessageLevel::Debug; - } - else - { - // Old style forge logs - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || - line.contains("[FINER]") || line.contains("[FINEST]")) - level = MessageLevel::Message; - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - if (line.contains("[WARNING]")) - level = MessageLevel::Warning; - if (line.contains("[DEBUG]")) - level = MessageLevel::Debug; - } - if (line.contains("overwriting existing")) - return MessageLevel::Fatal; - //NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; - if (line.contains("Exception in thread") - || line.contains(QRegularExpression("\\s+at " + javaSymbol)) - || line.contains(QRegularExpression("Caused by: " + javaSymbol)) - || line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) - || line.contains(QRegularExpression("... \\d+ more$")) - ) - return MessageLevel::Error; - return level; -} - -IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher() -{ - auto combined = std::make_shared<MultiMatcher>(); - combined->add(std::make_shared<RegexpMatcher>(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); - combined->add(std::make_shared<RegexpMatcher>("crash-.*\\.txt")); - combined->add(std::make_shared<RegexpMatcher>("IDMap dump.*\\.txt$")); - combined->add(std::make_shared<RegexpMatcher>("ModLoader\\.txt(\\..*)?$")); - return combined; -} - -QString MinecraftInstance::getLogFileRoot() -{ - return gameRoot(); -} - -QString MinecraftInstance::prettifyTimeDuration(int64_t duration) -{ - int seconds = (int) (duration % 60); - duration /= 60; - int minutes = (int) (duration % 60); - duration /= 60; - int hours = (int) (duration % 24); - int days = (int) (duration / 24); - if((hours == 0)&&(days == 0)) - { - return tr("%1m %2s").arg(minutes).arg(seconds); - } - if (days == 0) - { - return tr("%1h %2m").arg(hours).arg(minutes); - } - return tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes); -} - -QString MinecraftInstance::getStatusbarDescription() -{ - QStringList traits; - if (hasVersionBroken()) - { - traits.append(tr("broken")); - } - - QString description; - description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); - if(m_settings->get("ShowGameTime").toBool()) - { - if (lastTimePlayed() > 0) { - description.append(tr(", last played for %1").arg(prettifyTimeDuration(lastTimePlayed()))); - } - - if (totalTimePlayed() > 0) { - description.append(tr(", total played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); - } - } - if(hasCrashed()) - { - description.append(tr(", has crashed.")); - } - return description; -} - -shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode) -{ - switch (mode) - { - case Net::Mode::Offline: - { - return shared_qobject_ptr<Task>(new MinecraftLoadAndCheck(this)); - } - case Net::Mode::Online: - { - return shared_qobject_ptr<Task>(new MinecraftUpdate(this)); - } - } - return nullptr; -} - -shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) -{ - // FIXME: get rid of shared_from_this ... - auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this())); - auto pptr = process.get(); - - ENV.icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG"); - - // print a header - { - process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC)); - } - - // check java - { - process->appendStep(new CheckJava(pptr)); - } - - // check launch method - QStringList validMethods = {"LauncherPart", "DirectJava"}; - QString method = launchMethod(); - if(!validMethods.contains(method)) - { - process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); - return process; - } - - // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) - { - process->appendStep(new CreateGameFolders(pptr)); - } - - if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool()) - { - QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString(); - serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress))); - } - - if(serverToJoin && serverToJoin->port == 25565) - { - // Resolve server address to join on launch - auto *step = new LookupServerAddress(pptr); - step->setLookupAddress(serverToJoin->address); - step->setOutputAddressPtr(serverToJoin); - process->appendStep(step); - } - - // run pre-launch command if that's needed - if(getPreLaunchCommand().size()) - { - auto step = new PreLaunchCommand(pptr); - step->setWorkingDirectory(gameRoot()); - process->appendStep(step); - } - - // if we aren't in offline mode,. - if(session->status != AuthSession::PlayableOffline) - { - process->appendStep(new ClaimAccount(pptr, session)); - process->appendStep(new Update(pptr, Net::Mode::Online)); - } - else - { - process->appendStep(new Update(pptr, Net::Mode::Offline)); - } - - // if there are any jar mods - { - process->appendStep(new ModMinecraftJar(pptr)); - } - - // Scan mods folders for mods - { - process->appendStep(new ScanModFolders(pptr)); - } - - // print some instance info here... - { - process->appendStep(new PrintInstanceInfo(pptr, session, serverToJoin)); - } - - // extract native jars if needed - { - process->appendStep(new ExtractNatives(pptr)); - } - - // reconstruct assets if needed - { - process->appendStep(new ReconstructAssets(pptr)); - } - - // verify that minimum Java requirements are met - { - process->appendStep(new VerifyJavaInstall(pptr)); - } - - { - // actually launch the game - auto method = launchMethod(); - if(method == "LauncherPart") - { - auto step = new LauncherPartLaunch(pptr); - step->setWorkingDirectory(gameRoot()); - step->setAuthSession(session); - step->setServerToJoin(serverToJoin); - process->appendStep(step); - } - else if (method == "DirectJava") - { - auto step = new DirectJavaLaunch(pptr); - step->setWorkingDirectory(gameRoot()); - step->setAuthSession(session); - step->setServerToJoin(serverToJoin); - process->appendStep(step); - } - } - - // run post-exit command if that's needed - if(getPostExitCommand().size()) - { - auto step = new PostLaunchCommand(pptr); - step->setWorkingDirectory(gameRoot()); - process->appendStep(step); - } - if (session) - { - process->setCensorFilter(createCensorFilterFromSession(session)); - } - m_launchProcess = process; - emit launchTaskChanged(m_launchProcess); - return m_launchProcess; -} - -QString MinecraftInstance::launchMethod() -{ - return m_settings->get("MCLaunchMethod").toString(); -} - -JavaVersion MinecraftInstance::getJavaVersion() const -{ - return JavaVersion(settings()->get("JavaVersion").toString()); -} - -std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const -{ - if (!m_loader_mod_list) - { - m_loader_mod_list.reset(new ModFolderModel(loaderModsDir())); - m_loader_mod_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); - } - return m_loader_mod_list; -} - -std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const -{ - if (!m_core_mod_list) - { - m_core_mod_list.reset(new ModFolderModel(coreModsDir())); - m_core_mod_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); - } - return m_core_mod_list; -} - -std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const -{ - if (!m_resource_pack_list) - { - m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir())); - m_resource_pack_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction); - } - return m_resource_pack_list; -} - -std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const -{ - if (!m_texture_pack_list) - { - m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir())); - m_texture_pack_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction); - } - return m_texture_pack_list; -} - -std::shared_ptr<WorldList> MinecraftInstance::worldList() const -{ - if (!m_world_list) - { - m_world_list.reset(new WorldList(worldDir())); - } - return m_world_list; -} - -std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel() const -{ - if (!m_game_options) - { - m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt"))); - } - return m_game_options; -} - -QList< Mod > MinecraftInstance::getJarMods() const -{ - auto profile = m_components->getProfile(); - QList<Mod> mods; - for (auto jarmod : profile->getJarMods()) - { - QStringList jar, temp1, temp2, temp3; - jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); - // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); - mods.push_back(Mod(QFileInfo(jar[0]))); - } - return mods; -} - - -#include "MinecraftInstance.moc" diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h deleted file mode 100644 index 05600797..00000000 --- a/api/logic/minecraft/MinecraftInstance.h +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once -#include "BaseInstance.h" -#include <java/JavaVersion.h> -#include "minecraft/mod/Mod.h" -#include <QProcess> -#include <QDir> -#include "multimc_logic_export.h" -#include "minecraft/launch/MinecraftServerTarget.h" - -class ModFolderModel; -class WorldList; -class GameOptions; -class LaunchStep; -class PackProfile; - -class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance -{ - Q_OBJECT -public: - MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual ~MinecraftInstance() {}; - virtual void saveNow() override; - - // FIXME: remove - QString typeName() const override; - // FIXME: remove - QSet<QString> traits() const override; - - bool canEdit() const override - { - return true; - } - - bool canExport() const override - { - return true; - } - - ////// Directories and files ////// - QString jarModsDir() const; - QString resourcePacksDir() const; - QString texturePacksDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString modsCacheLocation() const; - QString libDir() const; - QString worldDir() const; - QString resourcesDir() const; - QDir jarmodsPath() const; - QDir librariesPath() const; - QDir versionsPath() const; - QString instanceConfigFolder() const override; - - // Path to the instance's minecraft directory. - QString gameRoot() const override; - - // Path to the instance's minecraft bin directory. - QString binRoot() const; - - // where to put the natives during/before launch - QString getNativePath() const; - - // where the instance-local libraries should be - QString getLocalLibraryPath() const; - - - ////// Profile management ////// - std::shared_ptr<PackProfile> getPackProfile() const; - - ////// Mod Lists ////// - std::shared_ptr<ModFolderModel> loaderModList() const; - std::shared_ptr<ModFolderModel> coreModList() const; - std::shared_ptr<ModFolderModel> resourcePackList() const; - std::shared_ptr<ModFolderModel> texturePackList() const; - std::shared_ptr<WorldList> worldList() const; - std::shared_ptr<GameOptions> gameOptionsModel() const; - - ////// Launch stuff ////// - shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override; - shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override; - QStringList extraArguments() const 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; - - /// get variables for launch command variable substitution/environment - QMap<QString, QString> getVariables() const override; - - /// create an environment for launching processes - QProcessEnvironment createEnvironment() override; - - /// guess log level from a line of minecraft log - MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; - - IPathMatcher::Ptr getLogFileMatcher() override; - - QString getLogFileRoot() override; - - QString getStatusbarDescription() override; - - // FIXME: remove - virtual QStringList getClassPath() const; - // FIXME: remove - virtual QStringList getNativeJars() const; - // FIXME: remove - virtual QString getMainClass() const; - - // FIXME: remove - virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const; - - virtual JavaVersion getJavaVersion() const; - -protected: - QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session); - QStringList validLaunchMethods(); - QString launchMethod(); - -private: - QString prettifyTimeDuration(int64_t duration); - -protected: // data - std::shared_ptr<PackProfile> m_components; - mutable std::shared_ptr<ModFolderModel> m_loader_mod_list; - mutable std::shared_ptr<ModFolderModel> m_core_mod_list; - mutable std::shared_ptr<ModFolderModel> m_resource_pack_list; - mutable std::shared_ptr<ModFolderModel> m_texture_pack_list; - mutable std::shared_ptr<WorldList> m_world_list; - mutable std::shared_ptr<GameOptions> m_game_options; -}; - -typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr; diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.cpp b/api/logic/minecraft/MinecraftLoadAndCheck.cpp deleted file mode 100644 index 79b0c484..00000000 --- a/api/logic/minecraft/MinecraftLoadAndCheck.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "MinecraftLoadAndCheck.h" -#include "MinecraftInstance.h" -#include "PackProfile.h" - -MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) -{ -} - -void MinecraftLoadAndCheck::executeTask() -{ - // add offline metadata load task - auto components = m_inst->getPackProfile(); - components->reload(Net::Mode::Offline); - m_task = components->getCurrentTask(); - - if(!m_task) - { - emitSucceeded(); - return; - } - connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded); - connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); - connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); - connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); -} - -void MinecraftLoadAndCheck::subtaskSucceeded() -{ - if(isFinished()) - { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; - } - emitSucceeded(); -} - -void MinecraftLoadAndCheck::subtaskFailed(QString error) -{ - if(isFinished()) - { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!"; - return; - } - emitFailed(error); -} diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h deleted file mode 100644 index 3435b52b..00000000 --- a/api/logic/minecraft/MinecraftLoadAndCheck.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QList> -#include <QUrl> - -#include "tasks/Task.h" -#include <quazip.h> - -#include "QObjectPtr.h" - -class MinecraftVersion; -class MinecraftInstance; - -class MinecraftLoadAndCheck : public Task -{ - Q_OBJECT -public: - explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0); - virtual ~MinecraftLoadAndCheck() {}; - void executeTask() override; - -private slots: - void subtaskSucceeded(); - void subtaskFailed(QString error); - -private: - MinecraftInstance *m_inst = nullptr; - shared_qobject_ptr<Task> m_task; - QString m_preFailure; - QString m_fail_reason; -}; - diff --git a/api/logic/minecraft/MinecraftUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp deleted file mode 100644 index 8f1565b0..00000000 --- a/api/logic/minecraft/MinecraftUpdate.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "Env.h" -#include "MinecraftUpdate.h" -#include "MinecraftInstance.h" - -#include <QFile> -#include <QFileInfo> -#include <QTextStream> -#include <QDataStream> - -#include "BaseInstance.h" -#include "minecraft/PackProfile.h" -#include "minecraft/Library.h" -#include <FileSystem.h> - -#include "update/FoldersTask.h" -#include "update/LibrariesTask.h" -#include "update/FMLLibrariesTask.h" -#include "update/AssetUpdateTask.h" - -#include <meta/Index.h> -#include <meta/Version.h> - -MinecraftUpdate::MinecraftUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) -{ -} - -void MinecraftUpdate::executeTask() -{ - m_tasks.clear(); - // create folders - { - m_tasks.append(std::make_shared<FoldersTask>(m_inst)); - } - - // add metadata update task if necessary - { - auto components = m_inst->getPackProfile(); - components->reload(Net::Mode::Online); - auto task = components->getCurrentTask(); - if(task) - { - m_tasks.append(task.unwrap()); - } - } - - // libraries download - { - m_tasks.append(std::make_shared<LibrariesTask>(m_inst)); - } - - // FML libraries download and copy into the instance - { - m_tasks.append(std::make_shared<FMLLibrariesTask>(m_inst)); - } - - // assets update - { - m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst)); - } - - if(!m_preFailure.isEmpty()) - { - emitFailed(m_preFailure); - return; - } - next(); -} - -void MinecraftUpdate::next() -{ - if(m_abort) - { - emitFailed(tr("Aborted by user.")); - return; - } - if(m_failed_out_of_order) - { - emitFailed(m_fail_reason); - return; - } - m_currentTask ++; - if(m_currentTask > 0) - { - auto task = m_tasks[m_currentTask - 1]; - disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); - disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); - disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); - disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); - } - if(m_currentTask == m_tasks.size()) - { - emitSucceeded(); - return; - } - auto task = m_tasks[m_currentTask]; - // if the task is already finished by the time we look at it, skip it - if(task->isFinished()) - { - qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get(); - next(); - } - connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); - connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); - connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); - connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); - // if the task is already running, do not start it again - if(!task->isRunning()) - { - task->start(); - } -} - -void MinecraftUpdate::subtaskSucceeded() -{ - if(isFinished()) - { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if(senderTask != currentTask) - { - qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order."; - return; - } - next(); -} - -void MinecraftUpdate::subtaskFailed(QString error) -{ - if(isFinished()) - { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if(senderTask != currentTask) - { - qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order."; - m_failed_out_of_order = true; - m_fail_reason = error; - return; - } - emitFailed(error); -} - - -bool MinecraftUpdate::abort() -{ - if(!m_abort) - { - m_abort = true; - auto task = m_tasks[m_currentTask]; - if(task->canAbort()) - { - return task->abort(); - } - } - return true; -} - -bool MinecraftUpdate::canAbort() const -{ - return true; -} diff --git a/api/logic/minecraft/MinecraftUpdate.h b/api/logic/minecraft/MinecraftUpdate.h deleted file mode 100644 index fadebff9..00000000 --- a/api/logic/minecraft/MinecraftUpdate.h +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QList> -#include <QUrl> - -#include "net/NetJob.h" -#include "tasks/Task.h" -#include "minecraft/VersionFilterData.h" -#include <quazip.h> - -class MinecraftVersion; -class MinecraftInstance; - -class MinecraftUpdate : public Task -{ - Q_OBJECT -public: - explicit MinecraftUpdate(MinecraftInstance *inst, QObject *parent = 0); - virtual ~MinecraftUpdate() {}; - - void executeTask() override; - bool canAbort() const override; - -private -slots: - bool abort() override; - void subtaskSucceeded(); - void subtaskFailed(QString error); - -private: - void next(); - -private: - MinecraftInstance *m_inst = nullptr; - QList<std::shared_ptr<Task>> m_tasks; - QString m_preFailure; - int m_currentTask = -1; - bool m_abort = false; - bool m_failed_out_of_order = false; - QString m_fail_reason; -}; diff --git a/api/logic/minecraft/MojangDownloadInfo.h b/api/logic/minecraft/MojangDownloadInfo.h deleted file mode 100644 index 88f87287..00000000 --- a/api/logic/minecraft/MojangDownloadInfo.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once -#include <QString> -#include <QMap> -#include <memory> - -struct MojangDownloadInfo -{ - // types - typedef std::shared_ptr<MojangDownloadInfo> Ptr; - - // data - /// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested! - QString path; - /// absolute URL of this file - QString url; - /// sha-1 checksum of the file - QString sha1; - /// size of the file in bytes - int size; -}; - - - -struct MojangLibraryDownloadInfo -{ - MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact): artifact(artifact) {}; - MojangLibraryDownloadInfo() {}; - - // types - typedef std::shared_ptr<MojangLibraryDownloadInfo> Ptr; - - // methods - MojangDownloadInfo *getDownloadInfo(QString classifier) - { - if (classifier.isNull()) - { - return artifact.get(); - } - - return classifiers[classifier].get(); - } - - // data - MojangDownloadInfo::Ptr artifact; - QMap<QString, MojangDownloadInfo::Ptr> classifiers; -}; - - - -struct MojangAssetIndexInfo : public MojangDownloadInfo -{ - // types - typedef std::shared_ptr<MojangAssetIndexInfo> Ptr; - - // methods - MojangAssetIndexInfo() - { - } - - MojangAssetIndexInfo(QString id) - { - this->id = id; - // HACK: ignore assets from other version files than Minecraft - // workaround for stupid assets issue caused by amazon: - // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ - if(id == "legacy") - { - url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json"; - } - // HACK - else - { - url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json"; - } - known = false; - } - - // data - int totalSize; - QString id; - bool known = true; -}; diff --git a/api/logic/minecraft/MojangVersionFormat.cpp b/api/logic/minecraft/MojangVersionFormat.cpp deleted file mode 100644 index f9cb2228..00000000 --- a/api/logic/minecraft/MojangVersionFormat.cpp +++ /dev/null @@ -1,383 +0,0 @@ -#include "MojangVersionFormat.h" -#include "OneSixVersionFormat.h" -#include "MojangDownloadInfo.h" - -#include "Json.h" -using namespace Json; -#include "ParseUtils.h" - -static const int CURRENT_MINIMUM_LAUNCHER_VERSION = 18; - -static MojangAssetIndexInfo::Ptr assetIndexFromJson (const QJsonObject &obj); -static MojangDownloadInfo::Ptr downloadInfoFromJson (const QJsonObject &obj); -static MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson (const QJsonObject &libObj); -static QJsonObject assetIndexToJson (MojangAssetIndexInfo::Ptr assetidxinfo); -static QJsonObject libDownloadInfoToJson (MojangLibraryDownloadInfo::Ptr libinfo); -static QJsonObject downloadInfoToJson (MojangDownloadInfo::Ptr info); - -namespace Bits -{ -static void readString(const QJsonObject &root, const QString &key, QString &variable) -{ - if (root.contains(key)) - { - variable = requireString(root.value(key)); - } -} - -static void readDownloadInfo(MojangDownloadInfo::Ptr out, const QJsonObject &obj) -{ - // optional, not used - readString(obj, "path", out->path); - // required! - out->sha1 = requireString(obj, "sha1"); - out->url = requireString(obj, "url"); - out->size = requireInteger(obj, "size"); -} - -static void readAssetIndex(MojangAssetIndexInfo::Ptr out, const QJsonObject &obj) -{ - out->totalSize = requireInteger(obj, "totalSize"); - out->id = requireString(obj, "id"); - // out->known = true; -} -} - -MojangDownloadInfo::Ptr downloadInfoFromJson(const QJsonObject &obj) -{ - auto out = std::make_shared<MojangDownloadInfo>(); - Bits::readDownloadInfo(out, obj); - return out; -} - -MojangAssetIndexInfo::Ptr assetIndexFromJson(const QJsonObject &obj) -{ - auto out = std::make_shared<MojangAssetIndexInfo>(); - Bits::readDownloadInfo(out, obj); - Bits::readAssetIndex(out, obj); - return out; -} - -QJsonObject downloadInfoToJson(MojangDownloadInfo::Ptr info) -{ - QJsonObject out; - if(!info->path.isNull()) - { - out.insert("path", info->path); - } - out.insert("sha1", info->sha1); - out.insert("size", info->size); - out.insert("url", info->url); - return out; -} - -MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson(const QJsonObject &libObj) -{ - auto out = std::make_shared<MojangLibraryDownloadInfo>(); - auto dlObj = requireObject(libObj.value("downloads")); - if(dlObj.contains("artifact")) - { - out->artifact = downloadInfoFromJson(requireObject(dlObj, "artifact")); - } - if(dlObj.contains("classifiers")) - { - auto classifiersObj = requireObject(dlObj, "classifiers"); - for(auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++) - { - auto classifier = iter.key(); - auto classifierObj = requireObject(iter.value()); - out->classifiers[classifier] = downloadInfoFromJson(classifierObj); - } - } - return out; -} - -QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo) -{ - QJsonObject out; - if(libinfo->artifact) - { - out.insert("artifact", downloadInfoToJson(libinfo->artifact)); - } - if(libinfo->classifiers.size()) - { - QJsonObject classifiersOut; - for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) - { - classifiersOut.insert(iter.key(), downloadInfoToJson(iter.value())); - } - out.insert("classifiers", classifiersOut); - } - return out; -} - -QJsonObject assetIndexToJson(MojangAssetIndexInfo::Ptr info) -{ - QJsonObject out; - if(!info->path.isNull()) - { - out.insert("path", info->path); - } - out.insert("sha1", info->sha1); - out.insert("size", info->size); - out.insert("url", info->url); - out.insert("totalSize", info->totalSize); - out.insert("id", info->id); - return out; -} - -void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFile *out) -{ - Bits::readString(in, "id", out->minecraftVersion); - Bits::readString(in, "mainClass", out->mainClass); - Bits::readString(in, "minecraftArguments", out->minecraftArguments); - if(out->minecraftArguments.isEmpty()) - { - QString processArguments; - Bits::readString(in, "processArguments", processArguments); - QString toCompare = processArguments.toLower(); - if (toCompare == "legacy") - { - out->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; - } - else if (!toCompare.isEmpty()) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments)); - } - } - Bits::readString(in, "type", out->type); - - Bits::readString(in, "assets", out->assets); - if(in.contains("assetIndex")) - { - out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex")); - } - else if (!out->assets.isNull()) - { - out->mojangAssetIndex = std::make_shared<MojangAssetIndexInfo>(out->assets); - } - - out->releaseTime = timeFromS3Time(in.value("releaseTime").toString("")); - out->updateTime = timeFromS3Time(in.value("time").toString("")); - - if (in.contains("minimumLauncherVersion")) - { - out->minimumLauncherVersion = requireInteger(in.value("minimumLauncherVersion")); - if (out->minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) - { - out->addProblem( - ProblemSeverity::Warning, - QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by MultiMC (%2). It might not work properly!") - .arg(out->minimumLauncherVersion) - .arg(CURRENT_MINIMUM_LAUNCHER_VERSION)); - } - } - if(in.contains("downloads")) - { - auto downloadsObj = requireObject(in, "downloads"); - for(auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++) - { - auto classifier = iter.key(); - auto classifierObj = requireObject(iter.value()); - out->mojangDownloads[classifier] = downloadInfoFromJson(classifierObj); - } - } -} - -VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename) -{ - VersionFilePtr out(new VersionFile()); - if (doc.isEmpty() || doc.isNull()) - { - throw JSONValidationError(filename + " is empty or null"); - } - if (!doc.isObject()) - { - throw JSONValidationError(filename + " is not an object"); - } - - QJsonObject root = doc.object(); - - readVersionProperties(root, out.get()); - - out->name = "Minecraft"; - out->uid = "net.minecraft"; - out->version = out->minecraftVersion; - // out->filename = filename; - - - if (root.contains("libraries")) - { - for (auto libVal : requireArray(root.value("libraries"))) - { - auto libObj = requireObject(libVal); - - auto lib = MojangVersionFormat::libraryFromJson(*out, libObj, filename); - out->libraries.append(lib); - } - } - return out; -} - -void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObject& out) -{ - writeString(out, "id", in->minecraftVersion); - writeString(out, "mainClass", in->mainClass); - writeString(out, "minecraftArguments", in->minecraftArguments); - writeString(out, "type", in->type); - if(!in->releaseTime.isNull()) - { - writeString(out, "releaseTime", timeToS3Time(in->releaseTime)); - } - if(!in->updateTime.isNull()) - { - writeString(out, "time", timeToS3Time(in->updateTime)); - } - if(in->minimumLauncherVersion != -1) - { - out.insert("minimumLauncherVersion", in->minimumLauncherVersion); - } - writeString(out, "assets", in->assets); - if(in->mojangAssetIndex && in->mojangAssetIndex->known) - { - out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex)); - } - if(in->mojangDownloads.size()) - { - QJsonObject downloadsOut; - for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) - { - downloadsOut.insert(iter.key(), downloadInfoToJson(iter.value())); - } - out.insert("downloads", downloadsOut); - } -} - -QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch) -{ - QJsonObject root; - writeVersionProperties(patch.get(), root); - if (!patch->libraries.isEmpty()) - { - QJsonArray array; - for (auto value: patch->libraries) - { - array.append(MojangVersionFormat::libraryToJson(value.get())); - } - root.insert("libraries", array); - } - - // write the contents to a json document. - { - QJsonDocument out; - out.setObject(root); - return out; - } -} - -LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename) -{ - LibraryPtr out(new Library()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field"); - } - auto rawName = libObj.value("name").toString(); - out->m_name = rawName; - if(!out->m_name.valid()) { - problems.addProblem(ProblemSeverity::Error, QObject::tr("Library %1 name is broken and cannot be processed.").arg(rawName)); - } - - Bits::readString(libObj, "url", out->m_repositoryURL); - if (libObj.contains("extract")) - { - out->m_hasExcludes = true; - auto extractObj = requireObject(libObj.value("extract")); - for (auto excludeVal : requireArray(extractObj.value("exclude"))) - { - out->m_extractExcludes.append(requireString(excludeVal)); - } - } - if (libObj.contains("natives")) - { - QJsonObject nativesObj = requireObject(libObj.value("natives")); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) - { - if (!it.value().isString()) - { - qWarning() << filename << "contains an invalid native (skipping)"; - } - OpSys opSys = OpSys_fromString(it.key()); - if (opSys != Os_Other) - { - out->m_nativeClassifiers[opSys] = it.value().toString(); - } - } - } - if (libObj.contains("rules")) - { - out->applyRules = true; - out->m_rules = rulesFromJsonV4(libObj); - } - if (libObj.contains("downloads")) - { - out->m_mojangDownloads = libDownloadInfoFromJson(libObj); - } - return out; -} - -QJsonObject MojangVersionFormat::libraryToJson(Library *library) -{ - QJsonObject libRoot; - libRoot.insert("name", library->m_name.serialize()); - if (!library->m_repositoryURL.isEmpty()) - { - libRoot.insert("url", library->m_repositoryURL); - } - if (library->isNative()) - { - QJsonObject nativeList; - auto iter = library->m_nativeClassifiers.begin(); - while (iter != library->m_nativeClassifiers.end()) - { - nativeList.insert(OpSys_toString(iter.key()), iter.value()); - iter++; - } - libRoot.insert("natives", nativeList); - if (library->m_extractExcludes.size()) - { - QJsonArray excludes; - QJsonObject extract; - for (auto exclude : library->m_extractExcludes) - { - excludes.append(exclude); - } - extract.insert("exclude", excludes); - libRoot.insert("extract", extract); - } - } - if (library->m_rules.size()) - { - QJsonArray allRules; - for (auto &rule : library->m_rules) - { - QJsonObject ruleObj = rule->toJson(); - allRules.append(ruleObj); - } - libRoot.insert("rules", allRules); - } - if(library->m_mojangDownloads) - { - auto downloadsObj = libDownloadInfoToJson(library->m_mojangDownloads); - libRoot.insert("downloads", downloadsObj); - } - return libRoot; -} diff --git a/api/logic/minecraft/MojangVersionFormat.h b/api/logic/minecraft/MojangVersionFormat.h deleted file mode 100644 index 2871dae4..00000000 --- a/api/logic/minecraft/MojangVersionFormat.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include <minecraft/VersionFile.h> -#include <minecraft/Library.h> -#include <QJsonDocument> -#include <ProblemProvider.h> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT MojangVersionFormat -{ -friend class OneSixVersionFormat; -protected: - // does not include libraries - static void readVersionProperties(const QJsonObject& in, VersionFile* out); - // does not include libraries - static void writeVersionProperties(const VersionFile* in, QJsonObject& out); -public: - // version files / profile patches - static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename); - static QJsonDocument versionFileToJson(const VersionFilePtr &patch); - - // libraries - static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); - static QJsonObject libraryToJson(Library *library); -}; diff --git a/api/logic/minecraft/MojangVersionFormat_test.cpp b/api/logic/minecraft/MojangVersionFormat_test.cpp deleted file mode 100644 index 9d095340..00000000 --- a/api/logic/minecraft/MojangVersionFormat_test.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include <QTest> -#include <QDebug> -#include "TestUtil.h" - -#include "minecraft/MojangVersionFormat.h" - -class MojangVersionFormatTest : public QObject -{ - Q_OBJECT - - static QJsonDocument readJson(const char *file) - { - auto path = QFINDTESTDATA(file); - QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); - auto data = jsonFile.readAll(); - jsonFile.close(); - return QJsonDocument::fromJson(data); - } - static void writeJson(const char *file, QJsonDocument doc) - { - QFile jsonFile(file); - jsonFile.open(QIODevice::WriteOnly | QIODevice::Text); - auto data = doc.toJson(QJsonDocument::Indented); - qDebug() << QString::fromUtf8(data); - jsonFile.write(data); - jsonFile.close(); - } - -private -slots: - void test_Through_Simple() - { - QJsonDocument doc = readJson("data/1.9-simple.json"); - auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json"); - auto doc2 = MojangVersionFormat::versionFileToJson(vfile); - writeJson("1.9-simple-passthorugh.json", doc2); - - QCOMPARE(doc.toJson(), doc2.toJson()); - } - - void test_Through() - { - QJsonDocument doc = readJson("data/1.9.json"); - auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json"); - auto doc2 = MojangVersionFormat::versionFileToJson(vfile); - writeJson("1.9-passthorugh.json", doc2); - QCOMPARE(doc.toJson(), doc2.toJson()); - } -}; - -QTEST_GUILESS_MAIN(MojangVersionFormatTest) - -#include "MojangVersionFormat_test.moc" - diff --git a/api/logic/minecraft/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp deleted file mode 100644 index 0329d70e..00000000 --- a/api/logic/minecraft/OneSixVersionFormat.cpp +++ /dev/null @@ -1,391 +0,0 @@ -#include "OneSixVersionFormat.h" -#include <Json.h> -#include "minecraft/ParseUtils.h" -#include <minecraft/MojangVersionFormat.h> - -using namespace Json; - -static void readString(const QJsonObject &root, const QString &key, QString &variable) -{ - if (root.contains(key)) - { - variable = requireString(root.value(key)); - } -} - -LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename) -{ - LibraryPtr out = MojangVersionFormat::libraryFromJson(problems, libObj, filename); - readString(libObj, "MMC-hint", out->m_hint); - readString(libObj, "MMC-absulute_url", out->m_absoluteURL); - readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL); - readString(libObj, "MMC-filename", out->m_filename); - readString(libObj, "MMC-displayname", out->m_displayname); - return out; -} - -QJsonObject OneSixVersionFormat::libraryToJson(Library *library) -{ - QJsonObject libRoot = MojangVersionFormat::libraryToJson(library); - if (library->m_absoluteURL.size()) - libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL); - if (library->m_hint.size()) - libRoot.insert("MMC-hint", library->m_hint); - if (library->m_filename.size()) - libRoot.insert("MMC-filename", library->m_filename); - if (library->m_displayname.size()) - libRoot.insert("MMC-displayname", library->m_displayname); - return libRoot; -} - -VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder) -{ - VersionFilePtr out(new VersionFile()); - if (doc.isEmpty() || doc.isNull()) - { - throw JSONValidationError(filename + " is empty or null"); - } - if (!doc.isObject()) - { - throw JSONValidationError(filename + " is not an object"); - } - - QJsonObject root = doc.object(); - - Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false); - switch(formatVersion) - { - case Meta::MetadataVersion::InitialRelease: - break; - case Meta::MetadataVersion::Invalid: - throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format."); - } - - if (requireOrder) - { - if (root.contains("order")) - { - out->order = requireInteger(root.value("order")); - } - else - { - // FIXME: evaluate if we don't want to throw exceptions here instead - qCritical() << filename << "doesn't contain an order field"; - } - } - - out->name = root.value("name").toString(); - - if(root.contains("uid")) - { - out->uid = root.value("uid").toString(); - } - else - { - out->uid = root.value("fileId").toString(); - } - - out->version = root.value("version").toString(); - - MojangVersionFormat::readVersionProperties(root, out.get()); - - // added for legacy Minecraft window embedding, TODO: remove - readString(root, "appletClass", out->appletClass); - - if (root.contains("+tweakers")) - { - for (auto tweakerVal : requireArray(root.value("+tweakers"))) - { - out->addTweakers.append(requireString(tweakerVal)); - } - } - - if (root.contains("+traits")) - { - for (auto tweakerVal : requireArray(root.value("+traits"))) - { - out->traits.insert(requireString(tweakerVal)); - } - } - - - if (root.contains("jarMods")) - { - for (auto libVal : requireArray(root.value("jarMods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::jarModFromJson(*out, libObj, filename); - // and add to jar mods - out->jarMods.append(lib); - } - } - else if (root.contains("+jarMods")) // DEPRECATED: old style '+jarMods' are only here for backwards compatibility - { - for (auto libVal : requireArray(root.value("+jarMods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::plusJarModFromJson(*out, libObj, filename, out->name); - // and add to jar mods - out->jarMods.append(lib); - } - } - - if (root.contains("mods")) - { - for (auto libVal : requireArray(root.value("mods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::modFromJson(*out, libObj, filename); - // and add to jar mods - out->mods.append(lib); - } - } - - auto readLibs = [&](const char * which, QList<LibraryPtr> & outList) - { - for (auto libVal : requireArray(root.value(which))) - { - QJsonObject libObj = requireObject(libVal); - // parse the library - auto lib = libraryFromJson(*out, libObj, filename); - outList.append(lib); - } - }; - bool hasPlusLibs = root.contains("+libraries"); - bool hasLibs = root.contains("libraries"); - if (hasPlusLibs && hasLibs) - { - out->addProblem(ProblemSeverity::Warning, - QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported.")); - readLibs("libraries", out->libraries); - readLibs("+libraries", out->libraries); - } - else if (hasLibs) - { - readLibs("libraries", out->libraries); - } - else if(hasPlusLibs) - { - readLibs("+libraries", out->libraries); - } - - if(root.contains("mavenFiles")) { - readLibs("mavenFiles", out->mavenFiles); - } - - // if we have mainJar, just use it - if(root.contains("mainJar")) - { - QJsonObject libObj = requireObject(root, "mainJar"); - out->mainJar = libraryFromJson(*out, libObj, filename); - } - // else reconstruct it from downloads and id ... if that's available - else if(!out->minecraftVersion.isEmpty()) - { - auto lib = std::make_shared<Library>(); - lib->setRawName(GradleSpecifier(QString("com.mojang:minecraft:%1:client").arg(out->minecraftVersion))); - // we have a reliable client download, use it. - if(out->mojangDownloads.contains("client")) - { - auto LibDLInfo = std::make_shared<MojangLibraryDownloadInfo>(); - LibDLInfo->artifact = out->mojangDownloads["client"]; - lib->setMojangDownloadInfo(LibDLInfo); - } - // we got nothing... - else - { - out->addProblem( - ProblemSeverity::Error, - QObject::tr("URL for the main jar could not be determined - Mojang removed the server that we used as fallback.") - ); - } - out->mainJar = lib; - } - - if (root.contains("requires")) - { - Meta::parseRequires(root, &out->requires); - } - QString dependsOnMinecraftVersion = root.value("mcVersion").toString(); - if(!dependsOnMinecraftVersion.isEmpty()) - { - Meta::Require mcReq; - mcReq.uid = "net.minecraft"; - mcReq.equalsVersion = dependsOnMinecraftVersion; - if (out->requires.count(mcReq) == 0) - { - out->requires.insert(mcReq); - } - } - if (root.contains("conflicts")) - { - Meta::parseRequires(root, &out->conflicts); - } - if (root.contains("volatile")) - { - out->m_volatile = requireBoolean(root, "volatile"); - } - - /* removed features that shouldn't be used */ - if (root.contains("tweakers")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'")); - } - if (root.contains("-libraries")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-libraries'")); - } - if (root.contains("-tweakers")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-tweakers'")); - } - if (root.contains("-minecraftArguments")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-minecraftArguments'")); - } - if (root.contains("+minecraftArguments")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '+minecraftArguments'")); - } - return out; -} - -QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch) -{ - QJsonObject root; - writeString(root, "name", patch->name); - - writeString(root, "uid", patch->uid); - - writeString(root, "version", patch->version); - - Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease); - - MojangVersionFormat::writeVersionProperties(patch.get(), root); - - if(patch->mainJar) - { - root.insert("mainJar", libraryToJson(patch->mainJar.get())); - } - writeString(root, "appletClass", patch->appletClass); - writeStringList(root, "+tweakers", patch->addTweakers); - writeStringList(root, "+traits", patch->traits.toList()); - if (!patch->libraries.isEmpty()) - { - QJsonArray array; - for (auto value: patch->libraries) - { - array.append(OneSixVersionFormat::libraryToJson(value.get())); - } - root.insert("libraries", array); - } - if (!patch->mavenFiles.isEmpty()) - { - QJsonArray array; - for (auto value: patch->mavenFiles) - { - array.append(OneSixVersionFormat::libraryToJson(value.get())); - } - root.insert("mavenFiles", array); - } - if (!patch->jarMods.isEmpty()) - { - QJsonArray array; - for (auto value: patch->jarMods) - { - array.append(OneSixVersionFormat::jarModtoJson(value.get())); - } - root.insert("jarMods", array); - } - if (!patch->mods.isEmpty()) - { - QJsonArray array; - for (auto value: patch->jarMods) - { - array.append(OneSixVersionFormat::modtoJson(value.get())); - } - root.insert("mods", array); - } - if(!patch->requires.empty()) - { - Meta::serializeRequires(root, &patch->requires, "requires"); - } - if(!patch->conflicts.empty()) - { - Meta::serializeRequires(root, &patch->conflicts, "conflicts"); - } - if(patch->m_volatile) - { - root.insert("volatile", true); - } - // write the contents to a json document. - { - QJsonDocument out; - out.setObject(root); - return out; - } -} - -LibraryPtr OneSixVersionFormat::plusJarModFromJson( - ProblemContainer & problems, - const QJsonObject &libObj, - const QString &filename, - const QString &originalName -) { - LibraryPtr out(new Library()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + - "contains a jarmod that doesn't have a 'name' field"); - } - - // just make up something unique on the spot for the library name. - auto uuid = QUuid::createUuid(); - QString id = uuid.toString().remove('{').remove('}'); - out->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); - - // filename override is the old name - out->setFilename(libObj.value("name").toString()); - - // it needs to be local, it is stored in the instance jarmods folder - out->setHint("local"); - - // read the original name if present - some versions did not set it - // it is the original jar mod filename before it got renamed at the point of addition - auto displayName = libObj.value("originalName").toString(); - if(displayName.isEmpty()) - { - auto fixed = originalName; - fixed.remove(" (jar mod)"); - out->setDisplayName(fixed); - } - else - { - out->setDisplayName(displayName); - } - return out; -} - -LibraryPtr OneSixVersionFormat::jarModFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename) -{ - return libraryFromJson(problems, libObj, filename); -} - - -QJsonObject OneSixVersionFormat::jarModtoJson(Library *jarmod) -{ - return libraryToJson(jarmod); -} - -LibraryPtr OneSixVersionFormat::modFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename) -{ - return libraryFromJson(problems, libObj, filename); -} - -QJsonObject OneSixVersionFormat::modtoJson(Library *jarmod) -{ - return libraryToJson(jarmod); -} diff --git a/api/logic/minecraft/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h deleted file mode 100644 index 1a091d88..00000000 --- a/api/logic/minecraft/OneSixVersionFormat.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include <minecraft/VersionFile.h> -#include <minecraft/PackProfile.h> -#include <minecraft/Library.h> -#include <QJsonDocument> -#include <ProblemProvider.h> - -class OneSixVersionFormat -{ -public: - // version files / profile patches - static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder); - static QJsonDocument versionFileToJson(const VersionFilePtr &patch); - - // libraries - static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); - static QJsonObject libraryToJson(Library *library); - - // DEPRECATED: old 'plus' jar mods generated by the application - static LibraryPtr plusJarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename, const QString &originalName); - - // new jar mods derived from libraries - static LibraryPtr jarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); - static QJsonObject jarModtoJson(Library * jarmod); - - // mods, also derived from libraries - static LibraryPtr modFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename); - static QJsonObject modtoJson(Library * jarmod); -}; diff --git a/api/logic/minecraft/OpSys.cpp b/api/logic/minecraft/OpSys.cpp deleted file mode 100644 index f6a4ed1c..00000000 --- a/api/logic/minecraft/OpSys.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "OpSys.h" - -OpSys OpSys_fromString(QString name) -{ - if (name == "linux") - return Os_Linux; - if (name == "windows") - return Os_Windows; - if (name == "osx") - return Os_OSX; - return Os_Other; -} - -QString OpSys_toString(OpSys name) -{ - switch (name) - { - case Os_Linux: - return "linux"; - case Os_OSX: - return "osx"; - case Os_Windows: - return "windows"; - default: - return "other"; - } -}
\ No newline at end of file diff --git a/api/logic/minecraft/OpSys.h b/api/logic/minecraft/OpSys.h deleted file mode 100644 index 63c750b1..00000000 --- a/api/logic/minecraft/OpSys.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -enum OpSys -{ - Os_Windows, - Os_Linux, - Os_OSX, - Os_Other -}; - -OpSys OpSys_fromString(QString); -QString OpSys_toString(OpSys); - -#ifdef Q_OS_WIN32 -#define currentSystem Os_Windows -#else -#ifdef Q_OS_MAC -#define currentSystem Os_OSX -#else -#define currentSystem Os_Linux -#endif -#endif
\ No newline at end of file diff --git a/api/logic/minecraft/PackProfile.cpp b/api/logic/minecraft/PackProfile.cpp deleted file mode 100644 index f6918116..00000000 --- a/api/logic/minecraft/PackProfile.cpp +++ /dev/null @@ -1,1225 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QFile> -#include <QCryptographicHash> -#include <Version.h> -#include <QDir> -#include <QJsonDocument> -#include <QJsonArray> -#include <QDebug> - -#include "Exception.h" -#include <minecraft/OneSixVersionFormat.h> -#include <FileSystem.h> -#include <QSaveFile> -#include <Env.h> -#include <meta/Index.h> -#include <minecraft/MinecraftInstance.h> -#include <QUuid> -#include <QTimer> -#include <Json.h> - -#include "PackProfile.h" -#include "PackProfile_p.h" -#include "ComponentUpdateTask.h" - -PackProfile::PackProfile(MinecraftInstance * instance) - : QAbstractListModel() -{ - d.reset(new PackProfileData); - d->m_instance = instance; - d->m_saveTimer.setSingleShot(true); - d->m_saveTimer.setInterval(5000); - d->interactionDisabled = instance->isRunning(); - connect(d->m_instance, &BaseInstance::runningStatusChanged, this, &PackProfile::disableInteraction); - connect(&d->m_saveTimer, &QTimer::timeout, this, &PackProfile::save_internal); -} - -PackProfile::~PackProfile() -{ - saveNow(); -} - -// BEGIN: component file format - -static const int currentComponentsFileVersion = 1; - -static QJsonObject componentToJsonV1(ComponentPtr component) -{ - QJsonObject obj; - // critical - obj.insert("uid", component->m_uid); - if(!component->m_version.isEmpty()) - { - obj.insert("version", component->m_version); - } - if(component->m_dependencyOnly) - { - obj.insert("dependencyOnly", true); - } - if(component->m_important) - { - obj.insert("important", true); - } - if(component->m_disabled) - { - obj.insert("disabled", true); - } - - // cached - if(!component->m_cachedVersion.isEmpty()) - { - obj.insert("cachedVersion", component->m_cachedVersion); - } - if(!component->m_cachedName.isEmpty()) - { - obj.insert("cachedName", component->m_cachedName); - } - Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires"); - Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); - if(component->m_cachedVolatile) - { - obj.insert("cachedVolatile", true); - } - return obj; -} - -static ComponentPtr componentFromJsonV1(PackProfile * parent, const QString & componentJsonPattern, const QJsonObject &obj) -{ - // critical - auto uid = Json::requireString(obj.value("uid")); - auto filePath = componentJsonPattern.arg(uid); - auto component = new Component(parent, uid); - component->m_version = Json::ensureString(obj.value("version")); - component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false); - component->m_important = Json::ensureBoolean(obj.value("important"), false); - - // cached - // TODO @RESILIENCE: ignore invalid values/structure here? - component->m_cachedVersion = Json::ensureString(obj.value("cachedVersion")); - component->m_cachedName = Json::ensureString(obj.value("cachedName")); - Meta::parseRequires(obj, &component->m_cachedRequires, "cachedRequires"); - Meta::parseRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); - component->m_cachedVolatile = Json::ensureBoolean(obj.value("volatile"), false); - bool disabled = Json::ensureBoolean(obj.value("disabled"), false); - component->setEnabled(!disabled); - return component; -} - -// Save the given component container data to a file -static bool savePackProfile(const QString & filename, const ComponentContainer & container) -{ - QJsonObject obj; - obj.insert("formatVersion", currentComponentsFileVersion); - QJsonArray orderArray; - for(auto component: container) - { - orderArray.append(componentToJsonV1(component)); - } - obj.insert("components", orderArray); - QSaveFile outFile(filename); - if (!outFile.open(QFile::WriteOnly)) - { - qCritical() << "Couldn't open" << outFile.fileName() - << "for writing:" << outFile.errorString(); - return false; - } - auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented); - if(outFile.write(data) != data.size()) - { - qCritical() << "Couldn't write all the data into" << outFile.fileName() - << "because:" << outFile.errorString(); - return false; - } - if(!outFile.commit()) - { - qCritical() << "Couldn't save" << outFile.fileName() - << "because:" << outFile.errorString(); - } - return true; -} - -// Read the given file into component containers -static bool loadPackProfile(PackProfile * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container) -{ - QFile componentsFile(filename); - if (!componentsFile.exists()) - { - qWarning() << "Components file doesn't exist. This should never happen."; - return false; - } - if (!componentsFile.open(QFile::ReadOnly)) - { - qCritical() << "Couldn't open" << componentsFile.fileName() - << " for reading:" << componentsFile.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } - - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } - - // and then read it and process it if all above is true. - try - { - auto obj = Json::requireObject(doc); - // check order file version. - auto version = Json::requireInteger(obj.value("formatVersion")); - if (version != currentComponentsFileVersion) - { - throw JSONValidationError(QObject::tr("Invalid component file version, expected %1") - .arg(currentComponentsFileVersion)); - } - auto orderArray = Json::requireArray(obj.value("components")); - for(auto item: orderArray) - { - auto obj = Json::requireObject(item, "Component must be an object."); - container.append(componentFromJsonV1(parent, componentJsonPattern, obj)); - } - } - catch (const JSONValidationError &err) - { - qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; - container.clear(); - return false; - } - return true; -} - -// END: component file format - -// BEGIN: save/load logic - -void PackProfile::saveNow() -{ - if(saveIsScheduled()) - { - d->m_saveTimer.stop(); - save_internal(); - } -} - -bool PackProfile::saveIsScheduled() const -{ - return d->dirty; -} - -void PackProfile::buildingFromScratch() -{ - d->loaded = true; - d->dirty = true; -} - -void PackProfile::scheduleSave() -{ - if(!d->loaded) - { - qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name(); - return; - } - if(!d->dirty) - { - d->dirty = true; - qDebug() << "Component list save is scheduled for" << d->m_instance->name(); - } - d->m_saveTimer.start(); -} - -QString PackProfile::componentsFilePath() const -{ - return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); -} - -QString PackProfile::patchesPattern() const -{ - return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json"); -} - -QString PackProfile::patchFilePathForUid(const QString& uid) const -{ - return patchesPattern().arg(uid); -} - -void PackProfile::save_internal() -{ - qDebug() << "Component list save performed now for" << d->m_instance->name(); - auto filename = componentsFilePath(); - savePackProfile(filename, d->components); - d->dirty = false; -} - -bool PackProfile::load() -{ - auto filename = componentsFilePath(); - QFile componentsFile(filename); - - // migrate old config to new one, if needed - if(!componentsFile.exists()) - { - if(!migratePreComponentConfig()) - { - // FIXME: the user should be notified... - qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name(); - return false; - } - } - - // load the new component list and swap it with the current one... - ComponentContainer newComponents; - if(!loadPackProfile(this, filename, patchesPattern(), newComponents)) - { - qCritical() << "Failed to load the component config for instance" << d->m_instance->name(); - return false; - } - else - { - // FIXME: actually use fine-grained updates, not this... - beginResetModel(); - // disconnect all the old components - for(auto component: d->components) - { - disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); - } - d->components.clear(); - d->componentIndex.clear(); - for(auto component: newComponents) - { - if(d->componentIndex.contains(component->m_uid)) - { - qWarning() << "Ignoring duplicate component entry" << component->m_uid; - continue; - } - connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); - d->components.append(component); - d->componentIndex[component->m_uid] = component; - } - endResetModel(); - d->loaded = true; - return true; - } -} - -void PackProfile::reload(Net::Mode netmode) -{ - // Do not reload when the update/resolve task is running. It is in control. - if(d->m_updateTask) - { - return; - } - - // flush any scheduled saves to not lose state - saveNow(); - - // FIXME: differentiate when a reapply is required by propagating state from components - invalidateLaunchProfile(); - - if(load()) - { - resolve(netmode); - } -} - -shared_qobject_ptr<Task> PackProfile::getCurrentTask() -{ - return d->m_updateTask; -} - -void PackProfile::resolve(Net::Mode netmode) -{ - auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this); - d->m_updateTask.reset(updateTask); - connect(updateTask, &ComponentUpdateTask::succeeded, this, &PackProfile::updateSucceeded); - connect(updateTask, &ComponentUpdateTask::failed, this, &PackProfile::updateFailed); - d->m_updateTask->start(); -} - - -void PackProfile::updateSucceeded() -{ - qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name(); - d->m_updateTask.reset(); - invalidateLaunchProfile(); -} - -void PackProfile::updateFailed(const QString& error) -{ - qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error; - d->m_updateTask.reset(); - invalidateLaunchProfile(); -} - -// NOTE this is really old stuff, and only needs to be used when loading the old hardcoded component-unaware format (loadPreComponentConfig). -static void upgradeDeprecatedFiles(QString root, QString instanceName) -{ - auto versionJsonPath = FS::PathCombine(root, "version.json"); - auto customJsonPath = FS::PathCombine(root, "custom.json"); - auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json"); - - QString sourceFile; - QString renameFile; - - // convert old crap. - if(QFile::exists(customJsonPath)) - { - sourceFile = customJsonPath; - renameFile = versionJsonPath; - } - else if(QFile::exists(versionJsonPath)) - { - sourceFile = versionJsonPath; - } - if(!sourceFile.isEmpty() && !QFile::exists(mcJson)) - { - if(!FS::ensureFilePathExists(mcJson)) - { - qWarning() << "Couldn't create patches folder for" << instanceName; - return; - } - if(!renameFile.isEmpty() && QFile::exists(renameFile)) - { - if(!QFile::rename(renameFile, renameFile + ".old")) - { - qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName; - return; - } - } - auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false); - ProfileUtils::removeLwjglFromPatch(file); - file->uid = "net.minecraft"; - file->version = file->minecraftVersion; - file->name = "Minecraft"; - - Meta::Require needsLwjgl; - needsLwjgl.uid = "org.lwjgl"; - file->requires.insert(needsLwjgl); - - if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson)) - { - return; - } - if(!QFile::rename(sourceFile, sourceFile + ".old")) - { - qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName; - return; - } - } -} - -/* - * Migrate old layout to the component based one... - * - Part of the version information is taken from `instance.cfg` (fed to this class from outside). - * - Part is taken from the old order.json file. - * - Part is loaded from loose json files in the instance's `patches` directory. - */ -bool PackProfile::migratePreComponentConfig() -{ - // upgrade the very old files from the beginnings of MultiMC 5 - upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name()); - - QList<ComponentPtr> components; - QSet<QString> loaded; - - auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict) - { - auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json"); - auto intendedVersion = d->getOldConfigVersion(uid); - // load up the base minecraft patch - ComponentPtr component; - if(QFile::exists(jsonFilePath)) - { - if(intendedVersion.isEmpty()) - { - intendedVersion = emptyVersion; - } - auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false); - // fix uid - file->uid = uid; - // if version is missing, add it from the outside. - if(file->version.isEmpty()) - { - file->version = intendedVersion; - } - // if this is a dependency (LWJGL), mark it also as volatile - if(asDependency) - { - file->m_volatile = true; - } - // insert requirements if needed - if(!req.uid.isEmpty()) - { - file->requires.insert(req); - } - // insert conflicts if needed - if(!conflict.uid.isEmpty()) - { - file->conflicts.insert(conflict); - } - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath); - component = new Component(this, uid, file); - component->m_version = intendedVersion; - } - else if(!intendedVersion.isEmpty()) - { - auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion); - component = new Component(this, metaVersion); - } - else - { - return; - } - component->m_dependencyOnly = asDependency; - component->m_important = !asDependency; - components.append(component); - }; - // TODO: insert depends and conflicts here if these are customized files... - Meta::Require reqLwjgl; - reqLwjgl.uid = "org.lwjgl"; - reqLwjgl.suggests = "2.9.1"; - Meta::Require conflictLwjgl3; - conflictLwjgl3.uid = "org.lwjgl3"; - Meta::Require nullReq; - addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3); - addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq); - - // first, collect all other file-based patches and load them - QMap<QString, ComponentPtr> loadedComponents; - QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches")); - for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - // parse the file - qDebug() << "Reading" << info.fileName(); - auto file = ProfileUtils::parseJsonFile(info, true); - - // correct missing or wrong uid based on the file name - QString uid = info.completeBaseName(); - - // ignore builtins, they've been handled already - if (uid == "net.minecraft") - continue; - if (uid == "org.lwjgl") - continue; - - // handle horrible corner cases - if(uid.isEmpty()) - { - // if you have a file named '.json', make it just go away. - // FIXME: @QUALITY do not ignore return value - QFile::remove(info.absoluteFilePath()); - continue; - } - file->uid = uid; - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath()); - - auto component = new Component(this, file->uid, file); - auto version = d->getOldConfigVersion(file->uid); - if(!version.isEmpty()) - { - component->m_version = version; - } - loadedComponents[file->uid] = component; - } - // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files - auto loadSpecial = [&](const QString & uid, int order) - { - auto patchVersion = d->getOldConfigVersion(uid); - if(!patchVersion.isEmpty() && !loadedComponents.contains(uid)) - { - auto patch = new Component(this, ENV.metadataIndex()->get(uid, patchVersion)); - patch->setOrder(order); - loadedComponents[uid] = patch; - } - }; - loadSpecial("net.minecraftforge", 5); - loadSpecial("com.mumfrey.liteloader", 10); - - // load the old order.json file, if present - ProfileUtils::PatchOrder userOrder; - ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder); - - // now add all the patches by user sort order - for (auto uid : userOrder) - { - // ignore builtins - if (uid == "net.minecraft") - continue; - if (uid == "org.lwjgl") - continue; - // ordering has a patch that is gone? - if(!loadedComponents.contains(uid)) - { - continue; - } - components.append(loadedComponents.take(uid)); - } - - // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json - if(!loadedComponents.isEmpty()) - { - // inserting into multimap by order number as key sorts the patches and detects duplicates - QMultiMap<int, ComponentPtr> files; - auto iter = loadedComponents.begin(); - while(iter != loadedComponents.end()) - { - files.insert((*iter)->getOrder(), *iter); - iter++; - } - - // then just extract the patches and put them in the list - for (auto order : files.keys()) - { - const auto &values = files.values(order); - for(auto &value: values) - { - // TODO: put back the insertion of problem messages here, so the user knows about the id duplication - components.append(value); - } - } - } - // new we have a complete list of components... - return savePackProfile(componentsFilePath(), components); -} - -// END: save/load - -void PackProfile::appendComponent(ComponentPtr component) -{ - insertComponent(d->components.size(), component); -} - -void PackProfile::insertComponent(size_t index, ComponentPtr component) -{ - auto id = component->getID(); - if(id.isEmpty()) - { - qWarning() << "Attempt to add a component with empty ID!"; - return; - } - if(d->componentIndex.contains(id)) - { - qWarning() << "Attempt to add a component that is already present!"; - return; - } - beginInsertRows(QModelIndex(), index, index); - d->components.insert(index, component); - d->componentIndex[id] = component; - endInsertRows(); - connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); - scheduleSave(); -} - -void PackProfile::componentDataChanged() -{ - auto objPtr = qobject_cast<Component *>(sender()); - if(!objPtr) - { - qWarning() << "PackProfile got dataChenged signal from a non-Component!"; - return; - } - if(objPtr->getID() == "net.minecraft") { - emit minecraftChanged(); - } - // figure out which one is it... in a seriously dumb way. - int index = 0; - for (auto component: d->components) - { - if(component.get() == objPtr) - { - emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); - scheduleSave(); - return; - } - index++; - } - qWarning() << "PackProfile got dataChenged signal from a Component which does not belong to it!"; -} - -bool PackProfile::remove(const int index) -{ - auto patch = getComponent(index); - if (!patch->isRemovable()) - { - qWarning() << "Patch" << patch->getID() << "is non-removable"; - return false; - } - - if(!removeComponent_internal(patch)) - { - qCritical() << "Patch" << patch->getID() << "could not be removed"; - return false; - } - - beginRemoveRows(QModelIndex(), index, index); - d->components.removeAt(index); - d->componentIndex.remove(patch->getID()); - endRemoveRows(); - invalidateLaunchProfile(); - scheduleSave(); - return true; -} - -bool PackProfile::remove(const QString id) -{ - int i = 0; - for (auto patch : d->components) - { - if (patch->getID() == id) - { - return remove(i); - } - i++; - } - return false; -} - -bool PackProfile::customize(int index) -{ - auto patch = getComponent(index); - if (!patch->isCustomizable()) - { - qDebug() << "Patch" << patch->getID() << "is not customizable"; - return false; - } - if(!patch->customize()) - { - qCritical() << "Patch" << patch->getID() << "could not be customized"; - return false; - } - invalidateLaunchProfile(); - scheduleSave(); - return true; -} - -bool PackProfile::revertToBase(int index) -{ - auto patch = getComponent(index); - if (!patch->isRevertible()) - { - qDebug() << "Patch" << patch->getID() << "is not revertible"; - return false; - } - if(!patch->revert()) - { - qCritical() << "Patch" << patch->getID() << "could not be reverted"; - return false; - } - invalidateLaunchProfile(); - scheduleSave(); - return true; -} - -Component * PackProfile::getComponent(const QString &id) -{ - auto iter = d->componentIndex.find(id); - if (iter == d->componentIndex.end()) - { - return nullptr; - } - return (*iter).get(); -} - -Component * PackProfile::getComponent(int index) -{ - if(index < 0 || index >= d->components.size()) - { - return nullptr; - } - return d->components[index].get(); -} - -QVariant PackProfile::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= d->components.size()) - return QVariant(); - - auto patch = d->components.at(row); - - switch (role) - { - case Qt::CheckStateRole: - { - switch (column) - { - case NameColumn: { - return patch->isEnabled() ? Qt::Checked : Qt::Unchecked; - } - default: - return QVariant(); - } - } - case Qt::DisplayRole: - { - switch (column) - { - case NameColumn: - return patch->getName(); - case VersionColumn: - { - if(patch->isCustom()) - { - return QString("%1 (Custom)").arg(patch->getVersion()); - } - else - { - return patch->getVersion(); - } - } - default: - return QVariant(); - } - } - case Qt::DecorationRole: - { - switch(column) - { - case NameColumn: - { - auto severity = patch->getProblemSeverity(); - switch (severity) - { - case ProblemSeverity::Warning: - return "warning"; - case ProblemSeverity::Error: - return "error"; - default: - return QVariant(); - } - } - default: - { - return QVariant(); - } - } - } - } - return QVariant(); -} - -bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role) -{ - if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index)) - { - return false; - } - - if (role == Qt::CheckStateRole) - { - auto component = d->components[index.row()]; - if (component->setEnabled(!component->isEnabled())) - { - return true; - } - } - return false; -} - -QVariant PackProfile::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) - { - if (role == Qt::DisplayRole) - { - switch (section) - { - case NameColumn: - return tr("Name"); - case VersionColumn: - return tr("Version"); - default: - return QVariant(); - } - } - } - return QVariant(); -} - -// FIXME: zero precision mess -Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const -{ - if (!index.isValid()) { - return Qt::NoItemFlags; - } - - Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; - - int row = index.row(); - - if (row < 0 || row >= d->components.size()) { - return Qt::NoItemFlags; - } - - auto patch = d->components.at(row); - // TODO: this will need fine-tuning later... - if(patch->canBeDisabled() && !d->interactionDisabled) - { - outFlags |= Qt::ItemIsUserCheckable; - } - return outFlags; -} - -int PackProfile::rowCount(const QModelIndex &parent) const -{ - return d->components.size(); -} - -int PackProfile::columnCount(const QModelIndex &parent) const -{ - return NUM_COLUMNS; -} - -void PackProfile::move(const int index, const MoveDirection direction) -{ - int theirIndex; - if (direction == MoveUp) - { - theirIndex = index - 1; - } - else - { - theirIndex = index + 1; - } - - if (index < 0 || index >= d->components.size()) - return; - if (theirIndex >= rowCount()) - theirIndex = rowCount() - 1; - if (theirIndex == -1) - theirIndex = rowCount() - 1; - if (index == theirIndex) - return; - int togap = theirIndex > index ? theirIndex + 1 : theirIndex; - - auto from = getComponent(index); - auto to = getComponent(theirIndex); - - if (!from || !to || !to->isMoveable() || !from->isMoveable()) - { - return; - } - beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); - d->components.swap(index, theirIndex); - endMoveRows(); - invalidateLaunchProfile(); - scheduleSave(); -} - -void PackProfile::invalidateLaunchProfile() -{ - d->m_profile.reset(); -} - -void PackProfile::installJarMods(QStringList selectedFiles) -{ - installJarMods_internal(selectedFiles); -} - -void PackProfile::installCustomJar(QString selectedFile) -{ - installCustomJar_internal(selectedFile); -} - -bool PackProfile::installEmpty(const QString& uid, const QString& name) -{ - QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - auto f = std::make_shared<VersionFile>(); - f->name = name; - f->uid = uid; - f->version = "1"; - QString patchFileName = FS::PathCombine(patchDir, uid + ".json"); - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - appendComponent(new Component(this, f->uid, f)); - scheduleSave(); - invalidateLaunchProfile(); - return true; -} - -bool PackProfile::removeComponent_internal(ComponentPtr patch) -{ - bool ok = true; - // first, remove the patch file. this ensures it's not used anymore - auto fileName = patch->getFilename(); - if(fileName.size()) - { - QFile patchFile(fileName); - if(patchFile.exists() && !patchFile.remove()) - { - qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString(); - return false; - } - } - - // FIXME: we need a generic way of removing local resources, not just jar mods... - auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool - { - if (!jarMod->isLocal()) - { - return true; - } - QStringList jar, temp1, temp2, temp3; - jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath()); - QFileInfo finfo (jar[0]); - if(finfo.exists()) - { - QFile jarModFile(jar[0]); - if(!jarModFile.remove()) - { - qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString(); - return false; - } - return true; - } - return true; - }; - - auto vFile = patch->getVersionFile(); - if(vFile) - { - auto &jarMods = vFile->jarMods; - for(auto &jarmod: jarMods) - { - ok &= preRemoveJarMod(jarmod); - } - } - return ok; -} - -bool PackProfile::installJarMods_internal(QStringList filepaths) -{ - QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - - if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir())) - { - return false; - } - - for(auto filepath:filepaths) - { - QFileInfo sourceInfo(filepath); - auto uuid = QUuid::createUuid(); - QString id = uuid.toString().remove('{').remove('}'); - QString target_filename = id + ".jar"; - QString target_id = "org.multimc.jarmod." + id; - QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; - QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename); - - QFileInfo targetInfo(finalPath); - if(targetInfo.exists()) - { - return false; - } - - if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) - { - return false; - } - - auto f = std::make_shared<VersionFile>(); - auto jarMod = std::make_shared<Library>(); - jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); - jarMod->setFilename(target_filename); - jarMod->setDisplayName(sourceInfo.completeBaseName()); - jarMod->setHint("local"); - f->jarMods.append(jarMod); - f->name = target_name; - f->uid = target_id; - QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - appendComponent(new Component(this, f->uid, f)); - } - scheduleSave(); - invalidateLaunchProfile(); - return true; -} - -bool PackProfile::installCustomJar_internal(QString filepath) -{ - QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - - QString libDir = d->m_instance->getLocalLibraryPath(); - if (!FS::ensureFolderPathExists(libDir)) - { - return false; - } - - auto specifier = GradleSpecifier("org.multimc:customjar:1"); - QFileInfo sourceInfo(filepath); - QString target_filename = specifier.getFileName(); - QString target_id = specifier.artifactId(); - QString target_name = sourceInfo.completeBaseName() + " (custom jar)"; - QString finalPath = FS::PathCombine(libDir, target_filename); - - QFileInfo jarInfo(finalPath); - if (jarInfo.exists()) - { - if(!QFile::remove(finalPath)) - { - return false; - } - } - if (!QFile::copy(filepath, finalPath)) - { - return false; - } - - auto f = std::make_shared<VersionFile>(); - auto jarMod = std::make_shared<Library>(); - jarMod->setRawName(specifier); - jarMod->setDisplayName(sourceInfo.completeBaseName()); - jarMod->setHint("local"); - f->mainJar = jarMod; - f->name = target_name; - f->uid = target_id; - QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - appendComponent(new Component(this, f->uid, f)); - - scheduleSave(); - invalidateLaunchProfile(); - return true; -} - -std::shared_ptr<LaunchProfile> PackProfile::getProfile() const -{ - if(!d->m_profile) - { - try - { - auto profile = std::make_shared<LaunchProfile>(); - for(auto file: d->components) - { - qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); - file->applyTo(profile.get()); - } - d->m_profile = profile; - } - catch (const Exception &error) - { - qWarning() << "Couldn't apply profile patches because: " << error.cause(); - } - } - return d->m_profile; -} - -void PackProfile::setOldConfigVersion(const QString& uid, const QString& version) -{ - if(version.isEmpty()) - { - return; - } - d->m_oldConfigVersions[uid] = version; -} - -bool PackProfile::setComponentVersion(const QString& uid, const QString& version, bool important) -{ - auto iter = d->componentIndex.find(uid); - if(iter != d->componentIndex.end()) - { - ComponentPtr component = *iter; - // set existing - if(component->revert()) - { - component->setVersion(version); - component->setImportant(important); - return true; - } - return false; - } - else - { - // add new - auto component = new Component(this, uid); - component->m_version = version; - component->m_important = important; - appendComponent(component); - return true; - } -} - -QString PackProfile::getComponentVersion(const QString& uid) const -{ - const auto iter = d->componentIndex.find(uid); - if (iter != d->componentIndex.end()) - { - return (*iter)->getVersion(); - } - return QString(); -} - -void PackProfile::disableInteraction(bool disable) -{ - if(d->interactionDisabled != disable) { - d->interactionDisabled = disable; - auto size = d->components.size(); - if(size) { - emit dataChanged(index(0), index(size - 1)); - } - } -} diff --git a/api/logic/minecraft/PackProfile.h b/api/logic/minecraft/PackProfile.h deleted file mode 100644 index e55e6a58..00000000 --- a/api/logic/minecraft/PackProfile.h +++ /dev/null @@ -1,152 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QAbstractListModel> - -#include <QString> -#include <QList> -#include <memory> - -#include "Library.h" -#include "LaunchProfile.h" -#include "Component.h" -#include "ProfileUtils.h" -#include "BaseVersion.h" -#include "MojangDownloadInfo.h" -#include "multimc_logic_export.h" -#include "net/Mode.h" - -class MinecraftInstance; -struct PackProfileData; -class ComponentUpdateTask; - -class MULTIMC_LOGIC_EXPORT PackProfile : public QAbstractListModel -{ - Q_OBJECT - friend ComponentUpdateTask; -public: - enum Columns - { - NameColumn = 0, - VersionColumn, - NUM_COLUMNS - }; - - explicit PackProfile(MinecraftInstance * instance); - virtual ~PackProfile(); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - virtual int columnCount(const QModelIndex &parent) const override; - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - - /// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch. - void buildingFromScratch(); - - /// install more jar mods - void installJarMods(QStringList selectedFiles); - - /// install a jar/zip as a replacement for the main jar - void installCustomJar(QString selectedFile); - - enum MoveDirection { MoveUp, MoveDown }; - /// move component file # up or down the list - void move(const int index, const MoveDirection direction); - - /// remove component file # - including files/records - bool remove(const int index); - - /// remove component file by id - including files/records - bool remove(const QString id); - - bool customize(int index); - - bool revertToBase(int index); - - /// reload the list, reload all components, resolve dependencies - void reload(Net::Mode netmode); - - // reload all components, resolve dependencies - void resolve(Net::Mode netmode); - - /// get current running task... - shared_qobject_ptr<Task> getCurrentTask(); - - std::shared_ptr<LaunchProfile> getProfile() const; - - // NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config - void setOldConfigVersion(const QString &uid, const QString &version); - - QString getComponentVersion(const QString &uid) const; - - bool setComponentVersion(const QString &uid, const QString &version, bool important = false); - - bool installEmpty(const QString &uid, const QString &name); - - QString patchFilePathForUid(const QString &uid) const; - - /// if there is a save scheduled, do it now. - void saveNow(); - -signals: - void minecraftChanged(); - -public: - /// get the profile component by id - Component * getComponent(const QString &id); - - /// get the profile component by index - Component * getComponent(int index); - - /// Add the component to the internal list of patches - // todo(merged): is this the best approach - void appendComponent(ComponentPtr component); - -private: - void scheduleSave(); - bool saveIsScheduled() const; - - /// apply the component patches. Catches all the errors and returns true/false for success/failure - void invalidateLaunchProfile(); - - /// insert component so that its index is ideally the specified one (returns real index) - void insertComponent(size_t index, ComponentPtr component); - - QString componentsFilePath() const; - QString patchesPattern() const; - -private slots: - void save_internal(); - void updateSucceeded(); - void updateFailed(const QString & error); - void componentDataChanged(); - void disableInteraction(bool disable); - -private: - bool load(); - bool installJarMods_internal(QStringList filepaths); - bool installCustomJar_internal(QString filepath); - bool removeComponent_internal(ComponentPtr patch); - - bool migratePreComponentConfig(); - -private: /* data */ - - std::unique_ptr<PackProfileData> d; -}; diff --git a/api/logic/minecraft/PackProfile_p.h b/api/logic/minecraft/PackProfile_p.h deleted file mode 100644 index 6cd2a4e5..00000000 --- a/api/logic/minecraft/PackProfile_p.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "Component.h" -#include <map> -#include <QTimer> -#include <QList> -#include <QMap> - -class MinecraftInstance; -using ComponentContainer = QList<ComponentPtr>; -using ComponentIndex = QMap<QString, ComponentPtr>; - -struct PackProfileData -{ - // the instance this belongs to - MinecraftInstance *m_instance; - - // the launch profile (volatile, temporary thing created on demand) - std::shared_ptr<LaunchProfile> m_profile; - - // version information migrated from instance.cfg file. Single use on migration! - std::map<QString, QString> m_oldConfigVersions; - QString getOldConfigVersion(const QString& uid) const - { - const auto iter = m_oldConfigVersions.find(uid); - if(iter != m_oldConfigVersions.cend()) - { - return (*iter).second; - } - return QString(); - } - - // persistent list of components and related machinery - ComponentContainer components; - ComponentIndex componentIndex; - bool dirty = false; - QTimer m_saveTimer; - shared_qobject_ptr<Task> m_updateTask; - bool loaded = false; - bool interactionDisabled = true; -}; - diff --git a/api/logic/minecraft/ParseUtils.cpp b/api/logic/minecraft/ParseUtils.cpp deleted file mode 100644 index c9640e77..00000000 --- a/api/logic/minecraft/ParseUtils.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include <QDateTime> -#include <QString> -#include "ParseUtils.h" -#include <QDebug> -#include <cstdlib> - -QDateTime timeFromS3Time(QString str) -{ - return QDateTime::fromString(str, Qt::ISODate); -} - -QString timeToS3Time(QDateTime time) -{ - // this all because Qt can't format timestamps right. - int offsetRaw = time.offsetFromUtc(); - bool negative = offsetRaw < 0; - int offsetAbs = std::abs(offsetRaw); - - int offsetSeconds = offsetAbs % 60; - offsetAbs -= offsetSeconds; - - int offsetMinutes = offsetAbs % 3600; - offsetAbs -= offsetMinutes; - offsetMinutes /= 60; - - int offsetHours = offsetAbs / 3600; - - QString raw = time.toString("yyyy-MM-ddTHH:mm:ss"); - raw += (negative ? QChar('-') : QChar('+')); - raw += QString("%1").arg(offsetHours, 2, 10, QChar('0')); - raw += ":"; - raw += QString("%1").arg(offsetMinutes, 2, 10, QChar('0')); - return raw; -} diff --git a/api/logic/minecraft/ParseUtils.h b/api/logic/minecraft/ParseUtils.h deleted file mode 100644 index 2b367a10..00000000 --- a/api/logic/minecraft/ParseUtils.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include <QString> -#include <QDateTime> - -#include "multimc_logic_export.h" - -/// take the timestamp used by S3 and turn it into QDateTime -MULTIMC_LOGIC_EXPORT QDateTime timeFromS3Time(QString str); - -/// take a timestamp and convert it into an S3 timestamp -MULTIMC_LOGIC_EXPORT QString timeToS3Time(QDateTime); diff --git a/api/logic/minecraft/ParseUtils_test.cpp b/api/logic/minecraft/ParseUtils_test.cpp deleted file mode 100644 index fcc137e5..00000000 --- a/api/logic/minecraft/ParseUtils_test.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "minecraft/ParseUtils.h" - -class ParseUtilsTest : public QObject -{ - Q_OBJECT -private -slots: - void test_Through_data() - { - QTest::addColumn<QString>("timestamp"); - const char * timestamps[] = - { - "2016-02-29T13:49:54+01:00", - "2016-02-26T15:21:11+00:01", - "2016-02-24T15:52:36+01:13", - "2016-02-18T17:41:00+00:00", - "2016-02-17T15:23:19+00:00", - "2016-02-16T15:22:39+09:22", - "2016-02-10T15:06:41+00:00", - "2016-02-04T15:28:02-05:33" - }; - for(unsigned i = 0; i < (sizeof(timestamps) / sizeof(const char *)); i++) - { - QTest::newRow(timestamps[i]) << QString(timestamps[i]); - } - } - void test_Through() - { - QFETCH(QString, timestamp); - - auto time_parsed = timeFromS3Time(timestamp); - auto time_serialized = timeToS3Time(time_parsed); - - QCOMPARE(time_serialized, timestamp); - } - -}; - -QTEST_GUILESS_MAIN(ParseUtilsTest) - -#include "ParseUtils_test.moc" - diff --git a/api/logic/minecraft/ProfileUtils.cpp b/api/logic/minecraft/ProfileUtils.cpp deleted file mode 100644 index 8ca24cc8..00000000 --- a/api/logic/minecraft/ProfileUtils.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "ProfileUtils.h" -#include "minecraft/VersionFilterData.h" -#include "minecraft/OneSixVersionFormat.h" -#include "Json.h" -#include <QDebug> - -#include <QJsonDocument> -#include <QJsonArray> -#include <QRegularExpression> -#include <QSaveFile> - -namespace ProfileUtils -{ - -static const int currentOrderFileVersion = 1; - -bool readOverrideOrders(QString path, PatchOrder &order) -{ - QFile orderFile(path); - if (!orderFile.exists()) - { - qWarning() << "Order file doesn't exist. Ignoring."; - return false; - } - if (!orderFile.open(QFile::ReadOnly)) - { - qCritical() << "Couldn't open" << orderFile.fileName() - << " for reading:" << orderFile.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } - - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } - - // and then read it and process it if all above is true. - try - { - auto obj = Json::requireObject(doc); - // check order file version. - auto version = Json::requireInteger(obj.value("version")); - if (version != currentOrderFileVersion) - { - throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") - .arg(currentOrderFileVersion)); - } - auto orderArray = Json::requireArray(obj.value("order")); - for(auto item: orderArray) - { - order.append(Json::requireString(item)); - } - } - catch (const JSONValidationError &err) - { - qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; - qWarning() << "Ignoring overriden order"; - order.clear(); - return false; - } - return true; -} - -static VersionFilePtr createErrorVersionFile(QString fileId, QString filepath, QString error) -{ - auto outError = std::make_shared<VersionFile>(); - outError->uid = outError->name = fileId; - // outError->filename = filepath; - outError->addProblem(ProblemSeverity::Error, error); - return outError; -} - -static VersionFilePtr guardedParseJson(const QJsonDocument & doc,const QString &fileId,const QString &filepath,const bool &requireOrder) -{ - try - { - return OneSixVersionFormat::versionFileFromJson(doc, filepath, requireOrder); - } - catch (const Exception &e) - { - return createErrorVersionFile(fileId, filepath, e.cause()); - } -} - -VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder) -{ - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - QJsonParseError error; - auto data = file.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - file.close(); - if (error.error != QJsonParseError::NoError) - { - int line = 1; - int column = 0; - for(int i = 0; i < error.offset; i++) - { - if(data[i] == '\n') - { - line++; - column = 0; - continue; - } - column++; - } - auto errorStr = QObject::tr("Unable to process the version file %1: %2 at line %3 column %4.") - .arg(fileInfo.fileName(), error.errorString()) - .arg(line).arg(column); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder); -} - -bool saveJsonFile(const QJsonDocument doc, const QString & filename) -{ - auto data = doc.toJson(); - QSaveFile jsonFile(filename); - if(!jsonFile.open(QIODevice::WriteOnly)) - { - jsonFile.cancelWriting(); - qWarning() << "Couldn't open" << filename << "for writing"; - return false; - } - jsonFile.write(data); - if(!jsonFile.commit()) - { - qWarning() << "Couldn't save" << filename; - return false; - } - return true; -} - -VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo) -{ - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); - file.close(); - if (doc.isNull()) - { - file.remove(); - throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); - } - return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false); -} - -void removeLwjglFromPatch(VersionFilePtr patch) -{ - auto filter = [](QList<LibraryPtr>& libs) - { - QList<LibraryPtr> filteredLibs; - for (auto lib : libs) - { - if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix())) - { - filteredLibs.append(lib); - } - } - libs = filteredLibs; - }; - filter(patch->libraries); -} -} diff --git a/api/logic/minecraft/ProfileUtils.h b/api/logic/minecraft/ProfileUtils.h deleted file mode 100644 index 351c36cb..00000000 --- a/api/logic/minecraft/ProfileUtils.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "Library.h" -#include "VersionFile.h" - -namespace ProfileUtils -{ -typedef QStringList PatchOrder; - -/// Read and parse a OneSix format order file -bool readOverrideOrders(QString path, PatchOrder &order); - -/// Write a OneSix format order file -bool writeOverrideOrders(QString path, const PatchOrder &order); - - -/// Parse a version file in JSON format -VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder); - -/// Save a JSON file (in any format) -bool saveJsonFile(const QJsonDocument doc, const QString & filename); - -/// Parse a version file in binary JSON format -VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo); - -/// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files. -void removeLwjglFromPatch(VersionFilePtr patch); - -} diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp deleted file mode 100644 index af2861e3..00000000 --- a/api/logic/minecraft/Rule.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QJsonObject> -#include <QJsonArray> - -#include "Rule.h" - -RuleAction RuleAction_fromString(QString name) -{ - if (name == "allow") - return Allow; - if (name == "disallow") - return Disallow; - return Defer; -} - -QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules) -{ - QList<std::shared_ptr<Rule>> rules; - auto rulesVal = objectWithRules.value("rules"); - if (!rulesVal.isArray()) - return rules; - - QJsonArray ruleList = rulesVal.toArray(); - for (auto ruleVal : ruleList) - { - std::shared_ptr<Rule> rule; - if (!ruleVal.isObject()) - continue; - auto ruleObj = ruleVal.toObject(); - auto actionVal = ruleObj.value("action"); - if (!actionVal.isString()) - continue; - auto action = RuleAction_fromString(actionVal.toString()); - if (action == Defer) - continue; - - auto osVal = ruleObj.value("os"); - if (!osVal.isObject()) - { - // add a new implicit action rule - rules.append(ImplicitRule::create(action)); - continue; - } - - auto osObj = osVal.toObject(); - auto osNameVal = osObj.value("name"); - if (!osNameVal.isString()) - continue; - OpSys requiredOs = OpSys_fromString(osNameVal.toString()); - QString versionRegex = osObj.value("version").toString(); - // add a new OS rule - rules.append(OsRule::create(action, requiredOs, versionRegex)); - } - return rules; -} - -QJsonObject ImplicitRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - return ruleObj; -} - -QJsonObject OsRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - QJsonObject osObj; - { - osObj.insert("name", OpSys_toString(m_system)); - if(!m_version_regexp.isEmpty()) - { - osObj.insert("version", m_version_regexp); - } - } - ruleObj.insert("os", osObj); - return ruleObj; -} - diff --git a/api/logic/minecraft/Rule.h b/api/logic/minecraft/Rule.h deleted file mode 100644 index 7aa34d96..00000000 --- a/api/logic/minecraft/Rule.h +++ /dev/null @@ -1,101 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QList> -#include <QJsonObject> -#include <memory> -#include "OpSys.h" - -class Library; -class Rule; - -enum RuleAction -{ - Allow, - Disallow, - Defer -}; - -QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules); - -class Rule -{ -protected: - RuleAction m_result; - virtual bool applies(const Library *parent) = 0; - -public: - Rule(RuleAction result) : m_result(result) - { - } - virtual ~Rule() {}; - virtual QJsonObject toJson() = 0; - RuleAction apply(const Library *parent) - { - if (applies(parent)) - return m_result; - else - return Defer; - } -}; - -class OsRule : public Rule -{ -private: - // the OS - OpSys m_system; - // the OS version regexp - QString m_version_regexp; - -protected: - virtual bool applies(const Library *) - { - return (m_system == currentSystem); - } - OsRule(RuleAction result, OpSys system, QString version_regexp) - : Rule(result), m_system(system), m_version_regexp(version_regexp) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr<OsRule> create(RuleAction result, OpSys system, - QString version_regexp) - { - return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp)); - } -}; - -class ImplicitRule : public Rule -{ -protected: - virtual bool applies(const Library *) - { - return true; - } - ImplicitRule(RuleAction result) : Rule(result) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr<ImplicitRule> create(RuleAction result) - { - return std::shared_ptr<ImplicitRule>(new ImplicitRule(result)); - } -}; diff --git a/api/logic/minecraft/VersionFile.cpp b/api/logic/minecraft/VersionFile.cpp deleted file mode 100644 index d0a1a507..00000000 --- a/api/logic/minecraft/VersionFile.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include <QJsonArray> -#include <QJsonDocument> - -#include <QDebug> - -#include "minecraft/VersionFile.h" -#include "minecraft/Library.h" -#include "minecraft/PackProfile.h" -#include "ParseUtils.h" - -#include <Version.h> - -static bool isMinecraftVersion(const QString &uid) -{ - return uid == "net.minecraft"; -} - -void VersionFile::applyTo(LaunchProfile *profile) -{ - // Only real Minecraft can set those. Don't let anything override them. - if (isMinecraftVersion(uid)) - { - profile->applyMinecraftVersion(minecraftVersion); - profile->applyMinecraftVersionType(type); - // HACK: ignore assets from other version files than Minecraft - // workaround for stupid assets issue caused by amazon: - // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ - profile->applyMinecraftAssets(mojangAssetIndex); - } - - profile->applyMainJar(mainJar); - profile->applyMainClass(mainClass); - profile->applyAppletClass(appletClass); - profile->applyMinecraftArguments(minecraftArguments); - profile->applyTweakers(addTweakers); - profile->applyJarMods(jarMods); - profile->applyMods(mods); - profile->applyTraits(traits); - - for (auto library : libraries) - { - profile->applyLibrary(library); - } - for (auto mavenFile : mavenFiles) - { - profile->applyMavenFile(mavenFile); - } - profile->applyProblemSeverity(getProblemSeverity()); -} - -/* - auto theirVersion = profile->getMinecraftVersion(); - if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull()) - { - if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1) - { - throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion); - } - } -*/ diff --git a/api/logic/minecraft/VersionFile.h b/api/logic/minecraft/VersionFile.h deleted file mode 100644 index b79fcd4f..00000000 --- a/api/logic/minecraft/VersionFile.h +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once - -#include <QString> -#include <QStringList> -#include <QDateTime> -#include <QSet> - -#include <memory> -#include "minecraft/OpSys.h" -#include "minecraft/Rule.h" -#include "ProblemProvider.h" -#include "Library.h" -#include <meta/JsonFormat.h> - -class PackProfile; -class VersionFile; -class LaunchProfile; -struct MojangDownloadInfo; -struct MojangAssetIndexInfo; - -using VersionFilePtr = std::shared_ptr<VersionFile>; -class VersionFile : public ProblemContainer -{ - friend class MojangVersionFormat; - friend class OneSixVersionFormat; -public: /* methods */ - void applyTo(LaunchProfile* profile); - -public: /* data */ - /// MultiMC: order hint for this version file if no explicit order is set - int order = 0; - - /// MultiMC: human readable name of this package - QString name; - - /// MultiMC: package ID of this package - QString uid; - - /// MultiMC: version of this package - QString version; - - /// MultiMC: DEPRECATED dependency on a Minecraft version - QString dependsOnMinecraftVersion; - - /// Mojang: DEPRECATED used to version the Mojang version format - int minimumLauncherVersion = -1; - - /// Mojang: DEPRECATED version of Minecraft this is - QString minecraftVersion; - - /// Mojang: class to launch Minecraft with - QString mainClass; - - /// MultiMC: class to launch legacy Minecraft with (embed in a custom window) - QString appletClass; - - /// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution) - QString minecraftArguments; - - /// Mojang: type of the Minecraft version - QString type; - - /// Mojang: the time this version was actually released by Mojang - QDateTime releaseTime; - - /// Mojang: DEPRECATED the time this version was last updated by Mojang - QDateTime updateTime; - - /// Mojang: DEPRECATED asset group to be used with Minecraft - QString assets; - - /// MultiMC: list of tweaker mod arguments for launchwrapper - QStringList addTweakers; - - /// Mojang: list of libraries to add to the version - QList<LibraryPtr> libraries; - - /// MultiMC: list of maven files to put in the libraries folder, but not in classpath - QList<LibraryPtr> mavenFiles; - - /// The main jar (Minecraft version library, normally) - LibraryPtr mainJar; - - /// MultiMC: list of attached traits of this version file - used to enable features - QSet<QString> traits; - - /// MultiMC: list of jar mods added to this version - QList<LibraryPtr> jarMods; - - /// MultiMC: list of mods added to this version - QList<LibraryPtr> mods; - - /** - * MultiMC: set of packages this depends on - * NOTE: this is shared with the meta format!!! - */ - Meta::RequireSet requires; - - /** - * MultiMC: set of packages this conflicts with - * NOTE: this is shared with the meta format!!! - */ - Meta::RequireSet conflicts; - - /// is volatile -- may be removed as soon as it is no longer needed by something else - bool m_volatile = false; - -public: - // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. - QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads; - - // Mojang: extended asset index download information - std::shared_ptr<MojangAssetIndexInfo> mojangAssetIndex; -}; diff --git a/api/logic/minecraft/VersionFilterData.cpp b/api/logic/minecraft/VersionFilterData.cpp deleted file mode 100644 index 38e7b60c..00000000 --- a/api/logic/minecraft/VersionFilterData.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "VersionFilterData.h" -#include "ParseUtils.h" - -VersionFilterData g_VersionFilterData = VersionFilterData(); - -VersionFilterData::VersionFilterData() -{ - // 1.3.* - auto libs13 = - QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"}}; - - fmlLibsMapping["1.3.2"] = libs13; - - // 1.4.* - auto libs14 = QList<FMLlib>{ - {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"}, - {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb"}}; - - fmlLibsMapping["1.4"] = libs14; - fmlLibsMapping["1.4.1"] = libs14; - fmlLibsMapping["1.4.2"] = libs14; - fmlLibsMapping["1.4.3"] = libs14; - fmlLibsMapping["1.4.4"] = libs14; - fmlLibsMapping["1.4.5"] = libs14; - fmlLibsMapping["1.4.6"] = libs14; - fmlLibsMapping["1.4.7"] = libs14; - - // 1.5 - fmlLibsMapping["1.5"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"}, - {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8"}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}}; - - // 1.5.1 - fmlLibsMapping["1.5.1"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"}, - {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6"}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}}; - - // 1.5.2 - fmlLibsMapping["1.5.2"] = QList<FMLlib>{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"}, - {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9"}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}}; - - // don't use installers for those. - forgeInstallerBlacklist = QSet<QString>({"1.5.2"}); - - // FIXME: remove, used for deciding when core mods should display - legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); - lwjglWhitelist = - QSet<QString>{"net.java.jinput:jinput", "net.java.jinput:jinput-platform", - "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", - "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; - - java8BeginsDate = timeFromS3Time("2017-03-30T09:32:19+00:00"); - java16BeginsDate = timeFromS3Time("2021-05-12T11:19:15+00:00"); -} diff --git a/api/logic/minecraft/VersionFilterData.h b/api/logic/minecraft/VersionFilterData.h deleted file mode 100644 index d100acc3..00000000 --- a/api/logic/minecraft/VersionFilterData.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include <QMap> -#include <QString> -#include <QSet> -#include <QDateTime> - -#include "multimc_logic_export.h" - -struct FMLlib -{ - QString filename; - QString checksum; -}; - -struct VersionFilterData -{ - VersionFilterData(); - // mapping between minecraft versions and FML libraries required - QMap<QString, QList<FMLlib>> fmlLibsMapping; - // set of minecraft versions for which using forge installers is blacklisted - QSet<QString> forgeInstallerBlacklist; - // no new versions below this date will be accepted from Mojang servers - QDateTime legacyCutoffDate; - // Libraries that belong to LWJGL - QSet<QString> lwjglWhitelist; - // release date of first version to require Java 8 (17w13a) - QDateTime java8BeginsDate; - // release data of first version to require Java 16 (21w19a) - QDateTime java16BeginsDate; -}; -extern VersionFilterData MULTIMC_LOGIC_EXPORT g_VersionFilterData; diff --git a/api/logic/minecraft/World.cpp b/api/logic/minecraft/World.cpp deleted file mode 100644 index a2b4dac7..00000000 --- a/api/logic/minecraft/World.cpp +++ /dev/null @@ -1,520 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 <QDir> -#include <QString> -#include <QDebug> -#include <QSaveFile> -#include "World.h" - -#include "GZip.h" -#include <MMCZip.h> -#include <FileSystem.h> -#include <sstream> -#include <io/stream_reader.h> -#include <tag_string.h> -#include <tag_primitive.h> -#include <quazip.h> -#include <quazipfile.h> -#include <quazipdir.h> - -#include <QCoreApplication> - -#include <nonstd/optional> - -using nonstd::optional; -using nonstd::nullopt; - -GameType::GameType(nonstd::optional<int> original): - original(original) -{ - if(!original) { - return; - } - switch(*original) { - case 0: - type = GameType::Survival; - break; - case 1: - type = GameType::Creative; - break; - case 2: - type = GameType::Adventure; - break; - case 3: - type = GameType::Spectator; - break; - default: - break; - } -} - -QString GameType::toTranslatedString() const -{ - switch (type) - { - case GameType::Survival: - return QCoreApplication::translate("GameType", "Survival"); - case GameType::Creative: - return QCoreApplication::translate("GameType", "Creative"); - case GameType::Adventure: - return QCoreApplication::translate("GameType", "Adventure"); - case GameType::Spectator: - return QCoreApplication::translate("GameType", "Spectator"); - default: - break; - } - if(original) { - return QCoreApplication::translate("GameType", "Unknown (%1)").arg(*original); - } - return QCoreApplication::translate("GameType", "Undefined"); -} - -QString GameType::toLogString() const -{ - switch (type) - { - case GameType::Survival: - return "Survival"; - case GameType::Creative: - return "Creative"; - case GameType::Adventure: - return "Adventure"; - case GameType::Spectator: - return "Spectator"; - default: - break; - } - if(original) { - return QString("Unknown (%1)").arg(*original); - } - return "Undefined"; -} - -std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data) -{ - QByteArray output; - if(!GZip::unzip(data, output)) - { - return nullptr; - } - std::istringstream foo(std::string(output.constData(), output.size())); - try { - auto pair = nbt::io::read_compound(foo); - - if(pair.first != "") - return nullptr; - - if(pair.second == nullptr) - return nullptr; - - return std::move(pair.second); - } - catch (const nbt::io::input_error &e) - { - qWarning() << "Unable to parse level.dat:" << e.what(); - return nullptr; - } -} - -QByteArray serializeLevelDat(nbt::tag_compound * levelInfo) -{ - std::ostringstream s; - nbt::io::write_tag("", *levelInfo, s); - QByteArray val( s.str().data(), (int) s.str().size() ); - return val; -} - -QString getLevelDatFromFS(const QFileInfo &file) -{ - QDir worldDir(file.filePath()); - if(!file.isDir() || !worldDir.exists("level.dat")) - { - return QString(); - } - return worldDir.absoluteFilePath("level.dat"); -} - -QByteArray getLevelDatDataFromFS(const QFileInfo &file) -{ - auto fullFilePath = getLevelDatFromFS(file); - if(fullFilePath.isNull()) - { - return QByteArray(); - } - QFile f(fullFilePath); - if(!f.open(QIODevice::ReadOnly)) - { - return QByteArray(); - } - return f.readAll(); -} - -bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data) -{ - auto fullFilePath = getLevelDatFromFS(file); - if(fullFilePath.isNull()) - { - return false; - } - QSaveFile f(fullFilePath); - if(!f.open(QIODevice::WriteOnly)) - { - return false; - } - QByteArray compressed; - if(!GZip::zip(data, compressed)) - { - return false; - } - if(f.write(compressed) != compressed.size()) - { - f.cancelWriting(); - return false; - } - return f.commit(); -} - -World::World(const QFileInfo &file) -{ - repath(file); -} - -void World::repath(const QFileInfo &file) -{ - m_containerFile = file; - m_folderName = file.fileName(); - if(file.isFile() && file.suffix() == "zip") - { - m_iconFile = QString(); - readFromZip(file); - } - else if(file.isDir()) - { - QFileInfo assumedIconPath(file.absoluteFilePath() + "/icon.png"); - if(assumedIconPath.exists()) { - m_iconFile = assumedIconPath.absoluteFilePath(); - } - readFromFS(file); - } -} - -bool World::resetIcon() -{ - if(m_iconFile.isNull()) { - return false; - } - if(QFile(m_iconFile).remove()) { - m_iconFile = QString(); - return true; - } - return false; -} - -void World::readFromFS(const QFileInfo &file) -{ - auto bytes = getLevelDatDataFromFS(file); - if(bytes.isEmpty()) - { - is_valid = false; - return; - } - loadFromLevelDat(bytes); - levelDatTime = file.lastModified(); -} - -void World::readFromZip(const QFileInfo &file) -{ - QuaZip zip(file.absoluteFilePath()); - is_valid = zip.open(QuaZip::mdUnzip); - if (!is_valid) - { - return; - } - auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); - is_valid = !location.isEmpty(); - if (!is_valid) - { - return; - } - m_containerOffsetPath = location; - QuaZipFile zippedFile(&zip); - // read the install profile - is_valid = zip.setCurrentFile(location + "level.dat"); - if (!is_valid) - { - return; - } - is_valid = zippedFile.open(QIODevice::ReadOnly); - QuaZipFileInfo64 levelDatInfo; - zippedFile.getFileInfo(&levelDatInfo); - auto modTime = levelDatInfo.getNTFSmTime(); - if(!modTime.isValid()) - { - modTime = levelDatInfo.dateTime; - } - levelDatTime = modTime; - if (!is_valid) - { - return; - } - loadFromLevelDat(zippedFile.readAll()); - zippedFile.close(); -} - -bool World::install(const QString &to, const QString &name) -{ - auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to)); - if(!FS::ensureFolderPathExists(finalPath)) - { - return false; - } - bool ok = false; - if(m_containerFile.isFile()) - { - QuaZip zip(m_containerFile.absoluteFilePath()); - if (!zip.open(QuaZip::mdUnzip)) - { - return false; - } - ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath); - } - else if(m_containerFile.isDir()) - { - QString from = m_containerFile.filePath(); - ok = FS::copy(from, finalPath)(); - } - - if(ok && !name.isEmpty() && m_actualName != name) - { - World newWorld(finalPath); - if(newWorld.isValid()) - { - newWorld.rename(name); - } - } - return ok; -} - -bool World::rename(const QString &newName) -{ - if(m_containerFile.isFile()) - { - return false; - } - - auto data = getLevelDatDataFromFS(m_containerFile); - if(data.isEmpty()) - { - return false; - } - - auto worldData = parseLevelDat(data); - if(!worldData) - { - return false; - } - auto &val = worldData->at("Data"); - if(val.get_type() != nbt::tag_type::Compound) - { - return false; - } - auto &dataCompound = val.as<nbt::tag_compound>(); - dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data())); - data = serializeLevelDat(worldData.get()); - - putLevelDatDataToFS(m_containerFile, data); - - m_actualName = newName; - - QDir parentDir(m_containerFile.absoluteFilePath()); - parentDir.cdUp(); - QFile container(m_containerFile.absoluteFilePath()); - auto dirName = FS::DirNameFromString(m_actualName, parentDir.absolutePath()); - container.rename(parentDir.absoluteFilePath(dirName)); - - return true; -} - -namespace { - -optional<QString> read_string (nbt::value& parent, const char * name) -{ - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::String) - { - return nullopt; - } - auto & tag_str = namedValue.as<nbt::tag_string>(); - return QString::fromStdString(tag_str.get()); - } - catch (const std::out_of_range &e) - { - // fallback for old world formats - qWarning() << "String NBT tag" << name << "could not be found."; - return nullopt; - } - catch (const std::bad_cast &e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to string."; - return nullopt; - } -} - -optional<int64_t> read_long (nbt::value& parent, const char * name) -{ - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::Long) - { - return nullopt; - } - auto & tag_str = namedValue.as<nbt::tag_long>(); - return tag_str.get(); - } - catch (const std::out_of_range &e) - { - // fallback for old world formats - qWarning() << "Long NBT tag" << name << "could not be found."; - return nullopt; - } - catch (const std::bad_cast &e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to long."; - return nullopt; - } -} - -optional<int> read_int (nbt::value& parent, const char * name) -{ - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::Int) - { - return nullopt; - } - auto & tag_str = namedValue.as<nbt::tag_int>(); - return tag_str.get(); - } - catch (const std::out_of_range &e) - { - // fallback for old world formats - qWarning() << "Int NBT tag" << name << "could not be found."; - return nullopt; - } - catch (const std::bad_cast &e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to int."; - return nullopt; - } -} - -GameType read_gametype(nbt::value& parent, const char * name) { - return GameType(read_int(parent, name)); -} - -} - -void World::loadFromLevelDat(QByteArray data) -{ - auto levelData = parseLevelDat(data); - if(!levelData) - { - is_valid = false; - return; - } - - nbt::value * valPtr = nullptr; - try { - valPtr = &levelData->at("Data"); - } - catch (const std::out_of_range &e) { - qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what(); - is_valid = false; - return; - } - nbt::value &val = *valPtr; - - is_valid = val.get_type() == nbt::tag_type::Compound; - if(!is_valid) - return; - - auto name = read_string(val, "LevelName"); - m_actualName = name ? *name : m_folderName; - - auto timestamp = read_long(val, "LastPlayed"); - m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime; - - m_gameType = read_gametype(val, "GameType"); - - optional<int64_t> randomSeed; - try { - auto &WorldGen_val = val.at("WorldGenSettings"); - randomSeed = read_long(WorldGen_val, "seed"); - } - catch (const std::out_of_range &) {} - if(!randomSeed) { - randomSeed = read_long(val, "RandomSeed"); - } - m_randomSeed = randomSeed ? *randomSeed : 0; - - qDebug() << "World Name:" << m_actualName; - qDebug() << "Last Played:" << m_lastPlayed.toString(); - if(randomSeed) { - qDebug() << "Seed:" << *randomSeed; - } - qDebug() << "GameType:" << m_gameType.toLogString(); -} - -bool World::replace(World &with) -{ - if (!destroy()) - return false; - bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())(); - if (success) - { - m_folderName = with.m_folderName; - m_containerFile.refresh(); - } - return success; -} - -bool World::destroy() -{ - if(!is_valid) return false; - if (m_containerFile.isDir()) - { - QDir d(m_containerFile.filePath()); - return d.removeRecursively(); - } - else if(m_containerFile.isFile()) - { - QFile file(m_containerFile.absoluteFilePath()); - return file.remove(); - } - return true; -} - -bool World::operator==(const World &other) const -{ - return is_valid == other.is_valid && folderName() == other.folderName(); -} diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h deleted file mode 100644 index 1d94d54d..00000000 --- a/api/logic/minecraft/World.h +++ /dev/null @@ -1,113 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 <QFileInfo> -#include <QDateTime> -#include <nonstd/optional> - -#include "multimc_logic_export.h" - -struct MULTIMC_LOGIC_EXPORT GameType { - GameType() = default; - GameType (nonstd::optional<int> original); - - QString toTranslatedString() const; - QString toLogString() const; - - enum - { - Unknown = -1, - Survival = 0, - Creative, - Adventure, - Spectator - } type = Unknown; - nonstd::optional<int> original; -}; - -class MULTIMC_LOGIC_EXPORT World -{ -public: - World(const QFileInfo &file); - QString folderName() const - { - return m_folderName; - } - QString name() const - { - return m_actualName; - } - QString iconFile() const - { - return m_iconFile; - } - QDateTime lastPlayed() const - { - return m_lastPlayed; - } - GameType gameType() const - { - return m_gameType; - } - int64_t seed() const - { - return m_randomSeed; - } - bool isValid() const - { - return is_valid; - } - bool isOnFS() const - { - return m_containerFile.isDir(); - } - QFileInfo container() const - { - return m_containerFile; - } - // delete all the files of this world - bool destroy(); - // replace this world with a copy of the other - bool replace(World &with); - // change the world's filesystem path (used by world lists for *MAGIC* purposes) - void repath(const QFileInfo &file); - // remove the icon file, if any - bool resetIcon(); - - bool rename(const QString &to); - bool install(const QString &to, const QString &name= QString()); - - // WEAK compare operator - used for replacing worlds - bool operator==(const World &other) const; - -private: - void readFromZip(const QFileInfo &file); - void readFromFS(const QFileInfo &file); - void loadFromLevelDat(QByteArray data); - -protected: - - QFileInfo m_containerFile; - QString m_containerOffsetPath; - QString m_folderName; - QString m_actualName; - QString m_iconFile; - QDateTime levelDatTime; - QDateTime m_lastPlayed; - int64_t m_randomSeed = 0; - GameType m_gameType; - bool is_valid = false; -}; diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp deleted file mode 100644 index f6309dbd..00000000 --- a/api/logic/minecraft/WorldList.cpp +++ /dev/null @@ -1,387 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 "WorldList.h" -#include <FileSystem.h> -#include <QMimeData> -#include <QUrl> -#include <QUuid> -#include <QString> -#include <QFileSystemWatcher> -#include <QDebug> - -WorldList::WorldList(const QString &dir) - : QAbstractListModel(), m_dir(dir) -{ - FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | - QDir::NoSymLinks); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); - m_watcher = new QFileSystemWatcher(this); - is_watching = false; - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, - SLOT(directoryChanged(QString))); -} - -void WorldList::startWatching() -{ - if(is_watching) - { - return; - } - update(); - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) - { - qDebug() << "Started watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to start watching " << m_dir.absolutePath(); - } -} - -void WorldList::stopWatching() -{ - if(!is_watching) - { - return; - } - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) - { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } -} - -bool WorldList::update() -{ - if (!isValid()) - return false; - - QList<World> newWorlds; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - // if there are any untracked files... - for (QFileInfo entry : folderContents) - { - if(!entry.isDir()) - continue; - - World w(entry); - if(w.isValid()) - { - newWorlds.append(w); - } - } - beginResetModel(); - worlds.swap(newWorlds); - endResetModel(); - return true; -} - -void WorldList::directoryChanged(QString path) -{ - update(); -} - -bool WorldList::isValid() -{ - return m_dir.exists() && m_dir.isReadable(); -} - -bool WorldList::deleteWorld(int index) -{ - if (index >= worlds.size() || index < 0) - return false; - World &m = worlds[index]; - if (m.destroy()) - { - beginRemoveRows(QModelIndex(), index, index); - worlds.removeAt(index); - endRemoveRows(); - emit changed(); - return true; - } - return false; -} - -bool WorldList::deleteWorlds(int first, int last) -{ - for (int i = first; i <= last; i++) - { - World &m = worlds[i]; - m.destroy(); - } - beginRemoveRows(QModelIndex(), first, last); - worlds.erase(worlds.begin() + first, worlds.begin() + last + 1); - endRemoveRows(); - emit changed(); - return true; -} - -bool WorldList::resetIcon(int row) -{ - if (row >= worlds.size() || row < 0) - return false; - World &m = worlds[row]; - if(m.resetIcon()) { - emit dataChanged(index(row), index(row), {WorldList::IconFileRole}); - return true; - } - return false; -} - - -int WorldList::columnCount(const QModelIndex &parent) const -{ - return 3; -} - -QVariant WorldList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= worlds.size()) - return QVariant(); - - auto & world = worlds[row]; - switch (role) - { - case Qt::DisplayRole: - switch (column) - { - case NameColumn: - return world.name(); - - case GameModeColumn: - return world.gameType().toTranslatedString(); - - case LastPlayedColumn: - return world.lastPlayed(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - { - return world.folderName(); - } - case ObjectRole: - { - return QVariant::fromValue<void *>((void *)&world); - } - case FolderRole: - { - return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName())); - } - case SeedRole: - { - return qVariantFromValue<qlonglong>(world.seed()); - } - case NameRole: - { - return world.name(); - } - case LastPlayedRole: - { - return world.lastPlayed(); - } - case IconFileRole: - { - return world.iconFile(); - } - default: - return QVariant(); - } -} - -QVariant WorldList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case NameColumn: - return tr("Name"); - case GameModeColumn: - return tr("Game Mode"); - case LastPlayedColumn: - return tr("Last Played"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case NameColumn: - return tr("The name of the world."); - case GameModeColumn: - return tr("Game mode of the world."); - case LastPlayedColumn: - return tr("Date and time the world was last played."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); -} - -QStringList WorldList::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - return types; -} - -class WorldMimeData : public QMimeData -{ -Q_OBJECT - -public: - WorldMimeData(QList<World> worlds) - { - m_worlds = worlds; - - } - QStringList formats() const - { - return QMimeData::formats() << "text/uri-list"; - } - -protected: - QVariant retrieveData(const QString &mimetype, QVariant::Type type) const - { - QList<QUrl> urls; - for(auto &world: m_worlds) - { - if(!world.isValid() || !world.isOnFS()) - continue; - QString worldPath = world.container().absoluteFilePath(); - qDebug() << worldPath; - urls.append(QUrl::fromLocalFile(worldPath)); - } - const_cast<WorldMimeData*>(this)->setUrls(urls); - return QMimeData::retrieveData(mimetype, type); - } -private: - QList<World> m_worlds; -}; - -QMimeData *WorldList::mimeData(const QModelIndexList &indexes) const -{ - if (indexes.size() == 0) - return new QMimeData(); - - QList<World> worlds; - for(auto idx : indexes) - { - if(idx.column() != 0) - continue; - int row = idx.row(); - if (row < 0 || row >= this->worlds.size()) - continue; - worlds.append(this->worlds[row]); - } - if(!worlds.size()) - { - return new QMimeData(); - } - return new WorldMimeData(worlds); -} - -Qt::ItemFlags WorldList::flags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - if (index.isValid()) - return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | - defaultFlags; - else - return Qt::ItemIsDropEnabled | defaultFlags; -} - -Qt::DropActions WorldList::supportedDragActions() const -{ - // move to other mod lists or VOID - return Qt::MoveAction; -} - -Qt::DropActions WorldList::supportedDropActions() const -{ - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; -} - -void WorldList::installWorld(QFileInfo filename) -{ - qDebug() << "installing: " << filename.absoluteFilePath(); - World w(filename); - if(!w.isValid()) - { - return; - } - w.install(m_dir.absolutePath()); -} - -bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, - const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) - return true; - // check if the action is supported - if (!data || !(action & supportedDropActions())) - return false; - // files dropped from outside? - if (data->hasUrls()) - { - bool was_watching = is_watching; - if (was_watching) - stopWatching(); - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - continue; - QString filename = url.toLocalFile(); - - QFileInfo worldInfo(filename); - - if(!m_dir.entryInfoList().contains(worldInfo)) - { - installWorld(worldInfo); - } - } - if (was_watching) - startWatching(); - return true; - } - return false; -} - -#include "WorldList.moc" diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h deleted file mode 100644 index 740b1461..00000000 --- a/api/logic/minecraft/WorldList.h +++ /dev/null @@ -1,131 +0,0 @@ -/* Copyright 2015-2021 MultiMC Contributors - * - * 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 <QList> -#include <QString> -#include <QDir> -#include <QAbstractListModel> -#include <QMimeData> -#include "minecraft/World.h" - -#include "multimc_logic_export.h" - -class QFileSystemWatcher; - -class MULTIMC_LOGIC_EXPORT WorldList : public QAbstractListModel -{ - Q_OBJECT -public: - enum Columns - { - NameColumn, - GameModeColumn, - LastPlayedColumn - }; - - enum Roles - { - ObjectRole = Qt::UserRole + 1, - FolderRole, - SeedRole, - NameRole, - GameModeRole, - LastPlayedRole, - IconFileRole - }; - - WorldList(const QString &dir); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const - { - return size(); - }; - virtual QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const; - virtual int columnCount(const QModelIndex &parent) const; - - size_t size() const - { - return worlds.size(); - }; - bool empty() const - { - return size() == 0; - } - World &operator[](size_t index) - { - return worlds[index]; - } - - /// Reloads the mod list and returns true if the list changed. - virtual bool update(); - - /// Install a world from location - void installWorld(QFileInfo filename); - - /// Deletes the mod at the given index. - virtual bool deleteWorld(int index); - - /// Removes the world icon, if any - virtual bool resetIcon(int index); - - /// Deletes all the selected mods - virtual bool deleteWorlds(int first, int last); - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - /// get data for drag action - virtual QMimeData *mimeData(const QModelIndexList &indexes) const; - /// get the supported mime types - virtual QStringList mimeTypes() const; - /// process data from drop action - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - /// what drag actions do we support? - virtual Qt::DropActions supportedDragActions() const; - - /// what drop actions do we support? - virtual Qt::DropActions supportedDropActions() const; - - void startWatching(); - void stopWatching(); - - virtual bool isValid(); - - QDir dir() const - { - return m_dir; - } - - const QList<World> &allWorlds() const - { - return worlds; - } - -private slots: - void directoryChanged(QString path); - -signals: - void changed(); - -protected: - QFileSystemWatcher *m_watcher; - bool is_watching; - QDir m_dir; - QList<World> worlds; -}; diff --git a/api/logic/minecraft/auth-msa/BuildConfig.cpp.in b/api/logic/minecraft/auth-msa/BuildConfig.cpp.in deleted file mode 100644 index 8f470e25..00000000 --- a/api/logic/minecraft/auth-msa/BuildConfig.cpp.in +++ /dev/null @@ -1,9 +0,0 @@ -#include "BuildConfig.h" -#include <QObject> - -const Config BuildConfig; - -Config::Config() -{ - CLIENT_ID = "@MOJANGDEMO_CLIENT_ID@"; -} diff --git a/api/logic/minecraft/auth-msa/BuildConfig.h b/api/logic/minecraft/auth-msa/BuildConfig.h deleted file mode 100644 index 7a01d704..00000000 --- a/api/logic/minecraft/auth-msa/BuildConfig.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include <QString> - -class Config -{ -public: - Config(); - QString CLIENT_ID; -}; - -extern const Config BuildConfig; diff --git a/api/logic/minecraft/auth-msa/CMakeLists.txt b/api/logic/minecraft/auth-msa/CMakeLists.txt deleted file mode 100644 index 22777d1b..00000000 --- a/api/logic/minecraft/auth-msa/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -find_package(Qt5 COMPONENTS Core Gui Network Widgets REQUIRED) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_INCLUDE_CURRENT_DIR ON) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") - - -set(MOJANGDEMO_CLIENT_ID "" CACHE STRING "Client ID used for OAuth2 in mojangdemo") - -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/BuildConfig.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp") - -set(mojang_SRCS - main.cpp - context.cpp - context.h - - mainwindow.cpp - mainwindow.h - mainwindow.ui - - ${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp - BuildConfig.h -) - -add_executable( mojangdemo ${mojang_SRCS} ) -target_link_libraries( mojangdemo Katabasis Qt5::Gui Qt5::Widgets ) -target_include_directories(mojangdemo PRIVATE logic) diff --git a/api/logic/minecraft/auth-msa/context.cpp b/api/logic/minecraft/auth-msa/context.cpp deleted file mode 100644 index d7ecda30..00000000 --- a/api/logic/minecraft/auth-msa/context.cpp +++ /dev/null @@ -1,938 +0,0 @@ -#include <QNetworkAccessManager> -#include <QNetworkRequest> -#include <QNetworkReply> -#include <QDesktopServices> -#include <QMetaEnum> -#include <QDebug> - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> - -#include <QUrlQuery> - -#include <QPixmap> -#include <QPainter> - -#include "context.h" -#include "katabasis/Globals.h" -#include "katabasis/StoreQSettings.h" -#include "katabasis/Requestor.h" -#include "BuildConfig.h" - -using OAuth2 = Katabasis::OAuth2; -using Requestor = Katabasis::Requestor; -using Activity = Katabasis::Activity; - -Context::Context(QObject *parent) : - QObject(parent) -{ - mgr = new QNetworkAccessManager(this); - - Katabasis::OAuth2::Options opts; - opts.scope = "XboxLive.signin offline_access"; - opts.clientIdentifier = BuildConfig.CLIENT_ID; - opts.authorizationUrl = "https://login.live.com/oauth20_authorize.srf"; - opts.accessTokenUrl = "https://login.live.com/oauth20_token.srf"; - opts.listenerPorts = {28562, 28563, 28564, 28565, 28566}; - - oauth2 = new OAuth2(opts, m_account.msaToken, this, mgr); - - connect(oauth2, &OAuth2::linkingFailed, this, &Context::onLinkingFailed); - connect(oauth2, &OAuth2::linkingSucceeded, this, &Context::onLinkingSucceeded); - connect(oauth2, &OAuth2::openBrowser, this, &Context::onOpenBrowser); - connect(oauth2, &OAuth2::closeBrowser, this, &Context::onCloseBrowser); - connect(oauth2, &OAuth2::activityChanged, this, &Context::onOAuthActivityChanged); -} - -void Context::beginActivity(Activity activity) { - if(isBusy()) { - throw 0; - } - activity_ = activity; - emit activityChanged(activity_); -} - -void Context::finishActivity() { - if(!isBusy()) { - throw 0; - } - activity_ = Katabasis::Activity::Idle; - m_account.validity_ = m_account.minecraftProfile.validity; - emit activityChanged(activity_); -} - -QString Context::gameToken() { - return m_account.minecraftToken.token; -} - -QString Context::userId() { - return m_account.minecraftProfile.id; -} - -QString Context::userName() { - return m_account.minecraftProfile.name; -} - -bool Context::silentSignIn() { - if(isBusy()) { - return false; - } - beginActivity(Activity::Refreshing); - if(!oauth2->refresh()) { - finishActivity(); - return false; - } - - requestsDone = 0; - xboxProfileSucceeded = false; - mcAuthSucceeded = false; - - return true; -} - -bool Context::signIn() { - if(isBusy()) { - return false; - } - - requestsDone = 0; - xboxProfileSucceeded = false; - mcAuthSucceeded = false; - - beginActivity(Activity::LoggingIn); - oauth2->unlink(); - m_account = AccountData(); - oauth2->link(); - return true; -} - -bool Context::signOut() { - if(isBusy()) { - return false; - } - beginActivity(Activity::LoggingOut); - oauth2->unlink(); - m_account = AccountData(); - finishActivity(); - return true; -} - - -void Context::onOpenBrowser(const QUrl &url) { - QDesktopServices::openUrl(url); -} - -void Context::onCloseBrowser() { - -} - -void Context::onLinkingFailed() { - finishActivity(); -} - -void Context::onLinkingSucceeded() { - auto *o2t = qobject_cast<OAuth2 *>(sender()); - if (!o2t->linked()) { - finishActivity(); - return; - } - QVariantMap extraTokens = o2t->extraTokens(); - if (!extraTokens.isEmpty()) { - qDebug() << "Extra tokens in response:"; - foreach (QString key, extraTokens.keys()) { - qDebug() << "\t" << key << ":" << extraTokens.value(key); - } - } - doUserAuth(); -} - -void Context::onOAuthActivityChanged(Katabasis::Activity activity) { - // respond to activity change here -} - -void Context::doUserAuth() { - QString xbox_auth_template = R"XXX( -{ - "Properties": { - "AuthMethod": "RPS", - "SiteName": "user.auth.xboxlive.com", - "RpsTicket": "d=%1" - }, - "RelyingParty": "http://auth.xboxlive.com", - "TokenType": "JWT" -} -)XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_account.msaToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - auto *requestor = new Katabasis::Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onUserAuthDone); - requestor->post(request, xbox_auth_data.toUtf8()); - qDebug() << "First layer of XBox auth ... commencing."; -} - -namespace { -bool getDateTime(QJsonValue value, QDateTime & out) { - if(!value.isString()) { - return false; - } - out = QDateTime::fromString(value.toString(), Qt::ISODateWithMs); - return out.isValid(); -} - -bool getString(QJsonValue value, QString & out) { - if(!value.isString()) { - return false; - } - out = value.toString(); - return true; -} - -bool getNumber(QJsonValue value, double & out) { - if(!value.isDouble()) { - return false; - } - out = value.toDouble(); - return true; -} - -/* -{ - "IssueInstant":"2020-12-07T19:52:08.4463796Z", - "NotAfter":"2020-12-21T19:52:08.4463796Z", - "Token":"token", - "DisplayClaims":{ - "xui":[ - { - "uhs":"userhash" - } - ] - } - } -*/ -// TODO: handle error responses ... -/* -{ - "Identity":"0", - "XErr":2148916238, - "Message":"", - "Redirect":"https://start.ui.xboxlive.com/AddChildToFamily" -} -// 2148916233 = missing XBox account -// 2148916238 = child account not linked to a family -*/ - -bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output) { - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if(jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); - qDebug() << data; - return false; - } - - auto obj = doc.object(); - if(!getDateTime(obj.value("IssueInstant"), output.issueInstant)) { - qWarning() << "User IssueInstant is not a timestamp"; - qDebug() << data; - return false; - } - if(!getDateTime(obj.value("NotAfter"), output.notAfter)) { - qWarning() << "User NotAfter is not a timestamp"; - qDebug() << data; - return false; - } - if(!getString(obj.value("Token"), output.token)) { - qWarning() << "User Token is not a timestamp"; - qDebug() << data; - return false; - } - auto arrayVal = obj.value("DisplayClaims").toObject().value("xui"); - if(!arrayVal.isArray()) { - qWarning() << "Missing xui claims array"; - qDebug() << data; - return false; - } - bool foundUHS = false; - for(auto item: arrayVal.toArray()) { - if(!item.isObject()) { - continue; - } - auto obj = item.toObject(); - if(obj.contains("uhs")) { - foundUHS = true; - } else { - continue; - } - // consume all 'display claims' ... whatever that means - for(auto iter = obj.begin(); iter != obj.end(); iter++) { - QString claim; - if(!getString(obj.value(iter.key()), claim)) { - qWarning() << "display claim " << iter.key() << " is not a string..."; - qDebug() << data; - return false; - } - output.extra[iter.key()] = claim; - } - - break; - } - if(!foundUHS) { - qWarning() << "Missing uhs"; - qDebug() << data; - return false; - } - output.validity = Katabasis::Validity::Certain; - qDebug() << data; - return true; -} - -} - -void Context::onUserAuthDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - finishActivity(); - return; - } - - Katabasis::Token temp; - if(!parseXTokenResponse(replyData, temp)) { - qWarning() << "Could not parse user authentication response..."; - finishActivity(); - return; - } - m_account.userToken = temp; - - doSTSAuthMinecraft(); - doSTSAuthGeneric(); -} -/* - url = "https://xsts.auth.xboxlive.com/xsts/authorize" - headers = {"x-xbl-contract-version": "1"} - data = { - "RelyingParty": relying_party, - "TokenType": "JWT", - "Properties": { - "UserTokens": [self.user_token.token], - "SandboxId": "RETAIL", - }, - } -*/ -void Context::doSTSAuthMinecraft() { - QString xbox_auth_template = R"XXX( -{ - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [ - "%1" - ] - }, - "RelyingParty": "rp://api.minecraftservices.com/", - "TokenType": "JWT" -} -)XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_account.userToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onSTSAuthMinecraftDone); - requestor->post(request, xbox_auth_data.toUtf8()); - qDebug() << "Second layer of XBox auth ... commencing."; -} - -void Context::onSTSAuthMinecraftDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - finishActivity(); - return; - } - - Katabasis::Token temp; - if(!parseXTokenResponse(replyData, temp)) { - qWarning() << "Could not parse authorization response for access to mojang services..."; - finishActivity(); - return; - } - - if(temp.extra["uhs"] != m_account.userToken.extra["uhs"]) { - qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING"; - qDebug() << replyData; - finishActivity(); - return; - } - m_account.mojangservicesToken = temp; - - doMinecraftAuth(); -} - -void Context::doSTSAuthGeneric() { - QString xbox_auth_template = R"XXX( -{ - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [ - "%1" - ] - }, - "RelyingParty": "http://xboxlive.com", - "TokenType": "JWT" -} -)XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_account.userToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onSTSAuthGenericDone); - requestor->post(request, xbox_auth_data.toUtf8()); - qDebug() << "Second layer of XBox auth ... commencing."; -} - -void Context::onSTSAuthGenericDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - finishActivity(); - return; - } - - Katabasis::Token temp; - if(!parseXTokenResponse(replyData, temp)) { - qWarning() << "Could not parse authorization response for access to xbox API..."; - finishActivity(); - return; - } - - if(temp.extra["uhs"] != m_account.userToken.extra["uhs"]) { - qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING"; - qDebug() << replyData; - finishActivity(); - return; - } - m_account.xboxApiToken = temp; - - doXBoxProfile(); -} - - -void Context::doMinecraftAuth() { - QString mc_auth_template = R"XXX( -{ - "identityToken": "XBL3.0 x=%1;%2" -} -)XXX"; - auto data = mc_auth_template.arg(m_account.mojangservicesToken.extra["uhs"].toString(), m_account.mojangservicesToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://api.minecraftservices.com/authentication/login_with_xbox")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onMinecraftAuthDone); - requestor->post(request, data.toUtf8()); - qDebug() << "Getting Minecraft access token..."; -} - -namespace { -bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) { - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if(jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); - qDebug() << data; - return false; - } - - auto obj = doc.object(); - double expires_in = 0; - if(!getNumber(obj.value("expires_in"), expires_in)) { - qWarning() << "expires_in is not a valid number"; - qDebug() << data; - return false; - } - auto currentTime = QDateTime::currentDateTimeUtc(); - output.issueInstant = currentTime; - output.notAfter = currentTime.addSecs(expires_in); - - QString username; - if(!getString(obj.value("username"), username)) { - qWarning() << "username is not valid"; - qDebug() << data; - return false; - } - - // TODO: it's a JWT... validate it? - if(!getString(obj.value("access_token"), output.token)) { - qWarning() << "access_token is not valid"; - qDebug() << data; - return false; - } - output.validity = Katabasis::Validity::Certain; - qDebug() << data; - return true; -} -} - -void Context::onMinecraftAuthDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - requestsDone++; - - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - qDebug() << replyData; - finishActivity(); - return; - } - - if(!parseMojangResponse(replyData, m_account.minecraftToken)) { - qWarning() << "Could not parse login_with_xbox response..."; - qDebug() << replyData; - finishActivity(); - return; - } - mcAuthSucceeded = true; - - checkResult(); -} - -void Context::doXBoxProfile() { - auto url = QUrl("https://profile.xboxlive.com/users/me/profile/settings"); - QUrlQuery q; - q.addQueryItem( - "settings", - "GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw," - "PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix," - "UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep," - "PreferredColor,Location,Bio,Watermarks," - "RealName,RealNameOverride,IsQuarantined" - ); - url.setQuery(q); - - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - request.setRawHeader("x-xbl-contract-version", "3"); - request.setRawHeader("Authorization", QString("XBL3.0 x=%1;%2").arg(m_account.userToken.extra["uhs"].toString(), m_account.xboxApiToken.token).toUtf8()); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onXBoxProfileDone); - requestor->get(request); - qDebug() << "Getting Xbox profile..."; -} - -void Context::onXBoxProfileDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList<QNetworkReply::RawHeaderPair> headers -) { - requestsDone ++; - - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - qDebug() << replyData; - finishActivity(); - return; - } - - qDebug() << "XBox profile: " << replyData; - - xboxProfileSucceeded = true; - checkResult(); -} - -void Context::checkResult() { - if(requestsDone != 2) { - return; - } - if(mcAuthSucceeded && xboxProfileSucceeded) { - doMinecraftProfile(); - } - else { - finishActivity(); - } -} - -namespace { -bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) { - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if(jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); - qDebug() << data; - return false; - } - - auto obj = doc.object(); - if(!getString(obj.value("id"), output.id)) { - qWarning() << "minecraft profile id is not a string"; - qDebug() << data; - return false; - } - - if(!getString(obj.value("name"), output.name)) { - qWarning() << "minecraft profile name is not a string"; - qDebug() << data; - return false; - } - - auto skinsArray = obj.value("skins").toArray(); - for(auto skin: skinsArray) { - auto skinObj = skin.toObject(); - Skin skinOut; - if(!getString(skinObj.value("id"), skinOut.id)) { - continue; - } - QString state; - if(!getString(skinObj.value("state"), state)) { - continue; - } - if(state != "ACTIVE") { - continue; - } - if(!getString(skinObj.value("url"), skinOut.url)) { - continue; - } - if(!getString(skinObj.value("variant"), skinOut.variant)) { - continue; - } - // we deal with only the active skin - output.skin = skinOut; - break; - } - auto capesArray = obj.value("capes").toArray(); - int i = -1; - int currentCape = -1; - for(auto cape: capesArray) { - i++; - auto capeObj = cape.toObject(); - Cape capeOut; - if(!getString(capeObj.value("id"), capeOut.id)) { - continue; - } - QString state; - if(!getString(capeObj.value("state"), state)) { - continue; - } - if(state == "ACTIVE") { - currentCape = i; - } - if(!getString(capeObj.value("url"), capeOut.url)) { - continue; - } - if(!getString(capeObj.value("alias"), capeOut.alias)) { - continue; - } - - // we deal with only the active skin - output.capes.push_back(capeOut); - } - output.currentCape = currentCape; - output.validity = Katabasis::Validity::Certain; - return true; -} -} - -void Context::doMinecraftProfile() { - auto url = QUrl("https://api.minecraftservices.com/minecraft/profile"); - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - // request.setRawHeader("Accept", "application/json"); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_account.minecraftToken.token).toUtf8()); - - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onMinecraftProfileDone); - requestor->get(request); -} - -void Context::onMinecraftProfileDone(int, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) { - qDebug() << data; - if (error == QNetworkReply::ContentNotFoundError) { - m_account.minecraftProfile = MinecraftProfile(); - finishActivity(); - return; - } - if (error != QNetworkReply::NoError) { - finishActivity(); - return; - } - if(!parseMinecraftProfile(data, m_account.minecraftProfile)) { - m_account.minecraftProfile = MinecraftProfile(); - finishActivity(); - return; - } - doGetSkin(); -} - -void Context::doGetSkin() { - auto url = QUrl(m_account.minecraftProfile.skin.url); - QNetworkRequest request = QNetworkRequest(url); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - connect(requestor, &Requestor::finished, this, &Context::onSkinDone); - requestor->get(request); -} - -void Context::onSkinDone(int, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair>) { - if (error == QNetworkReply::NoError) { - m_account.minecraftProfile.skin.data = data; - } - finishActivity(); -} - -namespace { -void tokenToJSON(QJsonObject &parent, Katabasis::Token t, const char * tokenName) { - if(t.validity == Katabasis::Validity::None || !t.persistent) { - return; - } - QJsonObject out; - if(t.issueInstant.isValid()) { - out["iat"] = QJsonValue(t.issueInstant.toSecsSinceEpoch()); - } - - if(t.notAfter.isValid()) { - out["exp"] = QJsonValue(t.notAfter.toSecsSinceEpoch()); - } - - if(!t.token.isEmpty()) { - out["token"] = QJsonValue(t.token); - } - if(!t.refresh_token.isEmpty()) { - out["refresh_token"] = QJsonValue(t.refresh_token); - } - if(t.extra.size()) { - out["extra"] = QJsonObject::fromVariantMap(t.extra); - } - if(out.size()) { - parent[tokenName] = out; - } -} - -Katabasis::Token tokenFromJSON(const QJsonObject &parent, const char * tokenName) { - Katabasis::Token out; - auto tokenObject = parent.value(tokenName).toObject(); - if(tokenObject.isEmpty()) { - return out; - } - auto issueInstant = tokenObject.value("iat"); - if(issueInstant.isDouble()) { - out.issueInstant = QDateTime::fromSecsSinceEpoch((int64_t) issueInstant.toDouble()); - } - - auto notAfter = tokenObject.value("exp"); - if(notAfter.isDouble()) { - out.notAfter = QDateTime::fromSecsSinceEpoch((int64_t) notAfter.toDouble()); - } - - auto token = tokenObject.value("token"); - if(token.isString()) { - out.token = token.toString(); - out.validity = Katabasis::Validity::Assumed; - } - - auto refresh_token = tokenObject.value("refresh_token"); - if(refresh_token.isString()) { - out.refresh_token = refresh_token.toString(); - } - - auto extra = tokenObject.value("extra"); - if(extra.isObject()) { - out.extra = extra.toObject().toVariantMap(); - } - return out; -} - -void profileToJSON(QJsonObject &parent, MinecraftProfile p, const char * tokenName) { - if(p.id.isEmpty()) { - return; - } - QJsonObject out; - out["id"] = QJsonValue(p.id); - out["name"] = QJsonValue(p.name); - if(p.currentCape != -1) { - out["cape"] = p.capes[p.currentCape].id; - } - - { - QJsonObject skinObj; - skinObj["id"] = p.skin.id; - skinObj["url"] = p.skin.url; - skinObj["variant"] = p.skin.variant; - if(p.skin.data.size()) { - skinObj["data"] = QString::fromLatin1(p.skin.data.toBase64()); - } - out["skin"] = skinObj; - } - - QJsonArray capesArray; - for(auto & cape: p.capes) { - QJsonObject capeObj; - capeObj["id"] = cape.id; - capeObj["url"] = cape.url; - capeObj["alias"] = cape.alias; - if(cape.data.size()) { - capeObj["data"] = QString::fromLatin1(cape.data.toBase64()); - } - capesArray.push_back(capeObj); - } - out["capes"] = capesArray; - parent[tokenName] = out; -} - -MinecraftProfile profileFromJSON(const QJsonObject &parent, const char * tokenName) { - MinecraftProfile out; - auto tokenObject = parent.value(tokenName).toObject(); - if(tokenObject.isEmpty()) { - return out; - } - { - auto idV = tokenObject.value("id"); - auto nameV = tokenObject.value("name"); - if(!idV.isString() || !nameV.isString()) { - qWarning() << "mandatory profile attributes are missing or of unexpected type"; - return MinecraftProfile(); - } - out.name = nameV.toString(); - out.id = idV.toString(); - } - - { - auto skinV = tokenObject.value("skin"); - if(!skinV.isObject()) { - qWarning() << "skin is missing"; - return MinecraftProfile(); - } - auto skinObj = skinV.toObject(); - auto idV = skinObj.value("id"); - auto urlV = skinObj.value("url"); - auto variantV = skinObj.value("variant"); - if(!idV.isString() || !urlV.isString() || !variantV.isString()) { - qWarning() << "mandatory skin attributes are missing or of unexpected type"; - return MinecraftProfile(); - } - out.skin.id = idV.toString(); - out.skin.url = urlV.toString(); - out.skin.variant = variantV.toString(); - - // data for skin is optional - auto dataV = skinObj.value("data"); - if(dataV.isString()) { - // TODO: validate base64 - out.skin.data = QByteArray::fromBase64(dataV.toString().toLatin1()); - } - else if (!dataV.isUndefined()) { - qWarning() << "skin data is something unexpected"; - return MinecraftProfile(); - } - } - - auto capesV = tokenObject.value("capes"); - if(!capesV.isArray()) { - qWarning() << "capes is not an array!"; - return MinecraftProfile(); - } - auto capesArray = capesV.toArray(); - for(auto capeV: capesArray) { - if(!capeV.isObject()) { - qWarning() << "cape is not an object!"; - return MinecraftProfile(); - } - auto capeObj = capeV.toObject(); - auto idV = capeObj.value("id"); - auto urlV = capeObj.value("url"); - auto aliasV = capeObj.value("alias"); - if(!idV.isString() || !urlV.isString() || !aliasV.isString()) { - qWarning() << "mandatory skin attributes are missing or of unexpected type"; - return MinecraftProfile(); - } - Cape cape; - cape.id = idV.toString(); - cape.url = urlV.toString(); - cape.alias = aliasV.toString(); - - // data for cape is optional. - auto dataV = capeObj.value("data"); - if(dataV.isString()) { - // TODO: validate base64 - cape.data = QByteArray::fromBase64(dataV.toString().toLatin1()); - } - else if (!dataV.isUndefined()) { - qWarning() << "cape data is something unexpected"; - return MinecraftProfile(); - } - out.capes.push_back(cape); - } - out.validity = Katabasis::Validity::Assumed; - return out; -} - -} - -bool Context::resumeFromState(QByteArray data) { - QJsonParseError error; - auto doc = QJsonDocument::fromJson(data, &error); - if(error.error != QJsonParseError::NoError) { - qWarning() << "Failed to parse account data as JSON."; - return false; - } - auto docObject = doc.object(); - m_account.msaToken = tokenFromJSON(docObject, "msa"); - m_account.userToken = tokenFromJSON(docObject, "utoken"); - m_account.xboxApiToken = tokenFromJSON(docObject, "xrp-main"); - m_account.mojangservicesToken = tokenFromJSON(docObject, "xrp-mc"); - m_account.minecraftToken = tokenFromJSON(docObject, "ygg"); - - m_account.minecraftProfile = profileFromJSON(docObject, "profile"); - - m_account.validity_ = m_account.minecraftProfile.validity; - - return true; -} - -QByteArray Context::saveState() { - QJsonDocument doc; - QJsonObject output; - tokenToJSON(output, m_account.msaToken, "msa"); - tokenToJSON(output, m_account.userToken, "utoken"); - tokenToJSON(output, m_account.xboxApiToken, "xrp-main"); - tokenToJSON(output, m_account.mojangservicesToken, "xrp-mc"); - tokenToJSON(output, m_account.minecraftToken, "ygg"); - profileToJSON(output, m_account.minecraftProfile, "profile"); - doc.setObject(output); - return doc.toJson(QJsonDocument::Indented); -} diff --git a/api/logic/minecraft/auth-msa/context.h b/api/logic/minecraft/auth-msa/context.h deleted file mode 100644 index f1ac99b8..00000000 --- a/api/logic/minecraft/auth-msa/context.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include <QObject> -#include <QList> -#include <QVector> -#include <QNetworkReply> -#include <QImage> - -#include <katabasis/OAuth2.h> - -struct Skin { - QString id; - QString url; - QString variant; - - QByteArray data; -}; - -struct Cape { - QString id; - QString url; - QString alias; - - QByteArray data; -}; - -struct MinecraftProfile { - QString id; - QString name; - Skin skin; - int currentCape = -1; - QVector<Cape> capes; - Katabasis::Validity validity = Katabasis::Validity::None; -}; - -enum class AccountType { - MSA, - Mojang -}; - -struct AccountData { - AccountType type = AccountType::MSA; - - Katabasis::Token msaToken; - Katabasis::Token userToken; - Katabasis::Token xboxApiToken; - Katabasis::Token mojangservicesToken; - Katabasis::Token minecraftToken; - - MinecraftProfile minecraftProfile; - Katabasis::Validity validity_ = Katabasis::Validity::None; -}; - -class Context : public QObject -{ - Q_OBJECT - -public: - explicit Context(QObject *parent = 0); - - QByteArray saveState(); - bool resumeFromState(QByteArray data); - - bool isBusy() { - return activity_ != Katabasis::Activity::Idle; - }; - Katabasis::Validity validity() { - return m_account.validity_; - }; - - bool signIn(); - bool silentSignIn(); - bool signOut(); - - QString userName(); - QString userId(); - QString gameToken(); -signals: - void succeeded(); - void failed(); - void activityChanged(Katabasis::Activity activity); - -private slots: - void onLinkingSucceeded(); - void onLinkingFailed(); - void onOpenBrowser(const QUrl &url); - void onCloseBrowser(); - void onOAuthActivityChanged(Katabasis::Activity activity); - -private: - void doUserAuth(); - Q_SLOT void onUserAuthDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void doSTSAuthMinecraft(); - Q_SLOT void onSTSAuthMinecraftDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - void doMinecraftAuth(); - Q_SLOT void onMinecraftAuthDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void doSTSAuthGeneric(); - Q_SLOT void onSTSAuthGenericDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - void doXBoxProfile(); - Q_SLOT void onXBoxProfileDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void doMinecraftProfile(); - Q_SLOT void onMinecraftProfileDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void doGetSkin(); - Q_SLOT void onSkinDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); - - void checkResult(); - -private: - void beginActivity(Katabasis::Activity activity); - void finishActivity(); - void clearTokens(); - -private: - Katabasis::OAuth2 *oauth2 = nullptr; - - int requestsDone = 0; - bool xboxProfileSucceeded = false; - bool mcAuthSucceeded = false; - Katabasis::Activity activity_ = Katabasis::Activity::Idle; - - AccountData m_account; - - QNetworkAccessManager *mgr = nullptr; -}; diff --git a/api/logic/minecraft/auth-msa/main.cpp b/api/logic/minecraft/auth-msa/main.cpp deleted file mode 100644 index 481e0126..00000000 --- a/api/logic/minecraft/auth-msa/main.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include <QApplication> -#include <QStringList> -#include <QTimer> -#include <QDebug> -#include <QFile> -#include <QSaveFile> - -#include "context.h" -#include "mainwindow.h" - -void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - QByteArray localMsg = msg.toLocal8Bit(); - const char *file = context.file ? context.file : ""; - const char *function = context.function ? context.function : ""; - switch (type) { - case QtDebugMsg: - fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - case QtInfoMsg: - fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - case QtWarningMsg: - fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - case QtCriticalMsg: - fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - case QtFatalMsg: - fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); - break; - } -} - -class Helper : public QObject { - Q_OBJECT - -public: - Helper(Context * context) : QObject(), context_(context), msg_(QString()) { - QFile tokenCache("usercache.dat"); - if(tokenCache.open(QIODevice::ReadOnly)) { - context_->resumeFromState(tokenCache.readAll()); - } - } - -public slots: - void run() { - connect(context_, &Context::activityChanged, this, &Helper::onActivityChanged); - context_->silentSignIn(); - } - - void onFailed() { - qDebug() << "Login failed"; - } - - void onActivityChanged(Katabasis::Activity activity) { - if(activity == Katabasis::Activity::Idle) { - switch(context_->validity()) { - case Katabasis::Validity::None: { - // account is gone, remove it. - QFile::remove("usercache.dat"); - } - break; - case Katabasis::Validity::Assumed: { - // this is basically a soft-failed refresh. do nothing. - } - break; - case Katabasis::Validity::Certain: { - // stuff got refreshed / signed in. Save. - auto data = context_->saveState(); - QSaveFile tokenCache("usercache.dat"); - if(tokenCache.open(QIODevice::WriteOnly)) { - tokenCache.write(context_->saveState()); - tokenCache.commit(); - } - } - break; - } - } - } - -private: - Context *context_; - QString msg_; -}; - -int main(int argc, char *argv[]) { - qInstallMessageHandler(myMessageOutput); - QApplication a(argc, argv); - QCoreApplication::setOrganizationName("MultiMC"); - QCoreApplication::setApplicationName("MultiMC"); - Context c; - Helper helper(&c); - MainWindow window(&c); - window.show(); - QTimer::singleShot(0, &helper, &Helper::run); - return a.exec(); -} - -#include "main.moc" diff --git a/api/logic/minecraft/auth-msa/mainwindow.cpp b/api/logic/minecraft/auth-msa/mainwindow.cpp deleted file mode 100644 index d4e18dc0..00000000 --- a/api/logic/minecraft/auth-msa/mainwindow.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include <QDebug> - -#include <QDesktopServices> - -#include "BuildConfig.h" - -MainWindow::MainWindow(Context * context, QWidget *parent) : - QMainWindow(parent), - m_context(context), - m_ui(new Ui::MainWindow) -{ - m_ui->setupUi(this); - connect(m_ui->signInButton_MSA, &QPushButton::clicked, this, &MainWindow::SignInMSAClicked); - connect(m_ui->signInButton_Mojang, &QPushButton::clicked, this, &MainWindow::SignInMojangClicked); - connect(m_ui->signOutButton, &QPushButton::clicked, this, &MainWindow::SignOutClicked); - connect(m_ui->refreshButton, &QPushButton::clicked, this, &MainWindow::RefreshClicked); - - // connect(m_context, &Context::linkingSucceeded, this, &MainWindow::SignInSucceeded); - // connect(m_context, &Context::linkingFailed, this, &MainWindow::SignInFailed); - connect(m_context, &Context::activityChanged, this, &MainWindow::ActivityChanged); - ActivityChanged(Katabasis::Activity::Idle); -} - -MainWindow::~MainWindow() = default; - -void MainWindow::ActivityChanged(Katabasis::Activity activity) { - switch(activity) { - case Katabasis::Activity::Idle: { - if(m_context->validity() != Katabasis::Validity::None) { - m_ui->signInButton_Mojang->setEnabled(false); - m_ui->signInButton_MSA->setEnabled(false); - m_ui->signOutButton->setEnabled(true); - m_ui->refreshButton->setEnabled(true); - m_ui->statusBar->showMessage(QString("Hello %1!").arg(m_context->userName())); - } - else { - m_ui->signInButton_Mojang->setEnabled(true); - m_ui->signInButton_MSA->setEnabled(true); - m_ui->signOutButton->setEnabled(false); - m_ui->refreshButton->setEnabled(false); - m_ui->statusBar->showMessage("Press the login button to start."); - } - } - break; - case Katabasis::Activity::LoggingIn: { - m_ui->signInButton_Mojang->setEnabled(false); - m_ui->signInButton_MSA->setEnabled(false); - m_ui->signOutButton->setEnabled(false); - m_ui->refreshButton->setEnabled(false); - m_ui->statusBar->showMessage("Logging in..."); - } - break; - case Katabasis::Activity::LoggingOut: { - m_ui->signInButton_Mojang->setEnabled(false); - m_ui->signInButton_MSA->setEnabled(false); - m_ui->signOutButton->setEnabled(false); - m_ui->refreshButton->setEnabled(false); - m_ui->statusBar->showMessage("Logging out..."); - } - break; - case Katabasis::Activity::Refreshing: { - m_ui->signInButton_Mojang->setEnabled(false); - m_ui->signInButton_MSA->setEnabled(false); - m_ui->signOutButton->setEnabled(false); - m_ui->refreshButton->setEnabled(false); - m_ui->statusBar->showMessage("Refreshing login..."); - } - break; - } -} - -void MainWindow::SignInMSAClicked() { - qDebug() << "Sign In MSA"; - // signIn({{"prompt", "select_account"}}) - // FIXME: wrong. very wrong. this should not be operating on the current context - m_context->signIn(); -} - -void MainWindow::SignInMojangClicked() { - qDebug() << "Sign In Mojang"; - // signIn({{"prompt", "select_account"}}) - // FIXME: wrong. very wrong. this should not be operating on the current context - m_context->signIn(); -} - - -void MainWindow::SignOutClicked() { - qDebug() << "Sign Out"; - m_context->signOut(); -} - -void MainWindow::RefreshClicked() { - qDebug() << "Refresh"; - m_context->silentSignIn(); -} diff --git a/api/logic/minecraft/auth-msa/mainwindow.h b/api/logic/minecraft/auth-msa/mainwindow.h deleted file mode 100644 index abde52d8..00000000 --- a/api/logic/minecraft/auth-msa/mainwindow.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include <QMainWindow> -#include <QScopedPointer> -#include <QtNetwork> -#include <katabasis/Bits.h> - -#include "context.h" - -namespace Ui { -class MainWindow; -} - -class MainWindow : public QMainWindow { - Q_OBJECT - -public: - explicit MainWindow(Context * context, QWidget *parent = nullptr); - ~MainWindow() override; - -private slots: - void SignInMojangClicked(); - void SignInMSAClicked(); - - void SignOutClicked(); - void RefreshClicked(); - - void ActivityChanged(Katabasis::Activity activity); - -private: - Context* m_context; - QScopedPointer<Ui::MainWindow> m_ui; -}; - diff --git a/api/logic/minecraft/auth-msa/mainwindow.ui b/api/logic/minecraft/auth-msa/mainwindow.ui deleted file mode 100644 index 32b34128..00000000 --- a/api/logic/minecraft/auth-msa/mainwindow.ui +++ /dev/null @@ -1,72 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>1037</width> - <height>511</height> - </rect> - </property> - <property name="windowTitle"> - <string>SmartMapsClient</string> - </property> - <property name="dockNestingEnabled"> - <bool>true</bool> - </property> - <widget class="QWidget" name="centralWidget"> - <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="3"> - <widget class="QPushButton" name="signInButton_Mojang"> - <property name="text"> - <string>SignIn Mojang</string> - </property> - </widget> - </item> - <item row="3" column="3"> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="1" column="0" rowspan="7" colspan="3"> - <widget class="QTreeView" name="accountView"/> - </item> - <item row="5" column="3"> - <widget class="QPushButton" name="refreshButton"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> - </item> - <item row="2" column="3"> - <widget class="QPushButton" name="signInButton_MSA"> - <property name="text"> - <string>SignIn MSA</string> - </property> - </widget> - </item> - <item row="6" column="3"> - <widget class="QPushButton" name="signOutButton"> - <property name="text"> - <string>SignOut</string> - </property> - </widget> - </item> - <item row="4" column="3"> - <widget class="QPushButton" name="makeActiveButton"> - <property name="text"> - <string>Make Active</string> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QStatusBar" name="statusBar"/> - </widget> - <resources/> - <connections/> -</ui> diff --git a/api/logic/minecraft/auth/AuthSession.cpp b/api/logic/minecraft/auth/AuthSession.cpp deleted file mode 100644 index 4e858796..00000000 --- a/api/logic/minecraft/auth/AuthSession.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "AuthSession.h" -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonDocument> -#include <QStringList> - -QString AuthSession::serializeUserProperties() -{ - QJsonObject userAttrs; - for (auto key : u.properties.keys()) - { - auto array = QJsonArray::fromStringList(u.properties.values(key)); - userAttrs.insert(key, array); - } - QJsonDocument value(userAttrs); - return value.toJson(QJsonDocument::Compact); - -} - -bool AuthSession::MakeOffline(QString offline_playername) -{ - if (status != PlayableOffline && status != PlayableOnline) - { - return false; - } - session = "-"; - player_name = offline_playername; - status = PlayableOffline; - return true; -} diff --git a/api/logic/minecraft/auth/AuthSession.h b/api/logic/minecraft/auth/AuthSession.h deleted file mode 100644 index b397d9a1..00000000 --- a/api/logic/minecraft/auth/AuthSession.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include <QString> -#include <QMultiMap> -#include <memory> - -#include "multimc_logic_export.h" - -class MojangAccount; - -struct User -{ - QString id; - QMultiMap<QString, QString> properties; -}; - -struct MULTIMC_LOGIC_EXPORT AuthSession -{ - bool MakeOffline(QString offline_playername); - - QString serializeUserProperties(); - - enum Status - { - Undetermined, - RequiresPassword, - PlayableOffline, - PlayableOnline - } status = Undetermined; - - User u; - - // client token - QString client_token; - // account user name - QString username; - // combined session ID - QString session; - // volatile auth token - QString access_token; - // profile name - QString player_name; - // profile ID - QString uuid; - // 'legacy' or 'mojang', depending on account type - QString user_type; - // Did the auth server reply? - bool auth_server_online = false; - // Did the user request online mode? - bool wants_online = true; - std::shared_ptr<MojangAccount> m_accountPtr; -}; - -typedef std::shared_ptr<AuthSession> AuthSessionPtr; diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp deleted file mode 100644 index f5853fe3..00000000 --- a/api/logic/minecraft/auth/MojangAccount.cpp +++ /dev/null @@ -1,315 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Authors: Orochimarufan <orochimarufan.x3@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 "MojangAccount.h" -#include "flows/RefreshTask.h" -#include "flows/AuthenticateTask.h" - -#include <QUuid> -#include <QJsonObject> -#include <QJsonArray> -#include <QRegExp> -#include <QStringList> -#include <QJsonDocument> - -#include <QDebug> - -MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) -{ - // The JSON object must at least have a username for it to be valid. - if (!object.value("username").isString()) - { - qCritical() << "Can't load Mojang account info from JSON object. Username field is " - "missing or of the wrong type."; - return nullptr; - } - - QString username = object.value("username").toString(""); - QString clientToken = object.value("clientToken").toString(""); - QString accessToken = object.value("accessToken").toString(""); - - QJsonArray profileArray = object.value("profiles").toArray(); - if (profileArray.size() < 1) - { - qCritical() << "Can't load Mojang account with username \"" << username - << "\". No profiles found."; - return nullptr; - } - - QList<AccountProfile> profiles; - for (QJsonValue profileVal : profileArray) - { - QJsonObject profileObject = profileVal.toObject(); - QString id = profileObject.value("id").toString(""); - QString name = profileObject.value("name").toString(""); - bool legacy = profileObject.value("legacy").toBool(false); - if (id.isEmpty() || name.isEmpty()) - { - qWarning() << "Unable to load a profile because it was missing an ID or a name."; - continue; - } - profiles.append({id, name, legacy}); - } - - MojangAccountPtr account(new MojangAccount()); - if (object.value("user").isObject()) - { - User u; - QJsonObject userStructure = object.value("user").toObject(); - u.id = userStructure.value("id").toString(); - /* - QJsonObject propMap = userStructure.value("properties").toObject(); - for(auto key: propMap.keys()) - { - auto values = propMap.operator[](key).toArray(); - for(auto value: values) - u.properties.insert(key, value.toString()); - } - */ - account->m_user = u; - } - account->m_username = username; - account->m_clientToken = clientToken; - account->m_accessToken = accessToken; - account->m_profiles = profiles; - - // Get the currently selected profile. - QString currentProfile = object.value("activeProfile").toString(""); - if (!currentProfile.isEmpty()) - account->setCurrentProfile(currentProfile); - - return account; -} - -MojangAccountPtr MojangAccount::createFromUsername(const QString &username) -{ - MojangAccountPtr account(new MojangAccount()); - account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - account->m_username = username; - return account; -} - -QJsonObject MojangAccount::saveToJson() const -{ - QJsonObject json; - json.insert("username", m_username); - json.insert("clientToken", m_clientToken); - json.insert("accessToken", m_accessToken); - - QJsonArray profileArray; - for (AccountProfile profile : m_profiles) - { - QJsonObject profileObj; - profileObj.insert("id", profile.id); - profileObj.insert("name", profile.name); - profileObj.insert("legacy", profile.legacy); - profileArray.append(profileObj); - } - json.insert("profiles", profileArray); - - QJsonObject userStructure; - { - userStructure.insert("id", m_user.id); - /* - QJsonObject userAttrs; - for(auto key: m_user.properties.keys()) - { - auto array = QJsonArray::fromStringList(m_user.properties.values(key)); - userAttrs.insert(key, array); - } - userStructure.insert("properties", userAttrs); - */ - } - json.insert("user", userStructure); - - if (m_currentProfile != -1) - json.insert("activeProfile", currentProfile()->id); - - return json; -} - -bool MojangAccount::setCurrentProfile(const QString &profileId) -{ - for (int i = 0; i < m_profiles.length(); i++) - { - if (m_profiles[i].id == profileId) - { - m_currentProfile = i; - return true; - } - } - return false; -} - -const AccountProfile *MojangAccount::currentProfile() const -{ - if (m_currentProfile == -1) - return nullptr; - return &m_profiles[m_currentProfile]; -} - -AccountStatus MojangAccount::accountStatus() const -{ - if (m_accessToken.isEmpty()) - return NotVerified; - else - return Verified; -} - -std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, QString password) -{ - Q_ASSERT(m_currentTask.get() == nullptr); - - // take care of the true offline status - if (accountStatus() == NotVerified && password.isEmpty()) - { - if (session) - { - session->status = AuthSession::RequiresPassword; - fillSession(session); - } - return nullptr; - } - - if(accountStatus() == Verified && !session->wants_online) - { - session->status = AuthSession::PlayableOffline; - session->auth_server_online = false; - fillSession(session); - return nullptr; - } - else - { - if (password.isEmpty()) - { - m_currentTask.reset(new RefreshTask(this)); - } - else - { - m_currentTask.reset(new AuthenticateTask(this, password)); - } - m_currentTask->assignSession(session); - - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); - } - return m_currentTask; -} - -void MojangAccount::authSucceeded() -{ - auto session = m_currentTask->getAssignedSession(); - if (session) - { - session->status = - session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline; - fillSession(session); - session->auth_server_online = true; - } - m_currentTask.reset(); - emit changed(); -} - -void MojangAccount::authFailed(QString reason) -{ - auto session = m_currentTask->getAssignedSession(); - // This is emitted when the yggdrasil tasks time out or are cancelled. - // -> we treat the error as no-op - if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT) - { - if (session) - { - session->status = accountStatus() == Verified ? AuthSession::PlayableOffline - : AuthSession::RequiresPassword; - session->auth_server_online = false; - fillSession(session); - } - } - else - { - m_accessToken = QString(); - emit changed(); - if (session) - { - session->status = AuthSession::RequiresPassword; - session->auth_server_online = true; - fillSession(session); - } - } - m_currentTask.reset(); -} - -void MojangAccount::fillSession(AuthSessionPtr session) -{ - // the user name. you have to have an user name - session->username = m_username; - // volatile auth token - session->access_token = m_accessToken; - // the semi-permanent client token - session->client_token = m_clientToken; - if (currentProfile()) - { - // profile name - session->player_name = currentProfile()->name; - // profile ID - session->uuid = currentProfile()->id; - // 'legacy' or 'mojang', depending on account type - session->user_type = currentProfile()->legacy ? "legacy" : "mojang"; - if (!session->access_token.isEmpty()) - { - session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; - } - else - { - session->session = "-"; - } - } - else - { - session->player_name = "Player"; - session->session = "-"; - } - session->u = user(); - session->m_accountPtr = shared_from_this(); -} - -void MojangAccount::decrementUses() -{ - Usable::decrementUses(); - if(!isInUse()) - { - emit changed(); - qWarning() << "Account" << m_username << "is no longer in use."; - } -} - -void MojangAccount::incrementUses() -{ - bool wasInUse = isInUse(); - Usable::incrementUses(); - if(!wasInUse) - { - emit changed(); - qWarning() << "Account" << m_username << "is now in use."; - } -} - -void MojangAccount::invalidateClientToken() -{ - m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - emit changed(); -} diff --git a/api/logic/minecraft/auth/MojangAccount.h b/api/logic/minecraft/auth/MojangAccount.h deleted file mode 100644 index 30a5f2ff..00000000 --- a/api/logic/minecraft/auth/MojangAccount.h +++ /dev/null @@ -1,182 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QList> -#include <QJsonObject> -#include <QPair> -#include <QMap> - -#include <memory> -#include "AuthSession.h" -#include "Usable.h" - -#include "multimc_logic_export.h" - -class Task; -class YggdrasilTask; -class MojangAccount; - -typedef std::shared_ptr<MojangAccount> MojangAccountPtr; -Q_DECLARE_METATYPE(MojangAccountPtr) - -/** - * A profile within someone's Mojang account. - * - * Currently, the profile system has not been implemented by Mojang yet, - * but we might as well add some things for it in MultiMC right now so - * we don't have to rip the code to pieces to add it later. - */ -struct AccountProfile -{ - QString id; - QString name; - bool legacy; -}; - -enum AccountStatus -{ - NotVerified, - Verified -}; - -/** - * Object that stores information about a certain Mojang account. - * - * Said information may include things such as that account's username, client token, and access - * token if the user chose to stay logged in. - */ -class MULTIMC_LOGIC_EXPORT MojangAccount : - public QObject, - public Usable, - public std::enable_shared_from_this<MojangAccount> -{ - Q_OBJECT -public: /* construction */ - //! Do not copy accounts. ever. - explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; - - //! Default constructor - explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; - - //! Creates an empty account for the specified user name. - static MojangAccountPtr createFromUsername(const QString &username); - - //! Loads a MojangAccount from the given JSON object. - static MojangAccountPtr loadFromJson(const QJsonObject &json); - - //! Saves a MojangAccount to a JSON object and returns it. - QJsonObject saveToJson() const; - -public: /* manipulation */ - /** - * Sets the currently selected profile to the profile with the given ID string. - * If profileId is not in the list of available profiles, the function will simply return - * false. - */ - bool setCurrentProfile(const QString &profileId); - - /** - * Attempt to login. Empty password means we use the token. - * If the attempt fails because we already are performing some task, it returns false. - */ - std::shared_ptr<YggdrasilTask> login(AuthSessionPtr session, QString password = QString()); - void invalidateClientToken(); - -public: /* queries */ - const QString &username() const - { - return m_username; - } - - const QString &clientToken() const - { - return m_clientToken; - } - - const QString &accessToken() const - { - return m_accessToken; - } - - const QList<AccountProfile> &profiles() const - { - return m_profiles; - } - - const User &user() - { - return m_user; - } - - //! Returns the currently selected profile (if none, returns nullptr) - const AccountProfile *currentProfile() const; - - //! Returns whether the account is NotVerified, Verified or Online - AccountStatus accountStatus() const; - -signals: - /** - * This signal is emitted when the account changes - */ - void changed(); - - // TODO: better signalling for the various possible state changes - especially errors - -protected: /* variables */ - QString m_username; - - // Used to identify the client - the user can have multiple clients for the same account - // Think: different launchers, all connecting to the same account/profile - QString m_clientToken; - - // Blank if not logged in. - QString m_accessToken; - - // Index of the selected profile within the list of available - // profiles. -1 if nothing is selected. - int m_currentProfile = -1; - - // List of available profiles. - QList<AccountProfile> m_profiles; - - // the user structure, whatever it is. - User m_user; - - // current task we are executing here - std::shared_ptr<YggdrasilTask> m_currentTask; - -protected: /* methods */ - - void incrementUses() override; - void decrementUses() override; - -private -slots: - void authSucceeded(); - void authFailed(QString reason); - -private: - void fillSession(AuthSessionPtr session); - -public: - friend class YggdrasilTask; - friend class AuthenticateTask; - friend class ValidateTask; - friend class RefreshTask; -}; diff --git a/api/logic/minecraft/auth/MojangAccountList.cpp b/api/logic/minecraft/auth/MojangAccountList.cpp deleted file mode 100644 index e584cb3b..00000000 --- a/api/logic/minecraft/auth/MojangAccountList.cpp +++ /dev/null @@ -1,468 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "MojangAccountList.h" -#include "MojangAccount.h" - -#include <QIODevice> -#include <QFile> -#include <QTextStream> -#include <QJsonDocument> -#include <QJsonArray> -#include <QJsonObject> -#include <QJsonParseError> -#include <QDir> - -#include <QDebug> - -#include <FileSystem.h> - -#define ACCOUNT_LIST_FORMAT_VERSION 2 - -MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent) -{ -} - -MojangAccountPtr MojangAccountList::findAccount(const QString &username) const -{ - for (int i = 0; i < count(); i++) - { - MojangAccountPtr account = at(i); - if (account->username() == username) - return account; - } - return nullptr; -} - -const MojangAccountPtr MojangAccountList::at(int i) const -{ - return MojangAccountPtr(m_accounts.at(i)); -} - -void MojangAccountList::addAccount(const MojangAccountPtr account) -{ - int row = m_accounts.count(); - beginInsertRows(QModelIndex(), row, row); - connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); - m_accounts.append(account); - endInsertRows(); - onListChanged(); -} - -void MojangAccountList::removeAccount(const QString &username) -{ - int idx = 0; - for (auto account : m_accounts) - { - if (account->username() == username) - { - beginRemoveRows(QModelIndex(), idx, idx); - m_accounts.removeOne(account); - endRemoveRows(); - return; - } - idx++; - } - onListChanged(); -} - -void MojangAccountList::removeAccount(QModelIndex index) -{ - int row = index.row(); - if(index.isValid() && row >= 0 && row < m_accounts.size()) - { - auto & account = m_accounts[row]; - if(account == m_activeAccount) - { - m_activeAccount = nullptr; - onActiveChanged(); - } - beginRemoveRows(QModelIndex(), row, row); - m_accounts.removeAt(index.row()); - endRemoveRows(); - onListChanged(); - } -} - -MojangAccountPtr MojangAccountList::activeAccount() const -{ - return m_activeAccount; -} - -void MojangAccountList::setActiveAccount(const QString &username) -{ - if (username.isEmpty() && m_activeAccount) - { - int idx = 0; - auto prevActiveAcc = m_activeAccount; - m_activeAccount = nullptr; - for (MojangAccountPtr account : m_accounts) - { - if (account == prevActiveAcc) - { - emit dataChanged(index(idx), index(idx)); - } - idx ++; - } - onActiveChanged(); - } - else - { - auto currentActiveAccount = m_activeAccount; - int currentActiveAccountIdx = -1; - auto newActiveAccount = m_activeAccount; - int newActiveAccountIdx = -1; - int idx = 0; - for (MojangAccountPtr account : m_accounts) - { - if (account->username() == username) - { - newActiveAccount = account; - newActiveAccountIdx = idx; - } - if(currentActiveAccount == account) - { - currentActiveAccountIdx = idx; - } - idx++; - } - if(currentActiveAccount != newActiveAccount) - { - emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx)); - emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx)); - m_activeAccount = newActiveAccount; - onActiveChanged(); - } - } -} - -void MojangAccountList::accountChanged() -{ - // the list changed. there is no doubt. - onListChanged(); -} - -void MojangAccountList::onListChanged() -{ - if (m_autosave) - // TODO: Alert the user if this fails. - saveList(); - - emit listChanged(); -} - -void MojangAccountList::onActiveChanged() -{ - if (m_autosave) - saveList(); - - emit activeAccountChanged(); -} - -int MojangAccountList::count() const -{ - return m_accounts.count(); -} - -QVariant MojangAccountList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - MojangAccountPtr account = at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - switch (index.column()) - { - case NameColumn: - return account->username(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return account->username(); - - case PointerRole: - return qVariantFromValue(account); - - case Qt::CheckStateRole: - switch (index.column()) - { - case ActiveColumn: - return account == m_activeAccount ? Qt::Checked : Qt::Unchecked; - } - - default: - return QVariant(); - } -} - -QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return tr("Active?"); - - case NameColumn: - return tr("Name"); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case NameColumn: - return tr("The name of the version."); - - default: - return QVariant(); - } - - default: - return QVariant(); - } -} - -int MojangAccountList::rowCount(const QModelIndex &) const -{ - // Return count - return count(); -} - -int MojangAccountList::columnCount(const QModelIndex &) const -{ - return 2; -} - -Qt::ItemFlags MojangAccountList::flags(const QModelIndex &index) const -{ - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return Qt::NoItemFlags; - } - - return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; -} - -bool MojangAccountList::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return false; - } - - if(role == Qt::CheckStateRole) - { - if(value == Qt::Checked) - { - MojangAccountPtr account = this->at(index.row()); - this->setActiveAccount(account->username()); - } - } - - emit dataChanged(index, index); - return true; -} - -void MojangAccountList::updateListData(QList<MojangAccountPtr> versions) -{ - beginResetModel(); - m_accounts = versions; - endResetModel(); -} - -bool MojangAccountList::loadList(const QString &filePath) -{ - QString path = filePath; - if (path.isEmpty()) - path = m_listFilePath; - if (path.isEmpty()) - { - qCritical() << "Can't load Mojang account list. No file path given and no default set."; - return false; - } - - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::ReadOnly)) - { - qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); - return false; - } - - // Read the file and close it. - QByteArray jsonData = file.readAll(); - file.close(); - - QJsonParseError parseError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); - - // Fail if the JSON is invalid. - if (parseError.error != QJsonParseError::NoError) - { - qCritical() << QString("Failed to parse account list file: %1 at offset %2") - .arg(parseError.errorString(), QString::number(parseError.offset)) - .toUtf8(); - return false; - } - - // Make sure the root is an object. - if (!jsonDoc.isObject()) - { - qCritical() << "Invalid account list JSON: Root should be an array."; - return false; - } - - QJsonObject root = jsonDoc.object(); - - // Make sure the format version matches. - if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION) - { - QString newName = "accounts-old.json"; - qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to" - << newName; - - // Attempt to rename the old version. - file.rename(newName); - return false; - } - - // Now, load the accounts array. - beginResetModel(); - QJsonArray accounts = root.value("accounts").toArray(); - for (QJsonValue accountVal : accounts) - { - QJsonObject accountObj = accountVal.toObject(); - MojangAccountPtr account = MojangAccount::loadFromJson(accountObj); - if (account.get() != nullptr) - { - connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); - m_accounts.append(account); - } - else - { - qWarning() << "Failed to load an account."; - } - } - // Load the active account. - m_activeAccount = findAccount(root.value("activeAccount").toString("")); - endResetModel(); - return true; -} - -bool MojangAccountList::saveList(const QString &filePath) -{ - QString path(filePath); - if (path.isEmpty()) - path = m_listFilePath; - if (path.isEmpty()) - { - qCritical() << "Can't save Mojang account list. No file path given and no default set."; - return false; - } - - // make sure the parent folder exists - if(!FS::ensureFilePathExists(path)) - return false; - - // make sure the file wasn't overwritten with a folder before (fixes a bug) - QFileInfo finfo(path); - if(finfo.isDir()) - { - QDir badDir(path); - badDir.removeRecursively(); - } - - qDebug() << "Writing account list to" << path; - - qDebug() << "Building JSON data structure."; - // Build the JSON document to write to the list file. - QJsonObject root; - - root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION); - - // Build a list of accounts. - qDebug() << "Building account array."; - QJsonArray accounts; - for (MojangAccountPtr account : m_accounts) - { - QJsonObject accountObj = account->saveToJson(); - accounts.append(accountObj); - } - - // Insert the account list into the root object. - root.insert("accounts", accounts); - - if(m_activeAccount) - { - // Save the active account. - root.insert("activeAccount", m_activeAccount->username()); - } - - // Create a JSON document object to convert our JSON to bytes. - QJsonDocument doc(root); - - // Now that we're done building the JSON object, we can write it to the file. - qDebug() << "Writing account list to file."; - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::WriteOnly)) - { - qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); - return false; - } - - // Write the JSON to the file. - file.write(doc.toJson()); - file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser); - file.close(); - - qDebug() << "Saved account list to" << path; - - return true; -} - -void MojangAccountList::setListFilePath(QString path, bool autosave) -{ - m_listFilePath = path; - m_autosave = autosave; -} - -bool MojangAccountList::anyAccountIsValid() -{ - for(auto account:m_accounts) - { - if(account->accountStatus() != NotVerified) - return true; - } - return false; -} diff --git a/api/logic/minecraft/auth/MojangAccountList.h b/api/logic/minecraft/auth/MojangAccountList.h deleted file mode 100644 index cc3a61a2..00000000 --- a/api/logic/minecraft/auth/MojangAccountList.h +++ /dev/null @@ -1,201 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "MojangAccount.h" - -#include <QObject> -#include <QVariant> -#include <QAbstractListModel> -#include <QSharedPointer> - -#include "multimc_logic_export.h" - -/*! - * \brief List of available Mojang accounts. - * This should be loaded in the background by MultiMC on startup. - * - * This class also inherits from QAbstractListModel. Methods from that - * class determine how this list shows up in a list view. Said methods - * all have a default implementation, but they can be overridden by subclasses to - * change the behavior of the list. - */ -class MULTIMC_LOGIC_EXPORT MojangAccountList : public QAbstractListModel -{ - Q_OBJECT -public: - enum ModelRoles - { - PointerRole = 0x34B1CB48 - }; - - enum VListColumns - { - // TODO: Add icon column. - - // First column - Active? - ActiveColumn = 0, - - // Second column - Name - NameColumn, - }; - - explicit MojangAccountList(QObject *parent = 0); - - //! Gets the account at the given index. - virtual const MojangAccountPtr at(int i) const; - - //! Returns the number of accounts in the list. - virtual int count() const; - - //////// List Model Functions //////// - virtual QVariant data(const QModelIndex &index, int role) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual int rowCount(const QModelIndex &parent) const; - virtual int columnCount(const QModelIndex &parent) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role); - - /*! - * Adds a the given Mojang account to the account list. - */ - virtual void addAccount(const MojangAccountPtr account); - - /*! - * Removes the mojang account with the given username from the account list. - */ - virtual void removeAccount(const QString &username); - - /*! - * Removes the account at the given QModelIndex. - */ - virtual void removeAccount(QModelIndex index); - - /*! - * \brief Finds an account by its username. - * \param The username of the account to find. - * \return A const pointer to the account with the given username. NULL if - * one doesn't exist. - */ - virtual MojangAccountPtr findAccount(const QString &username) const; - - /*! - * Sets the default path to save the list file to. - * If autosave is true, this list will automatically save to the given path whenever it changes. - * THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately - * after calling this function to ensure an autosaved change doesn't overwrite the list you intended - * to load. - */ - virtual void setListFilePath(QString path, bool autosave = false); - - /*! - * \brief Loads the account list from the given file path. - * If the given file is an empty string (default), will load from the default account list file. - * \return True if successful, otherwise false. - */ - virtual bool loadList(const QString &file = ""); - - /*! - * \brief Saves the account list to the given file. - * If the given file is an empty string (default), will save from the default account list file. - * \return True if successful, otherwise false. - */ - virtual bool saveList(const QString &file = ""); - - /*! - * \brief Gets a pointer to the account that the user has selected as their "active" account. - * Which account is active can be overridden on a per-instance basis, but this will return the one that - * is set as active globally. - * \return The currently active MojangAccount. If there isn't an active account, returns a null pointer. - */ - virtual MojangAccountPtr activeAccount() const; - - /*! - * Sets the given account as the current active account. - * If the username given is an empty string, sets the active account to nothing. - */ - virtual void setActiveAccount(const QString &username); - - /*! - * Returns true if any of the account is at least Validated - */ - bool anyAccountIsValid(); - -signals: - /*! - * Signal emitted to indicate that the account list has changed. - * This will also fire if the value of an element in the list changes (will be implemented - * later). - */ - void listChanged(); - - /*! - * Signal emitted to indicate that the active account has changed. - */ - void activeAccountChanged(); - -public -slots: - /** - * This is called when one of the accounts changes and the list needs to be updated - */ - void accountChanged(); - -protected: - /*! - * Called whenever the list changes. - * This emits the listChanged() signal and autosaves the list (if autosave is enabled). - */ - void onListChanged(); - - /*! - * Called whenever the active account changes. - * Emits the activeAccountChanged() signal and autosaves the list if enabled. - */ - void onActiveChanged(); - - QList<MojangAccountPtr> m_accounts; - - /*! - * Account that is currently active. - */ - MojangAccountPtr m_activeAccount; - - //! Path to the account list file. Empty string if there isn't one. - QString m_listFilePath; - - /*! - * If true, the account list will automatically save to the account list path when it changes. - * Ignored if m_listFilePath is blank. - */ - bool m_autosave = false; - -protected -slots: - /*! - * Updates this list with the given list of accounts. - * This is done by copying each account in the given list and inserting it - * into this one. - * We need to do this so that we can set the parents of the accounts are set to this - * account list. This can't be done in the load task, because the accounts the load - * task creates are on the load task's thread and Qt won't allow their parents - * to be set to something created on another thread. - * To get around that problem, we invoke this method on the GUI thread, which - * then copies the accounts and sets their parents correctly. - * \param accounts List of accounts whose parents should be set. - */ - virtual void updateListData(QList<MojangAccountPtr> versions); -}; diff --git a/api/logic/minecraft/auth/YggdrasilTask.cpp b/api/logic/minecraft/auth/YggdrasilTask.cpp deleted file mode 100644 index 0857b46b..00000000 --- a/api/logic/minecraft/auth/YggdrasilTask.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "YggdrasilTask.h" -#include "MojangAccount.h" - -#include <QObject> -#include <QString> -#include <QJsonObject> -#include <QJsonDocument> -#include <QNetworkReply> -#include <QByteArray> - -#include <Env.h> - -#include <BuildConfig.h> - -#include <QDebug> - -YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent) - : Task(parent), m_account(account) -{ - changeState(STATE_CREATED); -} - -void YggdrasilTask::executeTask() -{ - changeState(STATE_SENDING_REQUEST); - - // Get the content of the request we're going to send to the server. - QJsonDocument doc(getRequestContent()); - - QUrl reqUrl(BuildConfig.AUTH_BASE + getEndpoint()); - QNetworkRequest netRequest(reqUrl); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - QByteArray requestData = doc.toJson(); - m_netReply = ENV.qnam().post(netRequest, requestData); - connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply); - connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers); - connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers); - connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors); - timeout_keeper.setSingleShot(true); - timeout_keeper.start(timeout_max); - counter.setSingleShot(false); - counter.start(time_step); - progress(0, timeout_max); - connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout); - connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat); -} - -void YggdrasilTask::refreshTimers(qint64, qint64) -{ - timeout_keeper.stop(); - timeout_keeper.start(timeout_max); - progress(count = 0, timeout_max); -} -void YggdrasilTask::heartbeat() -{ - count += time_step; - progress(count, timeout_max); -} - -bool YggdrasilTask::abort() -{ - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = YggdrasilTask::BY_USER; - m_netReply->abort(); - return true; -} - -void YggdrasilTask::abortByTimeout() -{ - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = YggdrasilTask::BY_TIMEOUT; - m_netReply->abort(); -} - -void YggdrasilTask::sslErrors(QList<QSslError> errors) -{ - int i = 1; - for (auto error : errors) - { - qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -void YggdrasilTask::processReply() -{ - changeState(STATE_PROCESSING_RESPONSE); - - switch (m_netReply->error()) - { - case QNetworkReply::NoError: - break; - case QNetworkReply::TimeoutError: - changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out.")); - return; - case QNetworkReply::OperationCanceledError: - changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled.")); - return; - case QNetworkReply::SslHandshakeFailedError: - changeState( - STATE_FAILED_SOFT, - tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>" - "<ul>" - "<li>You use Windows XP and need to <a " - "href=\"https://www.microsoft.com/en-us/download/details.aspx?id=38918\">update " - "your root certificates</a></li>" - "<li>Some device on your network is interfering with SSL traffic. In that case, " - "you have bigger worries than Minecraft not starting.</li>" - "<li>Possibly something else. Check the MultiMC log file for details</li>" - "</ul>")); - return; - // used for invalid credentials and similar errors. Fall through. - case QNetworkReply::ContentAccessDenied: - case QNetworkReply::ContentOperationNotPermittedError: - break; - default: - changeState(STATE_FAILED_SOFT, - tr("Authentication operation failed due to a network error: %1 (%2)") - .arg(m_netReply->errorString()).arg(m_netReply->error())); - return; - } - - // Try to parse the response regardless of the response code. - // Sometimes the auth server will give more information and an error code. - QJsonParseError jsonError; - QByteArray replyData = m_netReply->readAll(); - QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); - // Check the response code. - int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) - { - // If the response code was 200, then there shouldn't be an error. Make sure - // anyways. - // Also, sometimes an empty reply indicates success. If there was no data received, - // pass an empty json object to the processResponse function. - if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) - { - processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()); - return; - } - else - { - changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response " - "JSON response: %1 at offset %2.") - .arg(jsonError.errorString()) - .arg(jsonError.offset)); - qCritical() << replyData; - } - return; - } - - // If the response code was not 200, then Yggdrasil may have given us information - // about the error. - // If we can parse the response, then get information from it. Otherwise just say - // there was an unknown error. - if (jsonError.error == QJsonParseError::NoError) - { - // We were able to parse the server's response. Woo! - // Call processError. If a subclass has overridden it then they'll handle their - // stuff there. - qDebug() << "The request failed, but the server gave us an error message. " - "Processing error."; - processError(doc.object()); - } - else - { - // The server didn't say anything regarding the error. Give the user an unknown - // error. - qDebug() - << "The request failed and the server gave no error message. Unknown error."; - changeState(STATE_FAILED_SOFT, - tr("An unknown error occurred when trying to communicate with the " - "authentication server: %1").arg(m_netReply->errorString())); - } -} - -void YggdrasilTask::processError(QJsonObject responseData) -{ - QJsonValue errorVal = responseData.value("error"); - QJsonValue errorMessageValue = responseData.value("errorMessage"); - QJsonValue causeVal = responseData.value("cause"); - - if (errorVal.isString() && errorMessageValue.isString()) - { - m_error = std::shared_ptr<Error>(new Error{ - errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); - changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose); - } - else - { - // Error is not in standard format. Don't set m_error and return unknown error. - changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred.")); - } -} - -QString YggdrasilTask::getStateMessage() const -{ - switch (m_state) - { - case STATE_CREATED: - return "Waiting..."; - case STATE_SENDING_REQUEST: - return tr("Sending request to auth servers..."); - case STATE_PROCESSING_RESPONSE: - return tr("Processing response from servers..."); - case STATE_SUCCEEDED: - return tr("Authentication task succeeded."); - case STATE_FAILED_SOFT: - return tr("Failed to contact the authentication server."); - case STATE_FAILED_HARD: - return tr("Failed to authenticate."); - default: - return tr("..."); - } -} - -void YggdrasilTask::changeState(YggdrasilTask::State newState, QString reason) -{ - m_state = newState; - setStatus(getStateMessage()); - if (newState == STATE_SUCCEEDED) - { - emitSucceeded(); - } - else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT) - { - emitFailed(reason); - } -} - -YggdrasilTask::State YggdrasilTask::state() -{ - return m_state; -} diff --git a/api/logic/minecraft/auth/YggdrasilTask.h b/api/logic/minecraft/auth/YggdrasilTask.h deleted file mode 100644 index 8af2e132..00000000 --- a/api/logic/minecraft/auth/YggdrasilTask.h +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <tasks/Task.h> - -#include <QString> -#include <QJsonObject> -#include <QTimer> -#include <qsslerror.h> - -#include "MojangAccount.h" - -class QNetworkReply; - -/** - * A Yggdrasil task is a task that performs an operation on a given mojang account. - */ -class YggdrasilTask : public Task -{ - Q_OBJECT -public: - explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); - virtual ~YggdrasilTask() {}; - - /** - * assign a session to this task. the session will be filled with required infomration - * upon completion - */ - void assignSession(AuthSessionPtr session) - { - m_session = session; - } - - /// get the assigned session for filling with information. - AuthSessionPtr getAssignedSession() - { - return m_session; - } - - /** - * Class describing a Yggdrasil error response. - */ - struct Error - { - QString m_errorMessageShort; - QString m_errorMessageVerbose; - QString m_cause; - }; - - enum AbortedBy - { - BY_NOTHING, - BY_USER, - BY_TIMEOUT - } m_aborted = BY_NOTHING; - - /** - * Enum for describing the state of the current task. - * Used by the getStateMessage function to determine what the status message should be. - */ - enum State - { - STATE_CREATED, - STATE_SENDING_REQUEST, - STATE_PROCESSING_RESPONSE, - STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated - STATE_FAILED_HARD, //!< hard failure. auth is invalid - STATE_SUCCEEDED - } m_state = STATE_CREATED; - -protected: - - virtual void executeTask() override; - - /** - * Gets the JSON object that will be sent to the authentication server. - * Should be overridden by subclasses. - */ - virtual QJsonObject getRequestContent() const = 0; - - /** - * Gets the endpoint to POST to. - * No leading slash. - */ - virtual QString getEndpoint() const = 0; - - /** - * Processes the response received from the server. - * If an error occurred, this should emit a failed signal and return false. - * If Yggdrasil gave an error response, it should call setError() first, and then return false. - * Otherwise, it should return true. - * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with - * an empty QJsonObject. - */ - virtual void processResponse(QJsonObject responseData) = 0; - - /** - * Processes an error response received from the server. - * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error. - * \returns a QString error message that will be passed to emitFailed. - */ - virtual void processError(QJsonObject responseData); - - /** - * Returns the state message for the given state. - * Used to set the status message for the task. - * Should be overridden by subclasses that want to change messages for a given state. - */ - virtual QString getStateMessage() const; - -protected -slots: - void processReply(); - void refreshTimers(qint64, qint64); - void heartbeat(); - void sslErrors(QList<QSslError>); - - void changeState(State newState, QString reason=QString()); -public -slots: - virtual bool abort() override; - void abortByTimeout(); - State state(); -protected: - // FIXME: segfault disaster waiting to happen - MojangAccount *m_account = nullptr; - QNetworkReply *m_netReply = nullptr; - std::shared_ptr<Error> m_error; - QTimer timeout_keeper; - QTimer counter; - int count = 0; // num msec since time reset - - const int timeout_max = 30000; - const int time_step = 50; - - AuthSessionPtr m_session; -}; diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp deleted file mode 100644 index 2e8dc859..00000000 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp +++ /dev/null @@ -1,202 +0,0 @@ - -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "AuthenticateTask.h" -#include "../MojangAccount.h" - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QVariant> - -#include <QDebug> -#include <QUuid> - -AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password, - QObject *parent) - : YggdrasilTask(account, parent), m_password(password) -{ -} - -QJsonObject AuthenticateTask::getRequestContent() const -{ - /* - * { - * "agent": { // optional - * "name": "Minecraft", // So far this is the only encountered value - * "version": 1 // This number might be increased - * // by the vanilla client in the future - * }, - * "username": "mojang account name", // Can be an email address or player name for - // unmigrated accounts - * "password": "mojang account password", - * "clientToken": "client identifier" // optional - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - - { - QJsonObject agent; - // C++ makes string literals void* for some stupid reason, so we have to tell it - // QString... Thanks Obama. - agent.insert("name", QString("Minecraft")); - agent.insert("version", 1); - req.insert("agent", agent); - } - - req.insert("username", m_account->username()); - req.insert("password", m_password); - req.insert("requestUser", true); - - // If we already have a client token, give it to the server. - // Otherwise, let the server give us one. - - if(m_account->m_clientToken.isEmpty()) - { - auto uuid = QUuid::createUuid(); - auto uuidString = uuid.toString().remove('{').remove('-').remove('}'); - m_account->m_clientToken = uuidString; - } - req.insert("clientToken", m_account->m_clientToken); - - return req; -} - -void AuthenticateTask::processResponse(QJsonObject responseData) -{ - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - qDebug() << "Getting client token."; - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) - { - // Fail if the server gave us an empty client token - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) - { - changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } - // Set the client token. - m_account->m_clientToken = clientToken; - - // Now, we set the access token. - qDebug() << "Getting access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) - { - // Fail if the server didn't give us an access token. - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } - // Set the access token. - m_account->m_accessToken = accessToken; - - // Now we load the list of available profiles. - // Mojang hasn't yet implemented the profile system, - // but we might as well support what's there so we - // don't have trouble implementing it later. - qDebug() << "Loading profile list."; - QJsonArray availableProfiles = responseData.value("availableProfiles").toArray(); - QList<AccountProfile> loadedProfiles; - for (auto iter : availableProfiles) - { - QJsonObject profile = iter.toObject(); - // Profiles are easy, we just need their ID and name. - QString id = profile.value("id").toString(""); - QString name = profile.value("name").toString(""); - bool legacy = profile.value("legacy").toBool(false); - - if (id.isEmpty() || name.isEmpty()) - { - // This should never happen, but we might as well - // warn about it if it does so we can debug it easily. - // You never know when Mojang might do something truly derpy. - qWarning() << "Found entry in available profiles list with missing ID or name " - "field. Ignoring it."; - } - - // Now, add a new AccountProfile entry to the list. - loadedProfiles.append({id, name, legacy}); - } - // Put the list of profiles we loaded into the MojangAccount object. - m_account->m_profiles = loadedProfiles; - - // Finally, we set the current profile to the correct value. This is pretty simple. - // We do need to make sure that the current profile that the server gave us - // is actually in the available profiles list. - // If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know). - qDebug() << "Setting current profile."; - QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); - QString currentProfileId = currentProfile.value("id").toString(""); - if (currentProfileId.isEmpty()) - { - changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium.")); - return; - } - if (!m_account->setCurrentProfile(currentProfileId)) - { - changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list.")); - return; - } - - // this is what the vanilla launcher passes to the userProperties launch param - if (responseData.contains("user")) - { - User u; - auto obj = responseData.value("user").toObject(); - u.id = obj.value("id").toString(); - auto propArray = obj.value("properties").toArray(); - for (auto prop : propArray) - { - auto propTuple = prop.toObject(); - auto name = propTuple.value("name").toString(); - auto value = propTuple.value("value").toString(); - u.properties.insert(name, value); - } - m_account->m_user = u; - } - - // We've made it through the minefield of possible errors. Return true to indicate that - // we've succeeded. - qDebug() << "Finished reading authentication response."; - changeState(STATE_SUCCEEDED); -} - -QString AuthenticateTask::getEndpoint() const -{ - return "authenticate"; -} - -QString AuthenticateTask::getStateMessage() const -{ - switch (m_state) - { - case STATE_SENDING_REQUEST: - return tr("Authenticating: Sending request..."); - case STATE_PROCESSING_RESPONSE: - return tr("Authenticating: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } -} diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.h b/api/logic/minecraft/auth/flows/AuthenticateTask.h deleted file mode 100644 index 4c14eec7..00000000 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.h +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "../YggdrasilTask.h" - -#include <QObject> -#include <QString> -#include <QJsonObject> - -/** - * The authenticate task takes a MojangAccount with no access token and password and attempts to - * authenticate with Mojang's servers. - * If successful, it will set the MojangAccount's access token. - */ -class AuthenticateTask : public YggdrasilTask -{ - Q_OBJECT -public: - AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0); - -protected: - virtual QJsonObject getRequestContent() const override; - - virtual QString getEndpoint() const override; - - virtual void processResponse(QJsonObject responseData) override; - - virtual QString getStateMessage() const override; - -private: - QString m_password; -}; diff --git a/api/logic/minecraft/auth/flows/RefreshTask.cpp b/api/logic/minecraft/auth/flows/RefreshTask.cpp deleted file mode 100644 index ecba178d..00000000 --- a/api/logic/minecraft/auth/flows/RefreshTask.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "RefreshTask.h" -#include "../MojangAccount.h" - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QVariant> - -#include <QDebug> - -RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account) -{ -} - -QJsonObject RefreshTask::getRequestContent() const -{ - /* - * { - * "clientToken": "client identifier" - * "accessToken": "current access token to be refreshed" - * "selectedProfile": // specifying this causes errors - * { - * "id": "profile ID" - * "name": "profile name" - * } - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - req.insert("clientToken", m_account->m_clientToken); - req.insert("accessToken", m_account->m_accessToken); - /* - { - auto currentProfile = m_account->currentProfile(); - QJsonObject profile; - profile.insert("id", currentProfile->id()); - profile.insert("name", currentProfile->name()); - req.insert("selectedProfile", profile); - } - */ - req.insert("requestUser", true); - - return req; -} - -void RefreshTask::processResponse(QJsonObject responseData) -{ - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; - - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) - { - // Fail if the server gave us an empty client token - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) - { - changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } - - // Now, we set the access token. - qDebug() << "Getting new access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) - { - // Fail if the server didn't give us an access token. - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } - - // we validate that the server responded right. (our current profile = returned current - // profile) - QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); - QString currentProfileId = currentProfile.value("id").toString(""); - if (m_account->currentProfile()->id != currentProfileId) - { - changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected.")); - return; - } - - // this is what the vanilla launcher passes to the userProperties launch param - if (responseData.contains("user")) - { - User u; - auto obj = responseData.value("user").toObject(); - u.id = obj.value("id").toString(); - auto propArray = obj.value("properties").toArray(); - for (auto prop : propArray) - { - auto propTuple = prop.toObject(); - auto name = propTuple.value("name").toString(); - auto value = propTuple.value("value").toString(); - u.properties.insert(name, value); - } - m_account->m_user = u; - } - - // We've made it through the minefield of possible errors. Return true to indicate that - // we've succeeded. - qDebug() << "Finished reading refresh response."; - // Reset the access token. - m_account->m_accessToken = accessToken; - changeState(STATE_SUCCEEDED); -} - -QString RefreshTask::getEndpoint() const -{ - return "refresh"; -} - -QString RefreshTask::getStateMessage() const -{ - switch (m_state) - { - case STATE_SENDING_REQUEST: - return tr("Refreshing login token..."); - case STATE_PROCESSING_RESPONSE: - return tr("Refreshing login token: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } -} diff --git a/api/logic/minecraft/auth/flows/RefreshTask.h b/api/logic/minecraft/auth/flows/RefreshTask.h deleted file mode 100644 index f0840dda..00000000 --- a/api/logic/minecraft/auth/flows/RefreshTask.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "../YggdrasilTask.h" - -#include <QObject> -#include <QString> -#include <QJsonObject> - -/** - * The authenticate task takes a MojangAccount with a possibly timed-out access token - * and attempts to authenticate with Mojang's servers. - * If successful, it will set the new access token. The token is considered validated. - */ -class RefreshTask : public YggdrasilTask -{ - Q_OBJECT -public: - RefreshTask(MojangAccount * account); - -protected: - virtual QJsonObject getRequestContent() const override; - - virtual QString getEndpoint() const override; - - virtual void processResponse(QJsonObject responseData) override; - - virtual QString getStateMessage() const override; -}; - diff --git a/api/logic/minecraft/auth/flows/ValidateTask.cpp b/api/logic/minecraft/auth/flows/ValidateTask.cpp deleted file mode 100644 index 6b3f0a65..00000000 --- a/api/logic/minecraft/auth/flows/ValidateTask.cpp +++ /dev/null @@ -1,61 +0,0 @@ - -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "ValidateTask.h" -#include "../MojangAccount.h" - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QVariant> - -#include <QDebug> - -ValidateTask::ValidateTask(MojangAccount * account, QObject *parent) - : YggdrasilTask(account, parent) -{ -} - -QJsonObject ValidateTask::getRequestContent() const -{ - QJsonObject req; - req.insert("accessToken", m_account->m_accessToken); - return req; -} - -void ValidateTask::processResponse(QJsonObject responseData) -{ - // Assume that if processError wasn't called, then the request was successful. - changeState(YggdrasilTask::STATE_SUCCEEDED); -} - -QString ValidateTask::getEndpoint() const -{ - return "validate"; -} - -QString ValidateTask::getStateMessage() const -{ - switch (m_state) - { - case YggdrasilTask::STATE_SENDING_REQUEST: - return tr("Validating access token: Sending request..."); - case YggdrasilTask::STATE_PROCESSING_RESPONSE: - return tr("Validating access token: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } -} diff --git a/api/logic/minecraft/auth/flows/ValidateTask.h b/api/logic/minecraft/auth/flows/ValidateTask.h deleted file mode 100644 index 986c2e9f..00000000 --- a/api/logic/minecraft/auth/flows/ValidateTask.h +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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. - */ - -/* - * :FIXME: DEAD CODE, DEAD CODE, DEAD CODE! :FIXME: - */ - -#pragma once - -#include "../YggdrasilTask.h" - -#include <QObject> -#include <QString> -#include <QJsonObject> - -/** - * The validate task takes a MojangAccount and checks to make sure its access token is valid. - */ -class ValidateTask : public YggdrasilTask -{ - Q_OBJECT -public: - ValidateTask(MojangAccount *account, QObject *parent = 0); - -protected: - virtual QJsonObject getRequestContent() const override; - - virtual QString getEndpoint() const override; - - virtual void processResponse(QJsonObject responseData) override; - - virtual QString getStateMessage() const override; - -private: -}; diff --git a/api/logic/minecraft/gameoptions/GameOptions.cpp b/api/logic/minecraft/gameoptions/GameOptions.cpp deleted file mode 100644 index e547b32a..00000000 --- a/api/logic/minecraft/gameoptions/GameOptions.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "GameOptions.h" -#include "FileSystem.h" -#include <QDebug> -#include <QSaveFile> - -namespace { -bool load(const QString& path, std::vector<GameOptionItem> &contents, int & version) -{ - contents.clear(); - QFile file(path); - if (!file.open(QFile::ReadOnly)) - { - qWarning() << "Failed to read options file."; - return false; - } - version = 0; - while(!file.atEnd()) - { - auto line = file.readLine(); - if(line.endsWith('\n')) - { - line.chop(1); - } - auto separatorIndex = line.indexOf(':'); - if(separatorIndex == -1) - { - continue; - } - auto key = QString::fromUtf8(line.data(), separatorIndex); - auto value = QString::fromUtf8(line.data() + separatorIndex + 1, line.size() - 1 - separatorIndex); - qDebug() << "!!" << key << "!!"; - if(key == "version") - { - version = value.toInt(); - continue; - } - contents.emplace_back(GameOptionItem{key, value}); - } - qDebug() << "Loaded" << path << "with version:" << version; - return true; -} -bool save(const QString& path, std::vector<GameOptionItem> &mapping, int version) -{ - QSaveFile out(path); - if(!out.open(QIODevice::WriteOnly)) - { - return false; - } - if(version != 0) - { - QString versionLine = QString("version:%1\n").arg(version); - out.write(versionLine.toUtf8()); - } - auto iter = mapping.begin(); - while (iter != mapping.end()) - { - out.write(iter->key.toUtf8()); - out.write(":"); - out.write(iter->value.toUtf8()); - out.write("\n"); - iter++; - } - return out.commit(); -} -} - -GameOptions::GameOptions(const QString& path): - path(path) -{ - reload(); -} - -QVariant GameOptions::headerData(int section, Qt::Orientation orientation, int role) const -{ - if(role != Qt::DisplayRole) - { - return QAbstractListModel::headerData(section, orientation, role); - } - switch(section) - { - case 0: - return tr("Key"); - case 1: - return tr("Value"); - default: - return QVariant(); - } -} - -QVariant GameOptions::data(const QModelIndex& index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= int(contents.size())) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - if(column == 0) - { - return contents[row].key; - } - else - { - return contents[row].value; - } - default: - return QVariant(); - } - return QVariant(); -} - -int GameOptions::rowCount(const QModelIndex&) const -{ - return contents.size(); -} - -int GameOptions::columnCount(const QModelIndex&) const -{ - return 2; -} - -bool GameOptions::isLoaded() const -{ - return loaded; -} - -bool GameOptions::reload() -{ - beginResetModel(); - loaded = load(path, contents, version); - endResetModel(); - return loaded; -} - -bool GameOptions::save() -{ - return ::save(path, contents, version); -} diff --git a/api/logic/minecraft/gameoptions/GameOptions.h b/api/logic/minecraft/gameoptions/GameOptions.h deleted file mode 100644 index c6d25492..00000000 --- a/api/logic/minecraft/gameoptions/GameOptions.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include <map> -#include <QString> -#include <QAbstractListModel> - -struct GameOptionItem -{ - QString key; - QString value; -}; - -class GameOptions : public QAbstractListModel -{ - Q_OBJECT -public: - explicit GameOptions(const QString& path); - virtual ~GameOptions() = default; - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & parent) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - - bool isLoaded() const; - bool reload(); - bool save(); - -private: - std::vector<GameOptionItem> contents; - bool loaded = false; - QString path; - int version = 0; -}; diff --git a/api/logic/minecraft/launch/ClaimAccount.cpp b/api/logic/minecraft/launch/ClaimAccount.cpp deleted file mode 100644 index a1180f0a..00000000 --- a/api/logic/minecraft/launch/ClaimAccount.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "ClaimAccount.h" -#include <launch/LaunchTask.h> - -ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session): LaunchStep(parent) -{ - if(session->status == AuthSession::Status::PlayableOnline) - { - m_account = session->m_accountPtr; - } -} - -void ClaimAccount::executeTask() -{ - if(m_account) - { - lock.reset(new UseLock(m_account)); - emitSucceeded(); - } -} - -void ClaimAccount::finalize() -{ - lock.reset(); -} diff --git a/api/logic/minecraft/launch/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h deleted file mode 100644 index c5bd75f3..00000000 --- a/api/logic/minecraft/launch/ClaimAccount.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <minecraft/auth/MojangAccount.h> - -class ClaimAccount: public LaunchStep -{ - Q_OBJECT -public: - explicit ClaimAccount(LaunchTask *parent, AuthSessionPtr session); - virtual ~ClaimAccount() {}; - - void executeTask() override; - void finalize() override; - bool canAbort() const override - { - return false; - } -private: - std::unique_ptr<UseLock> lock; - MojangAccountPtr m_account; -}; diff --git a/api/logic/minecraft/launch/CreateGameFolders.cpp b/api/logic/minecraft/launch/CreateGameFolders.cpp deleted file mode 100644 index 4081e72e..00000000 --- a/api/logic/minecraft/launch/CreateGameFolders.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "CreateGameFolders.h" -#include "minecraft/MinecraftInstance.h" -#include "launch/LaunchTask.h" -#include "FileSystem.h" - -CreateGameFolders::CreateGameFolders(LaunchTask* parent): LaunchStep(parent) -{ -} - -void CreateGameFolders::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - - if(!FS::ensureFolderPathExists(minecraftInstance->gameRoot())) - { - emit logLine("Couldn't create the main game folder", MessageLevel::Error); - emitFailed(tr("Couldn't create the main game folder")); - return; - } - - // HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created. - if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) - { - emit logLine("Couldn't create the 'server-resource-packs' folder", MessageLevel::Error); - } - emitSucceeded(); -} diff --git a/api/logic/minecraft/launch/CreateGameFolders.h b/api/logic/minecraft/launch/CreateGameFolders.h deleted file mode 100644 index 9c7d3c94..00000000 --- a/api/logic/minecraft/launch/CreateGameFolders.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <LoggedProcess.h> -#include <minecraft/auth/AuthSession.h> - -// Create the main .minecraft for the instance and any other necessary folders -class CreateGameFolders: public LaunchStep -{ - Q_OBJECT -public: - explicit CreateGameFolders(LaunchTask *parent); - virtual ~CreateGameFolders() {}; - - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } -}; - - diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.cpp b/api/logic/minecraft/launch/DirectJavaLaunch.cpp deleted file mode 100644 index 2110384f..00000000 --- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "DirectJavaLaunch.h" -#include <launch/LaunchTask.h> -#include <minecraft/MinecraftInstance.h> -#include <FileSystem.h> -#include <Commandline.h> -#include <QStandardPaths> - -DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent) -{ - connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state); -} - -void DirectJavaLaunch::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - QStringList args = minecraftInstance->javaArguments(); - - args.append("-Djava.library.path=" + minecraftInstance->getNativePath()); - - auto classPathEntries = minecraftInstance->getClassPath(); - args.append("-cp"); - QString classpath; -#ifdef Q_OS_WIN32 - classpath = classPathEntries.join(';'); -#else - classpath = classPathEntries.join(':'); -#endif - args.append(classpath); - args.append(minecraftInstance->getMainClass()); - - QString allArgs = args.join(", "); - emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); - - auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - - m_process.setProcessEnvironment(instance->createEnvironment()); - - // make detachable - this will keep the process running even if the object is destroyed - m_process.setDetachable(true); - - auto mcArgs = minecraftInstance->processMinecraftArgs(m_session, m_serverToJoin); - args.append(mcArgs); - - QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); - if(!wrapperCommandStr.isEmpty()) - { - auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); - auto wrapperCommand = wrapperArgs.takeFirst(); - auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); - if (realWrapperCommand.isEmpty()) - { - const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found."); - emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal); - emitFailed(tr(reason).arg(wrapperCommand)); - return; - } - emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); - args.prepend(javaPath); - m_process.start(wrapperCommand, wrapperArgs + args); - } - else - { - m_process.start(javaPath, args); - } -} - -void DirectJavaLaunch::on_state(LoggedProcess::State state) -{ - switch(state) - { - case LoggedProcess::FailedToStart: - { - //: Error message displayed if instance can't start - const char *reason = QT_TR_NOOP("Could not launch minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(tr(reason)); - return; - } - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - { - m_parent->setPid(-1); - emitFailed(tr("Game crashed.")); - return; - } - case LoggedProcess::Finished: - { - m_parent->setPid(-1); - // if the exit code wasn't 0, report this as a crash - auto exitCode = m_process.exitCode(); - if(exitCode != 0) - { - emitFailed(tr("Game crashed.")); - return; - } - //FIXME: make this work again - // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); - // run post-exit - emitSucceeded(); - break; - } - case LoggedProcess::Running: - emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); - m_parent->setPid(m_process.processId()); - m_parent->instance()->setLastLaunch(); - break; - default: - break; - } -} - -void DirectJavaLaunch::setWorkingDirectory(const QString &wd) -{ - m_process.setWorkingDirectory(wd); -} - -void DirectJavaLaunch::proceed() -{ - // nil -} - -bool DirectJavaLaunch::abort() -{ - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; -} - diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.h b/api/logic/minecraft/launch/DirectJavaLaunch.h deleted file mode 100644 index 58b119b8..00000000 --- a/api/logic/minecraft/launch/DirectJavaLaunch.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <LoggedProcess.h> -#include <minecraft/auth/AuthSession.h> - -#include "MinecraftServerTarget.h" - -class DirectJavaLaunch: public LaunchStep -{ - Q_OBJECT -public: - explicit DirectJavaLaunch(LaunchTask *parent); - virtual ~DirectJavaLaunch() {}; - - virtual void executeTask(); - virtual bool abort(); - virtual void proceed(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); - void setAuthSession(AuthSessionPtr session) - { - m_session = session; - } - - void setServerToJoin(MinecraftServerTargetPtr serverToJoin) - { - m_serverToJoin = std::move(serverToJoin); - } - -private slots: - void on_state(LoggedProcess::State state); - -private: - LoggedProcess m_process; - QString m_command; - AuthSessionPtr m_session; - MinecraftServerTargetPtr m_serverToJoin; -}; - diff --git a/api/logic/minecraft/launch/ExtractNatives.cpp b/api/logic/minecraft/launch/ExtractNatives.cpp deleted file mode 100644 index d57499aa..00000000 --- a/api/logic/minecraft/launch/ExtractNatives.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "ExtractNatives.h" -#include <minecraft/MinecraftInstance.h> -#include <launch/LaunchTask.h> - -#include <quazip.h> -#include <quazipdir.h> -#include "MMCZip.h" -#include "FileSystem.h" -#include <QDir> - -static QString replaceSuffix (QString target, const QString &suffix, const QString &replacement) -{ - if (!target.endsWith(suffix)) - { - return target; - } - target.resize(target.length() - suffix.length()); - return target + replacement; -} - -static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW) -{ - QuaZip zip(source); - if(!zip.open(QuaZip::mdUnzip)) - { - return false; - } - QDir directory(targetFolder); - if (!zip.goToFirstFile()) - { - return false; - } - do - { - QString name = zip.getCurrentFileName(); - auto lowercase = name.toLower(); - if (nativeGLFW && name.contains("glfw")) { - continue; - } - if (nativeOpenAL && name.contains("openal")) { - continue; - } - if(applyJnilibHack) - { - name = replaceSuffix(name, ".jnilib", ".dylib"); - } - QString absFilePath = directory.absoluteFilePath(name); - if (!JlCompress::extractFile(&zip, "", absFilePath)) - { - return false; - } - } while (zip.goToNextFile()); - zip.close(); - if(zip.getZipError()!=0) - { - return false; - } - return true; -} - -void ExtractNatives::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - auto toExtract = minecraftInstance->getNativeJars(); - if(toExtract.isEmpty()) - { - emitSucceeded(); - return; - } - auto settings = minecraftInstance->settings(); - bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); - bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); - - auto outputPath = minecraftInstance->getNativePath(); - auto javaVersion = minecraftInstance->getJavaVersion(); - bool jniHackEnabled = javaVersion.major() >= 8; - for(const auto &source: toExtract) - { - if(!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW)) - { - const char *reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'"); - emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal); - emitFailed(tr(reason).arg(source, outputPath)); - } - } - emitSucceeded(); -} - -void ExtractNatives::finalize() -{ - auto instance = m_parent->instance(); - QString target_dir = FS::PathCombine(instance->instanceRoot(), "natives/"); - QDir dir(target_dir); - dir.removeRecursively(); -} diff --git a/api/logic/minecraft/launch/ExtractNatives.h b/api/logic/minecraft/launch/ExtractNatives.h deleted file mode 100644 index 094fcd6b..00000000 --- a/api/logic/minecraft/launch/ExtractNatives.h +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <memory> -#include "minecraft/auth/AuthSession.h" - -// FIXME: temporary wrapper for existing task. -class ExtractNatives: public LaunchStep -{ - Q_OBJECT -public: - explicit ExtractNatives(LaunchTask *parent) : LaunchStep(parent){}; - virtual ~ExtractNatives(){}; - - void executeTask() override; - bool canAbort() const override - { - return false; - } - void finalize() override; -}; - - diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.cpp b/api/logic/minecraft/launch/LauncherPartLaunch.cpp deleted file mode 100644 index ee469770..00000000 --- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "LauncherPartLaunch.h" -#include <QCoreApplication> -#include <launch/LaunchTask.h> -#include <minecraft/MinecraftInstance.h> -#include <FileSystem.h> -#include <Commandline.h> -#include <QStandardPaths> -#include "Env.h" - -LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) -{ - connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state); -} - -#ifdef Q_OS_WIN -// returns 8.3 file format from long path -#include <windows.h> -QString shortPathName(const QString & file) -{ - auto input = file.toStdWString(); - std::wstring output; - long length = GetShortPathNameW(input.c_str(), NULL, 0); - // NOTE: this resizing might seem weird... - // when GetShortPathNameW fails, it returns length including null character - // when it succeeds, it returns length excluding null character - // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx - output.resize(length); - GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length); - output.resize(length-1); - QString ret = QString::fromStdWString(output); - return ret; -} -#endif - -// if the string survives roundtrip through local 8bit encoding... -bool fitsInLocal8bit(const QString & string) -{ - return string == QString::fromLocal8Bit(string.toLocal8Bit()); -} - -void LauncherPartLaunch::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - - m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin); - QStringList args = minecraftInstance->javaArguments(); - QString allArgs = args.join(", "); - emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); - - auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - - m_process.setProcessEnvironment(instance->createEnvironment()); - - // make detachable - this will keep the process running even if the object is destroyed - m_process.setDetachable(true); - - auto classPath = minecraftInstance->getClassPath(); - classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar")); - - auto natPath = minecraftInstance->getNativePath(); -#ifdef Q_OS_WIN - if (!fitsInLocal8bit(natPath)) - { - args << "-Djava.library.path=" + shortPathName(natPath); - } - else - { - args << "-Djava.library.path=" + natPath; - } -#else - args << "-Djava.library.path=" + natPath; -#endif - - args << "-cp"; -#ifdef Q_OS_WIN - QStringList processed; - for(auto & item: classPath) - { - if (!fitsInLocal8bit(item)) - { - processed << shortPathName(item); - } - else - { - processed << item; - } - } - args << processed.join(';'); -#else - args << classPath.join(':'); -#endif - args << "org.multimc.EntryPoint"; - - qDebug() << args.join(' '); - - QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); - if(!wrapperCommandStr.isEmpty()) - { - auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); - auto wrapperCommand = wrapperArgs.takeFirst(); - auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); - if (realWrapperCommand.isEmpty()) - { - const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found."); - emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal); - emitFailed(tr(reason).arg(wrapperCommand)); - return; - } - emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); - args.prepend(javaPath); - m_process.start(wrapperCommand, wrapperArgs + args); - } - else - { - m_process.start(javaPath, args); - } -} - -void LauncherPartLaunch::on_state(LoggedProcess::State state) -{ - switch(state) - { - case LoggedProcess::FailedToStart: - { - //: Error message displayed if instace can't start - const char *reason = QT_TR_NOOP("Could not launch minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(tr(reason)); - return; - } - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - { - m_parent->setPid(-1); - emitFailed(tr("Game crashed.")); - return; - } - case LoggedProcess::Finished: - { - m_parent->setPid(-1); - // if the exit code wasn't 0, report this as a crash - auto exitCode = m_process.exitCode(); - if(exitCode != 0) - { - emitFailed(tr("Game crashed.")); - return; - } - //FIXME: make this work again - // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); - // run post-exit - emitSucceeded(); - break; - } - case LoggedProcess::Running: - emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); - m_parent->setPid(m_process.processId()); - m_parent->instance()->setLastLaunch(); - // send the launch script to the launcher part - m_process.write(m_launchScript.toUtf8()); - - mayProceed = true; - emit readyForLaunch(); - break; - default: - break; - } -} - -void LauncherPartLaunch::setWorkingDirectory(const QString &wd) -{ - m_process.setWorkingDirectory(wd); -} - -void LauncherPartLaunch::proceed() -{ - if(mayProceed) - { - QString launchString("launch\n"); - m_process.write(launchString.toUtf8()); - mayProceed = false; - } -} - -bool LauncherPartLaunch::abort() -{ - if(mayProceed) - { - mayProceed = false; - QString launchString("abort\n"); - m_process.write(launchString.toUtf8()); - } - else - { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - } - return true; -} diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.h b/api/logic/minecraft/launch/LauncherPartLaunch.h deleted file mode 100644 index 6a7ee0e5..00000000 --- a/api/logic/minecraft/launch/LauncherPartLaunch.h +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <LoggedProcess.h> -#include <minecraft/auth/AuthSession.h> - -#include "MinecraftServerTarget.h" - -class LauncherPartLaunch: public LaunchStep -{ - Q_OBJECT -public: - explicit LauncherPartLaunch(LaunchTask *parent); - virtual ~LauncherPartLaunch() {}; - - virtual void executeTask(); - virtual bool abort(); - virtual void proceed(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); - void setAuthSession(AuthSessionPtr session) - { - m_session = session; - } - - void setServerToJoin(MinecraftServerTargetPtr serverToJoin) - { - m_serverToJoin = std::move(serverToJoin); - } - -private slots: - void on_state(LoggedProcess::State state); - -private: - LoggedProcess m_process; - QString m_command; - AuthSessionPtr m_session; - QString m_launchScript; - MinecraftServerTargetPtr m_serverToJoin; - - bool mayProceed = false; -}; diff --git a/api/logic/minecraft/launch/MinecraftServerTarget.cpp b/api/logic/minecraft/launch/MinecraftServerTarget.cpp deleted file mode 100644 index 569273b6..00000000 --- a/api/logic/minecraft/launch/MinecraftServerTarget.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "MinecraftServerTarget.h" - -#include <QStringList> - -MinecraftServerTarget MinecraftServerTarget::parse(const QString &fullAddress) { - QStringList split = fullAddress.split(":"); - - // The logic below replicates the exact logic minecraft uses for parsing server addresses. - // While the conversion is not lossless and eats errors, it ensures the same behavior - // within Minecraft and MultiMC when entering server addresses. - if (fullAddress.startsWith("[")) - { - int bracket = fullAddress.indexOf("]"); - if (bracket > 0) - { - QString ipv6 = fullAddress.mid(1, bracket - 1); - QString port = fullAddress.mid(bracket + 1).trimmed(); - - if (port.startsWith(":") && !ipv6.isEmpty()) - { - port = port.mid(1); - split = QStringList({ ipv6, port }); - } - else - { - split = QStringList({ipv6}); - } - } - } - - if (split.size() > 2) - { - split = QStringList({fullAddress}); - } - - QString realAddress = split[0]; - - quint16 realPort = 25565; - if (split.size() > 1) - { - bool ok; - realPort = split[1].toUInt(&ok); - - if (!ok) - { - realPort = 25565; - } - } - - return MinecraftServerTarget { realAddress, realPort }; -} diff --git a/api/logic/minecraft/launch/MinecraftServerTarget.h b/api/logic/minecraft/launch/MinecraftServerTarget.h deleted file mode 100644 index 3c5786f4..00000000 --- a/api/logic/minecraft/launch/MinecraftServerTarget.h +++ /dev/null @@ -1,30 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <memory> - -#include <QString> -#include <multimc_logic_export.h> - -struct MinecraftServerTarget { - QString address; - quint16 port; - - static MULTIMC_LOGIC_EXPORT MinecraftServerTarget parse(const QString &fullAddress); -}; - -typedef std::shared_ptr<MinecraftServerTarget> MinecraftServerTargetPtr; diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp deleted file mode 100644 index 93de9d59..00000000 --- a/api/logic/minecraft/launch/ModMinecraftJar.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "ModMinecraftJar.h" -#include "launch/LaunchTask.h" -#include "MMCZip.h" -#include "minecraft/OpSys.h" -#include "FileSystem.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" - -void ModMinecraftJar::executeTask() -{ - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - - if(!m_inst->getJarMods().size()) - { - emitSucceeded(); - return; - } - // nuke obsolete stripped jar(s) if needed - if(!FS::ensureFolderPathExists(m_inst->binRoot())) - { - emitFailed(tr("Couldn't create the bin folder for Minecraft.jar")); - } - - auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); - if(!removeJar()) - { - emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); - } - - // create temporary modded jar, if needed - auto components = m_inst->getPackProfile(); - auto profile = components->getProfile(); - auto jarMods = m_inst->getJarMods(); - if(jarMods.size()) - { - auto mainJar = profile->getMainJar(); - QStringList jars, temp1, temp2, temp3, temp4; - mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); - auto sourceJarPath = jars[0]; - if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) - { - emitFailed(tr("Failed to create the custom Minecraft jar file.")); - return; - } - } - emitSucceeded(); -} - -void ModMinecraftJar::finalize() -{ - removeJar(); -} - -bool ModMinecraftJar::removeJar() -{ - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); - QFile finalJar(finalJarPath); - if(finalJar.exists()) - { - if(!finalJar.remove()) - { - return false; - } - } - return true; -} diff --git a/api/logic/minecraft/launch/ModMinecraftJar.h b/api/logic/minecraft/launch/ModMinecraftJar.h deleted file mode 100644 index 081c6a91..00000000 --- a/api/logic/minecraft/launch/ModMinecraftJar.h +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <memory> - -class ModMinecraftJar: public LaunchStep -{ - Q_OBJECT -public: - explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {}; - virtual ~ModMinecraftJar(){}; - - virtual void executeTask() override; - virtual bool canAbort() const override - { - return false; - } - void finalize() override; -private: - bool removeJar(); -}; diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.cpp b/api/logic/minecraft/launch/PrintInstanceInfo.cpp deleted file mode 100644 index 0b9611ad..00000000 --- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <fstream> -#include <string> - -#include "PrintInstanceInfo.h" -#include <launch/LaunchTask.h> - -#ifdef Q_OS_LINUX -namespace { -void probeProcCpuinfo(QStringList &log) -{ - std::ifstream cpuin("/proc/cpuinfo"); - for (std::string line; std::getline(cpuin, line);) - { - if (strncmp(line.c_str(), "model name", 10) == 0) - { - log << QString::fromStdString(line.substr(13, std::string::npos)); - break; - } - } -} - -void runLspci(QStringList &log) -{ - // FIXME: fixed size buffers... - char buff[512]; - int gpuline = -1; - int cline = 0; - FILE * lspci = popen("lspci -k", "r"); - - if (!lspci) - return; - - while (fgets(buff, 512, lspci) != NULL) - { - std::string str(buff); - if (str.length() < 9) - continue; - if (str.substr(8, 3) == "VGA") - { - gpuline = cline; - log << QString::fromStdString(str.substr(35, std::string::npos)); - } - if (gpuline > -1 && gpuline != cline) - { - if (cline - gpuline < 3) - { - log << QString::fromStdString(str.substr(1, std::string::npos)); - } - } - cline++; - } - pclose(lspci); -} - -void runGlxinfo(QStringList & log) -{ - // FIXME: fixed size buffers... - char buff[512]; - FILE *glxinfo = popen("glxinfo", "r"); - if (!glxinfo) - return; - - while (fgets(buff, 512, glxinfo) != NULL) - { - if (strncmp(buff, "OpenGL version string:", 22) == 0) - { - log << QString::fromUtf8(buff); - break; - } - } - pclose(glxinfo); -} - -} -#endif - -void PrintInstanceInfo::executeTask() -{ - auto instance = m_parent->instance(); - QStringList log; - -#ifdef Q_OS_LINUX - ::probeProcCpuinfo(log); - ::runLspci(log); - ::runGlxinfo(log); -#endif - - logLines(log, MessageLevel::MultiMC); - logLines(instance->verboseDescription(m_session, m_serverToJoin), MessageLevel::MultiMC); - emitSucceeded(); -} diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.h b/api/logic/minecraft/launch/PrintInstanceInfo.h deleted file mode 100644 index fdc30f31..00000000 --- a/api/logic/minecraft/launch/PrintInstanceInfo.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <memory> -#include "minecraft/auth/AuthSession.h" -#include "minecraft/launch/MinecraftServerTarget.h" - -// FIXME: temporary wrapper for existing task. -class PrintInstanceInfo: public LaunchStep -{ - Q_OBJECT -public: - explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) : - LaunchStep(parent), m_session(session), m_serverToJoin(serverToJoin) {}; - virtual ~PrintInstanceInfo(){}; - - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } -private: - AuthSessionPtr m_session; - MinecraftServerTargetPtr m_serverToJoin; -}; - diff --git a/api/logic/minecraft/launch/ReconstructAssets.cpp b/api/logic/minecraft/launch/ReconstructAssets.cpp deleted file mode 100644 index 4d206665..00000000 --- a/api/logic/minecraft/launch/ReconstructAssets.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "ReconstructAssets.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "minecraft/AssetsUtils.h" -#include "launch/LaunchTask.h" - -void ReconstructAssets::executeTask() -{ - auto instance = m_parent->instance(); - std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); - auto components = minecraftInstance->getPackProfile(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); - - if(!AssetsUtils::reconstructAssets(assets->id, minecraftInstance->resourcesDir())) - { - emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error); - } - - emitSucceeded(); -} diff --git a/api/logic/minecraft/launch/ReconstructAssets.h b/api/logic/minecraft/launch/ReconstructAssets.h deleted file mode 100644 index 58d7febd..00000000 --- a/api/logic/minecraft/launch/ReconstructAssets.h +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <memory> - -class ReconstructAssets: public LaunchStep -{ - Q_OBJECT -public: - explicit ReconstructAssets(LaunchTask *parent) : LaunchStep(parent){}; - virtual ~ReconstructAssets(){}; - - void executeTask() override; - bool canAbort() const override - { - return false; - } -}; diff --git a/api/logic/minecraft/launch/ScanModFolders.cpp b/api/logic/minecraft/launch/ScanModFolders.cpp deleted file mode 100644 index 2a0e21b3..00000000 --- a/api/logic/minecraft/launch/ScanModFolders.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "ScanModFolders.h" -#include "launch/LaunchTask.h" -#include "MMCZip.h" -#include "minecraft/OpSys.h" -#include "FileSystem.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/mod/ModFolderModel.h" - -void ScanModFolders::executeTask() -{ - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - - auto loaders = m_inst->loaderModList(); - connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone); - if(!loaders->update()) { - m_modsDone = true; - } - - auto cores = m_inst->coreModList(); - connect(cores.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone); - if(!cores->update()) { - m_coreModsDone = true; - } - checkDone(); -} - -void ScanModFolders::modsDone() -{ - m_modsDone = true; - checkDone(); -} - -void ScanModFolders::coreModsDone() -{ - m_coreModsDone = true; - checkDone(); -} - -void ScanModFolders::checkDone() -{ - if(m_modsDone && m_coreModsDone) { - emitSucceeded(); - } -} diff --git a/api/logic/minecraft/launch/ScanModFolders.h b/api/logic/minecraft/launch/ScanModFolders.h deleted file mode 100644 index d5989170..00000000 --- a/api/logic/minecraft/launch/ScanModFolders.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <launch/LaunchStep.h> -#include <memory> - -class ScanModFolders: public LaunchStep -{ - Q_OBJECT -public: - explicit ScanModFolders(LaunchTask *parent) : LaunchStep(parent) {}; - virtual ~ScanModFolders(){}; - - virtual void executeTask() override; - virtual bool canAbort() const override - { - return false; - } -private slots: - void coreModsDone(); - void modsDone(); -private: - void checkDone(); - -private: // DATA - bool m_modsDone = false; - bool m_coreModsDone = false; -}; diff --git a/api/logic/minecraft/launch/VerifyJavaInstall.cpp b/api/logic/minecraft/launch/VerifyJavaInstall.cpp deleted file mode 100644 index 657669af..00000000 --- a/api/logic/minecraft/launch/VerifyJavaInstall.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "VerifyJavaInstall.h" - -#include <launch/LaunchTask.h> -#include <minecraft/MinecraftInstance.h> -#include <minecraft/PackProfile.h> -#include <minecraft/VersionFilterData.h> - -void VerifyJavaInstall::executeTask() { - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - - auto javaVersion = m_inst->getJavaVersion(); - auto minecraftComponent = m_inst->getPackProfile()->getComponent("net.minecraft"); - - // Java 16 requirement - if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java16BeginsDate) { - if (javaVersion.major() < 16) { - emit logLine("Minecraft 21w19a and above require the use of Java 16", - MessageLevel::Fatal); - emitFailed(tr("Minecraft 21w19a and above require the use of Java 16")); - return; - } - } - // Java 8 requirement - else if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java8BeginsDate) { - if (javaVersion.major() < 8) { - emit logLine("Minecraft 17w13a and above require the use of Java 8", - MessageLevel::Fatal); - emitFailed(tr("Minecraft 17w13a and above require the use of Java 8")); - return; - } - } - - emitSucceeded(); -} diff --git a/api/logic/minecraft/launch/VerifyJavaInstall.h b/api/logic/minecraft/launch/VerifyJavaInstall.h deleted file mode 100644 index a553106d..00000000 --- a/api/logic/minecraft/launch/VerifyJavaInstall.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include <launch/LaunchStep.h> - -class VerifyJavaInstall : public LaunchStep { - Q_OBJECT - -public: - explicit VerifyJavaInstall(LaunchTask *parent) : LaunchStep(parent) { - }; - ~VerifyJavaInstall() override = default; - - void executeTask() override; - bool canAbort() const override { - return false; - } -}; diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp deleted file mode 100644 index 9f9bda5a..00000000 --- a/api/logic/minecraft/legacy/LegacyInstance.cpp +++ /dev/null @@ -1,256 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QFileInfo> -#include <minecraft/launch/LauncherPartLaunch.h> -#include <QDir> -#include <settings/Setting.h> - -#include "LegacyInstance.h" - -#include "minecraft/legacy/LegacyModList.h" -#include "minecraft/WorldList.h" -#include <MMCZip.h> -#include <FileSystem.h> - -LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : BaseInstance(globalSettings, settings, rootDir) -{ - settings->registerSetting("NeedsRebuild", true); - settings->registerSetting("ShouldUpdate", false); - settings->registerSetting("JarVersion", QString()); - settings->registerSetting("IntendedJarVersion", QString()); - /* - * custom base jar has no default. it is determined in code... see the accessor methods for - *it - * - * for instances that DO NOT have the CustomBaseJar setting (legacy instances), - * [.]minecraft/bin/mcbackup.jar is the default base jar - */ - settings->registerSetting("UseCustomBaseJar", true); - settings->registerSetting("CustomBaseJar", ""); -} - -QString LegacyInstance::mainJarToPreserve() const -{ - bool customJar = m_settings->get("UseCustomBaseJar").toBool(); - if(customJar) - { - auto base = baseJar(); - if(QFile::exists(base)) - { - return base; - } - } - auto runnable = runnableJar(); - if(QFile::exists(runnable)) - { - return runnable; - } - return QString(); -} - - -QString LegacyInstance::baseJar() const -{ - bool customJar = m_settings->get("UseCustomBaseJar").toBool(); - if (customJar) - { - return customBaseJar(); - } - else - return defaultBaseJar(); -} - -QString LegacyInstance::customBaseJar() const -{ - QString value = m_settings->get("CustomBaseJar").toString(); - if (value.isNull() || value.isEmpty()) - { - return defaultCustomBaseJar(); - } - return value; -} - -bool LegacyInstance::shouldUseCustomBaseJar() const -{ - return m_settings->get("UseCustomBaseJar").toBool(); -} - - -shared_qobject_ptr<Task> LegacyInstance::createUpdateTask(Net::Mode) -{ - return nullptr; -} - -std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const -{ - if (!jar_mod_list) - { - auto list = new LegacyModList(jarModsDir(), modListFile()); - jar_mod_list.reset(list); - } - jar_mod_list->update(); - return jar_mod_list; -} - -QString LegacyInstance::gameRoot() const -{ - QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - - if (mcDir.exists() && !dotMCDir.exists()) - return mcDir.filePath(); - else - return dotMCDir.filePath(); -} - -QString LegacyInstance::binRoot() const -{ - return FS::PathCombine(gameRoot(), "bin"); -} - -QString LegacyInstance::jarModsDir() const -{ - return FS::PathCombine(instanceRoot(), "instMods"); -} - -QString LegacyInstance::libDir() const -{ - return FS::PathCombine(gameRoot(), "lib"); -} - -QString LegacyInstance::savesDir() const -{ - return FS::PathCombine(gameRoot(), "saves"); -} - -QString LegacyInstance::loaderModsDir() const -{ - return FS::PathCombine(gameRoot(), "mods"); -} - -QString LegacyInstance::coreModsDir() const -{ - return FS::PathCombine(gameRoot(), "coremods"); -} - -QString LegacyInstance::resourceDir() const -{ - return FS::PathCombine(gameRoot(), "resources"); -} -QString LegacyInstance::texturePacksDir() const -{ - return FS::PathCombine(gameRoot(), "texturepacks"); -} - -QString LegacyInstance::runnableJar() const -{ - return FS::PathCombine(binRoot(), "minecraft.jar"); -} - -QString LegacyInstance::modListFile() const -{ - return FS::PathCombine(instanceRoot(), "modlist"); -} - -QString LegacyInstance::instanceConfigFolder() const -{ - return FS::PathCombine(gameRoot(), "config"); -} - -bool LegacyInstance::shouldRebuild() const -{ - return m_settings->get("NeedsRebuild").toBool(); -} - -QString LegacyInstance::currentVersionId() const -{ - return m_settings->get("JarVersion").toString(); -} - -QString LegacyInstance::intendedVersionId() const -{ - return m_settings->get("IntendedJarVersion").toString(); -} - -bool LegacyInstance::shouldUpdate() const -{ - QVariant var = settings()->get("ShouldUpdate"); - if (!var.isValid() || var.toBool() == false) - { - return intendedVersionId() != currentVersionId(); - } - return true; -} - -QString LegacyInstance::defaultBaseJar() const -{ - return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; -} - -QString LegacyInstance::defaultCustomBaseJar() const -{ - return FS::PathCombine(binRoot(), "mcbackup.jar"); -} - -std::shared_ptr<WorldList> LegacyInstance::worldList() const -{ - if (!m_world_list) - { - m_world_list.reset(new WorldList(savesDir())); - } - return m_world_list; -} - -QString LegacyInstance::typeName() const -{ - return tr("Legacy"); -} - -QString LegacyInstance::getStatusbarDescription() -{ - return tr("Instance from previous versions."); -} - -QStringList LegacyInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) -{ - QStringList out; - - auto alltraits = traits(); - if(alltraits.size()) - { - out << "Traits:"; - for (auto trait : alltraits) - { - out << " " + trait; - } - out << ""; - } - - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - { - out << "Window size: max (if available)"; - } - else - { - auto width = settings()->get("MinecraftWinWidth").toInt(); - auto height = settings()->get("MinecraftWinHeight").toInt(); - out << "Window size: " + QString::number(width) + " x " + QString::number(height); - } - out << ""; - return out; -} diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h deleted file mode 100644 index 325bac7a..00000000 --- a/api/logic/minecraft/legacy/LegacyInstance.h +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "BaseInstance.h" -#include "launch/LaunchTask.h" - -#include "multimc_logic_export.h" - -class ModFolderModel; -class LegacyModList; -class WorldList; -class Task; -/* - * WHY: Legacy instances - from MultiMC 3 and 4 - are here only to provide a way to upgrade them to the current format. - */ -class MULTIMC_LOGIC_EXPORT LegacyInstance : public BaseInstance -{ - Q_OBJECT -public: - - explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - - virtual void saveNow() override {} - - /// Path to the instance's minecraft.jar - QString runnableJar() const; - - //! Path to the instance's modlist file. - QString modListFile() const; - - ////// Directories ////// - QString libDir() const; - QString savesDir() const; - QString texturePacksDir() const; - QString jarModsDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString resourceDir() const; - virtual QString instanceConfigFolder() const override; - QString gameRoot() const override; // Path to the instance's minecraft directory. - QString binRoot() const; // Path to the instance's minecraft bin directory. - - /// Get the curent base jar of this instance. By default, it's the - /// versions/$version/$version.jar - QString baseJar() const; - - /// the default base jar of this instance - QString defaultBaseJar() const; - /// the default custom base jar of this instance - QString defaultCustomBaseJar() const; - - // the main jar that we actually want to keep when migrating the instance - QString mainJarToPreserve() const; - - /*! - * Whether or not custom base jar is used - */ - bool shouldUseCustomBaseJar() const; - - /*! - * The value of the custom base jar - */ - QString customBaseJar() const; - - std::shared_ptr<LegacyModList> jarModList() const; - std::shared_ptr<WorldList> worldList() const; - - /*! - * Whether or not the instance's minecraft.jar needs to be rebuilt. - * If this is true, when the instance launches, its jar mods will be - * re-added to a fresh minecraft.jar file. - */ - bool shouldRebuild() const; - - QString currentVersionId() const; - QString intendedVersionId() const; - - QSet<QString> traits() const override - { - return {"legacy-instance", "texturepacks"}; - }; - - virtual bool shouldUpdate() const; - virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override; - - virtual QString typeName() const override; - - bool canLaunch() const override - { - return false; - } - bool canEdit() const override - { - return true; - } - bool canExport() const override - { - return false; - } - shared_qobject_ptr<LaunchTask> createLaunchTask( - AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override - { - return nullptr; - } - IPathMatcher::Ptr getLogFileMatcher() override - { - return nullptr; - } - QString getLogFileRoot() override - { - return gameRoot(); - } - - QString getStatusbarDescription() override; - QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override; - - QProcessEnvironment createEnvironment() override - { - return QProcessEnvironment(); - } - QMap<QString, QString> getVariables() const override - { - return {}; - } -protected: - mutable std::shared_ptr<LegacyModList> jar_mod_list; - mutable std::shared_ptr<WorldList> m_world_list; -}; diff --git a/api/logic/minecraft/legacy/LegacyModList.cpp b/api/logic/minecraft/legacy/LegacyModList.cpp deleted file mode 100644 index 7301eb8c..00000000 --- a/api/logic/minecraft/legacy/LegacyModList.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "LegacyModList.h" -#include <FileSystem.h> -#include <QString> -#include <QDebug> - -LegacyModList::LegacyModList(const QString &dir, const QString &list_file) - : m_dir(dir), m_list_file(list_file) -{ - FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); -} - - struct OrderItem - { - QString id; - bool enabled = false; - }; - typedef QList<OrderItem> OrderList; - -static void internalSort(QList<LegacyModList::Mod> &what) -{ - auto predicate = [](const LegacyModList::Mod &left, const LegacyModList::Mod &right) - { - return left.fileName().localeAwareCompare(right.fileName()) < 0; - }; - std::sort(what.begin(), what.end(), predicate); -} - -static OrderList readListFile(const QString &m_list_file) -{ - OrderList itemList; - if (m_list_file.isNull() || m_list_file.isEmpty()) - return itemList; - - QFile textFile(m_list_file); - if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) - return OrderList(); - - QTextStream textStream; - textStream.setAutoDetectUnicode(true); - textStream.setDevice(&textFile); - while (true) - { - QString line = textStream.readLine(); - if (line.isNull() || line.isEmpty()) - break; - else - { - OrderItem it; - it.enabled = !line.endsWith(".disabled"); - if (!it.enabled) - { - line.chop(9); - } - it.id = line; - itemList.append(it); - } - } - textFile.close(); - return itemList; -} - -bool LegacyModList::update() -{ - if (!m_dir.exists() || !m_dir.isReadable()) - return false; - - QList<Mod> orderedMods; - QList<Mod> newMods; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - - // first, process the ordered items (if any) - OrderList listOrder = readListFile(m_list_file); - for (auto item : listOrder) - { - QFileInfo infoEnabled(m_dir.filePath(item.id)); - QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled")); - int idxEnabled = folderContents.indexOf(infoEnabled); - int idxDisabled = folderContents.indexOf(infoDisabled); - bool isEnabled; - // if both enabled and disabled versions are present, it's a special case... - if (idxEnabled >= 0 && idxDisabled >= 0) - { - // we only process the one we actually have in the order file. - // and exactly as we have it. - // THIS IS A CORNER CASE - isEnabled = item.enabled; - } - else - { - // only one is present. - // we pick the one that we found. - // we assume the mod was enabled/disabled by external means - isEnabled = idxEnabled >= 0; - } - int idx = isEnabled ? idxEnabled : idxDisabled; - QFileInfo &info = isEnabled ? infoEnabled : infoDisabled; - // if the file from the index file exists - if (idx != -1) - { - // remove from the actual folder contents list - folderContents.takeAt(idx); - // append the new mod - orderedMods.append(info); - } - } - // if there are any untracked files... append them sorted at the end - if (folderContents.size()) - { - for (auto entry : folderContents) - { - newMods.append(entry); - } - internalSort(newMods); - orderedMods.append(newMods); - } - mods.swap(orderedMods); - return true; -} diff --git a/api/logic/minecraft/legacy/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h deleted file mode 100644 index 8881d471..00000000 --- a/api/logic/minecraft/legacy/LegacyModList.h +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QList> -#include <QString> -#include <QDir> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT LegacyModList -{ -public: - - using Mod = QFileInfo; - - LegacyModList(const QString &dir, const QString &list_file = QString()); - - /// Reloads the mod list and returns true if the list changed. - bool update(); - - QDir dir() - { - return m_dir; - } - - const QList<Mod> & allMods() - { - return mods; - } - -protected: - QDir m_dir; - QString m_list_file; - QList<Mod> mods; -}; diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp deleted file mode 100644 index a4ea60cd..00000000 --- a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include "LegacyUpgradeTask.h" -#include "settings/INISettingsObject.h" -#include "FileSystem.h" -#include "NullInstance.h" -#include "pathmatcher/RegexpMatcher.h" -#include <QtConcurrentRun> -#include "LegacyInstance.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "LegacyModList.h" -#include "classparser.h" - -LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance) -{ - m_origInstance = origInstance; -} - -void LegacyUpgradeTask::executeTask() -{ - setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(true); - - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); - connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &LegacyUpgradeTask::copyFinished); - connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &LegacyUpgradeTask::copyAborted); - m_copyFutureWatcher.setFuture(m_copyFuture); -} - -static QString decideVersion(const QString& currentVersion, const QString& intendedVersion) -{ - if(intendedVersion != currentVersion) - { - if(!intendedVersion.isEmpty()) - { - return intendedVersion; - } - else if(!currentVersion.isEmpty()) - { - return currentVersion; - } - } - else - { - if(!intendedVersion.isEmpty()) - { - return intendedVersion; - } - } - return QString(); -} - -void LegacyUpgradeTask::copyFinished() -{ - auto successful = m_copyFuture.result(); - if(!successful) - { - emitFailed(tr("Instance folder copy failed.")); - return; - } - auto legacyInst = std::dynamic_pointer_cast<LegacyInstance>(m_origInstance); - - auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - // NOTE: this scope ensures the instance is fully saved before we emitSucceeded - { - MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); - inst.setName(m_instName); - - QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId()); - if(preferredVersionNumber.isNull()) - { - // try to decide version based on the jar(s?) - preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar()); - if(preferredVersionNumber.isNull()) - { - preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar()); - if(preferredVersionNumber.isNull()) - { - emitFailed(tr("Could not decide Minecraft version.")); - return; - } - } - } - auto components = inst.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", preferredVersionNumber, true); - - QString jarPath = legacyInst->mainJarToPreserve(); - if(!jarPath.isNull()) - { - qDebug() << "Preserving base jar! : " << jarPath; - // FIXME: handle case when the jar is unreadable? - // TODO: check the hash, if it's the same as the upstream jar, do not do this - components->installCustomJar(jarPath); - } - - auto jarMods = legacyInst->jarModList()->allMods(); - for(auto & jarMod: jarMods) - { - QString modPath = jarMod.absoluteFilePath(); - qDebug() << "jarMod: " << modPath; - components->installJarMods({modPath}); - } - - // remove all the extra garbage we no longer need - auto removeAll = [&](const QString &root, const QStringList &things) - { - for(auto &thing : things) - { - auto removePath = FS::PathCombine(root, thing); - QFileInfo stat(removePath); - if(stat.isDir()) - { - FS::deletePath(removePath); - } - else - { - QFile::remove(removePath); - } - } - }; - QStringList rootRemovables = {"modlist", "version", "instMods"}; - QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"}; - removeAll(inst.instanceRoot(), rootRemovables); - removeAll(inst.gameRoot(), mcRemovables); - } - emitSucceeded(); -} - -void LegacyUpgradeTask::copyAborted() -{ - emitFailed(tr("Instance folder copy has been aborted.")); - return; -} - diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.h b/api/logic/minecraft/legacy/LegacyUpgradeTask.h deleted file mode 100644 index e35e43b7..00000000 --- a/api/logic/minecraft/legacy/LegacyUpgradeTask.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "InstanceTask.h" -#include "multimc_logic_export.h" -#include "net/NetJob.h" -#include <QUrl> -#include <QFuture> -#include <QFutureWatcher> -#include "settings/SettingsObject.h" -#include "BaseVersion.h" -#include "BaseInstance.h" - - -class MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public InstanceTask -{ - Q_OBJECT -public: - explicit LegacyUpgradeTask(InstancePtr origInstance); - -protected: - //! Entry point for tasks. - virtual void executeTask() override; - void copyFinished(); - void copyAborted(); - -private: /* data */ - InstancePtr m_origInstance; - QFuture<bool> m_copyFuture; - QFutureWatcher<bool> m_copyFutureWatcher; -}; diff --git a/api/logic/minecraft/mod/LocalModParseTask.cpp b/api/logic/minecraft/mod/LocalModParseTask.cpp deleted file mode 100644 index 0d6972fb..00000000 --- a/api/logic/minecraft/mod/LocalModParseTask.cpp +++ /dev/null @@ -1,467 +0,0 @@ -#include "LocalModParseTask.h" - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> -#include <quazip.h> -#include <quazipfile.h> -#include <toml.h> - -#include "settings/INIFile.h" -#include "FileSystem.h" - -namespace { - -// NEW format -// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3 - -// OLD format: -// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc -std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents) -{ - auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr<ModDetails> - { - if (!arr.at(0).isObject()) { - return nullptr; - } - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - auto firstObj = arr.at(0).toObject(); - details->mod_id = firstObj.value("modid").toString(); - auto name = firstObj.value("name").toString(); - // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name - if(name != "Example Mod") { - details->name = name; - } - details->version = firstObj.value("version").toString(); - details->updateurl = firstObj.value("updateUrl").toString(); - auto homeurl = firstObj.value("url").toString().trimmed(); - if(!homeurl.isEmpty()) - { - // fix up url. - if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) - { - homeurl.prepend("http://"); - } - } - details->homeurl = homeurl; - details->description = firstObj.value("description").toString(); - QJsonArray authors = firstObj.value("authorList").toArray(); - if (authors.size() == 0) { - // FIXME: what is the format of this? is there any? - authors = firstObj.value("authors").toArray(); - } - - for (auto author: authors) - { - details->authors.append(author.toString()); - } - details->credits = firstObj.value("credits").toString(); - return details; - }; - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - // this is the very old format that had just the array - if (jsonDoc.isArray()) - { - return getInfoFromArray(jsonDoc.array()); - } - else if (jsonDoc.isObject()) - { - auto val = jsonDoc.object().value("modinfoversion"); - if(val.isUndefined()) { - val = jsonDoc.object().value("modListVersion"); - } - int version = val.toDouble(); - if (version != 2) - { - qCritical() << "BAD stuff happened to mod json:"; - qCritical() << contents; - return nullptr; - } - auto arrVal = jsonDoc.object().value("modlist"); - if(arrVal.isUndefined()) { - arrVal = jsonDoc.object().value("modList"); - } - if (arrVal.isArray()) - { - return getInfoFromArray(arrVal.toArray()); - } - } - return nullptr; -} - -// https://github.com/MinecraftForge/Documentation/blob/5ab4ba6cf9abc0ac4c0abd96ad187461aefd72af/docs/gettingstarted/structuring.md -std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents) -{ - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - - char errbuf[200]; - // top-level table - toml_table_t* tomlData = toml_parse(contents.data(), errbuf, sizeof(errbuf)); - - if(!tomlData) - { - return nullptr; - } - - // array defined by [[mods]] - toml_array_t* tomlModsArr = toml_array_in(tomlData, "mods"); - // we only really care about the first element, since multiple mods in one file is not supported by us at the moment - toml_table_t* tomlModsTable0 = toml_table_at(tomlModsArr, 0); - - // mandatory properties - always in [[mods]] - toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId"); - if(modIdDatum.ok) - { - details->mod_id = modIdDatum.u.s; - // library says this is required for strings - free(modIdDatum.u.s); - } - toml_datum_t versionDatum = toml_string_in(tomlModsTable0, "version"); - if(versionDatum.ok) - { - details->version = versionDatum.u.s; - free(versionDatum.u.s); - } - toml_datum_t displayNameDatum = toml_string_in(tomlModsTable0, "displayName"); - if(displayNameDatum.ok) - { - details->name = displayNameDatum.u.s; - free(displayNameDatum.u.s); - } - toml_datum_t descriptionDatum = toml_string_in(tomlModsTable0, "description"); - if(descriptionDatum.ok) - { - details->description = descriptionDatum.u.s; - free(descriptionDatum.u.s); - } - - // optional properties - can be in the root table or [[mods]] - toml_datum_t authorsDatum = toml_string_in(tomlData, "authors"); - QString authors = ""; - if(authorsDatum.ok) - { - authors = authorsDatum.u.s; - free(authorsDatum.u.s); - } - else - { - authorsDatum = toml_string_in(tomlModsTable0, "authors"); - if(authorsDatum.ok) - { - authors = authorsDatum.u.s; - free(authorsDatum.u.s); - } - } - if(!authors.isEmpty()) - { - // author information is stored as a string now, not a list - details->authors.append(authors); - } - // is credits even used anywhere? including this for completion/parity with old data version - toml_datum_t creditsDatum = toml_string_in(tomlData, "credits"); - QString credits = ""; - if(creditsDatum.ok) - { - authors = creditsDatum.u.s; - free(creditsDatum.u.s); - } - else - { - creditsDatum = toml_string_in(tomlModsTable0, "credits"); - if(creditsDatum.ok) - { - credits = creditsDatum.u.s; - free(creditsDatum.u.s); - } - } - details->credits = credits; - toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL"); - QString homeurl = ""; - if(homeurlDatum.ok) - { - homeurl = homeurlDatum.u.s; - free(homeurlDatum.u.s); - } - else - { - homeurlDatum = toml_string_in(tomlModsTable0, "displayURL"); - if(homeurlDatum.ok) - { - homeurl = homeurlDatum.u.s; - free(homeurlDatum.u.s); - } - } - if(!homeurl.isEmpty()) - { - // fix up url. - if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) - { - homeurl.prepend("http://"); - } - } - details->homeurl = homeurl; - - // this seems to be recursive, so it should free everything - toml_free(tomlData); - - return details; -} - -// https://fabricmc.net/wiki/documentation:fabric_mod_json -std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents) -{ - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - auto object = jsonDoc.object(); - auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0; - - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - - details->mod_id = object.value("id").toString(); - details->version = object.value("version").toString(); - - details->name = object.contains("name") ? object.value("name").toString() : details->mod_id; - details->description = object.value("description").toString(); - - if (schemaVersion >= 1) - { - QJsonArray authors = object.value("authors").toArray(); - for (auto author: authors) - { - if(author.isObject()) { - details->authors.append(author.toObject().value("name").toString()); - } - else { - details->authors.append(author.toString()); - } - } - - if (object.contains("contact")) - { - QJsonObject contact = object.value("contact").toObject(); - - if (contact.contains("homepage")) - { - details->homeurl = contact.value("homepage").toString(); - } - } - } - return details; -} - -std::shared_ptr<ModDetails> ReadForgeInfo(QByteArray contents) -{ - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - // Read the data - details->name = "Minecraft Forge"; - details->mod_id = "Forge"; - details->homeurl = "http://www.minecraftforge.net/forum/"; - INIFile ini; - if (!ini.loadFile(contents)) - return details; - - QString major = ini.get("forge.major.number", "0").toString(); - QString minor = ini.get("forge.minor.number", "0").toString(); - QString revision = ini.get("forge.revision.number", "0").toString(); - QString build = ini.get("forge.build.number", "0").toString(); - - details->version = major + "." + minor + "." + revision + "." + build; - return details; -} - -std::shared_ptr<ModDetails> ReadLiteModInfo(QByteArray contents) -{ - std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>(); - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - auto object = jsonDoc.object(); - if (object.contains("name")) - { - details->mod_id = details->name = object.value("name").toString(); - } - if (object.contains("version")) - { - details->version = object.value("version").toString(""); - } - else - { - details->version = object.value("revision").toString(""); - } - details->mcversion = object.value("mcversion").toString(); - auto author = object.value("author").toString(); - if(!author.isEmpty()) { - details->authors.append(author); - } - details->description = object.value("description").toString(); - details->homeurl = object.value("url").toString(); - return details; -} - -} - -LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile): - m_token(token), - m_type(type), - m_modFile(modFile), - m_result(new Result()) -{ -} - -void LocalModParseTask::processAsZip() -{ - QuaZip zip(m_modFile.filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return; - - QuaZipFile file(&zip); - - if (zip.setCurrentFile("META-INF/mods.toml")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadMCModTOML(file.readAll()); - file.close(); - - // to replace ${file.jarVersion} with the actual version, as needed - if (m_result->details && m_result->details->version == "${file.jarVersion}") - { - if (zip.setCurrentFile("META-INF/MANIFEST.MF")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - // quick and dirty line-by-line parser - auto manifestLines = file.readAll().split('\n'); - QString manifestVersion = ""; - for (auto &line : manifestLines) - { - if (QString(line).startsWith("Implementation-Version: ")) - { - manifestVersion = QString(line).remove("Implementation-Version: "); - break; - } - } - - // some mods use ${projectversion} in their build.gradle, causing this mess to show up in MANIFEST.MF - // also keep with forge's behavior of setting the version to "NONE" if none is found - if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") - { - manifestVersion = "NONE"; - } - - m_result->details->version = manifestVersion; - - file.close(); - } - } - - zip.close(); - return; - } - else if (zip.setCurrentFile("mcmod.info")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadMCModInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - else if (zip.setCurrentFile("fabric.mod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadFabricModInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - else if (zip.setCurrentFile("forgeversion.properties")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadForgeInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - - zip.close(); -} - -void LocalModParseTask::processAsFolder() -{ - QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info")); - if (mcmod_info.isFile()) - { - QFile mcmod(mcmod_info.filePath()); - if (!mcmod.open(QIODevice::ReadOnly)) - return; - auto data = mcmod.readAll(); - if (data.isEmpty() || data.isNull()) - return; - m_result->details = ReadMCModInfo(data); - } -} - -void LocalModParseTask::processAsLitemod() -{ - QuaZip zip(m_modFile.filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return; - - QuaZipFile file(&zip); - - if (zip.setCurrentFile("litemod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - m_result->details = ReadLiteModInfo(file.readAll()); - file.close(); - } - zip.close(); -} - -void LocalModParseTask::run() -{ - switch(m_type) - { - case Mod::MOD_ZIPFILE: - processAsZip(); - break; - case Mod::MOD_FOLDER: - processAsFolder(); - break; - case Mod::MOD_LITEMOD: - processAsLitemod(); - break; - default: - break; - } - emit finished(m_token); -} diff --git a/api/logic/minecraft/mod/LocalModParseTask.h b/api/logic/minecraft/mod/LocalModParseTask.h deleted file mode 100644 index 0f119ba6..00000000 --- a/api/logic/minecraft/mod/LocalModParseTask.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once -#include <QRunnable> -#include <QDebug> -#include <QObject> -#include "Mod.h" -#include "ModDetails.h" - -class LocalModParseTask : public QObject, public QRunnable -{ - Q_OBJECT -public: - struct Result { - QString id; - std::shared_ptr<ModDetails> details; - }; - using ResultPtr = std::shared_ptr<Result>; - ResultPtr result() const { - return m_result; - } - - LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile); - void run(); - -signals: - void finished(int token); - -private: - void processAsZip(); - void processAsFolder(); - void processAsLitemod(); - -private: - int m_token; - Mod::ModType m_type; - QFileInfo m_modFile; - ResultPtr m_result; -}; diff --git a/api/logic/minecraft/mod/Mod.cpp b/api/logic/minecraft/mod/Mod.cpp deleted file mode 100644 index b6bff29b..00000000 --- a/api/logic/minecraft/mod/Mod.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QDir> -#include <QString> - -#include "Mod.h" -#include <QDebug> -#include <FileSystem.h> - -namespace { - -ModDetails invalidDetails; - -} - - -Mod::Mod(const QFileInfo &file) -{ - repath(file); - m_changedDateTime = file.lastModified(); -} - -void Mod::repath(const QFileInfo &file) -{ - m_file = file; - QString name_base = file.fileName(); - - m_type = Mod::MOD_UNKNOWN; - - m_mmc_id = name_base; - - if (m_file.isDir()) - { - m_type = MOD_FOLDER; - m_name = name_base; - } - else if (m_file.isFile()) - { - if (name_base.endsWith(".disabled")) - { - m_enabled = false; - name_base.chop(9); - } - else - { - m_enabled = true; - } - if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) - { - m_type = MOD_ZIPFILE; - name_base.chop(4); - } - else if (name_base.endsWith(".litemod")) - { - m_type = MOD_LITEMOD; - name_base.chop(8); - } - else - { - m_type = MOD_SINGLEFILE; - } - m_name = name_base; - } -} - -bool Mod::enable(bool value) -{ - if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER) - return false; - - if (m_enabled == value) - return false; - - QString path = m_file.absoluteFilePath(); - if (value) - { - QFile foo(path); - if (!path.endsWith(".disabled")) - return false; - path.chop(9); - if (!foo.rename(path)) - return false; - } - else - { - QFile foo(path); - path += ".disabled"; - if (!foo.rename(path)) - return false; - } - repath(QFileInfo(path)); - m_enabled = value; - return true; -} - -bool Mod::destroy() -{ - m_type = MOD_UNKNOWN; - return FS::deletePath(m_file.filePath()); -} - - -const ModDetails & Mod::details() const -{ - if(!m_localDetails) - return invalidDetails; - return *m_localDetails; -} - - -QString Mod::version() const -{ - return details().version; -} - -QString Mod::name() const -{ - auto & d = details(); - if(!d.name.isEmpty()) { - return d.name; - } - return m_name; -} - -QString Mod::homeurl() const -{ - return details().homeurl; -} - -QString Mod::description() const -{ - return details().description; -} - -QStringList Mod::authors() const -{ - return details().authors; -} diff --git a/api/logic/minecraft/mod/Mod.h b/api/logic/minecraft/mod/Mod.h deleted file mode 100644 index f77ffd41..00000000 --- a/api/logic/minecraft/mod/Mod.h +++ /dev/null @@ -1,117 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QFileInfo> -#include <QDateTime> -#include <QList> -#include <memory> - -#include "multimc_logic_export.h" - -#include "ModDetails.h" - - - -class MULTIMC_LOGIC_EXPORT Mod -{ -public: - enum ModType - { - MOD_UNKNOWN, //!< Indicates an unspecified mod type. - MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. - MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). - MOD_FOLDER, //!< The mod is in a folder on the filesystem. - MOD_LITEMOD, //!< The mod is a litemod - }; - - Mod() = default; - Mod(const QFileInfo &file); - - QFileInfo filename() const - { - return m_file; - } - QString mmc_id() const - { - return m_mmc_id; - } - ModType type() const - { - return m_type; - } - bool valid() - { - return m_type != MOD_UNKNOWN; - } - - QDateTime dateTimeChanged() const - { - return m_changedDateTime; - } - - bool enabled() const - { - return m_enabled; - } - - const ModDetails &details() const; - - QString name() const; - QString version() const; - QString homeurl() const; - QString description() const; - QStringList authors() const; - - bool enable(bool value); - - // delete all the files of this mod - bool destroy(); - - // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) - void repath(const QFileInfo &file); - - bool shouldResolve() { - return !m_resolving && !m_resolved; - } - bool isResolving() { - return m_resolving; - } - int resolutionTicket() - { - return m_resolutionTicket; - } - void setResolving(bool resolving, int resolutionTicket) { - m_resolving = resolving; - m_resolutionTicket = resolutionTicket; - } - void finishResolvingWithDetails(std::shared_ptr<ModDetails> details){ - m_resolving = false; - m_resolved = true; - m_localDetails = details; - } - -protected: - QFileInfo m_file; - QDateTime m_changedDateTime; - QString m_mmc_id; - QString m_name; - bool m_enabled = true; - bool m_resolving = false; - bool m_resolved = false; - int m_resolutionTicket = 0; - ModType m_type = MOD_UNKNOWN; - std::shared_ptr<ModDetails> m_localDetails; -}; diff --git a/api/logic/minecraft/mod/ModDetails.h b/api/logic/minecraft/mod/ModDetails.h deleted file mode 100644 index 6ab4aee7..00000000 --- a/api/logic/minecraft/mod/ModDetails.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include <QString> -#include <QStringList> - -struct ModDetails -{ - QString mod_id; - QString name; - QString version; - QString mcversion; - QString homeurl; - QString updateurl; - QString description; - QStringList authors; - QString credits; -}; diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.cpp b/api/logic/minecraft/mod/ModFolderLoadTask.cpp deleted file mode 100644 index 88349877..00000000 --- a/api/logic/minecraft/mod/ModFolderLoadTask.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "ModFolderLoadTask.h" -#include <QDebug> - -ModFolderLoadTask::ModFolderLoadTask(QDir dir) : - m_dir(dir), m_result(new Result()) -{ -} - -void ModFolderLoadTask::run() -{ - m_dir.refresh(); - for (auto entry : m_dir.entryInfoList()) - { - Mod m(entry); - m_result->mods[m.mmc_id()] = m; - } - emit succeeded(); -} diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.h b/api/logic/minecraft/mod/ModFolderLoadTask.h deleted file mode 100644 index 8d720e65..00000000 --- a/api/logic/minecraft/mod/ModFolderLoadTask.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include <QRunnable> -#include <QObject> -#include <QDir> -#include <QMap> -#include "Mod.h" -#include <memory> - -class ModFolderLoadTask : public QObject, public QRunnable -{ - Q_OBJECT -public: - struct Result { - QMap<QString, Mod> mods; - }; - using ResultPtr = std::shared_ptr<Result>; - ResultPtr result() const { - return m_result; - } - -public: - ModFolderLoadTask(QDir dir); - void run(); -signals: - void succeeded(); -private: - QDir m_dir; - ResultPtr m_result; -}; diff --git a/api/logic/minecraft/mod/ModFolderModel.cpp b/api/logic/minecraft/mod/ModFolderModel.cpp deleted file mode 100644 index 031eebe5..00000000 --- a/api/logic/minecraft/mod/ModFolderModel.cpp +++ /dev/null @@ -1,554 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "ModFolderModel.h" -#include <FileSystem.h> -#include <QMimeData> -#include <QUrl> -#include <QUuid> -#include <QString> -#include <QFileSystemWatcher> -#include <QDebug> -#include "ModFolderLoadTask.h" -#include <QThreadPool> -#include <algorithm> -#include "LocalModParseTask.h" - -ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir) -{ - FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); - m_watcher = new QFileSystemWatcher(this); - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); -} - -void ModFolderModel::startWatching() -{ - if(is_watching) - return; - - update(); - - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) - { - qDebug() << "Started watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to start watching " << m_dir.absolutePath(); - } -} - -void ModFolderModel::stopWatching() -{ - if(!is_watching) - return; - - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) - { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } -} - -bool ModFolderModel::update() -{ - if (!isValid()) { - return false; - } - if(m_update) { - scheduled_update = true; - return true; - } - - auto task = new ModFolderLoadTask(m_dir); - m_update = task->result(); - QThreadPool *threadPool = QThreadPool::globalInstance(); - connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate); - threadPool->start(task); - return true; -} - -void ModFolderModel::finishUpdate() -{ - QSet<QString> currentSet = modsIndex.keys().toSet(); - auto & newMods = m_update->mods; - QSet<QString> newSet = newMods.keys().toSet(); - - // see if the kept mods changed in some way - { - QSet<QString> kept = currentSet; - kept.intersect(newSet); - for(auto & keptMod: kept) { - auto & newMod = newMods[keptMod]; - auto row = modsIndex[keptMod]; - auto & currentMod = mods[row]; - if(newMod.dateTimeChanged() == currentMod.dateTimeChanged()) { - // no significant change, ignore... - continue; - } - auto & oldMod = mods[row]; - if(oldMod.isResolving()) { - activeTickets.remove(oldMod.resolutionTicket()); - } - oldMod = newMod; - resolveMod(mods[row]); - emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); - } - } - - // remove mods no longer present - { - QSet<QString> removed = currentSet; - QList<int> removedRows; - removed.subtract(newSet); - for(auto & removedMod: removed) { - removedRows.append(modsIndex[removedMod]); - } - std::sort(removedRows.begin(), removedRows.end(), std::greater<int>()); - for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) { - int removedIndex = *iter; - beginRemoveRows(QModelIndex(), removedIndex, removedIndex); - auto removedIter = mods.begin() + removedIndex; - if(removedIter->isResolving()) { - activeTickets.remove(removedIter->resolutionTicket()); - } - mods.erase(removedIter); - endRemoveRows(); - } - } - - // add new mods to the end - { - QSet<QString> added = newSet; - added.subtract(currentSet); - beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1); - for(auto & addedMod: added) { - mods.append(newMods[addedMod]); - resolveMod(mods.last()); - } - endInsertRows(); - } - - // update index - { - modsIndex.clear(); - int idx = 0; - for(auto & mod: mods) { - modsIndex[mod.mmc_id()] = idx; - idx++; - } - } - - m_update.reset(); - - emit updateFinished(); - - if(scheduled_update) { - scheduled_update = false; - update(); - } -} - -void ModFolderModel::resolveMod(Mod& m) -{ - if(!m.shouldResolve()) { - return; - } - - auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename()); - auto result = task->result(); - result->id = m.mmc_id(); - activeTickets.insert(nextResolutionTicket, result); - m.setResolving(true, nextResolutionTicket); - nextResolutionTicket++; - QThreadPool *threadPool = QThreadPool::globalInstance(); - connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse); - threadPool->start(task); -} - -void ModFolderModel::finishModParse(int token) -{ - auto iter = activeTickets.find(token); - if(iter == activeTickets.end()) { - return; - } - auto result = *iter; - activeTickets.remove(token); - int row = modsIndex[result->id]; - auto & mod = mods[row]; - mod.finishResolvingWithDetails(result->details); - emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); -} - -void ModFolderModel::disableInteraction(bool disabled) -{ - if (interaction_disabled == disabled) { - return; - } - interaction_disabled = disabled; - if(size()) { - emit dataChanged(index(0), index(size() - 1)); - } -} - -void ModFolderModel::directoryChanged(QString path) -{ - update(); -} - -bool ModFolderModel::isValid() -{ - return m_dir.exists() && m_dir.isReadable(); -} - -// FIXME: this does not take disabled mod (with extra .disable extension) into account... -bool ModFolderModel::installMod(const QString &filename) -{ - if(interaction_disabled) { - return false; - } - - // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - auto originalPath = FS::NormalizePath(filename); - QFileInfo fileinfo(originalPath); - - if (!fileinfo.exists() || !fileinfo.isReadable()) - { - qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath; - return false; - } - qDebug() << "installing: " << fileinfo.absoluteFilePath(); - - Mod installedMod(fileinfo); - if (!installedMod.valid()) - { - qDebug() << originalPath << "is not a valid mod. Ignoring it."; - return false; - } - - auto type = installedMod.type(); - if (type == Mod::MOD_UNKNOWN) - { - qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it."; - return false; - } - - auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName())); - if(originalPath == newpath) - { - qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense..."; - return false; - } - - if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) - { - if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled"))) - { - if(!QFile::remove(newpath)) - { - // FIXME: report error in a user-visible way - qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; - return false; - } - qDebug() << newpath << "has been deleted."; - } - if (!QFile::copy(fileinfo.filePath(), newpath)) - { - qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; - // FIXME: report error in a user-visible way - return false; - } - FS::updateTimestamp(newpath); - installedMod.repath(newpath); - update(); - return true; - } - else if (type == Mod::MOD_FOLDER) - { - QString from = fileinfo.filePath(); - if(QFile::exists(newpath)) - { - qDebug() << "Ignoring folder " << from << ", it would merge with " << newpath; - return false; - } - - if (!FS::copy(from, newpath)()) - { - qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed."; - return false; - } - installedMod.repath(newpath); - update(); - return true; - } - return false; -} - -bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable) -{ - if(interaction_disabled) { - return false; - } - - if(indexes.isEmpty()) - return true; - - for (auto index: indexes) - { - if(index.column() != 0) { - continue; - } - setModStatus(index.row(), enable); - } - return true; -} - -bool ModFolderModel::deleteMods(const QModelIndexList& indexes) -{ - if(interaction_disabled) { - return false; - } - - if(indexes.isEmpty()) - return true; - - for (auto i: indexes) - { - Mod &m = mods[i.row()]; - m.destroy(); - } - return true; -} - -int ModFolderModel::columnCount(const QModelIndex &parent) const -{ - return NUM_COLUMNS; -} - -QVariant ModFolderModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= mods.size()) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - switch (column) - { - case NameColumn: - return mods[row].name(); - case VersionColumn: { - switch(mods[row].type()) { - case Mod::MOD_FOLDER: - return tr("Folder"); - case Mod::MOD_SINGLEFILE: - return tr("File"); - default: - break; - } - return mods[row].version(); - } - case DateColumn: - return mods[row].dateTimeChanged(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return mods[row].mmc_id(); - - case Qt::CheckStateRole: - switch (column) - { - case ActiveColumn: - return mods[row].enabled() ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } - default: - return QVariant(); - } -} - -bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return false; - } - - if (role == Qt::CheckStateRole) - { - return setModStatus(index.row(), Toggle); - } - return false; -} - -bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action) -{ - if(row < 0 || row >= mods.size()) { - return false; - } - - auto &mod = mods[row]; - bool desiredStatus; - switch(action) { - case Enable: - desiredStatus = true; - break; - case Disable: - desiredStatus = false; - break; - case Toggle: - default: - desiredStatus = !mod.enabled(); - break; - } - - if(desiredStatus == mod.enabled()) { - return true; - } - - // preserve the row, but change its ID - auto oldId = mod.mmc_id(); - if(!mod.enable(!mod.enabled())) { - return false; - } - auto newId = mod.mmc_id(); - if(modsIndex.contains(newId)) { - // NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled - // But is it necessary? - } - modsIndex.remove(oldId); - modsIndex[newId] = row; - emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); - return true; -} - -QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return QString(); - case NameColumn: - return tr("Name"); - case VersionColumn: - return tr("Version"); - case DateColumn: - return tr("Last changed"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case ActiveColumn: - return tr("Is the mod enabled?"); - case NameColumn: - return tr("The name of the mod."); - case VersionColumn: - return tr("The version of the mod."); - case DateColumn: - return tr("The date and time this mod was last changed (or added)."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); -} - -Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - auto flags = defaultFlags; - if(interaction_disabled) { - flags &= ~Qt::ItemIsDropEnabled; - } - else - { - flags |= Qt::ItemIsDropEnabled; - if(index.isValid()) { - flags |= Qt::ItemIsUserCheckable; - } - } - return flags; -} - -Qt::DropActions ModFolderModel::supportedDropActions() const -{ - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; -} - -QStringList ModFolderModel::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - return types; -} - -bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) -{ - if (action == Qt::IgnoreAction) - { - return true; - } - - // check if the action is supported - if (!data || !(action & supportedDropActions())) - { - return false; - } - - // files dropped from outside? - if (data->hasUrls()) - { - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - { - continue; - } - // TODO: implement not only copy, but also move - // FIXME: handle errors here - installMod(url.toLocalFile()); - } - return true; - } - return false; -} diff --git a/api/logic/minecraft/mod/ModFolderModel.h b/api/logic/minecraft/mod/ModFolderModel.h deleted file mode 100644 index b0a76121..00000000 --- a/api/logic/minecraft/mod/ModFolderModel.h +++ /dev/null @@ -1,149 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QList> -#include <QMap> -#include <QSet> -#include <QString> -#include <QDir> -#include <QAbstractListModel> - -#include "Mod.h" - -#include "multimc_logic_export.h" -#include "ModFolderLoadTask.h" -#include "LocalModParseTask.h" - -class LegacyInstance; -class BaseInstance; -class QFileSystemWatcher; - -/** - * A legacy mod list. - * Backed by a folder. - */ -class MULTIMC_LOGIC_EXPORT ModFolderModel : public QAbstractListModel -{ - Q_OBJECT -public: - enum Columns - { - ActiveColumn = 0, - NameColumn, - VersionColumn, - DateColumn, - NUM_COLUMNS - }; - enum ModStatusAction { - Disable, - Enable, - Toggle - }; - ModFolderModel(const QString &dir); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::DropActions supportedDropActions() const override; - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - QStringList mimeTypes() const override; - bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; - - virtual int rowCount(const QModelIndex &) const override - { - return size(); - } - - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - virtual int columnCount(const QModelIndex &parent) const override; - - size_t size() const - { - return mods.size(); - } - ; - bool empty() const - { - return size() == 0; - } - Mod &operator[](size_t index) - { - return mods[index]; - } - const Mod &at(size_t index) const - { - return mods.at(index); - } - - /// Reloads the mod list and returns true if the list changed. - bool update(); - - /** - * Adds the given mod to the list at the given index - if the list supports custom ordering - */ - bool installMod(const QString& filename); - - /// Deletes all the selected mods - bool deleteMods(const QModelIndexList &indexes); - - /// Enable or disable listed mods - bool setModStatus(const QModelIndexList &indexes, ModStatusAction action); - - void startWatching(); - void stopWatching(); - - bool isValid(); - - QDir dir() - { - return m_dir; - } - - const QList<Mod> & allMods() - { - return mods; - } - -public slots: - void disableInteraction(bool disabled); - -private -slots: - void directoryChanged(QString path); - void finishUpdate(); - void finishModParse(int token); - -signals: - void updateFinished(); - -private: - void resolveMod(Mod& m); - bool setModStatus(int index, ModStatusAction action); - -protected: - QFileSystemWatcher *m_watcher; - bool is_watching = false; - ModFolderLoadTask::ResultPtr m_update; - bool scheduled_update = false; - bool interaction_disabled = false; - QDir m_dir; - QMap<QString, int> modsIndex; - QMap<int, LocalModParseTask::ResultPtr> activeTickets; - int nextResolutionTicket = 0; - QList<Mod> mods; -}; diff --git a/api/logic/minecraft/mod/ModFolderModel_test.cpp b/api/logic/minecraft/mod/ModFolderModel_test.cpp deleted file mode 100644 index 76f16ed5..00000000 --- a/api/logic/minecraft/mod/ModFolderModel_test.cpp +++ /dev/null @@ -1,53 +0,0 @@ - -#include <QTest> -#include <QTemporaryDir> -#include "TestUtil.h" - -#include "FileSystem.h" -#include "minecraft/mod/ModFolderModel.h" - -class ModFolderModelTest : public QObject -{ - Q_OBJECT - -private -slots: - // test for GH-1178 - install a folder with files to a mod list - void test_1178() - { - // source - QString source = QFINDTESTDATA("data/test_folder"); - - // sanity check - QVERIFY(!source.endsWith('/')); - - auto verify = [](QString path) - { - QDir target_dir(FS::PathCombine(path, "test_folder")); - QVERIFY(target_dir.entryList().contains("pack.mcmeta")); - QVERIFY(target_dir.entryList().contains("assets")); - }; - - // 1. test with no trailing / - { - QString folder = source; - QTemporaryDir tempDir; - ModFolderModel m(tempDir.path()); - m.installMod(folder); - verify(tempDir.path()); - } - - // 2. test with trailing / - { - QString folder = source + '/'; - QTemporaryDir tempDir; - ModFolderModel m(tempDir.path()); - m.installMod(folder); - verify(tempDir.path()); - } - } -}; - -QTEST_GUILESS_MAIN(ModFolderModelTest) - -#include "ModFolderModel_test.moc" diff --git a/api/logic/minecraft/mod/ResourcePackFolderModel.cpp b/api/logic/minecraft/mod/ResourcePackFolderModel.cpp deleted file mode 100644 index f3d7f566..00000000 --- a/api/logic/minecraft/mod/ResourcePackFolderModel.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "ResourcePackFolderModel.h" - -ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) { -} - -QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::ToolTipRole) { - switch (section) { - case ActiveColumn: - return tr("Is the resource pack enabled?"); - case NameColumn: - return tr("The name of the resource pack."); - case VersionColumn: - return tr("The version of the resource pack."); - case DateColumn: - return tr("The date and time this resource pack was last changed (or added)."); - default: - return QVariant(); - } - } - - return ModFolderModel::headerData(section, orientation, role); -} diff --git a/api/logic/minecraft/mod/ResourcePackFolderModel.h b/api/logic/minecraft/mod/ResourcePackFolderModel.h deleted file mode 100644 index 47eb4bb2..00000000 --- a/api/logic/minecraft/mod/ResourcePackFolderModel.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "ModFolderModel.h" - -class MULTIMC_LOGIC_EXPORT ResourcePackFolderModel : public ModFolderModel -{ - Q_OBJECT - -public: - explicit ResourcePackFolderModel(const QString &dir); - - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; -}; diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.cpp b/api/logic/minecraft/mod/TexturePackFolderModel.cpp deleted file mode 100644 index d5956da1..00000000 --- a/api/logic/minecraft/mod/TexturePackFolderModel.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "TexturePackFolderModel.h" - -TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) { -} - -QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::ToolTipRole) { - switch (section) { - case ActiveColumn: - return tr("Is the texture pack enabled?"); - case NameColumn: - return tr("The name of the texture pack."); - case VersionColumn: - return tr("The version of the texture pack."); - case DateColumn: - return tr("The date and time this texture pack was last changed (or added)."); - default: - return QVariant(); - } - } - - return ModFolderModel::headerData(section, orientation, role); -} diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.h b/api/logic/minecraft/mod/TexturePackFolderModel.h deleted file mode 100644 index d773b17b..00000000 --- a/api/logic/minecraft/mod/TexturePackFolderModel.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "ModFolderModel.h" - -class MULTIMC_LOGIC_EXPORT TexturePackFolderModel : public ModFolderModel -{ - Q_OBJECT - -public: - explicit TexturePackFolderModel(const QString &dir); - - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; -}; diff --git a/api/logic/minecraft/services/SkinDelete.cpp b/api/logic/minecraft/services/SkinDelete.cpp deleted file mode 100644 index 34977257..00000000 --- a/api/logic/minecraft/services/SkinDelete.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "SkinDelete.h" -#include <QNetworkRequest> -#include <QHttpMultiPart> -#include <Env.h> - -SkinDelete::SkinDelete(QObject *parent, AuthSessionPtr session) - : Task(parent), m_session(session) -{ -} - -void SkinDelete::executeTask() -{ - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active")); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_session->access_token).toLocal8Bit()); - QNetworkReply *rep = ENV.qnam().deleteResource(request); - m_reply = std::shared_ptr<QNetworkReply>(rep); - - setStatus(tr("Deleting skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); -} - -void SkinDelete::downloadError(QNetworkReply::NetworkError error) -{ - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); -} - -void SkinDelete::downloadFinished() -{ - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); -} - diff --git a/api/logic/minecraft/services/SkinDelete.h b/api/logic/minecraft/services/SkinDelete.h deleted file mode 100644 index 705ce8ef..00000000 --- a/api/logic/minecraft/services/SkinDelete.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include <QFile> -#include <QtNetwork/QtNetwork> -#include <memory> -#include <minecraft/auth/AuthSession.h> -#include "tasks/Task.h" -#include "multimc_logic_export.h" - -typedef std::shared_ptr<class SkinDelete> SkinDeletePtr; - -class MULTIMC_LOGIC_EXPORT SkinDelete : public Task -{ - Q_OBJECT -public: - SkinDelete(QObject *parent, AuthSessionPtr session); - virtual ~SkinDelete() = default; - -private: - AuthSessionPtr m_session; - std::shared_ptr<QNetworkReply> m_reply; - -protected: - virtual void executeTask(); - -public slots: - void downloadError(QNetworkReply::NetworkError); - void downloadFinished(); -}; - diff --git a/api/logic/minecraft/services/SkinUpload.cpp b/api/logic/minecraft/services/SkinUpload.cpp deleted file mode 100644 index 4e5a1698..00000000 --- a/api/logic/minecraft/services/SkinUpload.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "SkinUpload.h" -#include <QNetworkRequest> -#include <QHttpMultiPart> -#include <Env.h> - -QByteArray getVariant(SkinUpload::Model model) { - switch (model) { - default: - qDebug() << "Unknown skin type!"; - case SkinUpload::STEVE: - return "CLASSIC"; - case SkinUpload::ALEX: - return "SLIM"; - } -} - -SkinUpload::SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, SkinUpload::Model model) - : Task(parent), m_model(model), m_skin(skin), m_session(session) -{ -} - -void SkinUpload::executeTask() -{ - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins")); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_session->access_token).toLocal8Bit()); - QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - - QHttpPart skin; - skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); - skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); - skin.setBody(m_skin); - - QHttpPart model; - model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\"")); - model.setBody(getVariant(m_model)); - - multiPart->append(skin); - multiPart->append(model); - - QNetworkReply *rep = ENV.qnam().post(request, multiPart); - m_reply = std::shared_ptr<QNetworkReply>(rep); - - setStatus(tr("Uploading skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); -} - -void SkinUpload::downloadError(QNetworkReply::NetworkError error) -{ - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); -} - -void SkinUpload::downloadFinished() -{ - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); -} diff --git a/api/logic/minecraft/services/SkinUpload.h b/api/logic/minecraft/services/SkinUpload.h deleted file mode 100644 index c77abb03..00000000 --- a/api/logic/minecraft/services/SkinUpload.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include <QFile> -#include <QtNetwork/QtNetwork> -#include <memory> -#include <minecraft/auth/AuthSession.h> -#include "tasks/Task.h" -#include "multimc_logic_export.h" - -typedef std::shared_ptr<class SkinUpload> SkinUploadPtr; - -class MULTIMC_LOGIC_EXPORT SkinUpload : public Task -{ - Q_OBJECT -public: - enum Model - { - STEVE, - ALEX - }; - - // Note this class takes ownership of the file. - SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, Model model = STEVE); - virtual ~SkinUpload() {} - -private: - Model m_model; - QByteArray m_skin; - AuthSessionPtr m_session; - std::shared_ptr<QNetworkReply> m_reply; -protected: - virtual void executeTask(); - -public slots: - - void downloadError(QNetworkReply::NetworkError); - - void downloadFinished(); -}; diff --git a/api/logic/minecraft/testdata/1.9-simple.json b/api/logic/minecraft/testdata/1.9-simple.json deleted file mode 100644 index 574c5b06..00000000 --- a/api/logic/minecraft/testdata/1.9-simple.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "assets": "1.9", - "id": "1.9", - "libraries": [ - { - "name": "oshi-project:oshi-core:1.1" - }, - { - "name": "net.java.dev.jna:jna:3.4.0" - }, - { - "name": "net.java.dev.jna:platform:3.4.0" - }, - { - "name": "com.ibm.icu:icu4j-core-mojang:51.2" - }, - { - "name": "net.sf.jopt-simple:jopt-simple:4.6" - }, - { - "name": "com.paulscode:codecjorbis:20101023" - }, - { - "name": "com.paulscode:codecwav:20101023" - }, - { - "name": "com.paulscode:libraryjavasound:20101123" - }, - { - "name": "com.paulscode:librarylwjglopenal:20100824" - }, - { - "name": "com.paulscode:soundsystem:20120107" - }, - { - "name": "io.netty:netty-all:4.0.23.Final" - }, - { - "name": "com.google.guava:guava:17.0" - }, - { - "name": "org.apache.commons:commons-lang3:3.3.2" - }, - { - "name": "commons-io:commons-io:2.4" - }, - { - "name": "commons-codec:commons-codec:1.9" - }, - { - "name": "net.java.jinput:jinput:2.0.5" - }, - { - "name": "net.java.jutils:jutils:1.0.0" - }, - { - "name": "com.google.code.gson:gson:2.2.4" - }, - { - "name": "com.mojang:authlib:1.5.22" - }, - { - "name": "com.mojang:realms:1.8.4" - }, - { - "name": "org.apache.commons:commons-compress:1.8.1" - }, - { - "name": "org.apache.httpcomponents:httpclient:4.3.3" - }, - { - "name": "commons-logging:commons-logging:1.1.3" - }, - { - "name": "org.apache.httpcomponents:httpcore:4.3.2" - }, - { - "name": "org.apache.logging.log4j:log4j-api:2.0-beta9" - }, - { - "name": "org.apache.logging.log4j:log4j-core:2.0-beta9" - }, - { - "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209", - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209", - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "name": "org.lwjgl.lwjgl:lwjgl:2.9.2-nightly-20140822", - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.2-nightly-20140822", - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.2-nightly-20140822", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "net.java.jinput:jinput-platform:2.0.5", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - } - } - ], - "mainClass": "net.minecraft.client.main.Main", - "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userType ${user_type} --versionType ${version_type}", - "minimumLauncherVersion": 18, - "releaseTime": "2016-02-29T13:49:54+00:00", - "time": "2016-03-01T13:14:53+00:00", - "type": "release" -} diff --git a/api/logic/minecraft/testdata/1.9.json b/api/logic/minecraft/testdata/1.9.json deleted file mode 100644 index 697c6059..00000000 --- a/api/logic/minecraft/testdata/1.9.json +++ /dev/null @@ -1,529 +0,0 @@ -{ - "assetIndex": { - "id": "1.9", - "sha1": "cde65b47a43f638653ab1da3848b53f8a7477b16", - "size": 136916, - "totalSize": 119917473, - "url": "https://launchermeta.mojang.com/mc-staging/assets/1.9/cde65b47a43f638653ab1da3848b53f8a7477b16/1.9.json" - }, - "assets": "1.9", - "downloads": { - "client": { - "sha1": "2f67dfe8953299440d1902f9124f0f2c3a2c940f", - "size": 8697592, - "url": "https://launcher.mojang.com/mc/game/1.9/client/2f67dfe8953299440d1902f9124f0f2c3a2c940f/client.jar" - }, - "server": { - "sha1": "b4d449cf2918e0f3bd8aa18954b916a4d1880f0d", - "size": 8848015, - "url": "https://launcher.mojang.com/mc/game/1.9/server/b4d449cf2918e0f3bd8aa18954b916a4d1880f0d/server.jar" - } - }, - "id": "1.9", - "libraries": [ - { - "downloads": { - "artifact": { - "path": "oshi-project/oshi-core/1.1/oshi-core-1.1.jar", - "sha1": "9ddf7b048a8d701be231c0f4f95fd986198fd2d8", - "size": 30973, - "url": "https://libraries.minecraft.net/oshi-project/oshi-core/1.1/oshi-core-1.1.jar" - } - }, - "name": "oshi-project:oshi-core:1.1" - }, - { - "downloads": { - "artifact": { - "path": "net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar", - "sha1": "803ff252fedbd395baffd43b37341dc4a150a554", - "size": 1008730, - "url": "https://libraries.minecraft.net/net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar" - } - }, - "name": "net.java.dev.jna:jna:3.4.0" - }, - { - "downloads": { - "artifact": { - "path": "net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar", - "sha1": "e3f70017be8100d3d6923f50b3d2ee17714e9c13", - "size": 913436, - "url": "https://libraries.minecraft.net/net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar" - } - }, - "name": "net.java.dev.jna:platform:3.4.0" - }, - { - "downloads": { - "artifact": { - "path": "com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar", - "sha1": "63d216a9311cca6be337c1e458e587f99d382b84", - "size": 1634692, - "url": "https://libraries.minecraft.net/com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar" - } - }, - "name": "com.ibm.icu:icu4j-core-mojang:51.2" - }, - { - "downloads": { - "artifact": { - "path": "net/sf/jopt-simple/jopt-simple/4.6/jopt-simple-4.6.jar", - "sha1": "306816fb57cf94f108a43c95731b08934dcae15c", - "size": 62477, - "url": "https://libraries.minecraft.net/net/sf/jopt-simple/jopt-simple/4.6/jopt-simple-4.6.jar" - } - }, - "name": "net.sf.jopt-simple:jopt-simple:4.6" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar", - "sha1": "c73b5636faf089d9f00e8732a829577de25237ee", - "size": 103871, - "url": "https://libraries.minecraft.net/com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar" - } - }, - "name": "com.paulscode:codecjorbis:20101023" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar", - "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da", - "size": 5618, - "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar" - } - }, - "name": "com.paulscode:codecwav:20101023" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar", - "sha1": "5c5e304366f75f9eaa2e8cca546a1fb6109348b3", - "size": 21679, - "url": "https://libraries.minecraft.net/com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar" - } - }, - "name": "com.paulscode:libraryjavasound:20101123" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar", - "sha1": "73e80d0794c39665aec3f62eee88ca91676674ef", - "size": 18981, - "url": "https://libraries.minecraft.net/com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar" - } - }, - "name": "com.paulscode:librarylwjglopenal:20100824" - }, - { - "downloads": { - "artifact": { - "path": "com/paulscode/soundsystem/20120107/soundsystem-20120107.jar", - "sha1": "419c05fe9be71f792b2d76cfc9b67f1ed0fec7f6", - "size": 65020, - "url": "https://libraries.minecraft.net/com/paulscode/soundsystem/20120107/soundsystem-20120107.jar" - } - }, - "name": "com.paulscode:soundsystem:20120107" - }, - { - "downloads": { - "artifact": { - "path": "io/netty/netty-all/4.0.23.Final/netty-all-4.0.23.Final.jar", - "sha1": "0294104aaf1781d6a56a07d561e792c5d0c95f45", - "size": 1779991, - "url": "https://libraries.minecraft.net/io/netty/netty-all/4.0.23.Final/netty-all-4.0.23.Final.jar" - } - }, - "name": "io.netty:netty-all:4.0.23.Final" - }, - { - "downloads": { - "artifact": { - "path": "com/google/guava/guava/17.0/guava-17.0.jar", - "sha1": "9c6ef172e8de35fd8d4d8783e4821e57cdef7445", - "size": 2243036, - "url": "https://libraries.minecraft.net/com/google/guava/guava/17.0/guava-17.0.jar" - } - }, - "name": "com.google.guava:guava:17.0" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/commons/commons-lang3/3.3.2/commons-lang3-3.3.2.jar", - "sha1": "90a3822c38ec8c996e84c16a3477ef632cbc87a3", - "size": 412739, - "url": "https://libraries.minecraft.net/org/apache/commons/commons-lang3/3.3.2/commons-lang3-3.3.2.jar" - } - }, - "name": "org.apache.commons:commons-lang3:3.3.2" - }, - { - "downloads": { - "artifact": { - "path": "commons-io/commons-io/2.4/commons-io-2.4.jar", - "sha1": "b1b6ea3b7e4aa4f492509a4952029cd8e48019ad", - "size": 185140, - "url": "https://libraries.minecraft.net/commons-io/commons-io/2.4/commons-io-2.4.jar" - } - }, - "name": "commons-io:commons-io:2.4" - }, - { - "downloads": { - "artifact": { - "path": "commons-codec/commons-codec/1.9/commons-codec-1.9.jar", - "sha1": "9ce04e34240f674bc72680f8b843b1457383161a", - "size": 263965, - "url": "https://libraries.minecraft.net/commons-codec/commons-codec/1.9/commons-codec-1.9.jar" - } - }, - "name": "commons-codec:commons-codec:1.9" - }, - { - "downloads": { - "artifact": { - "path": "net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar", - "sha1": "39c7796b469a600f72380316f6b1f11db6c2c7c4", - "size": 208338, - "url": "https://libraries.minecraft.net/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar" - } - }, - "name": "net.java.jinput:jinput:2.0.5" - }, - { - "downloads": { - "artifact": { - "path": "net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar", - "sha1": "e12fe1fda814bd348c1579329c86943d2cd3c6a6", - "size": 7508, - "url": "https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar" - } - }, - "name": "net.java.jutils:jutils:1.0.0" - }, - { - "downloads": { - "artifact": { - "path": "com/google/code/gson/gson/2.2.4/gson-2.2.4.jar", - "sha1": "a60a5e993c98c864010053cb901b7eab25306568", - "size": 190432, - "url": "https://libraries.minecraft.net/com/google/code/gson/gson/2.2.4/gson-2.2.4.jar" - } - }, - "name": "com.google.code.gson:gson:2.2.4" - }, - { - "downloads": { - "artifact": { - "path": "com/mojang/authlib/1.5.22/authlib-1.5.22.jar", - "sha1": "afaa8f6df976fcb5520e76ef1d5798c9e6b5c0b2", - "size": 64539, - "url": "https://libraries.minecraft.net/com/mojang/authlib/1.5.22/authlib-1.5.22.jar" - } - }, - "name": "com.mojang:authlib:1.5.22" - }, - { - "downloads": { - "artifact": { - "path": "com/mojang/realms/1.8.4/realms-1.8.4.jar", - "sha1": "15f8dc326c97a96dee6e65392e145ad6d1cb46cb", - "size": 1131574, - "url": "https://libraries.minecraft.net/com/mojang/realms/1.8.4/realms-1.8.4.jar" - } - }, - "name": "com.mojang:realms:1.8.4" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar", - "sha1": "a698750c16740fd5b3871425f4cb3bbaa87f529d", - "size": 365552, - "url": "https://libraries.minecraft.net/org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar" - } - }, - "name": "org.apache.commons:commons-compress:1.8.1" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar", - "sha1": "18f4247ff4572a074444572cee34647c43e7c9c7", - "size": 589512, - "url": "https://libraries.minecraft.net/org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar" - } - }, - "name": "org.apache.httpcomponents:httpclient:4.3.3" - }, - { - "downloads": { - "artifact": { - "path": "commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar", - "sha1": "f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f", - "size": 62050, - "url": "https://libraries.minecraft.net/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar" - } - }, - "name": "commons-logging:commons-logging:1.1.3" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar", - "sha1": "31fbbff1ddbf98f3aa7377c94d33b0447c646b6e", - "size": 282269, - "url": "https://libraries.minecraft.net/org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar" - } - }, - "name": "org.apache.httpcomponents:httpcore:4.3.2" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar", - "sha1": "1dd66e68cccd907880229f9e2de1314bd13ff785", - "size": 108161, - "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar" - } - }, - "name": "org.apache.logging.log4j:log4j-api:2.0-beta9" - }, - { - "downloads": { - "artifact": { - "path": "org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar", - "sha1": "678861ba1b2e1fccb594bb0ca03114bb05da9695", - "size": 681134, - "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar" - } - }, - "name": "org.apache.logging.log4j:log4j-core:2.0-beta9" - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar", - "sha1": "697517568c68e78ae0b4544145af031c81082dfe", - "size": 1047168, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar" - } - }, - "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209", - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar", - "sha1": "d51a7c040a721d13efdfbd34f8b257b2df882ad0", - "size": 173887, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar" - } - }, - "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209", - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", - "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", - "size": 22, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" - }, - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", - "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", - "size": 578680, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", - "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", - "size": 426822, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", - "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", - "size": 613748, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl/2.9.2-nightly-20140822/lwjgl-2.9.2-nightly-20140822.jar", - "sha1": "7707204c9ffa5d91662de95f0a224e2f721b22af", - "size": 1045632, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.2-nightly-20140822/lwjgl-2.9.2-nightly-20140822.jar" - } - }, - "name": "org.lwjgl.lwjgl:lwjgl:2.9.2-nightly-20140822", - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl_util/2.9.2-nightly-20140822/lwjgl_util-2.9.2-nightly-20140822.jar", - "sha1": "f0e612c840a7639c1f77f68d72a28dae2f0c8490", - "size": 173887, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.2-nightly-20140822/lwjgl_util-2.9.2-nightly-20140822.jar" - } - }, - "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.2-nightly-20140822", - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-linux.jar", - "sha1": "d898a33b5d0a6ef3fed3a4ead506566dce6720a5", - "size": 578539, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-osx.jar", - "sha1": "79f5ce2fea02e77fe47a3c745219167a542121d7", - "size": 468116, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-windows.jar", - "sha1": "78b2a55ce4dc29c6b3ec4df8ca165eba05f9b341", - "size": 613680, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.2-nightly-20140822", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow", - "os": { - "name": "osx" - } - } - ] - }, - { - "downloads": { - "classifiers": { - "natives-linux": { - "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar", - "sha1": "7ff832a6eb9ab6a767f1ade2b548092d0fa64795", - "size": 10362, - "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar" - }, - "natives-osx": { - "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar", - "sha1": "53f9c919f34d2ca9de8c51fc4e1e8282029a9232", - "size": 12186, - "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar" - }, - "natives-windows": { - "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-windows.jar", - "sha1": "385ee093e01f587f30ee1c8a2ee7d408fd732e16", - "size": 155179, - "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "net.java.jinput:jinput-platform:2.0.5", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - } - } - ], - "mainClass": "net.minecraft.client.main.Main", - "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userType ${user_type} --versionType ${version_type}", - "minimumLauncherVersion": 18, - "releaseTime": "2016-02-29T13:49:54+00:00", - "time": "2016-03-01T13:14:53+00:00", - "type": "release" -} diff --git a/api/logic/minecraft/testdata/codecwav-20101023.jar b/api/logic/minecraft/testdata/codecwav-20101023.jar deleted file mode 100644 index f5236083..00000000 --- a/api/logic/minecraft/testdata/codecwav-20101023.jar +++ /dev/null @@ -1 +0,0 @@ -dummy test file. diff --git a/api/logic/minecraft/testdata/lib-native-arch.json b/api/logic/minecraft/testdata/lib-native-arch.json deleted file mode 100644 index 501826ae..00000000 --- a/api/logic/minecraft/testdata/lib-native-arch.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "downloads": { - "classifiers": { - "natives-osx": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar", - "sha1": "62503ee712766cf77f97252e5902786fd834b8c5", - "size": 418331, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar" - }, - "natives-windows-32": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", - "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23", - "size": 386792, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar" - }, - "natives-windows-64": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar", - "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9", - "size": 463390, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "tv.twitch:twitch-platform:5.16", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows-${arch}" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "linux" - } - } - ] -} diff --git a/api/logic/minecraft/testdata/lib-native.json b/api/logic/minecraft/testdata/lib-native.json deleted file mode 100644 index 5b9f3b55..00000000 --- a/api/logic/minecraft/testdata/lib-native.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", - "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", - "size": 22, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" - }, - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", - "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", - "size": 578680, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", - "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", - "size": 426822, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", - "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", - "size": 613748, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] -} diff --git a/api/logic/minecraft/testdata/lib-simple.json b/api/logic/minecraft/testdata/lib-simple.json deleted file mode 100644 index 90bbff07..00000000 --- a/api/logic/minecraft/testdata/lib-simple.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "downloads": { - "artifact": { - "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar", - "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da", - "size": 5618, - "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar" - } - }, - "name": "com.paulscode:codecwav:20101023" -} diff --git a/api/logic/minecraft/testdata/testname-testversion-linux-32.jar b/api/logic/minecraft/testdata/testname-testversion-linux-32.jar deleted file mode 100644 index f5236083..00000000 --- a/api/logic/minecraft/testdata/testname-testversion-linux-32.jar +++ /dev/null @@ -1 +0,0 @@ -dummy test file. diff --git a/api/logic/minecraft/update/AssetUpdateTask.cpp b/api/logic/minecraft/update/AssetUpdateTask.cpp deleted file mode 100644 index e26ab4ef..00000000 --- a/api/logic/minecraft/update/AssetUpdateTask.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "Env.h" -#include "AssetUpdateTask.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "net/ChecksumValidator.h" -#include "minecraft/AssetsUtils.h" - -AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst) -{ - m_inst = inst; -} - -AssetUpdateTask::~AssetUpdateTask() -{ -} - -void AssetUpdateTask::executeTask() -{ - setStatus(tr("Updating assets index...")); - auto components = m_inst->getPackProfile(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); - QUrl indexUrl = assets->url; - QString localPath = assets->id + ".json"; - auto job = new NetJob(tr("Asset index for %1").arg(m_inst->name())); - - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", localPath); - entry->setStale(true); - auto hexSha1 = assets->sha1.toLatin1(); - qDebug() << "Asset index SHA1:" << hexSha1; - auto dl = Net::Download::makeCached(indexUrl, entry); - auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - job->addNetAction(dl); - - downloadJob.reset(job); - - connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished); - connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); - connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); - - qDebug() << m_inst->name() << ": Starting asset index download"; - downloadJob->start(); -} - -bool AssetUpdateTask::canAbort() const -{ - return true; -} - -void AssetUpdateTask::assetIndexFinished() -{ - AssetsIndex index; - qDebug() << m_inst->name() << ": Finished asset index download"; - - auto components = m_inst->getPackProfile(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); - - QString asset_fname = "assets/indexes/" + assets->id + ".json"; - // FIXME: this looks like a job for a generic validator based on json schema? - if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, index)) - { - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); - metacache->evictEntry(entry); - emitFailed(tr("Failed to read the assets index!")); - } - - auto job = index.getDownloadJob(); - if(job) - { - setStatus(tr("Getting the assets files from Mojang...")); - downloadJob = job; - connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); - connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); - connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); - downloadJob->start(); - return; - } - emitSucceeded(); -} - -void AssetUpdateTask::assetIndexFailed(QString reason) -{ - qDebug() << m_inst->name() << ": Failed asset index download"; - emitFailed(tr("Failed to download the assets index:\n%1").arg(reason)); -} - -void AssetUpdateTask::assetsFailed(QString reason) -{ - emitFailed(tr("Failed to download assets:\n%1").arg(reason)); -} - -bool AssetUpdateTask::abort() -{ - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted AssetUpdateTask"; - } - return true; -} diff --git a/api/logic/minecraft/update/AssetUpdateTask.h b/api/logic/minecraft/update/AssetUpdateTask.h deleted file mode 100644 index fdfa8f1c..00000000 --- a/api/logic/minecraft/update/AssetUpdateTask.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -class MinecraftInstance; - -class AssetUpdateTask : public Task -{ - Q_OBJECT -public: - AssetUpdateTask(MinecraftInstance * inst); - virtual ~AssetUpdateTask(); - - void executeTask() override; - - bool canAbort() const override; - -private slots: - void assetIndexFinished(); - void assetIndexFailed(QString reason); - void assetsFailed(QString reason); - -public slots: - bool abort() override; - -private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; -}; diff --git a/api/logic/minecraft/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp deleted file mode 100644 index a05a7c2a..00000000 --- a/api/logic/minecraft/update/FMLLibrariesTask.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "Env.h" -#include <FileSystem.h> -#include <minecraft/VersionFilterData.h> -#include "FMLLibrariesTask.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "BuildConfig.h" - -FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst) -{ - m_inst = inst; -} -void FMLLibrariesTask::executeTask() -{ - // Get the mod list - MinecraftInstance *inst = (MinecraftInstance *)m_inst; - auto components = inst->getPackProfile(); - auto profile = components->getProfile(); - - if (!profile->hasTrait("legacyFML")) - { - emitSucceeded(); - return; - } - - QString version = components->getComponentVersion("net.minecraft"); - auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; - if (!fmlLibsMapping.contains(version)) - { - emitSucceeded(); - return; - } - - auto &libList = fmlLibsMapping[version]; - - // determine if we need some libs for FML or forge - setStatus(tr("Checking for FML libraries...")); - if(!components->getComponent("net.minecraftforge")) - { - emitSucceeded(); - return; - } - - // now check the lib folder inside the instance for files. - for (auto &lib : libList) - { - QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); - if (libInfo.exists()) - continue; - fmlLibsToProcess.append(lib); - } - - // if everything is in place, there's nothing to do here... - if (fmlLibsToProcess.isEmpty()) - { - emitSucceeded(); - return; - } - - // download missing libs to our place - setStatus(tr("Dowloading FML libraries...")); - auto dljob = new NetJob("FML libraries"); - auto metacache = ENV.metacache(); - for (auto &lib : fmlLibsToProcess) - { - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename; - dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); - } - - connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); - connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); - connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); - downloadJob.reset(dljob); - downloadJob->start(); -} - -bool FMLLibrariesTask::canAbort() const -{ - return true; -} - -void FMLLibrariesTask::fmllibsFinished() -{ - downloadJob.reset(); - if (!fmlLibsToProcess.isEmpty()) - { - setStatus(tr("Copying FML libraries into the instance...")); - MinecraftInstance *inst = (MinecraftInstance *)m_inst; - auto metacache = ENV.metacache(); - int index = 0; - for (auto &lib : fmlLibsToProcess) - { - progress(index, fmlLibsToProcess.size()); - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - auto path = FS::PathCombine(inst->libDir(), lib.filename); - if (!FS::ensureFilePathExists(path)) - { - emitFailed(tr("Failed creating FML library folder inside the instance.")); - return; - } - if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) - { - emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); - return; - } - index++; - } - progress(index, fmlLibsToProcess.size()); - } - emitSucceeded(); -} -void FMLLibrariesTask::fmllibsFailed(QString reason) -{ - QStringList failed = downloadJob->getFailedFiles(); - QString failed_all = failed.join("\n"); - emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); -} - -bool FMLLibrariesTask::abort() -{ - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted FMLLibrariesTask"; - } - return true; -} diff --git a/api/logic/minecraft/update/FMLLibrariesTask.h b/api/logic/minecraft/update/FMLLibrariesTask.h deleted file mode 100644 index a1e70ed4..00000000 --- a/api/logic/minecraft/update/FMLLibrariesTask.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -#include "minecraft/VersionFilterData.h" - -class MinecraftInstance; - -class FMLLibrariesTask : public Task -{ - Q_OBJECT -public: - FMLLibrariesTask(MinecraftInstance * inst); - virtual ~FMLLibrariesTask() {}; - - void executeTask() override; - - bool canAbort() const override; - -private slots: - void fmllibsFinished(); - void fmllibsFailed(QString reason); - -public slots: - bool abort() override; - -private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; - QList<FMLlib> fmlLibsToProcess; -}; - diff --git a/api/logic/minecraft/update/FoldersTask.cpp b/api/logic/minecraft/update/FoldersTask.cpp deleted file mode 100644 index e2b1bb48..00000000 --- a/api/logic/minecraft/update/FoldersTask.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "FoldersTask.h" -#include "minecraft/MinecraftInstance.h" -#include <QDir> - -FoldersTask::FoldersTask(MinecraftInstance * inst) - :Task() -{ - m_inst = inst; -} - -void FoldersTask::executeTask() -{ - // Make directories - QDir mcDir(m_inst->gameRoot()); - if (!mcDir.exists() && !mcDir.mkpath(".")) - { - emitFailed(tr("Failed to create folder for minecraft binaries.")); - return; - } - emitSucceeded(); -} diff --git a/api/logic/minecraft/update/FoldersTask.h b/api/logic/minecraft/update/FoldersTask.h deleted file mode 100644 index f6ed5e6d..00000000 --- a/api/logic/minecraft/update/FoldersTask.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "tasks/Task.h" - -class MinecraftInstance; -class FoldersTask : public Task -{ - Q_OBJECT -public: - FoldersTask(MinecraftInstance * inst); - virtual ~FoldersTask() {}; - - void executeTask() override; -private: - MinecraftInstance *m_inst; -}; - diff --git a/api/logic/minecraft/update/LibrariesTask.cpp b/api/logic/minecraft/update/LibrariesTask.cpp deleted file mode 100644 index 7f66a651..00000000 --- a/api/logic/minecraft/update/LibrariesTask.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "Env.h" -#include "LibrariesTask.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" - -LibrariesTask::LibrariesTask(MinecraftInstance * inst) -{ - m_inst = inst; -} - -void LibrariesTask::executeTask() -{ - setStatus(tr("Getting the library files from Mojang...")); - qDebug() << m_inst->name() << ": downloading libraries"; - MinecraftInstance *inst = (MinecraftInstance *)m_inst; - - // Build a list of URLs that will need to be downloaded. - auto components = inst->getPackProfile(); - auto profile = components->getProfile(); - - auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); - downloadJob.reset(job); - - auto metacache = ENV.metacache(); - - auto processArtifactPool = [&](const QList<LibraryPtr> & pool, QStringList & errors, const QString & localPath) - { - for (auto lib : pool) - { - if(!lib) - { - emitFailed(tr("Null jar is specified in the metadata, aborting.")); - return false; - } - auto dls = lib->getDownloads(currentSystem, metacache.get(), errors, localPath); - for(auto dl : dls) - { - downloadJob->addNetAction(dl); - } - } - return true; - }; - - QStringList failedLocalLibraries; - QList<LibraryPtr> libArtifactPool; - libArtifactPool.append(profile->getLibraries()); - libArtifactPool.append(profile->getNativeLibraries()); - libArtifactPool.append(profile->getMavenFiles()); - libArtifactPool.append(profile->getMainJar()); - processArtifactPool(libArtifactPool, failedLocalLibraries, inst->getLocalLibraryPath()); - - QStringList failedLocalJarMods; - processArtifactPool(profile->getJarMods(), failedLocalJarMods, inst->jarModsDir()); - - if (!failedLocalJarMods.empty() || !failedLocalLibraries.empty()) - { - downloadJob.reset(); - QString failed_all = (failedLocalLibraries + failedLocalJarMods).join("\n"); - emitFailed(tr("Some artifacts marked as 'local' are missing their files:\n%1\n\nYou need to either add the files, or removed the packages that require them.\nYou'll have to correct this problem manually.").arg(failed_all)); - return; - } - - connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded); - connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); - connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); - downloadJob->start(); -} - -bool LibrariesTask::canAbort() const -{ - return true; -} - -void LibrariesTask::jarlibFailed(QString reason) -{ - emitFailed(tr("Game update failed: it was impossible to fetch the required libraries.\nReason:\n%1").arg(reason)); -} - -bool LibrariesTask::abort() -{ - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted LibrariesTask"; - } - return true; -} diff --git a/api/logic/minecraft/update/LibrariesTask.h b/api/logic/minecraft/update/LibrariesTask.h deleted file mode 100644 index 49f76932..00000000 --- a/api/logic/minecraft/update/LibrariesTask.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -class MinecraftInstance; - -class LibrariesTask : public Task -{ - Q_OBJECT -public: - LibrariesTask(MinecraftInstance * inst); - virtual ~LibrariesTask() {}; - - void executeTask() override; - - bool canAbort() const override; - -private slots: - void jarlibFailed(QString reason); - -public slots: - bool abort() override; - -private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; -}; diff --git a/api/logic/modplatform/atlauncher/ATLPackIndex.cpp b/api/logic/modplatform/atlauncher/ATLPackIndex.cpp deleted file mode 100644 index 35f50b18..00000000 --- a/api/logic/modplatform/atlauncher/ATLPackIndex.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "ATLPackIndex.h" - -#include <QRegularExpression> - -#include "Json.h" - -static void loadIndexedVersion(ATLauncher::IndexedVersion & v, QJsonObject & obj) -{ - v.version = Json::requireString(obj, "version"); - v.minecraft = Json::requireString(obj, "minecraft"); -} - -void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack & m, QJsonObject & obj) -{ - m.id = Json::requireInteger(obj, "id"); - m.position = Json::requireInteger(obj, "position"); - m.name = Json::requireString(obj, "name"); - m.type = Json::requireString(obj, "type") == "private" ? - ATLauncher::PackType::Private : - ATLauncher::PackType::Public; - auto versionsArr = Json::requireArray(obj, "versions"); - for (const auto versionRaw : versionsArr) - { - auto versionObj = Json::requireObject(versionRaw); - ATLauncher::IndexedVersion version; - loadIndexedVersion(version, versionObj); - m.versions.append(version); - } - m.system = Json::ensureBoolean(obj, QString("system"), false); - m.description = Json::ensureString(obj, "description", ""); - - m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), ""); -} diff --git a/api/logic/modplatform/atlauncher/ATLPackIndex.h b/api/logic/modplatform/atlauncher/ATLPackIndex.h deleted file mode 100644 index 5e2e6487..00000000 --- a/api/logic/modplatform/atlauncher/ATLPackIndex.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "ATLPackManifest.h" - -#include <QString> -#include <QVector> -#include <QMetaType> - -#include "multimc_logic_export.h" - -namespace ATLauncher -{ - -struct IndexedVersion -{ - QString version; - QString minecraft; -}; - -struct IndexedPack -{ - int id; - int position; - QString name; - PackType type; - QVector<IndexedVersion> versions; - bool system; - QString description; - - QString safeName; -}; - -MULTIMC_LOGIC_EXPORT void loadIndexedPack(IndexedPack & m, QJsonObject & obj); -} - -Q_DECLARE_METATYPE(ATLauncher::IndexedPack) diff --git a/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp b/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp deleted file mode 100644 index 55087a27..00000000 --- a/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp +++ /dev/null @@ -1,764 +0,0 @@ -#include <Env.h> -#include <quazip.h> -#include <QtConcurrent/QtConcurrent> -#include <MMCZip.h> -#include <minecraft/OneSixVersionFormat.h> -#include <Version.h> -#include <net/ChecksumValidator.h> -#include "ATLPackInstallTask.h" - -#include "BuildConfig.h" -#include "FileSystem.h" -#include "Json.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "settings/INISettingsObject.h" -#include "meta/Index.h" -#include "meta/Version.h" -#include "meta/VersionList.h" - -namespace ATLauncher { - -PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version) -{ - m_support = support; - m_pack = pack; - m_version_name = version; -} - -bool PackInstallTask::abort() -{ - if(abortable) - { - return jobPtr->abort(); - } - return false; -} - -void PackInstallTask::executeTask() -{ - qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId(); - auto *netJob = new NetJob("ATLauncher::VersionFetch"); - auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json") - .arg(m_pack).arg(m_version_name); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); - jobPtr = netJob; - jobPtr->start(); - - QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); - QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); -} - -void PackInstallTask::onDownloadSucceeded() -{ - qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId(); - 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 FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; - return; - } - - auto obj = doc.object(); - - ATLauncher::PackVersion version; - try - { - ATLauncher::loadVersion(version, obj); - } - catch (const JSONValidationError &e) - { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - m_version = version; - - auto vlist = ENV.metadataIndex()->get("net.minecraft"); - if(!vlist) - { - emitFailed(tr("Failed to get local metadata index for %1").arg("net.minecraft")); - return; - } - - auto ver = vlist->getVersion(m_version.minecraft); - if (!ver) { - emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft").arg(m_version.minecraft)); - return; - } - ver->load(Net::Mode::Online); - minecraftVersion = ver; - - if(m_version.noConfigs) { - downloadMods(); - } - else { - installConfigs(); - } -} - -void PackInstallTask::onDownloadFailed(QString reason) -{ - qDebug() << "PackInstallTask::onDownloadFailed: " << QThread::currentThreadId(); - jobPtr.reset(); - emitFailed(reason); -} - -QString PackInstallTask::getDirForModType(ModType type, QString raw) -{ - switch (type) { - // Mod types that can either be ignored at this stage, or ignored - // completely. - case ModType::Root: - case ModType::Extract: - case ModType::Decomp: - case ModType::TexturePackExtract: - case ModType::ResourcePackExtract: - case ModType::MCPC: - return Q_NULLPTR; - case ModType::Forge: - // Forge detection happens later on, if it cannot be detected it will - // install a jarmod component. - case ModType::Jar: - return "jarmods"; - case ModType::Mods: - return "mods"; - case ModType::Flan: - return "Flan"; - case ModType::Dependency: - return FS::PathCombine("mods", m_version.minecraft); - case ModType::Ic2Lib: - return FS::PathCombine("mods", "ic2"); - case ModType::DenLib: - return FS::PathCombine("mods", "denlib"); - case ModType::Coremods: - return "coremods"; - case ModType::Plugins: - return "plugins"; - case ModType::TexturePack: - return "texturepacks"; - case ModType::ResourcePack: - return "resourcepacks"; - case ModType::ShaderPack: - return "shaderpacks"; - case ModType::Millenaire: - qWarning() << "Unsupported mod type: " + raw; - return Q_NULLPTR; - case ModType::Unknown: - emitFailed(tr("Unknown mod type: %1").arg(raw)); - return Q_NULLPTR; - } - - return Q_NULLPTR; -} - -QString PackInstallTask::getVersionForLoader(QString uid) -{ - if(m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) { - auto vlist = ENV.metadataIndex()->get(uid); - if(!vlist) - { - emitFailed(tr("Failed to get local metadata index for %1").arg(uid)); - return Q_NULLPTR; - } - - if(!vlist->isLoaded()) { - vlist->load(Net::Mode::Online); - } - - if(m_version.loader.recommended || m_version.loader.latest) { - 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. - // not all mod loaders depend on a given Minecraft version, so we won't do this - // filtering for those loaders. - if (m_version.loader.type != "fabric") { - 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 != m_version.minecraft) continue; - } - - if (m_version.loader.recommended) { - // first recommended build we find, we use. - if (!version->isRecommended()) continue; - } - - return version->descriptor(); - } - - emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type)); - return Q_NULLPTR; - } - else if(m_version.loader.choose) { - // Fabric Loader doesn't depend on a given Minecraft version. - if (m_version.loader.type == "fabric") { - return m_support->chooseVersion(vlist, Q_NULLPTR); - } - - return m_support->chooseVersion(vlist, m_version.minecraft); - } - } - - if (m_version.loader.version == Q_NULLPTR || m_version.loader.version.isEmpty()) { - emitFailed(tr("No loader version set for modpack!")); - return Q_NULLPTR; - } - - return m_version.loader.version; -} - -QString PackInstallTask::detectLibrary(VersionLibrary library) -{ - // Try to detect what the library is - if (!library.server.isEmpty() && library.server.split("/").length() >= 3) { - auto lastSlash = library.server.lastIndexOf("/"); - auto locationAndVersion = library.server.mid(0, lastSlash); - auto fileName = library.server.mid(lastSlash + 1); - - lastSlash = locationAndVersion.lastIndexOf("/"); - auto location = locationAndVersion.mid(0, lastSlash); - auto version = locationAndVersion.mid(lastSlash + 1); - - lastSlash = location.lastIndexOf("/"); - auto group = location.mid(0, lastSlash).replace("/", "."); - auto artefact = location.mid(lastSlash + 1); - - return group + ":" + artefact + ":" + version; - } - - if(library.file.contains("-")) { - auto lastSlash = library.file.lastIndexOf("-"); - auto name = library.file.mid(0, lastSlash); - auto version = library.file.mid(lastSlash + 1).remove(".jar"); - - if(name == QString("guava")) { - return "com.google.guava:guava:" + version; - } - else if(name == QString("commons-lang3")) { - return "org.apache.commons:commons-lang3:" + version; - } - } - - return "org.multimc.atlauncher:" + library.md5 + ":1"; -} - -bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile) -{ - if(m_version.libraries.isEmpty()) { - return true; - } - - QList<GradleSpecifier> exempt; - for(const auto & componentUid : componentsToInstall.keys()) { - auto componentVersion = componentsToInstall.value(componentUid); - - for(const auto & library : componentVersion->data()->libraries) { - GradleSpecifier lib(library->rawName()); - exempt.append(lib); - } - } - - { - for(const auto & library : minecraftVersion->data()->libraries) { - GradleSpecifier lib(library->rawName()); - exempt.append(lib); - } - } - - auto uuid = QUuid::createUuid(); - auto id = uuid.toString().remove('{').remove('}'); - auto target_id = "org.multimc.atlauncher." + id; - - auto patchDir = FS::PathCombine(instanceRoot, "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); - - auto f = std::make_shared<VersionFile>(); - f->name = m_pack + " " + m_version_name + " (libraries)"; - - for(const auto & lib : m_version.libraries) { - auto libName = detectLibrary(lib); - GradleSpecifier libSpecifier(libName); - - bool libExempt = false; - for(const auto & existingLib : exempt) { - if(libSpecifier.matchName(existingLib)) { - // If the pack specifies a newer version of the lib, use that! - libExempt = Version(libSpecifier.version()) >= Version(existingLib.version()); - } - } - if(libExempt) continue; - - auto library = std::make_shared<Library>(); - library->setRawName(libName); - - switch(lib.download) { - case DownloadType::Server: - library->setAbsoluteUrl(BuildConfig.ATL_DOWNLOAD_SERVER_URL + lib.url); - break; - case DownloadType::Direct: - library->setAbsoluteUrl(lib.url); - break; - case DownloadType::Browser: - case DownloadType::Unknown: - emitFailed(tr("Unknown or unsupported download type: %1").arg(lib.download_raw)); - return false; - } - - f->libraries.append(library); - } - - if(f->libraries.isEmpty()) { - return true; - } - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - profile->appendComponent(new Component(profile.get(), target_id, f)); - return true; -} - -bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile) -{ - if(m_version.mainClass == QString() && m_version.extraArguments == QString()) { - return true; - } - - auto uuid = QUuid::createUuid(); - auto id = uuid.toString().remove('{').remove('}'); - auto target_id = "org.multimc.atlauncher." + id; - - auto patchDir = FS::PathCombine(instanceRoot, "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); - - QStringList mainClasses; - QStringList tweakers; - for(const auto & componentUid : componentsToInstall.keys()) { - auto componentVersion = componentsToInstall.value(componentUid); - - if(componentVersion->data()->mainClass != QString("")) { - mainClasses.append(componentVersion->data()->mainClass); - } - tweakers.append(componentVersion->data()->addTweakers); - } - - auto f = std::make_shared<VersionFile>(); - f->name = m_pack + " " + m_version_name; - if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) { - f->mainClass = m_version.mainClass; - } - - // Parse out tweakers - auto args = m_version.extraArguments.split(" "); - QString previous; - for(auto arg : args) { - if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") { - auto tweakClass = arg.remove("--tweakClass="); - if(tweakers.contains(tweakClass)) continue; - - f->addTweakers.append(tweakClass); - } - previous = arg; - } - - if(f->mainClass == QString() && f->addTweakers.isEmpty()) { - return true; - } - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - profile->appendComponent(new Component(profile.get(), target_id, f)); - return true; -} - -void PackInstallTask::installConfigs() -{ - qDebug() << "PackInstallTask::installConfigs: " << QThread::currentThreadId(); - setStatus(tr("Downloading configs...")); - jobPtr.reset(new NetJob(tr("Config download"))); - - auto path = QString("Configs/%1/%2.zip").arg(m_pack).arg(m_version_name); - auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip") - .arg(m_pack).arg(m_version_name); - auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", path); - entry->setStale(true); - - auto dl = Net::Download::makeCached(url, entry); - if (!m_version.configs.sha1.isEmpty()) { - auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - } - jobPtr->addNetAction(dl); - archivePath = entry->getFullPath(); - - connect(jobPtr.get(), &NetJob::succeeded, this, [&]() - { - abortable = false; - jobPtr.reset(); - extractConfigs(); - }); - 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); - }); - - jobPtr->start(); -} - -void PackInstallTask::extractConfigs() -{ - qDebug() << "PackInstallTask::extractConfigs: " << QThread::currentThreadId(); - setStatus(tr("Extracting configs...")); - - QDir extractDir(m_stagingPath); - - QuaZip packZip(archivePath); - if(!packZip.open(QuaZip::mdUnzip)) - { - emitFailed(tr("Failed to open pack configs %1!").arg(archivePath)); - return; - } - - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft"); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]() - { - downloadMods(); - }); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]() - { - emitAborted(); - }); - m_extractFutureWatcher.setFuture(m_extractFuture); -} - -void PackInstallTask::downloadMods() -{ - qDebug() << "PackInstallTask::installMods: " << QThread::currentThreadId(); - - QVector<ATLauncher::VersionMod> optionalMods; - for (const auto& mod : m_version.mods) { - if (mod.optional) { - optionalMods.push_back(mod); - } - } - - // Select optional mods, if pack contains any - QVector<QString> selectedMods; - if (!optionalMods.isEmpty()) { - setStatus(tr("Selecting optional mods...")); - selectedMods = m_support->chooseOptionalMods(optionalMods); - } - - setStatus(tr("Downloading mods...")); - - jarmods.clear(); - jobPtr.reset(new NetJob(tr("Mod download"))); - for(const auto& mod : m_version.mods) { - // skip non-client mods - if(!mod.client) continue; - - // skip optional mods that were not selected - if(mod.optional && !selectedMods.contains(mod.name)) continue; - - QString url; - switch(mod.download) { - case DownloadType::Server: - url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url; - break; - case DownloadType::Browser: - emitFailed(tr("Unsupported download type: %1").arg(mod.download_raw)); - return; - case DownloadType::Direct: - url = mod.url; - break; - case DownloadType::Unknown: - emitFailed(tr("Unknown download type: %1").arg(mod.download_raw)); - return; - } - - QFileInfo fileName(mod.file); - auto cacheName = fileName.completeBaseName() + "-" + mod.md5 + "." + fileName.suffix(); - - if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) { - auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName); - entry->setStale(true); - modsToExtract.insert(entry->getFullPath(), mod); - - auto dl = Net::Download::makeCached(url, entry); - if (!mod.md5.isEmpty()) { - auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); - } - jobPtr->addNetAction(dl); - } - else if(mod.type == ModType::Decomp) { - auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName); - entry->setStale(true); - modsToDecomp.insert(entry->getFullPath(), mod); - - auto dl = Net::Download::makeCached(url, entry); - if (!mod.md5.isEmpty()) { - auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); - } - jobPtr->addNetAction(dl); - } - else { - auto relpath = getDirForModType(mod.type, mod.type_raw); - if(relpath == Q_NULLPTR) continue; - - auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName); - entry->setStale(true); - - auto dl = Net::Download::makeCached(url, entry); - if (!mod.md5.isEmpty()) { - auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); - } - jobPtr->addNetAction(dl); - - auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file); - qDebug() << "Will download" << url << "to" << path; - modsToCopy[entry->getFullPath()] = path; - - if(mod.type == ModType::Forge) { - auto vlist = ENV.metadataIndex()->get("net.minecraftforge"); - if(vlist) - { - auto ver = vlist->getVersion(mod.version); - if(ver) { - ver->load(Net::Mode::Online); - componentsToInstall.insert("net.minecraftforge", ver); - continue; - } - } - - qDebug() << "Jarmod: " + path; - jarmods.push_back(path); - } - - if(mod.type == ModType::Jar) { - qDebug() << "Jarmod: " + path; - jarmods.push_back(path); - } - } - } - - connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModsDownloaded); - 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); - }); - - jobPtr->start(); -} - -void PackInstallTask::onModsDownloaded() { - abortable = false; - - qDebug() << "PackInstallTask::onModsDownloaded: " << QThread::currentThreadId(); - jobPtr.reset(); - - if(!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) { - m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy); - connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted); - connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]() - { - emitAborted(); - }); - m_modExtractFutureWatcher.setFuture(m_modExtractFuture); - } - else { - install(); - } -} - -void PackInstallTask::onModsExtracted() { - qDebug() << "PackInstallTask::onModsExtracted: " << QThread::currentThreadId(); - if(m_modExtractFuture.result()) { - install(); - } - else { - emitFailed(tr("Failed to extract mods...")); - } -} - -bool PackInstallTask::extractMods( - const QMap<QString, VersionMod> &toExtract, - const QMap<QString, VersionMod> &toDecomp, - const QMap<QString, QString> &toCopy -) { - qDebug() << "PackInstallTask::extractMods: " << QThread::currentThreadId(); - - setStatus(tr("Extracting mods...")); - for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) { - auto &modPath = iter.key(); - auto &mod = iter.value(); - - QString extractToDir; - if(mod.type == ModType::Extract) { - extractToDir = getDirForModType(mod.extractTo, mod.extractTo_raw); - } - else if(mod.type == ModType::TexturePackExtract) { - extractToDir = FS::PathCombine("texturepacks", "extracted"); - } - else if(mod.type == ModType::ResourcePackExtract) { - extractToDir = FS::PathCombine("resourcepacks", "extracted"); - } - - QDir extractDir(m_stagingPath); - auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir); - - QString folderToExtract = ""; - if(mod.type == ModType::Extract) { - folderToExtract = mod.extractFolder; - folderToExtract.remove(QRegExp("^/")); - } - - qDebug() << "Extracting " + mod.file + " to " + extractToDir; - if(!MMCZip::extractDir(modPath, folderToExtract, extractToPath)) { - // assume error - return false; - } - } - - for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) { - auto &modPath = iter.key(); - auto &mod = iter.value(); - auto extractToDir = getDirForModType(mod.decompType, mod.decompType_raw); - - QDir extractDir(m_stagingPath); - auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir, mod.decompFile); - - qDebug() << "Extracting " + mod.decompFile + " to " + extractToDir; - if(!MMCZip::extractFile(modPath, mod.decompFile, extractToPath)) { - qWarning() << "Failed to extract" << mod.decompFile; - return false; - } - } - - for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) { - auto &from = iter.key(); - auto &to = iter.value(); - FS::copy fileCopyOperation(from, to); - if(!fileCopyOperation()) { - qWarning() << "Failed to copy" << from << "to" << to; - return false; - } - } - return true; -} - -void PackInstallTask::install() -{ - qDebug() << "PackInstallTask::install: " << QThread::currentThreadId(); - setStatus(tr("Installing modpack")); - - auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath); - instanceSettings->suspendSave(); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - - // Use a component to add libraries BEFORE Minecraft - if(!createLibrariesComponent(instance.instanceRoot(), components)) { - emitFailed(tr("Failed to create libraries component")); - return; - } - - // Minecraft - components->setComponentVersion("net.minecraft", m_version.minecraft, true); - - // Loader - if(m_version.loader.type == QString("forge")) - { - auto version = getVersionForLoader("net.minecraftforge"); - if(version == Q_NULLPTR) return; - - components->setComponentVersion("net.minecraftforge", version, true); - } - else if(m_version.loader.type == QString("fabric")) - { - auto version = getVersionForLoader("net.fabricmc.fabric-loader"); - if(version == Q_NULLPTR) return; - - components->setComponentVersion("net.fabricmc.fabric-loader", version, true); - } - else if(m_version.loader.type != QString()) - { - emitFailed(tr("Unknown loader type: ") + m_version.loader.type); - return; - } - - for(const auto & componentUid : componentsToInstall.keys()) { - auto version = componentsToInstall.value(componentUid); - components->setComponentVersion(componentUid, version->version()); - } - - components->installJarMods(jarmods); - - // Use a component to fill in the rest of the data - // todo: use more detection - if(!createPackComponent(instance.instanceRoot(), components)) { - emitFailed(tr("Failed to create pack component")); - return; - } - - components->saveNow(); - - instance.setName(m_instName); - instance.setIconKey(m_instIcon); - instanceSettings->resumeSave(); - - jarmods.clear(); - emitSucceeded(); -} - -} diff --git a/api/logic/modplatform/atlauncher/ATLPackInstallTask.h b/api/logic/modplatform/atlauncher/ATLPackInstallTask.h deleted file mode 100644 index 15fd9b32..00000000 --- a/api/logic/modplatform/atlauncher/ATLPackInstallTask.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -#include <meta/VersionList.h> -#include "ATLPackManifest.h" - -#include "InstanceTask.h" -#include "multimc_logic_export.h" -#include "net/NetJob.h" -#include "settings/INISettingsObject.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "meta/Version.h" - -#include <nonstd/optional> - -namespace ATLauncher { - -class MULTIMC_LOGIC_EXPORT UserInteractionSupport { - -public: - /** - * Requests a user interaction to select which optional mods should be installed. - */ - virtual QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) = 0; - - /** - * Requests a user interaction to select a component version from a given version list - * and constrained to a given Minecraft version. - */ - virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0; - -}; - -class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask -{ -Q_OBJECT - -public: - explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version); - virtual ~PackInstallTask(){} - - bool canAbort() const override { return true; } - bool abort() override; - -protected: - virtual void executeTask() override; - -private slots: - void onDownloadSucceeded(); - void onDownloadFailed(QString reason); - - void onModsDownloaded(); - void onModsExtracted(); - -private: - QString getDirForModType(ModType type, QString raw); - QString getVersionForLoader(QString uid); - QString detectLibrary(VersionLibrary library); - - bool createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile); - bool createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile); - - void installConfigs(); - void extractConfigs(); - void downloadMods(); - bool extractMods( - const QMap<QString, VersionMod> &toExtract, - const QMap<QString, VersionMod> &toDecomp, - const QMap<QString, QString> &toCopy - ); - void install(); - -private: - UserInteractionSupport *m_support; - - bool abortable = false; - - NetJobPtr jobPtr; - QByteArray response; - - QString m_pack; - QString m_version_name; - PackVersion m_version; - - QMap<QString, VersionMod> modsToExtract; - QMap<QString, VersionMod> modsToDecomp; - QMap<QString, QString> modsToCopy; - - QString archivePath; - QStringList jarmods; - Meta::VersionPtr minecraftVersion; - QMap<QString, Meta::VersionPtr> componentsToInstall; - - QFuture<nonstd::optional<QStringList>> m_extractFuture; - QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher; - - QFuture<bool> m_modExtractFuture; - QFutureWatcher<bool> m_modExtractFutureWatcher; - -}; - -} diff --git a/api/logic/modplatform/atlauncher/ATLPackManifest.cpp b/api/logic/modplatform/atlauncher/ATLPackManifest.cpp deleted file mode 100644 index e25d8346..00000000 --- a/api/logic/modplatform/atlauncher/ATLPackManifest.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "ATLPackManifest.h" - -#include "Json.h" - -static ATLauncher::DownloadType parseDownloadType(QString rawType) { - if(rawType == QString("server")) { - return ATLauncher::DownloadType::Server; - } - else if(rawType == QString("browser")) { - return ATLauncher::DownloadType::Browser; - } - else if(rawType == QString("direct")) { - return ATLauncher::DownloadType::Direct; - } - - return ATLauncher::DownloadType::Unknown; -} - -static ATLauncher::ModType parseModType(QString rawType) { - // See https://wiki.atlauncher.com/mod_types - if(rawType == QString("root")) { - return ATLauncher::ModType::Root; - } - else if(rawType == QString("forge")) { - return ATLauncher::ModType::Forge; - } - else if(rawType == QString("jar")) { - return ATLauncher::ModType::Jar; - } - else if(rawType == QString("mods")) { - return ATLauncher::ModType::Mods; - } - else if(rawType == QString("flan")) { - return ATLauncher::ModType::Flan; - } - else if(rawType == QString("dependency") || rawType == QString("depandency")) { - return ATLauncher::ModType::Dependency; - } - else if(rawType == QString("ic2lib")) { - return ATLauncher::ModType::Ic2Lib; - } - else if(rawType == QString("denlib")) { - return ATLauncher::ModType::DenLib; - } - else if(rawType == QString("coremods")) { - return ATLauncher::ModType::Coremods; - } - else if(rawType == QString("mcpc")) { - return ATLauncher::ModType::MCPC; - } - else if(rawType == QString("plugins")) { - return ATLauncher::ModType::Plugins; - } - else if(rawType == QString("extract")) { - return ATLauncher::ModType::Extract; - } - else if(rawType == QString("decomp")) { - return ATLauncher::ModType::Decomp; - } - else if(rawType == QString("texturepack")) { - return ATLauncher::ModType::TexturePack; - } - else if(rawType == QString("resourcepack")) { - return ATLauncher::ModType::ResourcePack; - } - else if(rawType == QString("shaderpack")) { - return ATLauncher::ModType::ShaderPack; - } - else if(rawType == QString("texturepackextract")) { - return ATLauncher::ModType::TexturePackExtract; - } - else if(rawType == QString("resourcepackextract")) { - return ATLauncher::ModType::ResourcePackExtract; - } - else if(rawType == QString("millenaire")) { - return ATLauncher::ModType::Millenaire; - } - - return ATLauncher::ModType::Unknown; -} - -static void loadVersionLoader(ATLauncher::VersionLoader & p, QJsonObject & obj) { - p.type = Json::requireString(obj, "type"); - p.choose = Json::ensureBoolean(obj, QString("choose"), false); - - auto metadata = Json::requireObject(obj, "metadata"); - p.latest = Json::ensureBoolean(metadata, QString("latest"), false); - p.recommended = Json::ensureBoolean(metadata, QString("recommended"), false); - - // Minecraft Forge - if (p.type == "forge") { - p.version = Json::ensureString(metadata, "version", ""); - } - - // Fabric Loader - if (p.type == "fabric") { - p.version = Json::ensureString(metadata, "loader", ""); - } -} - -static void loadVersionLibrary(ATLauncher::VersionLibrary & p, QJsonObject & obj) { - p.url = Json::requireString(obj, "url"); - p.file = Json::requireString(obj, "file"); - p.md5 = Json::requireString(obj, "md5"); - - p.download_raw = Json::requireString(obj, "download"); - p.download = parseDownloadType(p.download_raw); - - p.server = Json::ensureString(obj, "server", ""); -} - -static void loadVersionConfigs(ATLauncher::VersionConfigs & p, QJsonObject & obj) { - p.filesize = Json::requireInteger(obj, "filesize"); - p.sha1 = Json::requireString(obj, "sha1"); -} - -static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) { - p.name = Json::requireString(obj, "name"); - p.version = Json::requireString(obj, "version"); - p.url = Json::requireString(obj, "url"); - p.file = Json::requireString(obj, "file"); - p.md5 = Json::ensureString(obj, "md5", ""); - - p.download_raw = Json::requireString(obj, "download"); - p.download = parseDownloadType(p.download_raw); - - p.type_raw = Json::requireString(obj, "type"); - p.type = parseModType(p.type_raw); - - // This contributes to the Minecraft Forge detection, where we rely on mod.type being "Forge" - // when the mod represents Forge. As there is little difference between "Jar" and "Forge, some - // packs regretfully use "Jar". This will correct the type to "Forge" in these cases (as best - // it can). - if(p.name == QString("Minecraft Forge") && p.type == ATLauncher::ModType::Jar) { - p.type_raw = "forge"; - p.type = ATLauncher::ModType::Forge; - } - - if(obj.contains("extractTo")) { - p.extractTo_raw = Json::requireString(obj, "extractTo"); - p.extractTo = parseModType(p.extractTo_raw); - p.extractFolder = Json::ensureString(obj, "extractFolder", "").replace("%s%", "/"); - } - - if(obj.contains("decompType")) { - p.decompType_raw = Json::requireString(obj, "decompType"); - p.decompType = parseModType(p.decompType_raw); - p.decompFile = Json::requireString(obj, "decompFile"); - } - - p.description = Json::ensureString(obj, QString("description"), ""); - p.optional = Json::ensureBoolean(obj, QString("optional"), false); - p.recommended = Json::ensureBoolean(obj, QString("recommended"), false); - p.selected = Json::ensureBoolean(obj, QString("selected"), false); - p.hidden = Json::ensureBoolean(obj, QString("hidden"), false); - p.library = Json::ensureBoolean(obj, QString("library"), false); - p.group = Json::ensureString(obj, QString("group"), ""); - if(obj.contains("depends")) { - auto dependsArr = Json::requireArray(obj, "depends"); - for (const auto depends : dependsArr) { - p.depends.append(Json::requireString(depends)); - } - } - - p.client = Json::ensureBoolean(obj, QString("client"), false); - - // computed - p.effectively_hidden = p.hidden || p.library; -} - -void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) -{ - v.version = Json::requireString(obj, "version"); - v.minecraft = Json::requireString(obj, "minecraft"); - v.noConfigs = Json::ensureBoolean(obj, QString("noConfigs"), false); - - if(obj.contains("mainClass")) { - auto main = Json::requireObject(obj, "mainClass"); - v.mainClass = Json::ensureString(main, "mainClass", ""); - } - - if(obj.contains("extraArguments")) { - auto arguments = Json::requireObject(obj, "extraArguments"); - v.extraArguments = Json::ensureString(arguments, "arguments", ""); - } - - if(obj.contains("loader")) { - auto loader = Json::requireObject(obj, "loader"); - loadVersionLoader(v.loader, loader); - } - - if(obj.contains("libraries")) { - auto libraries = Json::requireArray(obj, "libraries"); - for (const auto libraryRaw : libraries) - { - auto libraryObj = Json::requireObject(libraryRaw); - ATLauncher::VersionLibrary target; - loadVersionLibrary(target, libraryObj); - v.libraries.append(target); - } - } - - if(obj.contains("mods")) { - auto mods = Json::requireArray(obj, "mods"); - for (const auto modRaw : mods) - { - auto modObj = Json::requireObject(modRaw); - ATLauncher::VersionMod mod; - loadVersionMod(mod, modObj); - v.mods.append(mod); - } - } - - if(obj.contains("configs")) { - auto configsObj = Json::requireObject(obj, "configs"); - loadVersionConfigs(v.configs, configsObj); - } -} diff --git a/api/logic/modplatform/atlauncher/ATLPackManifest.h b/api/logic/modplatform/atlauncher/ATLPackManifest.h deleted file mode 100644 index 17821e4c..00000000 --- a/api/logic/modplatform/atlauncher/ATLPackManifest.h +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include <QString> -#include <QVector> -#include <QJsonObject> -#include <multimc_logic_export.h> - -namespace ATLauncher -{ - -enum class PackType -{ - Public, - Private -}; - -enum class ModType -{ - Root, - Forge, - Jar, - Mods, - Flan, - Dependency, - Ic2Lib, - DenLib, - Coremods, - MCPC, - Plugins, - Extract, - Decomp, - TexturePack, - ResourcePack, - ShaderPack, - TexturePackExtract, - ResourcePackExtract, - Millenaire, - Unknown -}; - -enum class DownloadType -{ - Server, - Browser, - Direct, - Unknown -}; - -struct VersionLoader -{ - QString type; - bool latest; - bool recommended; - bool choose; - - QString version; -}; - -struct VersionLibrary -{ - QString url; - QString file; - QString server; - QString md5; - DownloadType download; - QString download_raw; -}; - -struct VersionMod -{ - QString name; - QString version; - QString url; - QString file; - QString md5; - DownloadType download; - QString download_raw; - ModType type; - QString type_raw; - - ModType extractTo; - QString extractTo_raw; - QString extractFolder; - - ModType decompType; - QString decompType_raw; - QString decompFile; - - QString description; - bool optional; - bool recommended; - bool selected; - bool hidden; - bool library; - QString group; - QVector<QString> depends; - - bool client; - - // computed - bool effectively_hidden; -}; - -struct VersionConfigs -{ - int filesize; - QString sha1; -}; - -struct PackVersion -{ - QString version; - QString minecraft; - bool noConfigs; - QString mainClass; - QString extraArguments; - - VersionLoader loader; - QVector<VersionLibrary> libraries; - QVector<VersionMod> mods; - VersionConfigs configs; -}; - -MULTIMC_LOGIC_EXPORT void loadVersion(PackVersion & v, QJsonObject & obj); - -} diff --git a/api/logic/modplatform/flame/FileResolvingTask.cpp b/api/logic/modplatform/flame/FileResolvingTask.cpp deleted file mode 100644 index 295574f0..00000000 --- a/api/logic/modplatform/flame/FileResolvingTask.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "FileResolvingTask.h" -#include "Json.h" - -namespace { - const char * metabase = "https://cursemeta.dries007.net"; -} - -Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess) - : m_toProcess(toProcess) -{ -} - -void Flame::FileResolvingTask::executeTask() -{ - setStatus(tr("Resolving mod IDs...")); - setProgress(0, m_toProcess.files.size()); - m_dljob.reset(new NetJob("Mod id resolver")); - results.resize(m_toProcess.files.size()); - int index = 0; - for(auto & file: m_toProcess.files) - { - auto projectIdStr = QString::number(file.projectId); - auto fileIdStr = QString::number(file.fileId); - QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr); - auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]); - m_dljob->addNetAction(dl); - index ++; - } - connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); - m_dljob->start(); -} - -void Flame::FileResolvingTask::netJobFinished() -{ - bool failed = false; - int index = 0; - for(auto & bytes: results) - { - auto & out = m_toProcess.files[index]; - try - { - failed &= (!out.parseFromBytes(bytes)); - } - catch (const JSONValidationError &e) - { - - qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; - qCritical() << e.cause(); - qCritical() << "JSON:"; - qCritical() << bytes; - failed = true; - } - index++; - } - if(!failed) - { - emitSucceeded(); - } - else - { - emitFailed(tr("Some mod ID resolving tasks failed.")); - } -} diff --git a/api/logic/modplatform/flame/FileResolvingTask.h b/api/logic/modplatform/flame/FileResolvingTask.h deleted file mode 100644 index 5679b907..00000000 --- a/api/logic/modplatform/flame/FileResolvingTask.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "tasks/Task.h" -#include "net/NetJob.h" -#include "PackManifest.h" - -#include "multimc_logic_export.h" - -namespace Flame -{ -class MULTIMC_LOGIC_EXPORT FileResolvingTask : public Task -{ - Q_OBJECT -public: - explicit FileResolvingTask(Flame::Manifest &toProcess); - virtual ~FileResolvingTask() {}; - - const Flame::Manifest &getResults() const - { - return m_toProcess; - } - -protected: - virtual void executeTask() override; - -protected slots: - void netJobFinished(); - -private: /* data */ - Flame::Manifest m_toProcess; - QVector<QByteArray> results; - NetJobPtr m_dljob; -}; -} diff --git a/api/logic/modplatform/flame/FlamePackIndex.cpp b/api/logic/modplatform/flame/FlamePackIndex.cpp deleted file mode 100644 index 3d8ea22a..00000000 --- a/api/logic/modplatform/flame/FlamePackIndex.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "FlamePackIndex.h" - -#include "Json.h" - -void Flame::loadIndexedPack(Flame::IndexedPack & pack, QJsonObject & obj) -{ - pack.addonId = Json::requireInteger(obj, "id"); - pack.name = Json::requireString(obj, "name"); - pack.websiteUrl = Json::ensureString(obj, "websiteUrl", ""); - pack.description = Json::ensureString(obj, "summary", ""); - - bool thumbnailFound = false; - auto attachments = Json::requireArray(obj, "attachments"); - for(auto attachmentRaw: attachments) { - auto attachmentObj = Json::requireObject(attachmentRaw); - bool isDefault = attachmentObj.value("isDefault").toBool(false); - if(isDefault) { - thumbnailFound = true; - pack.logoName = Json::requireString(attachmentObj, "title"); - pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl"); - break; - } - } - - if(!thumbnailFound) { - throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name)); - } - - auto authors = Json::requireArray(obj, "authors"); - for(auto authorIter: authors) { - auto author = Json::requireObject(authorIter); - Flame::ModpackAuthor packAuthor; - packAuthor.name = Json::requireString(author, "name"); - packAuthor.url = Json::requireString(author, "url"); - pack.authors.append(packAuthor); - } - int defaultFileId = Json::requireInteger(obj, "defaultFileId"); - - bool found = false; - // check if there are some files before adding the pack - auto files = Json::requireArray(obj, "latestFiles"); - for(auto fileIter: files) { - auto file = Json::requireObject(fileIter); - int id = Json::requireInteger(file, "id"); - - // NOTE: for now, ignore everything that's not the default... - if(id != defaultFileId) { - continue; - } - - auto versionArray = Json::requireArray(file, "gameVersion"); - if(versionArray.size() < 1) { - continue; - } - - found = true; - break; - } - if(!found) { - throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name)); - } -} - -void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) -{ - QVector<Flame::IndexedVersion> unsortedVersions; - for(auto versionIter: arr) { - auto version = Json::requireObject(versionIter); - Flame::IndexedVersion file; - - file.addonId = pack.addonId; - file.fileId = Json::requireInteger(version, "id"); - auto versionArray = Json::requireArray(version, "gameVersion"); - if(versionArray.size() < 1) { - continue; - } - - // pick the latest version supported - file.mcVersion = versionArray[0].toString(); - file.version = Json::requireString(version, "displayName"); - file.downloadUrl = Json::requireString(version, "downloadUrl"); - unsortedVersions.append(file); - } - - auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool - { - return a.fileId > b.fileId; - }; - std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); - pack.versions = unsortedVersions; - pack.versionsLoaded = true; -} diff --git a/api/logic/modplatform/flame/FlamePackIndex.h b/api/logic/modplatform/flame/FlamePackIndex.h deleted file mode 100644 index cdeb2c13..00000000 --- a/api/logic/modplatform/flame/FlamePackIndex.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include <QList> -#include <QMetaType> -#include <QString> -#include <QVector> - -#include "multimc_logic_export.h" - -namespace Flame { - -struct ModpackAuthor { - QString name; - QString url; -}; - -struct IndexedVersion { - int addonId; - int fileId; - QString version; - QString mcVersion; - QString downloadUrl; -}; - -struct IndexedPack -{ - int addonId; - QString name; - QString description; - QList<ModpackAuthor> authors; - QString logoName; - QString logoUrl; - QString websiteUrl; - - bool versionsLoaded = false; - QVector<IndexedVersion> versions; -}; - -MULTIMC_LOGIC_EXPORT void loadIndexedPack(IndexedPack & m, QJsonObject & obj); -MULTIMC_LOGIC_EXPORT void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr); -} - -Q_DECLARE_METATYPE(Flame::IndexedPack) diff --git a/api/logic/modplatform/flame/PackManifest.cpp b/api/logic/modplatform/flame/PackManifest.cpp deleted file mode 100644 index b928fd16..00000000 --- a/api/logic/modplatform/flame/PackManifest.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "PackManifest.h" -#include "Json.h" - -static void loadFileV1(Flame::File & f, QJsonObject & file) -{ - f.projectId = Json::requireInteger(file, "projectID"); - f.fileId = Json::requireInteger(file, "fileID"); - f.required = Json::ensureBoolean(file, QString("required"), true); -} - -static void loadModloaderV1(Flame::Modloader & m, QJsonObject & modLoader) -{ - m.id = Json::requireString(modLoader, "id"); - m.primary = Json::ensureBoolean(modLoader, QString("primary"), false); -} - -static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft) -{ - m.version = Json::requireString(minecraft, "version"); - // extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack - // intended use is likely hardcoded in the 'Flame' client, the manifest says nothing - m.libraries = Json::ensureString(minecraft, QString("libraries"), QString()); - auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray()); - for (QJsonValueRef item : arr) - { - auto obj = Json::requireObject(item); - Flame::Modloader loader; - loadModloaderV1(loader, obj); - m.modLoaders.append(loader); - } -} - -static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest) -{ - auto mc = Json::requireObject(manifest, "minecraft"); - loadMinecraftV1(m.minecraft, mc); - m.name = Json::ensureString(manifest, QString("name"), "Unnamed"); - m.version = Json::ensureString(manifest, QString("version"), QString()); - m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward"); - auto arr = Json::ensureArray(manifest, "files", QJsonArray()); - for (QJsonValueRef item : arr) - { - auto obj = Json::requireObject(item); - Flame::File file; - loadFileV1(file, obj); - m.files.append(file); - } - m.overrides = Json::ensureString(manifest, "overrides", "overrides"); -} - -void Flame::loadManifest(Flame::Manifest & m, const QString &filepath) -{ - auto doc = Json::requireDocument(filepath); - auto obj = Json::requireObject(doc); - m.manifestType = Json::requireString(obj, "manifestType"); - if(m.manifestType != "minecraftModpack") - { - throw JSONValidationError("Not a modpack manifest!"); - } - m.manifestVersion = Json::requireInteger(obj, "manifestVersion"); - if(m.manifestVersion != 1) - { - throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion)); - } - loadManifestV1(m, obj); -} - -bool Flame::File::parseFromBytes(const QByteArray& bytes) -{ - auto doc = Json::requireDocument(bytes); - auto obj = Json::requireObject(doc); - // result code signifies true failure. - if(obj.contains("code")) - { - qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:"; - qCritical() << bytes; - return false; - } - fileName = Json::requireString(obj, "FileNameOnDisk"); - QString rawUrl = Json::requireString(obj, "DownloadURL"); - url = QUrl(rawUrl, QUrl::TolerantMode); - if(!url.isValid()) - { - throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); - } - // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience - // It is also optional - QJsonObject projObj = Json::ensureObject(obj, "_Project", {}); - if(!projObj.isEmpty()) - { - QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower(); - if(strType == "singlefile") - { - type = File::Type::SingleFile; - } - else if(strType == "ctoc") - { - type = File::Type::Ctoc; - } - else if(strType == "cmod2") - { - type = File::Type::Cmod2; - } - else if(strType == "mod") - { - type = File::Type::Mod; - } - else if(strType == "folder") - { - type = File::Type::Folder; - } - else if(strType == "modpack") - { - type = File::Type::Modpack; - } - else - { - qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType; - type = File::Type::Unknown; - return false; - } - targetFolder = Json::ensureString(projObj, "Path", "mods"); - } - resolved = true; - return true; -} diff --git a/api/logic/modplatform/flame/PackManifest.h b/api/logic/modplatform/flame/PackManifest.h deleted file mode 100644 index 02f39f0e..00000000 --- a/api/logic/modplatform/flame/PackManifest.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include <QString> -#include <QVector> -#include <QUrl> - -namespace Flame -{ -struct File -{ - // NOTE: throws JSONValidationError - bool parseFromBytes(const QByteArray &bytes); - - int projectId = 0; - int fileId = 0; - // NOTE: the opposite to 'optional'. This is at the time of writing unused. - bool required = true; - - // our - bool resolved = false; - QString fileName; - QUrl url; - QString targetFolder = QLatin1Literal("mods"); - enum class Type - { - Unknown, - Folder, - Ctoc, - SingleFile, - Cmod2, - Modpack, - Mod - } type = Type::Mod; -}; - -struct Modloader -{ - QString id; - bool primary = false; -}; - -struct Minecraft -{ - QString version; - QString libraries; - QVector<Flame::Modloader> modLoaders; -}; - -struct Manifest -{ - QString manifestType; - int manifestVersion = 0; - Flame::Minecraft minecraft; - QString name; - QString version; - QString author; - QVector<Flame::File> files; - QString overrides; -}; - -void loadManifest(Flame::Manifest & m, const QString &filepath); -} diff --git a/api/logic/modplatform/legacy_ftb/PackFetchTask.cpp b/api/logic/modplatform/legacy_ftb/PackFetchTask.cpp deleted file mode 100644 index c2ef6436..00000000 --- a/api/logic/modplatform/legacy_ftb/PackFetchTask.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "PackFetchTask.h" -#include "PrivatePackManager.h" - -#include <QDomDocument> -#include <BuildConfig.h> - -namespace LegacyFTB { - -void PackFetchTask::fetch() -{ - publicPacks.clear(); - thirdPartyPacks.clear(); - - NetJob *netJob = new NetJob("LegacyFTB::ModpackFetch"); - - QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml"); - qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); - netJob->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData)); - - QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml"); - qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString(); - netJob->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData)); - - QObject::connect(netJob, &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished); - QObject::connect(netJob, &NetJob::failed, this, &PackFetchTask::fileDownloadFailed); - - jobPtr.reset(netJob); - netJob->start(); -} - -void PackFetchTask::fetchPrivate(const QStringList & toFetch) -{ - QString privatePackBaseUrl = BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1.xml"; - - for (auto &packCode: toFetch) - { - QByteArray *data = new QByteArray(); - NetJob *job = new NetJob("Fetching private pack"); - job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data)); - - QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] - { - ModpackList packs; - parseAndAddPacks(*data, PackType::Private, packs); - foreach(Modpack currentPack, packs) - { - currentPack.packCode = packCode; - emit privateFileDownloadFinished(currentPack); - } - - job->deleteLater(); - - data->clear(); - delete data; - }); - - QObject::connect(job, &NetJob::failed, this, [this, job, packCode, data](QString reason) - { - emit privateFileDownloadFailed(reason, packCode); - job->deleteLater(); - - data->clear(); - delete data; - }); - - job->start(); - } -} - -void PackFetchTask::fileDownloadFinished() -{ - jobPtr.reset(); - - QStringList failedLists; - - if(!parseAndAddPacks(publicModpacksXmlFileData, PackType::Public, publicPacks)) - { - failedLists.append(tr("Public Packs")); - } - - if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks)) - { - failedLists.append(tr("Third Party Packs")); - } - - if(failedLists.size() > 0) - { - emit failed(tr("Failed to download some pack lists: %1").arg(failedLists.join("\n- "))); - } - else - { - emit finished(publicPacks, thirdPartyPacks); - } -} - -bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list) -{ - QDomDocument doc; - - QString errorMsg = "Unknown error."; - int errorLine = -1; - int errorCol = -1; - - if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)) - { - auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:3d!").arg(errorMsg, errorLine, errorCol); - qWarning() << fullErrMsg; - data.clear(); - return false; - } - - QDomNodeList nodes = doc.elementsByTagName("modpack"); - for(int i = 0; i < nodes.length(); i++) - { - QDomElement element = nodes.at(i).toElement(); - - Modpack modpack; - modpack.name = element.attribute("name"); - modpack.currentVersion = element.attribute("version"); - modpack.mcVersion = element.attribute("mcVersion"); - modpack.description = element.attribute("description"); - modpack.mods = element.attribute("mods"); - modpack.logo = element.attribute("logo"); - modpack.oldVersions = element.attribute("oldVersions").split(";"); - modpack.broken = false; - modpack.bugged = false; - - //remove empty if the xml is bugged - for(QString curr : modpack.oldVersions) - { - if(curr.isNull() || curr.isEmpty()) - { - modpack.oldVersions.removeAll(curr); - modpack.bugged = true; - qWarning() << "Removed some empty versions from" << modpack.name; - } - } - - if(modpack.oldVersions.size() < 1) - { - if(!modpack.currentVersion.isNull() && !modpack.currentVersion.isEmpty()) - { - modpack.oldVersions.append(modpack.currentVersion); - qWarning() << "Added current version to oldVersions because oldVersions was empty! (" + modpack.name + ")"; - } - else - { - modpack.broken = true; - qWarning() << "Broken pack:" << modpack.name << " => No valid version!"; - } - } - - modpack.author = element.attribute("author"); - - modpack.dir = element.attribute("dir"); - modpack.file = element.attribute("url"); - - modpack.type = packType; - - list.append(modpack); - } - - return true; -} - -void PackFetchTask::fileDownloadFailed(QString reason) -{ - qWarning() << "Fetching FTBPacks failed:" << reason; - emit failed(reason); -} - -} diff --git a/api/logic/modplatform/legacy_ftb/PackFetchTask.h b/api/logic/modplatform/legacy_ftb/PackFetchTask.h deleted file mode 100644 index 4a8469b1..00000000 --- a/api/logic/modplatform/legacy_ftb/PackFetchTask.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "net/NetJob.h" -#include <QTemporaryDir> -#include <QByteArray> -#include <QObject> -#include "PackHelpers.h" - -namespace LegacyFTB { - -class MULTIMC_LOGIC_EXPORT PackFetchTask : public QObject { - - Q_OBJECT - -public: - PackFetchTask() = default; - virtual ~PackFetchTask() = default; - - void fetch(); - void fetchPrivate(const QStringList &toFetch); - -private: - NetJobPtr jobPtr; - - QByteArray publicModpacksXmlFileData; - QByteArray thirdPartyModpacksXmlFileData; - - bool parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list); - ModpackList publicPacks; - ModpackList thirdPartyPacks; - -protected slots: - void fileDownloadFinished(); - void fileDownloadFailed(QString reason); - -signals: - void finished(ModpackList publicPacks, ModpackList thirdPartyPacks); - void failed(QString reason); - - void privateFileDownloadFinished(Modpack modpack); - void privateFileDownloadFailed(QString reason, QString packCode); -}; - -} diff --git a/api/logic/modplatform/legacy_ftb/PackHelpers.h b/api/logic/modplatform/legacy_ftb/PackHelpers.h deleted file mode 100644 index 566210d0..00000000 --- a/api/logic/modplatform/legacy_ftb/PackHelpers.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include <QList> -#include <QString> -#include <QStringList> -#include <QMetaType> - -namespace LegacyFTB { - -//Header for structs etc... -enum class PackType -{ - Public, - ThirdParty, - Private -}; - -struct Modpack -{ - QString name; - QString description; - QString author; - QStringList oldVersions; - QString currentVersion; - QString mcVersion; - QString mods; - QString logo; - - //Technical data - QString dir; - QString file; //<- Url in the xml, but doesn't make much sense - - bool bugged = false; - bool broken = false; - - PackType type; - QString packCode; -}; - -typedef QList<Modpack> ModpackList; - -} - -//We need it for the proxy model -Q_DECLARE_METATYPE(LegacyFTB::Modpack) diff --git a/api/logic/modplatform/legacy_ftb/PackInstallTask.cpp b/api/logic/modplatform/legacy_ftb/PackInstallTask.cpp deleted file mode 100644 index c77f3250..00000000 --- a/api/logic/modplatform/legacy_ftb/PackInstallTask.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include "PackInstallTask.h" - -#include "Env.h" -#include "MMCZip.h" - -#include "BaseInstance.h" -#include "FileSystem.h" -#include "settings/INISettingsObject.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "minecraft/GradleSpecifier.h" -#include "BuildConfig.h" - -#include <QtConcurrent> - -namespace LegacyFTB { - -PackInstallTask::PackInstallTask(Modpack pack, QString version) -{ - m_pack = pack; - m_version = version; -} - -void PackInstallTask::executeTask() -{ - downloadPack(); -} - -void PackInstallTask::downloadPack() -{ - setStatus(tr("Downloading zip for %1").arg(m_pack.name)); - - auto packoffset = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); - auto entry = ENV.metacache()->resolveEntry("FTBPacks", packoffset); - NetJob *job = new NetJob("Download FTB Pack"); - - entry->setStale(true); - QString url; - if(m_pack.type == PackType::Private) - { - url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset); - } - else - { - url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(packoffset); - } - job->addNetAction(Net::Download::makeCached(url, entry)); - archivePath = entry->getFullPath(); - - netJobContainer.reset(job); - connect(job, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); - connect(job, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); - connect(job, &NetJob::progress, this, &PackInstallTask::onDownloadProgress); - job->start(); - - progress(1, 4); -} - -void PackInstallTask::onDownloadSucceeded() -{ - abortable = false; - unzip(); -} - -void PackInstallTask::onDownloadFailed(QString reason) -{ - abortable = false; - emitFailed(reason); -} - -void PackInstallTask::onDownloadProgress(qint64 current, qint64 total) -{ - abortable = true; - progress(current, total * 4); - setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10)); -} - -void PackInstallTask::unzip() -{ - progress(2, 4); - setStatus(tr("Extracting modpack")); - QDir extractDir(m_stagingPath); - - m_packZip.reset(new QuaZip(archivePath)); - if(!m_packZip->open(QuaZip::mdUnzip)) - { - emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); - return; - } - - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled); - m_extractFutureWatcher.setFuture(m_extractFuture); -} - -void PackInstallTask::onUnzipFinished() -{ - install(); -} - -void PackInstallTask::onUnzipCanceled() -{ - emitAborted(); -} - -void PackInstallTask::install() -{ - progress(3, 4); - setStatus(tr("Installing modpack")); - QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); - if(unzipMcDir.exists()) - { - //ok, found minecraft dir, move contents to instance dir - if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) - { - emitFailed(tr("Failed to move unzipped minecraft!")); - return; - } - } - - QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath); - instanceSettings->suspendSave(); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); - - bool fallback = true; - - //handle different versions - QFile packJson(m_stagingPath + "/.minecraft/pack.json"); - QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); - if(packJson.exists()) - { - packJson.open(QIODevice::ReadOnly | QIODevice::Text); - QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); - packJson.close(); - - //we only care about the libs - QJsonArray libs = doc.object().value("libraries").toArray(); - - foreach (const QJsonValue &value, libs) - { - QString nameValue = value.toObject().value("name").toString(); - if(!nameValue.startsWith("net.minecraftforge")) - { - continue; - } - - GradleSpecifier forgeVersion(nameValue); - - components->setComponentVersion("net.minecraftforge", forgeVersion.version().replace(m_pack.mcVersion, "").replace("-", "")); - packJson.remove(); - fallback = false; - break; - } - - } - - if(jarmodDir.exists()) - { - qDebug() << "Found jarmods, installing..."; - - QStringList jarmods; - for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) - { - qDebug() << "Jarmod:" << info.fileName(); - jarmods.push_back(info.absoluteFilePath()); - } - - components->installJarMods(jarmods); - fallback = false; - } - - //just nuke unzip directory, it s not needed anymore - FS::deletePath(m_stagingPath + "/unzip"); - - if(fallback) - { - //TODO: Some fallback mechanism... or just keep failing! - emitFailed(tr("No installation method found!")); - return; - } - - components->saveNow(); - - progress(4, 4); - - instance.setName(m_instName); - if(m_instIcon == "default") - { - m_instIcon = "ftb_logo"; - } - instance.setIconKey(m_instIcon); - instanceSettings->resumeSave(); - - emitSucceeded(); -} - -bool PackInstallTask::abort() -{ - if(abortable) - { - return netJobContainer->abort(); - } - return false; -} - -} diff --git a/api/logic/modplatform/legacy_ftb/PackInstallTask.h b/api/logic/modplatform/legacy_ftb/PackInstallTask.h deleted file mode 100644 index f3515781..00000000 --- a/api/logic/modplatform/legacy_ftb/PackInstallTask.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once -#include "InstanceTask.h" -#include "net/NetJob.h" -#include "quazip.h" -#include "quazipdir.h" -#include "meta/Index.h" -#include "meta/Version.h" -#include "meta/VersionList.h" -#include "PackHelpers.h" - -#include <nonstd/optional> - -namespace LegacyFTB { - -class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask -{ - Q_OBJECT - -public: - explicit PackInstallTask(Modpack pack, QString version); - virtual ~PackInstallTask(){} - - bool canAbort() const override { return true; } - bool abort() override; - -protected: - //! Entry point for tasks. - virtual void executeTask() override; - -private: - void downloadPack(); - void unzip(); - void install(); - -private slots: - void onDownloadSucceeded(); - void onDownloadFailed(QString reason); - void onDownloadProgress(qint64 current, qint64 total); - - void onUnzipFinished(); - void onUnzipCanceled(); - -private: /* data */ - bool abortable = false; - std::unique_ptr<QuaZip> m_packZip; - QFuture<nonstd::optional<QStringList>> m_extractFuture; - QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher; - NetJobPtr netJobContainer; - QString archivePath; - - Modpack m_pack; - QString m_version; -}; - -} diff --git a/api/logic/modplatform/legacy_ftb/PrivatePackManager.cpp b/api/logic/modplatform/legacy_ftb/PrivatePackManager.cpp deleted file mode 100644 index 501e6003..00000000 --- a/api/logic/modplatform/legacy_ftb/PrivatePackManager.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "PrivatePackManager.h" - -#include <QDebug> - -#include "FileSystem.h" - -namespace LegacyFTB { - -void PrivatePackManager::load() -{ - try - { - currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet(); - dirty = false; - } - catch(...) - { - currentPacks = {}; - qWarning() << "Failed to read third party FTB pack codes from" << m_filename; - } -} - -void PrivatePackManager::save() const -{ - if(!dirty) - { - return; - } - try - { - QStringList list = currentPacks.toList(); - FS::write(m_filename, list.join('\n').toUtf8()); - dirty = false; - } - catch(...) - { - qWarning() << "Failed to write third party FTB pack codes to" << m_filename; - } -} - -} diff --git a/api/logic/modplatform/legacy_ftb/PrivatePackManager.h b/api/logic/modplatform/legacy_ftb/PrivatePackManager.h deleted file mode 100644 index 0232bac7..00000000 --- a/api/logic/modplatform/legacy_ftb/PrivatePackManager.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include <QSet> -#include <QString> -#include <QFile> -#include "multimc_logic_export.h" - -namespace LegacyFTB { - -class MULTIMC_LOGIC_EXPORT PrivatePackManager -{ -public: - ~PrivatePackManager() - { - save(); - } - void load(); - void save() const; - bool empty() const - { - return currentPacks.empty(); - } - const QSet<QString> &getCurrentPackCodes() const - { - return currentPacks; - } - void add(const QString &code) - { - currentPacks.insert(code); - dirty = true; - } - void remove(const QString &code) - { - currentPacks.remove(code); - dirty = true; - } - -private: - QSet<QString> currentPacks; - QString m_filename = "private_packs.txt"; - mutable bool dirty = false; -}; - -} diff --git a/api/logic/modplatform/modpacksch/FTBPackInstallTask.cpp b/api/logic/modplatform/modpacksch/FTBPackInstallTask.cpp deleted file mode 100644 index f22373bc..00000000 --- a/api/logic/modplatform/modpacksch/FTBPackInstallTask.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include "FTBPackInstallTask.h" - -#include "BuildConfig.h" -#include "Env.h" -#include "FileSystem.h" -#include "Json.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "net/ChecksumValidator.h" -#include "settings/INISettingsObject.h" - -namespace ModpacksCH { - -PackInstallTask::PackInstallTask(Modpack pack, QString version) -{ - m_pack = pack; - m_version_name = version; -} - -bool PackInstallTask::abort() -{ - if(abortable) - { - return jobPtr->abort(); - } - return false; -} - -void PackInstallTask::executeTask() -{ - // Find pack version - bool found = false; - VersionInfo version; - - for(auto vInfo : m_pack.versions) { - if (vInfo.name == m_version_name) { - found = true; - version = vInfo; - break; - } - } - - if(!found) { - emitFailed(tr("Failed to find pack version %1").arg(m_version_name)); - return; - } - - auto *netJob = new NetJob("ModpacksCH::VersionFetch"); - 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(); - - QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); - QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); -} - -void PackInstallTask::onDownloadSucceeded() -{ - 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 FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; - return; - } - - auto obj = doc.object(); - - ModpacksCH::Version version; - try - { - ModpacksCH::loadVersion(version, obj); - } - catch (const JSONValidationError &e) - { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - m_version = version; - - downloadPack(); -} - -void PackInstallTask::onDownloadFailed(QString reason) -{ - jobPtr.reset(); - emitFailed(reason); -} - -void PackInstallTask::downloadPack() -{ - setStatus(tr("Downloading mods...")); - - jobPtr.reset(new NetJob(tr("Mod download"))); - for(auto file : m_version.files) { - if(file.serverOnly) continue; - - QFileInfo fileName(file.name); - auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix(); - - auto entry = ENV.metacache()->resolveEntry("ModpacksCHPacks", cacheName); - entry->setStale(true); - - auto relpath = FS::PathCombine("minecraft", file.path, file.name); - auto path = FS::PathCombine(m_stagingPath, relpath); - - qDebug() << "Will download" << file.url << "to" << path; - filesToCopy[entry->getFullPath()] = path; - - 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); - }); - - jobPtr->start(); -} - -void PackInstallTask::install() -{ - setStatus(tr("Copying modpack files")); - - for (auto iter = filesToCopy.begin(); iter != filesToCopy.end(); iter++) { - auto &from = iter.key(); - auto &to = iter.value(); - FS::copy fileCopyOperation(from, to); - if(!fileCopyOperation()) { - qWarning() << "Failed to copy" << from << "to" << to; - emitFailed(tr("Failed to copy files")); - return; - } - } - - setStatus(tr("Installing modpack")); - - auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath); - instanceSettings->suspendSave(); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - - 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; - - if(target.name == "forge") { - components->setComponentVersion("net.minecraftforge", target.version, true); - } - else if(target.name == "fabric") { - components->setComponentVersion("net.fabricmc.fabric-loader", target.version, true); - } - } - - // install any jar mods - QDir jarModsDir(FS::PathCombine(m_stagingPath, "minecraft", "jarmods")); - if (jarModsDir.exists()) { - QStringList jarMods; - - for (const auto& info : jarModsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { - jarMods.push_back(info.absoluteFilePath()); - } - - components->installJarMods(jarMods); - } - - components->saveNow(); - - instance.setName(m_instName); - instance.setIconKey(m_instIcon); - instanceSettings->resumeSave(); - - emitSucceeded(); -} - -} diff --git a/api/logic/modplatform/modpacksch/FTBPackInstallTask.h b/api/logic/modplatform/modpacksch/FTBPackInstallTask.h deleted file mode 100644 index 55db3d3c..00000000 --- a/api/logic/modplatform/modpacksch/FTBPackInstallTask.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "FTBPackManifest.h" - -#include "InstanceTask.h" -#include "multimc_logic_export.h" -#include "net/NetJob.h" - -namespace ModpacksCH { - -class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask -{ - Q_OBJECT - -public: - explicit PackInstallTask(Modpack pack, QString version); - virtual ~PackInstallTask(){} - - bool canAbort() const override { return true; } - bool abort() override; - -protected: - virtual void executeTask() override; - -private slots: - void onDownloadSucceeded(); - void onDownloadFailed(QString reason); - -private: - void downloadPack(); - void install(); - -private: - bool abortable = false; - - NetJobPtr jobPtr; - QByteArray response; - - Modpack m_pack; - QString m_version_name; - Version m_version; - - QMap<QString, QString> filesToCopy; - -}; - -} diff --git a/api/logic/modplatform/modpacksch/FTBPackManifest.cpp b/api/logic/modplatform/modpacksch/FTBPackManifest.cpp deleted file mode 100644 index fd99d332..00000000 --- a/api/logic/modplatform/modpacksch/FTBPackManifest.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include "FTBPackManifest.h" - -#include "Json.h" - -static void loadSpecs(ModpacksCH::Specs & s, QJsonObject & obj) -{ - s.id = Json::requireInteger(obj, "id"); - s.minimum = Json::requireInteger(obj, "minimum"); - s.recommended = Json::requireInteger(obj, "recommended"); -} - -static void loadTag(ModpacksCH::Tag & t, QJsonObject & obj) -{ - t.id = Json::requireInteger(obj, "id"); - t.name = Json::requireString(obj, "name"); -} - -static void loadArt(ModpacksCH::Art & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.url = Json::requireString(obj, "url"); - a.type = Json::requireString(obj, "type"); - a.width = Json::requireInteger(obj, "width"); - a.height = Json::requireInteger(obj, "height"); - a.compressed = Json::requireBoolean(obj, "compressed"); - a.sha1 = Json::requireString(obj, "sha1"); - a.size = Json::requireInteger(obj, "size"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadAuthor(ModpacksCH::Author & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.name = Json::requireString(obj, "name"); - a.type = Json::requireString(obj, "type"); - a.website = Json::requireString(obj, "website"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionInfo(ModpacksCH::VersionInfo & v, QJsonObject & obj) -{ - v.id = Json::requireInteger(obj, "id"); - v.name = Json::requireString(obj, "name"); - v.type = Json::requireString(obj, "type"); - v.updated = Json::requireInteger(obj, "updated"); - auto specs = Json::requireObject(obj, "specs"); - loadSpecs(v.specs, specs); -} - -void ModpacksCH::loadModpack(ModpacksCH::Modpack & m, QJsonObject & obj) -{ - m.id = Json::requireInteger(obj, "id"); - m.name = Json::requireString(obj, "name"); - m.synopsis = Json::requireString(obj, "synopsis"); - m.description = Json::requireString(obj, "description"); - m.type = Json::requireString(obj, "type"); - m.featured = Json::requireBoolean(obj, "featured"); - m.installs = Json::requireInteger(obj, "installs"); - m.plays = Json::requireInteger(obj, "plays"); - m.updated = Json::requireInteger(obj, "updated"); - m.refreshed = Json::requireInteger(obj, "refreshed"); - auto artArr = Json::requireArray(obj, "art"); - for (QJsonValueRef artRaw : artArr) - { - auto artObj = Json::requireObject(artRaw); - ModpacksCH::Art art; - loadArt(art, artObj); - m.art.append(art); - } - auto authorArr = Json::requireArray(obj, "authors"); - for (QJsonValueRef authorRaw : authorArr) - { - auto authorObj = Json::requireObject(authorRaw); - ModpacksCH::Author author; - loadAuthor(author, authorObj); - m.authors.append(author); - } - auto versionArr = Json::requireArray(obj, "versions"); - for (QJsonValueRef versionRaw : versionArr) - { - auto versionObj = Json::requireObject(versionRaw); - ModpacksCH::VersionInfo version; - loadVersionInfo(version, versionObj); - m.versions.append(version); - } - auto tagArr = Json::requireArray(obj, "tags"); - for (QJsonValueRef tagRaw : tagArr) - { - auto tagObj = Json::requireObject(tagRaw); - ModpacksCH::Tag tag; - loadTag(tag, tagObj); - m.tags.append(tag); - } - m.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionTarget(ModpacksCH::VersionTarget & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.name = Json::requireString(obj, "name"); - a.type = Json::requireString(obj, "type"); - a.version = Json::requireString(obj, "version"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.type = Json::requireString(obj, "type"); - 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.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"); -} - -void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj) -{ - m.id = Json::requireInteger(obj, "id"); - m.parent = Json::requireInteger(obj, "parent"); - m.name = Json::requireString(obj, "name"); - m.type = Json::requireString(obj, "type"); - m.installs = Json::requireInteger(obj, "installs"); - m.plays = Json::requireInteger(obj, "plays"); - m.updated = Json::requireInteger(obj, "updated"); - m.refreshed = Json::requireInteger(obj, "refreshed"); - auto specs = Json::requireObject(obj, "specs"); - loadSpecs(m.specs, specs); - auto targetArr = Json::requireArray(obj, "targets"); - for (QJsonValueRef targetRaw : targetArr) - { - auto versionObj = Json::requireObject(targetRaw); - ModpacksCH::VersionTarget target; - loadVersionTarget(target, versionObj); - m.targets.append(target); - } - auto fileArr = Json::requireArray(obj, "files"); - for (QJsonValueRef fileRaw : fileArr) - { - auto fileObj = Json::requireObject(fileRaw); - ModpacksCH::VersionFile file; - loadVersionFile(file, fileObj); - m.files.append(file); - } -} - -//static void loadVersionChangelog(ModpacksCH::VersionChangelog & m, QJsonObject & obj) -//{ -// m.content = Json::requireString(obj, "content"); -// m.updated = Json::requireInteger(obj, "updated"); -//} diff --git a/api/logic/modplatform/modpacksch/FTBPackManifest.h b/api/logic/modplatform/modpacksch/FTBPackManifest.h deleted file mode 100644 index 518fffbf..00000000 --- a/api/logic/modplatform/modpacksch/FTBPackManifest.h +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once - -#include <QString> -#include <QVector> -#include <QUrl> -#include <QJsonObject> -#include <QMetaType> - -#include "multimc_logic_export.h" - -namespace ModpacksCH -{ - -struct Specs -{ - int id; - int minimum; - int recommended; -}; - -struct Tag -{ - int id; - QString name; -}; - -struct Art -{ - int id; - QString url; - QString type; - int width; - int height; - bool compressed; - QString sha1; - int size; - int64_t updated; -}; - -struct Author -{ - int id; - QString name; - QString type; - QString website; - int64_t updated; -}; - -struct VersionInfo -{ - int id; - QString name; - QString type; - int64_t updated; - Specs specs; -}; - -struct Modpack -{ - int id; - QString name; - QString synopsis; - QString description; - QString type; - bool featured; - int installs; - int plays; - int64_t updated; - int64_t refreshed; - QVector<Art> art; - QVector<Author> authors; - QVector<VersionInfo> versions; - QVector<Tag> tags; -}; - -struct VersionTarget -{ - int id; - QString type; - QString name; - QString version; - int64_t updated; -}; - -struct VersionFile -{ - int id; - QString type; - QString path; - QString name; - QString version; - QString url; - QString sha1; - int size; - bool clientOnly; - bool serverOnly; - bool optional; - int64_t updated; -}; - -struct Version -{ - int id; - int parent; - QString name; - QString type; - int installs; - int plays; - int64_t updated; - int64_t refreshed; - Specs specs; - QVector<VersionTarget> targets; - QVector<VersionFile> files; -}; - -struct VersionChangelog -{ - QString content; - int64_t updated; -}; - -MULTIMC_LOGIC_EXPORT void loadModpack(Modpack & m, QJsonObject & obj); - -MULTIMC_LOGIC_EXPORT void loadVersion(Version & m, QJsonObject & obj); -} - -Q_DECLARE_METATYPE(ModpacksCH::Modpack) diff --git a/api/logic/modplatform/technic/SingleZipPackInstallTask.cpp b/api/logic/modplatform/technic/SingleZipPackInstallTask.cpp deleted file mode 100644 index dbce8e53..00000000 --- a/api/logic/modplatform/technic/SingleZipPackInstallTask.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "SingleZipPackInstallTask.h" - -#include "Env.h" -#include "MMCZip.h" -#include "TechnicPackProcessor.h" - -#include <QtConcurrent> -#include <FileSystem.h> - -Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion) -{ - m_sourceUrl = sourceUrl; - m_minecraftVersion = minecraftVersion; -} - -bool Technic::SingleZipPackInstallTask::abort() { - if(m_abortable) - { - return m_filesNetJob->abort(); - } - return false; -} - -void Technic::SingleZipPackInstallTask::executeTask() -{ - setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); - - const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path(); - auto entry = ENV.metacache()->resolveEntry("general", path); - entry->setStale(true); - m_filesNetJob.reset(new NetJob(tr("Modpack download"))); - m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); - m_archivePath = entry->getFullPath(); - auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded); - connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged); - connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed); - m_filesNetJob->start(); -} - -void Technic::SingleZipPackInstallTask::downloadSucceeded() -{ - m_abortable = false; - - setStatus(tr("Extracting modpack")); - QDir extractDir(FS::PathCombine(m_stagingPath, ".minecraft")); - qDebug() << "Attempting to create instance from" << m_archivePath; - - // open the zip and find relevant files in it - m_packZip.reset(new QuaZip(m_archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) - { - emitFailed(tr("Unable to open supplied modpack zip file.")); - return; - } - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString(""), extractDir.absolutePath()); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SingleZipPackInstallTask::extractFinished); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &Technic::SingleZipPackInstallTask::extractAborted); - m_extractFutureWatcher.setFuture(m_extractFuture); - m_filesNetJob.reset(); -} - -void Technic::SingleZipPackInstallTask::downloadFailed(QString reason) -{ - m_abortable = false; - emitFailed(reason); - m_filesNetJob.reset(); -} - -void Technic::SingleZipPackInstallTask::downloadProgressChanged(qint64 current, qint64 total) -{ - m_abortable = true; - setProgress(current / 2, total); -} - -void Technic::SingleZipPackInstallTask::extractFinished() -{ - m_packZip.reset(); - if (!m_extractFuture.result()) - { - emitFailed(tr("Failed to extract modpack")); - return; - } - QDir extractDir(m_stagingPath); - - qDebug() << "Fixing permissions for extracted pack files..."; - QDirIterator it(extractDir, QDirIterator::Subdirectories); - while (it.hasNext()) - { - auto filepath = it.next(); - QFileInfo file(filepath); - auto permissions = QFile::permissions(filepath); - auto origPermissions = permissions; - if (file.isDir()) - { - // Folder +rwx for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; - } - else - { - // File +rw for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; - } - if (origPermissions != permissions) - { - if (!QFile::setPermissions(filepath, permissions)) - { - logWarning(tr("Could not fix permissions for %1").arg(filepath)); - } - else - { - qDebug() << "Fixed" << filepath; - } - } - } - - shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor(); - connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SingleZipPackInstallTask::emitSucceeded); - connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SingleZipPackInstallTask::emitFailed); - packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion); -} - -void Technic::SingleZipPackInstallTask::extractAborted() -{ - emitFailed(tr("Instance import has been aborted.")); -} diff --git a/api/logic/modplatform/technic/SingleZipPackInstallTask.h b/api/logic/modplatform/technic/SingleZipPackInstallTask.h deleted file mode 100644 index ec2ff605..00000000 --- a/api/logic/modplatform/technic/SingleZipPackInstallTask.h +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "InstanceTask.h" -#include "net/NetJob.h" -#include "multimc_logic_export.h" - -#include "quazip.h" - -#include <QFutureWatcher> -#include <QStringList> -#include <QUrl> - -#include <nonstd/optional> - -namespace Technic { - -class MULTIMC_LOGIC_EXPORT SingleZipPackInstallTask : public InstanceTask -{ - Q_OBJECT - -public: - SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion); - - bool canAbort() const override { return true; } - bool abort() override; - -protected: - void executeTask() override; - - -private slots: - void downloadSucceeded(); - void downloadFailed(QString reason); - void downloadProgressChanged(qint64 current, qint64 total); - void extractFinished(); - void extractAborted(); - -private: - bool m_abortable = false; - - QUrl m_sourceUrl; - QString m_minecraftVersion; - QString m_archivePath; - NetJobPtr m_filesNetJob; - std::unique_ptr<QuaZip> m_packZip; - QFuture<nonstd::optional<QStringList>> m_extractFuture; - QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher; -}; - -} // namespace Technic diff --git a/api/logic/modplatform/technic/SolderPackInstallTask.cpp b/api/logic/modplatform/technic/SolderPackInstallTask.cpp deleted file mode 100644 index 1b4186d4..00000000 --- a/api/logic/modplatform/technic/SolderPackInstallTask.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "SolderPackInstallTask.h" - -#include <FileSystem.h> -#include <Json.h> -#include <QtConcurrentRun> -#include <MMCZip.h> -#include "TechnicPackProcessor.h" - -Technic::SolderPackInstallTask::SolderPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion) -{ - m_sourceUrl = sourceUrl; - m_minecraftVersion = minecraftVersion; -} - -bool Technic::SolderPackInstallTask::abort() { - if(m_abortable) - { - return m_filesNetJob->abort(); - } - return false; -} - -void Technic::SolderPackInstallTask::executeTask() -{ - setStatus(tr("Finding recommended version:\n%1").arg(m_sourceUrl.toString())); - m_filesNetJob.reset(new NetJob(tr("Finding recommended version"))); - m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response)); - auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::versionSucceeded); - connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); - m_filesNetJob->start(); -} - -void Technic::SolderPackInstallTask::versionSucceeded() -{ - try - { - QJsonDocument doc = Json::requireDocument(m_response); - QJsonObject obj = Json::requireObject(doc); - QString version = Json::requireString(obj, "recommended", "__placeholder__"); - m_sourceUrl = m_sourceUrl.toString() + '/' + version; - } - catch (const JSONValidationError &e) - { - emitFailed(e.cause()); - m_filesNetJob.reset(); - return; - } - - setStatus(tr("Resolving modpack files:\n%1").arg(m_sourceUrl.toString())); - m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"))); - m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response)); - auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); - connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); - m_filesNetJob->start(); -} - -void Technic::SolderPackInstallTask::fileListSucceeded() -{ - setStatus(tr("Downloading modpack:")); - QStringList modUrls; - try - { - QJsonDocument doc = Json::requireDocument(m_response); - QJsonObject obj = Json::requireObject(doc); - QString minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__"); - if (!minecraftVersion.isEmpty()) - m_minecraftVersion = minecraftVersion; - QJsonArray mods = Json::requireArray(obj, "mods", "'mods'"); - for (auto mod: mods) - { - QJsonObject modObject = Json::requireObject(mod); - modUrls.append(Json::requireString(modObject, "url", "'url'")); - } - } - catch (const JSONValidationError &e) - { - emitFailed(e.cause()); - m_filesNetJob.reset(); - return; - } - m_filesNetJob.reset(new NetJob(tr("Downloading modpack"))); - int i = 0; - for (auto &modUrl: modUrls) - { - auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i)); - m_filesNetJob->addNetAction(Net::Download::makeFile(modUrl, path)); - i++; - } - - m_modCount = modUrls.size(); - - connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); - connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); - connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); - m_filesNetJob->start(); -} - -void Technic::SolderPackInstallTask::downloadSucceeded() -{ - m_abortable = false; - - setStatus(tr("Extracting modpack")); - m_filesNetJob.reset(); - m_extractFuture = QtConcurrent::run([this]() - { - int i = 0; - QString extractDir = FS::PathCombine(m_stagingPath, ".minecraft"); - FS::ensureFolderPathExists(extractDir); - - while (m_modCount > i) - { - auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i)); - if (!MMCZip::extractDir(path, extractDir)) - { - return false; - } - i++; - } - return true; - }); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SolderPackInstallTask::extractFinished); - connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &Technic::SolderPackInstallTask::extractAborted); - m_extractFutureWatcher.setFuture(m_extractFuture); -} - -void Technic::SolderPackInstallTask::downloadFailed(QString reason) -{ - m_abortable = false; - emitFailed(reason); - m_filesNetJob.reset(); -} - -void Technic::SolderPackInstallTask::downloadProgressChanged(qint64 current, qint64 total) -{ - m_abortable = true; - setProgress(current / 2, total); -} - -void Technic::SolderPackInstallTask::extractFinished() -{ - if (!m_extractFuture.result()) - { - emitFailed(tr("Failed to extract modpack")); - return; - } - QDir extractDir(m_stagingPath); - - qDebug() << "Fixing permissions for extracted pack files..."; - QDirIterator it(extractDir, QDirIterator::Subdirectories); - while (it.hasNext()) - { - auto filepath = it.next(); - QFileInfo file(filepath); - auto permissions = QFile::permissions(filepath); - auto origPermissions = permissions; - if(file.isDir()) - { - // Folder +rwx for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; - } - else - { - // File +rw for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; - } - if(origPermissions != permissions) - { - if(!QFile::setPermissions(filepath, permissions)) - { - logWarning(tr("Could not fix permissions for %1").arg(filepath)); - } - else - { - qDebug() << "Fixed" << filepath; - } - } - } - - shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor(); - connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SolderPackInstallTask::emitSucceeded); - connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SolderPackInstallTask::emitFailed); - packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion, true); -} - -void Technic::SolderPackInstallTask::extractAborted() -{ - emitFailed(tr("Instance import has been aborted.")); - return; -} - diff --git a/api/logic/modplatform/technic/SolderPackInstallTask.h b/api/logic/modplatform/technic/SolderPackInstallTask.h deleted file mode 100644 index 9f0f20a9..00000000 --- a/api/logic/modplatform/technic/SolderPackInstallTask.h +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <InstanceTask.h> -#include <net/NetJob.h> -#include <tasks/Task.h> - -#include <QUrl> - -namespace Technic -{ - class MULTIMC_LOGIC_EXPORT SolderPackInstallTask : public InstanceTask - { - Q_OBJECT - public: - explicit SolderPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion); - - bool canAbort() const override { return true; } - bool abort() override; - - protected: - //! Entry point for tasks. - virtual void executeTask() override; - - private slots: - void versionSucceeded(); - void fileListSucceeded(); - void downloadSucceeded(); - void downloadFailed(QString reason); - void downloadProgressChanged(qint64 current, qint64 total); - void extractFinished(); - void extractAborted(); - - private: - bool m_abortable = false; - - NetJobPtr m_filesNetJob; - QUrl m_sourceUrl; - QString m_minecraftVersion; - QByteArray m_response; - QTemporaryDir m_outputDir; - int m_modCount; - QFuture<bool> m_extractFuture; - QFutureWatcher<bool> m_extractFutureWatcher; - }; -} diff --git a/api/logic/modplatform/technic/TechnicPackProcessor.cpp b/api/logic/modplatform/technic/TechnicPackProcessor.cpp deleted file mode 100644 index 52979b7c..00000000 --- a/api/logic/modplatform/technic/TechnicPackProcessor.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/* Copyright 2020-2021 MultiMC Contributors - * - * 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 "TechnicPackProcessor.h" - -#include <FileSystem.h> -#include <Json.h> -#include <minecraft/MinecraftInstance.h> -#include <minecraft/PackProfile.h> -#include <quazip.h> -#include <quazipdir.h> -#include <quazipfile.h> -#include <settings/INISettingsObject.h> - -#include <memory> - -void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const QString &instName, const QString &instIcon, const QString &stagingPath, const QString &minecraftVersion, const bool isSolder) -{ - QString minecraftPath = FS::PathCombine(stagingPath, ".minecraft"); - QString configPath = FS::PathCombine(stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared<INISettingsObject>(configPath); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - MinecraftInstance instance(globalSettings, instanceSettings, stagingPath); - - instance.setName(instName); - - if (instIcon != "default") - { - instance.setIconKey(instIcon); - } - - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - - QByteArray data; - - QString modpackJar = FS::PathCombine(minecraftPath, "bin", "modpack.jar"); - QString versionJson = FS::PathCombine(minecraftPath, "bin", "version.json"); - QString fmlMinecraftVersion; - if (QFile::exists(modpackJar)) - { - QuaZip zipFile(modpackJar); - if (!zipFile.open(QuaZip::mdUnzip)) - { - emit failed(tr("Unable to open \"bin/modpack.jar\" file!")); - return; - } - QuaZipDir zipFileRoot(&zipFile, "/"); - if (zipFileRoot.exists("/version.json")) - { - if (zipFileRoot.exists("/fmlversion.properties")) - { - zipFile.setCurrentFile("fmlversion.properties"); - QuaZipFile file(&zipFile); - if (!file.open(QIODevice::ReadOnly)) - { - emit failed(tr("Unable to open \"fmlversion.properties\"!")); - return; - } - QByteArray fmlVersionData = file.readAll(); - file.close(); - INIFile iniFile; - iniFile.loadFile(fmlVersionData); - // If not present, this evaluates to a null string - fmlMinecraftVersion = iniFile["fmlbuild.mcversion"].toString(); - } - zipFile.setCurrentFile("version.json", QuaZip::csSensitive); - QuaZipFile file(&zipFile); - if (!file.open(QIODevice::ReadOnly)) - { - emit failed(tr("Unable to open \"version.json\"!")); - return; - } - data = file.readAll(); - file.close(); - } - else - { - if (minecraftVersion.isEmpty()) - emit failed(tr("Could not find \"version.json\" inside \"bin/modpack.jar\", but minecraft version is unknown")); - components->setComponentVersion("net.minecraft", minecraftVersion, true); - components->installJarMods({modpackJar}); - - // Forge for 1.4.7 and for 1.5.2 require extra libraries. - // Figure out the forge version and add it as a component - // (the code still comes from the jar mod installed above) - if (zipFileRoot.exists("/forgeversion.properties")) - { - zipFile.setCurrentFile("forgeversion.properties", QuaZip::csSensitive); - QuaZipFile file(&zipFile); - if (!file.open(QIODevice::ReadOnly)) - { - // Really shouldn't happen, but error handling shall not be forgotten - emit failed(tr("Unable to open \"forgeversion.properties\"")); - return; - } - QByteArray forgeVersionData = file.readAll(); - file.close(); - INIFile iniFile; - iniFile.loadFile(forgeVersionData); - QString major, minor, revision, build; - major = iniFile["forge.major.number"].toString(); - minor = iniFile["forge.minor.number"].toString(); - revision = iniFile["forge.revision.number"].toString(); - build = iniFile["forge.build.number"].toString(); - - if (major.isEmpty() || minor.isEmpty() || revision.isEmpty() || build.isEmpty()) - { - emit failed(tr("Invalid \"forgeversion.properties\"!")); - return; - } - - components->setComponentVersion("net.minecraftforge", major + '.' + minor + '.' + revision + '.' + build); - } - - components->saveNow(); - emit succeeded(); - return; - } - } - else if (QFile::exists(versionJson)) - { - QFile file(versionJson); - if (!file.open(QIODevice::ReadOnly)) - { - emit failed(tr("Unable to open \"version.json\"!")); - return; - } - data = file.readAll(); - file.close(); - } - else - { - // This is the "Vanilla" modpack, excluded by the search code - emit failed(tr("Unable to find a \"version.json\"!")); - return; - } - - try - { - QJsonDocument doc = Json::requireDocument(data); - QJsonObject root = Json::requireObject(doc, "version.json"); - QString minecraftVersion = Json::ensureString(root, "inheritsFrom", QString(), ""); - if (minecraftVersion.isEmpty()) - { - if (fmlMinecraftVersion.isEmpty()) - { - emit failed(tr("Could not understand \"version.json\":\ninheritsFrom is missing")); - return; - } - minecraftVersion = fmlMinecraftVersion; - } - components->setComponentVersion("net.minecraft", minecraftVersion, true); - for (auto library: Json::ensureArray(root, "libraries", {})) - { - if (!library.isObject()) - { - continue; - } - - auto libraryObject = Json::ensureObject(library, {}, ""); - auto libraryName = Json::ensureString(libraryObject, "name", "", ""); - - if (libraryName.startsWith("net.minecraftforge:forge:") && libraryName.contains('-')) - { - QString libraryVersion = libraryName.section(':', 2); - if (!libraryVersion.startsWith("1.7.10-")) - { - components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1)); - } - else - { - // 1.7.10 versions sometimes look like 1.7.10-10.13.4.1614-1.7.10, this filters out the 10.13.4.1614 part - components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1)); - } - } - else if (libraryName.startsWith("net.minecraftforge:minecraftforge:")) - { - components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2)); - } - else if (libraryName.startsWith("net.fabricmc:fabric-loader:")) - { - components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2)); - } - } - } - catch (const JSONValidationError &e) - { - emit failed(tr("Could not understand \"version.json\":\n") + e.cause()); - return; - } - - components->saveNow(); - emit succeeded(); -} diff --git a/api/logic/modplatform/technic/TechnicPackProcessor.h b/api/logic/modplatform/technic/TechnicPackProcessor.h deleted file mode 100644 index 2ad803b3..00000000 --- a/api/logic/modplatform/technic/TechnicPackProcessor.h +++ /dev/null @@ -1,35 +0,0 @@ -/* Copyright 2020-2021 MultiMC Contributors - * - * 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 <QString> -#include "settings/SettingsObject.h" - -namespace Technic -{ - // not exporting it, only used in SingleZipPackInstallTask, InstanceImportTask and SolderPackInstallTask - class TechnicPackProcessor : public QObject - { - Q_OBJECT - - signals: - void succeeded(); - void failed(QString reason); - - public: - void run(SettingsObjectPtr globalSettings, const QString &instName, const QString &instIcon, const QString &stagingPath, const QString &minecraftVersion=QString(), const bool isSolder = false); - }; -} diff --git a/api/logic/mojang/PackageManifest.cpp b/api/logic/mojang/PackageManifest.cpp deleted file mode 100644 index b3dfd7fc..00000000 --- a/api/logic/mojang/PackageManifest.cpp +++ /dev/null @@ -1,427 +0,0 @@ -#include "PackageManifest.h" -#include <Json.h> -#include <QDir> -#include <QDirIterator> -#include <QCryptographicHash> -#include <QDebug> - -#ifndef Q_OS_WIN32 -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#endif - -namespace mojang_files { - -const Hash hash_of_empty_string = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; - -int Path::compare(const Path& rhs) const -{ - auto left_cursor = begin(); - auto left_end = end(); - auto right_cursor = rhs.begin(); - auto right_end = rhs.end(); - - while (left_cursor != left_end && right_cursor != right_end) - { - if(*left_cursor < *right_cursor) - { - return -1; - } - else if(*left_cursor > *right_cursor) - { - return 1; - } - left_cursor++; - right_cursor++; - } - - if(left_cursor == left_end) - { - if(right_cursor == right_end) - { - return 0; - } - return -1; - } - return 1; -} - -void Package::addFile(const Path& path, const File& file) { - addFolder(path.parent_path()); - files[path] = file; -} - -void Package::addFolder(Path folder) { - if(!folder.has_parent_path()) { - return; - } - do { - folders.insert(folder); - folder = folder.parent_path(); - } while(folder.has_parent_path()); -} - -void Package::addLink(const Path& path, const Path& target) { - addFolder(path.parent_path()); - symlinks[path] = target; -} - -void Package::addSource(const FileSource& source) { - sources[source.hash] = source; -} - - -namespace { -void fromJson(QJsonDocument & doc, Package & out) { - std::set<Path> seen_paths; - if (!doc.isObject()) - { - throw JSONValidationError("file manifest is not an object"); - } - QJsonObject root = doc.object(); - - auto filesObj = Json::ensureObject(root, "files"); - auto iter = filesObj.begin(); - while (iter != filesObj.end()) - { - Path objectPath = Path(iter.key()); - auto value = iter.value(); - iter++; - if(seen_paths.count(objectPath)) { - throw JSONValidationError("duplicate path inside manifest, the manifest is invalid"); - } - if (!value.isObject()) - { - throw JSONValidationError("file entry inside manifest is not an an object"); - } - seen_paths.insert(objectPath); - - auto fileObject = value.toObject(); - auto type = Json::requireString(fileObject, "type"); - if(type == "directory") { - out.addFolder(objectPath); - continue; - } - else if(type == "file") { - FileSource bestSource; - File file; - file.executable = Json::ensureBoolean(fileObject, QString("executable"), false); - auto downloads = Json::requireObject(fileObject, "downloads"); - for(auto iter2 = downloads.begin(); iter2 != downloads.end(); iter2++) { - FileSource source; - - auto downloadObject = Json::requireObject(iter2.value()); - source.hash = Json::requireString(downloadObject, "sha1"); - source.size = Json::requireInteger(downloadObject, "size"); - source.url = Json::requireString(downloadObject, "url"); - - auto compression = iter2.key(); - if(compression == "raw") { - file.hash = source.hash; - file.size = source.size; - source.compression = Compression::Raw; - } - else if (compression == "lzma") { - source.compression = Compression::Lzma; - } - else { - continue; - } - bestSource.upgrade(source); - } - if(bestSource.isBad()) { - throw JSONValidationError("No valid compression method for file " + iter.key()); - } - out.addFile(objectPath, file); - out.addSource(bestSource); - } - else if(type == "link") { - auto target = Json::requireString(fileObject, "target"); - out.symlinks[objectPath] = target; - out.addLink(objectPath, target); - } - else { - throw JSONValidationError("Invalid item type in manifest: " + type); - } - } - // make sure the containing folder exists - out.folders.insert(Path()); -} -} - -Package Package::fromManifestContents(const QByteArray& contents) -{ - Package out; - try - { - auto doc = Json::requireDocument(contents, "Manifest"); - fromJson(doc, out); - return out; - } - catch (const Exception &e) - { - qDebug() << QString("Unable to parse manifest: %1").arg(e.cause()); - out.valid = false; - return out; - } -} - -Package Package::fromManifestFile(const QString & filename) { - Package out; - try - { - auto doc = Json::requireDocument(filename, filename); - fromJson(doc, out); - return out; - } - catch (const Exception &e) - { - qDebug() << QString("Unable to parse manifest file %1: %2").arg(filename, e.cause()); - out.valid = false; - return out; - } -} - -#ifndef Q_OS_WIN32 - -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> - -namespace { -// FIXME: Qt obscures symlink targets by making them absolute. that is useless. this is the workaround - we do it ourselves -bool actually_read_symlink_target(const QString & filepath, Path & out) -{ - struct ::stat st; - // FIXME: here, we assume the native filesystem encoding. May the Gods have mercy upon our Souls. - QByteArray nativePath = filepath.toUtf8(); - const char * filepath_cstr = nativePath.data(); - - if (lstat(filepath_cstr, &st) != 0) - { - return false; - } - - auto size = st.st_size ? st.st_size + 1 : PATH_MAX; - std::string temp(size, '\0'); - // because we don't realiably know how long the damn thing actually is, we loop and expand. POSIX is naff - do - { - auto link_length = ::readlink(filepath_cstr, &temp[0], temp.size()); - if(link_length == -1) - { - return false; - } - if(std::string::size_type(link_length) < temp.size()) - { - // buffer was long enough and we managed to read the link target. RETURN here. - temp.resize(link_length); - out = Path(QString::fromUtf8(temp.c_str())); - return true; - } - temp.resize(temp.size() * 2); - } while (true); -} -} -#endif - -// FIXME: Qt filesystem abstraction is bad, but ... let's hope it doesn't break too much? -// FIXME: The error handling is just DEFICIENT -Package Package::fromInspectedFolder(const QString& folderPath) -{ - QDir root(folderPath); - - Package out; - QDirIterator iterator(folderPath, QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden, QDirIterator::Subdirectories); - while(iterator.hasNext()) { - iterator.next(); - - auto fileInfo = iterator.fileInfo(); - auto relPath = root.relativeFilePath(fileInfo.filePath()); - // FIXME: this is probably completely busted on Windows anyway, so just disable it. - // Qt makes shit up and doesn't understand the platform details - // TODO: Actually use a filesystem library that isn't terrible and has decen license. - // I only know one, and I wrote it. Sadly, currently proprietary. PAIN. -#ifndef Q_OS_WIN32 - if(fileInfo.isSymLink()) { - Path targetPath; - if(!actually_read_symlink_target(fileInfo.filePath(), targetPath)) { - qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath(); - out.valid = false; - } - out.addLink(relPath, targetPath); - } - else -#endif - if(fileInfo.isDir()) { - out.addFolder(relPath); - } - else if(fileInfo.isFile()) { - File f; - f.executable = fileInfo.isExecutable(); - f.size = fileInfo.size(); - // FIXME: async / optimize the hashing - QFile input(fileInfo.absoluteFilePath()); - if(!input.open(QIODevice::ReadOnly)) { - qCritical() << "Folder inspection: Failed to open file:" << fileInfo.absoluteFilePath(); - out.valid = false; - break; - } - f.hash = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1).toHex().constData(); - out.addFile(relPath, f); - } - else { - // Something else... oh my - qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath(); - out.valid = false; - break; - } - } - out.folders.insert(Path(".")); - out.valid = true; - return out; -} - -namespace { -struct shallow_first_sort -{ - bool operator()(const Path &lhs, const Path &rhs) const - { - auto lhs_depth = lhs.length(); - auto rhs_depth = rhs.length(); - if(lhs_depth < rhs_depth) - { - return true; - } - else if(lhs_depth == rhs_depth) - { - if(lhs < rhs) - { - return true; - } - } - return false; - } -}; - -struct deep_first_sort -{ - bool operator()(const Path &lhs, const Path &rhs) const - { - auto lhs_depth = lhs.length(); - auto rhs_depth = rhs.length(); - if(lhs_depth > rhs_depth) - { - return true; - } - else if(lhs_depth == rhs_depth) - { - if(lhs < rhs) - { - return true; - } - } - return false; - } -}; -} - -UpdateOperations UpdateOperations::resolve(const Package& from, const Package& to) -{ - UpdateOperations out; - - if(!from.valid || !to.valid) { - out.valid = false; - return out; - } - - // Files - for(auto iter = from.files.begin(); iter != from.files.end(); iter++) { - const auto ¤t_hash = iter->second.hash; - const auto ¤t_executable = iter->second.executable; - const auto &path = iter->first; - - auto iter2 = to.files.find(path); - if(iter2 == to.files.end()) { - // removed - out.deletes.push_back(path); - continue; - } - auto new_hash = iter2->second.hash; - auto new_executable = iter2->second.executable; - if (current_hash != new_hash) { - out.deletes.push_back(path); - out.downloads.emplace( - std::pair<Path, FileDownload>{ - path, - FileDownload(to.sources.at(iter2->second.hash), iter2->second.executable) - } - ); - } - else if (current_executable != new_executable) { - out.executable_fixes[path] = new_executable; - } - } - for(auto iter = to.files.begin(); iter != to.files.end(); iter++) { - auto path = iter->first; - if(!from.files.count(path)) { - out.downloads.emplace( - std::pair<Path, FileDownload>{ - path, - FileDownload(to.sources.at(iter->second.hash), iter->second.executable) - } - ); - } - } - - // Folders - std::set<Path, deep_first_sort> remove_folders; - std::set<Path, shallow_first_sort> make_folders; - for(auto from_path: from.folders) { - auto iter = to.folders.find(from_path); - if(iter == to.folders.end()) { - remove_folders.insert(from_path); - } - } - for(auto & rmdir: remove_folders) { - out.rmdirs.push_back(rmdir); - } - for(auto to_path: to.folders) { - auto iter = from.folders.find(to_path); - if(iter == from.folders.end()) { - make_folders.insert(to_path); - } - } - for(auto & mkdir: make_folders) { - out.mkdirs.push_back(mkdir); - } - - // Symlinks - for(auto iter = from.symlinks.begin(); iter != from.symlinks.end(); iter++) { - const auto ¤t_target = iter->second; - const auto &path = iter->first; - - auto iter2 = to.symlinks.find(path); - if(iter2 == to.symlinks.end()) { - // removed - out.deletes.push_back(path); - continue; - } - const auto &new_target = iter2->second; - if (current_target != new_target) { - out.deletes.push_back(path); - out.mklinks[path] = iter2->second; - } - } - for(auto iter = to.symlinks.begin(); iter != to.symlinks.end(); iter++) { - auto path = iter->first; - if(!from.symlinks.count(path)) { - out.mklinks[path] = iter->second; - } - } - out.valid = true; - return out; -} - -} diff --git a/api/logic/mojang/PackageManifest.h b/api/logic/mojang/PackageManifest.h deleted file mode 100644 index d01a0554..00000000 --- a/api/logic/mojang/PackageManifest.h +++ /dev/null @@ -1,173 +0,0 @@ -#pragma once - -#include <QString> -#include <map> -#include <set> -#include <QStringList> -#include "tasks/Task.h" - -#include "multimc_logic_export.h" - -namespace mojang_files { - -using Hash = QString; -extern const Hash empty_hash; - -// simple-ish path implementation. assumes always relative and does not allow '..' entries -class MULTIMC_LOGIC_EXPORT Path -{ -public: - using parts_type = QStringList; - - Path() = default; - Path(QString string) { - auto parts_in = string.split('/'); - for(auto & part: parts_in) { - if(part.isEmpty() || part == ".") { - continue; - } - if(part == "..") { - if(parts.size()) { - parts.pop_back(); - } - continue; - } - parts.push_back(part); - } - } - - bool has_parent_path() const - { - return parts.size() > 0; - } - - Path parent_path() const - { - if (parts.empty()) - return Path(); - return Path(parts.begin(), std::prev(parts.end())); - } - - bool empty() const - { - return parts.empty(); - } - - int length() const - { - return parts.length(); - } - - bool operator==(const Path & rhs) const { - return parts == rhs.parts; - } - - bool operator!=(const Path & rhs) const { - return parts != rhs.parts; - } - - inline bool operator<(const Path& rhs) const - { - return compare(rhs) < 0; - } - - parts_type::const_iterator begin() const - { - return parts.begin(); - } - - parts_type::const_iterator end() const - { - return parts.end(); - } - - QString toString() const { - return parts.join("/"); - } - -private: - Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) { - auto cursor = start; - while(cursor != end) { - parts.push_back(*cursor); - cursor++; - } - } - int compare(const Path& p) const; - - parts_type parts; -}; - - -enum class Compression { - Raw, - Lzma, - Unknown -}; - - -struct MULTIMC_LOGIC_EXPORT FileSource -{ - Compression compression = Compression::Unknown; - Hash hash; - QString url; - std::size_t size = 0; - void upgrade(const FileSource & other) { - if(compression == Compression::Unknown || other.size < size) { - *this = other; - } - } - bool isBad() const { - return compression == Compression::Unknown; - } -}; - -struct MULTIMC_LOGIC_EXPORT File -{ - Hash hash; - bool executable; - std::uint64_t size = 0; -}; - -struct MULTIMC_LOGIC_EXPORT Package { - static Package fromInspectedFolder(const QString &folderPath); - static Package fromManifestFile(const QString &path); - static Package fromManifestContents(const QByteArray& contents); - - explicit operator bool() const - { - return valid; - } - void addFolder(Path folder); - void addFile(const Path & path, const File & file); - void addLink(const Path & path, const Path & target); - void addSource(const FileSource & source); - - std::map<Hash, FileSource> sources; - bool valid = true; - std::set<Path> folders; - std::map<Path, File> files; - std::map<Path, Path> symlinks; -}; - -struct MULTIMC_LOGIC_EXPORT FileDownload : FileSource -{ - FileDownload(const FileSource& source, bool executable) { - static_cast<FileSource &> (*this) = source; - this->executable = executable; - } - bool executable = false; -}; - -struct MULTIMC_LOGIC_EXPORT UpdateOperations { - static UpdateOperations resolve(const Package & from, const Package & to); - bool valid = false; - std::vector<Path> deletes; - std::vector<Path> rmdirs; - std::vector<Path> mkdirs; - std::map<Path, FileDownload> downloads; - std::map<Path, Path> mklinks; - std::map<Path, bool> executable_fixes; -}; - -} diff --git a/api/logic/mojang/PackageManifest_test.cpp b/api/logic/mojang/PackageManifest_test.cpp deleted file mode 100644 index d4c55c5a..00000000 --- a/api/logic/mojang/PackageManifest_test.cpp +++ /dev/null @@ -1,344 +0,0 @@ -#include <QTest> -#include <QDebug> -#include "TestUtil.h" - -#include "mojang/PackageManifest.h" - -using namespace mojang_files; - -QDebug operator<<(QDebug debug, const Path &path) -{ - debug << path.toString(); - return debug; -} - -class PackageManifestTest : public QObject -{ - Q_OBJECT - -private slots: - void test_parse(); - void test_parse_file(); - void test_inspect(); -#ifndef Q_OS_WIN32 - void test_inspect_symlinks(); -#endif - void mkdir_deep(); - void rmdir_deep(); - - void identical_file(); - void changed_file(); - void added_file(); - void removed_file(); -}; - -namespace { -QByteArray basic_manifest = R"END( -{ - "files": { - "a/b.txt": { - "type": "file", - "downloads": { - "raw": { - "url": "http://dethware.org/b.txt", - "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", - "size": 0 - } - }, - "executable": true - }, - "a/b/c": { - "type": "directory" - }, - "a/b/c.txt": { - "type": "link", - "target": "../b.txt" - } - } -} -)END"; -} - -void PackageManifestTest::test_parse() -{ - auto manifest = Package::fromManifestContents(basic_manifest); - QVERIFY(manifest.valid == true); - QVERIFY(manifest.files.size() == 1); - QVERIFY(manifest.files.count(Path("a/b.txt"))); - auto &file = manifest.files[Path("a/b.txt")]; - QVERIFY(file.executable == true); - QVERIFY(file.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); - QVERIFY(file.size == 0); - QVERIFY(manifest.folders.size() == 4); - QVERIFY(manifest.folders.count(Path("."))); - QVERIFY(manifest.folders.count(Path("a"))); - QVERIFY(manifest.folders.count(Path("a/b"))); - QVERIFY(manifest.folders.count(Path("a/b/c"))); - QVERIFY(manifest.symlinks.size() == 1); - auto symlinkPath = Path("a/b/c.txt"); - QVERIFY(manifest.symlinks.count(symlinkPath)); - auto &symlink = manifest.symlinks[symlinkPath]; - QVERIFY(symlink == Path("../b.txt")); - QVERIFY(manifest.sources.size() == 1); -} - -void PackageManifestTest::test_parse_file() { - auto path = QFINDTESTDATA("testdata/1.8.0_202-x64.json"); - auto manifest = Package::fromManifestFile(path); - QVERIFY(manifest.valid == true); -} - - -void PackageManifestTest::test_inspect() { - auto path = QFINDTESTDATA("testdata/inspect_win/"); - auto manifest = Package::fromInspectedFolder(path); - QVERIFY(manifest.valid == true); - QVERIFY(manifest.files.size() == 2); - QVERIFY(manifest.files.count(Path("a/b.txt"))); - auto &file1 = manifest.files[Path("a/b.txt")]; - QVERIFY(file1.executable == false); - QVERIFY(file1.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); - QVERIFY(file1.size == 0); - QVERIFY(manifest.files.count(Path("a/b/b.txt"))); - auto &file2 = manifest.files[Path("a/b/b.txt")]; - QVERIFY(file2.executable == false); - QVERIFY(file2.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); - QVERIFY(file2.size == 0); - QVERIFY(manifest.folders.size() == 3); - QVERIFY(manifest.folders.count(Path("."))); - QVERIFY(manifest.folders.count(Path("a"))); - QVERIFY(manifest.folders.count(Path("a/b"))); - QVERIFY(manifest.symlinks.size() == 0); -} - -#ifndef Q_OS_WIN32 -void PackageManifestTest::test_inspect_symlinks() { - auto path = QFINDTESTDATA("testdata/inspect/"); - auto manifest = Package::fromInspectedFolder(path); - QVERIFY(manifest.valid == true); - QVERIFY(manifest.files.size() == 1); - QVERIFY(manifest.files.count(Path("a/b.txt"))); - auto &file = manifest.files[Path("a/b.txt")]; - QVERIFY(file.executable == true); - QVERIFY(file.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); - QVERIFY(file.size == 0); - QVERIFY(manifest.folders.size() == 3); - QVERIFY(manifest.folders.count(Path("."))); - QVERIFY(manifest.folders.count(Path("a"))); - QVERIFY(manifest.folders.count(Path("a/b"))); - QVERIFY(manifest.symlinks.size() == 1); - QVERIFY(manifest.symlinks.count(Path("a/b/b.txt"))); - qDebug() << manifest.symlinks[Path("a/b/b.txt")]; - QVERIFY(manifest.symlinks[Path("a/b/b.txt")] == Path("../b.txt")); -} -#endif - -void PackageManifestTest::mkdir_deep() { - - Package from; - auto to = Package::fromManifestContents(R"END( -{ - "files": { - "a/b/c/d/e": { - "type": "directory" - } - } -} -)END"); - auto operations = UpdateOperations::resolve(from, to); - QVERIFY(operations.deletes.size() == 0); - QVERIFY(operations.rmdirs.size() == 0); - - QVERIFY(operations.mkdirs.size() == 6); - QVERIFY(operations.mkdirs[0] == Path(".")); - QVERIFY(operations.mkdirs[1] == Path("a")); - QVERIFY(operations.mkdirs[2] == Path("a/b")); - QVERIFY(operations.mkdirs[3] == Path("a/b/c")); - QVERIFY(operations.mkdirs[4] == Path("a/b/c/d")); - QVERIFY(operations.mkdirs[5] == Path("a/b/c/d/e")); - - QVERIFY(operations.downloads.size() == 0); - QVERIFY(operations.mklinks.size() == 0); - QVERIFY(operations.executable_fixes.size() == 0); -} - -void PackageManifestTest::rmdir_deep() { - - Package to; - auto from = Package::fromManifestContents(R"END( -{ - "files": { - "a/b/c/d/e": { - "type": "directory" - } - } -} -)END"); - auto operations = UpdateOperations::resolve(from, to); - QVERIFY(operations.deletes.size() == 0); - - QVERIFY(operations.rmdirs.size() == 6); - QVERIFY(operations.rmdirs[0] == Path("a/b/c/d/e")); - QVERIFY(operations.rmdirs[1] == Path("a/b/c/d")); - QVERIFY(operations.rmdirs[2] == Path("a/b/c")); - QVERIFY(operations.rmdirs[3] == Path("a/b")); - QVERIFY(operations.rmdirs[4] == Path("a")); - QVERIFY(operations.rmdirs[5] == Path(".")); - - QVERIFY(operations.mkdirs.size() == 0); - QVERIFY(operations.downloads.size() == 0); - QVERIFY(operations.mklinks.size() == 0); - QVERIFY(operations.executable_fixes.size() == 0); -} - -void PackageManifestTest::identical_file() { - QByteArray manifest = R"END( -{ - "files": { - "a/b/c/d/empty.txt": { - "type": "file", - "downloads": { - "raw": { - "url": "http://dethware.org/empty.txt", - "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", - "size": 0 - } - }, - "executable": false - } - } -} -)END"; - auto from = Package::fromManifestContents(manifest); - auto to = Package::fromManifestContents(manifest); - auto operations = UpdateOperations::resolve(from, to); - QVERIFY(operations.deletes.size() == 0); - QVERIFY(operations.rmdirs.size() == 0); - QVERIFY(operations.mkdirs.size() == 0); - QVERIFY(operations.downloads.size() == 0); - QVERIFY(operations.mklinks.size() == 0); - QVERIFY(operations.executable_fixes.size() == 0); -} - -void PackageManifestTest::changed_file() { - auto from = Package::fromManifestContents(R"END( -{ - "files": { - "a/b/c/d/file": { - "type": "file", - "downloads": { - "raw": { - "url": "http://dethware.org/empty.txt", - "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", - "size": 0 - } - }, - "executable": false - } - } -} -)END"); - auto to = Package::fromManifestContents(R"END( -{ - "files": { - "a/b/c/d/file": { - "type": "file", - "downloads": { - "raw": { - "url": "http://dethware.org/space.txt", - "sha1": "dd122581c8cd44d0227f9c305581ffcb4b6f1b46", - "size": 1 - } - }, - "executable": false - } - } -} -)END"); - auto operations = UpdateOperations::resolve(from, to); - QVERIFY(operations.deletes.size() == 1); - QCOMPARE(operations.deletes[0], Path("a/b/c/d/file")); - QVERIFY(operations.rmdirs.size() == 0); - QVERIFY(operations.mkdirs.size() == 0); - QVERIFY(operations.downloads.size() == 1); - QVERIFY(operations.mklinks.size() == 0); - QVERIFY(operations.executable_fixes.size() == 0); -} - -void PackageManifestTest::added_file() { - auto from = Package::fromManifestContents(R"END( -{ - "files": { - "a/b/c/d": { - "type": "directory" - } - } -} -)END"); - auto to = Package::fromManifestContents(R"END( -{ - "files": { - "a/b/c/d/file": { - "type": "file", - "downloads": { - "raw": { - "url": "http://dethware.org/space.txt", - "sha1": "dd122581c8cd44d0227f9c305581ffcb4b6f1b46", - "size": 1 - } - }, - "executable": false - } - } -} -)END"); - auto operations = UpdateOperations::resolve(from, to); - QVERIFY(operations.deletes.size() == 0); - QVERIFY(operations.rmdirs.size() == 0); - QVERIFY(operations.mkdirs.size() == 0); - QVERIFY(operations.downloads.size() == 1); - QVERIFY(operations.mklinks.size() == 0); - QVERIFY(operations.executable_fixes.size() == 0); -} - -void PackageManifestTest::removed_file() { - auto from = Package::fromManifestContents(R"END( -{ - "files": { - "a/b/c/d/file": { - "type": "file", - "downloads": { - "raw": { - "url": "http://dethware.org/space.txt", - "sha1": "dd122581c8cd44d0227f9c305581ffcb4b6f1b46", - "size": 1 - } - }, - "executable": false - } - } -} -)END"); - auto to = Package::fromManifestContents(R"END( -{ - "files": { - "a/b/c/d": { - "type": "directory" - } - } -} -)END"); - auto operations = UpdateOperations::resolve(from, to); - QVERIFY(operations.deletes.size() == 1); - QCOMPARE(operations.deletes[0], Path("a/b/c/d/file")); - QVERIFY(operations.rmdirs.size() == 0); - QVERIFY(operations.mkdirs.size() == 0); - QVERIFY(operations.downloads.size() == 0); - QVERIFY(operations.mklinks.size() == 0); - QVERIFY(operations.executable_fixes.size() == 0); -} - -QTEST_GUILESS_MAIN(PackageManifestTest) - -#include "PackageManifest_test.moc" - diff --git a/api/logic/mojang/testdata/1.8.0_202-x64.json b/api/logic/mojang/testdata/1.8.0_202-x64.json deleted file mode 100644 index 3d99d719..00000000 --- a/api/logic/mojang/testdata/1.8.0_202-x64.json +++ /dev/null @@ -1 +0,0 @@ -{"files": {"COPYRIGHT": {"downloads": {"lzma": {"sha1": "dd860e040807f7e53ae89da5f28dd73d57ac605d", "size": 1431, "url": "https://launcher.mojang.com/v1/objects/dd860e040807f7e53ae89da5f28dd73d57ac605d/COPYRIGHT"}, "raw": {"sha1": "c725183c757011e7ba96c83c1e86ee7e8b516a2b", "size": 3244, "url": "https://launcher.mojang.com/v1/objects/c725183c757011e7ba96c83c1e86ee7e8b516a2b/COPYRIGHT"}}, "executable": false, "type": "file"}, "LICENSE": {"downloads": {"raw": {"sha1": "3e86865deec0814c958bcf7fb87f790bccc0e8bd", "size": 40, "url": "https://launcher.mojang.com/v1/objects/3e86865deec0814c958bcf7fb87f790bccc0e8bd/LICENSE"}}, "executable": false, "type": "file"}, "README": {"downloads": {"raw": {"sha1": "f90331df1e5badeadc501d8dd70714c62a920204", "size": 46, "url": "https://launcher.mojang.com/v1/objects/f90331df1e5badeadc501d8dd70714c62a920204/README"}}, "executable": false, "type": "file"}, "THIRDPARTYLICENSEREADME-JAVAFX.txt": {"downloads": {"lzma": {"sha1": "4fee85109d7ff04b982d0576dabd15397f599125", "size": 15455, "url": "https://launcher.mojang.com/v1/objects/4fee85109d7ff04b982d0576dabd15397f599125/THIRDPARTYLICENSEREADME-JAVAFX.txt"}, "raw": {"sha1": "56ff42f87607b997b52ae0ef8bf315e36932e870", "size": 112724, "url": "https://launcher.mojang.com/v1/objects/56ff42f87607b997b52ae0ef8bf315e36932e870/THIRDPARTYLICENSEREADME-JAVAFX.txt"}}, "executable": false, "type": "file"}, "THIRDPARTYLICENSEREADME.txt": {"downloads": {"lzma": {"sha1": "419c1414ba46ae9dbfd38cf4e0601fff61644429", "size": 32266, "url": "https://launcher.mojang.com/v1/objects/419c1414ba46ae9dbfd38cf4e0601fff61644429/THIRDPARTYLICENSEREADME.txt"}, "raw": {"sha1": "b83c3f32261de3e48ccd20614a11e066b1ec9027", "size": 153824, "url": "https://launcher.mojang.com/v1/objects/b83c3f32261de3e48ccd20614a11e066b1ec9027/THIRDPARTYLICENSEREADME.txt"}}, "executable": false, "type": "file"}, "Welcome.html": {"downloads": {"lzma": {"sha1": "01c21a74b4aafb7cbe0388233c43cbdf77dcaaea", "size": 528, "url": "https://launcher.mojang.com/v1/objects/01c21a74b4aafb7cbe0388233c43cbdf77dcaaea/Welcome.html"}, "raw": {"sha1": "d98ae54f03dac87419abc19b97e315830c2da55f", "size": 955, "url": "https://launcher.mojang.com/v1/objects/d98ae54f03dac87419abc19b97e315830c2da55f/Welcome.html"}}, "executable": false, "type": "file"}, "bin": {"type": "directory"}, "bin/ControlPanel": {"target": "jcontrol", "type": "link"}, "bin/java": {"downloads": {"lzma": {"sha1": "3857eea1d59e1bc545c67a753ed2768254807b8a", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/3857eea1d59e1bc545c67a753ed2768254807b8a/java"}, "raw": {"sha1": "3d20560fb5d1a49cb689c2226972e92e06d27ba6", "size": 8464, "url": "https://launcher.mojang.com/v1/objects/3d20560fb5d1a49cb689c2226972e92e06d27ba6/java"}}, "executable": true, "type": "file"}, "bin/javaws": {"downloads": {"lzma": {"sha1": "a6bec5c049e76c4488294a256a2084ea23ddb440", "size": 38173, "url": "https://launcher.mojang.com/v1/objects/a6bec5c049e76c4488294a256a2084ea23ddb440/javaws"}, "raw": {"sha1": "955c0f0066e2f893b0c2b3ccd83e223722e4ab74", "size": 140296, "url": "https://launcher.mojang.com/v1/objects/955c0f0066e2f893b0c2b3ccd83e223722e4ab74/javaws"}}, "executable": true, "type": "file"}, "bin/jcontrol": {"downloads": {"lzma": {"sha1": "40c5e33748f252e1d950b579a4185ab2c23fc908", "size": 2166, "url": "https://launcher.mojang.com/v1/objects/40c5e33748f252e1d950b579a4185ab2c23fc908/jcontrol"}, "raw": {"sha1": "ed541733c8b51e34349c1f8010b277e58ad73f1e", "size": 6264, "url": "https://launcher.mojang.com/v1/objects/ed541733c8b51e34349c1f8010b277e58ad73f1e/jcontrol"}}, "executable": true, "type": "file"}, "bin/jjs": {"downloads": {"lzma": {"sha1": "d44d1ac421979f7671921986214812095a5b0e3b", "size": 2168, "url": "https://launcher.mojang.com/v1/objects/d44d1ac421979f7671921986214812095a5b0e3b/jjs"}, "raw": {"sha1": "f00f944c3dbe556793b5dc686aaeee3e5722e99b", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/f00f944c3dbe556793b5dc686aaeee3e5722e99b/jjs"}}, "executable": true, "type": "file"}, "bin/keytool": {"downloads": {"lzma": {"sha1": "93c607dce450976667c382f609a367167bdec05c", "size": 2175, "url": "https://launcher.mojang.com/v1/objects/93c607dce450976667c382f609a367167bdec05c/keytool"}, "raw": {"sha1": "7114b561546270e441e9ed1bcc24e5188c068a42", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/7114b561546270e441e9ed1bcc24e5188c068a42/keytool"}}, "executable": true, "type": "file"}, "bin/orbd": {"downloads": {"lzma": {"sha1": "b27dfded5e2b2f6f02c555971c94e46ca14ac81b", "size": 2254, "url": "https://launcher.mojang.com/v1/objects/b27dfded5e2b2f6f02c555971c94e46ca14ac81b/orbd"}, "raw": {"sha1": "7f31217fecb3dbbd89f1dd3783fca58793a66fd2", "size": 8656, "url": "https://launcher.mojang.com/v1/objects/7f31217fecb3dbbd89f1dd3783fca58793a66fd2/orbd"}}, "executable": true, "type": "file"}, "bin/pack200": {"downloads": {"lzma": {"sha1": "b52da4497b49b1508b6225a5740857ddb8f52e97", "size": 2183, "url": "https://launcher.mojang.com/v1/objects/b52da4497b49b1508b6225a5740857ddb8f52e97/pack200"}, "raw": {"sha1": "16ef3e801efb57e50bc6477a27a9d95d02d0775b", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/16ef3e801efb57e50bc6477a27a9d95d02d0775b/pack200"}}, "executable": true, "type": "file"}, "bin/policytool": {"downloads": {"lzma": {"sha1": "87da4c07da45f3d1a1a9d732af197cd39bf69d10", "size": 2182, "url": "https://launcher.mojang.com/v1/objects/87da4c07da45f3d1a1a9d732af197cd39bf69d10/policytool"}, "raw": {"sha1": "a52a29424470cb9b8db5c2fb1751d0b697a7ec8e", "size": 8592, "url": "https://launcher.mojang.com/v1/objects/a52a29424470cb9b8db5c2fb1751d0b697a7ec8e/policytool"}}, "executable": true, "type": "file"}, "bin/rmid": {"downloads": {"lzma": {"sha1": "1494c1174fde0c0a93ea117bc7edf7eb936c0512", "size": 2172, "url": "https://launcher.mojang.com/v1/objects/1494c1174fde0c0a93ea117bc7edf7eb936c0512/rmid"}, "raw": {"sha1": "5c8710e1ab924e5b09a07bcb4c6e106293bbd1a8", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/5c8710e1ab924e5b09a07bcb4c6e106293bbd1a8/rmid"}}, "executable": true, "type": "file"}, "bin/rmiregistry": {"downloads": {"lzma": {"sha1": "7070cf2ec5a5e520a880bae699431edf02083e7e", "size": 2174, "url": "https://launcher.mojang.com/v1/objects/7070cf2ec5a5e520a880bae699431edf02083e7e/rmiregistry"}, "raw": {"sha1": "5f518daa7050028d5d9d849634c73136f2b23a54", "size": 8592, "url": "https://launcher.mojang.com/v1/objects/5f518daa7050028d5d9d849634c73136f2b23a54/rmiregistry"}}, "executable": true, "type": "file"}, "bin/servertool": {"downloads": {"lzma": {"sha1": "1db683a11cc9b7313426c84412f4d95be2fa7ccd", "size": 2185, "url": "https://launcher.mojang.com/v1/objects/1db683a11cc9b7313426c84412f4d95be2fa7ccd/servertool"}, "raw": {"sha1": "49d0ebfeb265ce5a8733e1014541ea2525674a60", "size": 8592, "url": "https://launcher.mojang.com/v1/objects/49d0ebfeb265ce5a8733e1014541ea2525674a60/servertool"}}, "executable": true, "type": "file"}, "bin/tnameserv": {"downloads": {"lzma": {"sha1": "36da9c9a2c5a8b662a3f8d52ca67339bce1c2714", "size": 2291, "url": "https://launcher.mojang.com/v1/objects/36da9c9a2c5a8b662a3f8d52ca67339bce1c2714/tnameserv"}, "raw": {"sha1": "09d998f8efcb6f55d0d87f59e08f8b89662796d9", "size": 8656, "url": "https://launcher.mojang.com/v1/objects/09d998f8efcb6f55d0d87f59e08f8b89662796d9/tnameserv"}}, "executable": true, "type": "file"}, "bin/unpack200": {"downloads": {"lzma": {"sha1": "344959e32fc7ee19eebe7b3cf5ab6d1a7d6641f2", "size": 79721, "url": "https://launcher.mojang.com/v1/objects/344959e32fc7ee19eebe7b3cf5ab6d1a7d6641f2/unpack200"}, "raw": {"sha1": "5dd933132f1b202e19e0c8e093f7113711cfdfc1", "size": 182616, "url": "https://launcher.mojang.com/v1/objects/5dd933132f1b202e19e0c8e093f7113711cfdfc1/unpack200"}}, "executable": true, "type": "file"}, "lib": {"type": "directory"}, "lib/amd64": {"type": "directory"}, "lib/amd64/jli": {"type": "directory"}, "lib/amd64/jli/libjli.so": {"downloads": {"lzma": {"sha1": "372331ee8e375888f798a2e88180a94493e141b0", "size": 48327, "url": "https://launcher.mojang.com/v1/objects/372331ee8e375888f798a2e88180a94493e141b0/libjli.so"}, "raw": {"sha1": "73b0cf8b7415686bc40c561ff77ff2740ccf7a44", "size": 108616, "url": "https://launcher.mojang.com/v1/objects/73b0cf8b7415686bc40c561ff77ff2740ccf7a44/libjli.so"}}, "executable": true, "type": "file"}, "lib/amd64/jvm.cfg": {"downloads": {"lzma": {"sha1": "86bcfebec37b38415525ffd77d3eaf70d0b1b4ca", "size": 435, "url": "https://launcher.mojang.com/v1/objects/86bcfebec37b38415525ffd77d3eaf70d0b1b4ca/jvm.cfg"}, "raw": {"sha1": "84b38bdc745de446ba0ca0232ea3aaf2efd721da", "size": 627, "url": "https://launcher.mojang.com/v1/objects/84b38bdc745de446ba0ca0232ea3aaf2efd721da/jvm.cfg"}}, "executable": false, "type": "file"}, "lib/amd64/libavplugin-53.so": {"downloads": {"lzma": {"sha1": "a332366762d9efc7b845a682b7edce62db44618c", "size": 14747, "url": "https://launcher.mojang.com/v1/objects/a332366762d9efc7b845a682b7edce62db44618c/libavplugin-53.so"}, "raw": {"sha1": "9bd1473dd8a0dc7950c7af1cc69a45548df26eb5", "size": 51720, "url": "https://launcher.mojang.com/v1/objects/9bd1473dd8a0dc7950c7af1cc69a45548df26eb5/libavplugin-53.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-54.so": {"downloads": {"lzma": {"sha1": "2c615852a0720a275163e00597c1f711f11341da", "size": 15153, "url": "https://launcher.mojang.com/v1/objects/2c615852a0720a275163e00597c1f711f11341da/libavplugin-54.so"}, "raw": {"sha1": "8808050c5949c4800b42d1b19b1f8b0d120bcacb", "size": 51768, "url": "https://launcher.mojang.com/v1/objects/8808050c5949c4800b42d1b19b1f8b0d120bcacb/libavplugin-54.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-55.so": {"downloads": {"lzma": {"sha1": "39ee8e7fe14f0010c78973962800f539c3e4c16b", "size": 15168, "url": "https://launcher.mojang.com/v1/objects/39ee8e7fe14f0010c78973962800f539c3e4c16b/libavplugin-55.so"}, "raw": {"sha1": "f10ea4ea3489e96d8d161a96790133c417ec44e1", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/f10ea4ea3489e96d8d161a96790133c417ec44e1/libavplugin-55.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-56.so": {"downloads": {"lzma": {"sha1": "abe7feced5a559f1bdc868526dc69484e0e591a0", "size": 15169, "url": "https://launcher.mojang.com/v1/objects/abe7feced5a559f1bdc868526dc69484e0e591a0/libavplugin-56.so"}, "raw": {"sha1": "e5bfcbff5a5a5a5993a3e689a05ef358c131a3ed", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/e5bfcbff5a5a5a5993a3e689a05ef358c131a3ed/libavplugin-56.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-57.so": {"downloads": {"lzma": {"sha1": "4dd26b4ef2294b6929dcb2c7546b47eac5cc78a9", "size": 15174, "url": "https://launcher.mojang.com/v1/objects/4dd26b4ef2294b6929dcb2c7546b47eac5cc78a9/libavplugin-57.so"}, "raw": {"sha1": "2949e7ff9b0ac90e8943c211cff141ab12eec3f8", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/2949e7ff9b0ac90e8943c211cff141ab12eec3f8/libavplugin-57.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-ffmpeg-56.so": {"downloads": {"lzma": {"sha1": "c688ba1cfa442bf18bee43b2fa870b4dc1ce3fb6", "size": 15231, "url": "https://launcher.mojang.com/v1/objects/c688ba1cfa442bf18bee43b2fa870b4dc1ce3fb6/libavplugin-ffmpeg-56.so"}, "raw": {"sha1": "0d36c971a9ad99fc2292092fdec3a4179b1021b9", "size": 51920, "url": "https://launcher.mojang.com/v1/objects/0d36c971a9ad99fc2292092fdec3a4179b1021b9/libavplugin-ffmpeg-56.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-ffmpeg-57.so": {"downloads": {"lzma": {"sha1": "087426bdbffebcfa372a438e863785f4ffbe9a6b", "size": 15180, "url": "https://launcher.mojang.com/v1/objects/087426bdbffebcfa372a438e863785f4ffbe9a6b/libavplugin-ffmpeg-57.so"}, "raw": {"sha1": "5e9c4eb4b49eb8e57c01003ec73a1eb8d6d8c462", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/5e9c4eb4b49eb8e57c01003ec73a1eb8d6d8c462/libavplugin-ffmpeg-57.so"}}, "executable": true, "type": "file"}, "lib/amd64/libawt.so": {"downloads": {"lzma": {"sha1": "018be58b205b73c842a55df811b70d0e8237216e", "size": 195720, "url": "https://launcher.mojang.com/v1/objects/018be58b205b73c842a55df811b70d0e8237216e/libawt.so"}, "raw": {"sha1": "02632cd326e3161c00a7e784599dd7b9ee053dce", "size": 759184, "url": "https://launcher.mojang.com/v1/objects/02632cd326e3161c00a7e784599dd7b9ee053dce/libawt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libawt_headless.so": {"downloads": {"lzma": {"sha1": "7ac2517cff75d4bbb0a0412a9b5f18c74ea188fa", "size": 11211, "url": "https://launcher.mojang.com/v1/objects/7ac2517cff75d4bbb0a0412a9b5f18c74ea188fa/libawt_headless.so"}, "raw": {"sha1": "862157ec957008d0911c5daedc004b3a202623a4", "size": 39176, "url": "https://launcher.mojang.com/v1/objects/862157ec957008d0911c5daedc004b3a202623a4/libawt_headless.so"}}, "executable": true, "type": "file"}, "lib/amd64/libawt_xawt.so": {"downloads": {"lzma": {"sha1": "d536a96af27dfe35de6bb2c8759d51c488cdd8d4", "size": 149598, "url": "https://launcher.mojang.com/v1/objects/d536a96af27dfe35de6bb2c8759d51c488cdd8d4/libawt_xawt.so"}, "raw": {"sha1": "28232b3e01b6f11bfe098bfc6eafc3a513dcebf1", "size": 470232, "url": "https://launcher.mojang.com/v1/objects/28232b3e01b6f11bfe098bfc6eafc3a513dcebf1/libawt_xawt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libbci.so": {"downloads": {"lzma": {"sha1": "c36fad091d11e64c815d5ca17c0ef7a55b0776b1", "size": 3509, "url": "https://launcher.mojang.com/v1/objects/c36fad091d11e64c815d5ca17c0ef7a55b0776b1/libbci.so"}, "raw": {"sha1": "33824051db1ccb6332e22c2b63231055240d0af0", "size": 12760, "url": "https://launcher.mojang.com/v1/objects/33824051db1ccb6332e22c2b63231055240d0af0/libbci.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdcpr.so": {"downloads": {"lzma": {"sha1": "70c6b0933a37f2b1124d6e7c131039241fe796ee", "size": 75969, "url": "https://launcher.mojang.com/v1/objects/70c6b0933a37f2b1124d6e7c131039241fe796ee/libdcpr.so"}, "raw": {"sha1": "fa7001bc5d80579e2716590f3eee8027da0beae7", "size": 204456, "url": "https://launcher.mojang.com/v1/objects/fa7001bc5d80579e2716590f3eee8027da0beae7/libdcpr.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdecora_sse.so": {"downloads": {"lzma": {"sha1": "514acc017dfb6cefaf8cc6d18006ce55781cc9bc", "size": 24397, "url": "https://launcher.mojang.com/v1/objects/514acc017dfb6cefaf8cc6d18006ce55781cc9bc/libdecora_sse.so"}, "raw": {"sha1": "d0c84233504c916e548e29f513e25f6a7479abfc", "size": 74912, "url": "https://launcher.mojang.com/v1/objects/d0c84233504c916e548e29f513e25f6a7479abfc/libdecora_sse.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdeploy.so": {"downloads": {"lzma": {"sha1": "6cf31fd98301c749ac0d2c7825f6d925a4409760", "size": 168999, "url": "https://launcher.mojang.com/v1/objects/6cf31fd98301c749ac0d2c7825f6d925a4409760/libdeploy.so"}, "raw": {"sha1": "b3832e97ed8ca794884b56a591b83d02a2c0c06f", "size": 642368, "url": "https://launcher.mojang.com/v1/objects/b3832e97ed8ca794884b56a591b83d02a2c0c06f/libdeploy.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdt_socket.so": {"downloads": {"lzma": {"sha1": "4cc5c880dbb6fa180436d12d60f0abec8ebb59dc", "size": 7784, "url": "https://launcher.mojang.com/v1/objects/4cc5c880dbb6fa180436d12d60f0abec8ebb59dc/libdt_socket.so"}, "raw": {"sha1": "91ce96f252b8139fc12f0f224ed5b1a041767ab7", "size": 24616, "url": "https://launcher.mojang.com/v1/objects/91ce96f252b8139fc12f0f224ed5b1a041767ab7/libdt_socket.so"}}, "executable": true, "type": "file"}, "lib/amd64/libfontmanager.so": {"downloads": {"lzma": {"sha1": "f94e5e94c71c603ff4d3cd1e7e3d9e181fcc145d", "size": 146951, "url": "https://launcher.mojang.com/v1/objects/f94e5e94c71c603ff4d3cd1e7e3d9e181fcc145d/libfontmanager.so"}, "raw": {"sha1": "2428e805f2c53d1283a033dfd11a86fbb7bd7159", "size": 490672, "url": "https://launcher.mojang.com/v1/objects/2428e805f2c53d1283a033dfd11a86fbb7bd7159/libfontmanager.so"}}, "executable": true, "type": "file"}, "lib/amd64/libfxplugins.so": {"downloads": {"lzma": {"sha1": "a640143365d382a5ad743a784bc2f3706d9d6d67", "size": 50048, "url": "https://launcher.mojang.com/v1/objects/a640143365d382a5ad743a784bc2f3706d9d6d67/libfxplugins.so"}, "raw": {"sha1": "0fd4ac04a84c131f1aaee9e6b0898ff9ea69e3ee", "size": 151448, "url": "https://launcher.mojang.com/v1/objects/0fd4ac04a84c131f1aaee9e6b0898ff9ea69e3ee/libfxplugins.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglass.so": {"downloads": {"lzma": {"sha1": "f1ff517714fa5f2c861f33b32db823fe851541f1", "size": 2856, "url": "https://launcher.mojang.com/v1/objects/f1ff517714fa5f2c861f33b32db823fe851541f1/libglass.so"}, "raw": {"sha1": "e7f4fece30ac727be8148d33b8256abd3a41cef9", "size": 13072, "url": "https://launcher.mojang.com/v1/objects/e7f4fece30ac727be8148d33b8256abd3a41cef9/libglass.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglassgtk2.so": {"downloads": {"lzma": {"sha1": "15b90f7a2baacd15e80aa9785d87cf1e4258376d", "size": 220476, "url": "https://launcher.mojang.com/v1/objects/15b90f7a2baacd15e80aa9785d87cf1e4258376d/libglassgtk2.so"}, "raw": {"sha1": "e30a634c2ff2143bdee512360553d6e0304f33b2", "size": 844984, "url": "https://launcher.mojang.com/v1/objects/e30a634c2ff2143bdee512360553d6e0304f33b2/libglassgtk2.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglassgtk3.so": {"downloads": {"lzma": {"sha1": "868c231165f8c9043b7f0e7de208ec023f06a6e7", "size": 220560, "url": "https://launcher.mojang.com/v1/objects/868c231165f8c9043b7f0e7de208ec023f06a6e7/libglassgtk3.so"}, "raw": {"sha1": "762a11a2b376b7b5a2a7cad780715524fdd176d5", "size": 845304, "url": "https://launcher.mojang.com/v1/objects/762a11a2b376b7b5a2a7cad780715524fdd176d5/libglassgtk3.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglib-lite.so": {"downloads": {"lzma": {"sha1": "61b8871242febe1be262de167dc20ae94bf964b4", "size": 457046, "url": "https://launcher.mojang.com/v1/objects/61b8871242febe1be262de167dc20ae94bf964b4/libglib-lite.so"}, "raw": {"sha1": "63afa060fc3f120af76128e51d32603fc4336fa8", "size": 1538352, "url": "https://launcher.mojang.com/v1/objects/63afa060fc3f120af76128e51d32603fc4336fa8/libglib-lite.so"}}, "executable": true, "type": "file"}, "lib/amd64/libgstreamer-lite.so": {"downloads": {"lzma": {"sha1": "2447dc368406ba1b989a29937d41924620e01988", "size": 673056, "url": "https://launcher.mojang.com/v1/objects/2447dc368406ba1b989a29937d41924620e01988/libgstreamer-lite.so"}, "raw": {"sha1": "5505e7ca592ac64371d3db8fe53bcb602e9723d3", "size": 2263872, "url": "https://launcher.mojang.com/v1/objects/5505e7ca592ac64371d3db8fe53bcb602e9723d3/libgstreamer-lite.so"}}, "executable": true, "type": "file"}, "lib/amd64/libhprof.so": {"downloads": {"lzma": {"sha1": "94a5589c818db1fb1cf1881e24e217c309fce2e4", "size": 64471, "url": "https://launcher.mojang.com/v1/objects/94a5589c818db1fb1cf1881e24e217c309fce2e4/libhprof.so"}, "raw": {"sha1": "4bb9bdeef6133b6dd558d52d691b077c03e9b0ee", "size": 175504, "url": "https://launcher.mojang.com/v1/objects/4bb9bdeef6133b6dd558d52d691b077c03e9b0ee/libhprof.so"}}, "executable": true, "type": "file"}, "lib/amd64/libinstrument.so": {"downloads": {"lzma": {"sha1": "84ffea356caf725b42c86a8ebc9587f477ddde29", "size": 18603, "url": "https://launcher.mojang.com/v1/objects/84ffea356caf725b42c86a8ebc9587f477ddde29/libinstrument.so"}, "raw": {"sha1": "cb8009769601e3fecd7ea2b36c344f737b1a9da7", "size": 51560, "url": "https://launcher.mojang.com/v1/objects/cb8009769601e3fecd7ea2b36c344f737b1a9da7/libinstrument.so"}}, "executable": true, "type": "file"}, "lib/amd64/libj2gss.so": {"downloads": {"lzma": {"sha1": "4b2aa699506b126098b585a9617ce1c05707fa29", "size": 14132, "url": "https://launcher.mojang.com/v1/objects/4b2aa699506b126098b585a9617ce1c05707fa29/libj2gss.so"}, "raw": {"sha1": "cbce4a302b255d4d1924ef7606f038af766c5e86", "size": 47688, "url": "https://launcher.mojang.com/v1/objects/cbce4a302b255d4d1924ef7606f038af766c5e86/libj2gss.so"}}, "executable": true, "type": "file"}, "lib/amd64/libj2pcsc.so": {"downloads": {"lzma": {"sha1": "2361d3b2e3da48593c391b29b0d2b5409e4c55e5", "size": 5074, "url": "https://launcher.mojang.com/v1/objects/2361d3b2e3da48593c391b29b0d2b5409e4c55e5/libj2pcsc.so"}, "raw": {"sha1": "1274178492e7a3e997e12f67794616f7c3d8d0b9", "size": 18296, "url": "https://launcher.mojang.com/v1/objects/1274178492e7a3e997e12f67794616f7c3d8d0b9/libj2pcsc.so"}}, "executable": true, "type": "file"}, "lib/amd64/libj2pkcs11.so": {"downloads": {"lzma": {"sha1": "ef927e2790ba05931d0f0bdd63da3d275a834946", "size": 21573, "url": "https://launcher.mojang.com/v1/objects/ef927e2790ba05931d0f0bdd63da3d275a834946/libj2pkcs11.so"}, "raw": {"sha1": "bd4f2af9bfdc6168633d1920c1a1415de06bb45a", "size": 79472, "url": "https://launcher.mojang.com/v1/objects/bd4f2af9bfdc6168633d1920c1a1415de06bb45a/libj2pkcs11.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjaas_unix.so": {"downloads": {"lzma": {"sha1": "7f7e843544ee1eb1454a5826bdd4218685b79430", "size": 2404, "url": "https://launcher.mojang.com/v1/objects/7f7e843544ee1eb1454a5826bdd4218685b79430/libjaas_unix.so"}, "raw": {"sha1": "4c517925c7d464a5b719898eb0bea1b04df31f1f", "size": 8192, "url": "https://launcher.mojang.com/v1/objects/4c517925c7d464a5b719898eb0bea1b04df31f1f/libjaas_unix.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjava.so": {"downloads": {"lzma": {"sha1": "5eee7a42600a44a8bb8d6d7f510fd96a29637ac0", "size": 63113, "url": "https://launcher.mojang.com/v1/objects/5eee7a42600a44a8bb8d6d7f510fd96a29637ac0/libjava.so"}, "raw": {"sha1": "e280aeddf3fc0ec664aef7efc0e0e197a54aaf02", "size": 227672, "url": "https://launcher.mojang.com/v1/objects/e280aeddf3fc0ec664aef7efc0e0e197a54aaf02/libjava.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjava_crw_demo.so": {"downloads": {"lzma": {"sha1": "b197cf23ae3556eb0b45c663f0a8cb62408b961e", "size": 10412, "url": "https://launcher.mojang.com/v1/objects/b197cf23ae3556eb0b45c663f0a8cb62408b961e/libjava_crw_demo.so"}, "raw": {"sha1": "18f20f906977c90d0090b41dbda8dd5cfead5a4c", "size": 26144, "url": "https://launcher.mojang.com/v1/objects/18f20f906977c90d0090b41dbda8dd5cfead5a4c/libjava_crw_demo.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font.so": {"downloads": {"lzma": {"sha1": "ffbba0e5022f829412b86063d8a90f95f16709b1", "size": 5608, "url": "https://launcher.mojang.com/v1/objects/ffbba0e5022f829412b86063d8a90f95f16709b1/libjavafx_font.so"}, "raw": {"sha1": "8634a0aca612fc40420a4a7cc8af4cc46cfc6725", "size": 17104, "url": "https://launcher.mojang.com/v1/objects/8634a0aca612fc40420a4a7cc8af4cc46cfc6725/libjavafx_font.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font_freetype.so": {"downloads": {"lzma": {"sha1": "310271eda8a2ac264ffc3640a9d847b49438d0bd", "size": 6942, "url": "https://launcher.mojang.com/v1/objects/310271eda8a2ac264ffc3640a9d847b49438d0bd/libjavafx_font_freetype.so"}, "raw": {"sha1": "3e7572d047c12ba2bc43acec7f98a67c20af8042", "size": 27616, "url": "https://launcher.mojang.com/v1/objects/3e7572d047c12ba2bc43acec7f98a67c20af8042/libjavafx_font_freetype.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font_pango.so": {"downloads": {"lzma": {"sha1": "a7bcf0669e70b0f43099a99c81e6b6440cb40ac0", "size": 5820, "url": "https://launcher.mojang.com/v1/objects/a7bcf0669e70b0f43099a99c81e6b6440cb40ac0/libjavafx_font_pango.so"}, "raw": {"sha1": "f0b775cc9a514c7ee8b4d6fb300653ce548caf10", "size": 25560, "url": "https://launcher.mojang.com/v1/objects/f0b775cc9a514c7ee8b4d6fb300653ce548caf10/libjavafx_font_pango.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font_t2k.so": {"downloads": {"lzma": {"sha1": "551c29dc7c7fc83223aa36a6187f7e0c5d650538", "size": 431450, "url": "https://launcher.mojang.com/v1/objects/551c29dc7c7fc83223aa36a6187f7e0c5d650538/libjavafx_font_t2k.so"}, "raw": {"sha1": "91e5813057c3b852d411540160f8ad05fb9f1ed3", "size": 1486128, "url": "https://launcher.mojang.com/v1/objects/91e5813057c3b852d411540160f8ad05fb9f1ed3/libjavafx_font_t2k.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_iio.so": {"downloads": {"lzma": {"sha1": "c832998fd5e06ed6dcd6428816194c350785420c", "size": 101479, "url": "https://launcher.mojang.com/v1/objects/c832998fd5e06ed6dcd6428816194c350785420c/libjavafx_iio.so"}, "raw": {"sha1": "dcdf68cb25677b76c1cf0bb94294e6e9880a6678", "size": 256336, "url": "https://launcher.mojang.com/v1/objects/dcdf68cb25677b76c1cf0bb94294e6e9880a6678/libjavafx_iio.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjawt.so": {"downloads": {"lzma": {"sha1": "c1ced6aad5c69ff444dc67d0fd7e333558953831", "size": 1872, "url": "https://launcher.mojang.com/v1/objects/c1ced6aad5c69ff444dc67d0fd7e333558953831/libjawt.so"}, "raw": {"sha1": "c5032f2c6fa40bea24e56605cf76b26a27e87b67", "size": 8048, "url": "https://launcher.mojang.com/v1/objects/c5032f2c6fa40bea24e56605cf76b26a27e87b67/libjawt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjdwp.so": {"downloads": {"lzma": {"sha1": "c1aabbb3f5a624b9ad10ed871a1d83510a99b646", "size": 94884, "url": "https://launcher.mojang.com/v1/objects/c1aabbb3f5a624b9ad10ed871a1d83510a99b646/libjdwp.so"}, "raw": {"sha1": "a043e97be47937f6f552e94cf79c76c1c57f9594", "size": 272248, "url": "https://launcher.mojang.com/v1/objects/a043e97be47937f6f552e94cf79c76c1c57f9594/libjdwp.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjfr.so": {"downloads": {"lzma": {"sha1": "11b8e6bfffdccbacbf9dd29dea4b90b753f3c1b7", "size": 8780, "url": "https://launcher.mojang.com/v1/objects/11b8e6bfffdccbacbf9dd29dea4b90b753f3c1b7/libjfr.so"}, "raw": {"sha1": "312392dd186b11c418183e818f1928e8685a07e5", "size": 28384, "url": "https://launcher.mojang.com/v1/objects/312392dd186b11c418183e818f1928e8685a07e5/libjfr.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjfxmedia.so": {"downloads": {"lzma": {"sha1": "a4e7a126eb648ce6e5e6dc151831da37d8334139", "size": 391897, "url": "https://launcher.mojang.com/v1/objects/a4e7a126eb648ce6e5e6dc151831da37d8334139/libjfxmedia.so"}, "raw": {"sha1": "5fa54944327a6012c3d34cb5c1c4432762178dc8", "size": 1636376, "url": "https://launcher.mojang.com/v1/objects/5fa54944327a6012c3d34cb5c1c4432762178dc8/libjfxmedia.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjfxwebkit.so": {"downloads": {"lzma": {"sha1": "b274debd222cdcc2ee84160ebb95144b3880bc97", "size": 20492825, "url": "https://launcher.mojang.com/v1/objects/b274debd222cdcc2ee84160ebb95144b3880bc97/libjfxwebkit.so"}, "raw": {"sha1": "ecee564c3b2f645131b35bb3004abd4caeabd291", "size": 91014584, "url": "https://launcher.mojang.com/v1/objects/ecee564c3b2f645131b35bb3004abd4caeabd291/libjfxwebkit.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjpeg.so": {"downloads": {"lzma": {"sha1": "9ad55e370c5eaaa73c3158339db3c368b1aaf0cb", "size": 113072, "url": "https://launcher.mojang.com/v1/objects/9ad55e370c5eaaa73c3158339db3c368b1aaf0cb/libjpeg.so"}, "raw": {"sha1": "651e6d53ae67db1f0efbf7f104447a9b49b7e333", "size": 292520, "url": "https://launcher.mojang.com/v1/objects/651e6d53ae67db1f0efbf7f104447a9b49b7e333/libjpeg.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsdt.so": {"downloads": {"lzma": {"sha1": "04b6d1361a34c496b5f652b2477784d69b8b6baf", "size": 3964, "url": "https://launcher.mojang.com/v1/objects/04b6d1361a34c496b5f652b2477784d69b8b6baf/libjsdt.so"}, "raw": {"sha1": "82b48a82bf6183d34cf00a0f81661b45c616f31b", "size": 12904, "url": "https://launcher.mojang.com/v1/objects/82b48a82bf6183d34cf00a0f81661b45c616f31b/libjsdt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsig.so": {"downloads": {"lzma": {"sha1": "37d3b89abde397216cc4ecb1339d8543d99b8428", "size": 3536, "url": "https://launcher.mojang.com/v1/objects/37d3b89abde397216cc4ecb1339d8543d99b8428/libjsig.so"}, "raw": {"sha1": "42e52ba1bcbe0362ab24bcf65c93797354db6fb9", "size": 13336, "url": "https://launcher.mojang.com/v1/objects/42e52ba1bcbe0362ab24bcf65c93797354db6fb9/libjsig.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsound.so": {"downloads": {"lzma": {"sha1": "7e3c565d74d8ffae716f32b05544fa4a6f108adc", "size": 2002, "url": "https://launcher.mojang.com/v1/objects/7e3c565d74d8ffae716f32b05544fa4a6f108adc/libjsound.so"}, "raw": {"sha1": "0c0fc63b92d7b83c9960fa80d45c80553ea20254", "size": 8232, "url": "https://launcher.mojang.com/v1/objects/0c0fc63b92d7b83c9960fa80d45c80553ea20254/libjsound.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsoundalsa.so": {"downloads": {"lzma": {"sha1": "b06c51858a25ff776519495f1b9b3d9f604b089f", "size": 23097, "url": "https://launcher.mojang.com/v1/objects/b06c51858a25ff776519495f1b9b3d9f604b089f/libjsoundalsa.so"}, "raw": {"sha1": "281d37f0326d4a12dc7ea316ead09c198ff7bdf7", "size": 83256, "url": "https://launcher.mojang.com/v1/objects/281d37f0326d4a12dc7ea316ead09c198ff7bdf7/libjsoundalsa.so"}}, "executable": true, "type": "file"}, "lib/amd64/liblcms.so": {"downloads": {"lzma": {"sha1": "7a239baba2086cae49114b382b74b971da02f08e", "size": 176175, "url": "https://launcher.mojang.com/v1/objects/7a239baba2086cae49114b382b74b971da02f08e/liblcms.so"}, "raw": {"sha1": "c8895cc3c3d023d9e059225969ab67954772c0a1", "size": 526872, "url": "https://launcher.mojang.com/v1/objects/c8895cc3c3d023d9e059225969ab67954772c0a1/liblcms.so"}}, "executable": true, "type": "file"}, "lib/amd64/libmanagement.so": {"downloads": {"lzma": {"sha1": "aed3fdbcefd1716abfc6a306687c8b741cbb318e", "size": 12838, "url": "https://launcher.mojang.com/v1/objects/aed3fdbcefd1716abfc6a306687c8b741cbb318e/libmanagement.so"}, "raw": {"sha1": "eba35f61e0d50e30874b7c7b335edf2d52662423", "size": 51808, "url": "https://launcher.mojang.com/v1/objects/eba35f61e0d50e30874b7c7b335edf2d52662423/libmanagement.so"}}, "executable": true, "type": "file"}, "lib/amd64/libmlib_image.so": {"downloads": {"lzma": {"sha1": "1bb181f079492d55c7a458e96488cd17fe0a7b86", "size": 310272, "url": "https://launcher.mojang.com/v1/objects/1bb181f079492d55c7a458e96488cd17fe0a7b86/libmlib_image.so"}, "raw": {"sha1": "c973c450d33873675945d4694be484e3427f58f1", "size": 1048136, "url": "https://launcher.mojang.com/v1/objects/c973c450d33873675945d4694be484e3427f58f1/libmlib_image.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnet.so": {"downloads": {"lzma": {"sha1": "9dd79703b6deb86e0321afe01c6ac508263c8312", "size": 38123, "url": "https://launcher.mojang.com/v1/objects/9dd79703b6deb86e0321afe01c6ac508263c8312/libnet.so"}, "raw": {"sha1": "b3a17b7d53fcdf1e689e1ec29ce851eee6022ead", "size": 116920, "url": "https://launcher.mojang.com/v1/objects/b3a17b7d53fcdf1e689e1ec29ce851eee6022ead/libnet.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnio.so": {"downloads": {"lzma": {"sha1": "5697c89d5d5d9b74f2e1555fcbba79dd4049e287", "size": 24445, "url": "https://launcher.mojang.com/v1/objects/5697c89d5d5d9b74f2e1555fcbba79dd4049e287/libnio.so"}, "raw": {"sha1": "573bf8f64dbcc397f8abd3e1da28f90ab0679f5b", "size": 93872, "url": "https://launcher.mojang.com/v1/objects/573bf8f64dbcc397f8abd3e1da28f90ab0679f5b/libnio.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnpjp2.so": {"downloads": {"lzma": {"sha1": "6fe53b5951ff740e7f2ef7ffe5975af26da06718", "size": 57892, "url": "https://launcher.mojang.com/v1/objects/6fe53b5951ff740e7f2ef7ffe5975af26da06718/libnpjp2.so"}, "raw": {"sha1": "2bb13c53a4280379253475e51216b97eed1d4ce3", "size": 216592, "url": "https://launcher.mojang.com/v1/objects/2bb13c53a4280379253475e51216b97eed1d4ce3/libnpjp2.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnpt.so": {"downloads": {"lzma": {"sha1": "1b170b09a32b1b8b6624fa5d1f94ec60b2bf3876", "size": 5070, "url": "https://launcher.mojang.com/v1/objects/1b170b09a32b1b8b6624fa5d1f94ec60b2bf3876/libnpt.so"}, "raw": {"sha1": "6b1ff6b9b4624f3cc7801f221c82b8046fb76364", "size": 17504, "url": "https://launcher.mojang.com/v1/objects/6b1ff6b9b4624f3cc7801f221c82b8046fb76364/libnpt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libprism_common.so": {"downloads": {"lzma": {"sha1": "f4aca04c90bc7505851c074a08b2c31cae1f94fa", "size": 23315, "url": "https://launcher.mojang.com/v1/objects/f4aca04c90bc7505851c074a08b2c31cae1f94fa/libprism_common.so"}, "raw": {"sha1": "b00866b6ed8646a29a334d46e297267552f27de8", "size": 59008, "url": "https://launcher.mojang.com/v1/objects/b00866b6ed8646a29a334d46e297267552f27de8/libprism_common.so"}}, "executable": true, "type": "file"}, "lib/amd64/libprism_es2.so": {"downloads": {"lzma": {"sha1": "7ff4173c338c7a6f370f231670055737e032da3e", "size": 18416, "url": "https://launcher.mojang.com/v1/objects/7ff4173c338c7a6f370f231670055737e032da3e/libprism_es2.so"}, "raw": {"sha1": "1390a1dc14345e5a948148e59195d62f3a83863f", "size": 63808, "url": "https://launcher.mojang.com/v1/objects/1390a1dc14345e5a948148e59195d62f3a83863f/libprism_es2.so"}}, "executable": true, "type": "file"}, "lib/amd64/libprism_sw.so": {"downloads": {"lzma": {"sha1": "6728e8bf7b214067d715be6fe0325910d63c2468", "size": 29457, "url": "https://launcher.mojang.com/v1/objects/6728e8bf7b214067d715be6fe0325910d63c2468/libprism_sw.so"}, "raw": {"sha1": "7a6c34cb2bbcde411778d1b3f8677c39e32c3ac4", "size": 71608, "url": "https://launcher.mojang.com/v1/objects/7a6c34cb2bbcde411778d1b3f8677c39e32c3ac4/libprism_sw.so"}}, "executable": true, "type": "file"}, "lib/amd64/libresource.so": {"downloads": {"lzma": {"sha1": "1e35e63f1e74915fba620f1febf420b919d49bc5", "size": 2633, "url": "https://launcher.mojang.com/v1/objects/1e35e63f1e74915fba620f1febf420b919d49bc5/libresource.so"}, "raw": {"sha1": "57490353ad0d83ab1930355213dea269795434fe", "size": 13456, "url": "https://launcher.mojang.com/v1/objects/57490353ad0d83ab1930355213dea269795434fe/libresource.so"}}, "executable": true, "type": "file"}, "lib/amd64/libsctp.so": {"downloads": {"lzma": {"sha1": "4340132ed250d7849a016e071be773eaedd33aa8", "size": 9332, "url": "https://launcher.mojang.com/v1/objects/4340132ed250d7849a016e071be773eaedd33aa8/libsctp.so"}, "raw": {"sha1": "4a80e743750f127c0d5a359f5cd60b97e7ee12ae", "size": 29552, "url": "https://launcher.mojang.com/v1/objects/4a80e743750f127c0d5a359f5cd60b97e7ee12ae/libsctp.so"}}, "executable": true, "type": "file"}, "lib/amd64/libsplashscreen.so": {"downloads": {"lzma": {"sha1": "b226c8dbd73a548fc2b042ee6db6cc80e727597c", "size": 190305, "url": "https://launcher.mojang.com/v1/objects/b226c8dbd73a548fc2b042ee6db6cc80e727597c/libsplashscreen.so"}, "raw": {"sha1": "87d6a491f5ba7e6c4d972264a0c9063afea567a2", "size": 441376, "url": "https://launcher.mojang.com/v1/objects/87d6a491f5ba7e6c4d972264a0c9063afea567a2/libsplashscreen.so"}}, "executable": true, "type": "file"}, "lib/amd64/libsunec.so": {"downloads": {"lzma": {"sha1": "6ebba98fab1e3d872d1363235b76497f6f9babdc", "size": 88829, "url": "https://launcher.mojang.com/v1/objects/6ebba98fab1e3d872d1363235b76497f6f9babdc/libsunec.so"}, "raw": {"sha1": "3b262a0a530f6e4e539aed2cd27b4de1d0ed8859", "size": 283368, "url": "https://launcher.mojang.com/v1/objects/3b262a0a530f6e4e539aed2cd27b4de1d0ed8859/libsunec.so"}}, "executable": true, "type": "file"}, "lib/amd64/libt2k.so": {"downloads": {"lzma": {"sha1": "602cb812ef0b350ccf56ce209a260ddbe3743d92", "size": 190720, "url": "https://launcher.mojang.com/v1/objects/602cb812ef0b350ccf56ce209a260ddbe3743d92/libt2k.so"}, "raw": {"sha1": "b072c56df997f61e15e6b5a43b8907b0d25c2043", "size": 504840, "url": "https://launcher.mojang.com/v1/objects/b072c56df997f61e15e6b5a43b8907b0d25c2043/libt2k.so"}}, "executable": true, "type": "file"}, "lib/amd64/libunpack.so": {"downloads": {"lzma": {"sha1": "7107b615e941074f0b14c31c88fb67200aacd37f", "size": 70308, "url": "https://launcher.mojang.com/v1/objects/7107b615e941074f0b14c31c88fb67200aacd37f/libunpack.so"}, "raw": {"sha1": "b05ff862ed87928ed91e80e5604673c5ea710a53", "size": 197712, "url": "https://launcher.mojang.com/v1/objects/b05ff862ed87928ed91e80e5604673c5ea710a53/libunpack.so"}}, "executable": true, "type": "file"}, "lib/amd64/libverify.so": {"downloads": {"lzma": {"sha1": "ecd98efb8c7da441a8c3580e8f5598f3cb4165b1", "size": 21885, "url": "https://launcher.mojang.com/v1/objects/ecd98efb8c7da441a8c3580e8f5598f3cb4165b1/libverify.so"}, "raw": {"sha1": "e2c8d92531c45ab9be69ffb72c87fa12e9e59827", "size": 66112, "url": "https://launcher.mojang.com/v1/objects/e2c8d92531c45ab9be69ffb72c87fa12e9e59827/libverify.so"}}, "executable": true, "type": "file"}, "lib/amd64/libzip.so": {"downloads": {"lzma": {"sha1": "7c562342e3f7b138dc978495447e3e6a96c2cf45", "size": 54876, "url": "https://launcher.mojang.com/v1/objects/7c562342e3f7b138dc978495447e3e6a96c2cf45/libzip.so"}, "raw": {"sha1": "5f4bf35a5c3e8f8c650e891d1031589b8ab6d77f", "size": 127016, "url": "https://launcher.mojang.com/v1/objects/5f4bf35a5c3e8f8c650e891d1031589b8ab6d77f/libzip.so"}}, "executable": true, "type": "file"}, "lib/amd64/server": {"type": "directory"}, "lib/amd64/server/Xusage.txt": {"downloads": {"lzma": {"sha1": "acb2da24a4c765887df83985e4c26d6be302a0a3", "size": 629, "url": "https://launcher.mojang.com/v1/objects/acb2da24a4c765887df83985e4c26d6be302a0a3/Xusage.txt"}, "raw": {"sha1": "6983727eafe140f9dd793c78aa6f3e007438243a", "size": 1423, "url": "https://launcher.mojang.com/v1/objects/6983727eafe140f9dd793c78aa6f3e007438243a/Xusage.txt"}}, "executable": false, "type": "file"}, "lib/amd64/server/libjsig.so": {"target": "../libjsig.so", "type": "link"}, "lib/amd64/server/libjvm.so": {"downloads": {"lzma": {"sha1": "d5c6f3338aaa6712f79d680ac8c3e31beebaa886", "size": 4154311, "url": "https://launcher.mojang.com/v1/objects/d5c6f3338aaa6712f79d680ac8c3e31beebaa886/libjvm.so"}, "raw": {"sha1": "23a98e1eb505cc3fb91bc0cb2adb71ab9270e9ca", "size": 17045016, "url": "https://launcher.mojang.com/v1/objects/23a98e1eb505cc3fb91bc0cb2adb71ab9270e9ca/libjvm.so"}}, "executable": true, "type": "file"}, "lib/applet": {"type": "directory"}, "lib/calendars.properties": {"downloads": {"lzma": {"sha1": "4a757c23f2942bd802a4f80235131146d9267750", "size": 558, "url": "https://launcher.mojang.com/v1/objects/4a757c23f2942bd802a4f80235131146d9267750/calendars.properties"}, "raw": {"sha1": "42ebb0988124433b8f2a6e5d9a74ed41240bcfc6", "size": 1378, "url": "https://launcher.mojang.com/v1/objects/42ebb0988124433b8f2a6e5d9a74ed41240bcfc6/calendars.properties"}}, "executable": false, "type": "file"}, "lib/charsets.jar": {"downloads": {"lzma": {"sha1": "2bf44143b2ad9d7d55045a4de4a562330c194dc0", "size": 412367, "url": "https://launcher.mojang.com/v1/objects/2bf44143b2ad9d7d55045a4de4a562330c194dc0/charsets.jar"}, "raw": {"sha1": "d73ab9f8de255a7e112ddd13622bf7f6b18c8447", "size": 3135615, "url": "https://launcher.mojang.com/v1/objects/d73ab9f8de255a7e112ddd13622bf7f6b18c8447/charsets.jar"}}, "executable": false, "type": "file"}, "lib/classlist": {"downloads": {"lzma": {"sha1": "14e7c73d21b8513b0aff8d86e5cb34c52021fbca", "size": 15024, "url": "https://launcher.mojang.com/v1/objects/14e7c73d21b8513b0aff8d86e5cb34c52021fbca/classlist"}, "raw": {"sha1": "9c0404b63c87e2fed35e3a6cd137d6cf876c42bd", "size": 84311, "url": "https://launcher.mojang.com/v1/objects/9c0404b63c87e2fed35e3a6cd137d6cf876c42bd/classlist"}}, "executable": false, "type": "file"}, "lib/cmm": {"type": "directory"}, "lib/cmm/CIEXYZ.pf": {"downloads": {"lzma": {"sha1": "fcc5ca2fd3f45cac3434b480fa3ce00007e96529", "size": 8964, "url": "https://launcher.mojang.com/v1/objects/fcc5ca2fd3f45cac3434b480fa3ce00007e96529/CIEXYZ.pf"}, "raw": {"sha1": "b7779924c70554647b87c2a86159ca7781e929f8", "size": 51236, "url": "https://launcher.mojang.com/v1/objects/b7779924c70554647b87c2a86159ca7781e929f8/CIEXYZ.pf"}}, "executable": false, "type": "file"}, "lib/cmm/GRAY.pf": {"downloads": {"lzma": {"sha1": "5388ccfe67d3131d6d02143d8e8895003ab14ff6", "size": 299, "url": "https://launcher.mojang.com/v1/objects/5388ccfe67d3131d6d02143d8e8895003ab14ff6/GRAY.pf"}, "raw": {"sha1": "27f93961d66b8230d0cdb8b166bc8b4153d5bc2d", "size": 632, "url": "https://launcher.mojang.com/v1/objects/27f93961d66b8230d0cdb8b166bc8b4153d5bc2d/GRAY.pf"}}, "executable": false, "type": "file"}, "lib/cmm/LINEAR_RGB.pf": {"downloads": {"lzma": {"sha1": "2bd90f09c8deb64b1729d6b8173c78f9e9cab27b", "size": 678, "url": "https://launcher.mojang.com/v1/objects/2bd90f09c8deb64b1729d6b8173c78f9e9cab27b/LINEAR_RGB.pf"}, "raw": {"sha1": "7913274c2f73bafcf888f09ff60990b100214ede", "size": 1044, "url": "https://launcher.mojang.com/v1/objects/7913274c2f73bafcf888f09ff60990b100214ede/LINEAR_RGB.pf"}}, "executable": false, "type": "file"}, "lib/cmm/PYCC.pf": {"downloads": {"lzma": {"sha1": "dbb2197ecff3fcdd142e9006490c8cb5c8d19af8", "size": 171521, "url": "https://launcher.mojang.com/v1/objects/dbb2197ecff3fcdd142e9006490c8cb5c8d19af8/PYCC.pf"}, "raw": {"sha1": "4f7eed05b8f0eea7bcdc8f8f7aaeb1925ce7b144", "size": 274474, "url": "https://launcher.mojang.com/v1/objects/4f7eed05b8f0eea7bcdc8f8f7aaeb1925ce7b144/PYCC.pf"}}, "executable": false, "type": "file"}, "lib/cmm/sRGB.pf": {"downloads": {"raw": {"sha1": "9eaea0911d89d63e39e95f2e2116eaec7e0bb91e", "size": 3144, "url": "https://launcher.mojang.com/v1/objects/9eaea0911d89d63e39e95f2e2116eaec7e0bb91e/sRGB.pf"}}, "executable": false, "type": "file"}, "lib/content-types.properties": {"downloads": {"lzma": {"sha1": "43a23d9a6c637c128b14cfa3feced93cbcf85b1a", "size": 1617, "url": "https://launcher.mojang.com/v1/objects/43a23d9a6c637c128b14cfa3feced93cbcf85b1a/content-types.properties"}, "raw": {"sha1": "b21698017c4a2866b5fabe59681b7592e72c83b1", "size": 5916, "url": "https://launcher.mojang.com/v1/objects/b21698017c4a2866b5fabe59681b7592e72c83b1/content-types.properties"}}, "executable": false, "type": "file"}, "lib/currency.data": {"downloads": {"lzma": {"sha1": "451b3f166ea34ef2aefbb01606ea5adcc0d65b42", "size": 1184, "url": "https://launcher.mojang.com/v1/objects/451b3f166ea34ef2aefbb01606ea5adcc0d65b42/currency.data"}, "raw": {"sha1": "bf524381a7a9b9d5bbab48069c583d2936e367a1", "size": 4134, "url": "https://launcher.mojang.com/v1/objects/bf524381a7a9b9d5bbab48069c583d2936e367a1/currency.data"}}, "executable": false, "type": "file"}, "lib/deploy": {"type": "directory"}, "lib/deploy.jar": {"downloads": {"raw": {"sha1": "fbe1de8fcd9a3d482c59414dce9311e4194766c9", "size": 2255881, "url": "https://launcher.mojang.com/v1/objects/fbe1de8fcd9a3d482c59414dce9311e4194766c9/deploy.jar"}}, "executable": false, "type": "file"}, "lib/deploy/MixedCodeMainDialog.ui": {"downloads": {"lzma": {"sha1": "7d812964343d1e978442f5c847c709784fc18fc0", "size": 683, "url": "https://launcher.mojang.com/v1/objects/7d812964343d1e978442f5c847c709784fc18fc0/MixedCodeMainDialog.ui"}, "raw": {"sha1": "c9b1af1c229e54b2d8a3d642d4f0bb31dc15be79", "size": 4507, "url": "https://launcher.mojang.com/v1/objects/c9b1af1c229e54b2d8a3d642d4f0bb31dc15be79/MixedCodeMainDialog.ui"}}, "executable": false, "type": "file"}, "lib/deploy/MixedCodeMainDialogJs.ui": {"downloads": {"lzma": {"sha1": "54fb58dbcc59e35e0ae896d0e266ae0c5bcf85c2", "size": 792, "url": "https://launcher.mojang.com/v1/objects/54fb58dbcc59e35e0ae896d0e266ae0c5bcf85c2/MixedCodeMainDialogJs.ui"}, "raw": {"sha1": "ad6337fb6d46750e14c12b439a5856f4b6864d0d", "size": 6110, "url": "https://launcher.mojang.com/v1/objects/ad6337fb6d46750e14c12b439a5856f4b6864d0d/MixedCodeMainDialogJs.ui"}}, "executable": false, "type": "file"}, "lib/deploy/cautionshield.icns": {"downloads": {"lzma": {"sha1": "7cea751dc168605054ec38ce8bfa71812be405c1", "size": 2333, "url": "https://launcher.mojang.com/v1/objects/7cea751dc168605054ec38ce8bfa71812be405c1/cautionshield.icns"}, "raw": {"sha1": "1de7ed5d5fc75aa1bcede088c655bee3bde64c38", "size": 3588, "url": "https://launcher.mojang.com/v1/objects/1de7ed5d5fc75aa1bcede088c655bee3bde64c38/cautionshield.icns"}}, "executable": false, "type": "file"}, "lib/deploy/ffjcext.zip": {"downloads": {"lzma": {"sha1": "80bcb9b3794f69d87dba93e90230f288e651e798", "size": 1809, "url": "https://launcher.mojang.com/v1/objects/80bcb9b3794f69d87dba93e90230f288e651e798/ffjcext.zip"}, "raw": {"sha1": "76d051ca7d3666ff25ea8eb9957a05574a45287f", "size": 13454, "url": "https://launcher.mojang.com/v1/objects/76d051ca7d3666ff25ea8eb9957a05574a45287f/ffjcext.zip"}}, "executable": false, "type": "file"}, "lib/deploy/java-icon.ico": {"downloads": {"lzma": {"sha1": "2a24f0207d7ab5976a8b0d92b4b381d49e895c9d", "size": 8468, "url": "https://launcher.mojang.com/v1/objects/2a24f0207d7ab5976a8b0d92b4b381d49e895c9d/java-icon.ico"}, "raw": {"sha1": "2997ceb26ff49a7d7c5e7a2405b5fb50b62c7914", "size": 29926, "url": "https://launcher.mojang.com/v1/objects/2997ceb26ff49a7d7c5e7a2405b5fb50b62c7914/java-icon.ico"}}, "executable": false, "type": "file"}, "lib/deploy/messages.properties": {"downloads": {"lzma": {"sha1": "c1e16f80dc0b1f1a591cecf3cbab4ba5e47492f4", "size": 1225, "url": "https://launcher.mojang.com/v1/objects/c1e16f80dc0b1f1a591cecf3cbab4ba5e47492f4/messages.properties"}, "raw": {"sha1": "dc52841c708e3c1eb2a044088a43396d1291bb5e", "size": 2860, "url": "https://launcher.mojang.com/v1/objects/dc52841c708e3c1eb2a044088a43396d1291bb5e/messages.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_de.properties": {"downloads": {"lzma": {"sha1": "42b42e6e1d2cb2d781f2226bde612ce519b29bc8", "size": 1394, "url": "https://launcher.mojang.com/v1/objects/42b42e6e1d2cb2d781f2226bde612ce519b29bc8/messages_de.properties"}, "raw": {"sha1": "d989fe1b8f7904888d5102294ebefd28d932ecdb", "size": 3306, "url": "https://launcher.mojang.com/v1/objects/d989fe1b8f7904888d5102294ebefd28d932ecdb/messages_de.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_es.properties": {"downloads": {"lzma": {"sha1": "c4a653e9802ca982e892b45d88c1e259c09e8c8e", "size": 1404, "url": "https://launcher.mojang.com/v1/objects/c4a653e9802ca982e892b45d88c1e259c09e8c8e/messages_es.properties"}, "raw": {"sha1": "1b0334b79db481c3a59be6915d5118d760c97baa", "size": 3600, "url": "https://launcher.mojang.com/v1/objects/1b0334b79db481c3a59be6915d5118d760c97baa/messages_es.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_fr.properties": {"downloads": {"lzma": {"sha1": "2d8dee07e3f5aab7318a22e169810b216ac44f97", "size": 1401, "url": "https://launcher.mojang.com/v1/objects/2d8dee07e3f5aab7318a22e169810b216ac44f97/messages_fr.properties"}, "raw": {"sha1": "69bd2d03c2064f8679de5b4e430ea61b567c69c5", "size": 3409, "url": "https://launcher.mojang.com/v1/objects/69bd2d03c2064f8679de5b4e430ea61b567c69c5/messages_fr.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_it.properties": {"downloads": {"lzma": {"sha1": "7c28cdd8d9e34355ba0fc03004c4f64749cae57e", "size": 1375, "url": "https://launcher.mojang.com/v1/objects/7c28cdd8d9e34355ba0fc03004c4f64749cae57e/messages_it.properties"}, "raw": {"sha1": "dbe49949308f28540a42ae6cd2ad58afbf615592", "size": 3223, "url": "https://launcher.mojang.com/v1/objects/dbe49949308f28540a42ae6cd2ad58afbf615592/messages_it.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_ja.properties": {"downloads": {"lzma": {"sha1": "9a6a4c086e48b9e615b72b6bbebb3c724d178ff4", "size": 1680, "url": "https://launcher.mojang.com/v1/objects/9a6a4c086e48b9e615b72b6bbebb3c724d178ff4/messages_ja.properties"}, "raw": {"sha1": "751170a7cdefcb1226604ac3f8196e06a04fd7ac", "size": 6349, "url": "https://launcher.mojang.com/v1/objects/751170a7cdefcb1226604ac3f8196e06a04fd7ac/messages_ja.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_ko.properties": {"downloads": {"lzma": {"sha1": "0c57c2ebfa0830f816657a384898487fc492efac", "size": 1645, "url": "https://launcher.mojang.com/v1/objects/0c57c2ebfa0830f816657a384898487fc492efac/messages_ko.properties"}, "raw": {"sha1": "bf9e055b5ab138ad6d49769e2b7630b7938848d6", "size": 5712, "url": "https://launcher.mojang.com/v1/objects/bf9e055b5ab138ad6d49769e2b7630b7938848d6/messages_ko.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_pt_BR.properties": {"downloads": {"lzma": {"sha1": "f8364dba0aa0a7e445a1a8d0e7ad66b996f70063", "size": 1388, "url": "https://launcher.mojang.com/v1/objects/f8364dba0aa0a7e445a1a8d0e7ad66b996f70063/messages_pt_BR.properties"}, "raw": {"sha1": "24e4951743521ab9a11381c77bd0cdb1ed30f5b5", "size": 3285, "url": "https://launcher.mojang.com/v1/objects/24e4951743521ab9a11381c77bd0cdb1ed30f5b5/messages_pt_BR.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_sv.properties": {"downloads": {"lzma": {"sha1": "65e5897d552258141aacf02f087c7c9c33ad0727", "size": 1355, "url": "https://launcher.mojang.com/v1/objects/65e5897d552258141aacf02f087c7c9c33ad0727/messages_sv.properties"}, "raw": {"sha1": "bb5a4aa0ba499f6b1916a83e3c7922a4583b4adb", "size": 3384, "url": "https://launcher.mojang.com/v1/objects/bb5a4aa0ba499f6b1916a83e3c7922a4583b4adb/messages_sv.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_zh_CN.properties": {"downloads": {"lzma": {"sha1": "de7d39a6e6748e9f47e842c9da90f515921c222c", "size": 1506, "url": "https://launcher.mojang.com/v1/objects/de7d39a6e6748e9f47e842c9da90f515921c222c/messages_zh_CN.properties"}, "raw": {"sha1": "1c2b96673dddd3596890ef4fc22017d484a1f652", "size": 4072, "url": "https://launcher.mojang.com/v1/objects/1c2b96673dddd3596890ef4fc22017d484a1f652/messages_zh_CN.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_zh_HK.properties": {"downloads": {"lzma": {"sha1": "e8d0e3a63caa2535a4f361033941f34dcc170a7e", "size": 1529, "url": "https://launcher.mojang.com/v1/objects/e8d0e3a63caa2535a4f361033941f34dcc170a7e/messages_zh_TW.properties"}, "raw": {"sha1": "37a57aad121c14c25e149206179728fa62203bf0", "size": 3752, "url": "https://launcher.mojang.com/v1/objects/37a57aad121c14c25e149206179728fa62203bf0/messages_zh_TW.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_zh_TW.properties": {"downloads": {"lzma": {"sha1": "e8d0e3a63caa2535a4f361033941f34dcc170a7e", "size": 1529, "url": "https://launcher.mojang.com/v1/objects/e8d0e3a63caa2535a4f361033941f34dcc170a7e/messages_zh_TW.properties"}, "raw": {"sha1": "37a57aad121c14c25e149206179728fa62203bf0", "size": 3752, "url": "https://launcher.mojang.com/v1/objects/37a57aad121c14c25e149206179728fa62203bf0/messages_zh_TW.properties"}}, "executable": false, "type": "file"}, "lib/deploy/mixcode_s.png": {"downloads": {"raw": {"sha1": "4604e9f265eec97bccd0151c3a81afa9e69499e5", "size": 3115, "url": "https://launcher.mojang.com/v1/objects/4604e9f265eec97bccd0151c3a81afa9e69499e5/mixcode_s.png"}}, "executable": false, "type": "file"}, "lib/deploy/splash.gif": {"downloads": {"raw": {"sha1": "20e7aec75f6d036d504277542e507eb7dc24aae8", "size": 8590, "url": "https://launcher.mojang.com/v1/objects/20e7aec75f6d036d504277542e507eb7dc24aae8/splash.gif"}}, "executable": false, "type": "file"}, "lib/deploy/splash@2x.gif": {"downloads": {"raw": {"sha1": "0ae4a5bda2a6d628fac51462390b503c99509fdc", "size": 15276, "url": "https://launcher.mojang.com/v1/objects/0ae4a5bda2a6d628fac51462390b503c99509fdc/splash2x.gif"}}, "executable": false, "type": "file"}, "lib/deploy/splash_11-lic.gif": {"downloads": {"raw": {"sha1": "8def364e07f40142822df84b5bb4f50846cb5e4e", "size": 7805, "url": "https://launcher.mojang.com/v1/objects/8def364e07f40142822df84b5bb4f50846cb5e4e/splash_11-lic.gif"}}, "executable": false, "type": "file"}, "lib/deploy/splash_11@2x-lic.gif": {"downloads": {"raw": {"sha1": "d2bff9bbf7920ca743b81a0ee23b0719b4d057ca", "size": 12250, "url": "https://launcher.mojang.com/v1/objects/d2bff9bbf7920ca743b81a0ee23b0719b4d057ca/splash_11%402x-lic.gif"}}, "executable": false, "type": "file"}, "lib/desktop": {"type": "directory"}, "lib/desktop/applications": {"type": "directory"}, "lib/desktop/applications/sun-java.desktop": {"downloads": {"lzma": {"sha1": "109d1cdf165f38da92da70b403ca940192a7a9a8", "size": 536, "url": "https://launcher.mojang.com/v1/objects/109d1cdf165f38da92da70b403ca940192a7a9a8/sun-java.desktop"}, "raw": {"sha1": "d346dfe90505603ce5aff5a3c6c2e4a23d5bd990", "size": 777, "url": "https://launcher.mojang.com/v1/objects/d346dfe90505603ce5aff5a3c6c2e4a23d5bd990/sun-java.desktop"}}, "executable": false, "type": "file"}, "lib/desktop/applications/sun-javaws.desktop": {"downloads": {"lzma": {"sha1": "5e1815e7f83515881e6998584dc6bb02c5bef09a", "size": 451, "url": "https://launcher.mojang.com/v1/objects/5e1815e7f83515881e6998584dc6bb02c5bef09a/sun-javaws.desktop"}, "raw": {"sha1": "50ce8e519b836e0f53d58ce1a359d98b6cafdda6", "size": 619, "url": "https://launcher.mojang.com/v1/objects/50ce8e519b836e0f53d58ce1a359d98b6cafdda6/sun-javaws.desktop"}}, "executable": false, "type": "file"}, "lib/desktop/applications/sun_java.desktop": {"downloads": {"lzma": {"sha1": "49ab0ccb54c3be68281d05055bc56a88b1281d3c", "size": 447, "url": "https://launcher.mojang.com/v1/objects/49ab0ccb54c3be68281d05055bc56a88b1281d3c/sun_java.desktop"}, "raw": {"sha1": "79120ee8160ad6f3c9b90c2641fb7edf3af96b5d", "size": 624, "url": "https://launcher.mojang.com/v1/objects/79120ee8160ad6f3c9b90c2641fb7edf3af96b5d/sun_java.desktop"}}, "executable": false, "type": "file"}, "lib/desktop/icons": {"type": "directory"}, "lib/desktop/icons/HighContrast": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16/apps": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "366e7a48e9e4fb92eaeabbcaeb4626122a66cecb", "size": 417, "url": "https://launcher.mojang.com/v1/objects/366e7a48e9e4fb92eaeabbcaeb4626122a66cecb/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "366e7a48e9e4fb92eaeabbcaeb4626122a66cecb", "size": 417, "url": "https://launcher.mojang.com/v1/objects/366e7a48e9e4fb92eaeabbcaeb4626122a66cecb/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "366e7a48e9e4fb92eaeabbcaeb4626122a66cecb", "size": 417, "url": "https://launcher.mojang.com/v1/objects/366e7a48e9e4fb92eaeabbcaeb4626122a66cecb/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "629c48907368ecf32d2395b6459c367f79d84689", "size": 464, "url": "https://launcher.mojang.com/v1/objects/629c48907368ecf32d2395b6459c367f79d84689/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "629c48907368ecf32d2395b6459c367f79d84689", "size": 464, "url": "https://launcher.mojang.com/v1/objects/629c48907368ecf32d2395b6459c367f79d84689/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "629c48907368ecf32d2395b6459c367f79d84689", "size": 464, "url": "https://launcher.mojang.com/v1/objects/629c48907368ecf32d2395b6459c367f79d84689/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48": {"type": "directory"}, "lib/desktop/icons/HighContrast/48x48/apps": {"type": "directory"}, "lib/desktop/icons/HighContrast/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "8373482d072684e09830dbdb97a76ea264c9f4e9", "size": 3451, "url": "https://launcher.mojang.com/v1/objects/8373482d072684e09830dbdb97a76ea264c9f4e9/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "8373482d072684e09830dbdb97a76ea264c9f4e9", "size": 3451, "url": "https://launcher.mojang.com/v1/objects/8373482d072684e09830dbdb97a76ea264c9f4e9/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "8373482d072684e09830dbdb97a76ea264c9f4e9", "size": 3451, "url": "https://launcher.mojang.com/v1/objects/8373482d072684e09830dbdb97a76ea264c9f4e9/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrast/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "56a4996519f8f3541eba7b7a7a69bcdcd8ed0410", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/56a4996519f8f3541eba7b7a7a69bcdcd8ed0410/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "56a4996519f8f3541eba7b7a7a69bcdcd8ed0410", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/56a4996519f8f3541eba7b7a7a69bcdcd8ed0410/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "56a4996519f8f3541eba7b7a7a69bcdcd8ed0410", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/56a4996519f8f3541eba7b7a7a69bcdcd8ed0410/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16/apps": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "bf0995acb94aa794e73c5b971282ff13ffe42793", "size": 402, "url": "https://launcher.mojang.com/v1/objects/bf0995acb94aa794e73c5b971282ff13ffe42793/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "bf0995acb94aa794e73c5b971282ff13ffe42793", "size": 402, "url": "https://launcher.mojang.com/v1/objects/bf0995acb94aa794e73c5b971282ff13ffe42793/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "bf0995acb94aa794e73c5b971282ff13ffe42793", "size": 402, "url": "https://launcher.mojang.com/v1/objects/bf0995acb94aa794e73c5b971282ff13ffe42793/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "1477eceda25e162fcda2e69ee3906091973d8344", "size": 473, "url": "https://launcher.mojang.com/v1/objects/1477eceda25e162fcda2e69ee3906091973d8344/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "1477eceda25e162fcda2e69ee3906091973d8344", "size": 473, "url": "https://launcher.mojang.com/v1/objects/1477eceda25e162fcda2e69ee3906091973d8344/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "1477eceda25e162fcda2e69ee3906091973d8344", "size": 473, "url": "https://launcher.mojang.com/v1/objects/1477eceda25e162fcda2e69ee3906091973d8344/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/48x48/apps": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "413da160dd9899b95f53d4cc11f5ee0550cc6585", "size": 3410, "url": "https://launcher.mojang.com/v1/objects/413da160dd9899b95f53d4cc11f5ee0550cc6585/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "413da160dd9899b95f53d4cc11f5ee0550cc6585", "size": 3410, "url": "https://launcher.mojang.com/v1/objects/413da160dd9899b95f53d4cc11f5ee0550cc6585/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "413da160dd9899b95f53d4cc11f5ee0550cc6585", "size": 3410, "url": "https://launcher.mojang.com/v1/objects/413da160dd9899b95f53d4cc11f5ee0550cc6585/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "d66e04dfa25c196bec2e201547325b79846ab674", "size": 2085, "url": "https://launcher.mojang.com/v1/objects/d66e04dfa25c196bec2e201547325b79846ab674/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "d66e04dfa25c196bec2e201547325b79846ab674", "size": 2085, "url": "https://launcher.mojang.com/v1/objects/d66e04dfa25c196bec2e201547325b79846ab674/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "d66e04dfa25c196bec2e201547325b79846ab674", "size": 2085, "url": "https://launcher.mojang.com/v1/objects/d66e04dfa25c196bec2e201547325b79846ab674/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16/apps": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "f93b7cf0a6d27d664a7f09dab6933b2768536f52", "size": 519, "url": "https://launcher.mojang.com/v1/objects/f93b7cf0a6d27d664a7f09dab6933b2768536f52/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "f93b7cf0a6d27d664a7f09dab6933b2768536f52", "size": 519, "url": "https://launcher.mojang.com/v1/objects/f93b7cf0a6d27d664a7f09dab6933b2768536f52/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "f93b7cf0a6d27d664a7f09dab6933b2768536f52", "size": 519, "url": "https://launcher.mojang.com/v1/objects/f93b7cf0a6d27d664a7f09dab6933b2768536f52/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "0aa1605877280b88de1f1cc3e7e4bdbeed968a73", "size": 525, "url": "https://launcher.mojang.com/v1/objects/0aa1605877280b88de1f1cc3e7e4bdbeed968a73/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "0aa1605877280b88de1f1cc3e7e4bdbeed968a73", "size": 525, "url": "https://launcher.mojang.com/v1/objects/0aa1605877280b88de1f1cc3e7e4bdbeed968a73/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "0aa1605877280b88de1f1cc3e7e4bdbeed968a73", "size": 525, "url": "https://launcher.mojang.com/v1/objects/0aa1605877280b88de1f1cc3e7e4bdbeed968a73/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48": {"type": "directory"}, "lib/desktop/icons/LowContrast/48x48/apps": {"type": "directory"}, "lib/desktop/icons/LowContrast/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "1fcf4fd6da61873b5f21b39412da26509734b7cc", "size": 1507, "url": "https://launcher.mojang.com/v1/objects/1fcf4fd6da61873b5f21b39412da26509734b7cc/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "1fcf4fd6da61873b5f21b39412da26509734b7cc", "size": 1507, "url": "https://launcher.mojang.com/v1/objects/1fcf4fd6da61873b5f21b39412da26509734b7cc/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "1fcf4fd6da61873b5f21b39412da26509734b7cc", "size": 1507, "url": "https://launcher.mojang.com/v1/objects/1fcf4fd6da61873b5f21b39412da26509734b7cc/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/LowContrast/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "e36636b1c04dc283c18adf669b892d54b15d3ee6", "size": 1948, "url": "https://launcher.mojang.com/v1/objects/e36636b1c04dc283c18adf669b892d54b15d3ee6/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "e36636b1c04dc283c18adf669b892d54b15d3ee6", "size": 1948, "url": "https://launcher.mojang.com/v1/objects/e36636b1c04dc283c18adf669b892d54b15d3ee6/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "e36636b1c04dc283c18adf669b892d54b15d3ee6", "size": 1948, "url": "https://launcher.mojang.com/v1/objects/e36636b1c04dc283c18adf669b892d54b15d3ee6/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16/apps": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "e91d05bfe9b889bf8a227908b597cab4630da8f2", "size": 383, "url": "https://launcher.mojang.com/v1/objects/e91d05bfe9b889bf8a227908b597cab4630da8f2/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "e91d05bfe9b889bf8a227908b597cab4630da8f2", "size": 383, "url": "https://launcher.mojang.com/v1/objects/e91d05bfe9b889bf8a227908b597cab4630da8f2/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "e91d05bfe9b889bf8a227908b597cab4630da8f2", "size": 383, "url": "https://launcher.mojang.com/v1/objects/e91d05bfe9b889bf8a227908b597cab4630da8f2/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "d2f6abe8e498aeecb334fb43f63001d34dbf6ea5", "size": 783, "url": "https://launcher.mojang.com/v1/objects/d2f6abe8e498aeecb334fb43f63001d34dbf6ea5/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "d2f6abe8e498aeecb334fb43f63001d34dbf6ea5", "size": 783, "url": "https://launcher.mojang.com/v1/objects/d2f6abe8e498aeecb334fb43f63001d34dbf6ea5/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "d2f6abe8e498aeecb334fb43f63001d34dbf6ea5", "size": 783, "url": "https://launcher.mojang.com/v1/objects/d2f6abe8e498aeecb334fb43f63001d34dbf6ea5/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48": {"type": "directory"}, "lib/desktop/icons/hicolor/48x48/apps": {"type": "directory"}, "lib/desktop/icons/hicolor/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "6c90a38eaada9c32a678a282be18ec5b43a84264", "size": 1439, "url": "https://launcher.mojang.com/v1/objects/6c90a38eaada9c32a678a282be18ec5b43a84264/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "6c90a38eaada9c32a678a282be18ec5b43a84264", "size": 1439, "url": "https://launcher.mojang.com/v1/objects/6c90a38eaada9c32a678a282be18ec5b43a84264/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "6c90a38eaada9c32a678a282be18ec5b43a84264", "size": 1439, "url": "https://launcher.mojang.com/v1/objects/6c90a38eaada9c32a678a282be18ec5b43a84264/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/hicolor/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "4d5e6e0c41d1076bc86f3ab157c88a41a5716997", "size": 3202, "url": "https://launcher.mojang.com/v1/objects/4d5e6e0c41d1076bc86f3ab157c88a41a5716997/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "4d5e6e0c41d1076bc86f3ab157c88a41a5716997", "size": 3202, "url": "https://launcher.mojang.com/v1/objects/4d5e6e0c41d1076bc86f3ab157c88a41a5716997/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "4d5e6e0c41d1076bc86f3ab157c88a41a5716997", "size": 3202, "url": "https://launcher.mojang.com/v1/objects/4d5e6e0c41d1076bc86f3ab157c88a41a5716997/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/mime": {"type": "directory"}, "lib/desktop/mime/packages": {"type": "directory"}, "lib/desktop/mime/packages/x-java-archive.xml": {"downloads": {"lzma": {"sha1": "841230729f0a59de2a1071d155d96358232b2ba1", "size": 591, "url": "https://launcher.mojang.com/v1/objects/841230729f0a59de2a1071d155d96358232b2ba1/x-java-archive.xml"}, "raw": {"sha1": "b6297fd36efa799312961f95ebf0c85c920d5037", "size": 1822, "url": "https://launcher.mojang.com/v1/objects/b6297fd36efa799312961f95ebf0c85c920d5037/x-java-archive.xml"}}, "executable": false, "type": "file"}, "lib/desktop/mime/packages/x-java-jnlp-file.xml": {"downloads": {"lzma": {"sha1": "abf9acbe7c18027c4f036c4e42bb2cf1115525fa", "size": 302, "url": "https://launcher.mojang.com/v1/objects/abf9acbe7c18027c4f036c4e42bb2cf1115525fa/x-java-jnlp-file.xml"}, "raw": {"sha1": "72f03da83ddb76c9105f619fcfa4dbdad70e6b30", "size": 412, "url": "https://launcher.mojang.com/v1/objects/72f03da83ddb76c9105f619fcfa4dbdad70e6b30/x-java-jnlp-file.xml"}}, "executable": false, "type": "file"}, "lib/ext": {"type": "directory"}, "lib/ext/cldrdata.jar": {"downloads": {"raw": {"sha1": "6cacc961942d3f02a88907aa8f2eaae8e20c95a0", "size": 3860502, "url": "https://launcher.mojang.com/v1/objects/6cacc961942d3f02a88907aa8f2eaae8e20c95a0/cldrdata.jar"}}, "executable": false, "type": "file"}, "lib/ext/dnsns.jar": {"downloads": {"raw": {"sha1": "93bebdd7514e53ae31d60c5daba673878c8822ec", "size": 8286, "url": "https://launcher.mojang.com/v1/objects/93bebdd7514e53ae31d60c5daba673878c8822ec/dnsns.jar"}}, "executable": false, "type": "file"}, "lib/ext/jaccess.jar": {"downloads": {"raw": {"sha1": "2f54879df7c29ec67c40d40cfc95c0d4a968bea1", "size": 44516, "url": "https://launcher.mojang.com/v1/objects/2f54879df7c29ec67c40d40cfc95c0d4a968bea1/jaccess.jar"}}, "executable": false, "type": "file"}, "lib/ext/jfxrt.jar": {"downloads": {"lzma": {"sha1": "a6c5b6a782ba360ada6651f5322dcab88c75711c", "size": 3374270, "url": "https://launcher.mojang.com/v1/objects/a6c5b6a782ba360ada6651f5322dcab88c75711c/jfxrt.jar"}, "raw": {"sha1": "1ad7a876f045399c23ee4ab7dee380a04ca2ac08", "size": 18508094, "url": "https://launcher.mojang.com/v1/objects/1ad7a876f045399c23ee4ab7dee380a04ca2ac08/jfxrt.jar"}}, "executable": false, "type": "file"}, "lib/ext/localedata.jar": {"downloads": {"raw": {"sha1": "0cc9f550d4e410b5aa29dbfd2c1b5c99391c7f70", "size": 1178926, "url": "https://launcher.mojang.com/v1/objects/0cc9f550d4e410b5aa29dbfd2c1b5c99391c7f70/localedata.jar"}}, "executable": false, "type": "file"}, "lib/ext/meta-index": {"downloads": {"lzma": {"sha1": "1359457529f42bacf495afcb68149ae036442dd9", "size": 594, "url": "https://launcher.mojang.com/v1/objects/1359457529f42bacf495afcb68149ae036442dd9/meta-index"}, "raw": {"sha1": "334649c6e2d5d7248211f30855e97cfcb4558851", "size": 1269, "url": "https://launcher.mojang.com/v1/objects/334649c6e2d5d7248211f30855e97cfcb4558851/meta-index"}}, "executable": false, "type": "file"}, "lib/ext/nashorn.jar": {"downloads": {"raw": {"sha1": "dec5dd17a0f52ae79dfbfb38840bffb8b7a679a5", "size": 2023869, "url": "https://launcher.mojang.com/v1/objects/dec5dd17a0f52ae79dfbfb38840bffb8b7a679a5/nashorn.jar"}}, "executable": false, "type": "file"}, "lib/ext/sunec.jar": {"downloads": {"raw": {"sha1": "bf1c817820341a246f7130fe046e8310b03d04f6", "size": 41672, "url": "https://launcher.mojang.com/v1/objects/bf1c817820341a246f7130fe046e8310b03d04f6/sunec.jar"}}, "executable": false, "type": "file"}, "lib/ext/sunjce_provider.jar": {"downloads": {"raw": {"sha1": "bb3494e4b5cb3c3e60da767207731f18b267cb34", "size": 279326, "url": "https://launcher.mojang.com/v1/objects/bb3494e4b5cb3c3e60da767207731f18b267cb34/sunjce_provider.jar"}}, "executable": false, "type": "file"}, "lib/ext/sunpkcs11.jar": {"downloads": {"raw": {"sha1": "5bb1dedc3344cd3bb86828d4aa8ca82f4a606ed4", "size": 250218, "url": "https://launcher.mojang.com/v1/objects/5bb1dedc3344cd3bb86828d4aa8ca82f4a606ed4/sunpkcs11.jar"}}, "executable": false, "type": "file"}, "lib/ext/zipfs.jar": {"downloads": {"raw": {"sha1": "37b338f0e8e60d6396f51275130e8110816d7b30", "size": 68964, "url": "https://launcher.mojang.com/v1/objects/37b338f0e8e60d6396f51275130e8110816d7b30/zipfs.jar"}}, "executable": false, "type": "file"}, "lib/flavormap.properties": {"downloads": {"lzma": {"sha1": "2d5ef19ee77ccfc95c9413eea155cde59a48fadd", "size": 1541, "url": "https://launcher.mojang.com/v1/objects/2d5ef19ee77ccfc95c9413eea155cde59a48fadd/flavormap.properties"}, "raw": {"sha1": "4e66e8fe12d7f8b3b0c4e1a1915f329bb1fbf6d2", "size": 3901, "url": "https://launcher.mojang.com/v1/objects/4e66e8fe12d7f8b3b0c4e1a1915f329bb1fbf6d2/flavormap.properties"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.5.bfc": {"downloads": {"lzma": {"sha1": "5197f6e387f16458b7408134e38b06f20f625e4c", "size": 795, "url": "https://launcher.mojang.com/v1/objects/5197f6e387f16458b7408134e38b06f20f625e4c/fontconfig.RedHat.5.bfc"}, "raw": {"sha1": "fb806ada6e68f16a9fe2b726a39d9ef5a835c0c2", "size": 4532, "url": "https://launcher.mojang.com/v1/objects/fb806ada6e68f16a9fe2b726a39d9ef5a835c0c2/fontconfig.RedHat.5.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.5.properties.src": {"downloads": {"lzma": {"sha1": "3897ae198e96e5a687c9c9b218ff5df60c868e0d", "size": 1089, "url": "https://launcher.mojang.com/v1/objects/3897ae198e96e5a687c9c9b218ff5df60c868e0d/fontconfig.RedHat.5.properties.src"}, "raw": {"sha1": "c67d1a06cb37b66e69560c9f5e4be7cf08af0493", "size": 8841, "url": "https://launcher.mojang.com/v1/objects/c67d1a06cb37b66e69560c9f5e4be7cf08af0493/fontconfig.RedHat.5.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.6.bfc": {"downloads": {"lzma": {"sha1": "ef2f5d1f8d620be9927db45d3a28bd75777245cb", "size": 818, "url": "https://launcher.mojang.com/v1/objects/ef2f5d1f8d620be9927db45d3a28bd75777245cb/fontconfig.RedHat.6.bfc"}, "raw": {"sha1": "9ba3b3e2c621c31d0ef1b2053c80f77419a19965", "size": 4250, "url": "https://launcher.mojang.com/v1/objects/9ba3b3e2c621c31d0ef1b2053c80f77419a19965/fontconfig.RedHat.6.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.6.properties.src": {"downloads": {"lzma": {"sha1": "74f4148f9d7ec3d67bbd724834d478a72cfdb0db", "size": 1111, "url": "https://launcher.mojang.com/v1/objects/74f4148f9d7ec3d67bbd724834d478a72cfdb0db/fontconfig.RedHat.6.properties.src"}, "raw": {"sha1": "768e58d4d314621c38daf9fde6d67119f370acd9", "size": 8735, "url": "https://launcher.mojang.com/v1/objects/768e58d4d314621c38daf9fde6d67119f370acd9/fontconfig.RedHat.6.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.10.bfc": {"downloads": {"lzma": {"sha1": "d8fe9b1d8d02368dcd452de93024c6f60670eb87", "size": 1083, "url": "https://launcher.mojang.com/v1/objects/d8fe9b1d8d02368dcd452de93024c6f60670eb87/fontconfig.SuSE.10.bfc"}, "raw": {"sha1": "ffd0dfbd1553e15b11649a73a0b3f48318138e0d", "size": 6702, "url": "https://launcher.mojang.com/v1/objects/ffd0dfbd1553e15b11649a73a0b3f48318138e0d/fontconfig.SuSE.10.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.10.properties.src": {"downloads": {"lzma": {"sha1": "2c382bd741a9e23114be3da82dee8290ebfca8a9", "size": 1555, "url": "https://launcher.mojang.com/v1/objects/2c382bd741a9e23114be3da82dee8290ebfca8a9/fontconfig.SuSE.10.properties.src"}, "raw": {"sha1": "a38dbdbbc514567b8281e1aea726865f37e97894", "size": 16772, "url": "https://launcher.mojang.com/v1/objects/a38dbdbbc514567b8281e1aea726865f37e97894/fontconfig.SuSE.10.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.11.bfc": {"downloads": {"lzma": {"sha1": "2b78cbf11289c9858951fea7180696ba3b7176d6", "size": 1092, "url": "https://launcher.mojang.com/v1/objects/2b78cbf11289c9858951fea7180696ba3b7176d6/fontconfig.SuSE.11.bfc"}, "raw": {"sha1": "a4d8500fcb47f6327460a95851b1368660da8302", "size": 7032, "url": "https://launcher.mojang.com/v1/objects/a4d8500fcb47f6327460a95851b1368660da8302/fontconfig.SuSE.11.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.11.properties.src": {"downloads": {"lzma": {"sha1": "5c1635803906e2c59d36492dec724dd7ae49a5ab", "size": 1589, "url": "https://launcher.mojang.com/v1/objects/5c1635803906e2c59d36492dec724dd7ae49a5ab/fontconfig.SuSE.11.properties.src"}, "raw": {"sha1": "c4b69589e41a7279a71866a9134213be19cdf88d", "size": 16781, "url": "https://launcher.mojang.com/v1/objects/c4b69589e41a7279a71866a9134213be19cdf88d/fontconfig.SuSE.11.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.Turbo.bfc": {"downloads": {"lzma": {"sha1": "1c771325d9ee4af209a3db92294451d58962c7a4", "size": 822, "url": "https://launcher.mojang.com/v1/objects/1c771325d9ee4af209a3db92294451d58962c7a4/fontconfig.Turbo.bfc"}, "raw": {"sha1": "f24368deeb85cc9d0781083bc56e773518d72219", "size": 4668, "url": "https://launcher.mojang.com/v1/objects/f24368deeb85cc9d0781083bc56e773518d72219/fontconfig.Turbo.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.Turbo.properties.src": {"downloads": {"lzma": {"sha1": "7748ffa17e2c8a34754138efa963ba39bd1cbbb3", "size": 1113, "url": "https://launcher.mojang.com/v1/objects/7748ffa17e2c8a34754138efa963ba39bd1cbbb3/fontconfig.Turbo.properties.src"}, "raw": {"sha1": "2bb7258bed7ccd4f117e4e5f892c9b13424b0c82", "size": 9192, "url": "https://launcher.mojang.com/v1/objects/2bb7258bed7ccd4f117e4e5f892c9b13424b0c82/fontconfig.Turbo.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.bfc": {"downloads": {"lzma": {"sha1": "be6d49ee8c64f458c4f0e64254963fec48d25150", "size": 286, "url": "https://launcher.mojang.com/v1/objects/be6d49ee8c64f458c4f0e64254963fec48d25150/fontconfig.bfc"}, "raw": {"sha1": "de39b0e19637f58d92a0188122514aa7247ebb5b", "size": 1678, "url": "https://launcher.mojang.com/v1/objects/de39b0e19637f58d92a0188122514aa7247ebb5b/fontconfig.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.properties.src": {"downloads": {"lzma": {"sha1": "9498d5e00e5401200667687e826e28c60fa60ba4", "size": 417, "url": "https://launcher.mojang.com/v1/objects/9498d5e00e5401200667687e826e28c60fa60ba4/fontconfig.properties.src"}, "raw": {"sha1": "3617ff1424fd204415242565541facf862b16eb4", "size": 1938, "url": "https://launcher.mojang.com/v1/objects/3617ff1424fd204415242565541facf862b16eb4/fontconfig.properties.src"}}, "executable": false, "type": "file"}, "lib/fonts": {"type": "directory"}, "lib/fonts/LucidaBrightDemiBold.ttf": {"downloads": {"lzma": {"sha1": "4f748750831a7719440dff5457f4d207d0f24d21", "size": 42347, "url": "https://launcher.mojang.com/v1/objects/4f748750831a7719440dff5457f4d207d0f24d21/LucidaBrightDemiBold.ttf"}, "raw": {"sha1": "b5c97f985639e19a3b712193ee48b55dda581fd1", "size": 75144, "url": "https://launcher.mojang.com/v1/objects/b5c97f985639e19a3b712193ee48b55dda581fd1/LucidaBrightDemiBold.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaBrightDemiItalic.ttf": {"downloads": {"lzma": {"sha1": "f82e9a688553c100ecb98412b985807ed56dff5d", "size": 43119, "url": "https://launcher.mojang.com/v1/objects/f82e9a688553c100ecb98412b985807ed56dff5d/LucidaBrightDemiItalic.ttf"}, "raw": {"sha1": "1fd1f757febf3e5f5fbb7fbf7a56587a40d57de7", "size": 75124, "url": "https://launcher.mojang.com/v1/objects/1fd1f757febf3e5f5fbb7fbf7a56587a40d57de7/LucidaBrightDemiItalic.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaBrightItalic.ttf": {"downloads": {"lzma": {"sha1": "6d630df719271319c3d53f90a3d425118b908266", "size": 46206, "url": "https://launcher.mojang.com/v1/objects/6d630df719271319c3d53f90a3d425118b908266/LucidaBrightItalic.ttf"}, "raw": {"sha1": "aa5c037865c563726ecd63d61ca26443589be425", "size": 80856, "url": "https://launcher.mojang.com/v1/objects/aa5c037865c563726ecd63d61ca26443589be425/LucidaBrightItalic.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaBrightRegular.ttf": {"downloads": {"lzma": {"sha1": "4b2e31aaec2238b6ecf9f845bad0a1c6d09fbbfe", "size": 181085, "url": "https://launcher.mojang.com/v1/objects/4b2e31aaec2238b6ecf9f845bad0a1c6d09fbbfe/LucidaBrightRegular.ttf"}, "raw": {"sha1": "5d7ed564791c900a8786936930ba99385653139c", "size": 344908, "url": "https://launcher.mojang.com/v1/objects/5d7ed564791c900a8786936930ba99385653139c/LucidaBrightRegular.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaSansDemiBold.ttf": {"downloads": {"lzma": {"sha1": "079b16dc3c4918ab1f4f760b6dc5e6586c219042", "size": 173229, "url": "https://launcher.mojang.com/v1/objects/079b16dc3c4918ab1f4f760b6dc5e6586c219042/LucidaSansDemiBold.ttf"}, "raw": {"sha1": "92b79fefc35e96190250c602a8fed85276b32a95", "size": 317896, "url": "https://launcher.mojang.com/v1/objects/92b79fefc35e96190250c602a8fed85276b32a95/LucidaSansDemiBold.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaSansRegular.ttf": {"downloads": {"lzma": {"sha1": "64a65d7b94d7153d20957ef6d06bebb4dd0f48e4", "size": 326062, "url": "https://launcher.mojang.com/v1/objects/64a65d7b94d7153d20957ef6d06bebb4dd0f48e4/LucidaSansRegular.ttf"}, "raw": {"sha1": "39cc8bcb8d4a71d4657fc92ef0b9f4e3e9e67add", "size": 698236, "url": "https://launcher.mojang.com/v1/objects/39cc8bcb8d4a71d4657fc92ef0b9f4e3e9e67add/LucidaSansRegular.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaTypewriterBold.ttf": {"downloads": {"lzma": {"sha1": "cdb017f7c34bea0802bc5ea5583aef721ed99c49", "size": 130412, "url": "https://launcher.mojang.com/v1/objects/cdb017f7c34bea0802bc5ea5583aef721ed99c49/LucidaTypewriterBold.ttf"}, "raw": {"sha1": "a5da2eb49448f461470387c939f0e69119310e0b", "size": 234068, "url": "https://launcher.mojang.com/v1/objects/a5da2eb49448f461470387c939f0e69119310e0b/LucidaTypewriterBold.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaTypewriterRegular.ttf": {"downloads": {"lzma": {"sha1": "aeda4a09a53783b0dc97de8e20071bea874cbfe5", "size": 135184, "url": "https://launcher.mojang.com/v1/objects/aeda4a09a53783b0dc97de8e20071bea874cbfe5/LucidaTypewriterRegular.ttf"}, "raw": {"sha1": "c144dcafe4faf2e79cfd74d8134a631f30234db1", "size": 242700, "url": "https://launcher.mojang.com/v1/objects/c144dcafe4faf2e79cfd74d8134a631f30234db1/LucidaTypewriterRegular.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/fonts.dir": {"downloads": {"lzma": {"sha1": "68f2dd93b215ec8b8d9409d2b9c825632c6b907d", "size": 273, "url": "https://launcher.mojang.com/v1/objects/68f2dd93b215ec8b8d9409d2b9c825632c6b907d/fonts.dir"}, "raw": {"sha1": "97f40cca185c954adf5cc582345a7cb8e4c50578", "size": 4041, "url": "https://launcher.mojang.com/v1/objects/97f40cca185c954adf5cc582345a7cb8e4c50578/fonts.dir"}}, "executable": false, "type": "file"}, "lib/hijrah-config-umalqura.properties": {"downloads": {"lzma": {"sha1": "02e8d296e3b18a450f1ed1547cbf2b7275664c9a", "size": 1969, "url": "https://launcher.mojang.com/v1/objects/02e8d296e3b18a450f1ed1547cbf2b7275664c9a/hijrah-config-umalqura.properties"}, "raw": {"sha1": "84aa425100740722e91f4725caf849e7863d12ba", "size": 13962, "url": "https://launcher.mojang.com/v1/objects/84aa425100740722e91f4725caf849e7863d12ba/hijrah-config-umalqura.properties"}}, "executable": false, "type": "file"}, "lib/images": {"type": "directory"}, "lib/images/cursors": {"type": "directory"}, "lib/images/cursors/cursors.properties": {"downloads": {"lzma": {"sha1": "612bd0f610ee1023947c4a2a8d3fc7d6f97e7d8f", "size": 385, "url": "https://launcher.mojang.com/v1/objects/612bd0f610ee1023947c4a2a8d3fc7d6f97e7d8f/cursors.properties"}, "raw": {"sha1": "f2b9a22ddd0a77869497a64f28f07e89a7d41f48", "size": 1274, "url": "https://launcher.mojang.com/v1/objects/f2b9a22ddd0a77869497a64f28f07e89a7d41f48/cursors.properties"}}, "executable": false, "type": "file"}, "lib/images/cursors/invalid32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_CopyDrop32x32.gif": {"downloads": {"raw": {"sha1": "eb7620fae702172aa663a19d170a0b929d3b11d1", "size": 158, "url": "https://launcher.mojang.com/v1/objects/eb7620fae702172aa663a19d170a0b929d3b11d1/motif_CopyDrop32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_CopyNoDrop32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_LinkDrop32x32.gif": {"downloads": {"raw": {"sha1": "9699137f990c240e714481563181069c8f6c17bb", "size": 162, "url": "https://launcher.mojang.com/v1/objects/9699137f990c240e714481563181069c8f6c17bb/motif_LinkDrop32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_LinkNoDrop32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_MoveDrop32x32.gif": {"downloads": {"raw": {"sha1": "03c1617ce3c5ab8af03e46d30a8c8f31ab57fb1b", "size": 141, "url": "https://launcher.mojang.com/v1/objects/03c1617ce3c5ab8af03e46d30a8c8f31ab57fb1b/motif_MoveDrop32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_MoveNoDrop32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/icons": {"type": "directory"}, "lib/images/icons/sun-java.png": {"downloads": {"raw": {"sha1": "d101b693aa054f51097eebdfeed8b8a6ca7b55b8", "size": 4707, "url": "https://launcher.mojang.com/v1/objects/d101b693aa054f51097eebdfeed8b8a6ca7b55b8/sun-java.png"}}, "executable": false, "type": "file"}, "lib/images/icons/sun-java_HighContrast.png": {"downloads": {"raw": {"sha1": "a6b1e418d6b5d03719b96f61f0c5236a60970151", "size": 3729, "url": "https://launcher.mojang.com/v1/objects/a6b1e418d6b5d03719b96f61f0c5236a60970151/sun-java_HighContrast.png"}}, "executable": false, "type": "file"}, "lib/images/icons/sun-java_HighContrastInverse.png": {"downloads": {"raw": {"sha1": "2dda28b9bddc9b5b018e3e8a8b062a99d9b2f887", "size": 3777, "url": "https://launcher.mojang.com/v1/objects/2dda28b9bddc9b5b018e3e8a8b062a99d9b2f887/sun-java_HighContrastInverse.png"}}, "executable": false, "type": "file"}, "lib/images/icons/sun-java_LowContrast.png": {"downloads": {"raw": {"sha1": "7714cc4e894c3626c8da6fe742ed22b2829122d9", "size": 4012, "url": "https://launcher.mojang.com/v1/objects/7714cc4e894c3626c8da6fe742ed22b2829122d9/sun-java_LowContrast.png"}}, "executable": false, "type": "file"}, "lib/javafx.properties": {"downloads": {"raw": {"sha1": "49e6b75d109e5fd3f6cbe7cc5fa9a7980796d14d", "size": 56, "url": "https://launcher.mojang.com/v1/objects/49e6b75d109e5fd3f6cbe7cc5fa9a7980796d14d/javafx.properties"}}, "executable": false, "type": "file"}, "lib/javaws.jar": {"downloads": {"raw": {"sha1": "04fa5ae04ead65b91be5dee575497e49ffd49fe9", "size": 488118, "url": "https://launcher.mojang.com/v1/objects/04fa5ae04ead65b91be5dee575497e49ffd49fe9/javaws.jar"}}, "executable": false, "type": "file"}, "lib/jce.jar": {"downloads": {"raw": {"sha1": "5460adee09cc5fc8829c0acfc46c34670a7d70a0", "size": 115646, "url": "https://launcher.mojang.com/v1/objects/5460adee09cc5fc8829c0acfc46c34670a7d70a0/jce.jar"}}, "executable": false, "type": "file"}, "lib/jexec": {"downloads": {"lzma": {"sha1": "2d4323d4e060f8126d026ca6c03b8972aedd2fab", "size": 3311, "url": "https://launcher.mojang.com/v1/objects/2d4323d4e060f8126d026ca6c03b8972aedd2fab/jexec"}, "raw": {"sha1": "6aa01f1d8d103974164bcfaea03c04eeeefd7d41", "size": 13376, "url": "https://launcher.mojang.com/v1/objects/6aa01f1d8d103974164bcfaea03c04eeeefd7d41/jexec"}}, "executable": true, "type": "file"}, "lib/jfr": {"type": "directory"}, "lib/jfr.jar": {"downloads": {"lzma": {"sha1": "5b9d615c91c72f4fe356d9b4105946679452d1e1", "size": 137982, "url": "https://launcher.mojang.com/v1/objects/5b9d615c91c72f4fe356d9b4105946679452d1e1/jfr.jar"}, "raw": {"sha1": "0f3fd66a336703d935bdc22ad8082bc51d34e534", "size": 560713, "url": "https://launcher.mojang.com/v1/objects/0f3fd66a336703d935bdc22ad8082bc51d34e534/jfr.jar"}}, "executable": false, "type": "file"}, "lib/jfr/default.jfc": {"downloads": {"lzma": {"sha1": "373ddd878146dd8ce8991c2c5115a05a82859bdb", "size": 2207, "url": "https://launcher.mojang.com/v1/objects/373ddd878146dd8ce8991c2c5115a05a82859bdb/default.jfc"}, "raw": {"sha1": "1a64b68d0e7d43f8149faba94440be54f4f24527", "size": 20109, "url": "https://launcher.mojang.com/v1/objects/1a64b68d0e7d43f8149faba94440be54f4f24527/default.jfc"}}, "executable": false, "type": "file"}, "lib/jfr/profile.jfc": {"downloads": {"lzma": {"sha1": "3dcdc5feee3ccfb66bc8726b666944cd4bdadae3", "size": 2199, "url": "https://launcher.mojang.com/v1/objects/3dcdc5feee3ccfb66bc8726b666944cd4bdadae3/profile.jfc"}, "raw": {"sha1": "5d7d08a595f76322c51ae43ea966fbba6b69eebe", "size": 20065, "url": "https://launcher.mojang.com/v1/objects/5d7d08a595f76322c51ae43ea966fbba6b69eebe/profile.jfc"}}, "executable": false, "type": "file"}, "lib/jfxswt.jar": {"downloads": {"raw": {"sha1": "99d9a264c898d84c01e1c42565e7fe1a89dcd72d", "size": 33932, "url": "https://launcher.mojang.com/v1/objects/99d9a264c898d84c01e1c42565e7fe1a89dcd72d/jfxswt.jar"}}, "executable": false, "type": "file"}, "lib/jsse.jar": {"downloads": {"lzma": {"sha1": "94a17dfbc2e76cd12c33970a15341424f875a9ce", "size": 187549, "url": "https://launcher.mojang.com/v1/objects/94a17dfbc2e76cd12c33970a15341424f875a9ce/jsse.jar"}, "raw": {"sha1": "92c5c626e8a2d16f41272c0e404d4f992dd8310a", "size": 675599, "url": "https://launcher.mojang.com/v1/objects/92c5c626e8a2d16f41272c0e404d4f992dd8310a/jsse.jar"}}, "executable": false, "type": "file"}, "lib/jvm.hprof.txt": {"downloads": {"lzma": {"sha1": "eccdb240a815b2a83a502749339b27bb8669965b", "size": 1863, "url": "https://launcher.mojang.com/v1/objects/eccdb240a815b2a83a502749339b27bb8669965b/jvm.hprof.txt"}, "raw": {"sha1": "fbd61d52534cdd0c15df332114d469c65d001e33", "size": 4226, "url": "https://launcher.mojang.com/v1/objects/fbd61d52534cdd0c15df332114d469c65d001e33/jvm.hprof.txt"}}, "executable": false, "type": "file"}, "lib/locale": {"type": "directory"}, "lib/locale/de": {"type": "directory"}, "lib/locale/de/LC_MESSAGES": {"type": "directory"}, "lib/locale/de/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "3061d922907cc557208109088fc6ab81d577ff6f", "size": 970, "url": "https://launcher.mojang.com/v1/objects/3061d922907cc557208109088fc6ab81d577ff6f/sunw_java_plugin.mo"}, "raw": {"sha1": "5b223a3d723ac1cfce63623fb109f2868d47d1b7", "size": 2483, "url": "https://launcher.mojang.com/v1/objects/5b223a3d723ac1cfce63623fb109f2868d47d1b7/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/es": {"type": "directory"}, "lib/locale/es/LC_MESSAGES": {"type": "directory"}, "lib/locale/es/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "24338049a89b323e17182b3a3006b50565d4fa0f", "size": 979, "url": "https://launcher.mojang.com/v1/objects/24338049a89b323e17182b3a3006b50565d4fa0f/sunw_java_plugin.mo"}, "raw": {"sha1": "6cc63dc97f2fdb2ed799e48b1dc98c4f37cdecc1", "size": 2477, "url": "https://launcher.mojang.com/v1/objects/6cc63dc97f2fdb2ed799e48b1dc98c4f37cdecc1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/fr": {"type": "directory"}, "lib/locale/fr/LC_MESSAGES": {"type": "directory"}, "lib/locale/fr/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "22796a48ef39f57d2d6fa70f41308e493d7f05c1", "size": 1033, "url": "https://launcher.mojang.com/v1/objects/22796a48ef39f57d2d6fa70f41308e493d7f05c1/sunw_java_plugin.mo"}, "raw": {"sha1": "d9d5b458db6e83fdf85c3526aeee3f57c4929840", "size": 2746, "url": "https://launcher.mojang.com/v1/objects/d9d5b458db6e83fdf85c3526aeee3f57c4929840/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/it": {"type": "directory"}, "lib/locale/it/LC_MESSAGES": {"type": "directory"}, "lib/locale/it/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "59a4cae38bfb8927745674d0efc2f284bc277987", "size": 958, "url": "https://launcher.mojang.com/v1/objects/59a4cae38bfb8927745674d0efc2f284bc277987/sunw_java_plugin.mo"}, "raw": {"sha1": "f6e72e3b2141ccc3dffab10ae14a754e494577ba", "size": 2434, "url": "https://launcher.mojang.com/v1/objects/f6e72e3b2141ccc3dffab10ae14a754e494577ba/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/ja": {"type": "directory"}, "lib/locale/ja/LC_MESSAGES": {"type": "directory"}, "lib/locale/ja/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "7d6aeed563e1cefcf0224cf522048468088884a9", "size": 1036, "url": "https://launcher.mojang.com/v1/objects/7d6aeed563e1cefcf0224cf522048468088884a9/sunw_java_plugin.mo"}, "raw": {"sha1": "378881a8cb8dd2aebb43eacd0c68519be4f258b1", "size": 2415, "url": "https://launcher.mojang.com/v1/objects/378881a8cb8dd2aebb43eacd0c68519be4f258b1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/ko": {"type": "directory"}, "lib/locale/ko.UTF-8": {"type": "directory"}, "lib/locale/ko.UTF-8/LC_MESSAGES": {"type": "directory"}, "lib/locale/ko.UTF-8/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "12ee3b21511e8497d95ea0ba9d6fe519227d0b16", "size": 1069, "url": "https://launcher.mojang.com/v1/objects/12ee3b21511e8497d95ea0ba9d6fe519227d0b16/sunw_java_plugin.mo"}, "raw": {"sha1": "cb19df01c59662dbe2f4050b1290d374b82fe1fa", "size": 2753, "url": "https://launcher.mojang.com/v1/objects/cb19df01c59662dbe2f4050b1290d374b82fe1fa/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/ko/LC_MESSAGES": {"type": "directory"}, "lib/locale/ko/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "6e2e47c64c360517fd436bc79c823b5679a1efe6", "size": 996, "url": "https://launcher.mojang.com/v1/objects/6e2e47c64c360517fd436bc79c823b5679a1efe6/sunw_java_plugin.mo"}, "raw": {"sha1": "12c8a118d150c78f719314df6dec49a967af71e9", "size": 2399, "url": "https://launcher.mojang.com/v1/objects/12c8a118d150c78f719314df6dec49a967af71e9/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/pt_BR": {"type": "directory"}, "lib/locale/pt_BR/LC_MESSAGES": {"type": "directory"}, "lib/locale/pt_BR/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "bcaa7e7916493f071f1bf64bf58c6b038e3569c9", "size": 940, "url": "https://launcher.mojang.com/v1/objects/bcaa7e7916493f071f1bf64bf58c6b038e3569c9/sunw_java_plugin.mo"}, "raw": {"sha1": "a3bc0c43994c53c59bba94982cf95f6d36283dd0", "size": 2420, "url": "https://launcher.mojang.com/v1/objects/a3bc0c43994c53c59bba94982cf95f6d36283dd0/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/sv": {"type": "directory"}, "lib/locale/sv/LC_MESSAGES": {"type": "directory"}, "lib/locale/sv/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "76017835d6261fe2eedbcbe5eb08a7484c3080c5", "size": 946, "url": "https://launcher.mojang.com/v1/objects/76017835d6261fe2eedbcbe5eb08a7484c3080c5/sunw_java_plugin.mo"}, "raw": {"sha1": "09a47686edec4bbb34e82fbd08559f8bb6266544", "size": 2359, "url": "https://launcher.mojang.com/v1/objects/09a47686edec4bbb34e82fbd08559f8bb6266544/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh": {"type": "directory"}, "lib/locale/zh.GBK": {"type": "directory"}, "lib/locale/zh.GBK/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh.GBK/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "75fd04045bf5890b8bb822770bfdb90a2e9ea65b", "size": 902, "url": "https://launcher.mojang.com/v1/objects/75fd04045bf5890b8bb822770bfdb90a2e9ea65b/sunw_java_plugin.mo"}, "raw": {"sha1": "7006fe7767b8807441a1f359a90509b3e507b0d1", "size": 2002, "url": "https://launcher.mojang.com/v1/objects/7006fe7767b8807441a1f359a90509b3e507b0d1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "75fd04045bf5890b8bb822770bfdb90a2e9ea65b", "size": 902, "url": "https://launcher.mojang.com/v1/objects/75fd04045bf5890b8bb822770bfdb90a2e9ea65b/sunw_java_plugin.mo"}, "raw": {"sha1": "7006fe7767b8807441a1f359a90509b3e507b0d1", "size": 2002, "url": "https://launcher.mojang.com/v1/objects/7006fe7767b8807441a1f359a90509b3e507b0d1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh_HK.BIG5HK": {"type": "directory"}, "lib/locale/zh_HK.BIG5HK/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh_HK.BIG5HK/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "3a1397bb1b1741697be1479232b6d9599940c851", "size": 912, "url": "https://launcher.mojang.com/v1/objects/3a1397bb1b1741697be1479232b6d9599940c851/sunw_java_plugin.mo"}, "raw": {"sha1": "c6023544067278c78599921f1032de353ff7da42", "size": 2025, "url": "https://launcher.mojang.com/v1/objects/c6023544067278c78599921f1032de353ff7da42/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh_TW": {"type": "directory"}, "lib/locale/zh_TW.BIG5": {"type": "directory"}, "lib/locale/zh_TW.BIG5/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh_TW.BIG5/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "3a1397bb1b1741697be1479232b6d9599940c851", "size": 912, "url": "https://launcher.mojang.com/v1/objects/3a1397bb1b1741697be1479232b6d9599940c851/sunw_java_plugin.mo"}, "raw": {"sha1": "c6023544067278c78599921f1032de353ff7da42", "size": 2025, "url": "https://launcher.mojang.com/v1/objects/c6023544067278c78599921f1032de353ff7da42/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh_TW/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh_TW/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "c05e610e75182f0c4e77f3e7a4d9670ed62bf63c", "size": 897, "url": "https://launcher.mojang.com/v1/objects/c05e610e75182f0c4e77f3e7a4d9670ed62bf63c/sunw_java_plugin.mo"}, "raw": {"sha1": "f9b972dd059eae3cd337dfcef6a178e8ed8a7db6", "size": 2025, "url": "https://launcher.mojang.com/v1/objects/f9b972dd059eae3cd337dfcef6a178e8ed8a7db6/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/logging.properties": {"downloads": {"lzma": {"sha1": "642202a58e5216d086ad37c0b5a633be802edc78", "size": 896, "url": "https://launcher.mojang.com/v1/objects/642202a58e5216d086ad37c0b5a633be802edc78/logging.properties"}, "raw": {"sha1": "89da8094484891f9ec1fa40c6c8b61f94c5869d0", "size": 2455, "url": "https://launcher.mojang.com/v1/objects/89da8094484891f9ec1fa40c6c8b61f94c5869d0/logging.properties"}}, "executable": false, "type": "file"}, "lib/management": {"type": "directory"}, "lib/management-agent.jar": {"downloads": {"lzma": {"sha1": "3ea0bf17e14b3428296a0f4011bf4025fcbfa4bd", "size": 243, "url": "https://launcher.mojang.com/v1/objects/3ea0bf17e14b3428296a0f4011bf4025fcbfa4bd/management-agent.jar"}, "raw": {"sha1": "9fbed36522aa3a80bac08a328942cbc5ef39ca8e", "size": 381, "url": "https://launcher.mojang.com/v1/objects/9fbed36522aa3a80bac08a328942cbc5ef39ca8e/management-agent.jar"}}, "executable": false, "type": "file"}, "lib/management/jmxremote.access": {"downloads": {"lzma": {"sha1": "69042ff1b14165db19c9d728614639dec16d6a31", "size": 1419, "url": "https://launcher.mojang.com/v1/objects/69042ff1b14165db19c9d728614639dec16d6a31/jmxremote.access"}, "raw": {"sha1": "21200eaad898ba4a2a8834a032efb6616fabb930", "size": 3998, "url": "https://launcher.mojang.com/v1/objects/21200eaad898ba4a2a8834a032efb6616fabb930/jmxremote.access"}}, "executable": false, "type": "file"}, "lib/management/jmxremote.password.template": {"downloads": {"lzma": {"sha1": "556c64b1e920766f8867be3964de6e49f5b81a60", "size": 1129, "url": "https://launcher.mojang.com/v1/objects/556c64b1e920766f8867be3964de6e49f5b81a60/jmxremote.password.template"}, "raw": {"sha1": "c1e0f01408bf20fbbb8b4810520c725f70050db5", "size": 2856, "url": "https://launcher.mojang.com/v1/objects/c1e0f01408bf20fbbb8b4810520c725f70050db5/jmxremote.password.template"}}, "executable": false, "type": "file"}, "lib/management/management.properties": {"downloads": {"lzma": {"sha1": "3e52f9baa6394ca6956845424c607e5cde5d3c67", "size": 3176, "url": "https://launcher.mojang.com/v1/objects/3e52f9baa6394ca6956845424c607e5cde5d3c67/management.properties"}, "raw": {"sha1": "e0451d8d7d9e84d7b1c39ec7d00993307a5cbbf1", "size": 14630, "url": "https://launcher.mojang.com/v1/objects/e0451d8d7d9e84d7b1c39ec7d00993307a5cbbf1/management.properties"}}, "executable": false, "type": "file"}, "lib/management/snmp.acl.template": {"downloads": {"lzma": {"sha1": "9a4aa6396c3b488b0663bed5e5ecb762985669c9", "size": 1121, "url": "https://launcher.mojang.com/v1/objects/9a4aa6396c3b488b0663bed5e5ecb762985669c9/snmp.acl.template"}, "raw": {"sha1": "2e9f9ac287274532eb1f0d1afcefd7f3e97cc794", "size": 3376, "url": "https://launcher.mojang.com/v1/objects/2e9f9ac287274532eb1f0d1afcefd7f3e97cc794/snmp.acl.template"}}, "executable": false, "type": "file"}, "lib/meta-index": {"downloads": {"lzma": {"sha1": "1ac60b31362fda4725c665b591c5fbe384cbc8c1", "size": 788, "url": "https://launcher.mojang.com/v1/objects/1ac60b31362fda4725c665b591c5fbe384cbc8c1/meta-index"}, "raw": {"sha1": "bf204f09242203e713c31785158a0792f9edb600", "size": 2034, "url": "https://launcher.mojang.com/v1/objects/bf204f09242203e713c31785158a0792f9edb600/meta-index"}}, "executable": false, "type": "file"}, "lib/net.properties": {"downloads": {"lzma": {"sha1": "e9ec3981a0797bf55bb87b24d9eb651ce7e6916b", "size": 1830, "url": "https://launcher.mojang.com/v1/objects/e9ec3981a0797bf55bb87b24d9eb651ce7e6916b/net.properties"}, "raw": {"sha1": "fd9471742eb759f4478bb1de9a0dc0527265b6ea", "size": 5352, "url": "https://launcher.mojang.com/v1/objects/fd9471742eb759f4478bb1de9a0dc0527265b6ea/net.properties"}}, "executable": false, "type": "file"}, "lib/oblique-fonts": {"type": "directory"}, "lib/oblique-fonts/LucidaSansDemiOblique.ttf": {"downloads": {"lzma": {"sha1": "49c8980c1b89bbdbab59d0f5bd5bebf0afcb93b2", "size": 38580, "url": "https://launcher.mojang.com/v1/objects/49c8980c1b89bbdbab59d0f5bd5bebf0afcb93b2/LucidaSansDemiOblique.ttf"}, "raw": {"sha1": "53e4e12a675ac222469341c3dbc102464a1be4c7", "size": 91352, "url": "https://launcher.mojang.com/v1/objects/53e4e12a675ac222469341c3dbc102464a1be4c7/LucidaSansDemiOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/LucidaSansOblique.ttf": {"downloads": {"lzma": {"sha1": "553123c0edcd08035dede4ffd92b5b81c9a7538a", "size": 116575, "url": "https://launcher.mojang.com/v1/objects/553123c0edcd08035dede4ffd92b5b81c9a7538a/LucidaSansOblique.ttf"}, "raw": {"sha1": "95a195ad4fc520b3e395c85b747fc3024d118dd9", "size": 253724, "url": "https://launcher.mojang.com/v1/objects/95a195ad4fc520b3e395c85b747fc3024d118dd9/LucidaSansOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/LucidaTypewriterBoldOblique.ttf": {"downloads": {"lzma": {"sha1": "2475b08151556ad4d89bb1d2b6494c6bee9abd82", "size": 29954, "url": "https://launcher.mojang.com/v1/objects/2475b08151556ad4d89bb1d2b6494c6bee9abd82/LucidaTypewriterBoldOblique.ttf"}, "raw": {"sha1": "f331fc8b0cc494702bc46b690f2b8eed36469a02", "size": 63168, "url": "https://launcher.mojang.com/v1/objects/f331fc8b0cc494702bc46b690f2b8eed36469a02/LucidaTypewriterBoldOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/LucidaTypewriterOblique.ttf": {"downloads": {"lzma": {"sha1": "5b970bc3b7abb21dce1aa28ff7f03459d351e552", "size": 60133, "url": "https://launcher.mojang.com/v1/objects/5b970bc3b7abb21dce1aa28ff7f03459d351e552/LucidaTypewriterOblique.ttf"}, "raw": {"sha1": "f8ea00db73f8a89a27674d050edc37c2280930e1", "size": 137484, "url": "https://launcher.mojang.com/v1/objects/f8ea00db73f8a89a27674d050edc37c2280930e1/LucidaTypewriterOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/fonts.dir": {"downloads": {"lzma": {"sha1": "067528c789bd713c7c3f34e779aa6e2e8253dcf6", "size": 188, "url": "https://launcher.mojang.com/v1/objects/067528c789bd713c7c3f34e779aa6e2e8253dcf6/fonts.dir"}, "raw": {"sha1": "5aee54ffba9e33de56fd84ef64fa496b898585bb", "size": 2115, "url": "https://launcher.mojang.com/v1/objects/5aee54ffba9e33de56fd84ef64fa496b898585bb/fonts.dir"}}, "executable": false, "type": "file"}, "lib/plugin.jar": {"downloads": {"raw": {"sha1": "3f250842c79112bae5369e372025b166990820e8", "size": 950772, "url": "https://launcher.mojang.com/v1/objects/3f250842c79112bae5369e372025b166990820e8/plugin.jar"}}, "executable": false, "type": "file"}, "lib/psfont.properties.ja": {"downloads": {"lzma": {"sha1": "7ca1cc244ed251cd1eb2347f1eea37d7d18c8ad4", "size": 701, "url": "https://launcher.mojang.com/v1/objects/7ca1cc244ed251cd1eb2347f1eea37d7d18c8ad4/psfont.properties.ja"}, "raw": {"sha1": "56ed1c661eeede17b4fae8c9de7b5edbad387abc", "size": 2796, "url": "https://launcher.mojang.com/v1/objects/56ed1c661eeede17b4fae8c9de7b5edbad387abc/psfont.properties.ja"}}, "executable": false, "type": "file"}, "lib/psfontj2d.properties": {"downloads": {"lzma": {"sha1": "4252fa01af8739a3545e2b705e3383892e22ab40", "size": 2278, "url": "https://launcher.mojang.com/v1/objects/4252fa01af8739a3545e2b705e3383892e22ab40/psfontj2d.properties"}, "raw": {"sha1": "aa327a22a49967f4d74afeee6726f505f209692f", "size": 10393, "url": "https://launcher.mojang.com/v1/objects/aa327a22a49967f4d74afeee6726f505f209692f/psfontj2d.properties"}}, "executable": false, "type": "file"}, "lib/resources.jar": {"downloads": {"lzma": {"sha1": "1b0e08441750dc17efe4b527aa146da6cc14e8a6", "size": 579294, "url": "https://launcher.mojang.com/v1/objects/1b0e08441750dc17efe4b527aa146da6cc14e8a6/resources.jar"}, "raw": {"sha1": "daa021906e4648d4c37e798c11733dc2047f2da1", "size": 3505206, "url": "https://launcher.mojang.com/v1/objects/daa021906e4648d4c37e798c11733dc2047f2da1/resources.jar"}}, "executable": false, "type": "file"}, "lib/rt.jar": {"downloads": {"lzma": {"sha1": "fc4a8681aeda29c2a2a3fd11bad7729543283f3d", "size": 14378994, "url": "https://launcher.mojang.com/v1/objects/fc4a8681aeda29c2a2a3fd11bad7729543283f3d/rt.jar"}, "raw": {"sha1": "5396b0954a20f3210f1f4f1886ead30880d6ebfe", "size": 66334986, "url": "https://launcher.mojang.com/v1/objects/5396b0954a20f3210f1f4f1886ead30880d6ebfe/rt.jar"}}, "executable": false, "type": "file"}, "lib/security": {"type": "directory"}, "lib/security/blacklist": {"downloads": {"lzma": {"sha1": "8206fce6c1d91a39fdf78e8e79e953913994a1cd", "size": 1969, "url": "https://launcher.mojang.com/v1/objects/8206fce6c1d91a39fdf78e8e79e953913994a1cd/blacklist"}, "raw": {"sha1": "d4ffb3857eab403955ce9d156e46d056061e6a5a", "size": 4054, "url": "https://launcher.mojang.com/v1/objects/d4ffb3857eab403955ce9d156e46d056061e6a5a/blacklist"}}, "executable": false, "type": "file"}, "lib/security/blacklisted.certs": {"downloads": {"lzma": {"sha1": "8311bead054caf6cfe678d4b7998de4caaabfa53", "size": 806, "url": "https://launcher.mojang.com/v1/objects/8311bead054caf6cfe678d4b7998de4caaabfa53/blacklisted.certs"}, "raw": {"sha1": "c5c005c29a80493f5c31cd7eb629ac1b9c752404", "size": 1273, "url": "https://launcher.mojang.com/v1/objects/c5c005c29a80493f5c31cd7eb629ac1b9c752404/blacklisted.certs"}}, "executable": false, "type": "file"}, "lib/security/cacerts": {"downloads": {"lzma": {"sha1": "654dd94809655d5b28385cbb5eba8d6ad9f2c1aa", "size": 67802, "url": "https://launcher.mojang.com/v1/objects/654dd94809655d5b28385cbb5eba8d6ad9f2c1aa/cacerts"}, "raw": {"sha1": "2917859c443c68e19f93abcd1315c3c2904cbef9", "size": 104430, "url": "https://launcher.mojang.com/v1/objects/2917859c443c68e19f93abcd1315c3c2904cbef9/cacerts"}}, "executable": false, "type": "file"}, "lib/security/java.policy": {"downloads": {"lzma": {"sha1": "b601c420d02ef3dbd8595453d08fdef91134e8b5", "size": 647, "url": "https://launcher.mojang.com/v1/objects/b601c420d02ef3dbd8595453d08fdef91134e8b5/java.policy"}, "raw": {"sha1": "c0112209a567b3b523cfed7041709f9440227968", "size": 2466, "url": "https://launcher.mojang.com/v1/objects/c0112209a567b3b523cfed7041709f9440227968/java.policy"}}, "executable": false, "type": "file"}, "lib/security/java.security": {"downloads": {"lzma": {"sha1": "531620e82ca0365ce8dc97096bb0ac5a7ace5952", "size": 10959, "url": "https://launcher.mojang.com/v1/objects/531620e82ca0365ce8dc97096bb0ac5a7ace5952/java.security"}, "raw": {"sha1": "5dcc17a168c53d0b366784e520bd4d55aa61ac18", "size": 41528, "url": "https://launcher.mojang.com/v1/objects/5dcc17a168c53d0b366784e520bd4d55aa61ac18/java.security"}}, "executable": false, "type": "file"}, "lib/security/javaws.policy": {"downloads": {"raw": {"sha1": "4384ca5e4d32f7dd86d8baddd1e690730d74e694", "size": 98, "url": "https://launcher.mojang.com/v1/objects/4384ca5e4d32f7dd86d8baddd1e690730d74e694/javaws.policy"}}, "executable": false, "type": "file"}, "lib/security/policy": {"type": "directory"}, "lib/security/policy/limited": {"type": "directory"}, "lib/security/policy/limited/US_export_policy.jar": {"downloads": {"raw": {"sha1": "7d69ea3b385bc067738520f1b5c549e1084be285", "size": 3026, "url": "https://launcher.mojang.com/v1/objects/7d69ea3b385bc067738520f1b5c549e1084be285/US_export_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/policy/limited/local_policy.jar": {"downloads": {"raw": {"sha1": "238b8826e110f58acb2e1959773b0a577cd4d569", "size": 3527, "url": "https://launcher.mojang.com/v1/objects/238b8826e110f58acb2e1959773b0a577cd4d569/local_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/policy/unlimited": {"type": "directory"}, "lib/security/policy/unlimited/US_export_policy.jar": {"downloads": {"raw": {"sha1": "f6fb2af1e87fc622cda194a7d6b5f5f069653ff1", "size": 3023, "url": "https://launcher.mojang.com/v1/objects/f6fb2af1e87fc622cda194a7d6b5f5f069653ff1/US_export_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/policy/unlimited/local_policy.jar": {"downloads": {"raw": {"sha1": "517368ab2cbaf6b42ea0b963f98eeedd996e83e3", "size": 3035, "url": "https://launcher.mojang.com/v1/objects/517368ab2cbaf6b42ea0b963f98eeedd996e83e3/local_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/trusted.libraries": {"downloads": {"raw": {"sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", "size": 0, "url": "https://launcher.mojang.com/v1/objects/da39a3ee5e6b4b0d3255bfef95601890afd80709/trusted.libraries"}}, "executable": false, "type": "file"}, "lib/sound.properties": {"downloads": {"lzma": {"sha1": "3b5f7e4ec437d79048af35094290577f483b3fe1", "size": 473, "url": "https://launcher.mojang.com/v1/objects/3b5f7e4ec437d79048af35094290577f483b3fe1/sound.properties"}, "raw": {"sha1": "9afceb218059d981d0fa9f07aad3c5097cf41b0c", "size": 1210, "url": "https://launcher.mojang.com/v1/objects/9afceb218059d981d0fa9f07aad3c5097cf41b0c/sound.properties"}}, "executable": false, "type": "file"}, "lib/tzdb.dat": {"downloads": {"lzma": {"sha1": "39c69339965484afe89c14111baeeb862fdefd97", "size": 32547, "url": "https://launcher.mojang.com/v1/objects/39c69339965484afe89c14111baeeb862fdefd97/tzdb.dat"}, "raw": {"sha1": "b59c07e3619271a3b9861e999f4b138e971baf69", "size": 105734, "url": "https://launcher.mojang.com/v1/objects/b59c07e3619271a3b9861e999f4b138e971baf69/tzdb.dat"}}, "executable": false, "type": "file"}, "man": {"type": "directory"}, "man/ja": {"target": "ja_JP.UTF-8", "type": "link"}, "man/ja_JP.UTF-8": {"type": "directory"}, "man/ja_JP.UTF-8/man1": {"type": "directory"}, "man/ja_JP.UTF-8/man1/java.1": {"downloads": {"lzma": {"sha1": "f9da09710b6c6df23c256e324a0c4df00a0d6ded", "size": 25461, "url": "https://launcher.mojang.com/v1/objects/f9da09710b6c6df23c256e324a0c4df00a0d6ded/java.1"}, "raw": {"sha1": "b0b12a0bb66e6171771ca4b1dfca32fb759bcaec", "size": 148688, "url": "https://launcher.mojang.com/v1/objects/b0b12a0bb66e6171771ca4b1dfca32fb759bcaec/java.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/javaws.1": {"downloads": {"lzma": {"sha1": "6188fae453ca09ccb19be5c9f4d2059926b36267", "size": 2154, "url": "https://launcher.mojang.com/v1/objects/6188fae453ca09ccb19be5c9f4d2059926b36267/javaws.1"}, "raw": {"sha1": "8f39d928870268ace07bedfebd18db1e1d07fc37", "size": 6641, "url": "https://launcher.mojang.com/v1/objects/8f39d928870268ace07bedfebd18db1e1d07fc37/javaws.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/jjs.1": {"downloads": {"lzma": {"sha1": "6e42b989d28b185dc1aab50c0389834e649a37d4", "size": 3452, "url": "https://launcher.mojang.com/v1/objects/6e42b989d28b185dc1aab50c0389834e649a37d4/jjs.1"}, "raw": {"sha1": "e023322a2013912315a2bd1034e6f829a27c76e0", "size": 11365, "url": "https://launcher.mojang.com/v1/objects/e023322a2013912315a2bd1034e6f829a27c76e0/jjs.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/keytool.1": {"downloads": {"lzma": {"sha1": "a78134a4bddd53d684a70aa677e51a215db1c9cb", "size": 20698, "url": "https://launcher.mojang.com/v1/objects/a78134a4bddd53d684a70aa677e51a215db1c9cb/keytool.1"}, "raw": {"sha1": "148583c837eaaf6333ccfd8c9e8df08574e14b0c", "size": 111033, "url": "https://launcher.mojang.com/v1/objects/148583c837eaaf6333ccfd8c9e8df08574e14b0c/keytool.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/orbd.1": {"downloads": {"lzma": {"sha1": "326af0dcbff173ef8aee29163dbe146d7389cc3e", "size": 4225, "url": "https://launcher.mojang.com/v1/objects/326af0dcbff173ef8aee29163dbe146d7389cc3e/orbd.1"}, "raw": {"sha1": "95651622d33c08286858ec337edd3ea72acd93dc", "size": 16092, "url": "https://launcher.mojang.com/v1/objects/95651622d33c08286858ec337edd3ea72acd93dc/orbd.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/pack200.1": {"downloads": {"lzma": {"sha1": "e0eedafa748c61a44e5be4355fe9d44b05048e80", "size": 4293, "url": "https://launcher.mojang.com/v1/objects/e0eedafa748c61a44e5be4355fe9d44b05048e80/pack200.1"}, "raw": {"sha1": "aa21a0ab75707f7fc66e83c7a392e69b37ddf80e", "size": 14482, "url": "https://launcher.mojang.com/v1/objects/aa21a0ab75707f7fc66e83c7a392e69b37ddf80e/pack200.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/policytool.1": {"downloads": {"lzma": {"sha1": "3c766ed12dab58166169d35680c392a6be1814a1", "size": 1380, "url": "https://launcher.mojang.com/v1/objects/3c766ed12dab58166169d35680c392a6be1814a1/policytool.1"}, "raw": {"sha1": "80879c74e072a98fad6f32b3283331aaf9bd002f", "size": 4020, "url": "https://launcher.mojang.com/v1/objects/80879c74e072a98fad6f32b3283331aaf9bd002f/policytool.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/rmid.1": {"downloads": {"lzma": {"sha1": "1e20779d990beacc32a48237777d670fcc47ca14", "size": 4836, "url": "https://launcher.mojang.com/v1/objects/1e20779d990beacc32a48237777d670fcc47ca14/rmid.1"}, "raw": {"sha1": "7e40cb8003d098d6e36f45640b26f979ac94b5c5", "size": 19715, "url": "https://launcher.mojang.com/v1/objects/7e40cb8003d098d6e36f45640b26f979ac94b5c5/rmid.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/rmiregistry.1": {"downloads": {"lzma": {"sha1": "aaf4ffe07e954f8696eef1ecb7a5e244628d0ad9", "size": 1627, "url": "https://launcher.mojang.com/v1/objects/aaf4ffe07e954f8696eef1ecb7a5e244628d0ad9/rmiregistry.1"}, "raw": {"sha1": "c53c52f3ae7a011c135894c9fc51b741e729c33d", "size": 4557, "url": "https://launcher.mojang.com/v1/objects/c53c52f3ae7a011c135894c9fc51b741e729c33d/rmiregistry.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/servertool.1": {"downloads": {"lzma": {"sha1": "3b9e624e9d1cf2959b438a35061162e2100ddecd", "size": 2626, "url": "https://launcher.mojang.com/v1/objects/3b9e624e9d1cf2959b438a35061162e2100ddecd/servertool.1"}, "raw": {"sha1": "50ab8bcd9dd9d0b1a3d81348fbce1c8f82e7189e", "size": 9081, "url": "https://launcher.mojang.com/v1/objects/50ab8bcd9dd9d0b1a3d81348fbce1c8f82e7189e/servertool.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/tnameserv.1": {"downloads": {"lzma": {"sha1": "bb3106ff74c60a76de3d20659b9c2128c70f3bf2", "size": 4478, "url": "https://launcher.mojang.com/v1/objects/bb3106ff74c60a76de3d20659b9c2128c70f3bf2/tnameserv.1"}, "raw": {"sha1": "01e714671ecd1167edcb5310b16a9c59c33c3eaa", "size": 17722, "url": "https://launcher.mojang.com/v1/objects/01e714671ecd1167edcb5310b16a9c59c33c3eaa/tnameserv.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/unpack200.1": {"downloads": {"lzma": {"sha1": "c115a881cf800b08df294df55d9f250ae944e33c", "size": 1973, "url": "https://launcher.mojang.com/v1/objects/c115a881cf800b08df294df55d9f250ae944e33c/unpack200.1"}, "raw": {"sha1": "7c882bba0067367a41ad84868d18793b8a7397a3", "size": 5382, "url": "https://launcher.mojang.com/v1/objects/7c882bba0067367a41ad84868d18793b8a7397a3/unpack200.1"}}, "executable": false, "type": "file"}, "man/man1": {"type": "directory"}, "man/man1/java.1": {"downloads": {"lzma": {"sha1": "06a6b0275c202bf698d73ca71f95618d56d81c15", "size": 25796, "url": "https://launcher.mojang.com/v1/objects/06a6b0275c202bf698d73ca71f95618d56d81c15/java.1"}, "raw": {"sha1": "69fec7a341aa91f18dbdcdb95952dede7e1b689a", "size": 124796, "url": "https://launcher.mojang.com/v1/objects/69fec7a341aa91f18dbdcdb95952dede7e1b689a/java.1"}}, "executable": false, "type": "file"}, "man/man1/javaws.1": {"downloads": {"lzma": {"sha1": "4bae251c6dfb5420f56928815cf80d0b6d517a1f", "size": 1759, "url": "https://launcher.mojang.com/v1/objects/4bae251c6dfb5420f56928815cf80d0b6d517a1f/javaws.1"}, "raw": {"sha1": "e61e44e101b1bc119c2d2d4b10320f38b36a8036", "size": 4897, "url": "https://launcher.mojang.com/v1/objects/e61e44e101b1bc119c2d2d4b10320f38b36a8036/javaws.1"}}, "executable": false, "type": "file"}, "man/man1/jjs.1": {"downloads": {"lzma": {"sha1": "29683cf2bd47015c9461b688749ddffd95f6671d", "size": 1881, "url": "https://launcher.mojang.com/v1/objects/29683cf2bd47015c9461b688749ddffd95f6671d/jjs.1"}, "raw": {"sha1": "78d419bd3a7f3e0802d5220e690429194b5d1beb", "size": 4932, "url": "https://launcher.mojang.com/v1/objects/78d419bd3a7f3e0802d5220e690429194b5d1beb/jjs.1"}}, "executable": false, "type": "file"}, "man/man1/keytool.1": {"downloads": {"lzma": {"sha1": "b67e5126d43713ee3675706724b34061578b42db", "size": 19690, "url": "https://launcher.mojang.com/v1/objects/b67e5126d43713ee3675706724b34061578b42db/keytool.1"}, "raw": {"sha1": "4c976f86057ab779763fcfb98f5702ebef47f629", "size": 86925, "url": "https://launcher.mojang.com/v1/objects/4c976f86057ab779763fcfb98f5702ebef47f629/keytool.1"}}, "executable": false, "type": "file"}, "man/man1/orbd.1": {"downloads": {"lzma": {"sha1": "147064d6f7e027002e296bb246ae572d0ce0495b", "size": 3708, "url": "https://launcher.mojang.com/v1/objects/147064d6f7e027002e296bb246ae572d0ce0495b/orbd.1"}, "raw": {"sha1": "64201e1846fcf1dcc45c786ffeab89426d1c7742", "size": 12180, "url": "https://launcher.mojang.com/v1/objects/64201e1846fcf1dcc45c786ffeab89426d1c7742/orbd.1"}}, "executable": false, "type": "file"}, "man/man1/pack200.1": {"downloads": {"lzma": {"sha1": "fe17486bbe9c58cf4182fa056b9cd124e8295607", "size": 3724, "url": "https://launcher.mojang.com/v1/objects/fe17486bbe9c58cf4182fa056b9cd124e8295607/pack200.1"}, "raw": {"sha1": "26826cf52b89924f2d2a60d6cda798891875eae6", "size": 11623, "url": "https://launcher.mojang.com/v1/objects/26826cf52b89924f2d2a60d6cda798891875eae6/pack200.1"}}, "executable": false, "type": "file"}, "man/man1/policytool.1": {"downloads": {"lzma": {"sha1": "bd154e7c39aca71d15b2098c588866f8d95bc743", "size": 1122, "url": "https://launcher.mojang.com/v1/objects/bd154e7c39aca71d15b2098c588866f8d95bc743/policytool.1"}, "raw": {"sha1": "ab296625155d9a2b25ecc2b4feff2f741b3ad136", "size": 3235, "url": "https://launcher.mojang.com/v1/objects/ab296625155d9a2b25ecc2b4feff2f741b3ad136/policytool.1"}}, "executable": false, "type": "file"}, "man/man1/rmid.1": {"downloads": {"lzma": {"sha1": "6a7da234e7f43ebca5c4ba8cd862fda3be62fbaa", "size": 4255, "url": "https://launcher.mojang.com/v1/objects/6a7da234e7f43ebca5c4ba8cd862fda3be62fbaa/rmid.1"}, "raw": {"sha1": "6f10e214d7950a6a8460524e41dc700f112f89e5", "size": 15979, "url": "https://launcher.mojang.com/v1/objects/6f10e214d7950a6a8460524e41dc700f112f89e5/rmid.1"}}, "executable": false, "type": "file"}, "man/man1/rmiregistry.1": {"downloads": {"lzma": {"sha1": "f40dd17e3a734600ad1828b0c42d3a1685c4c520", "size": 1301, "url": "https://launcher.mojang.com/v1/objects/f40dd17e3a734600ad1828b0c42d3a1685c4c520/rmiregistry.1"}, "raw": {"sha1": "d9a3d23fab689df5bb9a792b88f462f939b49f70", "size": 3449, "url": "https://launcher.mojang.com/v1/objects/d9a3d23fab689df5bb9a792b88f462f939b49f70/rmiregistry.1"}}, "executable": false, "type": "file"}, "man/man1/servertool.1": {"downloads": {"lzma": {"sha1": "74f1e10712202cd3ca0ff5833de05b7ee67092e1", "size": 2307, "url": "https://launcher.mojang.com/v1/objects/74f1e10712202cd3ca0ff5833de05b7ee67092e1/servertool.1"}, "raw": {"sha1": "e6c7b510740ac8681a9bfb5f4ee1f0306125b728", "size": 7237, "url": "https://launcher.mojang.com/v1/objects/e6c7b510740ac8681a9bfb5f4ee1f0306125b728/servertool.1"}}, "executable": false, "type": "file"}, "man/man1/tnameserv.1": {"downloads": {"lzma": {"sha1": "4bec7f4e070d023f124f9352a8971d7acd249a15", "size": 3955, "url": "https://launcher.mojang.com/v1/objects/4bec7f4e070d023f124f9352a8971d7acd249a15/tnameserv.1"}, "raw": {"sha1": "a31dbbe800d49cb371fab9a4b73d22c3bf8799ad", "size": 15747, "url": "https://launcher.mojang.com/v1/objects/a31dbbe800d49cb371fab9a4b73d22c3bf8799ad/tnameserv.1"}}, "executable": false, "type": "file"}, "man/man1/unpack200.1": {"downloads": {"lzma": {"sha1": "f8e73863187929debf2ea6dadefb2995ec7917e7", "size": 1672, "url": "https://launcher.mojang.com/v1/objects/f8e73863187929debf2ea6dadefb2995ec7917e7/unpack200.1"}, "raw": {"sha1": "437f7233d738cb9b822e99003127049005663e0f", "size": 4244, "url": "https://launcher.mojang.com/v1/objects/437f7233d738cb9b822e99003127049005663e0f/unpack200.1"}}, "executable": false, "type": "file"}, "plugin": {"type": "directory"}, "plugin/desktop": {"type": "directory"}, "plugin/desktop/sun_java.desktop": {"downloads": {"lzma": {"sha1": "49ab0ccb54c3be68281d05055bc56a88b1281d3c", "size": 447, "url": "https://launcher.mojang.com/v1/objects/49ab0ccb54c3be68281d05055bc56a88b1281d3c/sun_java.desktop"}, "raw": {"sha1": "79120ee8160ad6f3c9b90c2641fb7edf3af96b5d", "size": 624, "url": "https://launcher.mojang.com/v1/objects/79120ee8160ad6f3c9b90c2641fb7edf3af96b5d/sun_java.desktop"}}, "executable": false, "type": "file"}, "plugin/desktop/sun_java.png": {"downloads": {"raw": {"sha1": "699c41e97a35414e72a80327a54d6e14e874e951", "size": 4351, "url": "https://launcher.mojang.com/v1/objects/699c41e97a35414e72a80327a54d6e14e874e951/sun_java.png"}}, "executable": false, "type": "file"}, "release": {"downloads": {"raw": {"sha1": "cb462682644c0275d94a45b759108815f3112064", "size": 424, "url": "https://launcher.mojang.com/v1/objects/cb462682644c0275d94a45b759108815f3112064/release"}}, "executable": false, "type": "file"}}}
\ No newline at end of file diff --git a/api/logic/mojang/testdata/inspect/a/b.txt b/api/logic/mojang/testdata/inspect/a/b.txt deleted file mode 100755 index e69de29b..00000000 --- a/api/logic/mojang/testdata/inspect/a/b.txt +++ /dev/null diff --git a/api/logic/mojang/testdata/inspect/a/b/b.txt b/api/logic/mojang/testdata/inspect/a/b/b.txt deleted file mode 120000 index 4e19a044..00000000 --- a/api/logic/mojang/testdata/inspect/a/b/b.txt +++ /dev/null @@ -1 +0,0 @@ -../b.txt
\ No newline at end of file diff --git a/api/logic/mojang/testdata/inspect_win/a/b.txt b/api/logic/mojang/testdata/inspect_win/a/b.txt deleted file mode 100644 index e69de29b..00000000 --- a/api/logic/mojang/testdata/inspect_win/a/b.txt +++ /dev/null diff --git a/api/logic/mojang/testdata/inspect_win/a/b/b.txt b/api/logic/mojang/testdata/inspect_win/a/b/b.txt deleted file mode 100644 index e69de29b..00000000 --- a/api/logic/mojang/testdata/inspect_win/a/b/b.txt +++ /dev/null diff --git a/api/logic/net/ByteArraySink.h b/api/logic/net/ByteArraySink.h deleted file mode 100644 index 20e6764c..00000000 --- a/api/logic/net/ByteArraySink.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "Sink.h" - -namespace Net { -/* - * Sink object for downloads that uses an external QByteArray it doesn't own as a target. - */ -class ByteArraySink : public Sink -{ -public: - ByteArraySink(QByteArray *output) - :m_output(output) - { - // nil - }; - - virtual ~ByteArraySink() - { - // nil - } - -public: - JobStatus init(QNetworkRequest & request) override - { - m_output->clear(); - if(initAllValidators(request)) - return Job_InProgress; - return Job_Failed; - }; - - JobStatus write(QByteArray & data) override - { - m_output->append(data); - if(writeAllValidators(data)) - return Job_InProgress; - return Job_Failed; - } - - JobStatus abort() override - { - m_output->clear(); - failAllValidators(); - return Job_Failed; - } - - JobStatus finalize(QNetworkReply &reply) override - { - if(finalizeAllValidators(reply)) - return Job_Finished; - return Job_Failed; - } - - bool hasLocalData() override - { - return false; - } - -private: - QByteArray * m_output; -}; -} diff --git a/api/logic/net/ChecksumValidator.h b/api/logic/net/ChecksumValidator.h deleted file mode 100644 index 0d6b19c2..00000000 --- a/api/logic/net/ChecksumValidator.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include "Validator.h" -#include <QCryptographicHash> -#include <memory> -#include <QFile> - -namespace Net { -class ChecksumValidator: public Validator -{ -public: /* con/des */ - ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray()) - :m_checksum(algorithm), m_expected(expected) - { - }; - virtual ~ChecksumValidator() {}; - -public: /* methods */ - bool init(QNetworkRequest &) override - { - m_checksum.reset(); - return true; - } - bool write(QByteArray & data) override - { - m_checksum.addData(data); - return true; - } - bool abort() override - { - return true; - } - bool validate(QNetworkReply &) override - { - if(m_expected.size() && m_expected != hash()) - { - qWarning() << "Checksum mismatch, download is bad."; - return false; - } - return true; - } - QByteArray hash() - { - return m_checksum.result(); - } - void setExpected(QByteArray expected) - { - m_expected = expected; - } - -private: /* data */ - QCryptographicHash m_checksum; - QByteArray m_expected; -}; -}
\ No newline at end of file diff --git a/api/logic/net/Download.cpp b/api/logic/net/Download.cpp deleted file mode 100644 index 3f183b7d..00000000 --- a/api/logic/net/Download.cpp +++ /dev/null @@ -1,309 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "Download.h" - -#include "BuildConfig.h" -#include <QFileInfo> -#include <QDateTime> -#include <QDebug> -#include "Env.h" -#include <FileSystem.h> -#include "ChecksumValidator.h" -#include "MetaCacheSink.h" -#include "ByteArraySink.h" - -namespace Net { - -Download::Download():NetAction() -{ - m_status = Job_NotStarted; -} - -Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -{ - Download * dl = new Download(); - dl->m_url = url; - dl->m_options = options; - auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); - auto cachedNode = new MetaCacheSink(entry, md5Node); - dl->m_sink.reset(cachedNode); - dl->m_target_path = entry->getFullPath(); - return std::shared_ptr<Download>(dl); -} - -Download::Ptr Download::makeByteArray(QUrl url, QByteArray *output, Options options) -{ - Download * dl = new Download(); - dl->m_url = url; - dl->m_options = options; - dl->m_sink.reset(new ByteArraySink(output)); - return std::shared_ptr<Download>(dl); -} - -Download::Ptr Download::makeFile(QUrl url, QString path, Options options) -{ - Download * dl = new Download(); - dl->m_url = url; - dl->m_options = options; - dl->m_sink.reset(new FileSink(path)); - return std::shared_ptr<Download>(dl); -} - -void Download::addValidator(Validator * v) -{ - m_sink->addValidator(v); -} - -void Download::start() -{ - if(m_status == Job_Aborted) - { - qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); - emit aborted(m_index_within_job); - return; - } - QNetworkRequest request(m_url); - m_status = m_sink->init(request); - switch(m_status) - { - case Job_Finished: - emit succeeded(m_index_within_job); - qDebug() << "Download cache hit " << m_url.toString(); - return; - case Job_InProgress: - qDebug() << "Downloading " << m_url.toString(); - break; - case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. - case Job_NotStarted: - case Job_Failed: - emit failed(m_index_within_job); - return; - case Job_Aborted: - return; - } - - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); - - QNetworkReply *rep = ENV.qnam().get(request); - - m_reply.reset(rep); - connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); - connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); - connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); -} - -void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); -} - -void Download::downloadError(QNetworkReply::NetworkError error) -{ - if(error == QNetworkReply::OperationCanceledError) - { - qCritical() << "Aborted " << m_url.toString(); - m_status = Job_Aborted; - } - else - { - if(m_options & Option::AcceptLocalFiles) - { - if(m_sink->hasLocalData()) - { - m_status = Job_Failed_Proceed; - return; - } - } - // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; - m_status = Job_Failed; - } -} - -void Download::sslErrors(const QList<QSslError> & errors) -{ - int i = 1; - for (auto error : errors) - { - qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -bool Download::handleRedirect() -{ - QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); - if(!redirect.isValid()) - { - if(!m_reply->hasRawHeader("Location")) - { - // no redirect -> it's fine to continue - return false; - } - // there is a Location header, but it's not correct. we need to apply some workarounds... - QByteArray redirectBA = m_reply->rawHeader("Location"); - if(redirectBA.size() == 0) - { - // empty, yet present redirect header? WTF? - return false; - } - QString redirectStr = QString::fromUtf8(redirectBA); - - if(redirectStr.startsWith("//")) - { - /* - * IF the URL begins with //, we need to insert the URL scheme. - * See: https://bugreports.qt.io/browse/QTBUG-41061 - * See: http://tools.ietf.org/html/rfc3986#section-4.2 - */ - redirectStr = m_reply->url().scheme() + ":" + redirectStr; - } - else if(redirectStr.startsWith("/")) - { - /* - * IF the URL begins with /, we need to process it as a relative URL - */ - auto url = m_reply->url(); - url.setPath(redirectStr, QUrl::TolerantMode); - redirectStr = url.toString(); - } - - /* - * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. - * FIXME: report Qt bug for this - */ - redirect = QUrl(redirectStr, QUrl::TolerantMode); - if(!redirect.isValid()) - { - qWarning() << "Failed to parse redirect URL:" << redirectStr; - downloadError(QNetworkReply::ProtocolFailure); - return false; - } - qDebug() << "Fixed location header:" << redirect; - } - else - { - qDebug() << "Location header:" << redirect; - } - - m_url = QUrl(redirect.toString()); - qDebug() << "Following redirect to " << m_url.toString(); - start(); - return true; -} - - -void Download::downloadFinished() -{ - // handle HTTP redirection first - if(handleRedirect()) - { - qDebug() << "Download redirected:" << m_url.toString(); - return; - } - - // if the download failed before this point ... - if (m_status == Job_Failed_Proceed) - { - qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit succeeded(m_index_within_job); - return; - } - else if (m_status == Job_Failed) - { - qDebug() << "Download failed in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - else if(m_status == Job_Aborted) - { - qDebug() << "Download aborted in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit aborted(m_index_within_job); - return; - } - - // make sure we got all the remaining data, if any - auto data = m_reply->readAll(); - if(data.size()) - { - qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; - m_status = m_sink->write(data); - } - - // otherwise, finalize the whole graph - m_status = m_sink->finalize(*m_reply.get()); - if (m_status != Job_Finished) - { - qDebug() << "Download failed to finalize:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - m_reply.reset(); - qDebug() << "Download succeeded:" << m_url.toString(); - emit succeeded(m_index_within_job); -} - -void Download::downloadReadyRead() -{ - if(m_status == Job_InProgress) - { - auto data = m_reply->readAll(); - m_status = m_sink->write(data); - if(m_status == Job_Failed) - { - qCritical() << "Failed to process response chunk for " << m_target_path; - } - // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; - } - else - { - qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status; - } -} - -} - -bool Net::Download::abort() -{ - if(m_reply) - { - m_reply->abort(); - } - else - { - m_status = Job_Aborted; - } - return true; -} - -bool Net::Download::canAbort() -{ - return true; -} diff --git a/api/logic/net/Download.h b/api/logic/net/Download.h deleted file mode 100644 index 2c436032..00000000 --- a/api/logic/net/Download.h +++ /dev/null @@ -1,76 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "NetAction.h" -#include "HttpMetaCache.h" -#include "Validator.h" -#include "Sink.h" - -#include "multimc_logic_export.h" -namespace Net { -class MULTIMC_LOGIC_EXPORT Download : public NetAction -{ - Q_OBJECT - -public: /* types */ - typedef std::shared_ptr<class Download> Ptr; - enum class Option - { - NoOptions = 0, - AcceptLocalFiles = 1 - }; - Q_DECLARE_FLAGS(Options, Option) - -protected: /* con/des */ - explicit Download(); -public: - virtual ~Download(){}; - static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions); - static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions); - static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions); - -public: /* methods */ - QString getTargetFilepath() - { - return m_target_path; - } - void addValidator(Validator * v); - bool abort() override; - bool canAbort() override; - -private: /* methods */ - bool handleRedirect(); - -protected slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; - void downloadError(QNetworkReply::NetworkError error) override; - void sslErrors(const QList<QSslError> & errors); - void downloadFinished() override; - void downloadReadyRead() override; - -public slots: - void start() override; - -private: /* data */ - // FIXME: remove this, it has no business being here. - QString m_target_path; - std::unique_ptr<Sink> m_sink; - Options m_options; -}; -} - -Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options) diff --git a/api/logic/net/FileSink.cpp b/api/logic/net/FileSink.cpp deleted file mode 100644 index 8b3e917d..00000000 --- a/api/logic/net/FileSink.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "FileSink.h" -#include <QFile> -#include <QFileInfo> -#include "Env.h" -#include "FileSystem.h" - -namespace Net { - -FileSink::FileSink(QString filename) - :m_filename(filename) -{ - // nil -} - -FileSink::~FileSink() -{ - // nil -} - -JobStatus FileSink::init(QNetworkRequest& request) -{ - auto result = initCache(request); - if(result != Job_InProgress) - { - return result; - } - // create a new save file and open it for writing - if (!FS::ensureFilePathExists(m_filename)) - { - qCritical() << "Could not create folder for " + m_filename; - return Job_Failed; - } - wroteAnyData = false; - m_output_file.reset(new QSaveFile(m_filename)); - if (!m_output_file->open(QIODevice::WriteOnly)) - { - qCritical() << "Could not open " + m_filename + " for writing"; - return Job_Failed; - } - - if(initAllValidators(request)) - return Job_InProgress; - return Job_Failed; -} - -JobStatus FileSink::initCache(QNetworkRequest &) -{ - return Job_InProgress; -} - -JobStatus FileSink::write(QByteArray& data) -{ - if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) - { - qCritical() << "Failed writing into " + m_filename; - m_output_file->cancelWriting(); - m_output_file.reset(); - wroteAnyData = false; - return Job_Failed; - } - wroteAnyData = true; - return Job_InProgress; -} - -JobStatus FileSink::abort() -{ - m_output_file->cancelWriting(); - failAllValidators(); - return Job_Failed; -} - -JobStatus FileSink::finalize(QNetworkReply& reply) -{ - bool gotFile = false; - QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute); - bool validStatus = false; - int statusCode = statusCodeV.toInt(&validStatus); - if(validStatus) - { - // this leaves out 304 Not Modified - gotFile = statusCode == 200 || statusCode == 203; - } - // if we wrote any data to the save file, we try to commit the data to the real file. - // if it actually got a proper file, we write it even if it was empty - if (gotFile || wroteAnyData) - { - // ask validators for data consistency - // we only do this for actual downloads, not 'your data is still the same' cache hits - if(!finalizeAllValidators(reply)) - return Job_Failed; - // nothing went wrong... - if (!m_output_file->commit()) - { - qCritical() << "Failed to commit changes to " << m_filename; - m_output_file->cancelWriting(); - return Job_Failed; - } - } - // then get rid of the save file - m_output_file.reset(); - - return finalizeCache(reply); -} - -JobStatus FileSink::finalizeCache(QNetworkReply &) -{ - return Job_Finished; -} - -bool FileSink::hasLocalData() -{ - QFileInfo info(m_filename); - return info.exists() && info.size() != 0; -} -} diff --git a/api/logic/net/FileSink.h b/api/logic/net/FileSink.h deleted file mode 100644 index 875fe511..00000000 --- a/api/logic/net/FileSink.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "Sink.h" -#include <QSaveFile> - -namespace Net { -class FileSink : public Sink -{ -public: /* con/des */ - FileSink(QString filename); - virtual ~FileSink(); - -public: /* methods */ - JobStatus init(QNetworkRequest & request) override; - JobStatus write(QByteArray & data) override; - JobStatus abort() override; - JobStatus finalize(QNetworkReply & reply) override; - bool hasLocalData() override; - -protected: /* methods */ - virtual JobStatus initCache(QNetworkRequest &); - virtual JobStatus finalizeCache(QNetworkReply &reply); - -protected: /* data */ - QString m_filename; - bool wroteAnyData = false; - std::unique_ptr<QSaveFile> m_output_file; -}; -} diff --git a/api/logic/net/HttpMetaCache.cpp b/api/logic/net/HttpMetaCache.cpp deleted file mode 100644 index 4bc8fbc8..00000000 --- a/api/logic/net/HttpMetaCache.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "Env.h" -#include "HttpMetaCache.h" -#include "FileSystem.h" - -#include <QFileInfo> -#include <QFile> -#include <QDateTime> -#include <QCryptographicHash> - -#include <QDebug> - -#include <QJsonDocument> -#include <QJsonArray> -#include <QJsonObject> - -QString MetaEntry::getFullPath() -{ - // FIXME: make local? - return FS::PathCombine(basePath, relativePath); -} - -HttpMetaCache::HttpMetaCache(QString path) : QObject() -{ - m_index_file = path; - saveBatchingTimer.setSingleShot(true); - saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); - connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow())); -} - -HttpMetaCache::~HttpMetaCache() -{ - saveBatchingTimer.stop(); - SaveNow(); -} - -MetaEntryPtr HttpMetaCache::getEntry(QString base, QString resource_path) -{ - // no base. no base path. can't store - if (!m_entries.contains(base)) - { - // TODO: log problem - return MetaEntryPtr(); - } - EntryMap &map = m_entries[base]; - if (map.entry_list.contains(resource_path)) - { - return map.entry_list[resource_path]; - } - return MetaEntryPtr(); -} - -MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -{ - auto entry = getEntry(base, resource_path); - // it's not present? generate a default stale entry - if (!entry) - { - return staleEntry(base, resource_path); - } - - auto &selected_base = m_entries[base]; - QString real_path = FS::PathCombine(selected_base.base_path, resource_path); - QFileInfo finfo(real_path); - - // is the file really there? if not -> stale - if (!finfo.isFile() || !finfo.isReadable()) - { - // if the file doesn't exist, we disown the entry - selected_base.entry_list.remove(resource_path); - return staleEntry(base, resource_path); - } - - if (!expected_etag.isEmpty() && expected_etag != entry->etag) - { - // if the etag doesn't match expected, we disown the entry - selected_base.entry_list.remove(resource_path); - return staleEntry(base, resource_path); - } - - // if the file changed, check md5sum - qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); - if (file_last_changed != entry->local_changed_timestamp) - { - QFile input(real_path); - input.open(QIODevice::ReadOnly); - QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5) - .toHex() - .constData(); - if (entry->md5sum != md5sum) - { - selected_base.entry_list.remove(resource_path); - return staleEntry(base, resource_path); - } - // md5sums matched... keep entry and save the new state to file - entry->local_changed_timestamp = file_last_changed; - SaveEventually(); - } - - // entry passed all the checks we cared about. - entry->basePath = getBasePath(base); - return entry; -} - -bool HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -{ - if (!m_entries.contains(stale_entry->baseId)) - { - qCritical() << "Cannot add entry with unknown base: " - << stale_entry->baseId.toLocal8Bit(); - return false; - } - if (stale_entry->stale) - { - qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); - return false; - } - m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry; - SaveEventually(); - return true; -} - -bool HttpMetaCache::evictEntry(MetaEntryPtr entry) -{ - if(entry) - { - entry->stale = true; - SaveEventually(); - return true; - } - return false; -} - -MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path) -{ - auto foo = new MetaEntry(); - foo->baseId = base; - foo->basePath = getBasePath(base); - foo->relativePath = resource_path; - foo->stale = true; - return MetaEntryPtr(foo); -} - -void HttpMetaCache::addBase(QString base, QString base_root) -{ - // TODO: report error - if (m_entries.contains(base)) - return; - // TODO: check if the base path is valid - EntryMap foo; - foo.base_path = base_root; - m_entries[base] = foo; -} - -QString HttpMetaCache::getBasePath(QString base) -{ - if (m_entries.contains(base)) - { - return m_entries[base].base_path; - } - return QString(); -} - -void HttpMetaCache::Load() -{ - if(m_index_file.isNull()) - return; - - QFile index(m_index_file); - if (!index.open(QIODevice::ReadOnly)) - return; - - QJsonDocument json = QJsonDocument::fromJson(index.readAll()); - if (!json.isObject()) - return; - auto root = json.object(); - // check file version first - auto version_val = root.value("version"); - if (!version_val.isString()) - return; - if (version_val.toString() != "1") - return; - - // read the entry array - auto entries_val = root.value("entries"); - if (!entries_val.isArray()) - return; - QJsonArray array = entries_val.toArray(); - for (auto element : array) - { - if (!element.isObject()) - return; - auto element_obj = element.toObject(); - QString base = element_obj.value("base").toString(); - if (!m_entries.contains(base)) - continue; - auto &entrymap = m_entries[base]; - auto foo = new MetaEntry(); - foo->baseId = base; - QString path = foo->relativePath = element_obj.value("path").toString(); - foo->md5sum = element_obj.value("md5sum").toString(); - foo->etag = element_obj.value("etag").toString(); - foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); - foo->remote_changed_timestamp = - element_obj.value("remote_changed_timestamp").toString(); - // presumed innocent until closer examination - foo->stale = false; - entrymap.entry_list[path] = MetaEntryPtr(foo); - } -} - -void HttpMetaCache::SaveEventually() -{ - // reset the save timer - saveBatchingTimer.stop(); - saveBatchingTimer.start(30000); -} - -void HttpMetaCache::SaveNow() -{ - if(m_index_file.isNull()) - return; - QJsonObject toplevel; - toplevel.insert("version", QJsonValue(QString("1"))); - QJsonArray entriesArr; - for (auto group : m_entries) - { - for (auto entry : group.entry_list) - { - // do not save stale entries. they are dead. - if(entry->stale) - { - continue; - } - QJsonObject entryObj; - entryObj.insert("base", QJsonValue(entry->baseId)); - entryObj.insert("path", QJsonValue(entry->relativePath)); - entryObj.insert("md5sum", QJsonValue(entry->md5sum)); - entryObj.insert("etag", QJsonValue(entry->etag)); - 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)); - entriesArr.append(entryObj); - } - } - toplevel.insert("entries", entriesArr); - - QJsonDocument doc(toplevel); - try - { - FS::write(m_index_file, doc.toJson()); - } - catch (const Exception &e) - { - qWarning() << e.what(); - } -} diff --git a/api/logic/net/HttpMetaCache.h b/api/logic/net/HttpMetaCache.h deleted file mode 100644 index c3248793..00000000 --- a/api/logic/net/HttpMetaCache.h +++ /dev/null @@ -1,125 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QMap> -#include <qtimer.h> -#include <memory> - -#include "multimc_logic_export.h" - -class HttpMetaCache; - -class MULTIMC_LOGIC_EXPORT MetaEntry -{ -friend class HttpMetaCache; -protected: - MetaEntry() {} -public: - bool isStale() - { - return stale; - } - void setStale(bool stale) - { - this->stale = stale; - } - QString getFullPath(); - QString getRemoteChangedTimestamp() - { - return remote_changed_timestamp; - } - void setRemoteChangedTimestamp(QString remote_changed_timestamp) - { - this->remote_changed_timestamp = remote_changed_timestamp; - } - void setLocalChangedTimestamp(qint64 timestamp) - { - local_changed_timestamp = timestamp; - } - QString getETag() - { - return etag; - } - void setETag(QString etag) - { - this->etag = etag; - } - QString getMD5Sum() - { - return md5sum; - } - void setMD5Sum(QString md5sum) - { - this->md5sum = md5sum; - } -protected: - QString baseId; - QString basePath; - QString relativePath; - QString md5sum; - QString etag; - qint64 local_changed_timestamp = 0; - QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time - bool stale = true; -}; - -typedef std::shared_ptr<MetaEntry> MetaEntryPtr; - -class MULTIMC_LOGIC_EXPORT HttpMetaCache : public QObject -{ - Q_OBJECT -public: - // supply path to the cache index file - HttpMetaCache(QString path = QString()); - ~HttpMetaCache(); - - // get the entry solely from the cache - // you probably don't want this, unless you have some specific caching needs. - MetaEntryPtr getEntry(QString base, QString resource_path); - - // get the entry from cache and verify that it isn't stale (within reason) - MetaEntryPtr resolveEntry(QString base, QString resource_path, - QString expected_etag = QString()); - - // add a previously resolved stale entry - bool updateEntry(MetaEntryPtr stale_entry); - - // evict selected entry from cache - bool evictEntry(MetaEntryPtr entry); - - void addBase(QString base, QString base_root); - - // (re)start a timer that calls SaveNow later. - void SaveEventually(); - void Load(); - QString getBasePath(QString base); -public -slots: - void SaveNow(); - -private: - // create a new stale entry, given the parameters - MetaEntryPtr staleEntry(QString base, QString resource_path); - struct EntryMap - { - QString base_path; - QMap<QString, MetaEntryPtr> entry_list; - }; - QMap<QString, EntryMap> m_entries; - QString m_index_file; - QTimer saveBatchingTimer; -}; diff --git a/api/logic/net/MetaCacheSink.cpp b/api/logic/net/MetaCacheSink.cpp deleted file mode 100644 index d7f18533..00000000 --- a/api/logic/net/MetaCacheSink.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "MetaCacheSink.h" -#include <QFile> -#include <QFileInfo> -#include "Env.h" -#include "FileSystem.h" - -namespace Net { - -MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum) - :Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum) -{ - addValidator(md5sum); -} - -MetaCacheSink::~MetaCacheSink() -{ - // nil -} - -JobStatus MetaCacheSink::initCache(QNetworkRequest& request) -{ - if (!m_entry->isStale()) - { - return Job_Finished; - } - // check if file exists, if it does, use its information for the request - QFile current(m_filename); - if(current.exists() && current.size() != 0) - { - if (m_entry->getRemoteChangedTimestamp().size()) - { - request.setRawHeader(QString("If-Modified-Since").toLatin1(), m_entry->getRemoteChangedTimestamp().toLatin1()); - } - if (m_entry->getETag().size()) - { - request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); - } - } - return Job_InProgress; -} - -JobStatus MetaCacheSink::finalizeCache(QNetworkReply & reply) -{ - QFileInfo output_file_info(m_filename); - if(wroteAnyData) - { - m_entry->setMD5Sum(m_md5Node->hash().toHex().constData()); - } - m_entry->setETag(reply.rawHeader("ETag").constData()); - if (reply.hasRawHeader("Last-Modified")) - { - m_entry->setRemoteChangedTimestamp(reply.rawHeader("Last-Modified").constData()); - } - m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); - m_entry->setStale(false); - ENV.metacache()->updateEntry(m_entry); - return Job_Finished; -} - -bool MetaCacheSink::hasLocalData() -{ - QFileInfo info(m_filename); - return info.exists() && info.size() != 0; -} -} diff --git a/api/logic/net/MetaCacheSink.h b/api/logic/net/MetaCacheSink.h deleted file mode 100644 index edcf7ad1..00000000 --- a/api/logic/net/MetaCacheSink.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "FileSink.h" -#include "ChecksumValidator.h" -#include "net/HttpMetaCache.h" - -namespace Net { -class MetaCacheSink : public FileSink -{ -public: /* con/des */ - MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum); - virtual ~MetaCacheSink(); - bool hasLocalData() override; - -protected: /* methods */ - JobStatus initCache(QNetworkRequest & request) override; - JobStatus finalizeCache(QNetworkReply & reply) override; - -private: /* data */ - MetaEntryPtr m_entry; - ChecksumValidator * m_md5Node; -}; -} diff --git a/api/logic/net/Mode.h b/api/logic/net/Mode.h deleted file mode 100644 index 9a95f5ad..00000000 --- a/api/logic/net/Mode.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -namespace Net -{ -enum class Mode -{ - Offline, - Online -}; -} diff --git a/api/logic/net/NetAction.h b/api/logic/net/NetAction.h deleted file mode 100644 index 02b249a3..00000000 --- a/api/logic/net/NetAction.h +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QUrl> -#include <memory> -#include <QNetworkReply> -#include <QObjectPtr.h> - -#include "multimc_logic_export.h" - -enum JobStatus -{ - Job_NotStarted, - Job_InProgress, - Job_Finished, - Job_Failed, - Job_Aborted, - /* - * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion. - * Same could be true for aborted task - the presence of pre-existing result is a separate concern - */ - Job_Failed_Proceed -}; - -typedef std::shared_ptr<class NetAction> NetActionPtr; -class MULTIMC_LOGIC_EXPORT NetAction : public QObject -{ - Q_OBJECT -protected: - explicit NetAction() : QObject(0) {}; - -public: - virtual ~NetAction() {}; - - bool isRunning() const - { - return m_status == Job_InProgress; - } - bool isFinished() const - { - return m_status >= Job_Finished; - } - bool wasSuccessful() const - { - return m_status == Job_Finished || m_status == Job_Failed_Proceed; - } - - qint64 totalProgress() const - { - return m_total_progress; - } - qint64 currentProgress() const - { - return m_progress; - } - virtual bool abort() - { - return false; - } - virtual bool canAbort() - { - return false; - } - QUrl url() - { - return m_url; - } - -signals: - void started(int index); - void netActionProgress(int index, qint64 current, qint64 total); - void succeeded(int index); - void failed(int index); - void aborted(int index); - -protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; - virtual void downloadError(QNetworkReply::NetworkError error) = 0; - virtual void downloadFinished() = 0; - virtual void downloadReadyRead() = 0; - -public slots: - virtual void start() = 0; - -public: - /// index within the parent job, FIXME: nuke - int m_index_within_job = 0; - - /// the network reply - unique_qobject_ptr<QNetworkReply> m_reply; - - /// source URL - QUrl m_url; - - qint64 m_progress = 0; - qint64 m_total_progress = 1; - -protected: - JobStatus m_status = Job_NotStarted; -}; diff --git a/api/logic/net/NetJob.cpp b/api/logic/net/NetJob.cpp deleted file mode 100644 index 029d9e34..00000000 --- a/api/logic/net/NetJob.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "NetJob.h" -#include "Download.h" - -#include <QDebug> - -void NetJob::partSucceeded(int index) -{ - // do progress. all slots are 1 in size at least - auto &slot = parts_progress[index]; - partProgress(index, slot.total_progress, slot.total_progress); - - m_doing.remove(index); - m_done.insert(index); - downloads[index].get()->disconnect(this); - startMoreParts(); -} - -void NetJob::partFailed(int index) -{ - m_doing.remove(index); - auto &slot = parts_progress[index]; - if (slot.failures == 3) - { - m_failed.insert(index); - } - else - { - slot.failures++; - m_todo.enqueue(index); - } - downloads[index].get()->disconnect(this); - startMoreParts(); -} - -void NetJob::partAborted(int index) -{ - m_aborted = true; - m_doing.remove(index); - m_failed.insert(index); - downloads[index].get()->disconnect(this); - startMoreParts(); -} - -void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) -{ - auto &slot = parts_progress[index]; - slot.current_progress = bytesReceived; - slot.total_progress = bytesTotal; - - int done = m_done.size(); - int doing = m_doing.size(); - int all = parts_progress.size(); - - qint64 bytesAll = 0; - qint64 bytesTotalAll = 0; - for(auto & partIdx: m_doing) - { - auto part = parts_progress[partIdx]; - // do not count parts with unknown/nonsensical total size - if(part.total_progress <= 0) - { - continue; - } - bytesAll += part.current_progress; - bytesTotalAll += part.total_progress; - } - - qint64 inprogress = (bytesTotalAll == 0) ? 0 : (bytesAll * 1000) / bytesTotalAll; - auto current = done * 1000 + doing * inprogress; - auto current_total = all * 1000; - // HACK: make sure it never jumps backwards. - // FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress - if(m_current_progress == 1000) { - m_current_progress = inprogress; - } - if(m_current_progress > current) - { - current = m_current_progress; - } - m_current_progress = current; - setProgress(current, current_total); -} - -void NetJob::executeTask() -{ - // hack that delays early failures so they can be caught easier - QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); -} - -void NetJob::startMoreParts() -{ - if(!isRunning()) - { - // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later. - return; - } - // OK. We are actively processing tasks, proceed. - // Check for final conditions if there's nothing in the queue. - if(!m_todo.size()) - { - if(!m_doing.size()) - { - if(!m_failed.size()) - { - emitSucceeded(); - } - else if(m_aborted) - { - emitAborted(); - } - else - { - emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n"))); - } - } - return; - } - // There's work to do, try to start more parts. - while (m_doing.size() < 6) - { - if(!m_todo.size()) - return; - int doThis = m_todo.dequeue(); - m_doing.insert(doThis); - auto part = downloads[doThis]; - // connect signals :D - connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); - connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); - connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int))); - connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)), - SLOT(partProgress(int, qint64, qint64))); - part->start(); - } -} - - -QStringList NetJob::getFailedFiles() -{ - QStringList failed; - for (auto index: m_failed) - { - failed.push_back(downloads[index]->url().toString()); - } - failed.sort(); - return failed; -} - -bool NetJob::canAbort() const -{ - bool canFullyAbort = true; - // can abort the waiting? - for(auto index: m_todo) - { - auto part = downloads[index]; - canFullyAbort &= part->canAbort(); - } - // can abort the active? - for(auto index: m_doing) - { - auto part = downloads[index]; - canFullyAbort &= part->canAbort(); - } - return canFullyAbort; -} - -bool NetJob::abort() -{ - bool fullyAborted = true; - // fail all waiting - m_failed.unite(m_todo.toSet()); - m_todo.clear(); - // abort active - auto toKill = m_doing.toList(); - for(auto index: toKill) - { - auto part = downloads[index]; - fullyAborted &= part->abort(); - } - return fullyAborted; -} - -bool NetJob::addNetAction(NetActionPtr action) -{ - action->m_index_within_job = downloads.size(); - downloads.append(action); - part_info pi; - parts_progress.append(pi); - partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress()); - - if(action->isRunning()) - { - connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); - connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); - connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64))); - } - else - { - m_todo.append(parts_progress.size() - 1); - } - return true; -} - -NetJob::~NetJob() = default; diff --git a/api/logic/net/NetJob.h b/api/logic/net/NetJob.h deleted file mode 100644 index 480d8037..00000000 --- a/api/logic/net/NetJob.h +++ /dev/null @@ -1,91 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QtNetwork> -#include "NetAction.h" -#include "Download.h" -#include "HttpMetaCache.h" -#include "tasks/Task.h" -#include "QObjectPtr.h" - -#include "multimc_logic_export.h" - -class NetJob; -typedef shared_qobject_ptr<NetJob> NetJobPtr; - -class MULTIMC_LOGIC_EXPORT NetJob : public Task -{ - Q_OBJECT -public: - explicit NetJob(QString job_name) : Task() - { - setObjectName(job_name); - } - virtual ~NetJob(); - - bool addNetAction(NetActionPtr action); - - NetActionPtr operator[](int index) - { - return downloads[index]; - } - const NetActionPtr at(const int index) - { - return downloads.at(index); - } - NetActionPtr first() - { - if (downloads.size()) - return downloads[0]; - return NetActionPtr(); - } - int size() const - { - return downloads.size(); - } - QStringList getFailedFiles(); - - bool canAbort() const override; - -private slots: - void startMoreParts(); - -public slots: - virtual void executeTask() override; - virtual bool abort() override; - -private slots: - void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); - void partSucceeded(int index); - void partFailed(int index); - void partAborted(int index); - -private: - struct part_info - { - qint64 current_progress = 0; - qint64 total_progress = 1; - int failures = 0; - }; - QList<NetActionPtr> downloads; - QList<part_info> parts_progress; - QQueue<int> m_todo; - QSet<int> m_doing; - QSet<int> m_done; - QSet<int> m_failed; - qint64 m_current_progress = 0; - bool m_aborted = false; -}; diff --git a/api/logic/net/PasteUpload.cpp b/api/logic/net/PasteUpload.cpp deleted file mode 100644 index cb470c49..00000000 --- a/api/logic/net/PasteUpload.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "PasteUpload.h" -#include "Env.h" -#include <QDebug> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonDocument> -#include <QFile> -#include <BuildConfig.h> - -PasteUpload::PasteUpload(QWidget *window, QString text, QString key) : m_window(window) -{ - m_key = key; - QByteArray temp; - QJsonObject topLevelObj; - QJsonObject sectionObject; - sectionObject.insert("contents", text); - QJsonArray sectionArray; - sectionArray.append(sectionObject); - topLevelObj.insert("description", "MultiMC Log Upload"); - topLevelObj.insert("sections", sectionArray); - QJsonDocument docOut; - docOut.setObject(topLevelObj); - m_jsonContent = docOut.toJson(); -} - -PasteUpload::~PasteUpload() -{ -} - -bool PasteUpload::validateText() -{ - return m_jsonContent.size() <= maxSize(); -} - -void PasteUpload::executeTask() -{ - QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes")); - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); - - request.setRawHeader("Content-Type", "application/json"); - request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size())); - request.setRawHeader("X-Auth-Token", m_key.toStdString().c_str()); - - QNetworkReply *rep = ENV.qnam().post(request, m_jsonContent); - - m_reply = std::shared_ptr<QNetworkReply>(rep); - setStatus(tr("Uploading to paste.ee")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); -} - -void PasteUpload::downloadError(QNetworkReply::NetworkError error) -{ - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); -} - -void PasteUpload::downloadFinished() -{ - QByteArray data = m_reply->readAll(); - // if the download succeeded - if (m_reply->error() == QNetworkReply::NetworkError::NoError) - { - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - emitFailed(jsonError.errorString()); - return; - } - if (!parseResult(doc)) - { - emitFailed(tr("paste.ee returned an error. Please consult the logs for more information")); - return; - } - } - // else the download failed - else - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); -} - -bool PasteUpload::parseResult(QJsonDocument doc) -{ - auto object = doc.object(); - auto status = object.value("success").toBool(); - if (!status) - { - qCritical() << "paste.ee reported error:" << QString(object.value("error").toString()); - return false; - } - m_pasteLink = object.value("link").toString(); - m_pasteID = object.value("id").toString(); - qDebug() << m_pasteLink; - return true; -} - diff --git a/api/logic/net/PasteUpload.h b/api/logic/net/PasteUpload.h deleted file mode 100644 index 11e05c2e..00000000 --- a/api/logic/net/PasteUpload.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include "tasks/Task.h" -#include <QNetworkReply> -#include <QBuffer> -#include <memory> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT PasteUpload : public Task -{ - Q_OBJECT -public: - PasteUpload(QWidget *window, QString text, QString key = "public"); - virtual ~PasteUpload(); - - QString pasteLink() - { - return m_pasteLink; - } - QString pasteID() - { - return m_pasteID; - } - int maxSize() - { - // 2MB for paste.ee - public - if(m_key == "public") - return 1024*1024*2; - // 12MB for paste.ee - with actual key - return 1024*1024*12; - } - bool validateText(); -protected: - virtual void executeTask(); - -private: - bool parseResult(QJsonDocument doc); - QString m_error; - QWidget *m_window; - QString m_pasteID; - QString m_pasteLink; - QString m_key; - QByteArray m_jsonContent; - std::shared_ptr<QNetworkReply> m_reply; -public -slots: - void downloadError(QNetworkReply::NetworkError); - void downloadFinished(); -}; diff --git a/api/logic/net/Sink.h b/api/logic/net/Sink.h deleted file mode 100644 index d526895c..00000000 --- a/api/logic/net/Sink.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include "net/NetAction.h" - -#include "multimc_logic_export.h" -#include "Validator.h" - -namespace Net { -class MULTIMC_LOGIC_EXPORT Sink -{ -public: /* con/des */ - Sink() {}; - virtual ~Sink() {}; - -public: /* methods */ - virtual JobStatus init(QNetworkRequest & request) = 0; - virtual JobStatus write(QByteArray & data) = 0; - virtual JobStatus abort() = 0; - virtual JobStatus finalize(QNetworkReply & reply) = 0; - virtual bool hasLocalData() = 0; - - void addValidator(Validator * validator) - { - if(validator) - { - validators.push_back(std::shared_ptr<Validator>(validator)); - } - } - -protected: /* methods */ - bool finalizeAllValidators(QNetworkReply & reply) - { - for(auto & validator: validators) - { - if(!validator->validate(reply)) - return false; - } - return true; - } - bool failAllValidators() - { - bool success = true; - for(auto & validator: validators) - { - success &= validator->abort(); - } - return success; - } - bool initAllValidators(QNetworkRequest & request) - { - for(auto & validator: validators) - { - if(!validator->init(request)) - return false; - } - return true; - } - bool writeAllValidators(QByteArray & data) - { - for(auto & validator: validators) - { - if(!validator->write(data)) - return false; - } - return true; - } - -protected: /* data */ - std::vector<std::shared_ptr<Validator>> validators; -}; -} diff --git a/api/logic/net/Validator.h b/api/logic/net/Validator.h deleted file mode 100644 index 955412ce..00000000 --- a/api/logic/net/Validator.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "net/NetAction.h" - -#include "multimc_logic_export.h" - -namespace Net { -class MULTIMC_LOGIC_EXPORT Validator -{ -public: /* con/des */ - Validator() {}; - virtual ~Validator() {}; - -public: /* methods */ - virtual bool init(QNetworkRequest & request) = 0; - virtual bool write(QByteArray & data) = 0; - virtual bool abort() = 0; - virtual bool validate(QNetworkReply & reply) = 0; -}; -}
\ No newline at end of file diff --git a/api/logic/news/NewsChecker.cpp b/api/logic/news/NewsChecker.cpp deleted file mode 100644 index c66f49e1..00000000 --- a/api/logic/news/NewsChecker.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "NewsChecker.h" - -#include <QByteArray> -#include <QDomDocument> - -#include <QDebug> - -NewsChecker::NewsChecker(const QString& feedUrl) -{ - m_feedUrl = feedUrl; -} - -void NewsChecker::reloadNews() -{ - // Start a netjob to download the RSS feed and call rssDownloadFinished() when it's done. - if (isLoadingNews()) - { - qDebug() << "Ignored request to reload news. Currently reloading already."; - return; - } - - qDebug() << "Reloading news."; - - NetJob* job = new NetJob("News RSS Feed"); - job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData)); - QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); - QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed); - m_newsNetJob.reset(job); - job->start(); -} - -void NewsChecker::rssDownloadFinished() -{ - // Parse the XML file and process the RSS feed entries. - qDebug() << "Finished loading RSS feed."; - - m_newsNetJob.reset(); - QDomDocument doc; - { - // Stuff to store error info in. - QString errorMsg = "Unknown error."; - int errorLine = -1; - int errorCol = -1; - - // Parse the XML. - if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) - { - QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol); - fail(fullErrorMsg); - newsData.clear(); - return; - } - newsData.clear(); - } - - // If the parsing succeeded, read it. - QDomNodeList items = doc.elementsByTagName("item"); - m_newsEntries.clear(); - for (int i = 0; i < items.length(); i++) - { - QDomElement element = items.at(i).toElement(); - NewsEntryPtr entry; - entry.reset(new NewsEntry()); - QString errorMsg = "An unknown error occurred."; - if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) - { - qDebug() << "Loaded news entry" << entry->title; - m_newsEntries.append(entry); - } - else - { - qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg; - } - } - - succeed(); -} - -void NewsChecker::rssDownloadFailed(QString reason) -{ - // Set an error message and fail. - fail(tr("Failed to load news RSS feed:\n%1").arg(reason)); -} - - -QList<NewsEntryPtr> NewsChecker::getNewsEntries() const -{ - return m_newsEntries; -} - -bool NewsChecker::isLoadingNews() const -{ - return m_newsNetJob.get() != nullptr; -} - -QString NewsChecker::getLastLoadErrorMsg() const -{ - return m_lastLoadError; -} - -void NewsChecker::succeed() -{ - m_lastLoadError = ""; - qDebug() << "News loading succeeded."; - m_newsNetJob.reset(); - emit newsLoaded(); -} - -void NewsChecker::fail(const QString& errorMsg) -{ - m_lastLoadError = errorMsg; - qDebug() << "Failed to load news:" << errorMsg; - m_newsNetJob.reset(); - emit newsLoadingFailed(errorMsg); -} - diff --git a/api/logic/news/NewsChecker.h b/api/logic/news/NewsChecker.h deleted file mode 100644 index c473ecab..00000000 --- a/api/logic/news/NewsChecker.h +++ /dev/null @@ -1,105 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QList> - -#include <net/NetJob.h> - -#include "NewsEntry.h" - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT NewsChecker : public QObject -{ - Q_OBJECT -public: - /*! - * Constructs a news reader to read from the given RSS feed URL. - */ - NewsChecker(const QString& feedUrl); - - /*! - * Returns the error message for the last time the news was loaded. - * Empty string if the last load was successful. - */ - QString getLastLoadErrorMsg() const; - - /*! - * Returns true if the news has been loaded successfully. - */ - bool isNewsLoaded() const; - - //! True if the news is currently loading. If true, reloadNews() will do nothing. - bool isLoadingNews() const; - - /*! - * Returns a list of news entries. - */ - QList<NewsEntryPtr> getNewsEntries() const; - - /*! - * Reloads the news from the website's RSS feed. - * If the news is already loading, this does nothing. - */ - void Q_SLOT reloadNews(); - -signals: - /*! - * Signal fired after the news has finished loading. - */ - void newsLoaded(); - - /*! - * Signal fired after the news fails to load. - */ - void newsLoadingFailed(QString errorMsg); - -protected slots: - void rssDownloadFinished(); - void rssDownloadFailed(QString reason); - -protected: /* data */ - //! The URL for the RSS feed to fetch. - QString m_feedUrl; - - //! List of news entries. - QList<NewsEntryPtr> m_newsEntries; - - //! The network job to use to load the news. - NetJobPtr m_newsNetJob; - - //! True if news has been loaded. - bool m_loadedNews; - - QByteArray newsData; - - /*! - * Gets the error message that was given last time the news was loaded. - * If the last news load succeeded, this will be an empty string. - */ - QString m_lastLoadError; - -protected slots: - /// Emits newsLoaded() and sets m_lastLoadError to empty string. - void succeed(); - - /// Emits newsLoadingFailed() and sets m_lastLoadError to the given message. - void fail(const QString& errorMsg); -}; - diff --git a/api/logic/news/NewsEntry.cpp b/api/logic/news/NewsEntry.cpp deleted file mode 100644 index 7eff657b..00000000 --- a/api/logic/news/NewsEntry.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "NewsEntry.h" - -#include <QDomNodeList> -#include <QVariant> - -NewsEntry::NewsEntry(QObject* parent) : - QObject(parent) -{ - this->title = tr("Untitled"); - this->content = tr("No content."); - this->link = ""; - this->author = tr("Unknown Author"); - this->pubDate = QDateTime::currentDateTime(); -} - -NewsEntry::NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent) : - QObject(parent) -{ - this->title = title; - this->content = content; - this->link = link; - this->author = author; - this->pubDate = pubDate; -} - -/*! - * Gets the text content of the given child element as a QVariant. - */ -inline QString childValue(const QDomElement& element, const QString& childName, QString defaultVal="") -{ - QDomNodeList nodes = element.elementsByTagName(childName); - if (nodes.count() > 0) - { - QDomElement element = nodes.at(0).toElement(); - return element.text(); - } - else - { - return defaultVal; - } -} - -bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg) -{ - QString title = childValue(element, "title", tr("Untitled")); - QString content = childValue(element, "description", tr("No content.")); - QString link = childValue(element, "link"); - QString author = childValue(element, "dc:creator", tr("Unknown Author")); - QString pubDateStr = childValue(element, "pubDate"); - - // FIXME: For now, we're just ignoring timezones. We assume that all time zones in the RSS feed are the same. - QString dateFormat("ddd, dd MMM yyyy hh:mm:ss"); - QDateTime pubDate = QDateTime::fromString(pubDateStr, dateFormat); - - entry->title = title; - entry->content = content; - entry->link = link; - entry->author = author; - entry->pubDate = pubDate; - return true; -} - diff --git a/api/logic/news/NewsEntry.h b/api/logic/news/NewsEntry.h deleted file mode 100644 index 0dbc70a5..00000000 --- a/api/logic/news/NewsEntry.h +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QDomElement> -#include <QDateTime> - -#include <memory> - -class NewsEntry : public QObject -{ - Q_OBJECT - -public: - /*! - * Constructs an empty news entry. - */ - explicit NewsEntry(QObject* parent=0); - - /*! - * Constructs a new news entry. - * Note that content may contain HTML. - */ - NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent=0); - - /*! - * Attempts to load information from the given XML element into the given news entry pointer. - * If this fails, the function will return false and store an error message in the errorMsg pointer. - */ - static bool fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg=0); - - - //! The post title. - QString title; - - //! The post's content. May contain HTML. - QString content; - - //! URL to the post. - QString link; - - //! The post's author. - QString author; - - //! The date and time that this post was published. - QDateTime pubDate; -}; - -typedef std::shared_ptr<NewsEntry> NewsEntryPtr; - diff --git a/api/logic/notifications/NotificationChecker.cpp b/api/logic/notifications/NotificationChecker.cpp deleted file mode 100644 index 8209c28b..00000000 --- a/api/logic/notifications/NotificationChecker.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include "NotificationChecker.h" - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QDebug> - -#include "Env.h" -#include "net/Download.h" - - -NotificationChecker::NotificationChecker(QObject *parent) - : QObject(parent) -{ -} - -void NotificationChecker::setNotificationsUrl(const QUrl ¬ificationsUrl) -{ - m_notificationsUrl = notificationsUrl; -} - -void NotificationChecker::setApplicationChannel(QString channel) -{ - m_appVersionChannel = channel; -} - -void NotificationChecker::setApplicationFullVersion(QString version) -{ - m_appFullVersion = version; -} - -void NotificationChecker::setApplicationPlatform(QString platform) -{ - m_appPlatform = platform; -} - -QList<NotificationChecker::NotificationEntry> NotificationChecker::notificationEntries() const -{ - return m_entries; -} - -void NotificationChecker::checkForNotifications() -{ - if (!m_notificationsUrl.isValid()) - { - qCritical() << "Failed to check for notifications. No notifications URL set." - << "If you'd like to use MultiMC's notification system, please pass the " - "URL to CMake at compile time."; - return; - } - if (m_checkJob) - { - return; - } - m_checkJob.reset(new NetJob("Checking for notifications")); - auto entry = ENV.metacache()->resolveEntry("root", "notifications.json"); - entry->setStale(true); - m_checkJob->addNetAction(m_download = Net::Download::makeCached(m_notificationsUrl, entry)); - connect(m_download.get(), &Net::Download::succeeded, this, &NotificationChecker::downloadSucceeded); - m_checkJob->start(); -} - -void NotificationChecker::downloadSucceeded(int) -{ - m_entries.clear(); - - QFile file(m_download->getTargetFilepath()); - if (file.open(QFile::ReadOnly)) - { - QJsonArray root = QJsonDocument::fromJson(file.readAll()).array(); - for (auto it = root.begin(); it != root.end(); ++it) - { - QJsonObject obj = (*it).toObject(); - NotificationEntry entry; - entry.id = obj.value("id").toDouble(); - entry.message = obj.value("message").toString(); - entry.channel = obj.value("channel").toString(); - entry.platform = obj.value("platform").toString(); - entry.from = obj.value("from").toString(); - entry.to = obj.value("to").toString(); - const QString type = obj.value("type").toString("critical"); - if (type == "critical") - { - entry.type = NotificationEntry::Critical; - } - else if (type == "warning") - { - entry.type = NotificationEntry::Warning; - } - else if (type == "information") - { - entry.type = NotificationEntry::Information; - } - if(entryApplies(entry)) - m_entries.append(entry); - } - } - - m_checkJob.reset(); - - emit notificationCheckFinished(); -} - -bool versionLessThan(const QString &v1, const QString &v2) -{ - QStringList l1 = v1.split('.'); - QStringList l2 = v2.split('.'); - while (!l1.isEmpty() && !l2.isEmpty()) - { - int one = l1.isEmpty() ? 0 : l1.takeFirst().toInt(); - int two = l2.isEmpty() ? 0 : l2.takeFirst().toInt(); - if (one != two) - { - return one < two; - } - } - return false; -} - -bool NotificationChecker::entryApplies(const NotificationChecker::NotificationEntry& entry) const -{ - bool channelApplies = entry.channel.isEmpty() || entry.channel == m_appVersionChannel; - bool platformApplies = entry.platform.isEmpty() || entry.platform == m_appPlatform; - bool fromApplies = - entry.from.isEmpty() || entry.from == m_appFullVersion || !versionLessThan(m_appFullVersion, entry.from); - bool toApplies = - entry.to.isEmpty() || entry.to == m_appFullVersion || !versionLessThan(entry.to, m_appFullVersion); - return channelApplies && platformApplies && fromApplies && toApplies; -} diff --git a/api/logic/notifications/NotificationChecker.h b/api/logic/notifications/NotificationChecker.h deleted file mode 100644 index 4b1b893d..00000000 --- a/api/logic/notifications/NotificationChecker.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include <QObject> - -#include "net/NetJob.h" -#include "net/Download.h" - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT NotificationChecker : public QObject -{ - Q_OBJECT - -public: - explicit NotificationChecker(QObject *parent = 0); - - void setNotificationsUrl(const QUrl ¬ificationsUrl); - void setApplicationPlatform(QString platform); - void setApplicationChannel(QString channel); - void setApplicationFullVersion(QString version); - - struct NotificationEntry - { - int id; - QString message; - enum - { - Critical, - Warning, - Information - } type; - QString channel; - QString platform; - QString from; - QString to; - }; - - QList<NotificationEntry> notificationEntries() const; - -public -slots: - void checkForNotifications(); - -private -slots: - void downloadSucceeded(int); - -signals: - void notificationCheckFinished(); - -private: - bool entryApplies(const NotificationEntry &entry) const; - -private: - QList<NotificationEntry> m_entries; - QUrl m_notificationsUrl; - NetJobPtr m_checkJob; - Net::Download::Ptr m_download; - - QString m_appVersionChannel; - QString m_appPlatform; - QString m_appFullVersion; -}; diff --git a/api/logic/pathmatcher/FSTreeMatcher.h b/api/logic/pathmatcher/FSTreeMatcher.h deleted file mode 100644 index 361924af..00000000 --- a/api/logic/pathmatcher/FSTreeMatcher.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "IPathMatcher.h" -#include <SeparatorPrefixTree.h> -#include <QRegularExpression> - -class FSTreeMatcher : public IPathMatcher -{ -public: - virtual ~FSTreeMatcher() {}; - FSTreeMatcher(SeparatorPrefixTree<'/'> & tree) : m_fsTree(tree) - { - } - - virtual bool matches(const QString &string) const override - { - return m_fsTree.covers(string); - } - - SeparatorPrefixTree<'/'> & m_fsTree; -}; diff --git a/api/logic/pathmatcher/IPathMatcher.h b/api/logic/pathmatcher/IPathMatcher.h deleted file mode 100644 index b60621c9..00000000 --- a/api/logic/pathmatcher/IPathMatcher.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include <memory> - -class IPathMatcher -{ -public: - typedef std::shared_ptr<IPathMatcher> Ptr; - -public: - virtual ~IPathMatcher(){}; - virtual bool matches(const QString &string) const = 0; -}; diff --git a/api/logic/pathmatcher/MultiMatcher.h b/api/logic/pathmatcher/MultiMatcher.h deleted file mode 100644 index 8bc1b6ee..00000000 --- a/api/logic/pathmatcher/MultiMatcher.h +++ /dev/null @@ -1,31 +0,0 @@ -#include "IPathMatcher.h" -#include <SeparatorPrefixTree.h> -#include <QRegularExpression> - -class MultiMatcher : public IPathMatcher -{ -public: - virtual ~MultiMatcher() {}; - MultiMatcher() - { - } - MultiMatcher &add(Ptr add) - { - m_matchers.append(add); - return *this; - } - - virtual bool matches(const QString &string) const override - { - for(auto iter: m_matchers) - { - if(iter->matches(string)) - { - return true; - } - } - return false; - } - - QList<Ptr> m_matchers; -}; diff --git a/api/logic/pathmatcher/RegexpMatcher.h b/api/logic/pathmatcher/RegexpMatcher.h deleted file mode 100644 index 825d488c..00000000 --- a/api/logic/pathmatcher/RegexpMatcher.h +++ /dev/null @@ -1,42 +0,0 @@ -#include "IPathMatcher.h" -#include <QRegularExpression> - -class RegexpMatcher : public IPathMatcher -{ -public: - virtual ~RegexpMatcher() {}; - RegexpMatcher(const QString ®exp) - { - m_regexp.setPattern(regexp); - m_onlyFilenamePart = !regexp.contains('/'); - } - - RegexpMatcher &caseSensitive(bool cs = true) - { - if(cs) - { - m_regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); - } - else - { - m_regexp.setPatternOptions(QRegularExpression::NoPatternOption); - } - return *this; - } - - virtual bool matches(const QString &string) const override - { - if(m_onlyFilenamePart) - { - auto slash = string.lastIndexOf('/'); - if(slash != -1) - { - auto part = string.mid(slash + 1); - return m_regexp.match(part).hasMatch(); - } - } - return m_regexp.match(string).hasMatch(); - } - QRegularExpression m_regexp; - bool m_onlyFilenamePart = false; -}; diff --git a/api/logic/screenshots/ImgurAlbumCreation.cpp b/api/logic/screenshots/ImgurAlbumCreation.cpp deleted file mode 100644 index 1f195f00..00000000 --- a/api/logic/screenshots/ImgurAlbumCreation.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "ImgurAlbumCreation.h" - -#include <QNetworkRequest> -#include <QJsonDocument> -#include <QJsonObject> -#include <QUrl> -#include <QStringList> - -#include "BuildConfig.h" -#include "Env.h" -#include <QDebug> - -ImgurAlbumCreation::ImgurAlbumCreation(QList<ScreenshotPtr> screenshots) : NetAction(), m_screenshots(screenshots) -{ - m_url = BuildConfig.IMGUR_BASE_URL + "album.json"; - m_status = Job_NotStarted; -} - -void ImgurAlbumCreation::start() -{ - m_status = Job_InProgress; - QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str()); - request.setRawHeader("Accept", "application/json"); - - QStringList hashes; - for (auto shot : m_screenshots) - { - hashes.append(shot->m_imgurDeleteHash); - } - - const QByteArray data = "deletehashes=" + hashes.join(',').toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden"; - - QNetworkReply *rep = ENV.qnam().post(request, data); - - m_reply.reset(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress); - connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); -} -void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error) -{ - qDebug() << m_reply->errorString(); - m_status = Job_Failed; -} -void ImgurAlbumCreation::downloadFinished() -{ - if (m_status != Job_Failed) - { - QByteArray data = m_reply->readAll(); - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - qDebug() << jsonError.errorString(); - emit failed(m_index_within_job); - return; - } - auto object = doc.object(); - if (!object.value("success").toBool()) - { - qDebug() << doc.toJson(); - emit failed(m_index_within_job); - return; - } - m_deleteHash = object.value("data").toObject().value("deletehash").toString(); - m_id = object.value("data").toObject().value("id").toString(); - m_status = Job_Finished; - emit succeeded(m_index_within_job); - return; - } - else - { - qDebug() << m_reply->readAll(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } -} -void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); -} diff --git a/api/logic/screenshots/ImgurAlbumCreation.h b/api/logic/screenshots/ImgurAlbumCreation.h deleted file mode 100644 index 55478021..00000000 --- a/api/logic/screenshots/ImgurAlbumCreation.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include "net/NetAction.h" -#include "Screenshot.h" - -#include "multimc_logic_export.h" - -typedef std::shared_ptr<class ImgurAlbumCreation> ImgurAlbumCreationPtr; -class MULTIMC_LOGIC_EXPORT ImgurAlbumCreation : public NetAction -{ -public: - explicit ImgurAlbumCreation(QList<ScreenshotPtr> screenshots); - static ImgurAlbumCreationPtr make(QList<ScreenshotPtr> screenshots) - { - return ImgurAlbumCreationPtr(new ImgurAlbumCreation(screenshots)); - } - - QString deleteHash() const - { - return m_deleteHash; - } - QString id() const - { - return m_id; - } - -protected -slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - virtual void downloadError(QNetworkReply::NetworkError error); - virtual void downloadFinished(); - virtual void downloadReadyRead() - { - } - -public -slots: - virtual void start(); - -private: - QList<ScreenshotPtr> m_screenshots; - - QString m_deleteHash; - QString m_id; -}; diff --git a/api/logic/screenshots/ImgurUpload.cpp b/api/logic/screenshots/ImgurUpload.cpp deleted file mode 100644 index 7e95d5ca..00000000 --- a/api/logic/screenshots/ImgurUpload.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include "ImgurUpload.h" - -#include <QNetworkRequest> -#include <QHttpMultiPart> -#include <QJsonDocument> -#include <QJsonObject> -#include <QHttpPart> -#include <QFile> -#include <QUrl> - -#include "BuildConfig.h" -#include "Env.h" -#include <QDebug> - -ImgurUpload::ImgurUpload(ScreenshotPtr shot) : NetAction(), m_shot(shot) -{ - m_url = BuildConfig.IMGUR_BASE_URL + "upload.json"; - m_status = Job_NotStarted; -} - -void ImgurUpload::start() -{ - finished = false; - m_status = Job_InProgress; - QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); - request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str()); - request.setRawHeader("Accept", "application/json"); - - QFile f(m_shot->m_file.absoluteFilePath()); - if (!f.open(QFile::ReadOnly)) - { - emit failed(m_index_within_job); - return; - } - - QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - QHttpPart filePart; - filePart.setBody(f.readAll().toBase64()); - filePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); - filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"image\""); - multipart->append(filePart); - QHttpPart typePart; - typePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"type\""); - typePart.setBody("base64"); - multipart->append(typePart); - QHttpPart namePart; - namePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"name\""); - namePart.setBody(m_shot->m_file.baseName().toUtf8()); - multipart->append(namePart); - - QNetworkReply *rep = ENV.qnam().post(request, multipart); - - m_reply.reset(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress); - connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), - SLOT(downloadError(QNetworkReply::NetworkError))); -} -void ImgurUpload::downloadError(QNetworkReply::NetworkError error) -{ - qCritical() << "ImgurUpload failed with error" << m_reply->errorString() << "Server reply:\n" << m_reply->readAll(); - if(finished) - { - qCritical() << "Double finished ImgurUpload!"; - return; - } - m_status = Job_Failed; - finished = true; - m_reply.reset(); - emit failed(m_index_within_job); -} -void ImgurUpload::downloadFinished() -{ - if(finished) - { - qCritical() << "Double finished ImgurUpload!"; - return; - } - QByteArray data = m_reply->readAll(); - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - qDebug() << "imgur server did not reply with JSON" << jsonError.errorString(); - finished = true; - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - auto object = doc.object(); - if (!object.value("success").toBool()) - { - qDebug() << "Screenshot upload not successful:" << doc.toJson(); - finished = true; - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); - m_shot->m_url = object.value("data").toObject().value("link").toString(); - m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString(); - m_status = Job_Finished; - finished = true; - emit succeeded(m_index_within_job); - return; -} -void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); -} diff --git a/api/logic/screenshots/ImgurUpload.h b/api/logic/screenshots/ImgurUpload.h deleted file mode 100644 index d79807f2..00000000 --- a/api/logic/screenshots/ImgurUpload.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include "net/NetAction.h" -#include "Screenshot.h" - -#include "multimc_logic_export.h" - -typedef std::shared_ptr<class ImgurUpload> ImgurUploadPtr; -class MULTIMC_LOGIC_EXPORT ImgurUpload : public NetAction -{ -public: - explicit ImgurUpload(ScreenshotPtr shot); - static ImgurUploadPtr make(ScreenshotPtr shot) - { - return ImgurUploadPtr(new ImgurUpload(shot)); - } - -protected -slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - virtual void downloadError(QNetworkReply::NetworkError error); - virtual void downloadFinished(); - virtual void downloadReadyRead() - { - } - -public -slots: - virtual void start(); - -private: - ScreenshotPtr m_shot; - bool finished = true; -}; diff --git a/api/logic/screenshots/Screenshot.h b/api/logic/screenshots/Screenshot.h deleted file mode 100644 index 9db3a8a1..00000000 --- a/api/logic/screenshots/Screenshot.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include <QDateTime> -#include <QString> -#include <QFileInfo> -#include <memory> - -struct ScreenShot -{ - ScreenShot(QFileInfo file) - { - m_file = file; - } - QFileInfo m_file; - QString m_url; - QString m_imgurId; - QString m_imgurDeleteHash; -}; - -typedef std::shared_ptr<ScreenShot> ScreenshotPtr; diff --git a/api/logic/settings/INIFile.cpp b/api/logic/settings/INIFile.cpp deleted file mode 100644 index 6a3c801d..00000000 --- a/api/logic/settings/INIFile.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "settings/INIFile.h" -#include <FileSystem.h> - -#include <QFile> -#include <QTextStream> -#include <QStringList> -#include <QSaveFile> -#include <QDebug> - -INIFile::INIFile() -{ -} - -QString INIFile::unescape(QString orig) -{ - QString out; - QChar prev = 0; - for(auto c: orig) - { - if(prev == '\\') - { - if(c == 'n') - out += '\n'; - else if(c == 't') - out += '\t'; - else if(c == '#') - out += '#'; - else - out += c; - prev = 0; - } - else - { - if(c == '\\') - { - prev = c; - continue; - } - out += c; - prev = 0; - } - } - return out; -} - -QString INIFile::escape(QString orig) -{ - QString out; - for(auto c: orig) - { - if(c == '\n') - out += "\\n"; - else if (c == '\t') - out += "\\t"; - else if(c == '\\') - out += "\\\\"; - else if(c == '#') - out += "\\#"; - else - out += c; - } - return out; -} - -bool INIFile::saveFile(QString fileName) -{ - QByteArray outArray; - for (Iterator iter = begin(); iter != end(); iter++) - { - QString value = iter.value().toString(); - value = escape(value); - outArray.append(iter.key().toUtf8()); - outArray.append('='); - outArray.append(value.toUtf8()); - outArray.append('\n'); - } - - try - { - FS::write(fileName, outArray); - } - catch (const Exception &e) - { - qCritical() << e.what(); - return false; - } - - return true; -} - - -bool INIFile::loadFile(QString fileName) -{ - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) - return false; - bool success = loadFile(file.readAll()); - file.close(); - return success; -} - -bool INIFile::loadFile(QByteArray file) -{ - QTextStream in(file); - in.setCodec("UTF-8"); - - QStringList lines = in.readAll().split('\n'); - for (int i = 0; i < lines.count(); i++) - { - QString &lineRaw = lines[i]; - // Ignore comments. - int commentIndex = 0; - QString line = lineRaw; - // Search for comments until no more escaped # are available - while((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) { - if(commentIndex > 0 && line.at(commentIndex - 1) == '\\') { - continue; - } - line = line.left(lineRaw.indexOf('#')).trimmed(); - } - - int eqPos = line.indexOf('='); - if (eqPos == -1) - continue; - QString key = line.left(eqPos).trimmed(); - QString valueStr = line.right(line.length() - eqPos - 1).trimmed(); - - valueStr = unescape(valueStr); - - QVariant value(valueStr); - this->operator[](key) = value; - } - - return true; -} - -QVariant INIFile::get(QString key, QVariant def) const -{ - if (!this->contains(key)) - return def; - else - return this->operator[](key); -} - -void INIFile::set(QString key, QVariant val) -{ - this->operator[](key) = val; -} diff --git a/api/logic/settings/INIFile.h b/api/logic/settings/INIFile.h deleted file mode 100644 index 9e8c68ea..00000000 --- a/api/logic/settings/INIFile.h +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QVariant> -#include <QIODevice> - -#include "multimc_logic_export.h" - -// Sectionless INI parser (for instance config files) -class MULTIMC_LOGIC_EXPORT INIFile : public QMap<QString, QVariant> -{ -public: - explicit INIFile(); - - bool loadFile(QByteArray file); - bool loadFile(QString fileName); - bool saveFile(QString fileName); - - QVariant get(QString key, QVariant def) const; - void set(QString key, QVariant val); - static QString unescape(QString orig); - static QString escape(QString orig); -}; diff --git a/api/logic/settings/INIFile_test.cpp b/api/logic/settings/INIFile_test.cpp deleted file mode 100644 index 08c2155e..00000000 --- a/api/logic/settings/INIFile_test.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include <QTest> -#include "TestUtil.h" - -#include "settings/INIFile.h" - -class IniFileTest : public QObject -{ - Q_OBJECT -private -slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void test_Escape_data() - { - QTest::addColumn<QString>("through"); - - QTest::newRow("unix path") << "/abc/def/ghi/jkl"; - QTest::newRow("windows path") << "C:\\Program files\\terrible\\name\\of something\\"; - QTest::newRow("Plain text") << "Lorem ipsum dolor sit amet."; - QTest::newRow("Escape sequences") << "Lorem\n\t\n\\n\\tAAZ\nipsum dolor\n\nsit amet."; - QTest::newRow("Escape sequences 2") << "\"\n\n\""; - QTest::newRow("Hashtags") << "some data#something"; - } - void test_Escape() - { - QFETCH(QString, through); - - QString there = INIFile::escape(through); - QString back = INIFile::unescape(there); - - QCOMPARE(back, through); - } - - void test_SaveLoad() - { - QString a = "a"; - QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\#thisIsNotAComment"; - QString filename = "test_SaveLoad.ini"; - - // save - INIFile f; - f.set("a", a); - f.set("b", b); - f.saveFile(filename); - - // load - INIFile f2; - f2.loadFile(filename); - QCOMPARE(a, f2.get("a","NOT SET").toString()); - QCOMPARE(b, f2.get("b","NOT SET").toString()); - } -}; - -QTEST_GUILESS_MAIN(IniFileTest) - -#include "INIFile_test.moc" diff --git a/api/logic/settings/INISettingsObject.cpp b/api/logic/settings/INISettingsObject.cpp deleted file mode 100644 index 12513403..00000000 --- a/api/logic/settings/INISettingsObject.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "INISettingsObject.h" -#include "Setting.h" - -INISettingsObject::INISettingsObject(const QString &path, QObject *parent) - : SettingsObject(parent) -{ - m_filePath = path; - m_ini.loadFile(path); -} - -void INISettingsObject::setFilePath(const QString &filePath) -{ - m_filePath = filePath; -} - -bool INISettingsObject::reload() -{ - return m_ini.loadFile(m_filePath) && SettingsObject::reload(); -} - -void INISettingsObject::suspendSave() -{ - m_suspendSave = true; -} - -void INISettingsObject::resumeSave() -{ - m_suspendSave = false; - if(m_doSave) - { - m_ini.saveFile(m_filePath); - } -} - -void INISettingsObject::changeSetting(const Setting &setting, QVariant value) -{ - if (contains(setting.id())) - { - // valid value -> set the main config, remove all the sysnonyms - if (value.isValid()) - { - auto list = setting.configKeys(); - m_ini.set(list.takeFirst(), value); - for(auto iter: list) - m_ini.remove(iter); - } - // invalid -> remove all (just like resetSetting) - else - { - for(auto iter: setting.configKeys()) - m_ini.remove(iter); - } - doSave(); - } -} - -void INISettingsObject::doSave() -{ - if(m_suspendSave) - { - m_doSave = true; - } - else - { - m_ini.saveFile(m_filePath); - } -} - -void INISettingsObject::resetSetting(const Setting &setting) -{ - // if we have the setting, remove all the synonyms. ALL OF THEM - if (contains(setting.id())) - { - for(auto iter: setting.configKeys()) - m_ini.remove(iter); - doSave(); - } -} - -QVariant INISettingsObject::retrieveValue(const Setting &setting) -{ - // if we have the setting, return value of the first matching synonym - if (contains(setting.id())) - { - for(auto iter: setting.configKeys()) - { - if(m_ini.contains(iter)) - return m_ini[iter]; - } - } - return QVariant(); -} diff --git a/api/logic/settings/INISettingsObject.h b/api/logic/settings/INISettingsObject.h deleted file mode 100644 index 313f1512..00000000 --- a/api/logic/settings/INISettingsObject.h +++ /dev/null @@ -1,66 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "settings/INIFile.h" - -#include "settings/SettingsObject.h" - -#include "multimc_logic_export.h" - -/*! - * \brief A settings object that stores its settings in an INIFile. - */ -class MULTIMC_LOGIC_EXPORT INISettingsObject : public SettingsObject -{ - Q_OBJECT -public: - explicit INISettingsObject(const QString &path, QObject *parent = 0); - - /*! - * \brief Gets the path to the INI file. - * \return The path to the INI file. - */ - virtual QString filePath() const - { - return m_filePath; - } - - /*! - * \brief Sets the path to the INI file and reloads it. - * \param filePath The INI file's new path. - */ - virtual void setFilePath(const QString &filePath); - - bool reload() override; - - void suspendSave() override; - void resumeSave() override; - -protected slots: - virtual void changeSetting(const Setting &setting, QVariant value) override; - virtual void resetSetting(const Setting &setting) override; - -protected: - virtual QVariant retrieveValue(const Setting &setting) override; - void doSave(); - -protected: - INIFile m_ini; - QString m_filePath; -}; diff --git a/api/logic/settings/OverrideSetting.cpp b/api/logic/settings/OverrideSetting.cpp deleted file mode 100644 index 4396a381..00000000 --- a/api/logic/settings/OverrideSetting.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "OverrideSetting.h" - -OverrideSetting::OverrideSetting(std::shared_ptr<Setting> other, std::shared_ptr<Setting> gate) - : Setting(other->configKeys(), QVariant()) -{ - Q_ASSERT(other); - Q_ASSERT(gate); - m_other = other; - m_gate = gate; -} - -bool OverrideSetting::isOverriding() const -{ - return m_gate->get().toBool(); -} - -QVariant OverrideSetting::defValue() const -{ - return m_other->get(); -} - -QVariant OverrideSetting::get() const -{ - if(isOverriding()) - { - return Setting::get(); - } - return m_other->get(); -} - -void OverrideSetting::reset() -{ - Setting::reset(); -} - -void OverrideSetting::set(QVariant value) -{ - Setting::set(value); -} diff --git a/api/logic/settings/OverrideSetting.h b/api/logic/settings/OverrideSetting.h deleted file mode 100644 index 9f0c98b5..00000000 --- a/api/logic/settings/OverrideSetting.h +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <memory> - -#include "Setting.h" - -/*! - * \brief A setting that 'overrides another.' - * This means that the setting's default value will be the value of another setting. - * The other setting can be (and usually is) a part of a different SettingsObject - * than this one. - */ -class OverrideSetting : public Setting -{ - Q_OBJECT -public: - explicit OverrideSetting(std::shared_ptr<Setting> overriden, std::shared_ptr<Setting> gate); - - virtual QVariant defValue() const; - virtual QVariant get() const; - virtual void set (QVariant value); - virtual void reset(); - -private: - bool isOverriding() const; - -protected: - std::shared_ptr<Setting> m_other; - std::shared_ptr<Setting> m_gate; -}; diff --git a/api/logic/settings/PassthroughSetting.cpp b/api/logic/settings/PassthroughSetting.cpp deleted file mode 100644 index 8f93b251..00000000 --- a/api/logic/settings/PassthroughSetting.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "PassthroughSetting.h" - -PassthroughSetting::PassthroughSetting(std::shared_ptr<Setting> other, std::shared_ptr<Setting> gate) - : Setting(other->configKeys(), QVariant()) -{ - Q_ASSERT(other); - m_other = other; - m_gate = gate; -} - -bool PassthroughSetting::isOverriding() const -{ - if(!m_gate) - { - return false; - } - return m_gate->get().toBool(); -} - -QVariant PassthroughSetting::defValue() const -{ - if(isOverriding()) - { - return m_other->get(); - } - return m_other->defValue(); -} - -QVariant PassthroughSetting::get() const -{ - if(isOverriding()) - { - return Setting::get(); - } - return m_other->get(); -} - -void PassthroughSetting::reset() -{ - if(isOverriding()) - { - Setting::reset(); - } - m_other->reset(); -} - -void PassthroughSetting::set(QVariant value) -{ - if(isOverriding()) - { - Setting::set(value); - } - m_other->set(value); -} diff --git a/api/logic/settings/PassthroughSetting.h b/api/logic/settings/PassthroughSetting.h deleted file mode 100644 index 22008f83..00000000 --- a/api/logic/settings/PassthroughSetting.h +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <memory> - -#include "Setting.h" - -/*! - * \brief A setting that 'overrides another.' based on the value of a 'gate' setting - * If 'gate' evaluates to true, the override stores and returns data - * If 'gate' evaluates to false, the original does, - */ -class PassthroughSetting : public Setting -{ - Q_OBJECT -public: - explicit PassthroughSetting(std::shared_ptr<Setting> overriden, std::shared_ptr<Setting> gate); - - virtual QVariant defValue() const; - virtual QVariant get() const; - virtual void set (QVariant value); - virtual void reset(); - -private: - bool isOverriding() const; - -protected: - std::shared_ptr<Setting> m_other; - std::shared_ptr<Setting> m_gate; -}; diff --git a/api/logic/settings/Setting.cpp b/api/logic/settings/Setting.cpp deleted file mode 100644 index cfe5a7f9..00000000 --- a/api/logic/settings/Setting.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "Setting.h" -#include "settings/SettingsObject.h" - -Setting::Setting(QStringList synonyms, QVariant defVal) - : QObject(), m_synonyms(synonyms), m_defVal(defVal) -{ -} - -QVariant Setting::get() const -{ - SettingsObject *sbase = m_storage; - if (!sbase) - { - return defValue(); - } - else - { - QVariant test = sbase->retrieveValue(*this); - if (!test.isValid()) - return defValue(); - return test; - } -} - -QVariant Setting::defValue() const -{ - return m_defVal; -} - -void Setting::set(QVariant value) -{ - emit SettingChanged(*this, value); -} - -void Setting::reset() -{ - emit settingReset(*this); -} diff --git a/api/logic/settings/Setting.h b/api/logic/settings/Setting.h deleted file mode 100644 index a31193ac..00000000 --- a/api/logic/settings/Setting.h +++ /dev/null @@ -1,119 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QVariant> -#include <QStringList> -#include <memory> - -#include "multimc_logic_export.h" - -class SettingsObject; - -/*! - * - */ -class MULTIMC_LOGIC_EXPORT Setting : public QObject -{ - Q_OBJECT -public: - /** - * Construct a Setting - * - * Synonyms are all the possible names used in the settings object, in order of preference. - * First synonym is the ID, which identifies the setting in MultiMC. - * - * defVal is the default value that will be returned when the settings object - * doesn't have any value for this setting. - */ - explicit Setting(QStringList synonyms, QVariant defVal = QVariant()); - - /*! - * \brief Gets this setting's ID. - * This is used to refer to the setting within the application. - * \warning Changing the ID while the setting is registered with a SettingsObject results in - * undefined behavior. - * \return The ID of the setting. - */ - virtual QString id() const - { - return m_synonyms.first(); - } - - /*! - * \brief Gets this setting's config file key. - * This is used to store the setting's value in the config file. It is usually - * the same as the setting's ID, but it can be different. - * \return The setting's config file key. - */ - virtual QStringList configKeys() const - { - return m_synonyms; - } - - /*! - * \brief Gets this setting's value as a QVariant. - * This is done by calling the SettingsObject's retrieveValue() function. - * If this Setting doesn't have a SettingsObject, this returns an invalid QVariant. - * \return QVariant containing this setting's value. - * \sa value() - */ - virtual QVariant get() const; - - /*! - * \brief Gets this setting's default value. - * \return The default value of this setting. - */ - virtual QVariant defValue() const; - -signals: - /*! - * \brief Signal emitted when this Setting object's value changes. - * \param setting A reference to the Setting that changed. - * \param value This Setting object's new value. - */ - void SettingChanged(const Setting &setting, QVariant value); - - /*! - * \brief Signal emitted when this Setting object's value resets to default. - * \param setting A reference to the Setting that changed. - */ - void settingReset(const Setting &setting); - -public -slots: - /*! - * \brief Changes the setting's value. - * This is done by emitting the SettingChanged() signal which will then be - * handled by the SettingsObject object and cause the setting to change. - * \param value The new value. - */ - virtual void set(QVariant value); - - /*! - * \brief Reset the setting to default - * This is done by emitting the settingReset() signal which will then be - * handled by the SettingsObject object and cause the setting to change. - */ - virtual void reset(); - -protected: - friend class SettingsObject; - SettingsObject * m_storage; - QStringList m_synonyms; - QVariant m_defVal; -}; diff --git a/api/logic/settings/SettingsObject.cpp b/api/logic/settings/SettingsObject.cpp deleted file mode 100644 index 8a0bc045..00000000 --- a/api/logic/settings/SettingsObject.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "settings/SettingsObject.h" -#include "settings/Setting.h" -#include "settings/OverrideSetting.h" -#include "PassthroughSetting.h" -#include <QDebug> - -#include <QVariant> - -SettingsObject::SettingsObject(QObject *parent) : QObject(parent) -{ -} - -SettingsObject::~SettingsObject() -{ - m_settings.clear(); -} - -std::shared_ptr<Setting> SettingsObject::registerOverride(std::shared_ptr<Setting> original, - std::shared_ptr<Setting> gate) -{ - if (contains(original->id())) - { - qCritical() << QString("Failed to register setting %1. ID already exists.") - .arg(original->id()); - return nullptr; // Fail - } - auto override = std::make_shared<OverrideSetting>(original, gate); - override->m_storage = this; - connectSignals(*override); - m_settings.insert(override->id(), override); - return override; -} - -std::shared_ptr<Setting> SettingsObject::registerPassthrough(std::shared_ptr<Setting> original, - std::shared_ptr<Setting> gate) -{ - if (contains(original->id())) - { - qCritical() << QString("Failed to register setting %1. ID already exists.") - .arg(original->id()); - return nullptr; // Fail - } - auto passthrough = std::make_shared<PassthroughSetting>(original, gate); - passthrough->m_storage = this; - connectSignals(*passthrough); - m_settings.insert(passthrough->id(), passthrough); - return passthrough; -} - -std::shared_ptr<Setting> SettingsObject::registerSetting(QStringList synonyms, QVariant defVal) -{ - if (synonyms.empty()) - return nullptr; - if (contains(synonyms.first())) - { - qCritical() << QString("Failed to register setting %1. ID already exists.") - .arg(synonyms.first()); - return nullptr; // Fail - } - auto setting = std::make_shared<Setting>(synonyms, defVal); - setting->m_storage = this; - connectSignals(*setting); - m_settings.insert(setting->id(), setting); - return setting; -} - -std::shared_ptr<Setting> SettingsObject::getSetting(const QString &id) const -{ - // Make sure there is a setting with the given ID. - if (!m_settings.contains(id)) - return NULL; - - return m_settings[id]; -} - -QVariant SettingsObject::get(const QString &id) const -{ - auto setting = getSetting(id); - return (setting ? setting->get() : QVariant()); -} - -bool SettingsObject::set(const QString &id, QVariant value) -{ - auto setting = getSetting(id); - if (!setting) - { - qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id); - return false; - } - else - { - setting->set(value); - return true; - } -} - -void SettingsObject::reset(const QString &id) const -{ - auto setting = getSetting(id); - if (setting) - setting->reset(); -} - -bool SettingsObject::contains(const QString &id) -{ - return m_settings.contains(id); -} - -bool SettingsObject::reload() -{ - for (auto setting : m_settings.values()) - { - setting->set(setting->get()); - } - return true; -} - -void SettingsObject::connectSignals(const Setting &setting) -{ - connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), - SLOT(changeSetting(const Setting &, QVariant))); - connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), - SIGNAL(SettingChanged(const Setting &, QVariant))); - - connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &))); - connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &))); -} diff --git a/api/logic/settings/SettingsObject.h b/api/logic/settings/SettingsObject.h deleted file mode 100644 index 3ebdebdf..00000000 --- a/api/logic/settings/SettingsObject.h +++ /dev/null @@ -1,214 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QMap> -#include <QStringList> -#include <QVariant> -#include <memory> - -#include "multimc_logic_export.h" - -class Setting; -class SettingsObject; - -typedef std::shared_ptr<SettingsObject> SettingsObjectPtr; - -/*! - * \brief The SettingsObject handles communicating settings between the application and a - *settings file. - * The class keeps a list of Setting objects. Each Setting object represents one - * of the application's settings. These Setting objects are registered with - * a SettingsObject and can be managed similarly to the way a list works. - * - * \author Andrew Okin - * \date 2/22/2013 - * - * \sa Setting - */ -class MULTIMC_LOGIC_EXPORT SettingsObject : public QObject -{ - Q_OBJECT -public: - class Lock - { - public: - Lock(SettingsObjectPtr locked) - :m_locked(locked) - { - m_locked->suspendSave(); - } - ~Lock() - { - m_locked->resumeSave(); - } - private: - SettingsObjectPtr m_locked; - }; -public: - explicit SettingsObject(QObject *parent = 0); - virtual ~SettingsObject(); - /*! - * Registers an override setting for the given original setting in this settings object - * gate decides if the passthrough (true) or the original (false) is used for value - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr<Setting> registerOverride(std::shared_ptr<Setting> original, std::shared_ptr<Setting> gate); - - /*! - * Registers a passthorugh setting for the given original setting in this settings object - * gate decides if the passthrough (true) or the original (false) is used for value - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr<Setting> registerPassthrough(std::shared_ptr<Setting> original, std::shared_ptr<Setting> gate); - - /*! - * Registers the given setting with this SettingsObject and connects the necessary signals. - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr<Setting> registerSetting(QStringList synonyms, - QVariant defVal = QVariant()); - - /*! - * Registers the given setting with this SettingsObject and connects the necessary signals. - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr<Setting> registerSetting(QString id, QVariant defVal = QVariant()) - { - return registerSetting(QStringList(id), defVal); - } - - /*! - * \brief Gets the setting with the given ID. - * \param id The ID of the setting to get. - * \return A pointer to the setting with the given ID. - * Returns null if there is no setting with the given ID. - * \sa operator []() - */ - std::shared_ptr<Setting> getSetting(const QString &id) const; - - /*! - * \brief Gets the value of the setting with the given ID. - * \param id The ID of the setting to get. - * \return The setting's value as a QVariant. - * If no setting with the given ID exists, returns an invalid QVariant. - */ - QVariant get(const QString &id) const; - - /*! - * \brief Sets the value of the setting with the given ID. - * If no setting with the given ID exists, returns false - * \param id The ID of the setting to change. - * \param value The new value of the setting. - * \return True if successful, false if it failed. - */ - bool set(const QString &id, QVariant value); - - /*! - * \brief Reverts the setting with the given ID to default. - * \param id The ID of the setting to reset. - */ - void reset(const QString &id) const; - - /*! - * \brief Checks if this SettingsObject contains a setting with the given ID. - * \param id The ID to check for. - * \return True if the SettingsObject has a setting with the given ID. - */ - bool contains(const QString &id); - - /*! - * \brief Reloads the settings and emit signals for changed settings - * \return True if reloading was successful - */ - virtual bool reload(); - - virtual void suspendSave() = 0; - virtual void resumeSave() = 0; -signals: - /*! - * \brief Signal emitted when one of this SettingsObject object's settings changes. - * This is usually just connected directly to each Setting object's - * SettingChanged() signals. - * \param setting A reference to the Setting object that changed. - * \param value The Setting object's new value. - */ - void SettingChanged(const Setting &setting, QVariant value); - - /*! - * \brief Signal emitted when one of this SettingsObject object's settings resets. - * This is usually just connected directly to each Setting object's - * settingReset() signals. - * \param setting A reference to the Setting object that changed. - */ - void settingReset(const Setting &setting); - -protected -slots: - /*! - * \brief Changes a setting. - * This slot is usually connected to each Setting object's - * SettingChanged() signal. The signal is emitted, causing this slot - * to update the setting's value in the config file. - * \param setting A reference to the Setting object that changed. - * \param value The setting's new value. - */ - virtual void changeSetting(const Setting &setting, QVariant value) = 0; - - /*! - * \brief Resets a setting. - * This slot is usually connected to each Setting object's - * settingReset() signal. The signal is emitted, causing this slot - * to update the setting's value in the config file. - * \param setting A reference to the Setting object that changed. - */ - virtual void resetSetting(const Setting &setting) = 0; - -protected: - /*! - * \brief Connects the necessary signals to the given Setting. - * \param setting The setting to connect. - */ - void connectSignals(const Setting &setting); - - /*! - * \brief Function used by Setting objects to get their values from the SettingsObject. - * \param setting The - * \return - */ - virtual QVariant retrieveValue(const Setting &setting) = 0; - - friend class Setting; - -private: - QMap<QString, std::shared_ptr<Setting>> m_settings; -protected: - bool m_suspendSave = false; - bool m_doSave = false; -}; diff --git a/api/logic/status/StatusChecker.cpp b/api/logic/status/StatusChecker.cpp deleted file mode 100644 index 38fc2163..00000000 --- a/api/logic/status/StatusChecker.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "StatusChecker.h" - -#include <QByteArray> - -#include <QDebug> - -#include <BuildConfig.h> - -StatusChecker::StatusChecker() -{ - -} - -void StatusChecker::timerEvent(QTimerEvent *e) -{ - QObject::timerEvent(e); - reloadStatus(); -} - -void StatusChecker::reloadStatus() -{ - if (isLoadingStatus()) - { - // qDebug() << "Ignored request to reload status. Currently reloading already."; - return; - } - - // qDebug() << "Reloading status."; - - NetJob* job = new NetJob("Status JSON"); - job->addNetAction(Net::Download::makeByteArray(BuildConfig.MOJANG_STATUS_URL, &dataSink)); - QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished); - QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed); - m_statusNetJob.reset(job); - emit statusLoading(true); - job->start(); -} - -void StatusChecker::statusDownloadFinished() -{ - qDebug() << "Finished loading status JSON."; - m_statusEntries.clear(); - m_statusNetJob.reset(); - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(dataSink, &jsonError); - - if (jsonError.error != QJsonParseError::NoError) - { - fail("Error parsing status JSON:" + jsonError.errorString()); - return; - } - - if (!jsonDoc.isArray()) - { - fail("Error parsing status JSON: JSON root is not an array"); - return; - } - - QJsonArray root = jsonDoc.array(); - - for(auto status = root.begin(); status != root.end(); ++status) - { - QVariantMap map = (*status).toObject().toVariantMap(); - - for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) - { - QString key = iter.key(); - QVariant value = iter.value(); - - if(value.type() == QVariant::Type::String) - { - m_statusEntries.insert(key, value.toString()); - //qDebug() << "Status JSON object: " << key << m_statusEntries[key]; - } - else - { - fail("Malformed status JSON: expected status type to be a string."); - return; - } - } - } - - succeed(); -} - -void StatusChecker::statusDownloadFailed(QString reason) -{ - fail(tr("Failed to load status JSON:\n%1").arg(reason)); -} - - -QMap<QString, QString> StatusChecker::getStatusEntries() const -{ - return m_statusEntries; -} - -bool StatusChecker::isLoadingStatus() const -{ - return m_statusNetJob.get() != nullptr; -} - -QString StatusChecker::getLastLoadErrorMsg() const -{ - return m_lastLoadError; -} - -void StatusChecker::succeed() -{ - if(m_prevEntries != m_statusEntries) - { - emit statusChanged(m_statusEntries); - m_prevEntries = m_statusEntries; - } - m_lastLoadError = ""; - qDebug() << "Status loading succeeded."; - m_statusNetJob.reset(); - emit statusLoading(false); -} - -void StatusChecker::fail(const QString& errorMsg) -{ - if(m_prevEntries != m_statusEntries) - { - emit statusChanged(m_statusEntries); - m_prevEntries = m_statusEntries; - } - m_lastLoadError = errorMsg; - qDebug() << "Failed to load status:" << errorMsg; - m_statusNetJob.reset(); - emit statusLoading(false); -} - diff --git a/api/logic/status/StatusChecker.h b/api/logic/status/StatusChecker.h deleted file mode 100644 index e9961aff..00000000 --- a/api/logic/status/StatusChecker.h +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QList> - -#include <net/NetJob.h> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT StatusChecker : public QObject -{ - Q_OBJECT -public: /* con/des */ - StatusChecker(); - -public: /* methods */ - QString getLastLoadErrorMsg() const; - bool isLoadingStatus() const; - QMap<QString, QString> getStatusEntries() const; - -signals: - void statusLoading(bool loading); - void statusChanged(QMap<QString, QString> newStatus); - -public slots: - void reloadStatus(); - -protected: /* methods */ - virtual void timerEvent(QTimerEvent *); - -protected slots: - void statusDownloadFinished(); - void statusDownloadFailed(QString reason); - void succeed(); - void fail(const QString& errorMsg); - -protected: /* data */ - QMap<QString, QString> m_prevEntries; - QMap<QString, QString> m_statusEntries; - NetJobPtr m_statusNetJob; - QString m_lastLoadError; - QByteArray dataSink; -}; - diff --git a/api/logic/tasks/SequentialTask.cpp b/api/logic/tasks/SequentialTask.cpp deleted file mode 100644 index d0777132..00000000 --- a/api/logic/tasks/SequentialTask.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "SequentialTask.h" - -SequentialTask::SequentialTask(QObject *parent) : Task(parent), m_currentIndex(-1) -{ -} - -void SequentialTask::addTask(std::shared_ptr<Task> task) -{ - m_queue.append(task); -} - -void SequentialTask::executeTask() -{ - m_currentIndex = -1; - startNext(); -} - -void SequentialTask::startNext() -{ - if (m_currentIndex != -1) - { - std::shared_ptr<Task> previous = m_queue[m_currentIndex]; - disconnect(previous.get(), 0, this, 0); - } - m_currentIndex++; - if (m_queue.isEmpty() || m_currentIndex >= m_queue.size()) - { - emitSucceeded(); - return; - } - std::shared_ptr<Task> next = m_queue[m_currentIndex]; - connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString))); - connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); - connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); - connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext())); - next->start(); -} - -void SequentialTask::subTaskFailed(const QString &msg) -{ - emitFailed(msg); -} -void SequentialTask::subTaskStatus(const QString &msg) -{ - setStatus(msg); -} -void SequentialTask::subTaskProgress(qint64 current, qint64 total) -{ - if(total == 0) - { - setProgress(0, 100); - return; - } - setProgress(current, total); -} diff --git a/api/logic/tasks/SequentialTask.h b/api/logic/tasks/SequentialTask.h deleted file mode 100644 index 2ca77c00..00000000 --- a/api/logic/tasks/SequentialTask.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "Task.h" - -#include <QQueue> -#include <memory> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT SequentialTask : public Task -{ - Q_OBJECT -public: - explicit SequentialTask(QObject *parent = 0); - virtual ~SequentialTask() {}; - - void addTask(std::shared_ptr<Task> task); - -protected: - void executeTask(); - -private -slots: - void startNext(); - void subTaskFailed(const QString &msg); - void subTaskStatus(const QString &msg); - void subTaskProgress(qint64 current, qint64 total); - -private: - QQueue<std::shared_ptr<Task> > m_queue; - int m_currentIndex; -}; diff --git a/api/logic/tasks/Task.cpp b/api/logic/tasks/Task.cpp deleted file mode 100644 index d0ac7569..00000000 --- a/api/logic/tasks/Task.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "Task.h" - -#include <QDebug> - -Task::Task(QObject *parent) : QObject(parent) -{ -} - -void Task::setStatus(const QString &new_status) -{ - if(m_status != new_status) - { - m_status = new_status; - emit status(m_status); - } -} - -void Task::setProgress(qint64 current, qint64 total) -{ - m_progress = current; - m_progressTotal = total; - emit progress(m_progress, m_progressTotal); -} - -void Task::start() -{ - switch(m_state) - { - case State::Inactive: - { - qDebug() << "Task" << describe() << "starting for the first time"; - break; - } - case State::AbortedByUser: - { - qDebug() << "Task" << describe() << "restarting for after being aborted by user"; - break; - } - case State::Failed: - { - qDebug() << "Task" << describe() << "restarting for after failing at first"; - break; - } - case State::Succeeded: - { - qDebug() << "Task" << describe() << "restarting for after succeeding at first"; - break; - } - case State::Running: - { - qWarning() << "MultiMC tried to start task" << describe() << "while it was already running!"; - return; - } - } - // NOTE: only fall thorugh to here in end states - m_state = State::Running; - emit started(); - executeTask(); -} - -void Task::emitFailed(QString reason) -{ - // Don't fail twice. - if (!isRunning()) - { - qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; - return; - } - m_state = State::Failed; - m_failReason = reason; - qCritical() << "Task" << describe() << "failed: " << reason; - emit failed(reason); - emit finished(); -} - -void Task::emitAborted() -{ - // Don't abort twice. - if (!isRunning()) - { - qCritical() << "Task" << describe() << "aborted while not running!!!!"; - return; - } - m_state = State::AbortedByUser; - m_failReason = "Aborted."; - qDebug() << "Task" << describe() << "aborted."; - emit failed(m_failReason); - emit finished(); -} - -void Task::emitSucceeded() -{ - // Don't succeed twice. - if (!isRunning()) - { - qCritical() << "Task" << describe() << "succeeded while not running!!!!"; - return; - } - m_state = State::Succeeded; - qDebug() << "Task" << describe() << "succeeded"; - emit succeeded(); - emit finished(); -} - -QString Task::describe() -{ - QString outStr; - QTextStream out(&outStr); - out << metaObject()->className() << QChar('('); - auto name = objectName(); - if(name.isEmpty()) - { - out << QString("0x%1").arg((quintptr)this, 0, 16); - } - else - { - out << name; - } - out << QChar(')'); - out.flush(); - return outStr; -} - -bool Task::isRunning() const -{ - return m_state == State::Running; -} - -bool Task::isFinished() const -{ - return m_state != State::Running && m_state != State::Inactive; -} - -bool Task::wasSuccessful() const -{ - return m_state == State::Succeeded; -} - -QString Task::failReason() const -{ - return m_failReason; -} - -void Task::logWarning(const QString& line) -{ - qWarning() << line; - m_Warnings.append(line); -} - -QStringList Task::warnings() const -{ - return m_Warnings; -} diff --git a/api/logic/tasks/Task.h b/api/logic/tasks/Task.h deleted file mode 100644 index 7ed7086c..00000000 --- a/api/logic/tasks/Task.h +++ /dev/null @@ -1,108 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QString> -#include <QStringList> - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT Task : public QObject -{ - Q_OBJECT -public: - enum class State - { - Inactive, - Running, - Succeeded, - Failed, - AbortedByUser - }; - -public: - explicit Task(QObject *parent = 0); - virtual ~Task() {}; - - bool isRunning() const; - bool isFinished() const; - bool wasSuccessful() const; - - /*! - * Returns the string that was passed to emitFailed as the error message when the task failed. - * If the task hasn't failed, returns an empty string. - */ - QString failReason() const; - - virtual QStringList warnings() const; - - virtual bool canAbort() const { return false; } - - QString getStatus() - { - return m_status; - } - - qint64 getProgress() - { - return m_progress; - } - - qint64 getTotalProgress() - { - return m_progressTotal; - } - -protected: - void logWarning(const QString & line); - -private: - QString describe(); - -signals: - void started(); - void progress(qint64 current, qint64 total); - void finished(); - void succeeded(); - void failed(QString reason); - void status(QString status); - -public slots: - virtual void start(); - virtual bool abort() { return false; }; - -protected: - virtual void executeTask() = 0; - -protected slots: - virtual void emitSucceeded(); - virtual void emitAborted(); - virtual void emitFailed(QString reason); - -public slots: - void setStatus(const QString &status); - void setProgress(qint64 current, qint64 total); - -private: - State m_state = State::Inactive; - QStringList m_Warnings; - QString m_failReason = ""; - QString m_status; - int m_progress = 0; - int m_progressTotal = 100; -}; - diff --git a/api/logic/testdata/FileSystem-test_createShortcut-unix b/api/logic/testdata/FileSystem-test_createShortcut-unix deleted file mode 100755 index 1ce3a2bd..00000000 --- a/api/logic/testdata/FileSystem-test_createShortcut-unix +++ /dev/null @@ -1,6 +0,0 @@ -[Desktop Entry] -Type=Application -TryExec=asdfDest -Exec=asdfDest 'arg1' 'arg2' -Name=asdf -Icon= diff --git a/api/logic/testdata/test_folder/assets/minecraft/textures/blah.txt b/api/logic/testdata/test_folder/assets/minecraft/textures/blah.txt deleted file mode 100644 index 8d1c8b69..00000000 --- a/api/logic/testdata/test_folder/assets/minecraft/textures/blah.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/api/logic/testdata/test_folder/pack.mcmeta b/api/logic/testdata/test_folder/pack.mcmeta deleted file mode 100644 index 67ee0434..00000000 --- a/api/logic/testdata/test_folder/pack.mcmeta +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pack": { - "pack_format": 1, - "description": "Some resource pack maybe" - } -} diff --git a/api/logic/testdata/test_folder/pack.nfo b/api/logic/testdata/test_folder/pack.nfo deleted file mode 100644 index 8d1c8b69..00000000 --- a/api/logic/testdata/test_folder/pack.nfo +++ /dev/null @@ -1 +0,0 @@ - diff --git a/api/logic/tools/BaseExternalTool.cpp b/api/logic/tools/BaseExternalTool.cpp deleted file mode 100644 index 38d81788..00000000 --- a/api/logic/tools/BaseExternalTool.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "BaseExternalTool.h" - -#include <QProcess> -#include <QDir> - -#ifdef Q_OS_WIN -#include <windows.h> -#endif - -#include "BaseInstance.h" - -BaseExternalTool::BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : QObject(parent), m_instance(instance), globalSettings(settings) -{ -} - -BaseExternalTool::~BaseExternalTool() -{ -} - -BaseDetachedTool::BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : BaseExternalTool(settings, instance, parent) -{ - -} - -void BaseDetachedTool::run() -{ - runImpl(); -} - - -BaseExternalToolFactory::~BaseExternalToolFactory() -{ -} - -BaseDetachedTool *BaseDetachedToolFactory::createDetachedTool(InstancePtr instance, - QObject *parent) -{ - return qobject_cast<BaseDetachedTool *>(createTool(instance, parent)); -} diff --git a/api/logic/tools/BaseExternalTool.h b/api/logic/tools/BaseExternalTool.h deleted file mode 100644 index b393b9ef..00000000 --- a/api/logic/tools/BaseExternalTool.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include <QObject> -#include <BaseInstance.h> - -#include "multimc_logic_export.h" - -class BaseInstance; -class SettingsObject; -class QProcess; - -class MULTIMC_LOGIC_EXPORT BaseExternalTool : public QObject -{ - Q_OBJECT -public: - explicit BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); - virtual ~BaseExternalTool(); - -protected: - InstancePtr m_instance; - SettingsObjectPtr globalSettings; -}; - -class MULTIMC_LOGIC_EXPORT BaseDetachedTool : public BaseExternalTool -{ - Q_OBJECT -public: - explicit BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); - -public -slots: - void run(); - -protected: - virtual void runImpl() = 0; -}; - -class MULTIMC_LOGIC_EXPORT BaseExternalToolFactory -{ -public: - virtual ~BaseExternalToolFactory(); - - virtual QString name() const = 0; - - virtual void registerSettings(SettingsObjectPtr settings) = 0; - - virtual BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) = 0; - - virtual bool check(QString *error) = 0; - virtual bool check(const QString &path, QString *error) = 0; - -protected: - SettingsObjectPtr globalSettings; -}; - -class MULTIMC_LOGIC_EXPORT BaseDetachedToolFactory : public BaseExternalToolFactory -{ -public: - virtual BaseDetachedTool *createDetachedTool(InstancePtr instance, QObject *parent = 0); -}; diff --git a/api/logic/tools/BaseProfiler.cpp b/api/logic/tools/BaseProfiler.cpp deleted file mode 100644 index 300d1a73..00000000 --- a/api/logic/tools/BaseProfiler.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "BaseProfiler.h" -#include "QObjectPtr.h" - -#include <QProcess> - -BaseProfiler::BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : BaseExternalTool(settings, instance, parent) -{ -} - -void BaseProfiler::beginProfiling(shared_qobject_ptr<LaunchTask> process) -{ - beginProfilingImpl(process); -} - -void BaseProfiler::abortProfiling() -{ - abortProfilingImpl(); -} - -void BaseProfiler::abortProfilingImpl() -{ - if (!m_profilerProcess) - { - return; - } - m_profilerProcess->terminate(); - m_profilerProcess->deleteLater(); - m_profilerProcess = 0; - emit abortLaunch(tr("Profiler aborted")); -} - -BaseProfiler *BaseProfilerFactory::createProfiler(InstancePtr instance, QObject *parent) -{ - return qobject_cast<BaseProfiler *>(createTool(instance, parent)); -} diff --git a/api/logic/tools/BaseProfiler.h b/api/logic/tools/BaseProfiler.h deleted file mode 100644 index da817f52..00000000 --- a/api/logic/tools/BaseProfiler.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "BaseExternalTool.h" -#include "QObjectPtr.h" - -#include "multimc_logic_export.h" - -class BaseInstance; -class SettingsObject; -class LaunchTask; -class QProcess; - -class MULTIMC_LOGIC_EXPORT BaseProfiler : public BaseExternalTool -{ - Q_OBJECT -public: - explicit BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); - -public -slots: - void beginProfiling(shared_qobject_ptr<LaunchTask> process); - void abortProfiling(); - -protected: - QProcess *m_profilerProcess; - - virtual void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process) = 0; - virtual void abortProfilingImpl(); - -signals: - void readyToLaunch(const QString &message); - void abortLaunch(const QString &message); -}; - -class MULTIMC_LOGIC_EXPORT BaseProfilerFactory : public BaseExternalToolFactory -{ -public: - virtual BaseProfiler *createProfiler(InstancePtr instance, QObject *parent = 0); -}; diff --git a/api/logic/tools/JProfiler.cpp b/api/logic/tools/JProfiler.cpp deleted file mode 100644 index 1dc0d109..00000000 --- a/api/logic/tools/JProfiler.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "JProfiler.h" - -#include <QDir> - -#include "settings/SettingsObject.h" -#include "launch/LaunchTask.h" -#include "BaseInstance.h" - -class JProfiler : public BaseProfiler -{ - Q_OBJECT -public: - JProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); - -private slots: - void profilerStarted(); - void profilerFinished(int exit, QProcess::ExitStatus status); - -protected: - void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process); - -private: - int listeningPort = 0; -}; - -JProfiler::JProfiler(SettingsObjectPtr settings, InstancePtr instance, - QObject *parent) - : BaseProfiler(settings, instance, parent) -{ -} - -void JProfiler::profilerStarted() -{ - emit readyToLaunch(tr("Listening on port: %1").arg(listeningPort)); -} - -void JProfiler::profilerFinished(int exit, QProcess::ExitStatus status) -{ - if (status == QProcess::CrashExit) - { - emit abortLaunch(tr("Profiler aborted")); - } - if (m_profilerProcess) - { - m_profilerProcess->deleteLater(); - m_profilerProcess = 0; - } -} - -void JProfiler::beginProfilingImpl(shared_qobject_ptr<LaunchTask> process) -{ - listeningPort = globalSettings->get("JProfilerPort").toInt(); - QProcess *profiler = new QProcess(this); - QStringList profilerArgs = - { - "-d", QString::number(process->pid()), - "--gui", - "-p", QString::number(listeningPort) - }; - auto basePath = globalSettings->get("JProfilerPath").toString(); - -#ifdef Q_OS_WIN - QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable.exe"); -#else - QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable"); -#endif - - profiler->setArguments(profilerArgs); - profiler->setProgram(profilerProgram); - - connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); - connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); - - m_profilerProcess = profiler; - profiler->start(); -} - -void JProfilerFactory::registerSettings(SettingsObjectPtr settings) -{ - settings->registerSetting("JProfilerPath"); - settings->registerSetting("JProfilerPort", 42042); - globalSettings = settings; -} - -BaseExternalTool *JProfilerFactory::createTool(InstancePtr instance, QObject *parent) -{ - return new JProfiler(globalSettings, instance, parent); -} - -bool JProfilerFactory::check(QString *error) -{ - return check(globalSettings->get("JProfilerPath").toString(), error); -} - -bool JProfilerFactory::check(const QString &path, QString *error) -{ - if (path.isEmpty()) - { - *error = QObject::tr("Empty path"); - return false; - } - QDir dir(path); - if (!dir.exists()) - { - *error = QObject::tr("Path does not exist"); - return false; - } - if (!dir.exists("bin") || !(dir.exists("bin/jprofiler") || dir.exists("bin/jprofiler.exe")) || !dir.exists("bin/agent.jar")) - { - *error = QObject::tr("Invalid JProfiler install"); - return false; - } - return true; -} - -#include "JProfiler.moc" diff --git a/api/logic/tools/JProfiler.h b/api/logic/tools/JProfiler.h deleted file mode 100644 index f211ddbf..00000000 --- a/api/logic/tools/JProfiler.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "BaseProfiler.h" - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT JProfilerFactory : public BaseProfilerFactory -{ -public: - QString name() const override { return "JProfiler"; } - void registerSettings(SettingsObjectPtr settings) override; - BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; - bool check(QString *error) override; - bool check(const QString &path, QString *error) override; -}; diff --git a/api/logic/tools/JVisualVM.cpp b/api/logic/tools/JVisualVM.cpp deleted file mode 100644 index b1acc3c0..00000000 --- a/api/logic/tools/JVisualVM.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "JVisualVM.h" - -#include <QDir> -#include <QStandardPaths> - -#include "settings/SettingsObject.h" -#include "launch/LaunchTask.h" -#include "BaseInstance.h" - -class JVisualVM : public BaseProfiler -{ - Q_OBJECT -public: - JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); - -private slots: - void profilerStarted(); - void profilerFinished(int exit, QProcess::ExitStatus status); - -protected: - void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process); -}; - - -JVisualVM::JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : BaseProfiler(settings, instance, parent) -{ -} - -void JVisualVM::profilerStarted() -{ - emit readyToLaunch(tr("JVisualVM started")); -} - -void JVisualVM::profilerFinished(int exit, QProcess::ExitStatus status) -{ - if (status == QProcess::CrashExit) - { - emit abortLaunch(tr("Profiler aborted")); - } - if (m_profilerProcess) - { - m_profilerProcess->deleteLater(); - m_profilerProcess = 0; - } -} - -void JVisualVM::beginProfilingImpl(shared_qobject_ptr<LaunchTask> process) -{ - QProcess *profiler = new QProcess(this); - QStringList profilerArgs = - { - "--openpid", QString::number(process->pid()) - }; - auto programPath = globalSettings->get("JVisualVMPath").toString(); - - profiler->setArguments(profilerArgs); - profiler->setProgram(programPath); - - connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); - connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); - - profiler->start(); - m_profilerProcess = profiler; -} - -void JVisualVMFactory::registerSettings(SettingsObjectPtr settings) -{ - QString defaultValue = QStandardPaths::findExecutable("jvisualvm"); - if (defaultValue.isNull()) - { - defaultValue = QStandardPaths::findExecutable("visualvm"); - } - settings->registerSetting("JVisualVMPath", defaultValue); - globalSettings = settings; -} - -BaseExternalTool *JVisualVMFactory::createTool(InstancePtr instance, QObject *parent) -{ - return new JVisualVM(globalSettings, instance, parent); -} - -bool JVisualVMFactory::check(QString *error) -{ - return check(globalSettings->get("JVisualVMPath").toString(), error); -} - -bool JVisualVMFactory::check(const QString &path, QString *error) -{ - if (path.isEmpty()) - { - *error = QObject::tr("Empty path"); - return false; - } - QFileInfo finfo(path); - if (!finfo.isExecutable() || !finfo.fileName().contains("visualvm")) - { - *error = QObject::tr("Invalid path to JVisualVM"); - return false; - } - return true; -} - -#include "JVisualVM.moc" diff --git a/api/logic/tools/JVisualVM.h b/api/logic/tools/JVisualVM.h deleted file mode 100644 index 91d48d94..00000000 --- a/api/logic/tools/JVisualVM.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "BaseProfiler.h" - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT JVisualVMFactory : public BaseProfilerFactory -{ -public: - QString name() const override { return "JVisualVM"; } - void registerSettings(SettingsObjectPtr settings) override; - BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; - bool check(QString *error) override; - bool check(const QString &path, QString *error) override; -}; diff --git a/api/logic/tools/MCEditTool.cpp b/api/logic/tools/MCEditTool.cpp deleted file mode 100644 index 880327c7..00000000 --- a/api/logic/tools/MCEditTool.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "MCEditTool.h" - -#include <QDir> -#include <QProcess> -#include <QUrl> - -#include "settings/SettingsObject.h" -#include "BaseInstance.h" -#include "minecraft/MinecraftInstance.h" - -MCEditTool::MCEditTool(SettingsObjectPtr settings) -{ - settings->registerSetting("MCEditPath"); - m_settings = settings; -} - -void MCEditTool::setPath(QString& path) -{ - m_settings->set("MCEditPath", path); -} - -QString MCEditTool::path() const -{ - return m_settings->get("MCEditPath").toString(); -} - -bool MCEditTool::check(const QString& toolPath, QString& error) -{ - if (toolPath.isEmpty()) - { - error = QObject::tr("Path is empty"); - return false; - } - const QDir dir(toolPath); - if (!dir.exists()) - { - error = QObject::tr("Path does not exist"); - return false; - } - if (!dir.exists("mcedit.sh") && !dir.exists("mcedit.py") && !dir.exists("mcedit.exe") && !dir.exists("Contents") && !dir.exists("mcedit2.exe")) - { - error = QObject::tr("Path does not seem to be a MCEdit path"); - return false; - } - return true; -} - -QString MCEditTool::getProgramPath() -{ -#ifdef Q_OS_OSX - return path(); -#else - const QString mceditPath = path(); - QDir mceditDir(mceditPath); -#ifdef Q_OS_LINUX - if (mceditDir.exists("mcedit.sh")) - { - return mceditDir.absoluteFilePath("mcedit.sh"); - } - else if (mceditDir.exists("mcedit.py")) - { - return mceditDir.absoluteFilePath("mcedit.py"); - } - return QString(); -#elif defined(Q_OS_WIN32) - if (mceditDir.exists("mcedit.exe")) - { - return mceditDir.absoluteFilePath("mcedit.exe"); - } - else if (mceditDir.exists("mcedit2.exe")) - { - return mceditDir.absoluteFilePath("mcedit2.exe"); - } - return QString(); -#endif -#endif -} diff --git a/api/logic/tools/MCEditTool.h b/api/logic/tools/MCEditTool.h deleted file mode 100644 index 1465494e..00000000 --- a/api/logic/tools/MCEditTool.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include <QString> -#include "settings/SettingsObject.h" -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT MCEditTool -{ -public: - MCEditTool(SettingsObjectPtr settings); - void setPath(QString & path); - QString path() const; - bool check(const QString &toolPath, QString &error); - QString getProgramPath(); -private: - SettingsObjectPtr m_settings; -}; diff --git a/api/logic/translations/POTranslator.cpp b/api/logic/translations/POTranslator.cpp deleted file mode 100644 index 1ffcb9a4..00000000 --- a/api/logic/translations/POTranslator.cpp +++ /dev/null @@ -1,373 +0,0 @@ -#include "POTranslator.h" - -#include <QDebug> -#include "FileSystem.h" - -struct POEntry -{ - QString text; - bool fuzzy; -}; - -struct POTranslatorPrivate -{ - QString filename; - QHash<QByteArray, POEntry> mapping; - QHash<QByteArray, POEntry> mapping_disambiguatrion; - bool loaded = false; - - void reload(); -}; - -class ParserArray : public QByteArray -{ -public: - ParserArray(const QByteArray &in) : QByteArray(in) - { - } - bool chomp(const char * data, int length) - { - if(startsWith(data)) - { - remove(0, length); - return true; - } - return false; - } - bool chompString(QByteArray & appendHere) - { - QByteArray msg; - bool escape = false; - if(size() < 2) - { - qDebug() << "String fragment is too short"; - return false; - } - if(!startsWith('"')) - { - qDebug() << "String fragment does not start with \""; - return false; - } - if(!endsWith('"')) - { - qDebug() << "String fragment does not end with \", instead, there is" << at(size() - 1); - return false; - } - for(int i = 1; i < size() - 1; i++) - { - char c = operator[](i); - if(escape) - { - switch(c) - { - case 'r': - msg += '\r'; - break; - case 'n': - msg += '\n'; - break; - case 't': - msg += '\t'; - break; - case 'v': - msg += '\v'; - break; - case 'a': - msg += '\a'; - break; - case 'b': - msg += '\b'; - break; - case 'f': - msg += '\f'; - break; - case '"': - msg += '"'; - break; - case '\\': - msg.append('\\'); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - { - int octal_start = i; - while ((c = operator[](i)) >= '0' && c <= '7') - { - i++; - if (i == length() - 1) - { - qDebug() << "Something went bad while parsing an octal escape string..."; - return false; - } - } - msg += mid(octal_start, i - octal_start).toUInt(0, 8); - break; - } - case 'x': - { - // chomp the 'x' - i++; - int hex_start = i; - while (isxdigit(operator[](i))) - { - i++; - if (i == length() - 1) - { - qDebug() << "Something went bad while parsing a hex escape string..."; - return false; - } - } - msg += mid(hex_start, i - hex_start).toUInt(0, 16); - break; - } - default: - { - qDebug() << "Invalid escape sequence character:" << c; - return false; - } - } - escape = false; - } - else if(c == '\\') - { - escape = true; - } - else - { - msg += c; - } - } - if(escape) - { - qDebug() << "Unterminated escape sequence..."; - return false; - } - appendHere += msg; - return true; - } -}; - -void POTranslatorPrivate::reload() -{ - QFile file(filename); - if(!file.open(QFile::OpenMode::enum_type::ReadOnly | QFile::OpenMode::enum_type::Text)) - { - qDebug() << "Failed to open PO file:" << filename; - return; - } - - QByteArray context; - QByteArray disambiguation; - QByteArray id; - QByteArray str; - bool fuzzy = false; - bool nextFuzzy = false; - - enum class Mode - { - First, - MessageContext, - MessageId, - MessageString - } mode = Mode::First; - - int lineNumber = 0; - QHash<QByteArray, POEntry> newMapping; - QHash<QByteArray, POEntry> newMapping_disambiguation; - auto endEntry = [&]() { - auto strStr = QString::fromUtf8(str); - // NOTE: PO header has empty id. We skip it. - if(!id.isEmpty()) - { - auto normalKey = context + "|" + id; - newMapping.insert(normalKey, {strStr, fuzzy}); - if(!disambiguation.isEmpty()) - { - auto disambiguationKey = context + "|" + id + "@" + disambiguation; - newMapping_disambiguation.insert(disambiguationKey, {strStr, fuzzy}); - } - } - context.clear(); - disambiguation.clear(); - id.clear(); - str.clear(); - fuzzy = nextFuzzy; - nextFuzzy = false; - }; - while (!file.atEnd()) - { - ParserArray line = file.readLine(); - if(line.endsWith('\n')) - { - line.resize(line.size() - 1); - } - if(line.endsWith('\r')) - { - line.resize(line.size() - 1); - } - - if(!line.size()) - { - // NIL - } - else if(line[0] == '#') - { - if(line.contains(", fuzzy")) - { - nextFuzzy = true; - } - } - else if(line.startsWith('"')) - { - QByteArray temp; - QByteArray *out = &temp; - - switch(mode) - { - case Mode::First: - qDebug() << "Unexpected escaped string during initial state... line:" << lineNumber; - return; - case Mode::MessageString: - out = &str; - break; - case Mode::MessageContext: - out = &context; - break; - case Mode::MessageId: - out = &id; - break; - } - if(!line.chompString(*out)) - { - qDebug() << "Badly formatted string on line:" << lineNumber; - return; - } - } - else if(line.chomp("msgctxt ", 8)) - { - switch(mode) - { - case Mode::First: - break; - case Mode::MessageString: - endEntry(); - break; - case Mode::MessageContext: - case Mode::MessageId: - qDebug() << "Unexpected msgctxt line:" << lineNumber; - return; - } - if(line.chompString(context)) - { - auto parts = context.split('|'); - context = parts[0]; - if(parts.size() > 1 && !parts[1].isEmpty()) - { - disambiguation = parts[1]; - } - mode = Mode::MessageContext; - } - } - else if (line.chomp("msgid ", 6)) - { - switch(mode) - { - case Mode::MessageContext: - case Mode::First: - break; - case Mode::MessageString: - endEntry(); - break; - case Mode::MessageId: - qDebug() << "Unexpected msgid line:" << lineNumber; - return; - } - if(line.chompString(id)) - { - mode = Mode::MessageId; - } - } - else if (line.chomp("msgstr ", 7)) - { - switch(mode) - { - case Mode::First: - case Mode::MessageString: - case Mode::MessageContext: - qDebug() << "Unexpected msgstr line:" << lineNumber; - return; - case Mode::MessageId: - break; - } - if(line.chompString(str)) - { - mode = Mode::MessageString; - } - } - else - { - qDebug() << "I did not understand line: " << lineNumber << ":" << QString::fromUtf8(line); - } - lineNumber++; - } - endEntry(); - mapping = std::move(newMapping); - mapping_disambiguatrion = std::move(newMapping_disambiguation); - loaded = true; -} - -POTranslator::POTranslator(const QString& filename, QObject* parent) : QTranslator(parent) -{ - d = new POTranslatorPrivate; - d->filename = filename; - d->reload(); -} - -QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, int n) const -{ - if(disambiguation) - { - auto disambiguationKey = QByteArray(context) + "|" + QByteArray(sourceText) + "@" + QByteArray(disambiguation); - auto iter = d->mapping_disambiguatrion.find(disambiguationKey); - if(iter != d->mapping_disambiguatrion.end()) - { - auto & entry = *iter; - if(entry.text.isEmpty()) - { - qDebug() << "Translation entry has no content:" << disambiguationKey; - } - if(entry.fuzzy) - { - qDebug() << "Translation entry is fuzzy:" << disambiguationKey << "->" << entry.text; - } - return entry.text; - } - } - auto key = QByteArray(context) + "|" + QByteArray(sourceText); - auto iter = d->mapping.find(key); - if(iter != d->mapping.end()) - { - auto & entry = *iter; - if(entry.text.isEmpty()) - { - qDebug() << "Translation entry has no content:" << key; - } - if(entry.fuzzy) - { - qDebug() << "Translation entry is fuzzy:" << key << "->" << entry.text; - } - return entry.text; - } - return QString(); -} - -bool POTranslator::isEmpty() const -{ - return !d->loaded; -} diff --git a/api/logic/translations/POTranslator.h b/api/logic/translations/POTranslator.h deleted file mode 100644 index 6d518560..00000000 --- a/api/logic/translations/POTranslator.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include <QTranslator> - -struct POTranslatorPrivate; - -class POTranslator : public QTranslator -{ - Q_OBJECT -public: - explicit POTranslator(const QString& filename, QObject * parent = nullptr); - QString translate(const char * context, const char * sourceText, const char * disambiguation, int n) const override; - bool isEmpty() const override; -private: - POTranslatorPrivate * d; -}; diff --git a/api/logic/translations/TranslationsModel.cpp b/api/logic/translations/TranslationsModel.cpp deleted file mode 100644 index 29a952b0..00000000 --- a/api/logic/translations/TranslationsModel.cpp +++ /dev/null @@ -1,653 +0,0 @@ -#include "TranslationsModel.h" - -#include <QCoreApplication> -#include <QTranslator> -#include <QLocale> -#include <QDir> -#include <QLibraryInfo> -#include <QDebug> -#include <FileSystem.h> -#include <net/NetJob.h> -#include <net/ChecksumValidator.h> -#include <Env.h> -#include <BuildConfig.h> -#include "Json.h" - -#include "POTranslator.h" - -const static QLatin1Literal defaultLangCode("en_US"); - -enum class FileType -{ - NONE, - QM, - PO -}; - -struct Language -{ - Language() - { - updated = true; - } - Language(const QString & _key) - { - key = _key; - locale = QLocale(key); - updated = (key == defaultLangCode); - } - - float percentTranslated() const - { - if (total == 0) - { - return 100.0f; - } - return 100.0f * float(translated) / float(total); - } - - void setTranslationStats(unsigned _translated, unsigned _untranslated, unsigned _fuzzy) - { - translated = _translated; - untranslated = _untranslated; - fuzzy = _fuzzy; - total = translated + untranslated + fuzzy; - } - - bool isOfSameNameAs(const Language& other) const - { - return key == other.key; - } - - bool isIdenticalTo(const Language& other) const - { - return - ( - key == other.key && - file_name == other.file_name && - file_size == other.file_size && - file_sha1 == other.file_sha1 && - translated == other.translated && - fuzzy == other.fuzzy && - total == other.fuzzy && - localFileType == other.localFileType - ); - } - - Language & apply(Language & other) - { - if(!isOfSameNameAs(other)) - { - return *this; - } - file_name = other.file_name; - file_size = other.file_size; - file_sha1 = other.file_sha1; - translated = other.translated; - fuzzy = other.fuzzy; - total = other.total; - localFileType = other.localFileType; - return *this; - } - - QString key; - QLocale locale; - bool updated; - - QString file_name = QString(); - std::size_t file_size = 0; - QString file_sha1 = QString(); - - unsigned translated = 0; - unsigned untranslated = 0; - unsigned fuzzy = 0; - unsigned total = 0; - - FileType localFileType = FileType::NONE; -}; - - - -struct TranslationsModel::Private -{ - QDir m_dir; - - // initial state is just english - QVector<Language> m_languages = {Language (defaultLangCode)}; - - QString m_selectedLanguage = defaultLangCode; - std::unique_ptr<QTranslator> m_qt_translator; - std::unique_ptr<QTranslator> m_app_translator; - - std::shared_ptr<Net::Download> m_index_task; - QString m_downloadingTranslation; - NetJobPtr m_dl_job; - NetJobPtr m_index_job; - QString m_nextDownload; - - std::unique_ptr<POTranslator> m_po_translator; - QFileSystemWatcher *watcher; -}; - -TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractListModel(parent) -{ - d.reset(new Private); - d->m_dir.setPath(path); - FS::ensureFolderPathExists(path); - reloadLocalFiles(); - - d->watcher = new QFileSystemWatcher(this); - connect(d->watcher, &QFileSystemWatcher::directoryChanged, this, &TranslationsModel::translationDirChanged); - d->watcher->addPath(d->m_dir.canonicalPath()); -} - -TranslationsModel::~TranslationsModel() -{ -} - -void TranslationsModel::translationDirChanged(const QString& path) -{ - qDebug() << "Dir changed:" << path; - reloadLocalFiles(); - selectLanguage(selectedLanguage()); -} - -void TranslationsModel::indexReceived() -{ - qDebug() << "Got translations index!"; - d->m_index_job.reset(); - if(d->m_selectedLanguage != defaultLangCode) - { - downloadTranslation(d->m_selectedLanguage); - } -} - -namespace { -void readIndex(const QString & path, QMap<QString, Language>& languages) -{ - QByteArray data; - try - { - data = FS::read(path); - } - catch (const Exception &e) - { - qCritical() << "Translations Download Failed: index file not readable"; - return; - } - - int index = 1; - try - { - auto toplevel_doc = Json::requireDocument(data); - auto doc = Json::requireObject(toplevel_doc); - auto file_type = Json::requireString(doc, "file_type"); - if(file_type != "MMC-TRANSLATION-INDEX") - { - qCritical() << "Translations Download Failed: index file is of unknown file type" << file_type; - return; - } - auto version = Json::requireInteger(doc, "version"); - if(version > 2) - { - qCritical() << "Translations Download Failed: index file is of unknown format version" << file_type; - return; - } - auto langObjs = Json::requireObject(doc, "languages"); - for(auto iter = langObjs.begin(); iter != langObjs.end(); iter++) - { - Language lang(iter.key()); - - auto langObj = Json::requireObject(iter.value()); - lang.setTranslationStats( - Json::ensureInteger(langObj, "translated", 0), - Json::ensureInteger(langObj, "untranslated", 0), - Json::ensureInteger(langObj, "fuzzy", 0) - ); - lang.file_name = Json::requireString(langObj, "file"); - lang.file_sha1 = Json::requireString(langObj, "sha1"); - lang.file_size = Json::requireInteger(langObj, "size"); - - languages.insert(lang.key, lang); - index++; - } - } - catch (Json::JsonException & e) - { - qCritical() << "Translations Download Failed: index file could not be parsed as json"; - } -} -} - -void TranslationsModel::reloadLocalFiles() -{ - QMap<QString, Language> languages = {{defaultLangCode, Language(defaultLangCode)}}; - - readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages); - auto entries = d->m_dir.entryInfoList({"mmc_*.qm", "*.po"}, QDir::Files | QDir::NoDotAndDotDot); - for(auto & entry: entries) - { - auto completeSuffix = entry.completeSuffix(); - QString langCode; - FileType fileType = FileType::NONE; - if(completeSuffix == "qm") - { - langCode = entry.baseName().remove(0,4); - fileType = FileType::QM; - } - else if(completeSuffix == "po") - { - langCode = entry.baseName(); - fileType = FileType::PO; - } - else - { - continue; - } - - auto langIter = languages.find(langCode); - if(langIter != languages.end()) - { - auto & language = *langIter; - if(int(fileType) > int(language.localFileType)) - { - language.localFileType = fileType; - } - } - else - { - if(fileType == FileType::PO) - { - Language localFound(langCode); - localFound.localFileType = FileType::PO; - languages.insert(langCode, localFound); - } - } - } - - // changed and removed languages - for(auto iter = d->m_languages.begin(); iter != d->m_languages.end();) - { - auto &language = *iter; - auto row = iter - d->m_languages.begin(); - - auto updatedLanguageIter = languages.find(language.key); - if(updatedLanguageIter != languages.end()) - { - if(language.isIdenticalTo(*updatedLanguageIter)) - { - languages.remove(language.key); - } - else - { - language.apply(*updatedLanguageIter); - emit dataChanged(index(row), index(row)); - languages.remove(language.key); - } - iter++; - } - else - { - beginRemoveRows(QModelIndex(), row, row); - iter = d->m_languages.erase(iter); - endRemoveRows(); - } - } - // added languages - if(languages.isEmpty()) - { - return; - } - beginInsertRows(QModelIndex(), 0, d->m_languages.size() + languages.size() - 1); - for(auto & language: languages) - { - d->m_languages.append(language); - } - std::sort(d->m_languages.begin(), d->m_languages.end(), [](const Language& a, const Language& b) { - return a.key.compare(b.key) < 0; - }); - endInsertRows(); -} - -namespace { -enum class Column -{ - Language, - Completeness -}; -} - - -QVariant TranslationsModel::data(const QModelIndex& index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - auto column = static_cast<Column>(index.column()); - - if (row < 0 || row >= d->m_languages.size()) - return QVariant(); - - auto & lang = d->m_languages[row]; - switch (role) - { - case Qt::DisplayRole: - { - switch(column) - { - case Column::Language: - { - return lang.locale.nativeLanguageName(); - } - case Column::Completeness: - { - QString text; - text.sprintf("%3.1f %%", lang.percentTranslated()); - return text; - } - } - } - case Qt::ToolTipRole: - { - return tr("%1:\n%2 translated\n%3 fuzzy\n%4 total").arg(lang.key, QString::number(lang.translated), QString::number(lang.fuzzy), QString::number(lang.total)); - } - case Qt::UserRole: - return lang.key; - default: - return QVariant(); - } -} - -QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - auto column = static_cast<Column>(section); - if(role == Qt::DisplayRole) - { - switch(column) - { - case Column::Language: - { - return tr("Language"); - } - case Column::Completeness: - { - return tr("Completeness"); - } - } - } - else if(role == Qt::ToolTipRole) - { - switch(column) - { - case Column::Language: - { - return tr("The native language name."); - } - case Column::Completeness: - { - return tr("Completeness is the percentage of fully translated strings, not counting automatically guessed ones."); - } - } - } - return QAbstractListModel::headerData(section, orientation, role); -} - -int TranslationsModel::rowCount(const QModelIndex& parent) const -{ - return d->m_languages.size(); -} - -int TranslationsModel::columnCount(const QModelIndex& parent) const -{ - return 2; -} - -Language * TranslationsModel::findLanguage(const QString& key) -{ - auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language & lang) - { - return lang.key == key; - }); - if(found == d->m_languages.end()) - { - return nullptr; - } - else - { - return found; - } -} - -bool TranslationsModel::selectLanguage(QString key) -{ - QString &langCode = key; - auto langPtr = findLanguage(key); - if(!langPtr) - { - qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; - langCode = defaultLangCode; - } - else - { - langCode = langPtr->key; - } - - // uninstall existing translators if there are any - if (d->m_app_translator) - { - QCoreApplication::removeTranslator(d->m_app_translator.get()); - d->m_app_translator.reset(); - } - if (d->m_qt_translator) - { - QCoreApplication::removeTranslator(d->m_qt_translator.get()); - d->m_qt_translator.reset(); - } - - /* - * FIXME: potential source of crashes: - * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. - * This function is not reentrant. - */ - QLocale locale = QLocale(langCode); - QLocale::setDefault(locale); - - // if it's the default UI language, finish - if(langCode == defaultLangCode) - { - d->m_selectedLanguage = langCode; - return true; - } - - // otherwise install new translations - bool successful = false; - // FIXME: this is likely never present. FIX IT. - d->m_qt_translator.reset(new QTranslator()); - if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) - { - qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) - { - qCritical() << "Loading Qt Language File failed."; - d->m_qt_translator.reset(); - } - else - { - successful = true; - } - } - else - { - d->m_qt_translator.reset(); - } - - if(langPtr->localFileType == FileType::PO) - { - qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po")); - if(!poTranslator->isEmpty()) - { - if (!QCoreApplication::installTranslator(poTranslator)) - { - delete poTranslator; - qCritical() << "Installing Application Language File failed."; - } - else - { - d->m_app_translator.reset(poTranslator); - successful = true; - } - } - else - { - qCritical() << "Loading Application Language File failed."; - d->m_app_translator.reset(); - } - } - else if(langPtr->localFileType == FileType::QM) - { - d->m_app_translator.reset(new QTranslator()); - if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) - { - qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_app_translator.get())) - { - qCritical() << "Installing Application Language File failed."; - d->m_app_translator.reset(); - } - else - { - successful = true; - } - } - else - { - d->m_app_translator.reset(); - } - } - else - { - d->m_app_translator.reset(); - } - d->m_selectedLanguage = langCode; - return successful; -} - -QModelIndex TranslationsModel::selectedIndex() -{ - auto found = findLanguage(d->m_selectedLanguage); - if(found) - { - // QVector iterator freely converts to pointer to contained type - return index(found - d->m_languages.begin(), 0, QModelIndex()); - } - return QModelIndex(); -} - -QString TranslationsModel::selectedLanguage() -{ - return d->m_selectedLanguage; -} - -void TranslationsModel::downloadIndex() -{ - if(d->m_index_job || d->m_dl_job) - { - return; - } - qDebug() << "Downloading Translations Index..."; - d->m_index_job.reset(new NetJob("Translations Index")); - MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "index_v2.json"); - entry->setStale(true); - d->m_index_task = Net::Download::makeCached(QUrl("https://files.multimc.org/translations/index_v2.json"), entry); - d->m_index_job->addNetAction(d->m_index_task); - connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); - connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); - d->m_index_job->start(); -} - -void TranslationsModel::updateLanguage(QString key) -{ - if(key == defaultLangCode) - { - qWarning() << "Cannot update builtin language" << key; - return; - } - auto found = findLanguage(key); - if(!found) - { - qWarning() << "Cannot update invalid language" << key; - return; - } - if(!found->updated) - { - downloadTranslation(key); - } -} - -void TranslationsModel::downloadTranslation(QString key) -{ - if(d->m_dl_job) - { - d->m_nextDownload = key; - return; - } - auto lang = findLanguage(key); - if(!lang) - { - qWarning() << "Will not download an unknown translation" << key; - return; - } - - d->m_downloadingTranslation = key; - MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); - entry->setStale(true); - - auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + lang->file_name), entry); - auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); - dl->m_total_progress = lang->file_size; - - d->m_dl_job.reset(new NetJob("Translation for " + key)); - d->m_dl_job->addNetAction(dl); - - connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); - connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); - - d->m_dl_job->start(); -} - -void TranslationsModel::downloadNext() -{ - if(!d->m_nextDownload.isEmpty()) - { - downloadTranslation(d->m_nextDownload); - d->m_nextDownload.clear(); - } -} - -void TranslationsModel::dlFailed(QString reason) -{ - qCritical() << "Translations Download Failed:" << reason; - d->m_dl_job.reset(); - downloadNext(); -} - -void TranslationsModel::dlGood() -{ - qDebug() << "Got translation:" << d->m_downloadingTranslation; - - if(d->m_downloadingTranslation == d->m_selectedLanguage) - { - selectLanguage(d->m_selectedLanguage); - } - d->m_dl_job.reset(); - downloadNext(); -} - -void TranslationsModel::indexFailed(QString reason) -{ - qCritical() << "Translations Index Download Failed:" << reason; - d->m_index_job.reset(); -} diff --git a/api/logic/translations/TranslationsModel.h b/api/logic/translations/TranslationsModel.h deleted file mode 100644 index 17e5b124..00000000 --- a/api/logic/translations/TranslationsModel.h +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 <QAbstractListModel> -#include <memory> -#include "multimc_logic_export.h" - -struct Language; - -class MULTIMC_LOGIC_EXPORT TranslationsModel : public QAbstractListModel -{ - Q_OBJECT -public: - explicit TranslationsModel(QString path, QObject *parent = 0); - virtual ~TranslationsModel(); - - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & parent) const override; - - bool selectLanguage(QString key); - void updateLanguage(QString key); - QModelIndex selectedIndex(); - QString selectedLanguage(); - - void downloadIndex(); - -private: - Language *findLanguage(const QString & key); - void reloadLocalFiles(); - void downloadTranslation(QString key); - void downloadNext(); - - // hide copy constructor - TranslationsModel(const TranslationsModel &) = delete; - // hide assign op - TranslationsModel &operator=(const TranslationsModel &) = delete; - -private slots: - void indexReceived(); - void indexFailed(QString reason); - void dlFailed(QString reason); - void dlGood(); - void translationDirChanged(const QString &path); - - -private: /* data */ - struct Private; - std::unique_ptr<Private> d; -}; diff --git a/api/logic/updater/DownloadTask.cpp b/api/logic/updater/DownloadTask.cpp deleted file mode 100644 index 20b26ebb..00000000 --- a/api/logic/updater/DownloadTask.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "DownloadTask.h" - -#include "updater/UpdateChecker.h" -#include "GoUpdate.h" -#include "net/NetJob.h" - -#include <QFile> -#include <QTemporaryDir> -#include <QCryptographicHash> - -namespace GoUpdate -{ - -DownloadTask::DownloadTask(Status status, QString target, QObject *parent) - : Task(parent), m_updateFilesDir(target) -{ - m_status = status; - - m_updateFilesDir.setAutoRemove(false); -} - -void DownloadTask::executeTask() -{ - loadVersionInfo(); -} - -void DownloadTask::loadVersionInfo() -{ - setStatus(tr("Loading version information...")); - - NetJob *netJob = new NetJob("Version Info"); - - // Find the index URL. - QUrl newIndexUrl = QUrl(m_status.newRepoUrl).resolved(QString::number(m_status.newVersionId) + ".json"); - qDebug() << m_status.newRepoUrl << " turns into " << newIndexUrl; - - netJob->addNetAction(m_newVersionFileListDownload = Net::Download::makeByteArray(newIndexUrl, &newVersionFileListData)); - - // If we have a current version URL, get that one too. - if (!m_status.currentRepoUrl.isEmpty()) - { - QUrl cIndexUrl = QUrl(m_status.currentRepoUrl).resolved(QString::number(m_status.currentVersionId) + ".json"); - netJob->addNetAction(m_currentVersionFileListDownload = Net::Download::makeByteArray(cIndexUrl, ¤tVersionFileListData)); - qDebug() << m_status.currentRepoUrl << " turns into " << cIndexUrl; - } - - // connect signals and start the job - connect(netJob, &NetJob::succeeded, this, &DownloadTask::processDownloadedVersionInfo); - connect(netJob, &NetJob::failed, this, &DownloadTask::vinfoDownloadFailed); - m_vinfoNetJob.reset(netJob); - netJob->start(); -} - -void DownloadTask::vinfoDownloadFailed() -{ - // Something failed. We really need the second download (current version info), so parse - // downloads anyways as long as the first one succeeded. - if (m_newVersionFileListDownload->wasSuccessful()) - { - processDownloadedVersionInfo(); - return; - } - - // TODO: Give a more detailed error message. - qCritical() << "Failed to download version info files."; - emitFailed(tr("Failed to download version info files.")); -} - -void DownloadTask::processDownloadedVersionInfo() -{ - VersionFileList m_currentVersionFileList; - VersionFileList m_newVersionFileList; - - setStatus(tr("Reading file list for new version...")); - qDebug() << "Reading file list for new version..."; - QString error; - if (!parseVersionInfo(newVersionFileListData, m_newVersionFileList, error)) - { - qCritical() << error; - emitFailed(error); - return; - } - - // if we have the current version info, use it. - if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->wasSuccessful()) - { - setStatus(tr("Reading file list for current version...")); - qDebug() << "Reading file list for current version..."; - // if this fails, it's not a complete loss. - QString error; - if(!parseVersionInfo( currentVersionFileListData, m_currentVersionFileList, error)) - { - qDebug() << error << "This is not a fatal error."; - } - } - - // We don't need this any more. - m_currentVersionFileListDownload.reset(); - m_newVersionFileListDownload.reset(); - m_vinfoNetJob.reset(); - - setStatus(tr("Processing file lists - figuring out how to install the update...")); - - // make a new netjob for the actual update files - NetJobPtr netJob (new NetJob("Update Files")); - - // fill netJob and operationList - if (!processFileLists(m_currentVersionFileList, m_newVersionFileList, m_status.rootPath, m_updateFilesDir.path(), netJob, m_operations)) - { - emitFailed(tr("Failed to process update lists...")); - return; - } - - // Now start the download. - QObject::connect(netJob.get(), &NetJob::succeeded, this, &DownloadTask::fileDownloadFinished); - QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged); - QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed); - - if(netJob->size() == 1) // Translation issues... see https://github.com/MultiMC/MultiMC5/issues/1701 - { - setStatus(tr("Downloading one update file.")); - } - else - { - setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size()))); - } - qDebug() << "Begin downloading update files to" << m_updateFilesDir.path(); - m_filesNetJob = netJob; - m_filesNetJob->start(); -} - -void DownloadTask::fileDownloadFinished() -{ - emitSucceeded(); -} - -void DownloadTask::fileDownloadFailed(QString reason) -{ - qCritical() << "Failed to download update files:" << reason; - emitFailed(tr("Failed to download update files: %1").arg(reason)); -} - -void DownloadTask::fileDownloadProgressChanged(qint64 current, qint64 total) -{ - setProgress(current, total); -} - -QString DownloadTask::updateFilesDir() -{ - return m_updateFilesDir.path(); -} - -OperationList DownloadTask::operations() -{ - return m_operations; -} - -}
\ No newline at end of file diff --git a/api/logic/updater/DownloadTask.h b/api/logic/updater/DownloadTask.h deleted file mode 100644 index 88e60865..00000000 --- a/api/logic/updater/DownloadTask.h +++ /dev/null @@ -1,98 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "tasks/Task.h" -#include "net/NetJob.h" -#include "GoUpdate.h" - -#include "multimc_logic_export.h" - -namespace GoUpdate -{ -/*! - * The DownloadTask is a task that takes a given version ID and repository URL, - * downloads that version's files from the repository, and prepares to install them. - */ -class MULTIMC_LOGIC_EXPORT DownloadTask : public Task -{ - Q_OBJECT - -public: - /** - * Create a download task - * - * target is a template - XXXXXX at the end will be replaced with a random generated string, ensuring uniqueness - */ - explicit DownloadTask(Status status, QString target, QObject* parent = 0); - virtual ~DownloadTask() {}; - - /// Get the directory that will contain the update files. - QString updateFilesDir(); - - /// Get the list of operations that should be done - OperationList operations(); - - /// set updater download behavior - void setUseLocalUpdater(bool useLocal); - -protected: - //! Entry point for tasks. - virtual void executeTask() override; - - /*! - * Downloads the version info files from the repository. - * The files for both the current build, and the build that we're updating to need to be downloaded. - * If the current version's info file can't be found, MultiMC will not delete files that - * were removed between versions. It will still replace files that have changed, however. - * Note that although the repository URL for the current version is not given to the update task, - * the task will attempt to look it up in the UpdateChecker's channel list. - * If an error occurs here, the function will call emitFailed and return false. - */ - void loadVersionInfo(); - - NetJobPtr m_vinfoNetJob; - QByteArray currentVersionFileListData; - QByteArray newVersionFileListData; - Net::Download::Ptr m_currentVersionFileListDownload; - Net::Download::Ptr m_newVersionFileListDownload; - - NetJobPtr m_filesNetJob; - - Status m_status; - - OperationList m_operations; - - /*! - * Temporary directory to store update files in. - * This will be set to not auto delete. Task will fail if this fails to be created. - */ - QTemporaryDir m_updateFilesDir; - -protected slots: - /*! - * This function is called when version information is finished downloading - * and at least the new file list download succeeded - */ - void processDownloadedVersionInfo(); - void vinfoDownloadFailed(); - - void fileDownloadFinished(); - void fileDownloadFailed(QString reason); - void fileDownloadProgressChanged(qint64 current, qint64 total); -}; - -}
\ No newline at end of file diff --git a/api/logic/updater/DownloadTask_test.cpp b/api/logic/updater/DownloadTask_test.cpp deleted file mode 100644 index 8d5375e8..00000000 --- a/api/logic/updater/DownloadTask_test.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include <QTest> -#include <QSignalSpy> - -#include "TestUtil.h" - -#include "updater/GoUpdate.h" -#include "updater/DownloadTask.h" -#include "updater/UpdateChecker.h" -#include <FileSystem.h> - -using namespace GoUpdate; - -FileSourceList encodeBaseFile(const char *suffix) -{ - auto base = QDir::currentPath(); - QUrl localFile = QUrl::fromLocalFile(base + suffix); - QString localUrlString = localFile.toString(QUrl::FullyEncoded); - auto item = FileSource("http", localUrlString); - return FileSourceList({item}); -} - -Q_DECLARE_METATYPE(VersionFileList) -Q_DECLARE_METATYPE(Operation) - -QDebug operator<<(QDebug dbg, const FileSource &f) -{ - dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url - << " comp=" << f.compressionType << ")"; - return dbg.maybeSpace(); -} - -QDebug operator<<(QDebug dbg, const VersionFileEntry &v) -{ - dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode - << " md5=" << v.md5 << " sources=" << v.sources << ")"; - return dbg.maybeSpace(); -} - -QDebug operator<<(QDebug dbg, const Operation::Type &t) -{ - switch (t) - { - case Operation::OP_REPLACE: - dbg << "OP_COPY"; - break; - case Operation::OP_DELETE: - dbg << "OP_DELETE"; - break; - } - return dbg.maybeSpace(); -} - -QDebug operator<<(QDebug dbg, const Operation &u) -{ - dbg.nospace() << "Operation(type=" << u.type << " file=" << u.source - << " dest=" << u.destination << " mode=" << u.destinationMode << ")"; - return dbg.maybeSpace(); -} - -class DownloadTaskTest : public QObject -{ - Q_OBJECT -private -slots: - void initTestCase() - { - } - void cleanupTestCase() - { - } - - void test_parseVersionInfo_data() - { - QTest::addColumn<QByteArray>("data"); - QTest::addColumn<VersionFileList>("list"); - QTest::addColumn<QString>("error"); - QTest::addColumn<bool>("ret"); - - QTest::newRow("one") - << MULTIMC_GET_TEST_FILE("data/1.json") - << (VersionFileList() - << VersionFileEntry{"fileOne", - 493, - encodeBaseFile("/data/fileOneA"), - "9eb84090956c484e32cb6c08455a667b"} - << VersionFileEntry{"fileTwo", - 644, - encodeBaseFile("/data/fileTwo"), - "38f94f54fa3eb72b0ea836538c10b043"} - << VersionFileEntry{"fileThree", - 750, - encodeBaseFile("/data/fileThree"), - "f12df554b21e320be6471d7154130e70"}) - << QString() << true; - QTest::newRow("two") - << MULTIMC_GET_TEST_FILE("data/2.json") - << (VersionFileList() - << VersionFileEntry{"fileOne", - 493, - encodeBaseFile("/data/fileOneB"), - "42915a71277c9016668cce7b82c6b577"} - << VersionFileEntry{"fileTwo", - 644, - encodeBaseFile("/data/fileTwo"), - "38f94f54fa3eb72b0ea836538c10b043"}) - << QString() << true; - } - void test_parseVersionInfo() - { - QFETCH(QByteArray, data); - QFETCH(VersionFileList, list); - QFETCH(QString, error); - QFETCH(bool, ret); - - VersionFileList outList; - QString outError; - bool outRet = parseVersionInfo(data, outList, outError); - QCOMPARE(outRet, ret); - QCOMPARE(outList, list); - QCOMPARE(outError, error); - } - - void test_processFileLists_data() - { - QTest::addColumn<QString>("tempFolder"); - QTest::addColumn<VersionFileList>("currentVersion"); - QTest::addColumn<VersionFileList>("newVersion"); - QTest::addColumn<OperationList>("expectedOperations"); - - QTemporaryDir tempFolderObj; - QString tempFolder = tempFolderObj.path(); - // update fileOne, keep fileTwo, remove fileThree - QTest::newRow("test 1") - << tempFolder << (VersionFileList() - << VersionFileEntry{ - "data/fileOne", 493, - FileSourceList() - << FileSource( - "http", "http://host/path/fileOne-1"), - "9eb84090956c484e32cb6c08455a667b"} - << VersionFileEntry{ - "data/fileTwo", 644, - FileSourceList() - << FileSource( - "http", "http://host/path/fileTwo-1"), - "38f94f54fa3eb72b0ea836538c10b043"} - << VersionFileEntry{ - "data/fileThree", 420, - FileSourceList() - << FileSource( - "http", "http://host/path/fileThree-1"), - "f12df554b21e320be6471d7154130e70"}) - << (VersionFileList() - << VersionFileEntry{ - "data/fileOne", 493, - FileSourceList() - << FileSource("http", - "http://host/path/fileOne-2"), - "42915a71277c9016668cce7b82c6b577"} - << VersionFileEntry{ - "data/fileTwo", 644, - FileSourceList() - << FileSource("http", - "http://host/path/fileTwo-2"), - "38f94f54fa3eb72b0ea836538c10b043"}) - << (OperationList() - << Operation::DeleteOp("data/fileThree") - << Operation::CopyOp( - FS::PathCombine(tempFolder, - QString("data/fileOne").replace("/", "_")), - "data/fileOne", 493)); - } - void test_processFileLists() - { - QFETCH(QString, tempFolder); - QFETCH(VersionFileList, currentVersion); - QFETCH(VersionFileList, newVersion); - QFETCH(OperationList, expectedOperations); - - OperationList operations; - - processFileLists(currentVersion, newVersion, QDir::currentPath(), tempFolder, new NetJob("Dummy"), operations); - qDebug() << (operations == expectedOperations); - qDebug() << operations; - qDebug() << expectedOperations; - QCOMPARE(operations, expectedOperations); - } -}; - -extern "C" -{ - QTEST_GUILESS_MAIN(DownloadTaskTest) -} - -#include "DownloadTask_test.moc" diff --git a/api/logic/updater/GoUpdate.cpp b/api/logic/updater/GoUpdate.cpp deleted file mode 100644 index 6167418e..00000000 --- a/api/logic/updater/GoUpdate.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "GoUpdate.h" -#include <QDebug> -#include <QDomDocument> -#include <QFile> -#include <FileSystem.h> - -#include "net/Download.h" -#include "net/ChecksumValidator.h" - -namespace GoUpdate -{ - -bool parseVersionInfo(const QByteArray &data, VersionFileList &list, QString &error) -{ - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - error = QString("Failed to parse version info JSON: %1 at %2") - .arg(jsonError.errorString()) - .arg(jsonError.offset); - qCritical() << error; - return false; - } - - QJsonObject json = jsonDoc.object(); - - qDebug() << data; - qDebug() << "Loading version info from JSON."; - QJsonArray filesArray = json.value("Files").toArray(); - for (QJsonValue fileValue : filesArray) - { - QJsonObject fileObj = fileValue.toObject(); - - QString file_path = fileObj.value("Path").toString(); - - VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(), - FileSourceList(), fileObj.value("MD5").toString(), }; - qDebug() << "File" << file.path << "with perms" << file.mode; - - QJsonArray sourceArray = fileObj.value("Sources").toArray(); - for (QJsonValue val : sourceArray) - { - QJsonObject sourceObj = val.toObject(); - - QString type = sourceObj.value("SourceType").toString(); - if (type == "http") - { - file.sources.append(FileSource("http", sourceObj.value("Url").toString())); - } - else - { - qWarning() << "Unknown source type" << type << "ignored."; - } - } - - qDebug() << "Loaded info for" << file.path; - - list.append(file); - } - - return true; -} - -bool processFileLists -( - const VersionFileList ¤tVersion, - const VersionFileList &newVersion, - const QString &rootPath, - const QString &tempPath, - NetJobPtr job, - OperationList &ops -) -{ - // First, if we've loaded the current version's file list, we need to iterate through it and - // delete anything in the current one version's list that isn't in the new version's list. - for (VersionFileEntry entry : currentVersion) - { - QFileInfo toDelete(FS::PathCombine(rootPath, entry.path)); - if (!toDelete.exists()) - { - qCritical() << "Expected file " << toDelete.absoluteFilePath() - << " doesn't exist!"; - } - bool keep = false; - - // - for (VersionFileEntry newEntry : newVersion) - { - if (newEntry.path == entry.path) - { - qDebug() << "Not deleting" << entry.path - << "because it is still present in the new version."; - keep = true; - break; - } - } - - // If the loop reaches the end and we didn't find a match, delete the file. - if (!keep) - { - if (toDelete.exists()) - ops.append(Operation::DeleteOp(entry.path)); - } - } - - // Next, check each file in MultiMC's folder and see if we need to update them. - for (VersionFileEntry entry : newVersion) - { - // TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a - // way to do this in the background. - QString fileMD5; - QString realEntryPath = FS::PathCombine(rootPath, entry.path); - QFile entryFile(realEntryPath); - QFileInfo entryInfo(realEntryPath); - - bool needs_upgrade = false; - if (!entryFile.exists()) - { - needs_upgrade = true; - } - else - { - bool pass = true; - if (!entryInfo.isReadable()) - { - qCritical() << "File " << realEntryPath << " is not readable."; - pass = false; - } - if (!entryInfo.isWritable()) - { - qCritical() << "File " << realEntryPath << " is not writable."; - pass = false; - } - if (!entryFile.open(QFile::ReadOnly)) - { - qCritical() << "File " << realEntryPath << " cannot be opened for reading."; - pass = false; - } - if (!pass) - { - ops.clear(); - return false; - } - } - - if(!needs_upgrade) - { - QCryptographicHash hash(QCryptographicHash::Md5); - auto foo = entryFile.readAll(); - - hash.addData(foo); - fileMD5 = hash.result().toHex(); - if ((fileMD5 != entry.md5)) - { - qDebug() << "MD5Sum does not match!"; - qDebug() << "Expected:'" << entry.md5 << "'"; - qDebug() << "Got: '" << fileMD5 << "'"; - needs_upgrade = true; - } - } - - // skip file. it doesn't need an upgrade. - if (!needs_upgrade) - { - qDebug() << "File" << realEntryPath << " does not need updating."; - continue; - } - - // yep. this file actually needs an upgrade. PROCEED. - qDebug() << "Found file" << realEntryPath << " that needs updating."; - - // Go through the sources list and find one to use. - // TODO: Make a NetAction that takes a source list and tries each of them until one - // works. For now, we'll just use the first http one. - for (FileSource source : entry.sources) - { - if (source.type != "http") - continue; - - qDebug() << "Will download" << entry.path << "from" << source.url; - - // Download it to updatedir/<filepath>-<md5> where filepath is the file's - // path with slashes replaced by underscores. - QString dlPath = FS::PathCombine(tempPath, QString(entry.path).replace("/", "_")); - - // We need to download the file to the updatefiles folder and add a task - // to copy it to its install path. - auto download = Net::Download::makeFile(source.url, dlPath); - auto rawMd5 = QByteArray::fromHex(entry.md5.toLatin1()); - download->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); - job->addNetAction(download); - ops.append(Operation::CopyOp(dlPath, entry.path, entry.mode)); - } - } - return true; -} -} diff --git a/api/logic/updater/GoUpdate.h b/api/logic/updater/GoUpdate.h deleted file mode 100644 index 8f92bb99..00000000 --- a/api/logic/updater/GoUpdate.h +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once -#include <QByteArray> -#include <net/NetJob.h> - -#include "multimc_logic_export.h" - -namespace GoUpdate -{ - -/** - * A temporary object exchanged between updated checker and the actual update task - */ -struct MULTIMC_LOGIC_EXPORT Status -{ - bool updateAvailable = false; - - int newVersionId = -1; - QString newRepoUrl; - - int currentVersionId = -1; - QString currentRepoUrl; - - // path to the root of the application - QString rootPath; -}; - -/** - * Struct that describes an entry in a VersionFileEntry's `Sources` list. - */ -struct MULTIMC_LOGIC_EXPORT FileSource -{ - FileSource(QString type, QString url, QString compression="") - { - this->type = type; - this->url = url; - this->compressionType = compression; - } - - bool operator==(const FileSource &f2) const - { - return type == f2.type && url == f2.url && compressionType == f2.compressionType; - } - - QString type; - QString url; - QString compressionType; -}; -typedef QList<FileSource> FileSourceList; - -/** - * Structure that describes an entry in a GoUpdate version's `Files` list. - */ -struct MULTIMC_LOGIC_EXPORT VersionFileEntry -{ - QString path; - int mode; - FileSourceList sources; - QString md5; - bool operator==(const VersionFileEntry &v2) const - { - return path == v2.path && mode == v2.mode && sources == v2.sources && md5 == v2.md5; - } -}; -typedef QList<VersionFileEntry> VersionFileList; - -/** - * Structure that describes an operation to perform when installing updates. - */ -struct MULTIMC_LOGIC_EXPORT Operation -{ - static Operation CopyOp(QString from, QString to, int fmode=0644) - { - return Operation{OP_REPLACE, from, to, fmode}; - } - static Operation DeleteOp(QString file) - { - return Operation{OP_DELETE, QString(), file, 0644}; - } - - // FIXME: for some types, some of the other fields are irrelevant! - bool operator==(const Operation &u2) const - { - return type == u2.type && - source == u2.source && - destination == u2.destination && - destinationMode == u2.destinationMode; - } - - //! Specifies the type of operation that this is. - enum Type - { - OP_REPLACE, - OP_DELETE, - } type; - - //! The source file, if any - QString source; - - //! The destination file. - QString destination; - - //! The mode to change the destination file to. - int destinationMode; -}; -typedef QList<Operation> OperationList; - -/** - * Loads the file list from the given version info JSON object into the given list. - */ -bool MULTIMC_LOGIC_EXPORT parseVersionInfo(const QByteArray &data, VersionFileList& list, QString &error); - -/*! - * Takes a list of file entries for the current version's files and the new version's files - * and populates the downloadList and operationList with information about how to download and install the update. - */ -bool MULTIMC_LOGIC_EXPORT processFileLists -( - const VersionFileList ¤tVersion, - const VersionFileList &newVersion, - const QString &rootPath, - const QString &tempPath, - NetJobPtr job, - OperationList &ops -); - -} -Q_DECLARE_METATYPE(GoUpdate::Status) diff --git a/api/logic/updater/UpdateChecker.cpp b/api/logic/updater/UpdateChecker.cpp deleted file mode 100644 index be33c73c..00000000 --- a/api/logic/updater/UpdateChecker.cpp +++ /dev/null @@ -1,260 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "UpdateChecker.h" - -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> -#include <QDebug> - -#define API_VERSION 0 -#define CHANLIST_FORMAT 0 - -UpdateChecker::UpdateChecker(QString channelListUrl, QString currentChannel, int currentBuild) -{ - m_channelListUrl = channelListUrl; - m_currentChannel = currentChannel; - m_currentBuild = currentBuild; -} - -QList<UpdateChecker::ChannelListEntry> UpdateChecker::getChannelList() const -{ - return m_channels; -} - -bool UpdateChecker::hasChannels() const -{ - return !m_channels.isEmpty(); -} - -void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate) -{ - qDebug() << "Checking for updates."; - - // If the channel list hasn't loaded yet, load it and defer checking for updates until - // later. - if (!m_chanListLoaded) - { - qDebug() << "Channel list isn't loaded yet. Loading channel list and deferring " - "update check."; - m_checkUpdateWaiting = true; - m_deferredUpdateChannel = updateChannel; - updateChanList(notifyNoUpdate); - return; - } - - if (m_updateChecking) - { - qDebug() << "Ignoring update check request. Already checking for updates."; - return; - } - - m_updateChecking = true; - - // Find the desired channel within the channel list and get its repo URL. If if cannot be - // found, error. - m_newRepoUrl = ""; - for (ChannelListEntry entry : m_channels) - { - if (entry.id == updateChannel) - m_newRepoUrl = entry.url; - if (entry.id == m_currentChannel) - m_currentRepoUrl = entry.url; - } - - qDebug() << "m_repoUrl = " << m_newRepoUrl; - - // If we didn't find our channel, error. - if (m_newRepoUrl.isEmpty()) - { - qCritical() << "m_repoUrl is empty!"; - emit updateCheckFailed(); - return; - } - - QUrl indexUrl = QUrl(m_newRepoUrl).resolved(QUrl("index.json")); - - auto job = new NetJob("GoUpdate Repository Index"); - job->addNetAction(Net::Download::makeByteArray(indexUrl, &indexData)); - connect(job, &NetJob::succeeded, [this, notifyNoUpdate](){ updateCheckFinished(notifyNoUpdate); }); - connect(job, &NetJob::failed, this, &UpdateChecker::updateCheckFailed); - indexJob.reset(job); - job->start(); -} - -void UpdateChecker::updateCheckFinished(bool notifyNoUpdate) -{ - qDebug() << "Finished downloading repo index. Checking for new versions."; - - QJsonParseError jsonError; - indexJob.reset(); - - QJsonDocument jsonDoc = QJsonDocument::fromJson(indexData, &jsonError); - indexData.clear(); - if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isObject()) - { - qCritical() << "Failed to parse GoUpdate repository index. JSON error" - << jsonError.errorString() << "at offset" << jsonError.offset; - m_updateChecking = false; - return; - } - - QJsonObject object = jsonDoc.object(); - - bool success = false; - int apiVersion = object.value("ApiVersion").toVariant().toInt(&success); - if (apiVersion != API_VERSION || !success) - { - qCritical() << "Failed to check for updates. API version mismatch. We're using" - << API_VERSION << "server has" << apiVersion; - m_updateChecking = false; - return; - } - - qDebug() << "Processing repository version list."; - QJsonObject newestVersion; - QJsonArray versions = object.value("Versions").toArray(); - for (QJsonValue versionVal : versions) - { - QJsonObject version = versionVal.toObject(); - if (newestVersion.value("Id").toVariant().toInt() < - version.value("Id").toVariant().toInt()) - { - newestVersion = version; - } - } - - // We've got the version with the greatest ID number. Now compare it to our current build - // number and update if they're different. - int newBuildNumber = newestVersion.value("Id").toVariant().toInt(); - if (newBuildNumber != m_currentBuild) - { - qDebug() << "Found newer version with ID" << newBuildNumber; - // Update! - GoUpdate::Status updateStatus; - updateStatus.updateAvailable = true; - updateStatus.currentVersionId = m_currentBuild; - updateStatus.currentRepoUrl = m_currentRepoUrl; - updateStatus.newVersionId = newBuildNumber; - updateStatus.newRepoUrl = m_newRepoUrl; - emit updateAvailable(updateStatus); - } - else if (notifyNoUpdate) - { - emit noUpdateFound(); - } - m_updateChecking = false; -} - -void UpdateChecker::updateCheckFailed() -{ - qCritical() << "Update check failed for reasons unknown."; -} - -void UpdateChecker::updateChanList(bool notifyNoUpdate) -{ - qDebug() << "Loading the channel list."; - - if (m_chanListLoading) - { - qDebug() << "Ignoring channel list update request. Already grabbing channel list."; - return; - } - - if (m_channelListUrl.isEmpty()) - { - qCritical() << "Failed to update channel list. No channel list URL set." - << "If you'd like to use MultiMC's update system, please pass the channel " - "list URL to CMake at compile time."; - return; - } - - m_chanListLoading = true; - NetJob *job = new NetJob("Update System Channel List"); - job->addNetAction(Net::Download::makeByteArray(QUrl(m_channelListUrl), &chanlistData)); - connect(job, &NetJob::succeeded, [this, notifyNoUpdate]() { chanListDownloadFinished(notifyNoUpdate); }); - QObject::connect(job, &NetJob::failed, this, &UpdateChecker::chanListDownloadFailed); - chanListJob.reset(job); - job->start(); -} - -void UpdateChecker::chanListDownloadFinished(bool notifyNoUpdate) -{ - chanListJob.reset(); - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(chanlistData, &jsonError); - chanlistData.clear(); - if (jsonError.error != QJsonParseError::NoError) - { - // TODO: Report errors to the user. - qCritical() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at" << jsonError.offset; - m_chanListLoading = false; - return; - } - - QJsonObject object = jsonDoc.object(); - - bool success = false; - int formatVersion = object.value("format_version").toVariant().toInt(&success); - if (formatVersion != CHANLIST_FORMAT || !success) - { - qCritical() - << "Failed to check for updates. Channel list format version mismatch. We're using" - << CHANLIST_FORMAT << "server has" << formatVersion; - m_chanListLoading = false; - return; - } - - // Load channels into a temporary array. - QList<ChannelListEntry> loadedChannels; - QJsonArray channelArray = object.value("channels").toArray(); - for (QJsonValue chanVal : channelArray) - { - QJsonObject channelObj = chanVal.toObject(); - ChannelListEntry entry{channelObj.value("id").toVariant().toString(), - channelObj.value("name").toVariant().toString(), - channelObj.value("description").toVariant().toString(), - channelObj.value("url").toVariant().toString()}; - if (entry.id.isEmpty() || entry.name.isEmpty() || entry.url.isEmpty()) - { - qCritical() << "Channel list entry with empty ID, name, or URL. Skipping."; - continue; - } - loadedChannels.append(entry); - } - - // Swap the channel list we just loaded into the object's channel list. - m_channels.swap(loadedChannels); - - m_chanListLoading = false; - m_chanListLoaded = true; - qDebug() << "Successfully loaded UpdateChecker channel list."; - - // If we're waiting to check for updates, do that now. - if (m_checkUpdateWaiting) - checkForUpdate(m_deferredUpdateChannel, notifyNoUpdate); - - emit channelListLoaded(); -} - -void UpdateChecker::chanListDownloadFailed(QString reason) -{ - m_chanListLoading = false; - qCritical() << QString("Failed to download channel list: %1").arg(reason); - emit channelListLoaded(); -} - diff --git a/api/logic/updater/UpdateChecker.h b/api/logic/updater/UpdateChecker.h deleted file mode 100644 index 20906207..00000000 --- a/api/logic/updater/UpdateChecker.h +++ /dev/null @@ -1,121 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "net/NetJob.h" -#include "GoUpdate.h" - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT UpdateChecker : public QObject -{ - Q_OBJECT - -public: - UpdateChecker(QString channelListUrl, QString currentChannel, int currentBuild); - void checkForUpdate(QString updateChannel, bool notifyNoUpdate); - - /*! - * Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake). - * If this isn't called before checkForUpdate(), it will automatically be called. - */ - void updateChanList(bool notifyNoUpdate); - - /*! - * An entry in the channel list. - */ - struct ChannelListEntry - { - QString id; - QString name; - QString description; - QString url; - }; - - /*! - * Returns a the current channel list. - * If the channel list hasn't been loaded, this list will be empty. - */ - QList<ChannelListEntry> getChannelList() const; - - /*! - * Returns false if the channel list is empty. - */ - bool hasChannels() const; - -signals: - //! Signal emitted when an update is available. Passes the URL for the repo and the ID and name for the version. - void updateAvailable(GoUpdate::Status status); - - //! Signal emitted when the channel list finishes loading or fails to load. - void channelListLoaded(); - - void noUpdateFound(); - -private slots: - void updateCheckFinished(bool notifyNoUpdate); - void updateCheckFailed(); - - void chanListDownloadFinished(bool notifyNoUpdate); - void chanListDownloadFailed(QString reason); - -private: - friend class UpdateCheckerTest; - - NetJobPtr indexJob; - QByteArray indexData; - NetJobPtr chanListJob; - QByteArray chanlistData; - - QString m_channelListUrl; - - QList<ChannelListEntry> m_channels; - - /*! - * True while the system is checking for updates. - * If checkForUpdate is called while this is true, it will be ignored. - */ - bool m_updateChecking = false; - - /*! - * True if the channel list has loaded. - * If this is false, trying to check for updates will call updateChanList first. - */ - bool m_chanListLoaded = false; - - /*! - * Set to true while the channel list is currently loading. - */ - bool m_chanListLoading = false; - - /*! - * Set to true when checkForUpdate is called while the channel list isn't loaded. - * When the channel list finishes loading, if this is true, the update checker will check for updates. - */ - bool m_checkUpdateWaiting = false; - - /*! - * if m_checkUpdateWaiting, this is the last used update channel - */ - QString m_deferredUpdateChannel; - - int m_currentBuild = -1; - QString m_currentChannel; - QString m_currentRepoUrl; - - QString m_newRepoUrl; -}; - diff --git a/api/logic/updater/UpdateChecker_test.cpp b/api/logic/updater/UpdateChecker_test.cpp deleted file mode 100644 index 5702d9c6..00000000 --- a/api/logic/updater/UpdateChecker_test.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include <QTest> -#include <QSignalSpy> - -#include "TestUtil.h" -#include "updater/UpdateChecker.h" - -Q_DECLARE_METATYPE(UpdateChecker::ChannelListEntry) - -bool operator==(const UpdateChecker::ChannelListEntry &e1, const UpdateChecker::ChannelListEntry &e2) -{ - qDebug() << e1.url << "vs" << e2.url; - return e1.id == e2.id && - e1.name == e2.name && - e1.description == e2.description && - e1.url == e2.url; -} - -QDebug operator<<(QDebug dbg, const UpdateChecker::ChannelListEntry &c) -{ - dbg.nospace() << "ChannelListEntry(id=" << c.id << " name=" << c.name << " description=" << c.description << " url=" << c.url << ")"; - return dbg.maybeSpace(); -} - -QString findTestDataUrl(const char *file) -{ - return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString(); -} - -class UpdateCheckerTest : public QObject -{ - Q_OBJECT -private -slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void tst_ChannelListParsing_data() - { - QTest::addColumn<QString>("channel"); - QTest::addColumn<QString>("channelUrl"); - QTest::addColumn<bool>("hasChannels"); - QTest::addColumn<bool>("valid"); - QTest::addColumn<QList<UpdateChecker::ChannelListEntry> >("result"); - - QTest::newRow("garbage") - << QString() - << findTestDataUrl("data/garbageChannels.json") - << false - << false - << QList<UpdateChecker::ChannelListEntry>(); - QTest::newRow("errors") - << QString() - << findTestDataUrl("data/errorChannels.json") - << false - << true - << QList<UpdateChecker::ChannelListEntry>(); - QTest::newRow("no channels") - << QString() - << findTestDataUrl("data/noChannels.json") - << false - << true - << QList<UpdateChecker::ChannelListEntry>(); - QTest::newRow("one channel") - << QString("develop") - << findTestDataUrl("data/oneChannel.json") - << true - << true - << (QList<UpdateChecker::ChannelListEntry>() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"}); - QTest::newRow("several channels") - << QString("develop") - << findTestDataUrl("data/channels.json") - << true - << true - << (QList<UpdateChecker::ChannelListEntry>() - << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("data")} - << UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("data")} - << UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"}); - } - void tst_ChannelListParsing() - { - - QFETCH(QString, channel); - QFETCH(QString, channelUrl); - QFETCH(bool, hasChannels); - QFETCH(bool, valid); - QFETCH(QList<UpdateChecker::ChannelListEntry>, result); - - UpdateChecker checker(channelUrl, channel, 0); - - QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); - QVERIFY(channelListLoadedSpy.isValid()); - - checker.updateChanList(false); - - if (valid) - { - QVERIFY(channelListLoadedSpy.wait()); - QCOMPARE(channelListLoadedSpy.size(), 1); - } - else - { - channelListLoadedSpy.wait(); - QCOMPARE(channelListLoadedSpy.size(), 0); - } - - QCOMPARE(checker.hasChannels(), hasChannels); - QCOMPARE(checker.getChannelList(), result); - } - - void tst_UpdateChecking() - { - QString channel = "develop"; - QString channelUrl = findTestDataUrl("data/channels.json"); - int currentBuild = 2; - - UpdateChecker checker(channelUrl, channel, currentBuild); - - QSignalSpy updateAvailableSpy(&checker, SIGNAL(updateAvailable(GoUpdate::Status))); - QVERIFY(updateAvailableSpy.isValid()); - QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); - QVERIFY(channelListLoadedSpy.isValid()); - - checker.updateChanList(false); - QVERIFY(channelListLoadedSpy.wait()); - - qDebug() << "CWD:" << QDir::current().absolutePath(); - checker.m_channels[0].url = findTestDataUrl("data/"); - checker.checkForUpdate(channel, false); - - QVERIFY(updateAvailableSpy.wait()); - - auto status = updateAvailableSpy.first().first().value<GoUpdate::Status>(); - QCOMPARE(checker.m_channels[0].url, status.newRepoUrl); - QCOMPARE(3, status.newVersionId); - QCOMPARE(currentBuild, status.currentVersionId); - } -}; - -QTEST_GUILESS_MAIN(UpdateCheckerTest) - -#include "UpdateChecker_test.moc" diff --git a/api/logic/updater/testdata/1.json b/api/logic/updater/testdata/1.json deleted file mode 100644 index 7af7e52d..00000000 --- a/api/logic/updater/testdata/1.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "ApiVersion": 0, - "Id": 1, - "Name": "1.0.1", - "Files": [ - { - "Path": "fileOne", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileOneA" - } - ], - "Executable": true, - "Perms": 493, - "MD5": "9eb84090956c484e32cb6c08455a667b" - }, - { - "Path": "fileTwo", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileTwo" - } - ], - "Executable": false, - "Perms": 644, - "MD5": "38f94f54fa3eb72b0ea836538c10b043" - }, - { - "Path": "fileThree", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileThree" - } - ], - "Executable": false, - "Perms": "750", - "MD5": "f12df554b21e320be6471d7154130e70" - } - ] -} diff --git a/api/logic/updater/testdata/2.json b/api/logic/updater/testdata/2.json deleted file mode 100644 index 96d430d5..00000000 --- a/api/logic/updater/testdata/2.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "ApiVersion": 0, - "Id": 1, - "Name": "1.0.1", - "Files": [ - { - "Path": "fileOne", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileOneB" - } - ], - "Executable": true, - "Perms": 493, - "MD5": "42915a71277c9016668cce7b82c6b577" - }, - { - "Path": "fileTwo", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileTwo" - } - ], - "Executable": false, - "Perms": 644, - "MD5": "38f94f54fa3eb72b0ea836538c10b043" - } - ] -} diff --git a/api/logic/updater/testdata/channels.json b/api/logic/updater/testdata/channels.json deleted file mode 100644 index 5c6e42cb..00000000 --- a/api/logic/updater/testdata/channels.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "@TEST_DATA_URL@" - }, - { - "id": "stable", - "name": "Stable", - "description": "It's stable at least", - "url": "@TEST_DATA_URL@" - }, - { - "id": "42", - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "https://dent.me/tea" - } - ] -} diff --git a/api/logic/updater/testdata/errorChannels.json b/api/logic/updater/testdata/errorChannels.json deleted file mode 100644 index a2cb2165..00000000 --- a/api/logic/updater/testdata/errorChannels.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "format_version": 0, - "channels": [ - { - "id": "", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "http://example.org/stuff" - }, - { - "id": "stable", - "name": "", - "description": "It's stable at least", - "url": "ftp://username@host/path/to/stuff" - }, - { - "id": "42", - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "" - } - ] -} diff --git a/api/logic/updater/testdata/fileOneA b/api/logic/updater/testdata/fileOneA deleted file mode 100644 index f2e41136..00000000 --- a/api/logic/updater/testdata/fileOneA +++ /dev/null @@ -1 +0,0 @@ -stuff diff --git a/api/logic/updater/testdata/fileOneB b/api/logic/updater/testdata/fileOneB deleted file mode 100644 index f9aba922..00000000 --- a/api/logic/updater/testdata/fileOneB +++ /dev/null @@ -1,3 +0,0 @@ -stuff - -more stuff that came in the new version diff --git a/api/logic/updater/testdata/fileThree b/api/logic/updater/testdata/fileThree deleted file mode 100644 index 6353ff16..00000000 --- a/api/logic/updater/testdata/fileThree +++ /dev/null @@ -1 +0,0 @@ -this is yet another file diff --git a/api/logic/updater/testdata/fileTwo b/api/logic/updater/testdata/fileTwo deleted file mode 100644 index aad9a93a..00000000 --- a/api/logic/updater/testdata/fileTwo +++ /dev/null @@ -1 +0,0 @@ -some other stuff diff --git a/api/logic/updater/testdata/garbageChannels.json b/api/logic/updater/testdata/garbageChannels.json deleted file mode 100644 index 34437451..00000000 --- a/api/logic/updater/testdata/garbageChannels.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", -aa "url": "http://example.org/stuff" - }, -a "id": "stable", - "name": "Stable", - "description": "It's stable at least", - "url": "ftp://username@host/path/to/stuff" - }, - { - "id": "42"f - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "https://dent.me/tea" - } - ] -} diff --git a/api/logic/updater/testdata/index.json b/api/logic/updater/testdata/index.json deleted file mode 100644 index 867bdcfb..00000000 --- a/api/logic/updater/testdata/index.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ApiVersion": 0, - "Versions": [ - { "Id": 0, "Name": "1.0.0" }, - { "Id": 1, "Name": "1.0.1" }, - { "Id": 2, "Name": "1.0.2" }, - { "Id": 3, "Name": "1.0.3" } - ] -} diff --git a/api/logic/updater/testdata/noChannels.json b/api/logic/updater/testdata/noChannels.json deleted file mode 100644 index 76988982..00000000 --- a/api/logic/updater/testdata/noChannels.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "format_version": 0, - "channels": [ - ] -} diff --git a/api/logic/updater/testdata/oneChannel.json b/api/logic/updater/testdata/oneChannel.json deleted file mode 100644 index cc8ed255..00000000 --- a/api/logic/updater/testdata/oneChannel.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "http://example.org/stuff" - } - ] -} diff --git a/api/logic/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml b/api/logic/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml deleted file mode 100644 index 09c162ca..00000000 --- a/api/logic/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml +++ /dev/null @@ -1,17 +0,0 @@ -<update version="3"> - <install> - <file> - <source>sourceOne</source> - <dest>destOne</dest> - <mode>0777</mode> - </file> - <file> - <source>MultiMC.exe</source> - <dest>M/u/l/t/i/M/C/e/x/e</dest> - <mode>0644</mode> - </file> - </install> - <uninstall> - <file>toDelete.abc</file> - </uninstall> -</update> |