Merge pull request #426 from flowln/mod_perma

Add on-disk mod metadata information
This commit is contained in:
Sefa Eyeoglu 2022-06-04 13:23:38 +02:00 committed by GitHub
commit 1ab00ca8b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1560 additions and 385 deletions

View File

@ -643,6 +643,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Minecraft launch method
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
// Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false);
// Minecraft offline player name
m_settings->registerSetting("LastOfflinePlayerName", "");

View File

@ -324,19 +324,22 @@ set(MINECRAFT_SOURCES
minecraft/WorldList.h
minecraft/WorldList.cpp
minecraft/mod/MetadataHandler.h
minecraft/mod/Mod.h
minecraft/mod/Mod.cpp
minecraft/mod/ModDetails.h
minecraft/mod/ModFolderModel.h
minecraft/mod/ModFolderModel.cpp
minecraft/mod/ModFolderLoadTask.h
minecraft/mod/ModFolderLoadTask.cpp
minecraft/mod/LocalModParseTask.h
minecraft/mod/LocalModParseTask.cpp
minecraft/mod/ResourcePackFolderModel.h
minecraft/mod/ResourcePackFolderModel.cpp
minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/tasks/ModFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.cpp
minecraft/mod/tasks/LocalModParseTask.h
minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp
# Assets
minecraft/AssetsUtils.h
@ -499,6 +502,9 @@ set(META_SOURCES
)
set(API_SOURCES
modplatform/ModIndex.h
modplatform/ModIndex.cpp
modplatform/ModAPI.h
modplatform/flame/FlameAPI.h
@ -545,6 +551,17 @@ set(MODPACKSCH_SOURCES
modplatform/modpacksch/FTBPackManifest.cpp
)
set(PACKWIZ_SOURCES
modplatform/packwiz/Packwiz.h
modplatform/packwiz/Packwiz.cpp
)
add_unit_test(Packwiz
SOURCES modplatform/packwiz/Packwiz_test.cpp
DATA modplatform/packwiz/testdata
LIBS Launcher_logic
)
set(TECHNIC_SOURCES
modplatform/technic/SingleZipPackInstallTask.h
modplatform/technic/SingleZipPackInstallTask.cpp
@ -598,6 +615,7 @@ set(LOGIC_SOURCES
${FLAME_SOURCES}
${MODRINTH_SOURCES}
${MODPACKSCH_SOURCES}
${PACKWIZ_SOURCES}
${TECHNIC_SOURCES}
${ATLAUNCHER_SOURCES}
)

View File

@ -151,23 +151,23 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
continue;
if (mod.type() == Mod::MOD_ZIPFILE)
{
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles))
if (!mergeZipFiles(&zipOut, mod.fileinfo(), addedFiles))
{
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar.";
return false;
}
}
else if (mod.type() == Mod::MOD_SINGLEFILE)
{
// FIXME: buggy - does not work with addedFiles
auto filename = mod.filename();
auto filename = mod.fileinfo();
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
{
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar.";
return false;
}
addedFiles.insert(filename.fileName());
@ -176,7 +176,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
{
// untested, but seems to be unused / not possible to reach
// FIXME: buggy - does not work with addedFiles
auto filename = mod.filename();
auto filename = mod.fileinfo();
QString what_to_zip = filename.absoluteFilePath();
QDir dir(what_to_zip);
dir.cdUp();
@ -193,7 +193,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
{
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar.";
return false;
}
qDebug() << "Adding folder " << filename.fileName() << " from "
@ -204,7 +204,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
// Make sure we do not continue launching when something is missing or undefined...
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar.";
qCritical() << "Failed to add unknown mod type" << mod.fileinfo().fileName() << "to the jar.";
return false;
}
}

View File

@ -1,24 +1,47 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 "ModDownloadTask.h"
#include "Application.h"
#include "minecraft/mod/ModFolderModel.h"
ModDownloadTask::ModDownloadTask(const QUrl sourceUrl,const QString filename, const std::shared_ptr<ModFolderModel> mods)
: m_sourceUrl(sourceUrl), mods(mods), filename(filename) {
}
ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods)
: m_mod(mod), m_mod_version(version), mods(mods)
{
m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version));
void ModDownloadTask::executeTask() {
setStatus(tr("Downloading mod:\n%1").arg(m_sourceUrl.toString()));
addTask(m_update_task);
m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::Download::makeFile(m_sourceUrl, mods->dir().absoluteFilePath(filename)));
m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl));
m_filesNetJob->addNetAction(Net::Download::makeFile(m_mod_version.downloadUrl, mods->dir().absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed);
m_filesNetJob->start();
addTask(m_filesNetJob);
}
void ModDownloadTask::downloadSucceeded()
{
emitSucceeded();
m_filesNetJob.reset();
}
@ -32,8 +55,3 @@ void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
{
emit progress(current, total);
}
bool ModDownloadTask::abort() {
return m_filesNetJob->abort();
}

View File

@ -1,28 +1,44 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 "QObjectPtr.h"
#include "tasks/Task.h"
#include "minecraft/mod/ModFolderModel.h"
#include "net/NetJob.h"
#include <QUrl>
#include "tasks/SequentialTask.h"
#include "modplatform/ModIndex.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
class ModDownloadTask : public Task {
class ModFolderModel;
class ModDownloadTask : public SequentialTask {
Q_OBJECT
public:
explicit ModDownloadTask(const QUrl sourceUrl, const QString filename, const std::shared_ptr<ModFolderModel> mods);
const QString& getFilename() const { return filename; }
public slots:
bool abort() override;
protected:
//! Entry point for tasks.
void executeTask() override;
explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods);
const QString& getFilename() const { return m_mod_version.fileName; }
private:
QUrl m_sourceUrl;
NetJob::Ptr m_filesNetJob;
ModPlatform::IndexedPack m_mod;
ModPlatform::IndexedVersion m_mod_version;
const std::shared_ptr<ModFolderModel> mods;
const QString filename;
NetJob::Ptr m_filesNetJob;
LocalModUpdateTask::Ptr m_update_task;
void downloadProgressChanged(qint64 current, qint64 total);

View File

@ -659,23 +659,23 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << QString("%1:").arg(label);
auto modList = model.allMods();
std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) {
auto aName = a.filename().completeBaseName();
auto bName = b.filename().completeBaseName();
auto aName = a.fileinfo().completeBaseName();
auto bName = b.fileinfo().completeBaseName();
return aName.localeAwareCompare(bName) < 0;
});
for(auto & mod: modList)
{
if(mod.type() == Mod::MOD_FOLDER)
{
out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)";
out << u8" [📁] " + mod.fileinfo().completeBaseName() + " (folder)";
continue;
}
if(mod.enabled()) {
out << u8" [✔️] " + mod.filename().completeBaseName();
out << u8" [✔️] " + mod.fileinfo().completeBaseName();
}
else {
out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)";
out << u8" [❌] " + mod.fileinfo().completeBaseName() + " (disabled)";
}
}

