Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into catpacks

This commit is contained in:
Trial97 2023-07-02 13:14:19 +03:00
commit 7ac2a23bc8
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
118 changed files with 1685 additions and 2530 deletions

4
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,4 @@
# .git-blame-ignore-revs
# tabs -> spaces
bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9

View File

@ -594,7 +594,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Java Settings
m_settings->registerSetting("JavaPath", "");
m_settings->registerSetting("JavaTimestamp", 0);
m_settings->registerSetting("JavaSignature", "");
m_settings->registerSetting("JavaArchitecture", "");
m_settings->registerSetting("JavaRealArchitecture", "");
m_settings->registerSetting("JavaVersion", "");

View File

@ -362,6 +362,8 @@ set(MINECRAFT_SOURCES
minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
minecraft/mod/tasks/LocalResourceParse.h
minecraft/mod/tasks/LocalResourceParse.cpp
minecraft/mod/tasks/GetModDependenciesTask.h
minecraft/mod/tasks/GetModDependenciesTask.cpp
# Assets
minecraft/AssetsUtils.h
@ -375,8 +377,6 @@ set(MINECRAFT_SOURCES
minecraft/services/SkinDelete.cpp
minecraft/services/SkinDelete.h
mojang/PackageManifest.h
mojang/PackageManifest.cpp
minecraft/Agent.h)
# the screenshots feature
@ -682,6 +682,7 @@ SET(LAUNCHER_SOURCES
VersionProxyModel.h
VersionProxyModel.cpp
Markdown.h
Markdown.cpp
# Super secret!
KonamiCode.h
@ -827,8 +828,8 @@ SET(LAUNCHER_SOURCES
ui/pages/global/APIPage.h
# GUI - platform pages
ui/pages/modplatform/VanillaPage.cpp
ui/pages/modplatform/VanillaPage.h
ui/pages/modplatform/CustomPage.cpp
ui/pages/modplatform/CustomPage.h
ui/pages/modplatform/ResourcePage.cpp
ui/pages/modplatform/ResourcePage.h
@ -1034,7 +1035,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/instance/ScreenshotsPage.ui
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
ui/pages/modplatform/atlauncher/AtlPage.ui
ui/pages/modplatform/VanillaPage.ui
ui/pages/modplatform/CustomPage.ui
ui/pages/modplatform/ResourcePage.ui
ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui

View File

@ -40,6 +40,7 @@
#include <QFileSystemModel>
#include <QSortFilterProxyModel>
#include <QStack>
#include <algorithm>
#include "FileSystem.h"
#include "SeparatorPrefixTree.h"
#include "StringUtils.h"
@ -254,3 +255,25 @@ bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex&
return true;
}
bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
auto fileInfo = fsm->fileInfo(index);
return !ignoreFile(fileInfo);
}
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
{
auto fileName = fileInfo.fileName();
auto path = relPath(fileInfo.absoluteFilePath());
return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
m_ignoreFilePaths.covers(path);
}
bool FileIgnoreProxy::filterFile(const QString& fileName) const
{
return blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(root), fileName));
}

View File

@ -36,6 +36,7 @@
#pragma once
#include <QFileInfo>
#include <QSortFilterProxyModel>
#include "SeparatorPrefixTree.h"
@ -63,10 +64,22 @@ class FileIgnoreProxy : public QSortFilterProxyModel {
inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return blocked; }
inline SeparatorPrefixTree<'/'>& blockedPaths() { return blocked; }
// list of file names that need to be removed completely from model
inline QStringList& ignoreFilesWithName() { return m_ignoreFiles; }
// list of relative paths that need to be removed completely from model
inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; }
bool filterFile(const QString& fileName) const;
protected:
bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const;
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
bool ignoreFile(QFileInfo file) const;
private:
const QString root;
SeparatorPrefixTree<'/'> blocked;
QStringList m_ignoreFiles;
SeparatorPrefixTree<'/'> m_ignoreFilePaths;
};

View File

@ -102,7 +102,7 @@ namespace fs = ghc::filesystem;
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <unistd.h>
#elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD)
#elif defined(Q_OS_MACOS)
#include <sys/attr.h>
#include <sys/clonefile.h>
#elif defined(Q_OS_WIN)
@ -372,7 +372,7 @@ void create_link::make_link_list(const QString& offset)
auto src_path = source_it.next();
auto relative_path = src_dir.relativeFilePath(src_path);
if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth){
if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth) {
relative_path = pathTruncate(relative_path, m_max_depth);
src_path = src_dir.filePath(relative_path);
if (linkedPaths.contains(src_path)) {
@ -663,7 +663,7 @@ QString pathTruncate(const QString& path, int depth)
QString trunc = QFileInfo(path).path();
if (pathDepth(trunc) > depth ) {
if (pathDepth(trunc) > depth) {
return pathTruncate(trunc, depth);
}
@ -769,6 +769,9 @@ QString getDesktopDir()
// Cross-platform Shortcut creation
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
{
if (destination.isEmpty()) {
destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
}
#if defined(Q_OS_MACOS)
destination += ".command";
@ -791,6 +794,8 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
return true;
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
if (!destination.endsWith(".desktop")) // in case of isFlatpak destination is already populated
destination += ".desktop";
QFile f(destination);
f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f);
@ -974,7 +979,7 @@ FilesystemType getFilesystemType(const QString& name)
{
for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) {
auto fs_names = iter.value();
if(fs_names.contains(name.toUpper()))
if (fs_names.contains(name.toUpper()))
return iter.key();
}
return FilesystemType::UNKNOWN;
@ -1151,7 +1156,7 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
return false;
}
#elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD)
#elif defined(Q_OS_MACOS)
if (!macos_bsd_clonefile(src_path, dst_path, ec)) {
qDebug() << "failed macos_bsd_clonefile:";
@ -1380,7 +1385,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
return true;
}
#elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD)
#elif defined(Q_OS_MACOS)
bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec)
{

31
launcher/Markdown.cpp Normal file
View File

@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Joshua Goins <josh@redstrate.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Markdown.h"
QString markdownToHTML(const QString& markdown)
{
const QByteArray markdownData = markdown.toUtf8();
char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE);
QString htmlStr(buffer);
free(buffer);
return htmlStr;
}

View File

@ -21,14 +21,4 @@
#include <QString>
#include <cmark.h>
static QString markdownToHTML(const QString& markdown)
{
const QByteArray markdownData = markdown.toUtf8();
char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE);
QString htmlStr(buffer);
free(buffer);
return htmlStr;
}
QString markdownToHTML(const QString& markdown);

View File

@ -38,6 +38,8 @@ class ResourceDownloadTask : public SequentialTask {
const QString& getFilename() const { return m_pack_version.fileName; }
const QString& getCustomPath() const { return m_custom_target_folder; }
const QVariant& getVersionID() const { return m_pack_version.fileId; }
const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; }
const ModPlatform::ResourceProvider& getProvider() const { return m_pack->provider; }
const QString& getName() const { return m_pack->name; }
ModPlatform::IndexedPack::Ptr getPack() { return m_pack; }

View File

@ -81,15 +81,20 @@ void CheckJava::executeTask()
}
QFileInfo javaInfo(realJavaPath);
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
qint64 javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
auto storedSignature = settings->get("JavaSignature").toString();
auto storedArchitecture = settings->get("JavaArchitecture").toString();
auto storedRealArchitecture = settings->get("JavaRealArchitecture").toString();
auto storedVersion = settings->get("JavaVersion").toString();
auto storedVendor = settings->get("JavaVendor").toString();
m_javaUnixTime = javaUnixTime;
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(QByteArray::number(javaUnixTime));
hash.addData(m_javaPath.toUtf8());
m_javaSignature = hash.result().toHex();
// if timestamps are not the same, or something is missing, check!
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0
if (m_javaSignature != storedSignature || storedVersion.size() == 0
|| storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0
|| storedVendor.size() == 0)
{
@ -140,7 +145,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
instance->settings()->set("JavaVendor", result.javaVendor);
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
instance->settings()->set("JavaSignature", m_javaSignature);
emitSucceeded();
return;
}

View File

@ -40,6 +40,6 @@ private:
private:
QString m_javaPath;
qlonglong m_javaUnixTime;
QString m_javaSignature;
JavaCheckerPtr m_JavaChecker;
};

View File

@ -148,7 +148,7 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
// special!
m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaSignature"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);

View File

@ -53,7 +53,7 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstanc
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE};
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
}

View File

@ -0,0 +1,252 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "GetModDependenciesTask.h"
#include <QDebug>
#include <algorithm>
#include <memory>
#include "Json.h"
#include "QObjectPtr.h"
#include "minecraft/mod/MetadataHandler.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/modrinth/ModrinthAPI.h"
#include "tasks/ConcurrentTask.h"
#include "tasks/SequentialTask.h"
#include "ui/pages/modplatform/ModModel.h"
#include "ui/pages/modplatform/flame/FlameResourceModels.h"
#include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h"
static Version mcVersion(BaseInstance* inst)
{
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
}
static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
{
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value();
}
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
BaseInstance* instance,
ModFolderModel* folder,
QList<std::shared_ptr<PackDependency>> selected)
: SequentialTask(parent, tr("Get dependencies"))
, m_selected(selected)
, m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance),
std::make_shared<FlameAPI>() }
, m_modrinth_provider{ ModPlatform::ResourceProvider::MODRINTH, std::make_shared<ResourceDownload::ModrinthModModel>(*instance),
std::make_shared<ModrinthAPI>() }
, m_version(mcVersion(instance))
, m_loaderType(mcLoaders(instance))
{
for (auto mod : folder->allMods())
if (auto meta = mod->metadata(); meta)
m_mods.append(meta);
prepare();
};
void GetModDependenciesTask::prepare()
{
for (auto sel : m_selected) {
for (auto dep : getDependenciesForVersion(sel->version, sel->pack->provider)) {
addTask(prepareDependencyTask(dep, sel->pack->provider, 20));
}
}
}
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName)
{
if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
});
if (over != overide.cend()) {
return { isQuilt ? over->quilt : over->fabric, dep.type };
}
}
return dep;
}
QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion(const ModPlatform::IndexedVersion& version,
const ModPlatform::ResourceProvider providerName)
{
QList<ModPlatform::Dependency> c_dependencies;
for (auto ver_dep : version.dependencies) {
if (ver_dep.type != ModPlatform::DependencyType::REQUIRED)
continue;
auto isOnlyVersion = providerName == ModPlatform::ResourceProvider::MODRINTH && ver_dep.addonId.toString().isEmpty();
if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(),
[&ver_dep, isOnlyVersion](const ModPlatform::Dependency& i) {
return isOnlyVersion ? i.version == ver_dep.version : i.addonId == ver_dep.addonId;
});
dep != c_dependencies.end())
continue; // check the current dependency list
if (auto dep = std::find_if(m_selected.begin(), m_selected.end(),
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<PackDependency> i) {
return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.version
: i->pack->addonId == ver_dep.addonId);
});
dep != m_selected.end())
continue; // check the selected versions
if (auto dep = std::find_if(m_mods.begin(), m_mods.end(),
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<Metadata::ModStruct> i) {
return i->provider == providerName &&
(isOnlyVersion ? i->file_id == ver_dep.version : i->project_id == ver_dep.addonId);
});
dep != m_mods.end())
continue; // check the existing mods
if (auto dep = std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(),
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<PackDependency> i) {
return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.addonId
: i->pack->addonId == ver_dep.addonId);
});
dep != m_pack_dependencies.end()) // check loaded dependencies
continue;
c_dependencies.append(getOverride(ver_dep, providerName));
}
return c_dependencies;
};
Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDependency> pDep)
{
auto provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
auto responseInfo = std::make_shared<QByteArray>();
auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo);
QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qDebug() << *responseInfo;
return;
}
try {
auto obj = provider.name == ModPlatform::ResourceProvider::FLAME ? Json::requireObject(Json::requireObject(doc), "data")
: Json::requireObject(doc);
provider.mod->loadIndexedPack(*pDep->pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause();
}
});
return info;
}
Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName,
int level)
{
auto pDep = std::make_shared<PackDependency>();
pDep->dependency = dep;
pDep->pack = std::make_shared<ModPlatform::IndexedPack>();
pDep->pack->addonId = dep.addonId;
pDep->pack->provider = providerName;
m_pack_dependencies.append(pDep);
auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
auto tasks = makeShared<SequentialTask>(
this, QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString()));
if (!dep.addonId.toString().isEmpty()) {
tasks->addTask(getProjectInfoTask(pDep));
}
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
ResourceAPI::DependencySearchCallbacks callbacks;
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, auto& pack) {
try {
QJsonArray arr;
if (dep.version.length() != 0 && doc.isObject()) {
arr.append(doc.object());
} else {
arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
}
pDep->version = provider.mod->loadDependencyVersions(dep, arr);
if (!pDep->version.addonId.isValid()) {
if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(),
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
if (over != overide.cend()) {
removePack(dep.addonId);
addTask(prepareDependencyTask({ over->fabric, dep.type }, provider.name, level));
return;
}
}
qWarning() << "Error while reading mod version empty ";
qDebug() << doc;
return;
}
pDep->version.is_currently_selected = true;
pDep->pack->versions = { pDep->version };
pDep->pack->versionsLoaded = true;
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod version: " << e.cause();
return;
}
if (level == 0) {
qWarning() << "Dependency cycle exeeded";
return;
}
if (dep.addonId.toString().isEmpty() && !pDep->version.addonId.toString().isEmpty()) {
pDep->pack->addonId = pDep->version.addonId;
auto dep = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name);
if (dep.addonId != pDep->version.addonId) {
removePack(pDep->version.addonId);
addTask(prepareDependencyTask(dep, provider.name, level));
} else
addTask(getProjectInfoTask(pDep));
}
for (auto dep : getDependenciesForVersion(pDep->version, provider.name)) {
addTask(prepareDependencyTask(dep, provider.name, level - 1));
}
};
auto version = provider.api->getDependencyVersion(std::move(args), std::move(callbacks));
tasks->addTask(version);
return tasks;
};
void GetModDependenciesTask::removePack(const QVariant addonId)
{
auto pred = [addonId](const std::shared_ptr<PackDependency>& v) { return v->pack->addonId == addonId; };
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
m_pack_dependencies.removeIf(pred);
#else
for (auto it = m_pack_dependencies.begin(); it != m_pack_dependencies.end();)
if (pred(*it))
it = m_pack_dependencies.erase(it);
else
++it;
#endif
}

