Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into feat/launcher-updater
This commit is contained in:
commit
6bbff310bc
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@ -53,18 +53,6 @@ jobs:
|
||||
msystem: clang64
|
||||
vcvars_arch: 'amd64_x86'
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC-Legacy"
|
||||
msystem: ''
|
||||
architecture: 'win32'
|
||||
vcvars_arch: 'amd64_x86'
|
||||
qt_ver: 5
|
||||
qt_host: windows
|
||||
qt_arch: 'win32_msvc2019'
|
||||
qt_version: '5.15.2'
|
||||
qt_modules: ''
|
||||
qt_tools: 'tools_openssl_x86'
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC"
|
||||
msystem: ''
|
||||
|
3
.github/workflows/trigger_release.yml
vendored
3
.github/workflows/trigger_release.yml
vendored
@ -96,9 +96,6 @@ jobs:
|
||||
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
|
||||
|
12
flake.lock
generated
12
flake.lock
generated
@ -106,11 +106,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1696661029,
|
||||
"narHash": "sha256-GIB5VTkvsDIqfMpdtuetOzpm64P8wm8nBSv5Eo8XM3Y=",
|
||||
"lastModified": 1697009197,
|
||||
"narHash": "sha256-viVRhBTFT8fPJTb1N3brQIpFZnttmwo3JVKNuWRVc3s=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2de1be5b51c3d6fa833f1c1f222dc867dd054b31",
|
||||
"rev": "01441e14af5e29c9d27ace398e6dd0b293e25a54",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -153,11 +153,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1696516544,
|
||||
"narHash": "sha256-8rKE8Je6twTNFRTGF63P9mE3lZIq917RAicdc4XJO80=",
|
||||
"lastModified": 1696846637,
|
||||
"narHash": "sha256-0hv4kbXxci2+pxhuXlVgftj/Jq79VSmtAyvfabCCtYk=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "66c352d33e0907239e4a69416334f64af2c685cc",
|
||||
"rev": "42e1b6095ef80a51f79595d9951eb38e91c4e6ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -970,6 +970,9 @@ SET(LAUNCHER_SOURCES
|
||||
ui/pages/modplatform/ImportPage.cpp
|
||||
ui/pages/modplatform/ImportPage.h
|
||||
|
||||
ui/pages/modplatform/OptionalModDialog.cpp
|
||||
ui/pages/modplatform/OptionalModDialog.h
|
||||
|
||||
ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
|
||||
ui/pages/modplatform/modrinth/ModrinthResourceModels.h
|
||||
ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
|
||||
@ -1141,6 +1144,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/pages/modplatform/legacy_ftb/Page.ui
|
||||
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
|
||||
ui/pages/modplatform/ImportPage.ui
|
||||
ui/pages/modplatform/OptionalModDialog.ui
|
||||
ui/pages/modplatform/modrinth/ModrinthPage.ui
|
||||
ui/pages/modplatform/technic/TechnicPage.ui
|
||||
ui/widgets/InstanceCardWidget.ui
|
||||
|
@ -272,6 +272,28 @@ bool ensureFolderPathExists(QString foldernamepath)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool copyFileAttributes(QString src, QString dst)
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
auto attrs = GetFileAttributesW(src.toStdWString().c_str());
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES)
|
||||
return false;
|
||||
return SetFileAttributesW(dst.toStdWString().c_str(), attrs);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// needs folders to exists
|
||||
void copyFolderAttributes(QString src, QString dst, QString relative)
|
||||
{
|
||||
auto path = PathCombine(src, relative);
|
||||
QDir dsrc(src);
|
||||
while ((path = QFileInfo(path).path()).length() >= src.length()) {
|
||||
auto dst_path = PathCombine(dst, dsrc.relativeFilePath(path));
|
||||
copyFileAttributes(path, dst_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copies a directory and it's contents from src to dest
|
||||
* @param offset subdirectory form src to copy to dest
|
||||
@ -310,6 +332,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||
auto dst_path = PathCombine(dst, relative_dst_path);
|
||||
if (!dryRun) {
|
||||
ensureFilePathExists(dst_path);
|
||||
#ifdef Q_OS_WIN32
|
||||
copyFolderAttributes(src, dst, relative_dst_path);
|
||||
#endif
|
||||
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
||||
}
|
||||
if (err) {
|
||||
|
@ -1018,8 +1018,7 @@ std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
|
||||
// TODO: remove this or add version condition once Quilt drops official Fabric support
|
||||
if (loaders & ModPlatform::Quilt)
|
||||
loaders |= ModPlatform::Fabric;
|
||||
// TODO: remove this or add version condition once NeoForge drops official Forge support
|
||||
if (loaders & ModPlatform::NeoForge)
|
||||
if (getComponentVersion("net.minecraft") == "1.20.1" && (loaders & ModPlatform::NeoForge))
|
||||
loaders |= ModPlatform::Forge;
|
||||
return loaders;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ class Mod;
|
||||
class Metadata {
|
||||
public:
|
||||
using ModStruct = Packwiz::V1::Mod;
|
||||
using ModSide = Packwiz::V1::Side;
|
||||
|
||||
static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
|
||||
{
|
||||
|
@ -122,7 +122,7 @@ void ModFolderLoadTask::getFromMetadata()
|
||||
auto metadata = Metadata::get(m_index_dir, entry);
|
||||
|
||||
if (!metadata.isValid()) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* mod = new Mod(m_mods_dir, metadata);
|
||||
|
@ -137,6 +137,7 @@ struct IndexedPack {
|
||||
QString logoName;
|
||||
QString logoUrl;
|
||||
QString websiteUrl;
|
||||
QString side;
|
||||
|
||||
bool versionsLoaded = false;
|
||||
QVector<IndexedVersion> versions;
|
||||
|
@ -43,5 +43,5 @@ void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack& m, QJsonObject& obj)
|
||||
m.system = Json::ensureBoolean(obj, QString("system"), false);
|
||||
m.description = Json::ensureString(obj, "description", "");
|
||||
|
||||
m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "");
|
||||
m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "").toLower() + ".png";
|
||||
}
|
||||
|
@ -62,6 +62,7 @@
|
||||
#include "minecraft/World.h"
|
||||
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "ui/pages/modplatform/OptionalModDialog.h"
|
||||
|
||||
static const FlameAPI api;
|
||||
|
||||
@ -509,13 +510,33 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
{
|
||||
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
|
||||
for (const auto& result : m_mod_id_resolver->getResults().files) {
|
||||
QString filename = result.fileName;
|
||||
auto results = m_mod_id_resolver->getResults().files;
|
||||
|
||||
QStringList optionalFiles;
|
||||
for (auto& result : results) {
|
||||
if (!result.required) {
|
||||
filename += ".disabled";
|
||||
optionalFiles << FS::PathCombine(result.targetFolder, result.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList selectedOptionalMods;
|
||||
if (!optionalFiles.empty()) {
|
||||
OptionalModDialog optionalModDialog(m_parent, optionalFiles);
|
||||
if (optionalModDialog.exec() == QDialog::Rejected) {
|
||||
emitAborted();
|
||||
loop.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
|
||||
selectedOptionalMods = optionalModDialog.getResult();
|
||||
}
|
||||
for (const auto& result : results) {
|
||||
auto relpath = FS::PathCombine(result.targetFolder, result.fileName);
|
||||
if (!result.required && !selectedOptionalMods.contains(relpath)) {
|
||||
relpath += ".disabled";
|
||||
}
|
||||
|
||||
relpath = FS::PathCombine("minecraft", relpath);
|
||||
auto path = FS::PathCombine(m_stagingPath, relpath);
|
||||
|
||||
switch (result.type) {
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "FlamePackIndex.h"
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
@ -9,8 +11,8 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
|
||||
pack.description = Json::ensureString(obj, "summary", "");
|
||||
|
||||
auto logo = Json::requireObject(obj, "logo");
|
||||
pack.logoName = Json::requireString(logo, "title");
|
||||
pack.logoUrl = Json::requireString(logo, "thumbnailUrl");
|
||||
pack.logoName = Json::requireString(obj, "slug") + "." + QFileInfo(QUrl(pack.logoUrl).fileName()).suffix();
|
||||
|
||||
auto authors = Json::requireArray(obj, "authors");
|
||||
for (auto authorIter : authors) {
|
||||
|
@ -48,7 +48,7 @@ struct File {
|
||||
|
||||
int projectId = 0;
|
||||
int fileId = 0;
|
||||
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
|
||||
// NOTE: the opposite to 'optional'
|
||||
bool required = true;
|
||||
QString hash;
|
||||
// NOTE: only set on blocked files ! Empty otherwise.
|
||||
|
@ -70,16 +70,18 @@ void PackInstallTask::downloadPack()
|
||||
setProgress(1, 4);
|
||||
setAbortable(false);
|
||||
|
||||
archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
|
||||
|
||||
auto path = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
|
||||
auto entry = APPLICATION->metacache()->resolveEntry("FTBPacks", path);
|
||||
entry->setStale(true);
|
||||
archivePath = entry->getFullPath();
|
||||
netJobContainer.reset(new NetJob("Download FTB Pack", m_network));
|
||||
QString url;
|
||||
if (m_pack.type == PackType::Private) {
|
||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(archivePath);
|
||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(path);
|
||||
} else {
|
||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath);
|
||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(path);
|
||||
}
|
||||
netJobContainer->addNetAction(Net::ApiDownload::makeFile(url, archivePath));
|
||||
netJobContainer->addNetAction(Net::ApiDownload::makeCached(url, entry));
|
||||
|
||||
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
|
||||
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "modplatform/helpers/OverrideUtils.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
@ -16,8 +17,10 @@
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/pages/modplatform/OptionalModDialog.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <vector>
|
||||
|
||||
bool ModrinthCreationTask::abort()
|
||||
{
|
||||
@ -319,10 +322,10 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
}
|
||||
|
||||
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
|
||||
bool had_optional = false;
|
||||
std::vector<Modrinth::File> optionalFiles;
|
||||
for (const auto& modInfo : jsonFiles) {
|
||||
Modrinth::File file;
|
||||
file.path = Json::requireString(modInfo, "path");
|
||||
file.path = Json::requireString(modInfo, "path").replace("\\", "/");
|
||||
|
||||
auto env = Json::ensureObject(modInfo, "env");
|
||||
// 'env' field is optional
|
||||
@ -331,18 +334,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
if (support == "unsupported") {
|
||||
continue;
|
||||
} else if (support == "optional") {
|
||||
// TODO: Make a review dialog for choosing which ones the user wants!
|
||||
if (!had_optional && show_optional_dialog) {
|
||||
had_optional = true;
|
||||
auto info = CustomMessageBox::selectable(
|
||||
m_parent, tr("Optional mod detected!"),
|
||||
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
|
||||
QMessageBox::Information);
|
||||
info->exec();
|
||||
}
|
||||
|
||||
if (file.path.endsWith(".jar"))
|
||||
file.path += ".disabled";
|
||||
file.required = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,9 +377,29 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
}
|
||||
}
|
||||
|
||||
files.push_back(file);
|
||||
(file.required ? files : optionalFiles).push_back(file);
|
||||
}
|
||||
|
||||
if (!optionalFiles.empty()) {
|
||||
QStringList oFiles;
|
||||
for (auto file : optionalFiles)
|
||||
oFiles.push_back(file.path);
|
||||
OptionalModDialog optionalModDialog(m_parent, oFiles);
|
||||
if (optionalModDialog.exec() == QDialog::Rejected) {
|
||||
emitAborted();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto selectedMods = optionalModDialog.getResult();
|
||||
for (auto file : optionalFiles) {
|
||||
if (selectedMods.contains(file.path)) {
|
||||
file.required = true;
|
||||
} else {
|
||||
file.path += ".disabled";
|
||||
}
|
||||
files.push_back(file);
|
||||
}
|
||||
}
|
||||
if (set_internal_data) {
|
||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "Json.h"
|
||||
#include "MMCZip.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
|
||||
const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" });
|
||||
@ -129,7 +130,8 @@ void ModrinthPackExportTask::collectHashes()
|
||||
QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1);
|
||||
sha1.addData(data);
|
||||
|
||||
ResolvedFile resolvedFile{ sha1.result().toHex(), sha512.result().toHex(), url.toEncoded(), openFile.size() };
|
||||
ResolvedFile resolvedFile{ sha1.result().toHex(), sha512.result().toHex(), url.toEncoded(), openFile.size(),
|
||||
mod->metadata()->side };
|
||||
resolvedFiles[relative] = resolvedFile;
|
||||
|
||||
// nice! we've managed to resolve based on local metadata!
|
||||
@ -272,22 +274,33 @@ QByteArray ModrinthPackExportTask::generateIndex()
|
||||
QString path = iterator.key();
|
||||
const ResolvedFile& value = iterator.value();
|
||||
|
||||
if (optionalFiles) {
|
||||
// detect disabled mod
|
||||
const QFileInfo pathInfo(path);
|
||||
if (pathInfo.suffix() == "disabled") {
|
||||
// rename it
|
||||
path = pathInfo.dir().filePath(pathInfo.completeBaseName());
|
||||
// ...and make it optional
|
||||
QJsonObject env;
|
||||
env["client"] = "optional";
|
||||
env["server"] = "optional";
|
||||
fileOut["env"] = env;
|
||||
}
|
||||
QJsonObject env;
|
||||
|
||||
// detect disabled mod
|
||||
const QFileInfo pathInfo(path);
|
||||
if (optionalFiles && pathInfo.suffix() == "disabled") {
|
||||
// rename it
|
||||
path = pathInfo.dir().filePath(pathInfo.completeBaseName());
|
||||
env["client"] = "optional";
|
||||
env["server"] = "optional";
|
||||
} else {
|
||||
env["client"] = "required";
|
||||
env["server"] = "required";
|
||||
}
|
||||
switch (iterator->side) {
|
||||
case Metadata::ModSide::ClientSide:
|
||||
env["server"] = "unsupported";
|
||||
break;
|
||||
case Metadata::ModSide::ServerSide:
|
||||
env["client"] = "unsupported";
|
||||
break;
|
||||
case Metadata::ModSide::UniversalSide:
|
||||
break;
|
||||
}
|
||||
fileOut["env"] = env;
|
||||
|
||||
fileOut["path"] = path;
|
||||
fileOut["downloads"] = QJsonArray{ iterator.value().url };
|
||||
fileOut["downloads"] = QJsonArray{ iterator->url };
|
||||
|
||||
QJsonObject hashes;
|
||||
hashes["sha1"] = value.sha1;
|
||||
|
@ -44,6 +44,7 @@ class ModrinthPackExportTask : public Task {
|
||||
struct ResolvedFile {
|
||||
QString sha1, sha512, url;
|
||||
qint64 size;
|
||||
Metadata::ModSide side;
|
||||
};
|
||||
|
||||
static const QStringList PREFIXES;
|
||||
|
@ -27,6 +27,11 @@
|
||||
static ModrinthAPI api;
|
||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||
|
||||
bool shouldDownloadOnSide(QString side)
|
||||
{
|
||||
return side == "required" || side == "optional";
|
||||
}
|
||||
|
||||
// https://docs.modrinth.com/api-spec/#tag/projects/operation/getProject
|
||||
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
{
|
||||
@ -53,6 +58,17 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
modAuthor.url = api.getAuthorURL(modAuthor.name);
|
||||
pack.authors.append(modAuthor);
|
||||
|
||||
auto client = shouldDownloadOnSide(Json::ensureString(obj, "client_side"));
|
||||
auto server = shouldDownloadOnSide(Json::ensureString(obj, "server_side"));
|
||||
|
||||
if (server && client) {
|
||||
pack.side = "both";
|
||||
} else if (server) {
|
||||
pack.side = "server";
|
||||
} else if (client) {
|
||||
pack.side = "client";
|
||||
}
|
||||
|
||||
// Modrinth can have more data than what's provided by the basic search :)
|
||||
pack.extraDataLoaded = false;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "ModrinthPackManifest.h"
|
||||
#include <QFileInfo>
|
||||
#include "Json.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||
@ -56,8 +57,8 @@ void loadIndexedPack(Modpack& pack, QJsonObject& obj)
|
||||
pack.description = Json::ensureString(obj, "description");
|
||||
auto temp_author_name = Json::ensureString(obj, "author");
|
||||
pack.author = std::make_tuple(temp_author_name, api.getAuthorURL(temp_author_name));
|
||||
pack.iconName = QString("modrinth_%1").arg(Json::ensureString(obj, "slug"));
|
||||
pack.iconUrl = Json::ensureString(obj, "icon_url");
|
||||
pack.iconName = QString("modrinth_%1.%2").arg(Json::ensureString(obj, "slug"), QFileInfo(pack.iconUrl.fileName()).suffix());
|
||||
}
|
||||
|
||||
void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
|
||||
@ -111,9 +112,8 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
|
||||
unsortedVersions.append(file);
|
||||
}
|
||||
auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool {
|
||||
bool a_better_release = a.version_type <= b.version_type;
|
||||
// dates are in RFC 3339 format
|
||||
return a.date > b.date && a_better_release;
|
||||
return a.date > b.date;
|
||||
};
|
||||
|
||||
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
|
||||
|
@ -57,6 +57,7 @@ struct File {
|
||||
QCryptographicHash::Algorithm hashAlgorithm;
|
||||
QByteArray hash;
|
||||
QQueue<QUrl> downloads;
|
||||
bool required = true;
|
||||
};
|
||||
|
||||
struct DonationData {
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QObject>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "StringUtils.h"
|
||||
@ -111,6 +113,7 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir, ModPlatform::IndexedP
|
||||
mod.provider = mod_pack.provider;
|
||||
mod.file_id = mod_version.fileId;
|
||||
mod.project_id = mod_pack.addonId;
|
||||
mod.side = stringToSide(mod_pack.side);
|
||||
|
||||
return mod;
|
||||
}
|
||||
@ -154,38 +157,52 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
|
||||
FS::ensureFilePathExists(index_file.fileName());
|
||||
}
|
||||
|
||||
toml::table update;
|
||||
switch (mod.provider) {
|
||||
case (ModPlatform::ResourceProvider::FLAME):
|
||||
if (mod.file_id.toInt() == 0 || mod.project_id.toInt() == 0) {
|
||||
qCritical() << QString("Did not write file %1 because missing information!").arg(normalized_fname);
|
||||
return;
|
||||
}
|
||||
update = toml::table{
|
||||
{ "file-id", mod.file_id.toInt() },
|
||||
{ "project-id", mod.project_id.toInt() },
|
||||
};
|
||||
break;
|
||||
case (ModPlatform::ResourceProvider::MODRINTH):
|
||||
if (mod.mod_id().toString().isEmpty() || mod.version().toString().isEmpty()) {
|
||||
qCritical() << QString("Did not write file %1 because missing information!").arg(normalized_fname);
|
||||
return;
|
||||
}
|
||||
update = toml::table{
|
||||
{ "mod-id", mod.mod_id().toString().toStdString() },
|
||||
{ "version", mod.version().toString().toStdString() },
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (!index_file.open(QIODevice::ReadWrite)) {
|
||||
qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name));
|
||||
qCritical() << QString("Could not open file %1!").arg(normalized_fname);
|
||||
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::ResourceProvider::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::ResourceProvider::MODRINTH):
|
||||
addToStream("mod-id", mod.mod_id().toString());
|
||||
addToStream("version", mod.version().toString());
|
||||
break;
|
||||
}
|
||||
auto tbl = toml::table{ { "name", mod.name.toStdString() },
|
||||
{ "filename", mod.filename.toStdString() },
|
||||
{ "side", sideToString(mod.side).toStdString() },
|
||||
{ "download",
|
||||
toml::table{
|
||||
{ "mode", mod.mode.toStdString() },
|
||||
{ "url", mod.url.toString().toStdString() },
|
||||
{ "hash-format", mod.hash_format.toStdString() },
|
||||
{ "hash", mod.hash.toStdString() },
|
||||
} },
|
||||
{ "update", toml::table{ { ProviderCaps.name(mod.provider), update } } } };
|
||||
std::stringstream ss;
|
||||
ss << tbl;
|
||||
in_stream << QString::fromStdString(ss.str());
|
||||
}
|
||||
|
||||
index_file.flush();
|
||||
@ -258,7 +275,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
|
||||
{ // Basic info
|
||||
mod.name = stringEntry(table, "name");
|
||||
mod.filename = stringEntry(table, "filename");
|
||||
mod.side = stringEntry(table, "side");
|
||||
mod.side = stringToSide(stringEntry(table, "side"));
|
||||
}
|
||||
|
||||
{ // [download] info
|
||||
@ -313,4 +330,28 @@ auto V1::getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod
|
||||
return {};
|
||||
}
|
||||
|
||||
auto V1::sideToString(Side side) -> QString
|
||||
{
|
||||
switch (side) {
|
||||
case Side::ClientSide:
|
||||
return "client";
|
||||
case Side::ServerSide:
|
||||
return "server";
|
||||
case Side::UniversalSide:
|
||||
return "both";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto V1::stringToSide(QString side) -> Side
|
||||
{
|
||||
if (side == "client")
|
||||
return Side::ClientSide;
|
||||
if (side == "server")
|
||||
return Side::ServerSide;
|
||||
if (side == "both")
|
||||
return Side::UniversalSide;
|
||||
return Side::UniversalSide;
|
||||
}
|
||||
|
||||
} // namespace Packwiz
|
||||
|
@ -35,12 +35,12 @@ auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool shoul
|
||||
|
||||
class V1 {
|
||||
public:
|
||||
enum class Side { ClientSide = 1 << 0, ServerSide = 1 << 1, UniversalSide = ClientSide | ServerSide };
|
||||
struct Mod {
|
||||
QString slug{};
|
||||
QString name{};
|
||||
QString filename{};
|
||||
// FIXME: make side an enum
|
||||
QString side{ "both" };
|
||||
Side side{ Side::UniversalSide };
|
||||
|
||||
// [download]
|
||||
QString mode{};
|
||||
@ -93,6 +93,9 @@ class V1 {
|
||||
* If the mod doesn't have a metadata, it simply returns an empty Mod object.
|
||||
* */
|
||||
static auto getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod;
|
||||
|
||||
static auto sideToString(Side side) -> QString;
|
||||
static auto stringToSide(QString side) -> Side;
|
||||
};
|
||||
|
||||
} // namespace Packwiz
|
||||
|
@ -206,7 +206,7 @@ void TranslationsModel::indexReceived()
|
||||
reloadLocalFiles();
|
||||
|
||||
auto language = d->m_system_locale;
|
||||
if (!findLanguage(language)) {
|
||||
if (!findLanguageAsOptional(language).has_value()) {
|
||||
language = d->m_system_language;
|
||||
}
|
||||
selectLanguage(language);
|
||||
@ -417,14 +417,17 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c
|
||||
return 2;
|
||||
}
|
||||
|
||||
Language* TranslationsModel::findLanguage(const QString& key)
|
||||
QVector<Language>::Iterator TranslationsModel::findLanguage(const QString& key)
|
||||
{
|
||||
auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language& lang) { return lang.key == key; });
|
||||
if (found == d->m_languages.end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return found;
|
||||
}
|
||||
return std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language& lang) { return lang.key == key; });
|
||||
}
|
||||
|
||||
std::optional<Language> TranslationsModel::findLanguageAsOptional(const QString& key)
|
||||
{
|
||||
auto found = findLanguage(key);
|
||||
if (found != d->m_languages.end())
|
||||
return *found;
|
||||
return {};
|
||||
}
|
||||
|
||||
void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
|
||||
@ -436,13 +439,13 @@ void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
|
||||
bool TranslationsModel::selectLanguage(QString key)
|
||||
{
|
||||
QString& langCode = key;
|
||||
auto langPtr = findLanguage(key);
|
||||
auto langPtr = findLanguageAsOptional(key);
|
||||
|
||||
if (langCode.isEmpty()) {
|
||||
d->no_language_set = true;
|
||||
}
|
||||
|
||||
if (!langPtr) {
|
||||
if (!langPtr.has_value()) {
|
||||
qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
|
||||
langCode = defaultLangCode;
|
||||
} else {
|
||||
@ -527,9 +530,8 @@ bool TranslationsModel::selectLanguage(QString key)
|
||||
QModelIndex TranslationsModel::selectedIndex()
|
||||
{
|
||||
auto found = findLanguage(d->m_selectedLanguage);
|
||||
if (found) {
|
||||
// QVector iterator freely converts to pointer to contained type
|
||||
return index(found - d->m_languages.begin(), 0, QModelIndex());
|
||||
if (found != d->m_languages.end()) {
|
||||
return index(std::distance(d->m_languages.begin(), found), 0, QModelIndex());
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
@ -562,8 +564,8 @@ void TranslationsModel::updateLanguage(QString key)
|
||||
qWarning() << "Cannot update builtin language" << key;
|
||||
return;
|
||||
}
|
||||
auto found = findLanguage(key);
|
||||
if (!found) {
|
||||
auto found = findLanguageAsOptional(key);
|
||||
if (!found.has_value()) {
|
||||
qWarning() << "Cannot update invalid language" << key;
|
||||
return;
|
||||
}
|
||||
@ -578,8 +580,8 @@ void TranslationsModel::downloadTranslation(QString key)
|
||||
d->m_nextDownload = key;
|
||||
return;
|
||||
}
|
||||
auto lang = findLanguage(key);
|
||||
if (!lang) {
|
||||
auto lang = findLanguageAsOptional(key);
|
||||
if (!lang.has_value()) {
|
||||
qWarning() << "Will not download an unknown translation" << key;
|
||||
return;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
struct Language;
|
||||
|
||||
@ -40,7 +41,8 @@ class TranslationsModel : public QAbstractListModel {
|
||||
void setUseSystemLocale(bool useSystemLocale);
|
||||
|
||||
private:
|
||||
Language* findLanguage(const QString& key);
|
||||
QVector<Language>::Iterator findLanguage(const QString& key);
|
||||
std::optional<Language> findLanguageAsOptional(const QString& key);
|
||||
void reloadLocalFiles();
|
||||
void downloadTranslation(QString key);
|
||||
void downloadNext();
|
||||
|
@ -237,8 +237,7 @@ void NewInstanceDialog::setSuggestedIcon(const QString& key)
|
||||
|
||||
InstanceTask* NewInstanceDialog::extractTask()
|
||||
{
|
||||
InstanceTask* extracted = creationTask.get();
|
||||
creationTask.release();
|
||||
InstanceTask* extracted = creationTask.release();
|
||||
|
||||
InstanceName inst_name(ui->instNameTextBox->placeholderText().trimmed(), importVersion);
|
||||
inst_name.setName(ui->instNameTextBox->text().trimmed());
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <QUrlQuery>
|
||||
#include "ui_ManagedPackPage.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QListView>
|
||||
#include <QProxyStyle>
|
||||
#include <QStyleFactory>
|
||||
@ -223,6 +224,7 @@ ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWin
|
||||
Q_ASSERT(inst->isManagedPack());
|
||||
connect(ui->versionsComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(suggestVersion()));
|
||||
connect(ui->updateButton, &QPushButton::clicked, this, &ModrinthManagedPackPage::update);
|
||||
connect(ui->updateFromFileButton, &QPushButton::clicked, this, &ModrinthManagedPackPage::updateFromFile);
|
||||
}
|
||||
|
||||
// MODRINTH
|
||||
@ -350,6 +352,27 @@ void ModrinthManagedPackPage::update()
|
||||
m_instance_window->close();
|
||||
}
|
||||
|
||||
void ModrinthManagedPackPage::updateFromFile()
|
||||
{
|
||||
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "Modrinth pack (*.mrpack *.zip)");
|
||||
QMap<QString, QString> extra_info;
|
||||
extra_info.insert("pack_id", m_inst->getManagedPackID());
|
||||
extra_info.insert("pack_version_id", QString());
|
||||
extra_info.insert("original_instance_id", m_inst->id());
|
||||
|
||||
auto extracted = new InstanceImportTask(output, this, std::move(extra_info));
|
||||
|
||||
extracted->setName(m_inst->name());
|
||||
extracted->setGroup(APPLICATION->instances()->getInstanceGroup(m_inst->id()));
|
||||
extracted->setIcon(m_inst->iconKey());
|
||||
extracted->setConfirmUpdate(false);
|
||||
|
||||
auto did_succeed = runUpdateTask(extracted);
|
||||
|
||||
if (m_instance_window && did_succeed)
|
||||
m_instance_window->close();
|
||||
}
|
||||
|
||||
// FLAME
|
||||
|
||||
FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent)
|
||||
@ -358,6 +381,7 @@ FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* i
|
||||
Q_ASSERT(inst->isManagedPack());
|
||||
connect(ui->versionsComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(suggestVersion()));
|
||||
connect(ui->updateButton, &QPushButton::clicked, this, &FlameManagedPackPage::update);
|
||||
connect(ui->updateFromFileButton, &QPushButton::clicked, this, &FlameManagedPackPage::updateFromFile);
|
||||
}
|
||||
|
||||
void FlameManagedPackPage::parseManagedPack()
|
||||
@ -492,4 +516,25 @@ void FlameManagedPackPage::update()
|
||||
m_instance_window->close();
|
||||
}
|
||||
|
||||
void FlameManagedPackPage::updateFromFile()
|
||||
{
|
||||
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "CurseForge pack (*.zip)");
|
||||
|
||||
QMap<QString, QString> extra_info;
|
||||
extra_info.insert("pack_id", m_inst->getManagedPackID());
|
||||
extra_info.insert("pack_version_id", QString());
|
||||
extra_info.insert("original_instance_id", m_inst->id());
|
||||
|
||||
auto extracted = new InstanceImportTask(output, this, std::move(extra_info));
|
||||
|
||||
extracted->setName(m_inst->name());
|
||||
extracted->setGroup(APPLICATION->instances()->getInstanceGroup(m_inst->id()));
|
||||
extracted->setIcon(m_inst->iconKey());
|
||||
extracted->setConfirmUpdate(false);
|
||||
|
||||
auto did_succeed = runUpdateTask(extracted);
|
||||
|
||||
if (m_instance_window && did_succeed)
|
||||
m_instance_window->close();
|
||||
}
|
||||
#include "ManagedPackPage.moc"
|
||||
|
@ -65,6 +65,7 @@ class ManagedPackPage : public QWidget, public BasePage {
|
||||
virtual void suggestVersion();
|
||||
|
||||
virtual void update(){};
|
||||
virtual void updateFromFile(){};
|
||||
|
||||
protected slots:
|
||||
/** Does the necessary UI changes for when something failed.
|
||||
@ -123,6 +124,7 @@ class ModrinthManagedPackPage final : public ManagedPackPage {
|
||||
void suggestVersion() override;
|
||||
|
||||
void update() override;
|
||||
void updateFromFile() override;
|
||||
|
||||
private:
|
||||
NetJob::Ptr m_fetch_job = nullptr;
|
||||
@ -145,6 +147,7 @@ class FlameManagedPackPage final : public ManagedPackPage {
|
||||
void suggestVersion() override;
|
||||
|
||||
void update() override;
|
||||
void updateFromFile() override;
|
||||
|
||||
private:
|
||||
NetJob::Ptr m_fetch_job = nullptr;
|
||||
|
@ -153,6 +153,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="updateFromFileButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update from file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
63
launcher/ui/pages/modplatform/OptionalModDialog.cpp
Normal file
63
launcher/ui/pages/modplatform/OptionalModDialog.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "OptionalModDialog.h"
|
||||
#include "ui_OptionalModDialog.h"
|
||||
|
||||
OptionalModDialog::OptionalModDialog(QWidget* parent, const QStringList& mods) : QDialog(parent), ui(new Ui::OptionalModDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
for (const QString& mod : mods) {
|
||||
auto item = new QListWidgetItem(mod, ui->list);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
item->setData(Qt::UserRole, mod);
|
||||
}
|
||||
|
||||
connect(ui->selectAllButton, &QPushButton::clicked, ui->list, [this] {
|
||||
for (int i = 0; i < ui->list->count(); i++)
|
||||
ui->list->item(i)->setCheckState(Qt::Checked);
|
||||
});
|
||||
connect(ui->clearAllButton, &QPushButton::clicked, ui->list, [this] {
|
||||
for (int i = 0; i < ui->list->count(); i++)
|
||||
ui->list->item(i)->setCheckState(Qt::Unchecked);
|
||||
});
|
||||
connect(ui->list, &QListWidget::itemActivated, [](QListWidgetItem* item) {
|
||||
if (item->checkState() == Qt::Checked)
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
else
|
||||
item->setCheckState(Qt::Checked);
|
||||
});
|
||||
}
|
||||
|
||||
OptionalModDialog::~OptionalModDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
QStringList OptionalModDialog::getResult()
|
||||
{
|
||||
QStringList result;
|
||||
result.reserve(ui->list->count());
|
||||
for (int i = 0; i < ui->list->count(); i++) {
|
||||
auto item = ui->list->item(i);
|
||||
if (item->checkState() == Qt::Checked)
|
||||
result.append(item->data(Qt::UserRole).toString());
|
||||
}
|
||||
return result;
|
||||
}
|
39
launcher/ui/pages/modplatform/OptionalModDialog.h
Normal file
39
launcher/ui/pages/modplatform/OptionalModDialog.h
Normal file
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class OptionalModDialog;
|
||||
}
|
||||
|
||||
class OptionalModDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OptionalModDialog(QWidget* parent, const QStringList& mods);
|
||||
~OptionalModDialog() override;
|
||||
|
||||
QStringList getResult();
|
||||
|
||||
private:
|
||||
Ui::OptionalModDialog* ui;
|
||||
};
|
113
launcher/ui/pages/modplatform/OptionalModDialog.ui
Normal file
113
launcher/ui/pages/modplatform/OptionalModDialog.ui
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OptionalModDialog</class>
|
||||
<widget class="QDialog" name="OptionalModDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>550</width>
|
||||
<height>310</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Optional Mods</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="list">
|
||||
<property name="defaultDropAction">
|
||||
<enum>Qt::IgnoreAction</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectAllButton">
|
||||
<property name="text">
|
||||
<string>Select All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clearAllButton">
|
||||
<property name="text">
|
||||
<string>Deselect All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Unchecked mods will be disabled.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>OptionalModDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>274</x>
|
||||
<y>284</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>274</x>
|
||||
<y>154</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>OptionalModDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>274</x>
|
||||
<y>284</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>274</x>
|
||||
<y>154</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -63,7 +63,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
|
||||
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1").arg(pack.safeName);
|
||||
((ListModel*)this)->requestLogo(pack.safeName, url);
|
||||
|
||||
return icon;
|
||||
|
@ -38,7 +38,7 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <QDialog>
|
||||
|
||||
#include "modplatform/atlauncher/ATLPackIndex.h"
|
||||
#include "modplatform/atlauncher/ATLPackManifest.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
namespace Ui {
|
||||
|
@ -114,8 +114,8 @@ void AtlPage::suggestCurrent()
|
||||
auto uiSupport = new AtlUserInteractionSupportImpl(this);
|
||||
dialog->setSuggestedPack(selected.name, selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion));
|
||||
|
||||
auto editedLogoName = selected.safeName;
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());
|
||||
auto editedLogoName = "atl_" + selected.safeName;
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1").arg(selected.safeName);
|
||||
listModel->getLogo(selected.safeName, url,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
}
|
||||
|
@ -228,8 +228,7 @@ void FlamePage::suggestCurrent()
|
||||
extra_info.insert("pack_version_id", QString::number(version.fileId));
|
||||
|
||||
dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)));
|
||||
QString editedLogoName;
|
||||
editedLogoName = "curseforge_" + current.logoName;
|
||||
QString editedLogoName = "curseforge_" + current.logoName;
|
||||
listModel->getLogo(current.logoName, current.logoUrl,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ void ImportFTBPage::suggestCurrent()
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(selected.name, new PackInstallTask(selected));
|
||||
QString editedLogoName = QString("ftb_%1").arg(selected.id);
|
||||
QString editedLogoName = QString("ftb_%1_%2,jpg").arg(selected.name, selected.id);
|
||||
dialog->setSuggestedIconFromFile(FS::PathCombine(selected.path, "folder.jpg"), editedLogoName);
|
||||
}
|
||||
|
||||
|
@ -179,15 +179,11 @@ void Page::suggestCurrent()
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(selected.name, selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
|
||||
QString editedLogoName;
|
||||
if (selected.logo.toLower().startsWith("ftb")) {
|
||||
editedLogoName = selected.logo;
|
||||
} else {
|
||||
editedLogoName = "ftb_" + selected.logo;
|
||||
QString editedLogoName = selected.logo;
|
||||
if (!selected.logo.toLower().startsWith("ftb")) {
|
||||
editedLogoName = "ftb_" + editedLogoName;
|
||||
}
|
||||
|
||||
editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png"));
|
||||
|
||||
if (selected.type == PackType::Public) {
|
||||
publicListModel->getLogo(selected.logo,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
|
@ -41,7 +41,9 @@
|
||||
#include "net/ApiDownload.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
#include <QUrl>
|
||||
|
||||
Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||
|
||||
@ -193,7 +195,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
pack.logoName = "null";
|
||||
} else {
|
||||
pack.logoUrl = rawURL;
|
||||
pack.logoName = rawURL.section(QLatin1Char('/'), -1);
|
||||
pack.logoName = pack.slug + "." + QFileInfo(QUrl(rawURL).fileName()).suffix();
|
||||
}
|
||||
pack.broken = false;
|
||||
newList.append(pack);
|
||||
@ -215,7 +217,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
auto iconUrl = Json::requireString(iconObj, "url");
|
||||
|
||||
pack.logoUrl = iconUrl;
|
||||
pack.logoName = iconUrl.section(QLatin1Char('/'), -1);
|
||||
pack.logoName = pack.slug + "." + QFileInfo(QUrl(iconUrl).fileName()).suffix();
|
||||
} else {
|
||||
pack.logoUrl = "null";
|
||||
pack.logoName = "null";
|
||||
|
@ -42,7 +42,7 @@ class PackwizTest : public QObject {
|
||||
|
||||
QCOMPARE(metadata.name, "Borderless Mining");
|
||||
QCOMPARE(metadata.filename, "borderless-mining-1.1.1+1.18.jar");
|
||||
QCOMPARE(metadata.side, "client");
|
||||
QCOMPARE(metadata.side, Packwiz::V1::Side::ClientSide);
|
||||
|
||||
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");
|
||||
@ -72,7 +72,7 @@ class PackwizTest : public QObject {
|
||||
|
||||
QCOMPARE(metadata.name, "Screenshot to Clipboard (Fabric)");
|
||||
QCOMPARE(metadata.filename, "screenshot-to-clipboard-1.0.7-fabric.jar");
|
||||
QCOMPARE(metadata.side, "both");
|
||||
QCOMPARE(metadata.side, Packwiz::V1::Side::UniversalSide);
|
||||
|
||||
QCOMPARE(metadata.url, QUrl("https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar"));
|
||||
QCOMPARE(metadata.hash_format, "murmur2");
|
||||
|
@ -55,6 +55,8 @@ class VersionTest : public QObject {
|
||||
<< "2.2.0" << true << false;
|
||||
QTest::newRow("lessThan, two-digit") << "1.41"
|
||||
<< "1.42" << true << false;
|
||||
QTest::newRow("lessThan, snapshot") << "1.20.0-rc2"
|
||||
<< "1.20.1" << true << false;
|
||||
|
||||
QTest::newRow("greaterThan, explicit 1") << "1.2.1"
|
||||
<< "1.2.0" << false << false;
|
||||
@ -72,6 +74,8 @@ class VersionTest : public QObject {
|
||||
<< "1.2" << false << false;
|
||||
QTest::newRow("greaterThan, two-digit") << "1.42"
|
||||
<< "1.41" << false << false;
|
||||
QTest::newRow("greaterThan, snapshot") << "1.20.2-rc2"
|
||||
<< "1.20.1" << false << false;
|
||||
}
|
||||
|
||||
private slots:
|
||||
|
Loading…
Reference in New Issue
Block a user