View File

@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 <memory>
#include "modplatform/packwiz/Packwiz.h"
// launcher/minecraft/mod/Mod.h
class Mod;
/* Abstraction file for easily changing the way metadata is stored / handled
* Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]]
* */
class Metadata {
public:
using ModStruct = Packwiz::V1::Mod;
static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
{
return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
}
static auto create(QDir& index_dir, Mod& internal_mod) -> ModStruct
{
return Packwiz::V1::createModFormat(index_dir, internal_mod);
}
static void update(QDir& index_dir, ModStruct& mod)
{
Packwiz::V1::updateModIndex(index_dir, mod);
}
static void remove(QDir& index_dir, QString& mod_name)
{
Packwiz::V1::deleteModIndex(index_dir, mod_name);
}
static auto get(QDir& index_dir, QString& mod_name) -> ModStruct
{
return Packwiz::V1::getIndexForMod(index_dir, mod_name);
}
};

View File

@ -1,24 +1,48 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Mod.h"
#include <QDir>
#include <QString>
#include "Mod.h"
#include <QDebug>
#include <FileSystem.h>
#include <QDebug>
#include "Application.h"
#include "MetadataHandler.h"
namespace {
@ -26,57 +50,69 @@ ModDetails invalidDetails;
}
Mod::Mod(const QFileInfo &file)
Mod::Mod(const QFileInfo& file)
{
repath(file);
m_changedDateTime = file.lastModified();
}
void Mod::repath(const QFileInfo &file)
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)
: m_file(mods_dir.absoluteFilePath(metadata.filename))
// It is weird, but name is not reliable for comparing with the JAR files name
// FIXME: Maybe use hash when implemented?
, m_internal_id(metadata.filename)
, m_name(metadata.name)
{
if (m_file.isDir()) {
m_type = MOD_FOLDER;
} else {
if (metadata.filename.endsWith(".zip") || metadata.filename.endsWith(".jar"))
m_type = MOD_ZIPFILE;
else if (metadata.filename.endsWith(".litemod"))
m_type = MOD_LITEMOD;
else
m_type = MOD_SINGLEFILE;
}
m_enabled = true;
m_changedDateTime = m_file.lastModified();
m_temp_metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
}
void Mod::repath(const QFileInfo& file)
{
m_file = file;
QString name_base = file.fileName();
m_type = Mod::MOD_UNKNOWN;
m_mmc_id = name_base;
m_internal_id = name_base;
if (m_file.isDir())
{
if (m_file.isDir()) {
m_type = MOD_FOLDER;
m_name = name_base;
}
else if (m_file.isFile())
{
if (name_base.endsWith(".disabled"))
{
} else if (m_file.isFile()) {
if (name_base.endsWith(".disabled")) {
m_enabled = false;
name_base.chop(9);
}
else
{
} else {
m_enabled = true;
}
if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
{
if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) {
m_type = MOD_ZIPFILE;
name_base.chop(4);
}
else if (name_base.endsWith(".litemod"))
{
} else if (name_base.endsWith(".litemod")) {
m_type = MOD_LITEMOD;
name_base.chop(8);
}
else
{
} else {
m_type = MOD_SINGLEFILE;
}
m_name = name_base;
}
}
bool Mod::enable(bool value)
auto Mod::enable(bool value) -> bool
{
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
return false;
@ -85,67 +121,104 @@ bool Mod::enable(bool value)
return false;
QString path = m_file.absoluteFilePath();
if (value)
{
QFile foo(path);
QFile file(path);
if (value) {
if (!path.endsWith(".disabled"))
return false;
path.chop(9);
if (!foo.rename(path))
if (!file.rename(path))
return false;
}
else
{
QFile foo(path);
} else {
path += ".disabled";
if (!foo.rename(path))
if (!file.rename(path))
return false;
}
repath(QFileInfo(path));
if (status() == ModStatus::NoMetadata)
repath(QFileInfo(path));
m_enabled = value;
return true;
}
bool Mod::destroy()
void Mod::setStatus(ModStatus status)
{
if(m_localDetails.get())
m_localDetails->status = status;
}
void Mod::setMetadata(Metadata::ModStruct* metadata)
{
if(status() == ModStatus::NoMetadata)
setStatus(ModStatus::Installed);
if(m_localDetails.get())
m_localDetails->metadata.reset(metadata);
}
auto Mod::destroy(QDir& index_dir) -> bool
{
auto n = name();
// FIXME: This can fail to remove the metadata if the
// "DontUseModMetadata" setting is on, since there could
// be a name mismatch!
Metadata::remove(index_dir, n);
m_type = MOD_UNKNOWN;
return FS::deletePath(m_file.filePath());
}
const ModDetails & Mod::details() const
auto Mod::details() const -> const ModDetails&
{
if(!m_localDetails)
return invalidDetails;
return *m_localDetails;
return m_localDetails ? *m_localDetails : invalidDetails;
}
QString Mod::version() const
auto Mod::name() const -> QString
{
return details().version;
}
QString Mod::name() const
{
auto & d = details();
if(!d.name.isEmpty()) {
return d.name;
auto d_name = details().name;
if (!d_name.isEmpty()) {
return d_name;
}
return m_name;
}
QString Mod::homeurl() const
auto Mod::version() const -> QString
{
return details().version;
}
auto Mod::homeurl() const -> QString
{
return details().homeurl;
}
QString Mod::description() const
auto Mod::description() const -> QString
{
return details().description;
}
QStringList Mod::authors() const
auto Mod::authors() const -> QStringList
{
return details().authors;
}
auto Mod::status() const -> ModStatus
{
return details().status;
}
void Mod::finishResolvingWithDetails(std::shared_ptr<ModDetails> details)
{
m_resolving = false;
m_resolved = true;
m_localDetails = details;
if (status() != ModStatus::NoMetadata
&& m_temp_metadata.get()
&& m_temp_metadata->isValid() &&
m_localDetails.get()) {
m_localDetails->metadata.swap(m_temp_metadata);
}
}

View File

@ -1,28 +1,46 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QFileInfo>
#include <QDateTime>
#include <QFileInfo>
#include <QList>
#include <memory>
#include "ModDetails.h"
class Mod
{
public:
@ -32,84 +50,69 @@ public:
MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
MOD_SINGLEFILE, //!< The mod is a single file (not a zip file).
MOD_FOLDER, //!< The mod is in a folder on the filesystem.
MOD_LITEMOD, //!< The mod is a litemod
MOD_LITEMOD, //!< The mod is a litemod
};
Mod() = default;
Mod(const QFileInfo &file);
explicit Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
QFileInfo filename() const
{
return m_file;
}
QString mmc_id() const
{
return m_mmc_id;
}
ModType type() const
{
return m_type;
}
bool valid()
{
return m_type != MOD_UNKNOWN;
}
auto fileinfo() const -> QFileInfo { return m_file; }
auto dateTimeChanged() const -> QDateTime { return m_changedDateTime; }
auto internal_id() const -> QString { return m_internal_id; }
auto type() const -> ModType { return m_type; }
auto enabled() const -> bool { return m_enabled; }
QDateTime dateTimeChanged() const
{
return m_changedDateTime;
}
auto valid() const -> bool { return m_type != MOD_UNKNOWN; }
bool enabled() const
{
return m_enabled;
}
auto details() const -> const ModDetails&;
auto name() const -> QString;
auto version() const -> QString;
auto homeurl() const -> QString;
auto description() const -> QString;
auto authors() const -> QStringList;
auto status() const -> ModStatus;
const ModDetails &details() const;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct> { return details().metadata; };
auto metadata() -> std::shared_ptr<Metadata::ModStruct> { return m_localDetails->metadata; };
QString name() const;
QString version() const;
QString homeurl() const;
QString description() const;
QStringList authors() const;
void setStatus(ModStatus status);
void setMetadata(Metadata::ModStruct* metadata);
bool enable(bool value);
auto enable(bool value) -> bool;
// delete all the files of this mod
bool destroy();
auto destroy(QDir& index_dir) -> bool;
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
bool shouldResolve() {
return !m_resolving && !m_resolved;
}
bool isResolving() {
return m_resolving;
}
int resolutionTicket()
{
return m_resolutionTicket;
}
auto shouldResolve() const -> bool { return !m_resolving && !m_resolved; }
auto isResolving() const -> bool { return m_resolving; }
auto resolutionTicket() const -> int { return m_resolutionTicket; }
void setResolving(bool resolving, int resolutionTicket) {
m_resolving = resolving;
m_resolutionTicket = resolutionTicket;
}
void finishResolvingWithDetails(std::shared_ptr<ModDetails> details){
m_resolving = false;
m_resolved = true;
m_localDetails = details;
}
void finishResolvingWithDetails(std::shared_ptr<ModDetails> details);
protected:
QFileInfo m_file;
QDateTime m_changedDateTime;
QString m_mmc_id;
QString m_internal_id;
/* Name as reported via the file name */
QString m_name;
ModType m_type = MOD_UNKNOWN;
/* If the mod has metadata, this will be filled in the constructor, and passed to
* the ModDetails when calling finishResolvingWithDetails */
std::shared_ptr<Metadata::ModStruct> m_temp_metadata;
std::shared_ptr<ModDetails> m_localDetails;
bool m_enabled = true;
bool m_resolving = false;
bool m_resolved = false;
int m_resolutionTicket = 0;
ModType m_type = MOD_UNKNOWN;
std::shared_ptr<ModDetails> m_localDetails;
};

View File

@ -1,17 +1,79 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <memory>
#include <QString>
#include <QStringList>
#include "minecraft/mod/MetadataHandler.h"
enum class ModStatus {
Installed, // Both JAR and Metadata are present
NotInstalled, // Only the Metadata is present
NoMetadata, // Only the JAR is present
};
struct ModDetails
{
/* Mod ID as defined in the ModLoader-specific metadata */
QString mod_id;
/* Human-readable name */
QString name;
/* Human-readable mod version */
QString version;
/* Human-readable minecraft version */
QString mcversion;
/* URL for mod's home page */
QString homeurl;
QString updateurl;
/* Human-readable description */
QString description;
/* List of the author's names */
QStringList authors;
QString credits;
/* Installation status of the mod */
ModStatus status;
/* Metadata information, if any */
std::shared_ptr<Metadata::ModStruct> metadata;
};

View File

@ -1,18 +0,0 @@
#include "ModFolderLoadTask.h"
#include <QDebug>
ModFolderLoadTask::ModFolderLoadTask(QDir dir) :
m_dir(dir), m_result(new Result())
{
}
void ModFolderLoadTask::run()
{
m_dir.refresh();
for (auto entry : m_dir.entryInfoList())
{
Mod m(entry);
m_result->mods[m.mmc_id()] = m;
}
emit succeeded();
}

View File

@ -1,29 +0,0 @@
#pragma once
#include <QRunnable>
#include <QObject>
#include <QDir>
#include <QMap>
#include "Mod.h"
#include <memory>
class ModFolderLoadTask : public QObject, public QRunnable
{
Q_OBJECT
public:
struct Result {
QMap<QString, Mod> mods;
};
using ResultPtr = std::shared_ptr<Result>;
ResultPtr result() const {
return m_result;
}
public:
ModFolderLoadTask(QDir dir);
void run();
signals:
void succeeded();
private:
QDir m_dir;
ResultPtr m_result;
};

View File

@ -14,17 +14,19 @@
*/
#include "ModFolderModel.h"
#include <FileSystem.h>
#include <QDebug>
#include <QFileSystemWatcher>
#include <QMimeData>
#include <QString>
#include <QThreadPool>
#include <QUrl>
#include <QUuid>
#include <QString>
#include <QFileSystemWatcher>
#include <QDebug>
#include "ModFolderLoadTask.h"
#include <QThreadPool>
#include <algorithm>
#include "LocalModParseTask.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir)
{
@ -79,10 +81,14 @@ bool ModFolderModel::update()
return true;
}
auto task = new ModFolderLoadTask(m_dir);
auto index_dir = indexDir();
auto task = new ModFolderLoadTask(dir(), index_dir);
m_update = task->result();
QThreadPool *threadPool = QThreadPool::globalInstance();
connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
threadPool->start(task);
return true;
}
@ -153,7 +159,7 @@ void ModFolderModel::finishUpdate()
modsIndex.clear();
int idx = 0;
for(auto & mod: mods) {
modsIndex[mod.mmc_id()] = idx;
modsIndex[mod.internal_id()] = idx;
idx++;
}
}
@ -174,9 +180,9 @@ void ModFolderModel::resolveMod(Mod& m)
return;
}
auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename());
auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.fileinfo());
auto result = task->result();
result->id = m.mmc_id();
result->id = m.internal_id();
activeTickets.insert(nextResolutionTicket, result);
m.setResolving(true, nextResolutionTicket);
nextResolutionTicket++;
@ -333,8 +339,12 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
for (auto i: indexes)
{
if(i.column() != 0) {
continue;
}
Mod &m = mods[i.row()];
m.destroy();
auto index_dir = indexDir();
m.destroy(index_dir);
}
return true;
}
@ -381,7 +391,7 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
}
case Qt::ToolTipRole:
return mods[row].mmc_id();
return mods[row].internal_id();
case Qt::CheckStateRole:
switch (column)
@ -436,11 +446,11 @@ bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction actio
}
// preserve the row, but change its ID
auto oldId = mod.mmc_id();
auto oldId = mod.internal_id();
if(!mod.enable(!mod.enabled())) {
return false;
}
auto newId = mod.mmc_id();
auto newId = mod.internal_id();
if(modsIndex.contains(newId)) {
// NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled
// But is it necessary?

View File

@ -24,8 +24,8 @@
#include "Mod.h"
#include "ModFolderLoadTask.h"
#include "LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
class LegacyInstance;
class BaseInstance;
@ -108,11 +108,16 @@ public:
bool isValid();
QDir dir()
QDir& dir()
{
return m_dir;
}
QDir indexDir()
{
return { QString("%1/.index").arg(dir().absolutePath()) };
}
const QList<Mod> & allMods()
{
return mods;

View File

@ -35,7 +35,6 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
details->name = name;
}
details->version = firstObj.value("version").toString();
details->updateurl = firstObj.value("updateUrl").toString();
auto homeurl = firstObj.value("url").toString().trimmed();
if(!homeurl.isEmpty())
{
@ -57,7 +56,6 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
{
details->authors.append(author.toString());
}
details->credits = firstObj.value("credits").toString();
return details;
};
QJsonParseError jsonError;
@ -168,27 +166,9 @@ std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
}
if(!authors.isEmpty())
{
// author information is stored as a string now, not a list
details->authors.append(authors);
}
// is credits even used anywhere? including this for completion/parity with old data version
toml_datum_t creditsDatum = toml_string_in(tomlData, "credits");
QString credits = "";
if(creditsDatum.ok)
{
authors = creditsDatum.u.s;
free(creditsDatum.u.s);
}
else
{
creditsDatum = toml_string_in(tomlModsTable0, "credits");
if(creditsDatum.ok)
{
credits = creditsDatum.u.s;
free(creditsDatum.u.s);
}
}
details->credits = credits;
toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL");
QString homeurl = "";
if(homeurlDatum.ok)