View File

@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDir>
#include <QEventLoop>
#include <QList>
#include <QVariant>
#include <functional>
#include <memory>
#include "minecraft/mod/MetadataHandler.h"
#include "minecraft/mod/ModFolderModel.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/SequentialTask.h"
#include "tasks/Task.h"
#include "ui/pages/modplatform/ModModel.h"
class GetModDependenciesTask : public SequentialTask {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<GetModDependenciesTask>;
struct PackDependency {
ModPlatform::Dependency dependency;
ModPlatform::IndexedPack::Ptr pack;
ModPlatform::IndexedVersion version;
PackDependency() = default;
PackDependency(const ModPlatform::IndexedPack::Ptr p, const ModPlatform::IndexedVersion& v)
{
pack = p;
version = v;
}
};
struct Provider {
ModPlatform::ResourceProvider name;
std::shared_ptr<ResourceDownload::ModModel> mod;
std::shared_ptr<ResourceAPI> api;
};
explicit GetModDependenciesTask(QObject* parent,
BaseInstance* instance,
ModFolderModel* folder,
QList<std::shared_ptr<PackDependency>> selected);
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
protected slots:
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int);
QList<ModPlatform::Dependency> getDependenciesForVersion(const ModPlatform::IndexedVersion&,
const ModPlatform::ResourceProvider providerName);
void prepare();
Task::Ptr getProjectInfoTask(std::shared_ptr<PackDependency> pDep);
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider providerName);
void removePack(const QVariant addonId);
private:
QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
QList<std::shared_ptr<Metadata::ModStruct>> m_mods;
QList<std::shared_ptr<PackDependency>> m_selected;
Provider m_flame_provider;
Provider m_modrinth_provider;
Version m_version;
ResourceAPI::ModLoaderTypes m_loaderType;
};

View File

@ -1,25 +1,24 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "LocalModUpdateTask.h"
#include "Application.h"
#include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h"

View File

@ -10,6 +10,7 @@
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
#include "modplatform/helpers/HashUtils.h"
#include "modplatform/modrinth/ModrinthAPI.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
@ -24,8 +25,8 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Resource
auto hash_task = createNewHash(mod);
if (!hash_task)
return;
connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); });
connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); });
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
hash_task->start();
}
@ -37,8 +38,8 @@ EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform:
auto hash_task = createNewHash(mod);
if (!hash_task)
continue;
connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); });
connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); });
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
m_hashing_task->addTask(hash_task);
}
}
@ -212,12 +213,12 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
{
auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first();
auto* response = new QByteArray();
auto response = std::make_shared<QByteArray>();
auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response);
// Prevents unfortunate timings when aborting the task
if (!ver_task)
return Task::Ptr{nullptr};
return Task::Ptr{ nullptr };
connect(ver_task.get(), &Task::succeeded, this, [this, response] {
QJsonParseError parse_error{};
@ -264,7 +265,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
for (auto const& data : m_temp_versions)
addonIds.insert(data.addonId.toString(), data.hash);
auto response = new QByteArray();
auto response = std::make_shared<QByteArray>();
Task::Ptr proj_task;
if (addonIds.isEmpty()) {
@ -277,7 +278,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
// Prevents unfortunate timings when aborting the task
if (!proj_task)
return Task::Ptr{nullptr};
return Task::Ptr{ nullptr };
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{};
@ -345,7 +346,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
// Flame
Task::Ptr EnsureMetadataTask::flameVersionsTask()
{
auto* response = new QByteArray();
auto response = std::make_shared<QByteArray>();
QList<uint> fingerprints;
for (auto& murmur : m_mods.keys()) {
@ -413,7 +414,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
QHash<QString, QString> addonIds;
for (auto const& hash : m_mods.keys()) {
if (m_temp_versions.contains(hash)) {
auto const& data = m_temp_versions.find(hash).value();
auto data = m_temp_versions.find(hash).value();
auto id_str = data.addonId.toString();
if (!id_str.isEmpty())
@ -421,7 +422,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
}
}
auto response = new QByteArray();
auto response = std::make_shared<QByteArray>();
Task::Ptr proj_task;
if (addonIds.isEmpty()) {
@ -434,7 +435,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
// Prevents unfortunate timings when aborting the task
if (!proj_task)
return Task::Ptr{nullptr};
return Task::Ptr{ nullptr };
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{};

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -33,6 +34,8 @@ enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };
class ProviderCapabilities {
public:
auto name(ResourceProvider) -> const char*;
@ -52,6 +55,12 @@ struct DonationData {
QString url;
};
struct Dependency {
QVariant addonId;
DependencyType type;
QString version;
};
struct IndexedVersion {
QVariant addonId;
QVariant fileId;
@ -66,6 +75,7 @@ struct IndexedVersion {
QString hash;
bool is_preferred = true;
QString changelog;
QList<Dependency> dependencies;
// For internal use, not provided by APIs
bool is_currently_selected = false;
@ -119,6 +129,22 @@ struct IndexedPack {
}
};
struct OverrideDep {
QString quilt;
QString fabric;
QString slug;
ModPlatform::ResourceProvider provider;
};
inline auto getOverrideDeps() -> QList<OverrideDep>
{
return { { "634179", "306612", "API", ModPlatform::ResourceProvider::FLAME },
{ "720410", "308769", "KotlinLibraries", ModPlatform::ResourceProvider::FLAME },
{ "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH },
{ "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } };
};
} // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)

View File

@ -111,6 +111,16 @@ class ResourceAPI {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
};
struct DependencySearchArgs {
ModPlatform::Dependency dependency;
Version mcVersion;
ModLoaderTypes loader;
};
struct DependencySearchCallbacks {
std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
};
public:
/** Gets a list of available sorting methods for this API. */
[[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
@ -121,12 +131,12 @@ class ResourceAPI {
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProject(QString addonId, QByteArray* response) const
[[nodiscard]] virtual Task::Ptr getProject(QString addonId, std::shared_ptr<QByteArray> response) const
{
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const
[[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
{
qWarning() << "TODO";
return nullptr;
@ -143,6 +153,12 @@ class ResourceAPI {
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const
{
qWarning() << "TODO";
return nullptr;
}
static auto getModLoaderString(ModLoaderType type) -> const QString
{
switch (type) {

View File

@ -82,9 +82,9 @@ void PackInstallTask::executeTask()
{
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json")
.arg(m_pack_safe_name).arg(m_version_name);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
auto searchUrl =
QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
@ -99,11 +99,12 @@ 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 ATLauncher at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ATLauncher at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response.get();
return;
}
auto obj = doc.object();

View File

@ -40,12 +40,13 @@
#include "ATLPackManifest.h"
#include "InstanceTask.h"
#include "net/NetJob.h"
#include "settings/INISettingsObject.h"
#include "meta/Version.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "meta/Version.h"
#include "net/NetJob.h"
#include "settings/INISettingsObject.h"
#include <memory>
#include <optional>
namespace ATLauncher {
@ -57,8 +58,7 @@ enum class InstallMode {
};
class UserInteractionSupport {
public:
public:
/**
* Requests a user interaction to select which optional mods should be installed.
*/
@ -74,23 +74,27 @@ public:
* Requests a user interaction to display a message.
*/
virtual void displayMessage(QString message) = 0;
virtual ~UserInteractionSupport() = default;
};
class PackInstallTask : public InstanceTask
{
Q_OBJECT
class PackInstallTask : public InstanceTask {
Q_OBJECT
public:
explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode = InstallMode::Install);
virtual ~PackInstallTask(){}
public:
explicit PackInstallTask(UserInteractionSupport* support,
QString packName,
QString version,
InstallMode installMode = InstallMode::Install);
virtual ~PackInstallTask() { delete m_support; }
bool canAbort() const override { return true; }
bool abort() override;
protected:
protected:
virtual void executeTask() override;
private slots:
private slots:
void onDownloadSucceeded();
void onDownloadFailed(QString reason);
void onDownloadAborted();
@ -98,7 +102,7 @@ private slots:
void onModsDownloaded();
void onModsExtracted();
private:
private:
QString getDirForModType(ModType type, QString raw);
QString getVersionForLoader(QString uid);
QString detectLibrary(VersionLibrary library);
@ -110,20 +114,18 @@ private:
void installConfigs();
void extractConfigs();
void downloadMods();
bool extractMods(
const QMap<QString, VersionMod> &toExtract,
const QMap<QString, VersionMod> &toDecomp,
const QMap<QString, QString> &toCopy
);
bool extractMods(const QMap<QString, VersionMod>& toExtract,
const QMap<QString, VersionMod>& toDecomp,
const QMap<QString, QString>& toCopy);
void install();
private:
UserInteractionSupport *m_support;
private:
UserInteractionSupport* m_support;
bool abortable = false;
NetJob::Ptr jobPtr;
QByteArray response;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
InstallMode m_install_mode;
QString m_pack_name;
@ -145,7 +147,6 @@ private:
QFuture<bool> m_modExtractFuture;
QFutureWatcher<bool> m_modExtractFutureWatcher;
};
}
} // namespace ATLauncher

View File

@ -25,15 +25,16 @@ void Flame::FileResolvingTask::executeTask()
setProgress(0, 3);
m_dljob.reset(new NetJob("Mod id resolver", m_network));
result.reset(new QByteArray());
//build json data to send
// build json data to send
QJsonObject object;
object["fileIds"] = QJsonArray::fromVariantList(std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) {
l.push_back(s.fileId);
return l;
}));
object["fileIds"] = QJsonArray::fromVariantList(
std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) {
l.push_back(s.fileId);
return l;
}));
QByteArray data = Json::toText(object);
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data);
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
m_dljob->addNetAction(dl);
auto step_progress = std::make_shared<TaskStepProgress>();
@ -87,17 +88,15 @@ void Flame::FileResolvingTask::netJobFinished()
auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
auto& out = m_toProcess.files[fileid];
try {
out.parseFromObject(Json::requireObject(file));
out.parseFromObject(Json::requireObject(file));
} catch (const JSONValidationError& e) {
qDebug() << "Blocked mod on curseforge" << out.fileName;
auto hash = out.hash;
if(!hash.isEmpty()) {
if (!hash.isEmpty()) {
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = std::make_shared<QByteArray>();
auto dl = Net::Download::makeByteArray(QUrl(url), output.get());
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() {
out.resolved = true;
});
auto dl = Net::Download::makeByteArray(QUrl(url), output);
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
m_checkJob->addNetAction(dl);
blockedProjects.insert(&out, output);
@ -169,7 +168,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
auto projectId = mod->projectId;
auto output = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::Download::makeByteArray(url, output.get());
auto dl = Net::Download::makeByteArray(url, output);
qDebug() << "Fetching url slug for file:" << mod->fileName;
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done

View File

@ -11,7 +11,7 @@
#include "net/NetJob.h"
#include "net/Upload.h"
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response)
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Flame::MatchFingerprints"), APPLICATION->network());
@ -28,8 +28,6 @@ Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArra
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
@ -43,7 +41,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
netJob->addNetAction(Net::Download::makeByteArray(
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
response.get()));
response));
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &changelog] {
QJsonParseError parse_error{};
@ -75,8 +73,8 @@ auto FlameAPI::getModDescription(int modId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray(
QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response.get()));
netJob->addNetAction(
Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
QJsonParseError parse_error{};
@ -115,7 +113,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto response = std::make_shared<QByteArray>();
ModPlatform::IndexedVersion ver;
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response.get()));
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
QJsonParseError parse_error{};
@ -137,7 +135,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
for (auto file : arr) {
auto file_obj = Json::requireObject(file);
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
if(file_tmp.date > ver_tmp.date) {
if (file_tmp.date > ver_tmp.date) {
ver_tmp = file_tmp;
latest_file_obj = file_obj;
}
@ -160,7 +158,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
return ver;
}
Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const
Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
{
auto netJob = makeShared<NetJob>(QString("Flame::GetProjects"), APPLICATION->network());
@ -177,13 +175,12 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) cons
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob;
}
Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const
Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const
{
auto netJob = makeShared<NetJob>(QString("Flame::GetFiles"), APPLICATION->network());
@ -200,7 +197,6 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) c
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob;

View File

@ -4,7 +4,10 @@
#pragma once
#include <algorithm>
#include <memory>
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/helpers/NetworkResourceAPI.h"
class FlameAPI : public NetworkResourceAPI {
@ -14,9 +17,9 @@ class FlameAPI : public NetworkResourceAPI {
auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion;
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response);
Task::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const;
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response);
Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const;
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
@ -41,14 +44,15 @@ class FlameAPI : public NetworkResourceAPI {
return 4;
// TODO: remove this once Quilt drops official Fabric support
if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
return 4; // Quilt would probably be 5
return 4; // Quilt would probably be 5
return 0;
}
private:
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
{
auto gameVersionStr = args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString();
auto gameVersionStr =
args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString();
QStringList get_arguments;
get_arguments.append(QString("classId=%1").arg(getClassId(args.type)));
@ -73,14 +77,48 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{
QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString())};
auto addonId = args.pack.addonId.toString();
QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(addonId) };
QStringList get_parameters;
if (args.mcVersions.has_value())
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
if (args.loaders.has_value())
get_parameters.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));
if (args.loaders.has_value()) {
int mappedModLoader = getMappedModLoader(args.loaders.value());
if (args.loaders.value() & Quilt) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
});
if (over != overide.cend()) {
mappedModLoader = 5;
}
}
get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
}
return url + get_parameters.join('&');
};
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
auto mappedModLoader = getMappedModLoader(args.loader);
auto addonId = args.dependency.addonId.toString();
if (args.loader & Quilt) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
});
if (over != overide.cend()) {
mappedModLoader = 5;
}
}
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3")
.arg(addonId)
.arg(args.mcVersion.toString())
.arg(mappedModLoader);
};
};

