Merge pull request #426 from flowln/mod_perma
Add on-disk mod metadata information
This commit is contained in:
commit
1ab00ca8b2
@ -643,6 +643,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
// Minecraft launch method
|
// Minecraft launch method
|
||||||
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
|
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
|
||||||
|
|
||||||
|
// Minecraft mods
|
||||||
|
m_settings->registerSetting("ModMetadataDisabled", false);
|
||||||
|
|
||||||
// Minecraft offline player name
|
// Minecraft offline player name
|
||||||
m_settings->registerSetting("LastOfflinePlayerName", "");
|
m_settings->registerSetting("LastOfflinePlayerName", "");
|
||||||
|
|
||||||
|
@ -324,19 +324,22 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/WorldList.h
|
minecraft/WorldList.h
|
||||||
minecraft/WorldList.cpp
|
minecraft/WorldList.cpp
|
||||||
|
|
||||||
|
minecraft/mod/MetadataHandler.h
|
||||||
minecraft/mod/Mod.h
|
minecraft/mod/Mod.h
|
||||||
minecraft/mod/Mod.cpp
|
minecraft/mod/Mod.cpp
|
||||||
minecraft/mod/ModDetails.h
|
minecraft/mod/ModDetails.h
|
||||||
minecraft/mod/ModFolderModel.h
|
minecraft/mod/ModFolderModel.h
|
||||||
minecraft/mod/ModFolderModel.cpp
|
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.h
|
||||||
minecraft/mod/ResourcePackFolderModel.cpp
|
minecraft/mod/ResourcePackFolderModel.cpp
|
||||||
minecraft/mod/TexturePackFolderModel.h
|
minecraft/mod/TexturePackFolderModel.h
|
||||||
minecraft/mod/TexturePackFolderModel.cpp
|
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
|
# Assets
|
||||||
minecraft/AssetsUtils.h
|
minecraft/AssetsUtils.h
|
||||||
@ -499,6 +502,9 @@ set(META_SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(API_SOURCES
|
set(API_SOURCES
|
||||||
|
modplatform/ModIndex.h
|
||||||
|
modplatform/ModIndex.cpp
|
||||||
|
|
||||||
modplatform/ModAPI.h
|
modplatform/ModAPI.h
|
||||||
|
|
||||||
modplatform/flame/FlameAPI.h
|
modplatform/flame/FlameAPI.h
|
||||||
@ -545,6 +551,17 @@ set(MODPACKSCH_SOURCES
|
|||||||
modplatform/modpacksch/FTBPackManifest.cpp
|
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
|
set(TECHNIC_SOURCES
|
||||||
modplatform/technic/SingleZipPackInstallTask.h
|
modplatform/technic/SingleZipPackInstallTask.h
|
||||||
modplatform/technic/SingleZipPackInstallTask.cpp
|
modplatform/technic/SingleZipPackInstallTask.cpp
|
||||||
@ -598,6 +615,7 @@ set(LOGIC_SOURCES
|
|||||||
${FLAME_SOURCES}
|
${FLAME_SOURCES}
|
||||||
${MODRINTH_SOURCES}
|
${MODRINTH_SOURCES}
|
||||||
${MODPACKSCH_SOURCES}
|
${MODPACKSCH_SOURCES}
|
||||||
|
${PACKWIZ_SOURCES}
|
||||||
${TECHNIC_SOURCES}
|
${TECHNIC_SOURCES}
|
||||||
${ATLAUNCHER_SOURCES}
|
${ATLAUNCHER_SOURCES}
|
||||||
)
|
)
|
||||||
|
@ -151,23 +151,23 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
continue;
|
continue;
|
||||||
if (mod.type() == Mod::MOD_ZIPFILE)
|
if (mod.type() == Mod::MOD_ZIPFILE)
|
||||||
{
|
{
|
||||||
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles))
|
if (!mergeZipFiles(&zipOut, mod.fileinfo(), addedFiles))
|
||||||
{
|
{
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mod.type() == Mod::MOD_SINGLEFILE)
|
else if (mod.type() == Mod::MOD_SINGLEFILE)
|
||||||
{
|
{
|
||||||
// FIXME: buggy - does not work with addedFiles
|
// FIXME: buggy - does not work with addedFiles
|
||||||
auto filename = mod.filename();
|
auto filename = mod.fileinfo();
|
||||||
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
|
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
|
||||||
{
|
{
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
addedFiles.insert(filename.fileName());
|
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
|
// untested, but seems to be unused / not possible to reach
|
||||||
// FIXME: buggy - does not work with addedFiles
|
// FIXME: buggy - does not work with addedFiles
|
||||||
auto filename = mod.filename();
|
auto filename = mod.fileinfo();
|
||||||
QString what_to_zip = filename.absoluteFilePath();
|
QString what_to_zip = filename.absoluteFilePath();
|
||||||
QDir dir(what_to_zip);
|
QDir dir(what_to_zip);
|
||||||
dir.cdUp();
|
dir.cdUp();
|
||||||
@ -193,7 +193,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
{
|
{
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
qDebug() << "Adding folder " << filename.fileName() << " from "
|
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...
|
// Make sure we do not continue launching when something is missing or undefined...
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 "ModDownloadTask.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
|
|
||||||
ModDownloadTask::ModDownloadTask(const QUrl sourceUrl,const QString filename, const std::shared_ptr<ModFolderModel> mods)
|
ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods)
|
||||||
: m_sourceUrl(sourceUrl), mods(mods), filename(filename) {
|
: 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() {
|
addTask(m_update_task);
|
||||||
setStatus(tr("Downloading mod:\n%1").arg(m_sourceUrl.toString()));
|
|
||||||
|
|
||||||
m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
|
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::succeeded, this, &ModDownloadTask::downloadSucceeded);
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged);
|
connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged);
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed);
|
connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed);
|
||||||
m_filesNetJob->start();
|
|
||||||
|
addTask(m_filesNetJob);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModDownloadTask::downloadSucceeded()
|
void ModDownloadTask::downloadSucceeded()
|
||||||
{
|
{
|
||||||
emitSucceeded();
|
|
||||||
m_filesNetJob.reset();
|
m_filesNetJob.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,8 +55,3 @@ void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
|
|||||||
{
|
{
|
||||||
emit progress(current, total);
|
emit progress(current, total);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModDownloadTask::abort() {
|
|
||||||
return m_filesNetJob->abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -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
|
#pragma once
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
#include "minecraft/mod/ModFolderModel.h"
|
|
||||||
#include "net/NetJob.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
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ModDownloadTask(const QUrl sourceUrl, const QString filename, const std::shared_ptr<ModFolderModel> mods);
|
explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods);
|
||||||
const QString& getFilename() const { return filename; }
|
const QString& getFilename() const { return m_mod_version.fileName; }
|
||||||
|
|
||||||
public slots:
|
|
||||||
bool abort() override;
|
|
||||||
protected:
|
|
||||||
//! Entry point for tasks.
|
|
||||||
void executeTask() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUrl m_sourceUrl;
|
ModPlatform::IndexedPack m_mod;
|
||||||
NetJob::Ptr m_filesNetJob;
|
ModPlatform::IndexedVersion m_mod_version;
|
||||||
const std::shared_ptr<ModFolderModel> mods;
|
const std::shared_ptr<ModFolderModel> mods;
|
||||||
const QString filename;
|
|
||||||
|
NetJob::Ptr m_filesNetJob;
|
||||||
|
LocalModUpdateTask::Ptr m_update_task;
|
||||||
|
|
||||||
void downloadProgressChanged(qint64 current, qint64 total);
|
void downloadProgressChanged(qint64 current, qint64 total);
|
||||||
|
|
||||||
|
@ -659,23 +659,23 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||||||
out << QString("%1:").arg(label);
|
out << QString("%1:").arg(label);
|
||||||
auto modList = model.allMods();
|
auto modList = model.allMods();
|
||||||
std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) {
|
std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) {
|
||||||
auto aName = a.filename().completeBaseName();
|
auto aName = a.fileinfo().completeBaseName();
|
||||||
auto bName = b.filename().completeBaseName();
|
auto bName = b.fileinfo().completeBaseName();
|
||||||
return aName.localeAwareCompare(bName) < 0;
|
return aName.localeAwareCompare(bName) < 0;
|
||||||
});
|
});
|
||||||
for(auto & mod: modList)
|
for(auto & mod: modList)
|
||||||
{
|
{
|
||||||
if(mod.type() == Mod::MOD_FOLDER)
|
if(mod.type() == Mod::MOD_FOLDER)
|
||||||
{
|
{
|
||||||
out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)";
|
out << u8" [📁] " + mod.fileinfo().completeBaseName() + " (folder)";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mod.enabled()) {
|
if(mod.enabled()) {
|
||||||
out << u8" [✔️] " + mod.filename().completeBaseName();
|
out << u8" [✔️] " + mod.fileinfo().completeBaseName();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)";
|
out << u8" [❌] " + mod.fileinfo().completeBaseName() + " (disabled)";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
59
launcher/minecraft/mod/MetadataHandler.h
Normal file
59
launcher/minecraft/mod/MetadataHandler.h
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
@ -1,24 +1,48 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
/*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* PolyMC - Minecraft Launcher
|
||||||
* you may not use this file except in compliance with the License.
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
* You may obtain a copy of the License at
|
*
|
||||||
*
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* it under the terms of the GNU General Public License as published by
|
||||||
*
|
* the Free Software Foundation, version 3.
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
*
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* See the License for the specific language governing permissions and
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* limitations under the License.
|
* 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 <QDir>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "Mod.h"
|
|
||||||
#include <QDebug>
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "MetadataHandler.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -26,57 +50,69 @@ ModDetails invalidDetails;
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mod::Mod(const QFileInfo& file)
|
||||||
Mod::Mod(const QFileInfo &file)
|
|
||||||
{
|
{
|
||||||
repath(file);
|
repath(file);
|
||||||
m_changedDateTime = file.lastModified();
|
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;
|
m_file = file;
|
||||||
QString name_base = file.fileName();
|
QString name_base = file.fileName();
|
||||||
|
|
||||||
m_type = Mod::MOD_UNKNOWN;
|
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_type = MOD_FOLDER;
|
||||||
m_name = name_base;
|
m_name = name_base;
|
||||||
}
|
} else if (m_file.isFile()) {
|
||||||
else if (m_file.isFile())
|
if (name_base.endsWith(".disabled")) {
|
||||||
{
|
|
||||||
if (name_base.endsWith(".disabled"))
|
|
||||||
{
|
|
||||||
m_enabled = false;
|
m_enabled = false;
|
||||||
name_base.chop(9);
|
name_base.chop(9);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
m_enabled = true;
|
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;
|
m_type = MOD_ZIPFILE;
|
||||||
name_base.chop(4);
|
name_base.chop(4);
|
||||||
}
|
} else if (name_base.endsWith(".litemod")) {
|
||||||
else if (name_base.endsWith(".litemod"))
|
|
||||||
{
|
|
||||||
m_type = MOD_LITEMOD;
|
m_type = MOD_LITEMOD;
|
||||||
name_base.chop(8);
|
name_base.chop(8);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
m_type = MOD_SINGLEFILE;
|
m_type = MOD_SINGLEFILE;
|
||||||
}
|
}
|
||||||
m_name = name_base;
|
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)
|
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
|
||||||
return false;
|
return false;
|
||||||
@ -85,67 +121,104 @@ bool Mod::enable(bool value)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
QString path = m_file.absoluteFilePath();
|
QString path = m_file.absoluteFilePath();
|
||||||
if (value)
|
QFile file(path);
|
||||||
{
|
if (value) {
|
||||||
QFile foo(path);
|
|
||||||
if (!path.endsWith(".disabled"))
|
if (!path.endsWith(".disabled"))
|
||||||
return false;
|
return false;
|
||||||
path.chop(9);
|
path.chop(9);
|
||||||
if (!foo.rename(path))
|
|
||||||
|
if (!file.rename(path))
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
QFile foo(path);
|
|
||||||
path += ".disabled";
|
path += ".disabled";
|
||||||
if (!foo.rename(path))
|
|
||||||
|
if (!file.rename(path))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
repath(QFileInfo(path));
|
|
||||||
|
if (status() == ModStatus::NoMetadata)
|
||||||
|
repath(QFileInfo(path));
|
||||||
|
|
||||||
m_enabled = value;
|
m_enabled = value;
|
||||||
return true;
|
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;
|
m_type = MOD_UNKNOWN;
|
||||||
return FS::deletePath(m_file.filePath());
|
return FS::deletePath(m_file.filePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Mod::details() const -> const ModDetails&
|
||||||
const ModDetails & Mod::details() const
|
|
||||||
{
|
{
|
||||||
if(!m_localDetails)
|
return m_localDetails ? *m_localDetails : invalidDetails;
|
||||||
return invalidDetails;
|
|
||||||
return *m_localDetails;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Mod::name() const -> QString
|
||||||
QString Mod::version() const
|
|
||||||
{
|
{
|
||||||
return details().version;
|
auto d_name = details().name;
|
||||||
}
|
if (!d_name.isEmpty()) {
|
||||||
|
return d_name;
|
||||||
QString Mod::name() const
|
|
||||||
{
|
|
||||||
auto & d = details();
|
|
||||||
if(!d.name.isEmpty()) {
|
|
||||||
return d.name;
|
|
||||||
}
|
}
|
||||||
return m_name;
|
return m_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Mod::homeurl() const
|
auto Mod::version() const -> QString
|
||||||
|
{
|
||||||
|
return details().version;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Mod::homeurl() const -> QString
|
||||||
{
|
{
|
||||||
return details().homeurl;
|
return details().homeurl;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Mod::description() const
|
auto Mod::description() const -> QString
|
||||||
{
|
{
|
||||||
return details().description;
|
return details().description;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Mod::authors() const
|
auto Mod::authors() const -> QStringList
|
||||||
{
|
{
|
||||||
return details().authors;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,28 +1,46 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
/*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* PolyMC - Minecraft Launcher
|
||||||
* you may not use this file except in compliance with the License.
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
* You may obtain a copy of the License at
|
*
|
||||||
*
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* it under the terms of the GNU General Public License as published by
|
||||||
*
|
* the Free Software Foundation, version 3.
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
*
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* See the License for the specific language governing permissions and
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* limitations under the License.
|
* 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
|
#pragma once
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "ModDetails.h"
|
#include "ModDetails.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Mod
|
class Mod
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -32,84 +50,69 @@ public:
|
|||||||
MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
|
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_SINGLEFILE, //!< The mod is a single file (not a zip file).
|
||||||
MOD_FOLDER, //!< The mod is in a folder on the filesystem.
|
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() = default;
|
||||||
Mod(const QFileInfo &file);
|
Mod(const QFileInfo &file);
|
||||||
|
explicit Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
|
||||||
|
|
||||||
QFileInfo filename() const
|
auto fileinfo() const -> QFileInfo { return m_file; }
|
||||||
{
|
auto dateTimeChanged() const -> QDateTime { return m_changedDateTime; }
|
||||||
return m_file;
|
auto internal_id() const -> QString { return m_internal_id; }
|
||||||
}
|
auto type() const -> ModType { return m_type; }
|
||||||
QString mmc_id() const
|
auto enabled() const -> bool { return m_enabled; }
|
||||||
{
|
|
||||||
return m_mmc_id;
|
|
||||||
}
|
|
||||||
ModType type() const
|
|
||||||
{
|
|
||||||
return m_type;
|
|
||||||
}
|
|
||||||
bool valid()
|
|
||||||
{
|
|
||||||
return m_type != MOD_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDateTime dateTimeChanged() const
|
auto valid() const -> bool { return m_type != MOD_UNKNOWN; }
|
||||||
{
|
|
||||||
return m_changedDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool enabled() const
|
auto details() const -> const ModDetails&;
|
||||||
{
|
auto name() const -> QString;
|
||||||
return m_enabled;
|
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;
|
void setStatus(ModStatus status);
|
||||||
QString version() const;
|
void setMetadata(Metadata::ModStruct* metadata);
|
||||||
QString homeurl() const;
|
|
||||||
QString description() const;
|
|
||||||
QStringList authors() const;
|
|
||||||
|
|
||||||
bool enable(bool value);
|
auto enable(bool value) -> bool;
|
||||||
|
|
||||||
// delete all the files of this mod
|
// 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)
|
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
|
||||||
void repath(const QFileInfo &file);
|
void repath(const QFileInfo &file);
|
||||||
|
|
||||||
bool shouldResolve() {
|
auto shouldResolve() const -> bool { return !m_resolving && !m_resolved; }
|
||||||
return !m_resolving && !m_resolved;
|
auto isResolving() const -> bool { return m_resolving; }
|
||||||
}
|
auto resolutionTicket() const -> int { return m_resolutionTicket; }
|
||||||
bool isResolving() {
|
|
||||||
return m_resolving;
|
|
||||||
}
|
|
||||||
int resolutionTicket()
|
|
||||||
{
|
|
||||||
return m_resolutionTicket;
|
|
||||||
}
|
|
||||||
void setResolving(bool resolving, int resolutionTicket) {
|
void setResolving(bool resolving, int resolutionTicket) {
|
||||||
m_resolving = resolving;
|
m_resolving = resolving;
|
||||||
m_resolutionTicket = resolutionTicket;
|
m_resolutionTicket = resolutionTicket;
|
||||||
}
|
}
|
||||||
void finishResolvingWithDetails(std::shared_ptr<ModDetails> details){
|
void finishResolvingWithDetails(std::shared_ptr<ModDetails> details);
|
||||||
m_resolving = false;
|
|
||||||
m_resolved = true;
|
|
||||||
m_localDetails = details;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QFileInfo m_file;
|
QFileInfo m_file;
|
||||||
QDateTime m_changedDateTime;
|
QDateTime m_changedDateTime;
|
||||||
QString m_mmc_id;
|
|
||||||
|
QString m_internal_id;
|
||||||
|
/* Name as reported via the file name */
|
||||||
QString m_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_enabled = true;
|
||||||
bool m_resolving = false;
|
bool m_resolving = false;
|
||||||
bool m_resolved = false;
|
bool m_resolved = false;
|
||||||
int m_resolutionTicket = 0;
|
int m_resolutionTicket = 0;
|
||||||
ModType m_type = MOD_UNKNOWN;
|
|
||||||
std::shared_ptr<ModDetails> m_localDetails;
|
|
||||||
};
|
};
|
||||||
|
@ -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
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#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
|
struct ModDetails
|
||||||
{
|
{
|
||||||
|
/* Mod ID as defined in the ModLoader-specific metadata */
|
||||||
QString mod_id;
|
QString mod_id;
|
||||||
|
|
||||||
|
/* Human-readable name */
|
||||||
QString name;
|
QString name;
|
||||||
|
|
||||||
|
/* Human-readable mod version */
|
||||||
QString version;
|
QString version;
|
||||||
|
|
||||||
|
/* Human-readable minecraft version */
|
||||||
QString mcversion;
|
QString mcversion;
|
||||||
|
|
||||||
|
/* URL for mod's home page */
|
||||||
QString homeurl;
|
QString homeurl;
|
||||||
QString updateurl;
|
|
||||||
|
/* Human-readable description */
|
||||||
QString description;
|
QString description;
|
||||||
|
|
||||||
|
/* List of the author's names */
|
||||||
QStringList authors;
|
QStringList authors;
|
||||||
QString credits;
|
|
||||||
|
/* Installation status of the mod */
|
||||||
|
ModStatus status;
|
||||||
|
|
||||||
|
/* Metadata information, if any */
|
||||||
|
std::shared_ptr<Metadata::ModStruct> metadata;
|
||||||
};
|
};
|
||||||
|
@ -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();
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
@ -14,17 +14,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ModFolderModel.h"
|
#include "ModFolderModel.h"
|
||||||
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFileSystemWatcher>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <QString>
|
||||||
|
#include <QThreadPool>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
#include <QString>
|
|
||||||
#include <QFileSystemWatcher>
|
|
||||||
#include <QDebug>
|
|
||||||
#include "ModFolderLoadTask.h"
|
|
||||||
#include <QThreadPool>
|
|
||||||
#include <algorithm>
|
#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)
|
ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir)
|
||||||
{
|
{
|
||||||
@ -79,10 +81,14 @@ bool ModFolderModel::update()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto task = new ModFolderLoadTask(m_dir);
|
auto index_dir = indexDir();
|
||||||
|
auto task = new ModFolderLoadTask(dir(), index_dir);
|
||||||
|
|
||||||
m_update = task->result();
|
m_update = task->result();
|
||||||
|
|
||||||
QThreadPool *threadPool = QThreadPool::globalInstance();
|
QThreadPool *threadPool = QThreadPool::globalInstance();
|
||||||
connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
|
connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
|
||||||
|
|
||||||
threadPool->start(task);
|
threadPool->start(task);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -153,7 +159,7 @@ void ModFolderModel::finishUpdate()
|
|||||||
modsIndex.clear();
|
modsIndex.clear();
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
for(auto & mod: mods) {
|
for(auto & mod: mods) {
|
||||||
modsIndex[mod.mmc_id()] = idx;
|
modsIndex[mod.internal_id()] = idx;
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,9 +180,9 @@ void ModFolderModel::resolveMod(Mod& m)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename());
|
auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.fileinfo());
|
||||||
auto result = task->result();
|
auto result = task->result();
|
||||||
result->id = m.mmc_id();
|
result->id = m.internal_id();
|
||||||
activeTickets.insert(nextResolutionTicket, result);
|
activeTickets.insert(nextResolutionTicket, result);
|
||||||
m.setResolving(true, nextResolutionTicket);
|
m.setResolving(true, nextResolutionTicket);
|
||||||
nextResolutionTicket++;
|
nextResolutionTicket++;
|
||||||
@ -333,8 +339,12 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
|||||||
|
|
||||||
for (auto i: indexes)
|
for (auto i: indexes)
|
||||||
{
|
{
|
||||||
|
if(i.column() != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Mod &m = mods[i.row()];
|
Mod &m = mods[i.row()];
|
||||||
m.destroy();
|
auto index_dir = indexDir();
|
||||||
|
m.destroy(index_dir);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -381,7 +391,7 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
case Qt::ToolTipRole:
|
case Qt::ToolTipRole:
|
||||||
return mods[row].mmc_id();
|
return mods[row].internal_id();
|
||||||
|
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole:
|
||||||
switch (column)
|
switch (column)
|
||||||
@ -436,11 +446,11 @@ bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction actio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// preserve the row, but change its ID
|
// preserve the row, but change its ID
|
||||||
auto oldId = mod.mmc_id();
|
auto oldId = mod.internal_id();
|
||||||
if(!mod.enable(!mod.enabled())) {
|
if(!mod.enable(!mod.enabled())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto newId = mod.mmc_id();
|
auto newId = mod.internal_id();
|
||||||
if(modsIndex.contains(newId)) {
|
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
|
// 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?
|
// But is it necessary?
|
||||||
|
@ -24,8 +24,8 @@
|
|||||||
|
|
||||||
#include "Mod.h"
|
#include "Mod.h"
|
||||||
|
|
||||||
#include "ModFolderLoadTask.h"
|
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||||
#include "LocalModParseTask.h"
|
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||||
|
|
||||||
class LegacyInstance;
|
class LegacyInstance;
|
||||||
class BaseInstance;
|
class BaseInstance;
|
||||||
@ -108,11 +108,16 @@ public:
|
|||||||
|
|
||||||
bool isValid();
|
bool isValid();
|
||||||
|
|
||||||
QDir dir()
|
QDir& dir()
|
||||||
{
|
{
|
||||||
return m_dir;
|
return m_dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDir indexDir()
|
||||||
|
{
|
||||||
|
return { QString("%1/.index").arg(dir().absolutePath()) };
|
||||||
|
}
|
||||||
|
|
||||||
const QList<Mod> & allMods()
|
const QList<Mod> & allMods()
|
||||||
{
|
{
|
||||||
return mods;
|
return mods;
|
||||||
|
@ -35,7 +35,6 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
|
|||||||
details->name = name;
|
details->name = name;
|
||||||
}
|
}
|
||||||
details->version = firstObj.value("version").toString();
|
details->version = firstObj.value("version").toString();
|
||||||
details->updateurl = firstObj.value("updateUrl").toString();
|
|
||||||
auto homeurl = firstObj.value("url").toString().trimmed();
|
auto homeurl = firstObj.value("url").toString().trimmed();
|
||||||
if(!homeurl.isEmpty())
|
if(!homeurl.isEmpty())
|
||||||
{
|
{
|
||||||
@ -57,7 +56,6 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
|
|||||||
{
|
{
|
||||||
details->authors.append(author.toString());
|
details->authors.append(author.toString());
|
||||||
}
|
}
|
||||||
details->credits = firstObj.value("credits").toString();
|
|
||||||
return details;
|
return details;
|
||||||
};
|
};
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
@ -168,27 +166,9 @@ std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
|
|||||||
}
|
}
|
||||||
if(!authors.isEmpty())
|
if(!authors.isEmpty())
|
||||||
{
|
{
|
||||||
// author information is stored as a string now, not a list
|
|
||||||
details->authors.append(authors);
|
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");
|
toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL");
|
||||||
QString homeurl = "";
|
QString homeurl = "";
|
||||||
if(homeurlDatum.ok)
|
if(homeurlDatum.ok)
|
@ -1,9 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QRunnable>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include "Mod.h"
|
#include <QRunnable>
|
||||||
#include "ModDetails.h"
|
|
||||||
|
#include "minecraft/mod/Mod.h"
|
||||||
|
#include "minecraft/mod/ModDetails.h"
|
||||||
|
|
||||||
class LocalModParseTask : public QObject, public QRunnable
|
class LocalModParseTask : public QObject, public QRunnable
|
||||||
{
|
{
|
53
launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
Normal file
53
launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
Normal 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;
|
||||||
|
}
|
44
launcher/minecraft/mod/tasks/LocalModUpdateTask.h
Normal file
44
launcher/minecraft/mod/tasks/LocalModUpdateTask.h
Normal 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;
|
||||||
|
};
|
82
launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
Normal file
82
launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
69
launcher/minecraft/mod/tasks/ModFolderLoadTask.h
Normal file
69
launcher/minecraft/mod/tasks/ModFolderLoadTask.h
Normal 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;
|
||||||
|
};
|
86
launcher/modplatform/ModIndex.cpp
Normal file
86
launcher/modplatform/ModIndex.cpp
Normal 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
|
@ -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
|
#pragma once
|
||||||
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
@ -8,6 +26,19 @@
|
|||||||
|
|
||||||
namespace ModPlatform {
|
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 {
|
struct ModpackAuthor {
|
||||||
QString name;
|
QString name;
|
||||||
QString url;
|
QString url;
|
||||||
@ -22,10 +53,13 @@ struct IndexedVersion {
|
|||||||
QString date;
|
QString date;
|
||||||
QString fileName;
|
QString fileName;
|
||||||
QVector<QString> loaders = {};
|
QVector<QString> loaders = {};
|
||||||
|
QString hash_type;
|
||||||
|
QString hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IndexedPack {
|
struct IndexedPack {
|
||||||
QVariant addonId;
|
QVariant addonId;
|
||||||
|
Provider provider;
|
||||||
QString name;
|
QString name;
|
||||||
QString description;
|
QString description;
|
||||||
QList<ModpackAuthor> authors;
|
QList<ModpackAuthor> authors;
|
||||||
@ -40,3 +74,4 @@ struct IndexedPack {
|
|||||||
} // namespace ModPlatform
|
} // namespace ModPlatform
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
|
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
|
||||||
|
Q_DECLARE_METATYPE(ModPlatform::Provider)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
#include "modplatform/helpers/NetworkModAPI.h"
|
#include "modplatform/helpers/NetworkModAPI.h"
|
||||||
|
|
||||||
class FlameAPI : public NetworkModAPI {
|
class FlameAPI : public NetworkModAPI {
|
||||||
|
@ -6,9 +6,12 @@
|
|||||||
#include "modplatform/flame/FlameAPI.h"
|
#include "modplatform/flame/FlameAPI.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
|
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||||
|
|
||||||
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||||
{
|
{
|
||||||
pack.addonId = Json::requireInteger(obj, "id");
|
pack.addonId = Json::requireInteger(obj, "id");
|
||||||
|
pack.provider = ModPlatform::Provider::FLAME;
|
||||||
pack.name = Json::requireString(obj, "name");
|
pack.name = Json::requireString(obj, "name");
|
||||||
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
|
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
|
||||||
pack.description = Json::ensureString(obj, "summary", "");
|
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,
|
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||||
QJsonArray& arr,
|
QJsonArray& arr,
|
||||||
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||||
@ -38,28 +52,13 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
|||||||
|
|
||||||
for (auto versionIter : arr) {
|
for (auto versionIter : arr) {
|
||||||
auto obj = versionIter.toObject();
|
auto obj = versionIter.toObject();
|
||||||
|
|
||||||
|
auto file = loadIndexedPackVersion(obj);
|
||||||
|
if(!file.addonId.isValid())
|
||||||
|
file.addonId = pack.addonId;
|
||||||
|
|
||||||
auto versionArray = Json::requireArray(obj, "gameVersions");
|
if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||||
if (versionArray.isEmpty()) {
|
unsortedVersions.append(file);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
|
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.versions = unsortedVersions;
|
||||||
pack.versionsLoaded = true;
|
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;
|
||||||
|
}
|
||||||
|
@ -16,5 +16,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
|||||||
QJsonArray& arr,
|
QJsonArray& arr,
|
||||||
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||||
BaseInstance* inst);
|
BaseInstance* inst);
|
||||||
|
auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion;
|
||||||
|
|
||||||
} // namespace FlameMod
|
} // namespace FlameMod
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "modplatform/ModAPI.h"
|
#include "modplatform/ModAPI.h"
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
#include "modplatform/helpers/NetworkModAPI.h"
|
#include "modplatform/helpers/NetworkModAPI.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* the Free Software Foundation, version 3.
|
* 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
|
* This program is distributed in the hope that it will be useful,
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* GNU General Public License for more details.
|
* 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/>.
|
* 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 "ModrinthPackIndex.h"
|
||||||
#include "ModrinthAPI.h"
|
#include "ModrinthAPI.h"
|
||||||
@ -24,10 +25,12 @@
|
|||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
static ModrinthAPI api;
|
static ModrinthAPI api;
|
||||||
|
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||||
|
|
||||||
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||||
{
|
{
|
||||||
pack.addonId = Json::requireString(obj, "project_id");
|
pack.addonId = Json::requireString(obj, "project_id");
|
||||||
|
pack.provider = ModPlatform::Provider::MODRINTH;
|
||||||
pack.name = Json::requireString(obj, "title");
|
pack.name = Json::requireString(obj, "title");
|
||||||
|
|
||||||
QString slug = Json::ensureString(obj, "slug", "");
|
QString slug = Json::ensureString(obj, "slug", "");
|
||||||
@ -57,46 +60,10 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
|||||||
|
|
||||||
for (auto versionIter : arr) {
|
for (auto versionIter : arr) {
|
||||||
auto obj = versionIter.toObject();
|
auto obj = versionIter.toObject();
|
||||||
ModPlatform::IndexedVersion file;
|
auto file = loadIndexedPackVersion(obj);
|
||||||
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");
|
|
||||||
|
|
||||||
|
if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||||
unsortedVersions.append(file);
|
unsortedVersions.append(file);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
|
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
|
||||||
// dates are in RFC 3339 format
|
// dates are in RFC 3339 format
|
||||||
@ -106,3 +73,61 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
|||||||
pack.versions = unsortedVersions;
|
pack.versions = unsortedVersions;
|
||||||
pack.versionsLoaded = true;
|
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 {};
|
||||||
|
}
|
||||||
|
@ -29,5 +29,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
|||||||
QJsonArray& arr,
|
QJsonArray& arr,
|
||||||
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||||
BaseInstance* inst);
|
BaseInstance* inst);
|
||||||
|
auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion;
|
||||||
|
|
||||||
} // namespace Modrinth
|
} // namespace Modrinth
|
||||||
|
289
launcher/modplatform/packwiz/Packwiz.cpp
Normal file
289
launcher/modplatform/packwiz/Packwiz.cpp
Normal 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
|
93
launcher/modplatform/packwiz/Packwiz.h
Normal file
93
launcher/modplatform/packwiz/Packwiz.h
Normal 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
|
86
launcher/modplatform/packwiz/Packwiz_test.cpp
Normal file
86
launcher/modplatform/packwiz/Packwiz_test.cpp
Normal 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"
|
BIN
launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml
vendored
Normal file
BIN
launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml
vendored
Normal file
Binary file not shown.
BIN
launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml
vendored
Normal file
BIN
launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml
vendored
Normal file
Binary file not shown.
@ -64,12 +64,18 @@ void SequentialTask::startNext()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Task::Ptr next = m_queue[m_currentIndex];
|
Task::Ptr next = m_queue[m_currentIndex];
|
||||||
|
|
||||||
connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString)));
|
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(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()));
|
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();
|
next->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +85,7 @@ void SequentialTask::subTaskFailed(const QString& msg)
|
|||||||
}
|
}
|
||||||
void SequentialTask::subTaskStatus(const QString& msg)
|
void SequentialTask::subTaskStatus(const QString& msg)
|
||||||
{
|
{
|
||||||
setStepStatus(m_queue[m_currentIndex]->getStatus());
|
setStepStatus(msg);
|
||||||
}
|
}
|
||||||
void SequentialTask::subTaskProgress(qint64 current, qint64 total)
|
void SequentialTask::subTaskProgress(qint64 current, qint64 total)
|
||||||
{
|
{
|
||||||
|
@ -32,13 +32,10 @@ slots:
|
|||||||
void subTaskStatus(const QString &msg);
|
void subTaskStatus(const QString &msg);
|
||||||
void subTaskProgress(qint64 current, qint64 total);
|
void subTaskProgress(qint64 current, qint64 total);
|
||||||
|
|
||||||
signals:
|
protected:
|
||||||
void stepStatus(QString status);
|
void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); };
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
void setStepStatus(QString status) { m_step_status = status; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_name;
|
QString m_name;
|
||||||
QString m_step_status;
|
QString m_step_status;
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ class Task : public QObject {
|
|||||||
void aborted();
|
void aborted();
|
||||||
void failed(QString reason);
|
void failed(QString reason);
|
||||||
void status(QString status);
|
void status(QString status);
|
||||||
|
void stepStatus(QString status);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void start();
|
virtual void start();
|
||||||
|
@ -184,6 +184,11 @@ void LauncherPage::on_modsDirBrowseBtn_clicked()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LauncherPage::on_metadataDisableBtn_clicked()
|
||||||
|
{
|
||||||
|
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
void LauncherPage::refreshUpdateChannelList()
|
void LauncherPage::refreshUpdateChannelList()
|
||||||
{
|
{
|
||||||
// Stop listening for selection changes. It's going to change a lot while we update it and
|
// 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");
|
s->set("InstSortMode", "Name");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mods
|
||||||
|
s->set("ModMetadataDisabled", ui->metadataDisableBtn->isChecked());
|
||||||
}
|
}
|
||||||
void LauncherPage::loadSettings()
|
void LauncherPage::loadSettings()
|
||||||
{
|
{
|
||||||
@ -440,6 +448,10 @@ void LauncherPage::loadSettings()
|
|||||||
{
|
{
|
||||||
ui->sortByNameBtn->setChecked(true);
|
ui->sortByNameBtn->setChecked(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mods
|
||||||
|
ui->metadataDisableBtn->setChecked(s->get("DontUseModMetadata").toBool());
|
||||||
|
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherPage::refreshFontPreview()
|
void LauncherPage::refreshFontPreview()
|
||||||
|
@ -88,6 +88,7 @@ slots:
|
|||||||
void on_instDirBrowseBtn_clicked();
|
void on_instDirBrowseBtn_clicked();
|
||||||
void on_modsDirBrowseBtn_clicked();
|
void on_modsDirBrowseBtn_clicked();
|
||||||
void on_iconsDirBrowseBtn_clicked();
|
void on_iconsDirBrowseBtn_clicked();
|
||||||
|
void on_metadataDisableBtn_clicked();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Updates the list of update channels in the combo box.
|
* Updates the list of update channels in the combo box.
|
||||||
|
@ -94,19 +94,13 @@
|
|||||||
<string>Folders</string>
|
<string>Folders</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="foldersBoxLayout">
|
<layout class="QGridLayout" name="foldersBoxLayout">
|
||||||
<item row="0" column="0">
|
<item row="1" column="2">
|
||||||
<widget class="QLabel" name="labelInstDir">
|
<widget class="QToolButton" name="modsDirBrowseBtn">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>I&nstances:</string>
|
<string notr="true">...</string>
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>instDirTextBox</cstring>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLineEdit" name="instDirTextBox"/>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="2">
|
<item row="0" column="2">
|
||||||
<widget class="QToolButton" name="instDirBrowseBtn">
|
<widget class="QToolButton" name="instDirBrowseBtn">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -114,28 +108,15 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="2" column="2">
|
||||||
<widget class="QLabel" name="labelModsDir">
|
<widget class="QToolButton" name="iconsDirBrowseBtn">
|
||||||
<property name="text">
|
|
||||||
<string>&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">
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true">...</string>
|
<string notr="true">...</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QLineEdit" name="iconsDirTextBox"/>
|
<widget class="QLineEdit" name="instDirTextBox"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="labelIconsDir">
|
<widget class="QLabel" name="labelIconsDir">
|
||||||
@ -147,10 +128,58 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="2">
|
<item row="1" column="1">
|
||||||
<widget class="QToolButton" name="iconsDirBrowseBtn">
|
<widget class="QLineEdit" name="modsDirTextBox"/>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="labelInstDir">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true">...</string>
|
<string>I&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>&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><html><head/><body><p><span style=" font-weight:600; color:#f5c211;">Warning</span><span style=" color:#f5c211;">: Disabling mod metadata may also disable some upcoming QoL features, such as mod updating!</span></p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -150,7 +150,7 @@ void ModPage::onModSelected()
|
|||||||
if (dialog->isModSelected(current.name, version.fileName)) {
|
if (dialog->isModSelected(current.name, version.fileName)) {
|
||||||
dialog->removeSelectedMod(current.name);
|
dialog->removeSelectedMod(current.name);
|
||||||
} else {
|
} else {
|
||||||
dialog->addSelectedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName, dialog->mods));
|
dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectionButton();
|
updateSelectionButton();
|
||||||
|
@ -32,7 +32,7 @@ void MCModInfoFrame::updateWithMod(Mod &m)
|
|||||||
QString text = "";
|
QString text = "";
|
||||||
QString name = "";
|
QString name = "";
|
||||||
if (m.name().isEmpty())
|
if (m.name().isEmpty())
|
||||||
name = m.mmc_id();
|
name = m.internal_id();
|
||||||
else
|
else
|
||||||
name = m.name();
|
name = m.name();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user