View File

@ -1,9 +1,11 @@
#pragma once
#include <QRunnable>
#include <QDebug>
#include <QObject>
#include "Mod.h"
#include "ModDetails.h"
#include <QRunnable>
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/ModDetails.h"
class LocalModParseTask : public QObject, public QRunnable
{

View File

@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 "LocalModUpdateTask.h"
#include "Application.h"
#include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h"
LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version)
: m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version)
{
// Ensure a '.index' folder exists in the mods folder, and create it if it does not
if (!FS::ensureFolderPathExists(index_dir.path())) {
emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name));
}
}
void LocalModUpdateTask::executeTask()
{
setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name));
if(APPLICATION->settings()->get("DontUseModMetadata").toBool()){
emitSucceeded();
return;
}
auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version);
Metadata::update(m_index_dir, pw_mod);
emitSucceeded();
}
auto LocalModUpdateTask::abort() -> bool
{
emitAborted();
return true;
}

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 "modplatform/ModIndex.h"
#include "tasks/Task.h"
class LocalModUpdateTask : public Task {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<LocalModUpdateTask>;
explicit LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version);
auto canAbort() const -> bool override { return true; }
auto abort() -> bool override;
protected slots:
//! Entry point for tasks.
void executeTask() override;
private:
QDir m_index_dir;
ModPlatform::IndexedPack& m_mod;
ModPlatform::IndexedVersion& m_mod_version;
};