View File

@ -31,7 +31,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
auto get_project_job = new NetJob("Flame::GetProjectJob", APPLICATION->network());
auto response = new QByteArray();
auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
auto dl = Net::Download::makeByteArray(url, response);
get_project_job->addNetAction(dl);
@ -75,7 +75,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
auto get_file_info_job = new NetJob("Flame::GetFileInfoJob", APPLICATION->network());
auto response = new QByteArray();
auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
auto dl = Net::Download::makeByteArray(url, response);
get_file_info_job->addNetAction(dl);

View File

@ -182,7 +182,7 @@ bool FlameCreationTask::updateInstance()
fileIds.append(QString::number(file.fileId));
}
auto* raw_response = new QByteArray;
auto raw_response = std::make_shared<QByteArray>();
auto job = api.getFiles(fileIds, raw_response);
QEventLoop loop;

View File

@ -39,15 +39,15 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
auto links_obj = Json::ensureObject(obj, "links");
pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
if(pack.extraData.issuesUrl.endsWith('/'))
if (pack.extraData.issuesUrl.endsWith('/'))
pack.extraData.issuesUrl.chop(1);
pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
if(pack.extraData.sourceUrl.endsWith('/'))
if (pack.extraData.sourceUrl.endsWith('/'))
pack.extraData.sourceUrl.chop(1);
pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
if(pack.extraData.wikiUrl.endsWith('/'))
if (pack.extraData.wikiUrl.endsWith('/'))
pack.extraData.wikiUrl.chop(1);
if (!pack.extraData.body.isEmpty())
@ -56,7 +56,7 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.extraData.body = api.getModDescription(pack.addonId.toInt());
pack.extraData.body = api.getModDescription(pack.addonId.toInt());
if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty())
pack.extraDataLoaded = true;
@ -64,12 +64,12 @@ void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
static QString enumToString(int hash_algorithm)
{
switch(hash_algorithm){
default:
case 1:
return "sha1";
case 2:
return "md5";
switch (hash_algorithm) {
default:
case 1:
return "sha1";
case 2:
return "md5";
}
}
@ -86,10 +86,10 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
if(!file.addonId.isValid())
if (!file.addonId.isValid())
file.addonId = pack.addonId;
if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
@ -136,8 +136,61 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
}
}
if(load_changelog)
auto dependencies = Json::ensureArray(obj, "dependencies");
for (auto d : dependencies) {
auto dep = Json::ensureObject(d);
ModPlatform::Dependency dependency;
dependency.addonId = Json::requireInteger(dep, "modId");
switch (Json::requireInteger(dep, "relationType")) {
case 1: // EmbeddedLibrary
dependency.type = ModPlatform::DependencyType::EMBEDDED;
break;
case 2: // OptionalDependency
dependency.type = ModPlatform::DependencyType::OPTIONAL;
break;
case 3: // RequiredDependency
dependency.type = ModPlatform::DependencyType::REQUIRED;
break;
case 4: // Tool
dependency.type = ModPlatform::DependencyType::TOOL;
break;
case 5: // Incompatible
dependency.type = ModPlatform::DependencyType::INCOMPATIBLE;
break;
case 6: // Include
dependency.type = ModPlatform::DependencyType::INCLUDE;
break;
default:
dependency.type = ModPlatform::DependencyType::UNKNOWN;
break;
}
file.dependencies.append(dependency);
}
if (load_changelog)
file.changelog = api.getModFileChangelog(file.addonId.toInt(), file.fileId.toInt());
return file;
}
ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr)
{
QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
if (!file.addonId.isValid())
file.addonId = m.addonId;
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
versions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
// dates are in RFC 3339 format
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
return versions.front();
};

View File

@ -6,8 +6,8 @@
#include "modplatform/ModIndex.h"
#include "BaseInstance.h"
#include <QNetworkAccessManager>
#include "BaseInstance.h"
namespace FlameMod {
@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
} // namespace FlameMod

View File

@ -4,6 +4,7 @@
#include <QMetaType>
#include <QString>
#include <QVector>
#include "modplatform/ModIndex.h"
namespace Flame {
@ -27,8 +28,7 @@ struct ModpackExtra {
QString sourceUrl;
};
struct IndexedPack
{
struct IndexedPack {
int addonId;
QString name;
QString description;
@ -43,9 +43,9 @@ struct IndexedPack
ModpackExtra extra;
};
void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
void loadIndexedPack(IndexedPack& m, QJsonObject& obj);
void loadIndexedInfo(IndexedPack&, QJsonObject&);
void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr);
}
void loadIndexedPackVersions(IndexedPack& m, QJsonArray& arr);
} // namespace Flame
Q_DECLARE_METATYPE(Flame::IndexedPack)

View File

@ -71,6 +71,7 @@ void ModrinthHasher::executeTask()
emitFailed("Empty hash!");
} else {
emitSucceeded();
emit resultsReady(m_hash);
}
}
@ -88,12 +89,12 @@ void FlameHasher::executeTask()
emitFailed("Empty hash!");
} else {
emitSucceeded();
emit resultsReady(m_hash);
}
}
BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider)
: Hasher(file_path), provider(provider) {
BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) : Hasher(file_path), provider(provider)
{
setObjectName(QString("BlockedModHasher: %1").arg(file_path));
hash_type = ProviderCaps.hashType(provider).first();
}
@ -120,14 +121,17 @@ void BlockedModHasher::executeTask()
emitFailed("Empty hash!");
} else {
emitSucceeded();
emit resultsReady(m_hash);
}
}
QStringList BlockedModHasher::getHashTypes() {
QStringList BlockedModHasher::getHashTypes()
{
return ProviderCaps.hashType(provider);
}
bool BlockedModHasher::useHashType(QString type) {
bool BlockedModHasher::useHashType(QString type)
{
auto types = ProviderCaps.hashType(provider);
if (types.contains(type)) {
hash_type = type;

View File

@ -8,6 +8,7 @@
namespace Hashing {
class Hasher : public Task {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Hasher>;
@ -21,6 +22,9 @@ class Hasher : public Task {
QString getResult() const { return m_hash; };
QString getPath() const { return m_path; };
signals:
void resultsReady(QString hash);
protected:
QString m_hash;
QString m_path;
@ -48,6 +52,7 @@ class BlockedModHasher : public Hasher {
QStringList getHashTypes();
bool useHashType(QString type);
private:
ModPlatform::ResourceProvider provider;
QString hash_type;

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "NetworkResourceAPI.h"
#include <memory>
#include "Application.h"
#include "net/NetJob.h"
@ -19,12 +20,12 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
auto search_url = search_url_optional.value();
auto response = new QByteArray();
auto response = std::make_shared<QByteArray>();
auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks]{
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
@ -40,27 +41,21 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
callbacks.on_succeed(doc);
});
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason){
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
callbacks.on_fail(reason, network_error_code);
callbacks.on_fail(reason, network_error_code);
});
QObject::connect(netJob.get(), &NetJob::aborted, [callbacks]{
callbacks.on_abort();
});
QObject::connect(netJob.get(), &NetJob::finished, [response] {
delete response;
});
QObject::connect(netJob.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); });
return netJob;
}
Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const
{
auto response = new QByteArray();
auto response = std::make_shared<QByteArray>();
auto job = getProject(args.pack.addonId.toString(), response);
QObject::connect(job.get(), &NetJob::succeeded, [response, callbacks, args] {
@ -88,7 +83,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
auto versions_url = versions_url_optional.value();
auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network());
auto response = new QByteArray();
auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
@ -105,14 +100,10 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
callbacks.on_succeed(doc, args.pack);
});
QObject::connect(netJob.get(), &NetJob::finished, [response] {
delete response;
});
return netJob;
}
Task::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) const
Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr<QByteArray> response) const
{
auto project_url_optional = getInfoURL(addonId);
if (!project_url_optional.has_value())
@ -124,9 +115,34 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response)
netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response));
QObject::connect(netJob.get(), &NetJob::finished, [response] {
delete response;
return netJob;
}
Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args, DependencySearchCallbacks&& callbacks) const
{
auto versions_url_optional = getDependencyURL(args);
if (!versions_url_optional.has_value())
return nullptr;
auto versions_url = versions_url_optional.value();
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
return;
}
callbacks.on_succeed(doc, args.dependency);
});
return netJob;
}
};

View File

@ -4,19 +4,22 @@
#pragma once
#include <memory>
#include "modplatform/ResourceAPI.h"
class NetworkResourceAPI : public ResourceAPI {
public:
Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override;
Task::Ptr getProject(QString addonId, QByteArray* response) const override;
Task::Ptr getProject(QString addonId, std::shared_ptr<QByteArray> response) const override;
Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override;
Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override;
Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const override;
protected:
[[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> = 0;
[[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional<QString> = 0;
[[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> = 0;
[[nodiscard]] virtual auto getDependencyURL(DependencySearchArgs const& args) const -> std::optional<QString> = 0;
};

View File

@ -51,11 +51,11 @@ void PackFetchTask::fetch()
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
jobPtr->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData));
jobPtr->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();
jobPtr->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData));
jobPtr->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, thirdPartyModpacksXmlFileData));
QObject::connect(jobPtr.get(), &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished);
QObject::connect(jobPtr.get(), &NetJob::failed, this, &PackFetchTask::fileDownloadFailed);
@ -64,22 +64,19 @@ void PackFetchTask::fetch()
jobPtr->start();
}
void PackFetchTask::fetchPrivate(const QStringList & toFetch)
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", m_network);
for (auto& packCode : toFetch) {
auto data = std::make_shared<QByteArray>();
NetJob* job = new NetJob("Fetching private pack", m_network);
job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data));
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode]
{
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] {
ModpackList packs;
parseAndAddPacks(*data, PackType::Private, packs);
foreach(Modpack currentPack, packs)
{
foreach (Modpack currentPack, packs) {
currentPack.packCode = packCode;
emit privateFileDownloadFinished(currentPack);
}
@ -87,24 +84,20 @@ void PackFetchTask::fetchPrivate(const QStringList & toFetch)
job->deleteLater();
data->clear();
delete data;
});
QObject::connect(job, &NetJob::failed, this, [this, job, packCode, data](QString reason)
{
QObject::connect(job, &NetJob::failed, this, [this, job, packCode, data](QString reason) {
emit privateFileDownloadFailed(reason, packCode);
job->deleteLater();
data->clear();
delete data;
});
QObject::connect(job, &NetJob::aborted, this, [this, job, data]{
QObject::connect(job, &NetJob::aborted, this, [this, job, data] {
emit aborted();
job->deleteLater();
data->clear();
delete data;
});
job->start();
@ -117,27 +110,22 @@ void PackFetchTask::fileDownloadFinished()
QStringList failedLists;
if(!parseAndAddPacks(publicModpacksXmlFileData, PackType::Public, publicPacks))
{
if (!parseAndAddPacks(*publicModpacksXmlFileData, PackType::Public, publicPacks)) {
failedLists.append(tr("Public Packs"));
}
if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks))
{
if (!parseAndAddPacks(*thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks)) {
failedLists.append(tr("Third Party Packs"));
}
if(failedLists.size() > 0)
{
if (failedLists.size() > 0) {
emit failed(tr("Failed to download some pack lists: %1").arg(failedLists.join("\n- ")));
}
else
{
} else {
emit finished(publicPacks, thirdPartyPacks);
}
}
bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list)
bool PackFetchTask::parseAndAddPacks(QByteArray& data, PackType packType, ModpackList& list)
{
QDomDocument doc;
@ -145,8 +133,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
int errorLine = -1;
int errorCol = -1;
if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol))
{
if (!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)) {
auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:%3!").arg(errorMsg).arg(errorLine).arg(errorCol);
qWarning() << fullErrMsg;
data.clear();
@ -154,8 +141,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
}
QDomNodeList nodes = doc.elementsByTagName("modpack");
for(int i = 0; i < nodes.length(); i++)
{
for (int i = 0; i < nodes.length(); i++) {
QDomElement element = nodes.at(i).toElement();
Modpack modpack;
@ -169,26 +155,20 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
modpack.broken = false;
modpack.bugged = false;
//remove empty if the xml is bugged
for(QString curr : modpack.oldVersions)
{
if(curr.isNull() || curr.isEmpty())
{
// 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())
{
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
{
} else {
modpack.broken = true;
qWarning() << "Broken pack:" << modpack.name << " => No valid version!";
}
@ -218,4 +198,4 @@ void PackFetchTask::fileDownloadAborted()
emit aborted();
}
}
} // namespace LegacyFTB

View File

@ -1,41 +1,41 @@
#pragma once
#include "net/NetJob.h"
#include <QTemporaryDir>
#include <QByteArray>
#include <QObject>
#include <QTemporaryDir>
#include <memory>
#include "PackHelpers.h"
#include "net/NetJob.h"
namespace LegacyFTB {
class PackFetchTask : public QObject {
Q_OBJECT
public:
PackFetchTask(shared_qobject_ptr<QNetworkAccessManager> network) : QObject(nullptr), m_network(network) {};
public:
PackFetchTask(shared_qobject_ptr<QNetworkAccessManager> network) : QObject(nullptr), m_network(network){};
virtual ~PackFetchTask() = default;
void fetch();
void fetchPrivate(const QStringList &toFetch);
void fetchPrivate(const QStringList& toFetch);
private:
private:
shared_qobject_ptr<QNetworkAccessManager> m_network;
NetJob::Ptr jobPtr;
QByteArray publicModpacksXmlFileData;
QByteArray thirdPartyModpacksXmlFileData;
std::shared_ptr<QByteArray> publicModpacksXmlFileData = std::make_shared<QByteArray>();
std::shared_ptr<QByteArray> thirdPartyModpacksXmlFileData = std::make_shared<QByteArray>();
bool parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list);
bool parseAndAddPacks(QByteArray& data, PackType packType, ModpackList& list);
ModpackList publicPacks;
ModpackList thirdPartyPacks;
protected slots:
protected slots:
void fileDownloadFinished();
void fileDownloadFailed(QString reason);
void fileDownloadAborted();
signals:
signals:
void finished(ModpackList publicPacks, ModpackList thirdPartyPacks);
void failed(QString reason);
void aborted();
@ -44,4 +44,4 @@ signals:
void privateFileDownloadFailed(QString reason, QString packCode);
};
}
} // namespace LegacyFTB

View File

@ -9,19 +9,17 @@
#include "net/NetJob.h"
#include "net/Upload.h"
Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response)
Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response)
Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersions"), APPLICATION->network());
@ -35,8 +33,6 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
@ -44,7 +40,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response)
std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
@ -67,8 +63,6 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
netJob->addNetAction(Net::Upload::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
@ -76,7 +70,7 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response)
std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network());
@ -101,22 +95,16 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const
Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
auto searchUrl = getMultipleModInfoURL(addonIds);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob.get(), &NetJob::finished, [response, netJob] {
delete response;
});
return netJob;
}

