Merge branch 'develop' into feat/launcher-updater
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
@ -4,6 +4,7 @@
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
* Copyright (c) 2023 seth <getchoo at tuta dot io>
|
||||
*
|
||||
* 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
|
||||
@ -186,6 +187,10 @@ void MinecraftInstance::loadSpecificSettings()
|
||||
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
|
||||
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
|
||||
|
||||
// Mod loader specific options
|
||||
auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false);
|
||||
m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings);
|
||||
|
||||
m_settings->set("InstanceType", "OneSix");
|
||||
}
|
||||
|
||||
@ -391,6 +396,12 @@ QStringList MinecraftInstance::extraArguments()
|
||||
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
|
||||
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
|
||||
}
|
||||
|
||||
{
|
||||
const auto loaders = version->getModLoaders();
|
||||
if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool())
|
||||
list.append("-Dloader.disable_beacon=true");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@ -832,7 +843,7 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
||||
{
|
||||
addToFilter(sessionRef.session, tr("<SESSION ID>"));
|
||||
}
|
||||
if (sessionRef.access_token != "offline") {
|
||||
if (sessionRef.access_token != "0") {
|
||||
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
|
||||
}
|
||||
if(sessionRef.client_token.size()) {
|
||||
@ -1112,36 +1123,27 @@ JavaVersion MinecraftInstance::getJavaVersion()
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList()
|
||||
{
|
||||
if (!m_loader_mod_list)
|
||||
{
|
||||
if (!m_loader_mod_list) {
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed));
|
||||
m_loader_mod_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||
}
|
||||
return m_loader_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList()
|
||||
{
|
||||
if (!m_core_mod_list)
|
||||
{
|
||||
if (!m_core_mod_list) {
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed));
|
||||
m_core_mod_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||
}
|
||||
return m_core_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
|
||||
{
|
||||
if (!m_nil_mod_list)
|
||||
{
|
||||
if (!m_nil_mod_list) {
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), this, is_indexed, false));
|
||||
m_nil_mod_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||
}
|
||||
return m_nil_mod_list;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ void MinecraftLoadAndCheck::executeTask()
|
||||
connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
|
||||
connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); });
|
||||
connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
|
||||
connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propogateStepProgress);
|
||||
connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propagateStepProgress);
|
||||
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ void MinecraftUpdate::next()
|
||||
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
||||
disconnect(task.get(), &Task::aborted, this, &Task::abort);
|
||||
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
||||
disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress);
|
||||
disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress);
|
||||
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||
disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
||||
}
|
||||
@ -120,7 +120,7 @@ void MinecraftUpdate::next()
|
||||
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
||||
connect(task.get(), &Task::aborted, this, &Task::abort);
|
||||
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
||||
connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress);
|
||||
connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress);
|
||||
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||
connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
||||
// if the task is already running, do not start it again
|
||||
|
@ -65,7 +65,8 @@
|
||||
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{
|
||||
{"net.minecraftforge", ResourceAPI::Forge},
|
||||
{"net.fabricmc.fabric-loader", ResourceAPI::Fabric},
|
||||
{"org.quiltmc.quilt-loader", ResourceAPI::Quilt}
|
||||
{"org.quiltmc.quilt-loader", ResourceAPI::Quilt},
|
||||
{"com.mumfrey.liteloader", ResourceAPI::LiteLoader}
|
||||
};
|
||||
|
||||
PackProfile::PackProfile(MinecraftInstance * instance)
|
||||
|
@ -374,6 +374,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
}
|
||||
|
||||
yggdrasilToken = tokenFromJSONV3(data, "ygg");
|
||||
// versions before 7.2 used "offline" as the offline token
|
||||
if (yggdrasilToken.token == "offline")
|
||||
yggdrasilToken.token = "0";
|
||||
|
||||
minecraftProfile = profileFromJSONV3(data, "profile");
|
||||
if(!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
||||
if(minecraftProfile.validity != Katabasis::Validity::None) {
|
||||
|
@ -328,6 +328,9 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
case AccountState::Gone: {
|
||||
return tr("Gone", "Account status");
|
||||
}
|
||||
default: {
|
||||
return tr("Unknown", "Account status");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,11 +357,12 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue(account);
|
||||
|
||||
case Qt::CheckStateRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case ProfileNameColumn:
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
if (index.column() == ProfileNameColumn) {
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
|
@ -26,6 +26,7 @@ bool AuthSession::MakeOffline(QString offline_playername)
|
||||
return false;
|
||||
}
|
||||
session = "-";
|
||||
access_token = "0";
|
||||
player_name = offline_playername;
|
||||
status = PlayableOffline;
|
||||
return true;
|
||||
|
@ -37,6 +37,7 @@
|
||||
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QUuid>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
@ -93,14 +94,14 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
|
||||
{
|
||||
auto account = makeShared<MinecraftAccount>();
|
||||
account->data.type = AccountType::Offline;
|
||||
account->data.yggdrasilToken.token = "offline";
|
||||
account->data.yggdrasilToken.token = "0";
|
||||
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain;
|
||||
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||
account->data.yggdrasilToken.extra["userName"] = username;
|
||||
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftEntitlement.ownsMinecraft = true;
|
||||
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
||||
account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftProfile.name = username;
|
||||
account->data.minecraftProfile.validity = Katabasis::Validity::Certain;
|
||||
return account;
|
||||
@ -334,3 +335,32 @@ void MinecraftAccount::incrementUses()
|
||||
qWarning() << "Profile" << data.profileId() << "is now in use.";
|
||||
}
|
||||
}
|
||||
|
||||
QUuid MinecraftAccount::uuidFromUsername(QString username) {
|
||||
auto input = QString("OfflinePlayer:%1").arg(username).toUtf8();
|
||||
|
||||
// basically a reimplementation of Java's UUID#nameUUIDFromBytes
|
||||
QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5);
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
auto bOr = [](QByteArray& array, int index, char value) {
|
||||
array[index] = array.at(index) | value;
|
||||
};
|
||||
auto bAnd = [](QByteArray& array, int index, char value) {
|
||||
array[index] = array.at(index) & value;
|
||||
};
|
||||
#else
|
||||
auto bOr = [](QByteArray& array, qsizetype index, char value) {
|
||||
array[index] |= value;
|
||||
};
|
||||
auto bAnd = [](QByteArray& array, qsizetype index, char value) {
|
||||
array[index] &= value;
|
||||
};
|
||||
#endif
|
||||
bAnd(digest, 6, (char) 0x0f); // clear version
|
||||
bOr(digest, 6, (char) 0x30); // set to version 3
|
||||
bAnd(digest, 8, (char) 0x3f); // clear variant
|
||||
bOr(digest, 8, (char) 0x80); // set to IETF variant
|
||||
|
||||
return QUuid::fromRfc4122(digest);
|
||||
}
|
||||
|
@ -98,6 +98,8 @@ public: /* construction */
|
||||
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json);
|
||||
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json);
|
||||
|
||||
static QUuid uuidFromUsername(QString username);
|
||||
|
||||
//! Saves a MinecraftAccount to a JSON object and returns it.
|
||||
QJsonObject saveToJson() const;
|
||||
|
||||
|
@ -273,6 +273,7 @@ void Yggdrasil::processReply() {
|
||||
AccountTaskState::STATE_FAILED_GONE,
|
||||
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
changeState(
|
||||
|
@ -74,6 +74,7 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
@ -83,6 +84,7 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con
|
||||
return { 1, type == SortType::PACK_FORMAT };
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::PACK_FORMAT };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
|
@ -91,6 +91,7 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::VERSION: {
|
||||
auto this_ver = Version(version());
|
||||
@ -99,11 +100,13 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||
return { 1, type == SortType::VERSION };
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::VERSION };
|
||||
break;
|
||||
}
|
||||
case SortType::PROVIDER: {
|
||||
auto compare_result = QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::PROVIDER };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
@ -123,7 +126,7 @@ bool Mod::applyFilter(QRegularExpression filter) const
|
||||
return Resource::applyFilter(filter);
|
||||
}
|
||||
|
||||
auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
|
||||
auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
|
||||
{
|
||||
if (!preserve_metadata) {
|
||||
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
|
||||
@ -136,7 +139,7 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
|
||||
}
|
||||
}
|
||||
|
||||
return Resource::destroy();
|
||||
return Resource::destroy(attempt_trash);
|
||||
}
|
||||
|
||||
auto Mod::details() const -> const ModDetails&
|
||||
@ -166,6 +169,13 @@ auto Mod::homeurl() const -> QString
|
||||
return details().homeurl;
|
||||
}
|
||||
|
||||
auto Mod::metaurl() const -> QString
|
||||
{
|
||||
if (metadata() == nullptr)
|
||||
return homeurl();
|
||||
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
|
||||
}
|
||||
|
||||
auto Mod::description() const -> QString
|
||||
{
|
||||
return details().description;
|
||||
|
@ -70,6 +70,7 @@ public:
|
||||
auto provider() const -> std::optional<QString>;
|
||||
auto licenses() const -> const QList<ModLicense>&;
|
||||
auto issueTracker() const -> QString;
|
||||
auto metaurl() const -> QString;
|
||||
|
||||
/** Get the intneral path to the mod's icon file*/
|
||||
QString iconPath() const { return m_local_details.icon_file; };
|
||||
@ -92,7 +93,7 @@ public:
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
// Delete all the files of this mod
|
||||
auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool;
|
||||
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
|
||||
|
||||
void finishResolvingWithDetails(ModDetails&& details);
|
||||
|
||||
|
@ -199,10 +199,10 @@ Task* ModFolderModel::createParseTask(Resource& resource)
|
||||
|
||||
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
|
||||
{
|
||||
for(auto mod : allMods()){
|
||||
if(mod->fileinfo().fileName() == filename){
|
||||
for(auto mod : allMods()) {
|
||||
if(mod->fileinfo().fileName() == filename) {
|
||||
auto index_dir = indexDir();
|
||||
mod->destroy(index_dir, preserve_metadata);
|
||||
mod->destroy(index_dir, preserve_metadata, false);
|
||||
|
||||
update();
|
||||
|
||||
@ -215,16 +215,11 @@ bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadat
|
||||
|
||||
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
||||
{
|
||||
if(!m_can_interact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(indexes.isEmpty())
|
||||
if (indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
for (auto i: indexes)
|
||||
{
|
||||
if(i.column() != 0) {
|
||||
for (auto i : indexes) {
|
||||
if (i.column() != 0) {
|
||||
continue;
|
||||
}
|
||||
auto m = at(i.row());
|
||||
|
@ -71,6 +71,7 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
|
||||
return { 1, type == SortType::ENABLED };
|
||||
if (!enabled() && other.enabled())
|
||||
return { -1, type == SortType::ENABLED };
|
||||
break;
|
||||
case SortType::NAME: {
|
||||
QString this_name{ name() };
|
||||
QString other_name{ other.name() };
|
||||
@ -81,12 +82,14 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
|
||||
auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::NAME };
|
||||
break;
|
||||
}
|
||||
case SortType::DATE:
|
||||
if (dateTimeChanged() > other.dateTimeChanged())
|
||||
return { 1, type == SortType::DATE };
|
||||
if (dateTimeChanged() < other.dateTimeChanged())
|
||||
return { -1, type == SortType::DATE };
|
||||
break;
|
||||
}
|
||||
|
||||
return { 0, false };
|
||||
@ -145,14 +148,10 @@ bool Resource::enable(EnableAction action)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Resource::destroy()
|
||||
bool Resource::destroy(bool attemptTrash)
|
||||
{
|
||||
m_type = ResourceType::UNKNOWN;
|
||||
|
||||
if (FS::trash(m_file_info.filePath()))
|
||||
return true;
|
||||
|
||||
return FS::deletePath(m_file_info.filePath());
|
||||
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
|
||||
}
|
||||
|
||||
bool Resource::isSymLinkUnder(const QString& instPath) const
|
||||
|
@ -92,7 +92,7 @@ class Resource : public QObject {
|
||||
}
|
||||
|
||||
// Delete all files of this resource.
|
||||
bool destroy();
|
||||
bool destroy(bool attemptTrash = true);
|
||||
|
||||
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
#include "ResourceFolderModel.h"
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
#include <QMenu>
|
||||
#include <QMimeData>
|
||||
#include <QStyle>
|
||||
#include <QThreadPool>
|
||||
#include <QUrl>
|
||||
#include <QMenu>
|
||||
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
@ -18,6 +19,7 @@
|
||||
|
||||
#include "settings/Setting.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
||||
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
|
||||
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
|
||||
@ -77,10 +79,6 @@ bool ResourceFolderModel::stopWatching(const QStringList paths)
|
||||
|
||||
bool ResourceFolderModel::installResource(QString original_path)
|
||||
{
|
||||
if (!m_can_interact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
|
||||
original_path = FS::NormalizePath(original_path);
|
||||
QFileInfo file_info(original_path);
|
||||
@ -159,7 +157,7 @@ bool ResourceFolderModel::uninstallResource(QString file_name)
|
||||
{
|
||||
for (auto& resource : m_resources) {
|
||||
if (resource->fileinfo().fileName() == file_name) {
|
||||
auto res = resource->destroy();
|
||||
auto res = resource->destroy(false);
|
||||
|
||||
update();
|
||||
|
||||
@ -171,9 +169,6 @@ bool ResourceFolderModel::uninstallResource(QString file_name)
|
||||
|
||||
bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
||||
{
|
||||
if (!m_can_interact)
|
||||
return false;
|
||||
|
||||
if (indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
@ -192,11 +187,8 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList &indexes, EnableAction action)
|
||||
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
|
||||
{
|
||||
if (!m_can_interact)
|
||||
return false;
|
||||
|
||||
if (indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
@ -249,15 +241,18 @@ bool ResourceFolderModel::update()
|
||||
connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded,
|
||||
Qt::ConnectionType::QueuedConnection);
|
||||
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
|
||||
connect(m_current_update_task.get(), &Task::finished, this, [=] {
|
||||
m_current_update_task.reset();
|
||||
if (m_scheduled_update) {
|
||||
m_scheduled_update = false;
|
||||
update();
|
||||
} else {
|
||||
emit updateFinished();
|
||||
}
|
||||
}, Qt::ConnectionType::QueuedConnection);
|
||||
connect(
|
||||
m_current_update_task.get(), &Task::finished, this,
|
||||
[=] {
|
||||
m_current_update_task.reset();
|
||||
if (m_scheduled_update) {
|
||||
m_scheduled_update = false;
|
||||
update();
|
||||
} else {
|
||||
emit updateFinished();
|
||||
}
|
||||
},
|
||||
Qt::ConnectionType::QueuedConnection);
|
||||
|
||||
QThreadPool::globalInstance()->start(m_current_update_task.get());
|
||||
|
||||
@ -347,15 +342,9 @@ Qt::DropActions ResourceFolderModel::supportedDropActions() const
|
||||
Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
auto flags = defaultFlags;
|
||||
if (!m_can_interact) {
|
||||
flags &= ~Qt::ItemIsDropEnabled;
|
||||
} else {
|
||||
flags |= Qt::ItemIsDropEnabled;
|
||||
if (index.isValid()) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
}
|
||||
}
|
||||
auto flags = defaultFlags | Qt::ItemIsDropEnabled;
|
||||
if (index.isValid())
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
return flags;
|
||||
}
|
||||
|
||||
@ -428,16 +417,17 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||
if (column == NAME_COLUMN) {
|
||||
if (at(row).isSymLinkUnder(instDirPath())) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
"\nCanonical Path: %1")
|
||||
.arg(at(row).fileinfo().canonicalFilePath());;
|
||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
"\nCanonical Path: %1")
|
||||
.arg(at(row).fileinfo().canonicalFilePath());
|
||||
;
|
||||
}
|
||||
if (at(row).isMoreThanOneHardLink()) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return m_resources[row]->internal_id();
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||
@ -463,8 +453,20 @@ bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& valu
|
||||
if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
|
||||
return false;
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
if (role == Qt::CheckStateRole) {
|
||||
if (m_instance != nullptr && m_instance->isRunning()) {
|
||||
auto response =
|
||||
CustomMessageBox::selectable(nullptr, "Confirm toggle",
|
||||
"If you enable/disable this resource while the game is running it may crash your game.\n"
|
||||
"Are you sure you want to do this?",
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response != QMessageBox::Yes)
|
||||
return false;
|
||||
}
|
||||
return setResourceEnabled({ index }, EnableAction::TOGGLE);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -583,16 +585,6 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const
|
||||
return m_column_sort_keys.at(column);
|
||||
}
|
||||
|
||||
void ResourceFolderModel::enableInteraction(bool enabled)
|
||||
{
|
||||
if (m_can_interact == enabled)
|
||||
return;
|
||||
|
||||
m_can_interact = enabled;
|
||||
if (size())
|
||||
emit dataChanged(index(0), index(size() - 1));
|
||||
}
|
||||
|
||||
/* Standard Proxy Model for createFilterProxyModel */
|
||||
[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
|
||||
{
|
||||
@ -628,6 +620,7 @@ void ResourceFolderModel::enableInteraction(bool enabled)
|
||||
return (compare_result.first > 0);
|
||||
}
|
||||
|
||||
QString ResourceFolderModel::instDirPath() const {
|
||||
QString ResourceFolderModel::instDirPath() const
|
||||
{
|
||||
return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
|
||||
}
|
||||
|
@ -14,8 +14,8 @@
|
||||
|
||||
#include "BaseInstance.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
@ -141,10 +141,6 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
|
||||
QString instDirPath() const;
|
||||
|
||||
public slots:
|
||||
void enableInteraction(bool enabled);
|
||||
void disableInteraction(bool disabled) { enableInteraction(!disabled); }
|
||||
|
||||
signals:
|
||||
void updateFinished();
|
||||
|
||||
@ -193,7 +189,11 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
* if the resource is complex and has more stuff to parse.
|
||||
*/
|
||||
virtual void onParseSucceeded(int ticket, QString resource_id);
|
||||
virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); }
|
||||
virtual void onParseFailed(int ticket, QString resource_id)
|
||||
{
|
||||
Q_UNUSED(ticket);
|
||||
Q_UNUSED(resource_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
||||
@ -203,8 +203,6 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("Last Modified")};
|
||||
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
|
||||
|
||||
bool m_can_interact = true;
|
||||
|
||||
QDir m_dir;
|
||||
BaseInstance* m_instance;
|
||||
QFileSystemWatcher m_watcher;
|
||||
|
@ -102,6 +102,7 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type)
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
@ -111,6 +112,7 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type)
|
||||
return { 1, type == SortType::PACK_FORMAT };
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::PACK_FORMAT };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
|
@ -1,9 +1,9 @@
|
||||
#include "LocalModParseTask.h"
|
||||
|
||||
#include <qdcss.h>
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#include <toml++/toml.h>
|
||||
#include <qdcss.h>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
@ -370,12 +370,11 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
|
||||
details.icon_file = icon.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
ModDetails ReadForgeInfo(QString fileName)
|
||||
ModDetails ReadForgeInfo(QByteArray contents)
|
||||
{
|
||||
ModDetails details;
|
||||
// Read the data
|
||||
@ -383,7 +382,7 @@ ModDetails ReadForgeInfo(QString fileName)
|
||||
details.mod_id = "Forge";
|
||||
details.homeurl = "http://www.minecraftforge.net/forum/";
|
||||
INIFile ini;
|
||||
if (!ini.loadFile(fileName))
|
||||
if (!ini.loadFile(contents))
|
||||
return details;
|
||||
|
||||
QString major = ini.get("forge.major.number", "0").toString();
|
||||
@ -555,7 +554,7 @@ bool processZIP(Mod& mod, ProcessingLevel level)
|
||||
return false;
|
||||
}
|
||||
|
||||
details = ReadForgeInfo(file.getFileName());
|
||||
details = ReadForgeInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
|
||||
|
@ -44,7 +44,11 @@ static const QMap<PackedResourceType, QString> s_packed_type_names = {
|
||||
namespace ResourceUtils {
|
||||
PackedResourceType identify(QFileInfo file){
|
||||
if (file.exists() && file.isFile()) {
|
||||
if (ResourcePackUtils::validate(file)) {
|
||||
if (ModUtils::validate(file)) {
|
||||
// mods can contain resource and data packs so they must be tested first
|
||||
qDebug() << file.fileName() << "is a mod";
|
||||
return PackedResourceType::Mod;
|
||||
} else if (ResourcePackUtils::validate(file)) {
|
||||
qDebug() << file.fileName() << "is a resource pack";
|
||||
return PackedResourceType::ResourcePack;
|
||||
} else if (TexturePackUtils::validate(file)) {
|
||||
@ -53,9 +57,6 @@ PackedResourceType identify(QFileInfo file){
|
||||
} else if (DataPackUtils::validate(file)) {
|
||||
qDebug() << file.fileName() << "is a data pack";
|
||||
return PackedResourceType::DataPack;
|
||||
} else if (ModUtils::validate(file)) {
|
||||
qDebug() << file.fileName() << "is a mod";
|
||||
return PackedResourceType::Mod;
|
||||
} else if (WorldSaveUtils::validate(file)) {
|
||||
qDebug() << file.fileName() << "is a world save";
|
||||
return PackedResourceType::WorldSave;
|
||||
|
@ -103,7 +103,7 @@ void ModFolderLoadTask::executeTask()
|
||||
while (iter.hasNext()) {
|
||||
auto mod = iter.next().value();
|
||||
if (mod->status() == ModStatus::NotInstalled) {
|
||||
mod->destroy(m_index_dir, false);
|
||||
mod->destroy(m_index_dir, false, false);
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ void AssetUpdateTask::executeTask()
|
||||
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
|
||||
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
||||
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress);
|
||||
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propagateStepProgress);
|
||||
|
||||
qDebug() << m_inst->name() << ": Starting asset index download";
|
||||
downloadJob->start();
|
||||
@ -86,7 +86,7 @@ void AssetUpdateTask::assetIndexFinished()
|
||||
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
|
||||
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
||||
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress);
|
||||
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propagateStepProgress);
|
||||
downloadJob->start();
|
||||
return;
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ void FMLLibrariesTask::executeTask()
|
||||
connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
|
||||
connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||
connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress);
|
||||
connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propogateStepProgress);
|
||||
connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propagateStepProgress);
|
||||
downloadJob.reset(dljob);
|
||||
downloadJob->start();
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ void LibrariesTask::executeTask()
|
||||
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
|
||||
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
|
||||
connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propogateStepProgress);
|
||||
connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propagateStepProgress);
|
||||
|
||||
downloadJob->start();
|
||||
}
|
||||
|
Reference in New Issue
Block a user