View File

@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ModFolderLoadTask.h"
#include "Application.h"
#include "minecraft/mod/MetadataHandler.h"
ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir)
: m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result())
{}
void ModFolderLoadTask::run()
{
if (!APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
// Read metadata first
getFromMetadata();
}
// Read JAR files that don't have metadata
m_mods_dir.refresh();
for (auto entry : m_mods_dir.entryInfoList()) {
Mod mod(entry);
if(m_result->mods.contains(mod.internal_id())){
m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed);
}
else {
m_result->mods[mod.internal_id()] = mod;
m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata);
}
}
emit succeeded();
}
void ModFolderLoadTask::getFromMetadata()
{
m_index_dir.refresh();
for (auto entry : m_index_dir.entryList(QDir::Files)) {
auto metadata = Metadata::get(m_index_dir, entry);
if(!metadata.isValid()){
return;
}
Mod mod(m_mods_dir, metadata);
mod.setStatus(ModStatus::NotInstalled);
m_result->mods[mod.internal_id()] = mod;
}
}

View File

@ -0,0 +1,69 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QDir>
#include <QMap>
#include <QObject>
#include <QRunnable>
#include <memory>
#include "minecraft/mod/Mod.h"
class ModFolderLoadTask : public QObject, public QRunnable
{
Q_OBJECT
public:
struct Result {
QMap<QString, Mod> mods;
};
using ResultPtr = std::shared_ptr<Result>;
ResultPtr result() const {
return m_result;
}
public:
ModFolderLoadTask(QDir& mods_dir, QDir& index_dir);
void run();
signals:
void succeeded();
private:
void getFromMetadata();
private:
QDir& m_mods_dir, m_index_dir;
ResultPtr m_result;
};

View File