View File

@ -12,27 +12,23 @@
class ModrinthAPI : public NetworkResourceAPI {
public:
auto currentVersion(QString hash,
QString hash_format,
QByteArray* response) -> Task::Ptr;
auto currentVersion(QString hash, QString hash_format, std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto currentVersions(const QStringList& hashes,
QString hash_format,
QByteArray* response) -> Task::Ptr;
auto currentVersions(const QStringList& hashes, QString hash_format, std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> Task::Ptr;
std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> Task::Ptr;
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr;
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
public:
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
@ -42,7 +38,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
{
QStringList l;
for (auto loader : {Forge, Fabric, Quilt}) {
for (auto loader : { Forge, Fabric, Quilt }) {
if (types & loader) {
l << getModLoaderString(loader);
}
@ -55,8 +51,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
{
QStringList l;
for (auto loader : getModLoaderStrings(types))
{
for (auto loader : getModLoaderStrings(types)) {
l << QString("\"categories:%1\"").arg(loader);
}
return l.join(',');
@ -139,16 +134,22 @@ class ModrinthAPI : public NetworkResourceAPI {
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
{
QString s;
for(auto& ver : mcVersions){
for (auto& ver : mcVersions) {
s += QString("\"versions:%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); //remove last comma
s.remove(s.length() - 1, 1); // remove last comma
return s.isEmpty() ? QString() : s;
}
inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool
{
return loaders & (Forge | Fabric | Quilt);
}
inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { return loaders & (Forge | Fabric | Quilt); }
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
return args.dependency.version.length() != 0 ? QString("%1/version/%2").arg(BuildConfig.MODRINTH_PROD_URL, args.dependency.version)
: QString("%1/project/%2/version?game_versions=[\"%3\"]&loaders=[\"%4\"]")
.arg(BuildConfig.MODRINTH_PROD_URL)
.arg(args.dependency.addonId.toString())
.arg(args.mcVersion.toString())
.arg(getModLoaderStrings(args.loader).join("\",\""));
};
};

View File

@ -53,12 +53,11 @@ void ModrinthCheckUpdate::executeTask()
// (though it will rarely happen, if at all)
if (mod->metadata()->hash_format != best_hash_type) {
auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath());
connect(hash_task.get(), &Task::succeeded, [&] {
QString hash(hash_task->getResult());
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, mod](QString hash) {
hashes.append(hash);
mappings.insert(hash, mod);
});
connect(hash_task.get(), &Task::failed, [this, hash_task] { failed("Failed to generate hash"); });
connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashing_task.addTask(hash_task);
} else {
hashes.append(hash);
@ -71,7 +70,7 @@ void ModrinthCheckUpdate::executeTask()
hashing_task.start();
loop.exec();
auto* response = new QByteArray();
auto response = std::make_shared<QByteArray>();
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response);
QEventLoop lock;

View File

@ -134,8 +134,8 @@ void ModrinthPackExportTask::collectHashes()
QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1);
sha1.addData(data);
ResolvedFile file{ sha1.result().toHex(), sha512.result().toHex(), url.toString(), openFile.size() };
resolvedFiles[relative] = file;
ResolvedFile resolvedFile{ sha1.result().toHex(), sha512.result().toHex(), url.toEncoded(), openFile.size() };
resolvedFiles[relative] = resolvedFile;
// nice! we've managed to resolve based on local metadata!
// no need to enqueue it
@ -157,7 +157,7 @@ void ModrinthPackExportTask::makeApiRequest()
if (pendingHashes.isEmpty())
buildZip();
else {
QByteArray* response = new QByteArray;
auto response = std::make_shared<QByteArray>();
task = api.currentVersions(pendingHashes.values(), "sha512", response);
connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); });
connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed);
@ -165,7 +165,7 @@ void ModrinthPackExportTask::makeApiRequest()
}
}
void ModrinthPackExportTask::parseApiResponse(const QByteArray* response)
void ModrinthPackExportTask::parseApiResponse(const std::shared_ptr<QByteArray> response)
{
task = nullptr;

View File

@ -69,7 +69,7 @@ class ModrinthPackExportTask : public Task {
void collectFiles();
void collectHashes();
void makeApiRequest();
void parseApiResponse(const QByteArray* response);
void parseApiResponse(const std::shared_ptr<QByteArray> response);
void buildZip();
void finish();

View File

@ -22,7 +22,7 @@
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "net/NetJob.h"
#include "modplatform/ModIndex.h"
static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
@ -140,6 +140,28 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
file.version_number = Json::requireString(obj, "version_number");
file.changelog = Json::requireString(obj, "changelog");
auto dependencies = Json::ensureArray(obj, "dependencies");
for (auto d : dependencies) {
auto dep = Json::ensureObject(d);
ModPlatform::Dependency dependency;
dependency.addonId = Json::ensureString(dep, "project_id");
dependency.version = Json::ensureString(dep, "version_id");
auto depType = Json::requireString(dep, "dependency_type");
if (depType == "required")
dependency.type = ModPlatform::DependencyType::REQUIRED;
else if (depType == "optional")
dependency.type = ModPlatform::DependencyType::OPTIONAL;
else if (depType == "incompatible")
dependency.type = ModPlatform::DependencyType::INCOMPATIBLE;
else if (depType == "embedded")
dependency.type = ModPlatform::DependencyType::EMBEDDED;
else
dependency.type = ModPlatform::DependencyType::UNKNOWN;
file.dependencies.append(dependency);
}
auto files = Json::requireArray(obj, "files");
int i = 0;
@ -195,3 +217,22 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
return {};
}
auto Modrinth::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
versions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
// dates are in RFC 3339 format
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
return versions.length() != 0 ? versions.front() : ModPlatform::IndexedVersion();
}

View File

@ -19,8 +19,8 @@
#include "modplatform/ModIndex.h"
#include "BaseInstance.h"
#include <QNetworkAccessManager>
#include "BaseInstance.h"
namespace Modrinth {
@ -31,5 +31,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
} // namespace Modrinth

View File

@ -37,20 +37,19 @@
#include <FileSystem.h>
#include <Json.h>
#include <QtConcurrentRun>
#include <MMCZip.h>
#include <QtConcurrentRun>
#include "TechnicPackProcessor.h"
#include "SolderPackManifest.h"
#include "TechnicPackProcessor.h"
#include "net/ChecksumValidator.h"
Technic::SolderPackInstallTask::SolderPackInstallTask(
shared_qobject_ptr<QNetworkAccessManager> network,
const QUrl &solderUrl,
const QString &pack,
const QString &version,
const QString &minecraftVersion
) {
Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
const QUrl& solderUrl,
const QString& pack,
const QString& version,
const QString& minecraftVersion)
{
m_solderUrl = solderUrl;
m_pack = pack;
m_version = version;
@ -58,9 +57,9 @@ Technic::SolderPackInstallTask::SolderPackInstallTask(
m_minecraftVersion = minecraftVersion;
}
bool Technic::SolderPackInstallTask::abort() {
if(m_abortable)
{
bool Technic::SolderPackInstallTask::abort()
{
if (m_abortable) {
return m_filesNetJob->abort();
}
return false;
@ -72,7 +71,7 @@ void Technic::SolderPackInstallTask::executeTask()
m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network));
auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version);
m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, &m_response));
m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, m_response));
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
@ -85,11 +84,11 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
{
setStatus(tr("Downloading modpack"));
QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error);
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*m_response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << m_response;
qWarning() << *m_response;
return;
}
auto obj = doc.object();
@ -110,7 +109,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
m_filesNetJob.reset(new NetJob(tr("Downloading modpack"), m_network));
int i = 0;
for (const auto &mod : build.mods) {
for (const auto& mod : build.mods) {
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
auto dl = Net::Download::makeFile(mod.url, path);

View File

@ -40,45 +40,48 @@
#include <tasks/Task.h>
#include <QUrl>
#include <memory>
namespace Technic
{
class SolderPackInstallTask : public InstanceTask
{
Q_OBJECT
public:
explicit SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, const QUrl &solderUrl, const QString& pack, const QString& version, const QString &minecraftVersion);
namespace Technic {
class SolderPackInstallTask : public InstanceTask {
Q_OBJECT
public:
explicit SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
const QUrl& solderUrl,
const QString& pack,
const QString& version,
const QString& minecraftVersion);
bool canAbort() const override { return true; }
bool abort() override;
bool canAbort() const override { return true; }
bool abort() override;
protected:
//! Entry point for tasks.
virtual void executeTask() override;
protected:
//! Entry point for tasks.
virtual void executeTask() override;
private slots:
void fileListSucceeded();
void downloadSucceeded();
void downloadFailed(QString reason);
void downloadProgressChanged(qint64 current, qint64 total);
void downloadAborted();
void extractFinished();
void extractAborted();
private slots:
void fileListSucceeded();
void downloadSucceeded();
void downloadFailed(QString reason);
void downloadProgressChanged(qint64 current, qint64 total);
void downloadAborted();
void extractFinished();
void extractAborted();
private:
bool m_abortable = false;
private:
bool m_abortable = false;
shared_qobject_ptr<QNetworkAccessManager> m_network;
shared_qobject_ptr<QNetworkAccessManager> m_network;
NetJob::Ptr m_filesNetJob;
QUrl m_solderUrl;
QString m_pack;
QString m_version;
QString m_minecraftVersion;
QByteArray m_response;
QTemporaryDir m_outputDir;
int m_modCount;
QFuture<bool> m_extractFuture;
QFutureWatcher<bool> m_extractFutureWatcher;
};
}
NetJob::Ptr m_filesNetJob;
QUrl m_solderUrl;
QString m_pack;
QString m_version;
QString m_minecraftVersion;
std::shared_ptr<QByteArray> m_response = std::make_shared<QByteArray>();
QTemporaryDir m_outputDir;
int m_modCount;
QFuture<bool> m_extractFuture;
QFutureWatcher<bool> m_extractFutureWatcher;
};
} // namespace Technic

View File

@ -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 &current_hash = iter->second.hash;
const auto &current_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 &current_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;
}
}

View File

@ -1,171 +0,0 @@
#pragma once
#include <QString>
#include <map>
#include <set>
#include <QStringList>
#include "tasks/Task.h"
namespace mojang_files {
using Hash = QString;
extern const Hash empty_hash;
// simple-ish path implementation. assumes always relative and does not allow '..' entries
class Path
{
public:
using parts_type = QStringList;
Path() = default;
Path(QString string) {
auto parts_in = string.split('/');
for(auto & part: parts_in) {
if(part.isEmpty() || part == ".") {
continue;
}
if(part == "..") {
if(parts.size()) {
parts.pop_back();
}
continue;
}
parts.push_back(part);
}
}
bool has_parent_path() const
{
return parts.size() > 0;
}
Path parent_path() const
{
if (parts.empty())
return Path();
return Path(parts.begin(), std::prev(parts.end()));
}
bool empty() const
{
return parts.empty();
}
int length() const
{
return parts.length();
}
bool operator==(const Path & rhs) const {
return parts == rhs.parts;
}
bool operator!=(const Path & rhs) const {
return parts != rhs.parts;
}
inline bool operator<(const Path& rhs) const
{
return compare(rhs) < 0;
}
parts_type::const_iterator begin() const
{
return parts.begin();
}
parts_type::const_iterator end() const
{
return parts.end();
}
QString toString() const {
return parts.join("/");
}
private:
Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) {
auto cursor = start;
while(cursor != end) {
parts.push_back(*cursor);
cursor++;
}
}
int compare(const Path& p) const;
parts_type parts;
};
enum class Compression {
Raw,
Lzma,
Unknown
};
struct 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 File
{
Hash hash;
bool executable;
std::uint64_t size = 0;
};
struct 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 FileDownload : FileSource
{
FileDownload(const FileSource& source, bool executable) {
static_cast<FileSource &> (*this) = source;
this->executable = executable;
}
bool executable = false;
};
struct 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;
};
}

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -46,7 +47,7 @@ namespace Net {
*/
class ByteArraySink : public Sink {
public:
ByteArraySink(QByteArray* output) : m_output(output){};
ByteArraySink(std::shared_ptr<QByteArray> output) : m_output(output){};
virtual ~ByteArraySink() = default;
@ -93,6 +94,6 @@ class ByteArraySink : public Sink {
auto hasLocalData() -> bool override { return false; }
private:
QByteArray* m_output;
std::shared_ptr<QByteArray> m_output;
};
} // namespace Net

View File

@ -41,6 +41,7 @@
#include <QDateTime>
#include <QFileInfo>
#include <memory>
#include "ByteArraySink.h"
#include "ChecksumValidator.h"
@ -69,7 +70,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
return dl;
}
auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> Download::Ptr
auto Download::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
{
auto dl = makeShared<Download>();
dl->m_url = url;

View File

@ -60,7 +60,7 @@ class Download : public NetAction {
~Download() override = default;
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeByteArray(QUrl url, QByteArray* output, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
public:

View File

@ -39,218 +39,226 @@
#include "Upload.h"
#include <utility>
#include "ByteArraySink.h"
#include "BuildConfig.h"
#include "Application.h"
#include "BuildConfig.h"
#include "ByteArraySink.h"
#include "net/Logging.h"
namespace Net {
bool Upload::abort()
{
if (m_reply) {
m_reply->abort();
} else {
m_state = State::AbortedByUser;
bool Upload::abort()
{
if (m_reply) {
m_reply->abort();
} else {
m_state = State::AbortedByUser;
}
return true;
}
void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
setProgress(bytesReceived, bytesTotal);
}
void Upload::downloadError(QNetworkReply::NetworkError error)
{
if (error == QNetworkReply::OperationCanceledError) {
qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser;
} else {
// error happened during download.
qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
}
}
void Upload::sslErrors(const QList<QSslError>& errors)
{
int i = 1;
for (const auto& error : errors) {
qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : "
<< error.errorString();
auto cert = error.certificate();
qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++;
}
}
bool Upload::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;
}
return true;
}
void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
setProgress(bytesReceived, bytesTotal);
}
void Upload::downloadError(QNetworkReply::NetworkError error) {
if (error == QNetworkReply::OperationCanceledError) {
qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser;
} else {
// error happened during download.
qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
// 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;
}
}
void Upload::sslErrors(const QList<QSslError> &errors) {
int i = 1;
for (const auto& error : errors) {
qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++;
}
}
bool Upload::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();
}
QString redirectStr = QString::fromUtf8(redirectBA);
if (redirectStr.startsWith("//")) {
/*
* 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
* 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
*/
redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) {
qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure);
return false;
}
qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect;
} else {
qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect;
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();
}
m_url = QUrl(redirect.toString());
qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
startAction(m_network);
return true;
/*
* 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()) {
qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure);
return false;
}
qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect;
} else {
qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect;
}
void Upload::downloadFinished() {
// handle HTTP redirection first
// very unlikely for post requests, still can happen
if (handleRedirect()) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString();
return;
}
m_url = QUrl(redirect.toString());
qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
startAction(m_network);
return true;
}
// if the download failed before this point ...
if (m_state == State::Succeeded) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit succeeded();
return;
} else if (m_state == State::Failed) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
} else if (m_state == State::AbortedByUser) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
return;
}
void Upload::downloadFinished()
{
// handle HTTP redirection first
// very unlikely for post requests, still can happen
if (handleRedirect()) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString();
return;
}
// make sure we got all the remaining data, if any
auto data = m_reply->readAll();
if (data.size()) {
qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data);
}
// otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
}
// if the download failed before this point ...
if (m_state == State::Succeeded) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString();
emit succeeded();
return;
} else if (m_state == State::Failed) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
} else if (m_state == State::AbortedByUser) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
return;
}
void Upload::downloadReadyRead() {
if (m_state == State::Running) {
auto data = m_reply->readAll();
m_state = m_sink->write(data);
}
// make sure we got all the remaining data, if any
auto data = m_reply->readAll();
if (data.size()) {
qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data);
}
void Upload::executeTask() {
setStatus(tr("Uploading %1").arg(m_url.toString()));
// otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
}
m_reply.reset();
qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString();
emit succeeded();
}
if (m_state == State::AbortedByUser) {
qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString();
emit aborted();
void Upload::downloadReadyRead()
{
if (m_state == State::Running) {
auto data = m_reply->readAll();
m_state = m_sink->write(data);
}
}
void Upload::executeTask()
{
setStatus(tr("Uploading %1").arg(m_url.toString()));
if (m_state == State::AbortedByUser) {
qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString();
emit aborted();
return;
}
QNetworkRequest request(m_url);
m_state = m_sink->init(request);
switch (m_state) {
case State::Succeeded:
emitSucceeded();
qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString();
return;
}
QNetworkRequest request(m_url);
m_state = m_sink->init(request);
switch (m_state) {
case State::Succeeded:
emitSucceeded();
qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString();
return;
case State::Running:
qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString();
break;
case State::Inactive:
case State::Failed:
emitFailed("");
return;
case State::AbortedByUser:
emitAborted();
return;
}
case State::Running:
qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString();
break;
case State::Inactive:
case State::Failed:
emitFailed("");
return;
case State::AbortedByUser:
emitAborted();
return;
}
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
// TODO remove duplication
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8());
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
QString token = APPLICATION->getModrinthAPIToken();
if (!token.isNull())
request.setRawHeader("Authorization", token.toUtf8());
}
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
// TODO remove duplication
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8());
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
QString token = APPLICATION->getModrinthAPIToken();
if (!token.isNull())
request.setRawHeader("Authorization", token.toUtf8());
}
//TODO other types of post requests ?
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* rep = m_network->post(request, m_post_data);
// TODO other types of post requests ?
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* rep = m_network->post(request, m_post_data);
m_reply.reset(rep);
connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError);
m_reply.reset(rep);
connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError);
#else
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Upload::downloadError);
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Upload::downloadError);
#endif
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
}
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
}
Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data) {
auto up = makeShared<Upload>();
up->m_url = std::move(url);
up->m_sink.reset(new ByteArraySink(output));
up->m_post_data = std::move(m_post_data);
return up;
}
} // Net
Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
{
auto up = makeShared<Upload>();
up->m_url = std::move(url);
up->m_sink.reset(new ByteArraySink(output));
up->m_post_data = std::move(m_post_data);
return up;
}
} // namespace Net

View File

@ -42,31 +42,31 @@
namespace Net {
class Upload : public NetAction {
Q_OBJECT
class Upload : public NetAction {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Upload>;
public:
using Ptr = shared_qobject_ptr<Upload>;
static Upload::Ptr makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data);
auto abort() -> bool override;
auto canAbort() const -> bool override { return true; };
static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
auto abort() -> bool override;
auto canAbort() const -> bool override { return true; };
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void sslErrors(const QList<QSslError> & errors) override;
void downloadFinished() override;
void downloadReadyRead() override;
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void sslErrors(const QList<QSslError>& errors) override;
void downloadFinished() override;
void downloadReadyRead() override;
public slots:
void executeTask() override;
private:
std::unique_ptr<Sink> m_sink;
QByteArray m_post_data;
public slots:
void executeTask() override;
bool handleRedirect();
};
private:
std::unique_ptr<Sink> m_sink;
QByteArray m_post_data;
} // Net
bool handleRedirect();
};
} // namespace Net

View File

@ -58,7 +58,7 @@ void NewsChecker::reloadNews()
qDebug() << "Reloading news.";
NetJob::Ptr job{ new NetJob("News RSS Feed", m_network) };
job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData));
job->addNetAction(Net::Download::makeByteArray(m_feedUrl, newsData));
QObject::connect(job.get(), &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished);
QObject::connect(job.get(), &NetJob::failed, this, &NewsChecker::rssDownloadFailed);
m_newsNetJob.reset(job);
@ -79,32 +79,27 @@ void NewsChecker::rssDownloadFinished()
int errorCol = -1;
// Parse the XML.
if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol))
{
if (!doc.setContent(*newsData, false, &errorMsg, &errorLine, &errorCol)) {
QString fullErrorMsg = QString("Error parsing RSS feed XML. %1 at %2:%3.").arg(errorMsg).arg(errorLine).arg(errorCol);
fail(fullErrorMsg);
newsData.clear();
newsData->clear();
return;
}
newsData.clear();
newsData->clear();
}
// If the parsing succeeded, read it.
QDomNodeList items = doc.elementsByTagName("entry");
m_newsEntries.clear();
for (int i = 0; i < items.length(); i++)
{
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))
{
if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) {
qDebug() << "Loaded news entry" << entry->title;
m_newsEntries.append(entry);
}
else
{
} else {
qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg;
}
}

View File

@ -85,7 +85,7 @@ protected: /* data */
//! True if news has been loaded.
bool m_loadedNews;
QByteArray newsData;
std::shared_ptr<QByteArray> newsData = std::make_shared<QByteArray>();
/*!
* Gets the error message that was given last time the news was loaded.

View File

@ -16,7 +16,6 @@
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
<file>scalable/language.svg</file>
<file>scalable/launcher.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<title>Prism Launcher Logo</title>
<g stroke-width=".26458">
<path d="m6.35 6.35" fill="#99cd61"/>
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
</g>
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
<g transform="translate(13.26 2.2776)">
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
</g>
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
</g>
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>Prism Launcher Logo</dc:title>
<dc:date>19/10/2022</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
</cc:Agent>
</dc:contributor>
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
<dc:publisher>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<title>Prism Launcher Logo</title>
<g stroke-width=".26458">
<path d="m6.35 6.35" fill="#99cd61"/>
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
</g>
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
<g transform="translate(13.26 2.2776)">
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
</g>
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
</g>
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>Prism Launcher Logo</dc:title>
<dc:date>19/10/2022</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
</cc:Agent>
</dc:contributor>
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
<dc:publisher>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -18,7 +18,6 @@
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
<file>scalable/language.svg</file>
<file>scalable/launcher.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<title>Prism Launcher Logo</title>
<g stroke-width=".26458">
<path d="m6.35 6.35" fill="#99cd61"/>
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
</g>
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
<g transform="translate(13.26 2.2776)">
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
</g>
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
</g>
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>Prism Launcher Logo</dc:title>
<dc:date>19/10/2022</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
</cc:Agent>
</dc:contributor>
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
<dc:publisher>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -18,7 +18,6 @@
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
<file>scalable/language.svg</file>
<file>scalable/launcher.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" fill="#eeeeee" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m20 4h-16v16h16zm0 18h-16c-1.1046 0-2-0.89543-2-2v-16c0-1.1046 0.89543-2 2-2h16c1.1046 0 2 0.89543 2 2v16c0 1.1046-0.89543 2-2 2z"/><path d="m7.2 18c-0.225 0-0.45-0.075-0.6-0.15-0.375-0.225-0.6-0.6-0.6-1.05v-9.6c0-0.45 0.225-0.825 0.6-1.05 0.225-0.15 0.375-0.15 0.6-0.15 0.15 0 0.375 0.075 0.525 0.15l9.6 4.8c0.375 0.225 0.675 0.6 0.675 1.05 0 0.45-0.225 0.9-0.675 1.05l-9.6 4.8c-0.15 0.075-0.375 0.15-0.525 0.15z" clip-rule="evenodd" fill="#eeeeee" fill-rule="evenodd" stroke-width=".99999"/></svg>

Before

Width:  |  Height:  |  Size: 660 B

View File

@ -16,7 +16,6 @@
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
<file>scalable/language.svg</file>
<file>scalable/launcher.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<title>Prism Launcher Logo</title>
<g stroke-width=".26458">
<path d="m6.35 6.35" fill="#99cd61"/>
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
</g>
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
<g transform="translate(13.26 2.2776)">
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
</g>
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
</g>
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>Prism Launcher Logo</dc:title>
<dc:date>19/10/2022</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
</cc:Agent>
</dc:contributor>
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
<dc:publisher>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -16,7 +16,6 @@
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
<file>scalable/language.svg</file>
<file>scalable/launcher.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<title>Prism Launcher Logo</title>
<g stroke-width=".26458">
<path d="m6.35 6.35" fill="#99cd61"/>
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
</g>
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
<g transform="translate(13.26 2.2776)">
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
</g>
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
</g>
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>Prism Launcher Logo</dc:title>
<dc:date>19/10/2022</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
</cc:Agent>
</dc:contributor>
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
<dc:publisher>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -16,7 +16,6 @@
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
<file>scalable/language.svg</file>
<file>scalable/launcher.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<title>Prism Launcher Logo</title>
<g stroke-width=".26458">
<path d="m6.35 6.35" fill="#99cd61"/>
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
</g>
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
<g transform="translate(13.26 2.2776)">
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
</g>
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
</g>
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>Prism Launcher Logo</dc:title>
<dc:date>19/10/2022</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
</cc:Agent>
</dc:contributor>
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
<dc:publisher>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -16,7 +16,6 @@
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
<file>scalable/language.svg</file>
<file>scalable/launcher.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<title>Prism Launcher Logo</title>
<g stroke-width=".26458">
<path d="m6.35 6.35" fill="#99cd61"/>
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
</g>
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
<g transform="translate(13.26 2.2776)">
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
</g>
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
</g>
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>Prism Launcher Logo</dc:title>
<dc:date>19/10/2022</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
</cc:Agent>
</dc:contributor>
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
<dc:publisher>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -16,7 +16,6 @@
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
<file>scalable/language.svg</file>
<file>scalable/launcher.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<title>Prism Launcher Logo</title>
<g stroke-width=".26458">
<path d="m6.35 6.35" fill="#99cd61"/>
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
</g>
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
<g transform="translate(13.26 2.2776)">
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
</g>
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
</g>
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>Prism Launcher Logo</dc:title>
<dc:date>19/10/2022</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
</cc:Agent>
</dc:contributor>
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
<dc:publisher>
<cc:Agent>
<dc:title>Prism Launcher</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -224,6 +224,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// disabled until we have an instance selected
ui->instanceToolBar->setEnabled(false);
setInstanceActionsEnabled(false);
// add a close button at the end of the main toolbar when running on gamescope / steam deck
// FIXME: detect if we don't have server side decorations instead
if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
ui->mainToolBar->addAction(ui->actionCloseWindow);
}
}
// add the toolbar toggles to the view menu
@ -1503,140 +1510,113 @@ void MainWindow::on_actionKillInstance_triggered()
void MainWindow::on_actionCreateInstanceShortcut_triggered()
{
if (m_selectedInstance)
{
auto desktopPath = FS::getDesktopDir();
if (desktopPath.isEmpty()) {
// TODO come up with an alternative solution (open "save file" dialog)
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
return;
}
if (!m_selectedInstance)
return;
auto desktopPath = FS::getDesktopDir();
if (desktopPath.isEmpty()) {
// TODO come up with an alternative solution (open "save file" dialog)
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
return;
}
QString desktopFilePath;
QString appPath = QApplication::applicationFilePath();
QString iconPath;
QStringList args;
#if defined(Q_OS_MACOS)
QString appPath = QApplication::applicationFilePath();
if (appPath.startsWith("/private/var/")) {
QMessageBox::critical(this, tr("Create instance shortcut"), tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
return;
}
if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()),
appPath, { "--launch", m_selectedInstance->id() },
m_selectedInstance->name(), "")) {
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
}
else
{
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
}
if (appPath.startsWith("/private/var/")) {
QMessageBox::critical(this, tr("Create instance shortcut"),
tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
return;
}
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
QString appPath = QApplication::applicationFilePath();
if (appPath.startsWith("/tmp/.mount_")) {
// AppImage!
appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
if (appPath.isEmpty())
{
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
}
else if (appPath.endsWith("/"))
{
appPath.chop(1);
}
if (appPath.startsWith("/tmp/.mount_")) {
// AppImage!
appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
if (appPath.isEmpty()) {
QMessageBox::critical(this, tr("Create instance shortcut"),
tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
} else if (appPath.endsWith("/")) {
appPath.chop(1);
}
}
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
if (icon == nullptr)
{
icon = APPLICATION->icons()->icon("grass");
}
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
if (icon == nullptr) {
icon = APPLICATION->icons()->icon("grass");
}
QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png");
iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png");
QFile iconFile(iconPath);
if (!iconFile.open(QFile::WriteOnly))
{
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
return;
}
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
iconFile.close();
QFile iconFile(iconPath);
if (!iconFile.open(QFile::WriteOnly)) {
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
return;
}
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
iconFile.close();
if (!success)
{
iconFile.remove();
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
return;
}
if (!success) {
iconFile.remove();
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
return;
}
if (DesktopServices::isFlatpak()) {
desktopFilePath = FS::PathCombine(desktopPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + ".desktop");
QFileDialog fileDialog;
// workaround to make sure the portal file dialog opens in the desktop directory
fileDialog.setDirectoryUrl(desktopPath);
desktopFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), desktopFilePath, tr("Desktop Entries (*.desktop)"));
if (desktopFilePath.isEmpty())
return; // file dialog canceled by user
appPath = "flatpak";
QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME;
flatpakAppId.remove(".desktop");
args.append({ "run", flatpakAppId });
}
QString desktopFilePath = FS::PathCombine(desktopPath, m_selectedInstance->name() + ".desktop");
QStringList args;
if (DesktopServices::isFlatpak()) {
QFileDialog fileDialog;
// workaround to make sure the portal file dialog opens in the desktop directory
fileDialog.setDirectoryUrl(desktopPath);
desktopFilePath = fileDialog.getSaveFileName(
this, tr("Create Shortcut"), desktopFilePath,
tr("Desktop Entries (*.desktop)"));
if (desktopFilePath.isEmpty())
return; // file dialog canceled by user
appPath = "flatpak";
QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME;
flatpakAppId.remove(".desktop");
args.append({ "run", flatpakAppId });
}
args.append({ "--launch", m_selectedInstance->id() });
if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
}
else
{
iconFile.remove();
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
}
#elif defined(Q_OS_WIN)
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
if (icon == nullptr)
{
icon = APPLICATION->icons()->icon("grass");
}
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
if (icon == nullptr) {
icon = APPLICATION->icons()->icon("grass");
}
QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico");
iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico");
// part of fix for weird bug involving the window icon being replaced
// dunno why it happens, but this 2-line fix seems to be enough, so w/e
auto appIcon = APPLICATION->getThemedIcon("logo");
// part of fix for weird bug involving the window icon being replaced
// dunno why it happens, but this 2-line fix seems to be enough, so w/e
auto appIcon = APPLICATION->getThemedIcon("logo");
QFile iconFile(iconPath);
if (!iconFile.open(QFile::WriteOnly))
{
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
return;
}
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
iconFile.close();
QFile iconFile(iconPath);
if (!iconFile.open(QFile::WriteOnly)) {
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
return;
}
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
iconFile.close();
// restore original window icon
QGuiApplication::setWindowIcon(appIcon);
// restore original window icon
QGuiApplication::setWindowIcon(appIcon);
if (!success)
{
iconFile.remove();
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
return;
}
if (!success) {
iconFile.remove();
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
return;
}
if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()),
QApplication::applicationFilePath(), { "--launch", m_selectedInstance->id() },
m_selectedInstance->name(), iconPath)) {
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
}
else
{
iconFile.remove();
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
}
#else
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!"));
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!"));
return;
#endif
args.append({ "--launch", m_selectedInstance->id() });
if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
} else {
#if not defined(Q_OS_MACOS)
iconFile.remove();
#endif
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
}
}

View File

@ -71,13 +71,18 @@ QString getCreditsHtml()
//: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Developers"
stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n";
stream << QString("<p>Sefa Eyeoglu (Scrumplex) %1</p>\n") .arg(getWebsite("https://scrumplex.net"));
stream << QString("<p>dada513 %1</p>\n") .arg(getGitHub("dada513"));
stream << QString("<p>d-513 %1</p>\n") .arg(getGitHub("d-513"));
stream << QString("<p>txtsd %1</p>\n") .arg(getWebsite("https://ihavea.quest"));
stream << QString("<p>timoreo %1</p>\n") .arg(getGitHub("timoreo22"));
stream << QString("<p>Ezekiel Smith (ZekeSmith) %1</p>\n") .arg(getGitHub("ZekeSmith"));
stream << QString("<p>cozyGalvinism %1</p>\n") .arg(getGitHub("cozyGalvinism"));
stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio"));
stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln"));
stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio"));
stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln"));
stream << QString("<p>ViRb3 %1</p>\n") .arg(getGitHub("ViRb3"));
stream << QString("<p>Rachel Powers (Ryex) %1</p>\n") .arg(getGitHub("Ryex"));
stream << QString("<p>TayouVR %1</p>\n") .arg(getGitHub("TayouVR"));
stream << QString("<p>TheKodeToad %1</p>\n") .arg(getGitHub("TheKodeToad"));
stream << QString("<p>getchoo %1</p>\n") .arg(getGitHub("getchoo"));
stream << "<br />\n";
// TODO: possibly retrieve from git history at build time?

View File

@ -35,24 +35,26 @@
*/
#include "ExportInstanceDialog.h"
#include "ui_ExportInstanceDialog.h"
#include <BaseInstance.h>
#include <MMCZip.h>
#include <QFileDialog>
#include <QMessageBox>
#include <QFileSystemModel>
#include <QMessageBox>
#include "FileIgnoreProxy.h"
#include "ui_ExportInstanceDialog.h"
#include <QSortFilterProxyModel>
#include <QDebug>
#include <QSaveFile>
#include <QStack>
#include <QFileInfo>
#include "SeparatorPrefixTree.h"
#include "Application.h"
#include <icons/IconList.h>
#include <FileSystem.h>
#include <icons/IconList.h>
#include <QDebug>
#include <QFileInfo>
#include <QSaveFile>
#include <QSortFilterProxyModel>
#include <QStack>
#include <functional>
#include "Application.h"
#include "SeparatorPrefixTree.h"
ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent)
ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent)
: QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance)
{
ui->setupUi(this);
@ -60,8 +62,12 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent
model->setIconProvider(&icons);
auto root = instance->instanceRoot();
proxyModel = new FileIgnoreProxy(root, this);
loadPackIgnore();
proxyModel->setSourceModel(model);
auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot());
proxyModel->ignoreFilesWithPath().insert({ FS::PathCombine(prefix, "logs"), FS::PathCombine(prefix, "crash-reports") });
proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" });
loadPackIgnore();
ui->treeView->setModel(proxyModel);
ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root)));
ui->treeView->sortByColumn(0, Qt::AscendingOrder);
@ -133,11 +139,9 @@ bool ExportInstanceDialog::doExport()
SaveIcon(m_instance);
auto & blocked = proxyModel->blockedPaths();
using std::placeholders::_1;
auto files = QFileInfoList();
if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files,
std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) {
std::bind(&FileIgnoreProxy::filterFile, proxyModel, std::placeholders::_1))) {
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
return false;
}