@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 "modplatform/ModIndex.h"
#include <QCryptographicHash>
namespace ModPlatform {
auto ProviderCapabilities::name(Provider p) -> const char*
{
switch (p) {
case Provider::MODRINTH:
return "modrinth";
case Provider::FLAME:
return "curseforge";
}
return {};
}
auto ProviderCapabilities::readableName(Provider p) -> QString
{
switch (p) {
case Provider::MODRINTH:
return "Modrinth";
case Provider::FLAME:
return "CurseForge";
}
return {};
}
auto ProviderCapabilities::hashType(Provider p) -> QStringList
{
switch (p) {
case Provider::MODRINTH:
return { "sha512", "sha1" };
case Provider::FLAME:
// Try newer formats first, fall back to old format
return { "sha1", "md5", "murmur2" };
}
return {};
}
auto ProviderCapabilities::hash(Provider p, QByteArray& data, QString type) -> QByteArray
{
switch (p) {
case Provider::MODRINTH: {
// NOTE: Data is the result of reading the entire JAR file!
// If 'type' was specified, we use that
if (!type.isEmpty() && hashType(p).contains(type)) {
if (type == "sha512")
return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
else if (type == "sha1")
return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
}
return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
}
case Provider::FLAME:
// If 'type' was specified, we use that
if (!type.isEmpty() && hashType(p).contains(type)) {
if(type == "sha1")
return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
else if (type == "md5")
return QCryptographicHash::hash(data, QCryptographicHash::Md5);
}
break;
}
return {};
}
} // namespace ModPlatform

View File

@ -1,3 +1,21 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 <QList>
@ -8,6 +26,19 @@
namespace ModPlatform {
enum class Provider {
MODRINTH,
FLAME
};
class ProviderCapabilities {
public:
auto name(Provider) -> const char*;
auto readableName(Provider) -> QString;
auto hashType(Provider) -> QStringList;
auto hash(Provider, QByteArray&, QString type = "") -> QByteArray;
};
struct ModpackAuthor {
QString name;
QString url;
@ -22,10 +53,13 @@ struct IndexedVersion {
QString date;
QString fileName;
QVector<QString> loaders = {};
QString hash_type;
QString hash;
};
struct IndexedPack {
QVariant addonId;
Provider provider;
QString name;
QString description;
QList<ModpackAuthor> authors;
@ -40,3 +74,4 @@ struct IndexedPack {
} // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
Q_DECLARE_METATYPE(ModPlatform::Provider)

View File

@ -1,5 +1,6 @@
#pragma once
#include "modplatform/ModIndex.h"
#include "modplatform/helpers/NetworkModAPI.h"
class FlameAPI : public NetworkModAPI {

View File

@ -6,9 +6,12 @@
#include "modplatform/flame/FlameAPI.h"
#include "net/NetJob.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireInteger(obj, "id");
pack.provider = ModPlatform::Provider::FLAME;
pack.name = Json::requireString(obj, "name");
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
pack.description = Json::ensureString(obj, "summary", "");
@ -27,6 +30,17 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
}
}
static QString enumToString(int hash_algorithm)
{
switch(hash_algorithm){
default:
case 1:
return "sha1";
case 2:
return "md5";
}
}
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
@ -38,28 +52,13 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
if(!file.addonId.isValid())
file.addonId = pack.addonId;
auto versionArray = Json::requireArray(obj, "gameVersions");
if (versionArray.isEmpty()) {
continue;
}
ModPlatform::IndexedVersion file;
for (auto mcVer : versionArray) {
auto str = mcVer.toString();
if (str.contains('.'))
file.mcVersion.append(str);
}
file.addonId = pack.addonId;
file.fileId = Json::requireInteger(obj, "id");
file.date = Json::requireString(obj, "fileDate");
file.version = Json::requireString(obj, "displayName");
file.downloadUrl = Json::requireString(obj, "downloadUrl");
file.fileName = Json::requireString(obj, "fileName");
unsortedVersions.append(file);
if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
@ -70,3 +69,39 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
pack.versions = unsortedVersions;
pack.versionsLoaded = true;
}
auto FlameMod::loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion
{
auto versionArray = Json::requireArray(obj, "gameVersions");
if (versionArray.isEmpty()) {
return {};
}
ModPlatform::IndexedVersion file;
for (auto mcVer : versionArray) {
auto str = mcVer.toString();
if (str.contains('.'))
file.mcVersion.append(str);
}
file.addonId = Json::requireInteger(obj, "modId");
file.fileId = Json::requireInteger(obj, "id");
file.date = Json::requireString(obj, "fileDate");
file.version = Json::requireString(obj, "displayName");
file.downloadUrl = Json::requireString(obj, "downloadUrl");
file.fileName = Json::requireString(obj, "fileName");
auto hash_list = Json::ensureArray(obj, "hashes");
for (auto h : hash_list) {
auto hash_entry = Json::ensureObject(h);
auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME);
auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm"));
if (hash_types.contains(hash_algo)) {
file.hash = Json::requireString(hash_entry, "value");
file.hash_type = hash_algo;
break;
}
}
return file;
}

View File

@ -16,5 +16,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion;
} // namespace FlameMod

View File

@ -20,6 +20,7 @@
#include "BuildConfig.h"
#include "modplatform/ModAPI.h"
#include "modplatform/ModIndex.h"
#include "modplatform/helpers/NetworkModAPI.h"
#include <QDebug>

View File