View File

@ -52,8 +52,9 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent)
// use the game root - everything outside cannot be exported
const QDir root(instance->gameRoot());
proxy = new FileIgnoreProxy(instance->gameRoot(), this);
proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports" });
proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" });
proxy->setSourceModel(model);
proxy->setFilterRegularExpression("^(?!\\.DS_Store).+$");
const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden);
@ -99,7 +100,7 @@ void ExportMrPackDialog::done(int result)
return;
ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
[this](const QString& path) { return proxy->blockedPaths().covers(path); });
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
connect(&task, &Task::failed,
[this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });

View File

@ -54,7 +54,7 @@
#include <utility>
#include "ui/widgets/PageContainer.h"
#include "ui/pages/modplatform/VanillaPage.h"
#include "ui/pages/modplatform/CustomPage.h"
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
@ -162,7 +162,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
importPage = new ImportPage(this);
pages.append(new VanillaPage(this));
pages.append(new CustomPage(this));
pages.append(importPage);
pages.append(new AtlPage(this));
if (APPLICATION->capabilities() & Application::SupportsFlame)

View File

@ -32,7 +32,7 @@ NewsDialog::~NewsDialog()
void NewsDialog::selectedArticleChanged(const QString& new_title)
{
auto const& article_entry = m_entries.constFind(new_title).value();
auto article_entry = m_entries.constFind(new_title).value();
ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title));

View File