@ -1,19 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
*
* 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/>.
*/
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 "ModrinthPackIndex.h"
#include "ModrinthAPI.h"
@ -24,10 +25,12 @@
#include "net/NetJob.h"
static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireString(obj, "project_id");
pack.provider = ModPlatform::Provider::MODRINTH;
pack.name = Json::requireString(obj, "title");
QString slug = Json::ensureString(obj, "slug", "");
@ -57,46 +60,10 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
ModPlatform::IndexedVersion file;
file.addonId = Json::requireString(obj, "project_id");
file.fileId = Json::requireString(obj, "id");
file.date = Json::requireString(obj, "date_published");
auto versionArray = Json::requireArray(obj, "game_versions");
if (versionArray.empty()) { continue; }
for (auto mcVer : versionArray) {
file.mcVersion.append(mcVer.toString());
}
auto loaders = Json::requireArray(obj, "loaders");
for (auto loader : loaders) {
file.loaders.append(loader.toString());
}
file.version = Json::requireString(obj, "name");
auto files = Json::requireArray(obj, "files");
int i = 0;
// Find correct file (needed in cases where one version may have multiple files)
// Will default to the last one if there's no primary (though I think Modrinth requires that
// at least one file is primary, idk)
// NOTE: files.count() is 1-indexed, so we need to subtract 1 to become 0-indexed
while (i < files.count() - 1){
auto parent = files[i].toObject();
auto fileName = Json::requireString(parent, "filename");
// Grab the primary file, if available
if(Json::requireBoolean(parent, "primary"))
break;
i++;
}
auto parent = files[i].toObject();
if (parent.contains("url")) {
file.downloadUrl = Json::requireString(parent, "url");
file.fileName = Json::requireString(parent, "filename");
auto file = loadIndexedPackVersion(obj);
if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
// dates are in RFC 3339 format
@ -106,3 +73,61 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
pack.versions = unsortedVersions;
pack.versionsLoaded = true;
}
auto Modrinth::loadIndexedPackVersion(QJsonObject &obj) -> ModPlatform::IndexedVersion
{
ModPlatform::IndexedVersion file;
file.addonId = Json::requireString(obj, "project_id");
file.fileId = Json::requireString(obj, "id");
file.date = Json::requireString(obj, "date_published");
auto versionArray = Json::requireArray(obj, "game_versions");
if (versionArray.empty()) {
return {};
}
for (auto mcVer : versionArray) {
file.mcVersion.append(mcVer.toString());
}
auto loaders = Json::requireArray(obj, "loaders");
for (auto loader : loaders) {
file.loaders.append(loader.toString());
}
file.version = Json::requireString(obj, "name");
auto files = Json::requireArray(obj, "files");
int i = 0;
// Find correct file (needed in cases where one version may have multiple files)
// Will default to the last one if there's no primary (though I think Modrinth requires that
// at least one file is primary, idk)
// NOTE: files.count() is 1-indexed, so we need to subtract 1 to become 0-indexed
while (i < files.count() - 1) {
auto parent = files[i].toObject();
auto fileName = Json::requireString(parent, "filename");
// Grab the primary file, if available
if (Json::requireBoolean(parent, "primary"))
break;
i++;
}
auto parent = files[i].toObject();
if (parent.contains("url")) {
file.downloadUrl = Json::requireString(parent, "url");
file.fileName = Json::requireString(parent, "filename");
auto hash_list = Json::requireObject(parent, "hashes");
auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH);
for (auto& hash_type : hash_types) {
if (hash_list.contains(hash_type)) {
file.hash = Json::requireString(hash_list, hash_type);
file.hash_type = hash_type;
break;
}
}
return file;
}
return {};
}

View File

@ -29,5 +29,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion;
} // namespace Modrinth

View File

@ -0,0 +1,289 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 "Packwiz.h"
#include <QDebug>
#include <QDir>
#include <QObject>
#include "toml.h"
#include "FileSystem.h"
#include "minecraft/mod/Mod.h"
#include "modplatform/ModIndex.h"
namespace Packwiz {
auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
{
QFile index_file(index_dir.absoluteFilePath(normalized_fname));
QString real_fname = normalized_fname;
if (!index_file.exists()) {
// Tries to get similar entries
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
if (!QString::compare(normalized_fname, file_name, Qt::CaseInsensitive)) {
real_fname = file_name;
break;
}
}
if(should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)){
qCritical() << "Could not find a match for a valid metadata file!";
qCritical() << "File: " << normalized_fname;
return {};
}
}
return real_fname;
}
// Helpers
static inline auto indexFileName(QString const& mod_name) -> QString
{
if(mod_name.endsWith(".pw.toml"))
return mod_name;
return QString("%1.pw.toml").arg(mod_name);
}
static ModPlatform::ProviderCapabilities ProviderCaps;
// Helper functions for extracting data from the TOML file
auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString
{
toml_datum_t var = toml_string_in(parent, entry_name);
if (!var.ok) {
qCritical() << QString("Failed to read str property '%1' in mod metadata.").arg(entry_name);
return {};
}
QString tmp = var.u.s;
free(var.u.s);
return tmp;
}
auto intEntry(toml_table_t* parent, const char* entry_name) -> int
{
toml_datum_t var = toml_int_in(parent, entry_name);
if (!var.ok) {
qCritical() << QString("Failed to read int property '%1' in mod metadata.").arg(entry_name);
return {};
}
return var.u.i;
}
auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod
{
Mod mod;
mod.name = mod_pack.name;
mod.filename = mod_version.fileName;
if(mod_pack.provider == ModPlatform::Provider::FLAME){
mod.mode = "metadata:curseforge";
}
else {
mod.mode = "url";
mod.url = mod_version.downloadUrl;
}
mod.hash_format = mod_version.hash_type;
mod.hash = mod_version.hash;
mod.provider = mod_pack.provider;
mod.file_id = mod_version.fileId;
mod.project_id = mod_pack.addonId;
return mod;
}
auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod
{
auto mod_name = internal_mod.name();
// Try getting metadata if it exists
Mod mod { getIndexForMod(index_dir, mod_name) };
if(mod.isValid())
return mod;
qWarning() << QString("Tried to create mod metadata with a Mod without metadata!");
return {};
}
void V1::updateModIndex(QDir& index_dir, Mod& mod)
{
if(!mod.isValid()){
qCritical() << QString("Tried to update metadata of an invalid mod!");
return;
}
// Ensure the corresponding mod's info exists, and create it if not
auto normalized_fname = indexFileName(mod.name);
auto real_fname = getRealIndexName(index_dir, normalized_fname);
QFile index_file(index_dir.absoluteFilePath(real_fname));
// There's already data on there!
// TODO: We should do more stuff here, as the user is likely trying to
// override a file. In this case, check versions and ask the user what
// they want to do!
if (index_file.exists()) { index_file.remove(); }
if (!index_file.open(QIODevice::ReadWrite)) {
qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name));
return;
}
// Put TOML data into the file
QTextStream in_stream(&index_file);
auto addToStream = [&in_stream](QString&& key, QString value) { in_stream << QString("%1 = \"%2\"\n").arg(key, value); };
{
addToStream("name", mod.name);
addToStream("filename", mod.filename);
addToStream("side", mod.side);
in_stream << QString("\n[download]\n");
addToStream("mode", mod.mode);
addToStream("url", mod.url.toString());
addToStream("hash-format", mod.hash_format);
addToStream("hash", mod.hash);
in_stream << QString("\n[update]\n");
in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider));
switch(mod.provider){
case(ModPlatform::Provider::FLAME):
in_stream << QString("file-id = %1\n").arg(mod.file_id.toString());
in_stream << QString("project-id = %1\n").arg(mod.project_id.toString());
break;
case(ModPlatform::Provider::MODRINTH):
addToStream("mod-id", mod.mod_id().toString());
addToStream("version", mod.version().toString());
break;
}
}
index_file.close();
}
void V1::deleteModIndex(QDir& index_dir, QString& mod_name)
{
auto normalized_fname = indexFileName(mod_name);
auto real_fname = getRealIndexName(index_dir, normalized_fname);
if (real_fname.isEmpty())
return;
QFile index_file(index_dir.absoluteFilePath(real_fname));
if(!index_file.exists()){
qWarning() << QString("Tried to delete non-existent mod metadata for %1!").arg(mod_name);
return;
}
if(!index_file.remove()){
qWarning() << QString("Failed to remove metadata for mod %1!").arg(mod_name);
}
}
auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod
{
Mod mod;
auto normalized_fname = indexFileName(index_file_name);
auto real_fname = getRealIndexName(index_dir, normalized_fname, true);
if (real_fname.isEmpty())
return {};
QFile index_file(index_dir.absoluteFilePath(real_fname));
if (!index_file.open(QIODevice::ReadOnly)) {
qWarning() << QString("Failed to open mod metadata for %1").arg(index_file_name);
return {};
}
toml_table_t* table = nullptr;
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
char errbuf[200];
auto file_bytearray = index_file.readAll();
table = toml_parse(file_bytearray.data(), errbuf, sizeof(errbuf));
index_file.close();
if (!table) {
qWarning() << QString("Could not open file %1!").arg(indexFileName(index_file_name));
qWarning() << "Reason: " << QString(errbuf);
return {};
}
{ // Basic info
mod.name = stringEntry(table, "name");
mod.filename = stringEntry(table, "filename");
mod.side = stringEntry(table, "side");
}
{ // [download] info
toml_table_t* download_table = toml_table_in(table, "download");
if (!download_table) {
qCritical() << QString("No [download] section found on mod metadata!");
return {};
}
mod.mode = stringEntry(download_table, "mode");
mod.url = stringEntry(download_table, "url");
mod.hash_format = stringEntry(download_table, "hash-format");
mod.hash = stringEntry(download_table, "hash");
}
{ // [update] info
using Provider = ModPlatform::Provider;
toml_table_t* update_table = toml_table_in(table, "update");
if (!update_table) {
qCritical() << QString("No [update] section found on mod metadata!");
return {};
}
toml_table_t* mod_provider_table = nullptr;
if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::FLAME)))) {
mod.provider = Provider::FLAME;
mod.file_id = intEntry(mod_provider_table, "file-id");
mod.project_id = intEntry(mod_provider_table, "project-id");
} else if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::MODRINTH)))) {
mod.provider = Provider::MODRINTH;
mod.mod_id() = stringEntry(mod_provider_table, "mod-id");
mod.version() = stringEntry(mod_provider_table, "version");
} else {
qCritical() << QString("No mod provider on mod metadata!");
return {};
}
}
toml_free(table);
return mod;
}
} // namespace Packwiz