@ -18,6 +18,8 @@
*/
#include "ResourceDownloadDialog.h"
#include <QEventLoop>
#include <QList>
#include <QPushButton>
#include <algorithm>
@ -30,6 +32,10 @@
#include "minecraft/mod/ShaderPackFolderModel.h"
#include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ReviewMessageBox.h"
#include "ui/pages/modplatform/ResourcePage.h"
@ -117,18 +123,71 @@ void ResourceDownloadDialog::connectButtons()
connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
}
static ModPlatform::ProviderCapabilities ProviderCaps;
QStringList getRequiredBy(QList<ResourceDownloadDialog::DownloadTaskPtr> tasks, ResourceDownloadDialog::DownloadTaskPtr pack)
{
auto addonId = pack->getPack()->addonId;
auto provider = pack->getPack()->provider;
auto version = pack->getVersionID();
auto req = QStringList();
for (auto& task : tasks) {
if (provider != task->getPack()->provider)
continue;
auto deps = task->getVersion().dependencies;
if (auto dep = std::find_if(deps.begin(), deps.end(),
[addonId, provider, version](const ModPlatform::Dependency& d) {
return d.type == ModPlatform::DependencyType::REQUIRED &&
(provider == ModPlatform::ResourceProvider::MODRINTH && d.addonId.toString().isEmpty()
? version == d.version
: d.addonId == addonId);
});
dep != deps.end()) {
req.append(task->getName());
}
}
return req;
}
void ResourceDownloadDialog::confirm()
{
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
confirm_dialog->retranslateUi(resourcesString());
if (auto task = getModDependenciesTask(); task) {
connect(task.get(), &Task::failed, this,
[&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
connect(task.get(), &Task::succeeded, this, [&]() {
QStringList warnings = task->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
}
});
// Check for updates
ProgressDialog progress_dialog(this);
progress_dialog.setSkipButton(true, tr("Abort"));
progress_dialog.setWindowTitle(tr("Checking for dependencies..."));
auto ret = progress_dialog.execWithTask(task.get());
// If the dialog was skipped / some download error happened
if (ret == QDialog::DialogCode::Rejected) {
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
return;
} else {
for (auto dep : task->getDependecies())
addResource(dep->pack, dep->version);
}
}
auto selected = getTasks();
std::sort(selected.begin(), selected.end(), [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) {
return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0;
});
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
confirm_dialog->retranslateUi(resourcesString());
for (auto& task : selected) {
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath() });
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
ProviderCaps.name(task->getProvider()), getRequiredBy(selected, task) });
}
if (confirm_dialog->exec()) {
@ -231,6 +290,19 @@ QList<BasePage*> ModDownloadDialog::getPages()
return pages;
}
GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask()
{
if (auto model = dynamic_cast<ModFolderModel*>(getBaseModel().get()); model) {
QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> selectedVers;
for (auto& selected : getTasks()) {
selectedVers.append(std::make_shared<GetModDependenciesTask::PackDependency>(selected->getPack(), selected->getVersion()));
}
return makeShared<GetModDependenciesTask>(this, m_instance, model, selectedVers);
}
return nullptr;
};
ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent,
const std::shared_ptr<ResourcePackFolderModel>& resource_packs,
BaseInstance* instance)

View File

@ -25,6 +25,7 @@
#include <QLayout>
#include "QObjectPtr.h"
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h"
#include "ui/pages/BasePageProvider.h"
@ -81,6 +82,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
[[nodiscard]] virtual QString geometrySaveKey() const { return ""; }
void setButtonStatus();
[[nodiscard]] virtual GetModDependenciesTask::Ptr getModDependenciesTask() { return nullptr; }
protected:
const std::shared_ptr<ResourceFolderModel> m_base_model;
@ -103,6 +106,7 @@ class ModDownloadDialog final : public ResourceDownloadDialog {
[[nodiscard]] QString geometrySaveKey() const override { return "ModDownloadGeometry"; }
QList<BasePage*> getPages() override;
GetModDependenciesTask::Ptr getModDependenciesTask() override;
private:
BaseInstance* m_instance;

View File

@ -40,7 +40,8 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
auto filenameItem = new QTreeWidgetItem(itemTop);
filenameItem->setText(0, tr("Filename: %1").arg(info.filename));
itemTop->insertChildren(0, { filenameItem });
auto childIndx = 0;
itemTop->insertChildren(childIndx++, { filenameItem });
if (!info.custom_file_path.isEmpty()) {
auto customPathItem = new QTreeWidgetItem(itemTop);
@ -49,7 +50,31 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
itemTop->insertChildren(1, { customPathItem });
itemTop->setIcon(1, QIcon(APPLICATION->getThemedIcon("status-yellow")));
itemTop->setToolTip(1, tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it."));
itemTop->setToolTip(
childIndx++,
tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it."));
}
auto providerItem = new QTreeWidgetItem(itemTop);
providerItem->setText(0, tr("Provider: %1").arg(info.provider));
itemTop->insertChildren(childIndx++, { providerItem });
if (!info.required_by.isEmpty()) {
auto requiredByItem = new QTreeWidgetItem(itemTop);
if (info.required_by.length() == 1) {
requiredByItem->setText(0, tr("Required by: %1").arg(info.required_by.back()));
} else {
requiredByItem->setText(0, tr("Required by:"));
auto i = 0;
for (auto req : info.required_by) {
auto reqItem = new QTreeWidgetItem(requiredByItem);
reqItem->setText(0, req);
reqItem->insertChildren(i++, { reqItem });
}
}
itemTop->insertChildren(childIndx++, { requiredByItem });
}
ui->modTreeWidget->addTopLevelItem(itemTop);

View File

@ -15,7 +15,9 @@ class ReviewMessageBox : public QDialog {
using ResourceInformation = struct res_info {
QString name;
QString filename;
QString custom_file_path {};
QString custom_file_path{};
QString provider;
QStringList required_by;
};
void appendResource(ResourceInformation&& info);

View File

@ -44,7 +44,7 @@
class BasePage {
public:
using updateExtraInfoFunc = std::function<void(QString)>;
using updateExtraInfoFunc = std::function<void(QString, QString)>;
virtual ~BasePage() {}
virtual QString id() const = 0;
virtual QString displayName() const = 0;

View File

@ -83,7 +83,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current);
auto updateExtra = [this]() {
if (updateExtraInfo)
updateExtraInfo(extraHeaderInfoString());
updateExtraInfo(id(), extraHeaderInfoString());
};
connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra);
connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);

View File

@ -85,12 +85,12 @@ void InstanceSettingsPage::globalSettingsButtonClicked(bool)
case 0:
APPLICATION->ShowGlobalSettings(this, "java-settings");
return;
case 1:
APPLICATION->ShowGlobalSettings(this, "minecraft-settings");
return;
case 2:
APPLICATION->ShowGlobalSettings(this, "custom-commands");
return;
default:
APPLICATION->ShowGlobalSettings(this, "minecraft-settings");
return;
}
}

View File

@ -205,7 +205,7 @@ ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWin
{
Q_ASSERT(inst->isManagedPack());
connect(ui->versionsComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(suggestVersion()));
connect(ui->updateButton, &QPushButton::pressed, this, &ModrinthManagedPackPage::update);
connect(ui->updateButton, &QPushButton::clicked, this, &ModrinthManagedPackPage::update);
}
// MODRINTH
@ -226,7 +226,7 @@ void ModrinthManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID();
m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response.get()));
m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
QJsonParseError parse_error{};
@ -332,7 +332,7 @@ FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* i
{
Q_ASSERT(inst->isManagedPack());
connect(ui->versionsComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(suggestVersion()));
connect(ui->updateButton, &QPushButton::pressed, this, &FlameManagedPackPage::update);
connect(ui->updateButton, &QPushButton::clicked, this, &FlameManagedPackPage::update);
}
void FlameManagedPackPage::parseManagedPack()
@ -369,7 +369,7 @@ void FlameManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID();
m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response.get()));
m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response));
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
QJsonParseError parse_error{};

View File

@ -36,6 +36,7 @@
*/
#include "ScreenshotsPage.h"
#include "BuildConfig.h"
#include "ui_ScreenshotsPage.h"
#include <QModelIndex>
@ -380,16 +381,18 @@ void ScreenshotsPage::on_actionUpload_triggered()
if (selection.isEmpty())
return;
QString text;
QUrl baseUrl(BuildConfig.IMGUR_BASE_URL);
if (selection.size() > 1)
text = tr("You are about to upload %1 screenshots.\n\n"
text = tr("You are about to upload %1 screenshots to %2.\n"
"You should double-check for personal information.\n\n"
"Are you sure?")
.arg(selection.size());
.arg(QString::number(selection.size()), baseUrl.host());
else
text =
tr("You are about to upload the selected screenshot.\n\n"
"Are you sure?");
text = tr("You are about to upload the selected screenshot to %1.\n"
"You should double-check for personal information.\n\n"
"Are you sure?")
.arg(baseUrl.host());
auto response = CustomMessageBox::selectable(this, "Confirm Upload", text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No,
QMessageBox::No)

View File

@ -287,7 +287,6 @@ void VersionPage::updateButtons(int row)
ui->actionAdd_Empty->setEnabled(controlsEnabled);
ui->actionImport_Components->setEnabled(controlsEnabled);
ui->actionReload->setEnabled(controlsEnabled);
ui->actionInstall_mods->setEnabled(controlsEnabled);
ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled);
ui->actionAdd_to_Minecraft_jar->setEnabled(controlsEnabled);
ui->actionAdd_Agents->setEnabled(controlsEnabled);

View File

@ -102,7 +102,6 @@
<addaction name="actionInstall_Fabric"/>
<addaction name="actionInstall_Quilt"/>
<addaction name="actionInstall_LiteLoader"/>
<addaction name="actionInstall_mods"/>
<addaction name="separator"/>
<addaction name="actionAdd_to_Minecraft_jar"/>
<addaction name="actionReplace_Minecraft_jar"/>
@ -112,7 +111,6 @@
<addaction name="separator"/>
<addaction name="actionMinecraftFolder"/>
<addaction name="actionLibrariesFolder"/>
<addaction name="separator"/>
<addaction name="actionReload"/>
<addaction name="actionDownload_All"/>
</widget>
@ -204,14 +202,6 @@
<string>Install the LiteLoader package.</string>
</property>
</action>
<action name="actionInstall_mods">
<property name="text">
<string>Install mods</string>
</property>
<property name="toolTip">
<string>Install normal mods.</string>
</property>
</action>
<action name="actionAdd_to_Minecraft_jar">
<property name="text">
<string>Add to Minecraft.jar</string>

View File