View File

@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 "modplatform/ModIndex.h"
#include <QString>
#include <QUrl>
#include <QVariant>
struct toml_table_t;
class QDir;
// Mod from launcher/minecraft/mod/Mod.h
class Mod;
namespace Packwiz {
auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString;
auto intEntry(toml_table_t* parent, const char* entry_name) -> int;
class V1 {
public:
struct Mod {
QString name {};
QString filename {};
// FIXME: make side an enum
QString side {"both"};
// [download]
QString mode {};
QUrl url {};
QString hash_format {};
QString hash {};
// [update]
ModPlatform::Provider provider {};
QVariant file_id {};
QVariant project_id {};
public:
// This is a totally heuristic, but should work for now.
auto isValid() const -> bool { return !name.isEmpty() && !project_id.isNull(); }
// Different providers can use different names for the same thing
// Modrinth-specific
auto mod_id() -> QVariant& { return project_id; }
auto version() -> QVariant& { return file_id; }
};
/* Generates the object representing the information in a mod.pw.toml file via
* its common representation in the launcher, when downloading mods.
* */
static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod;
/* Generates the object representing the information in a mod.pw.toml file via
* its common representation in the launcher.
* */
static auto createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod;
/* Updates the mod index for the provided mod.
* This creates a new index if one does not exist already
* TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one.
* */
static void updateModIndex(QDir& index_dir, Mod& mod);
/* Deletes the metadata for the mod with the given name. If the metadata doesn't exist, it does nothing. */
static void deleteModIndex(QDir& index_dir, QString& mod_name);
/* Gets the metadata for a mod with a particular name.
* If the mod doesn't have a metadata, it simply returns an empty Mod object.
* */
static auto getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod;
};
} // namespace Packwiz

View File

@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@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 <QTemporaryDir>
#include <QTest>
#include "Packwiz.h"
#include "TestUtil.h"
class PackwizTest : public QObject {
Q_OBJECT
private slots:
// Files taken from https://github.com/packwiz/packwiz-example-pack
void loadFromFile_Modrinth()
{
QString source = QFINDTESTDATA("testdata");
QDir index_dir(source);
QString name_mod("borderless-mining.pw.toml");
QVERIFY(index_dir.entryList().contains(name_mod));
auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod);
QVERIFY(metadata.isValid());
QCOMPARE(metadata.name, "Borderless Mining");
QCOMPARE(metadata.filename, "borderless-mining-1.1.1+1.18.jar");
QCOMPARE(metadata.side, "client");
QCOMPARE(metadata.url, QUrl("https://cdn.modrinth.com/data/kYq5qkSL/versions/1.1.1+1.18/borderless-mining-1.1.1+1.18.jar"));
QCOMPARE(metadata.hash_format, "sha512");
QCOMPARE(metadata.hash, "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d");
QCOMPARE(metadata.provider, ModPlatform::Provider::MODRINTH);
QCOMPARE(metadata.version(), "ug2qKTPR");
QCOMPARE(metadata.mod_id(), "kYq5qkSL");
}
void loadFromFile_Curseforge()
{
QString source = QFINDTESTDATA("testdata");
QDir index_dir(source);
QString name_mod("screenshot-to-clipboard-fabric.pw.toml");
QVERIFY(index_dir.entryList().contains(name_mod));
// Try without the .pw.toml at the end
name_mod.chop(5);
auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod);
QVERIFY(metadata.isValid());
QCOMPARE(metadata.name, "Screenshot to Clipboard (Fabric)");
QCOMPARE(metadata.filename, "screenshot-to-clipboard-1.0.7-fabric.jar");
QCOMPARE(metadata.side, "both");
QCOMPARE(metadata.url, QUrl("https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar"));
QCOMPARE(metadata.hash_format, "murmur2");
QCOMPARE(metadata.hash, "1781245820");
QCOMPARE(metadata.provider, ModPlatform::Provider::FLAME);
QCOMPARE(metadata.file_id, 3509043);
QCOMPARE(metadata.project_id, 327154);
}
};
QTEST_GUILESS_MAIN(PackwizTest)
#include "Packwiz_test.moc"