@ -33,8 +33,8 @@
* limitations under the License.
*/
#include "VanillaPage.h"
#include "ui_VanillaPage.h"
#include "CustomPage.h"
#include "ui_CustomPage.h"
#include <QTabBar>
@ -46,32 +46,32 @@
#include "minecraft/VanillaInstanceCreationTask.h"
#include "ui/dialogs/NewInstanceDialog.h"
VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent)
: QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage)
CustomPage::CustomPage(NewInstanceDialog *dialog, QWidget *parent)
: QWidget(parent), dialog(dialog), ui(new Ui::CustomPage)
{
ui->setupUi(this);
ui->tabWidget->tabBar()->hide();
connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedVersion);
connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &CustomPage::setSelectedVersion);
filterChanged();
connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->betaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh);
connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
connect(ui->betaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
connect(ui->refreshBtn, &QPushButton::clicked, this, &CustomPage::refresh);
connect(ui->loaderVersionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedLoaderVersion);
connect(ui->noneFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged);
connect(ui->forgeFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged);
connect(ui->fabricFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged);
connect(ui->quiltFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged);
connect(ui->liteLoaderFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged);
connect(ui->loaderRefreshBtn, &QPushButton::clicked, this, &VanillaPage::loaderRefresh);
connect(ui->loaderVersionList, &VersionSelectWidget::selectedVersionChanged, this, &CustomPage::setSelectedLoaderVersion);
connect(ui->noneFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
connect(ui->forgeFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
connect(ui->fabricFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
connect(ui->quiltFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
connect(ui->liteLoaderFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
connect(ui->loaderRefreshBtn, &QPushButton::clicked, this, &CustomPage::loaderRefresh);
}
void VanillaPage::openedImpl()
void CustomPage::openedImpl()
{
if(!initialized)
{
@ -85,19 +85,19 @@ void VanillaPage::openedImpl()
}
}
void VanillaPage::refresh()
void CustomPage::refresh()
{
ui->versionList->loadList();
}
void VanillaPage::loaderRefresh()
void CustomPage::loaderRefresh()
{
if(ui->noneFilter->isChecked())
return;
ui->loaderVersionList->loadList();
}
void VanillaPage::filterChanged()
void CustomPage::filterChanged()
{
QStringList out;
if(ui->alphaFilter->isChecked())
@ -116,7 +116,7 @@ void VanillaPage::filterChanged()
ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false));
}
void VanillaPage::loaderFilterChanged()
void CustomPage::loaderFilterChanged()
{
QString minecraftVersion;
if (m_selectedVersion)
@ -172,37 +172,37 @@ void VanillaPage::loaderFilterChanged()
ui->loaderVersionList->setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion));
}
VanillaPage::~VanillaPage()
CustomPage::~CustomPage()
{
delete ui;
}
bool VanillaPage::shouldDisplay() const
bool CustomPage::shouldDisplay() const
{
return true;
}
void VanillaPage::retranslate()
void CustomPage::retranslate()
{
ui->retranslateUi(this);
}
BaseVersion::Ptr VanillaPage::selectedVersion() const
BaseVersion::Ptr CustomPage::selectedVersion() const
{
return m_selectedVersion;
}
BaseVersion::Ptr VanillaPage::selectedLoaderVersion() const
BaseVersion::Ptr CustomPage::selectedLoaderVersion() const
{
return m_selectedLoaderVersion;
}
QString VanillaPage::selectedLoader() const
QString CustomPage::selectedLoader() const
{
return m_selectedLoader;
}
void VanillaPage::suggestCurrent()
void CustomPage::suggestCurrent()
{
if (!isOpened)
{
@ -227,14 +227,14 @@ void VanillaPage::suggestCurrent()
dialog->setSuggestedIcon("default");
}
void VanillaPage::setSelectedVersion(BaseVersion::Ptr version)
void CustomPage::setSelectedVersion(BaseVersion::Ptr version)
{
m_selectedVersion = version;
suggestCurrent();
loaderFilterChanged();
}
void VanillaPage::setSelectedLoaderVersion(BaseVersion::Ptr version)
void CustomPage::setSelectedLoaderVersion(BaseVersion::Ptr version)
{
m_selectedLoaderVersion = version;
suggestCurrent();

View File

@ -43,21 +43,21 @@
namespace Ui
{
class VanillaPage;
class CustomPage;
}
class NewInstanceDialog;
class VanillaPage : public QWidget, public BasePage
class CustomPage : public QWidget, public BasePage
{
Q_OBJECT
public:
explicit VanillaPage(NewInstanceDialog *dialog, QWidget *parent = 0);
virtual ~VanillaPage();
explicit CustomPage(NewInstanceDialog *dialog, QWidget *parent = 0);
virtual ~CustomPage();
virtual QString displayName() const override
{
return tr("Vanilla");
return tr("Custom");
}
virtual QIcon icon() const override
{
@ -96,7 +96,7 @@ private:
private:
bool initialized = false;
NewInstanceDialog *dialog = nullptr;
Ui::VanillaPage *ui = nullptr;
Ui::CustomPage *ui = nullptr;
bool m_versionSetByUser = false;
BaseVersion::Ptr m_selectedVersion;
BaseVersion::Ptr m_selectedLoaderVersion;

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VanillaPage</class>
<widget class="QWidget" name="VanillaPage">
<class>CustomPage</class>
<widget class="QWidget" name="CustomPage">
<property name="geometry">
<rect>
<x>0</x>

View File

@ -32,6 +32,7 @@ class ModModel : public ResourceModel {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override = 0;
virtual ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) = 0;
void setFilter(std::shared_ptr<ModFilterWidget::Filter> filter) { m_filter = filter; }

View File

@ -16,62 +16,49 @@
#include "AtlListModel.h"
#include <BuildConfig.h>
#include <Application.h>
#include <BuildConfig.h>
#include <Json.h>
namespace Atl {
ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
{
}
ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
ListModel::~ListModel()
{
}
ListModel::~ListModel() {}
int ListModel::rowCount(const QModelIndex &parent) const
int ListModel::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : modpacks.size();
}
int ListModel::columnCount(const QModelIndex &parent) const
int ListModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : 1;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
QVariant ListModel::data(const QModelIndex& index, int role) const
{
int pos = index.row();
if(pos >= modpacks.size() || pos < 0 || !index.isValid())
{
if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
return QString("INVALID INDEX %1").arg(pos);
}
ATLauncher::IndexedPack pack = modpacks.at(pos);
if(role == Qt::DisplayRole)
{
if (role == Qt::DisplayRole) {
return pack.name;
}
else if (role == Qt::ToolTipRole)
{
} else if (role == Qt::ToolTipRole) {
return pack.name;
}
else if(role == Qt::DecorationRole)
{
if(m_logoMap.contains(pack.safeName))
{
} else if (role == Qt::DecorationRole) {
if (m_logoMap.contains(pack.safeName)) {
return (m_logoMap.value(pack.safeName));
}
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
((ListModel *)this)->requestLogo(pack.safeName, url);
((ListModel*)this)->requestLogo(pack.safeName, url);
return icon;
}
else if(role == Qt::UserRole)
{
} else if (role == Qt::UserRole) {
QVariant v;
v.setValue(pack);
return v;
@ -88,7 +75,7 @@ void ListModel::request()
auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
jobPtr = netJob;
jobPtr->start();
@ -101,36 +88,38 @@ void ListModel::requestFinished()
jobPtr.reset();
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) {
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
qWarning() << *response;
return;
}
QList<ATLauncher::IndexedPack> newList;
auto packs = doc.array();
for(auto packRaw : packs) {
for (auto packRaw : packs) {
auto packObj = packRaw.toObject();
ATLauncher::IndexedPack pack;
try {
ATLauncher::loadIndexedPack(pack, packObj);
}
catch (const JSONValidationError &e) {
qDebug() << QString::fromUtf8(response);
} catch (const JSONValidationError& e) {
qDebug() << QString::fromUtf8(*response);
qWarning() << "Error while reading pack manifest from ATLauncher: " << e.cause();
return;
}
// ignore packs without a published version
if(pack.versions.length() == 0) continue;
if (pack.versions.length() == 0)
continue;
// only display public packs (for now)
if(pack.type != ATLauncher::PackType::Public) continue;
if (pack.type != ATLauncher::PackType::Public)
continue;
// ignore "system" packs (Vanilla, Vanilla with Forge, etc)
if(pack.system) continue;
if (pack.system)
continue;
newList.append(pack);
}
@ -145,14 +134,12 @@ void ListModel::requestFailed(QString reason)
jobPtr.reset();
}
void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{
if(m_logoMap.contains(logo))
{
callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
}
else
{
if (m_logoMap.contains(logo)) {
callback(
APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
} else {
requestLogo(logo, logoUrl);
}
}
@ -168,36 +155,34 @@ void ListModel::logoLoaded(QString logo, QIcon out)
m_loadingLogos.removeAll(logo);
m_logoMap.insert(logo, out);
for(int i = 0; i < modpacks.size(); i++) {
if(modpacks[i].safeName == logo) {
emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
for (int i = 0; i < modpacks.size(); i++) {
if (modpacks[i].safeName == logo) {
emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole });
}
}
}
void ListModel::requestLogo(QString file, QString url)
{
if(m_loadingLogos.contains(file) || m_failedLogos.contains(file))
{
if (m_loadingLogos.contains(file) || m_failedLogos.contains(file)) {
return;
}
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
NetJob *job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath]
{
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] {
job->deleteLater();
emit logoLoaded(file, QIcon(fullPath));
if(waitingCallbacks.contains(file))
{
if (waitingCallbacks.contains(file)) {
waitingCallbacks.value(file)(fullPath);
}
});
QObject::connect(job, &NetJob::failed, this, [this, file]
{
QObject::connect(job, &NetJob::failed, this, [this, file, job] {
job->deleteLater();
emit logoFailed(file);
});
@ -206,4 +191,4 @@ void ListModel::requestLogo(QString file, QString url)
m_loadingLogos.append(file);
}
}
} // namespace Atl

View File

@ -18,42 +18,41 @@
#include <QAbstractListModel>
#include "net/NetJob.h"
#include <QIcon>
#include <modplatform/atlauncher/ATLPackIndex.h>
#include <QIcon>
#include "net/NetJob.h"
namespace Atl {
typedef QMap<QString, QIcon> LogoMap;
typedef std::function<void(QString)> LogoCallback;
class ListModel : public QAbstractListModel
{
class ListModel : public QAbstractListModel {
Q_OBJECT
public:
ListModel(QObject *parent);
public:
ListModel(QObject* parent);
virtual ~ListModel();
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
void request();
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
private slots:
private slots:
void requestFinished();
void requestFailed(QString reason);
void logoFailed(QString logo);
void logoLoaded(QString logo, QIcon out);
private:
private:
void requestLogo(QString file, QString url);
private:
private:
QList<ATLauncher::IndexedPack> modpacks;
QStringList m_failedLogos;
@ -62,7 +61,7 @@ private:
QMap<QString, LogoCallback> waitingCallbacks;
NetJob::Ptr jobPtr;
QByteArray response;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
}
} // namespace Atl

View File

@ -152,7 +152,7 @@ Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
void AtlOptionalModListModel::useShareCode(const QString& code) {
m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));
auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code);
m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), &m_response));
m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), m_response));
connect(m_jobPtr.get(), &NetJob::succeeded,
this, &AtlOptionalModListModel::shareCodeSuccess);
@ -166,10 +166,10 @@ void AtlOptionalModListModel::shareCodeSuccess() {
m_jobPtr.reset();
QJsonParseError parse_error {};
auto doc = QJsonDocument::fromJson(m_response, &parse_error);
auto doc = QJsonDocument::fromJson(*m_response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << m_response;
qWarning() << *m_response;
return;
}
auto obj = doc.object();
@ -179,7 +179,7 @@ void AtlOptionalModListModel::shareCodeSuccess() {
ATLauncher::loadShareCodeResponse(response, obj);
}
catch (const JSONValidationError& e) {
qDebug() << QString::fromUtf8(m_response);
qDebug() << QString::fromUtf8(*m_response);
qWarning() << "Error while reading response from ATLauncher: " << e.cause();
return;
}

View File

@ -82,9 +82,9 @@ private:
void toggleMod(ATLauncher::VersionMod mod, int index);
void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true);
private:
private:
NetJob::Ptr m_jobPtr;
QByteArray m_response;
std::shared_ptr<QByteArray> m_response = std::make_shared<QByteArray>();
ATLauncher::PackVersion m_version;
QVector<ATLauncher::VersionMod> m_mods;

View File

@ -42,15 +42,15 @@
class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInteractionSupport {
Q_OBJECT
public:
public:
AtlUserInteractionSupportImpl(QWidget* parent);
virtual ~AtlUserInteractionSupportImpl() = default;
private:
private:
QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override;
std::optional<QVector<QString>> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
void displayMessage(QString message) override;
private:
private:
QWidget* m_parent;
};

View File

@ -171,7 +171,7 @@ void ListModel::performPaginatedSearch()
.arg(currentSearchTerm)
.arg(currentSort + 1);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@ -204,11 +204,11 @@ void Flame::ListModel::searchRequestFinished()
jobPtr.reset();
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << response;
qWarning() << *response;
return;
}

View File

@ -3,46 +3,44 @@
#include <RWStorage.h>
#include <QAbstractListModel>
#include <QSortFilterProxyModel>
#include <QThreadPool>
#include <QIcon>
#include <QStyledItemDelegate>
#include <QList>
#include <QMetaType>
#include <QSortFilterProxyModel>
#include <QString>
#include <QStringList>
#include <QMetaType>
#include <QStyledItemDelegate>
#include <QThreadPool>
#include <functional>
#include <net/NetJob.h>
#include <functional>
#include <modplatform/flame/FlamePackIndex.h>
namespace Flame {
typedef QMap<QString, QIcon> LogoMap;
typedef std::function<void(QString)> LogoCallback;
class ListModel : public QAbstractListModel
{
class ListModel : public QAbstractListModel {
Q_OBJECT
public:
ListModel(QObject *parent);
public:
ListModel(QObject* parent);
virtual ~ListModel();
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool canFetchMore(const QModelIndex & parent) const override;
void fetchMore(const QModelIndex & parent) override;
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
bool canFetchMore(const QModelIndex& parent) const override;
void fetchMore(const QModelIndex& parent) override;
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
void searchWithTerm(const QString & term, const int sort);
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term, const int sort);
private slots:
private slots:
void performPaginatedSearch();
void logoFailed(QString logo);
@ -51,10 +49,10 @@ private slots:
void searchRequestFinished();
void searchRequestFailed(QString reason);
private:
private:
void requestLogo(QString file, QString url);
private:
private:
QList<IndexedPack> modpacks;
QStringList m_failedLogos;
QStringList m_loadingLogos;
@ -64,14 +62,9 @@ private:
QString currentSearchTerm;
int currentSort = 0;
int nextSearchOffset = 0;
enum SearchState {
None,
CanPossiblyFetchMore,
ResetRequested,
Finished
} searchState = None;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
NetJob::Ptr jobPtr;
QByteArray response;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
}
} // namespace Flame

View File

@ -130,7 +130,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
if (current.versionsLoaded == false) {
qDebug() << "Loading flame modpack versions";
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
auto response = new QByteArray();
auto response = std::make_shared<QByteArray>();
int addonId = current.addonId;
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
@ -170,10 +170,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
}
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
netJob->deleteLater();
delete response;
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
netJob->start();
} else {
for (auto version : current.versions) {

View File

@ -29,6 +29,11 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
}
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
return FlameMod::loadDependencyVersions(m, arr);
};
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
return Json::ensureArray(obj.object(), "data");
@ -81,7 +86,7 @@ void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m,
auto const& mc_versions = version.mcVersion;
if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(),
[this](auto const& mc_version){ return Version(mc_version) <= maximumTexturePackVersion(); }))
[this](auto const& mc_version) { return Version(mc_version) <= maximumTexturePackVersion(); }))
filtered_versions.push_back(version);
}

Some files were not shown because too many files have changed in this diff Show More