View File

@ -0,0 +1,13 @@
name = "Borderless Mining"
filename = "borderless-mining-1.1.1+1.18.jar"
side = "client"
[download]
url = "https://cdn.modrinth.com/data/kYq5qkSL/versions/1.1.1+1.18/borderless-mining-1.1.1+1.18.jar"
hash-format = "sha512"
hash = "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d"
[update]
[update.modrinth]
mod-id = "kYq5qkSL"
version = "ug2qKTPR"

View File

@ -0,0 +1,13 @@
name = "Screenshot to Clipboard (Fabric)"
filename = "screenshot-to-clipboard-1.0.7-fabric.jar"
side = "both"
[download]
url = "https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar"
hash-format = "murmur2"
hash = "1781245820"
[update]
[update.curseforge]
file-id = 3509043
project-id = 327154

View File

@ -64,12 +64,18 @@ void SequentialTask::startNext()
return;
}
Task::Ptr next = m_queue[m_currentIndex];
connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString)));
connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString)));
connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64)));
connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext()));
connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString)));
connect(next.get(), SIGNAL(stepStatus(QString)), this, SLOT(subTaskStatus(QString)));
connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64)));
setStatus(tr("Executing task %1 out of %2").arg(m_currentIndex + 1).arg(m_queue.size()));
setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus());
next->start();
}
@ -79,7 +85,7 @@ void SequentialTask::subTaskFailed(const QString& msg)
}
void SequentialTask::subTaskStatus(const QString& msg)
{
setStepStatus(m_queue[m_currentIndex]->getStatus());
setStepStatus(msg);
}
void SequentialTask::subTaskProgress(qint64 current, qint64 total)
{

View File

@ -32,13 +32,10 @@ slots:
void subTaskStatus(const QString &msg);
void subTaskProgress(qint64 current, qint64 total);
signals:
void stepStatus(QString status);
protected:
void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); };
private:
void setStepStatus(QString status) { m_step_status = status; };
private:
protected:
QString m_name;
QString m_step_status;

View File

@ -92,6 +92,7 @@ class Task : public QObject {
void aborted();
void failed(QString reason);
void status(QString status);
void stepStatus(QString status);
public slots:
virtual void start();

View File

@ -184,6 +184,11 @@ void LauncherPage::on_modsDirBrowseBtn_clicked()
}
}
void LauncherPage::on_metadataDisableBtn_clicked()
{
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
}
void LauncherPage::refreshUpdateChannelList()
{
// Stop listening for selection changes. It's going to change a lot while we update it and
@ -338,6 +343,9 @@ void LauncherPage::applySettings()
s->set("InstSortMode", "Name");
break;
}
// Mods
s->set("ModMetadataDisabled", ui->metadataDisableBtn->isChecked());
}
void LauncherPage::loadSettings()
{
@ -440,6 +448,10 @@ void LauncherPage::loadSettings()
{
ui->sortByNameBtn->setChecked(true);
}
// Mods
ui->metadataDisableBtn->setChecked(s->get("DontUseModMetadata").toBool());
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
}
void LauncherPage::refreshFontPreview()

View File

@ -88,6 +88,7 @@ slots:
void on_instDirBrowseBtn_clicked();
void on_modsDirBrowseBtn_clicked();
void on_iconsDirBrowseBtn_clicked();
void on_metadataDisableBtn_clicked();
/*!
* Updates the list of update channels in the combo box.

View File

@ -94,19 +94,13 @@
<string>Folders</string>
</property>
<layout class="QGridLayout" name="foldersBoxLayout">
<item row="0" column="0">
<widget class="QLabel" name="labelInstDir">
<item row="1" column="2">
<widget class="QToolButton" name="modsDirBrowseBtn">
<property name="text">
<string>I&amp;nstances:</string>
</property>
<property name="buddy">
<cstring>instDirTextBox</cstring>
<string notr="true">...</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="instDirTextBox"/>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="instDirBrowseBtn">
<property name="text">
@ -114,28 +108,15 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelModsDir">
<property name="text">
<string>&amp;Mods:</string>
</property>
<property name="buddy">
<cstring>modsDirTextBox</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="modsDirTextBox"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="modsDirBrowseBtn">
<item row="2" column="2">
<widget class="QToolButton" name="iconsDirBrowseBtn">
<property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="iconsDirTextBox"/>
<item row="0" column="1">
<widget class="QLineEdit" name="instDirTextBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelIconsDir">
@ -147,10 +128,58 @@
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QToolButton" name="iconsDirBrowseBtn">
<item row="1" column="1">
<widget class="QLineEdit" name="modsDirTextBox"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelInstDir">
<property name="text">
<string notr="true">...</string>
<string>I&amp;nstances:</string>
</property>
<property name="buddy">
<cstring>instDirTextBox</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="iconsDirTextBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelModsDir">
<property name="text">
<string>&amp;Mods:</string>
</property>
<property name="buddy">
<cstring>modsDirTextBox</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="modsBox">
<property name="title">
<string>Mods</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="metadataDisableBtn">
<property name="toolTip">
<string>Disable using metadata provided by mod providers (like Modrinth or Curseforge) for mods.</string>
</property>
<property name="text">
<string>Disable using metadata for mods?</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="metadataWarningLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; color:#f5c211;&quot;&gt;Warning&lt;/span&gt;&lt;span style=&quot; color:#f5c211;&quot;&gt;: Disabling mod metadata may also disable some upcoming QoL features, such as mod updating!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>

View File

@ -150,7 +150,7 @@ void ModPage::onModSelected()
if (dialog->isModSelected(current.name, version.fileName)) {
dialog->removeSelectedMod(current.name);
} else {
dialog->addSelectedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName, dialog->mods));
dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods));
}
updateSelectionButton();

View File

@ -32,7 +32,7 @@ void MCModInfoFrame::updateWithMod(Mod &m)
QString text = "";
QString name = "";
if (m.name().isEmpty())
name = m.mmc_id();
name = m.internal_id();
else
name = m.name();