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

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2024-03-29 10:12:02 +02:00
commit 1930689a27
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
37 changed files with 349 additions and 134 deletions

View File

@ -166,7 +166,7 @@ jobs:
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v4.0.0
uses: actions/cache@v4.0.2
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@ -496,7 +496,6 @@ jobs:
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt
cd ${{ env.INSTALL_DIR }}
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
@ -505,9 +504,12 @@ jobs:
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
# workaround to make portable installs to work on fedora
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
cd ${{ env.INSTALL_PORTABLE_DIR }}
tar -czf ../PrismLauncher-portable.tar.gz *

View File

@ -84,7 +84,7 @@ jobs:
- name: Create release
id: create_release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref }}

View File

@ -17,9 +17,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@6004951b182f8860210c8d6f0d808ec5b1a33d28 # v25
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
- uses: DeterminateSystems/update-flake-lock@v20
- uses: DeterminateSystems/update-flake-lock@v21
with:
commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile"

View File

@ -178,7 +178,7 @@ set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL th
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 8)
set(Launcher_VERSION_MAJOR 9)
set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")

30
flake.lock generated
View File

@ -23,11 +23,11 @@
]
},
"locked": {
"lastModified": 1706830856,
"narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=",
"lastModified": 1709336216,
"narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f",
"rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2",
"type": "github"
},
"original": {
@ -41,11 +41,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@ -62,11 +62,11 @@
]
},
"locked": {
"lastModified": 1703887061,
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
@ -93,11 +93,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1708751719,
"narHash": "sha256-0uWOKSpXJXmXswOvDM5Vk3blB74apFB6rNGWV5IjoN0=",
"lastModified": 1711200738,
"narHash": "sha256-dkJmk/ET/tRV4007O6kU101UEg1svUwiyk/zEEX9Tdg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "f63ce824cd2f036216eb5f637dfef31e1a03ee89",
"rev": "20bc93ca7b2158ebc99b8cef987a2173a81cde35",
"type": "github"
},
"original": {
@ -122,11 +122,11 @@
]
},
"locked": {
"lastModified": 1708018599,
"narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=",
"lastModified": 1710923068,
"narHash": "sha256-6hOpUiuxuwpXXc/xfJsBUJeqqgGI+JMJuLo45aG3cKc=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431",
"rev": "e611897ddfdde3ed3eaac4758635d7177ff78673",
"type": "github"
},
"original": {

View File

@ -308,7 +308,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
adjustedBy = "Persistent data path";
#ifndef Q_OS_MACOS
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
if (auto portableUserData = FS::PathCombine(m_rootPath, "UserData"); QDir(portableUserData).exists()) {
dataPath = portableUserData;
adjustedBy = "Portable user data path";
m_portable = true;
} else if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
dataPath = m_rootPath;
adjustedBy = "Portable data path";
m_portable = true;

View File

@ -38,6 +38,7 @@
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QJsonArray>
#include <QJsonDocument>
@ -847,14 +848,16 @@ class InstanceStaging : public Task {
const unsigned maxBackoff = 16;
public:
InstanceStaging(InstanceList* parent, InstanceTask* child, QString stagingPath, InstanceName const& instanceName, QString groupName)
: m_parent(parent)
, backoff(minBackoff, maxBackoff)
, m_stagingPath(std::move(stagingPath))
, m_instance_name(std::move(instanceName))
, m_groupName(std::move(groupName))
InstanceStaging(InstanceList* parent, InstanceTask* child, SettingsObjectPtr settings)
: m_parent(parent), backoff(minBackoff, maxBackoff)
{
m_stagingPath = parent->getStagedInstancePath();
m_child.reset(child);
m_child->setStagingPath(m_stagingPath);
m_child->setParentSettings(std::move(settings));
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceeded);
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
@ -866,7 +869,7 @@ class InstanceStaging : public Task {
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
}
virtual ~InstanceStaging(){};
virtual ~InstanceStaging() {}
// FIXME/TODO: add ability to abort during instance commit retries
bool abort() override
@ -881,14 +884,22 @@ class InstanceStaging : public Task {
bool canAbort() const override { return (m_child && m_child->canAbort()); }
protected:
virtual void executeTask() override { m_child->start(); }
virtual void executeTask() override
{
if (m_stagingPath.isNull()) {
emitFailed(tr("Could not create staging folder"));
return;
}
m_child->start();
}
QStringList warnings() const override { return m_child->warnings(); }
private slots:
void childSucceeded()
{
unsigned sleepTime = backoff();
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, *m_child.get())) {
if (m_parent->commitStagedInstance(m_stagingPath, *m_child.get(), m_child->group(), *m_child.get())) {
emitSucceeded();
return;
}
@ -897,7 +908,7 @@ class InstanceStaging : public Task {
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
return;
}
qDebug() << "Failed to commit instance" << m_instance_name.name() << "Initiating backoff:" << sleepTime;
qDebug() << "Failed to commit instance" << m_child->name() << "Initiating backoff:" << sleepTime;
m_backoffTimer.start(sleepTime * 500);
}
void childFailed(const QString& reason)
@ -906,7 +917,11 @@ class InstanceStaging : public Task {
emitFailed(reason);
}
void childAborted() { emitAborted(); }
void childAborted()
{
m_parent->destroyStagingPath(m_stagingPath);
emitAborted();
}
private:
InstanceList* m_parent;
@ -918,34 +933,35 @@ class InstanceStaging : public Task {
ExponentialSeries backoff;
QString m_stagingPath;
unique_qobject_ptr<InstanceTask> m_child;
InstanceName m_instance_name;
QString m_groupName;
QTimer m_backoffTimer;
};
Task* InstanceList::wrapInstanceTask(InstanceTask* task)
{
auto stagingPath = getStagedInstancePath();
task->setStagingPath(stagingPath);
task->setParentSettings(m_globalSettings);
return new InstanceStaging(this, task, stagingPath, *task, task->group());
return new InstanceStaging(this, task, m_globalSettings);
}
QString InstanceList::getStagedInstancePath()
{
QString key = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString tempDir = ".LAUNCHER_TEMP/";
QString relPath = FS::PathCombine(tempDir, key);
QDir rootPath(m_instDir);
auto path = FS::PathCombine(m_instDir, relPath);
if (!rootPath.mkpath(relPath)) {
return QString();
}
const QString tempRoot = FS::PathCombine(m_instDir, ".tmp");
QString result;
int tries = 0;
do {
if (++tries > 256)
return {};
const QString key = QUuid::createUuid().toString(QUuid::Id128).left(6);
result = FS::PathCombine(tempRoot, key);
} while (QFileInfo::exists(result));
if (!QDir::current().mkpath(result))
return {};
#ifdef Q_OS_WIN32
auto tempPath = FS::PathCombine(m_instDir, tempDir);
SetFileAttributesA(tempPath.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
SetFileAttributesA(tempRoot.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
#endif
return path;
return result;
}
bool InstanceList::commitStagedInstance(const QString& path,

View File

@ -463,7 +463,7 @@ auto ExportToZipTask::exportZip() -> ZipResult
auto absolute = file.absoluteFilePath();
auto relative = m_dir.relativeFilePath(absolute);
setStatus("Compresing: " + relative);
setStatus("Compressing: " + relative);
setProgress(m_progress + 1, m_progressTotal);
if (m_follow_symlinks) {
if (file.isSymLink())

View File

@ -665,8 +665,12 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine
}
if (serverToJoin && !serverToJoin->address.isEmpty()) {
args_pattern += " --server " + serverToJoin->address;
args_pattern += " --port " + QString::number(serverToJoin->port);
if (profile->hasTrait("feature:is_quick_play_multiplayer")) {
args_pattern += " --quickPlayMultiplayer " + serverToJoin->address + ':' + QString::number(serverToJoin->port);
} else {
args_pattern += " --server " + serverToJoin->address;
args_pattern += " --port " + QString::number(serverToJoin->port);
}
}
QMap<QString, QString> token_mapping;

View File

@ -126,7 +126,35 @@ bool XboxAuthorizationStep::processSTSError(QNetworkReply::NetworkError error, Q
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("This Microsoft account is underaged and is not linked to a family.\n\nPlease set up your account according to %1.")
.arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4403181904525\">help.minecraft.net</a>"));
.arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4408968616077\">help.minecraft.net</a>"));
return true;
}
// the following codes where copied from: https://github.com/PrismarineJS/prismarine-auth/pull/44
case 2148916236: {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("This Microsoft account requires proof of age to play. Please login to %1 to provide proof of age.")
.arg("<a href=\"https://login.live.com/login.srf\">login.live.com</a>"));
return true;
}
case 2148916237:
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("This Microsoft account has reached its limit for playtime. This "
"Microsoft account has been blocked from logging in."));
return true;
case 2148916227: {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("This Microsoft account was banned by Xbox for violating one or more "
"Community Standards for Xbox and is unable to be used."));
return true;
}
case 2148916229: {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("This Microsoft account is currently restricted and your guardian has not given you permission to play "
"online. Login to %1 and have your guardian change your permissions.")
.arg("<a href=\"https://account.microsoft.com/family/\">account.microsoft.com</a>"));
return true;
}
case 2148916234: {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("This Microsoft account has not accepted Xbox's Terms of Service. Please login and accept them."));
return true;
}
default: {

View File

@ -57,9 +57,11 @@ GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
, m_version(mcVersion(instance))
, m_loaderType(mcLoaders(instance))
{
for (auto mod : folder->allMods())
for (auto mod : folder->allMods()) {
m_mods_file_names << mod->fileinfo().fileName();
if (auto meta = mod->metadata(); meta)
m_mods.append(meta);
}
prepare();
}
@ -231,8 +233,13 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
if (dep_.addonId != pDep->version.addonId) {
removePack(pDep->version.addonId);
addTask(prepareDependencyTask(dep_, provider.name, level));
} else
} else {
addTask(getProjectInfoTask(pDep));
}
}
if (isLocalyInstalled(pDep)) {
removePack(pDep->version.addonId);
return;
}
for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) {
addTask(prepareDependencyTask(dep_, provider.name, level - 1));
@ -258,9 +265,9 @@ void GetModDependenciesTask::removePack(const QVariant& addonId)
#endif
}
QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
auto GetModDependenciesTask::getExtraInfo() -> QHash<QString, PackDependencyExtraInfo>
{
QHash<QString, QStringList> rby;
QHash<QString, PackDependencyExtraInfo> rby;
auto fullList = m_selected + m_pack_dependencies;
for (auto& mod : fullList) {
auto addonId = mod->pack->addonId;
@ -282,7 +289,61 @@ QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
req.append(smod->pack->name);
}
}
rby[addonId.toString()] = req;
rby[addonId.toString()] = { maybeInstalled(mod), req };
}
return rby;
}
// super lax compare (but not fuzzy)
// convert to lowercase
// convert all speratores to whitespace
// simplify sequence of internal whitespace to a single space
// efectivly compare two strings ignoring all separators and case
auto laxCompare = [](QString fsfilename, QString metadataFilename, bool excludeDigits = false) {
// allowed character seperators
QList<QChar> allowedSeperators = { '-', '+', '.', '_' };
if (excludeDigits)
allowedSeperators.append({ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
// copy in lowercase
auto fsName = fsfilename.toLower();
auto metaName = metadataFilename.toLower();
// replace all potential allowed seperatores with whitespace
for (auto sep : allowedSeperators) {
fsName = fsName.replace(sep, ' ');
metaName = metaName.replace(sep, ' ');
}
// remove extraneous whitespace
fsName = fsName.simplified();
metaName = metaName.simplified();
return fsName.compare(metaName) == 0;
};
bool GetModDependenciesTask::isLocalyInstalled(std::shared_ptr<PackDependency> pDep)
{
return pDep->version.fileName.isEmpty() ||
std::find_if(m_selected.begin(), m_selected.end(),
[pDep](std::shared_ptr<PackDependency> i) {
return !i->version.fileName.isEmpty() && laxCompare(i->version.fileName, pDep->version.fileName);
}) != m_selected.end() || // check the selected versions
std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(),
[pDep](QString i) { return !i.isEmpty() && laxCompare(i, pDep->version.fileName); }) !=
m_mods_file_names.end() || // check the existing mods
std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(), [pDep](std::shared_ptr<PackDependency> i) {
return pDep->pack->addonId != i->pack->addonId && !i->version.fileName.isEmpty() &&
laxCompare(pDep->version.fileName, i->version.fileName);
}) != m_pack_dependencies.end(); // check loaded dependencies
}
bool GetModDependenciesTask::maybeInstalled(std::shared_ptr<PackDependency> pDep)
{
return std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(), [pDep](QString i) {
return !i.isEmpty() && laxCompare(i, pDep->version.fileName, true);
}) != m_mods_file_names.end(); // check the existing mods
}

View File

@ -50,6 +50,11 @@ class GetModDependenciesTask : public SequentialTask {
}
};
struct PackDependencyExtraInfo {
bool maybe_installed;
QStringList required_by;
};
struct Provider {
ModPlatform::ResourceProvider name;
std::shared_ptr<ResourceDownload::ModModel> mod;
@ -62,7 +67,7 @@ class GetModDependenciesTask : public SequentialTask {
QList<std::shared_ptr<PackDependency>> selected);
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
QHash<QString, QStringList> getRequiredBy();
QHash<QString, PackDependencyExtraInfo> getExtraInfo();
protected slots:
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, ModPlatform::ResourceProvider, int);
@ -73,10 +78,14 @@ class GetModDependenciesTask : public SequentialTask {
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName);
void removePack(const QVariant& addonId);
bool isLocalyInstalled(std::shared_ptr<PackDependency> pDep);
bool maybeInstalled(std::shared_ptr<PackDependency> pDep);
private:
QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
QList<std::shared_ptr<Metadata::ModStruct>> m_mods;
QList<std::shared_ptr<PackDependency>> m_selected;
QStringList m_mods_file_names;
Provider m_flame_provider;
Provider m_modrinth_provider;

View File

@ -28,6 +28,7 @@ class CheckUpdateTask : public Task {
QString changelog;
ModPlatform::ResourceProvider provider;
shared_qobject_ptr<ResourceDownloadTask> download;
bool enabled = true;
public:
UpdatableMod(QString name,
@ -37,7 +38,8 @@ class CheckUpdateTask : public Task {
std::optional<ModPlatform::IndexedVersionType> new_v_type,
QString changelog,
ModPlatform::ResourceProvider p,
shared_qobject_ptr<ResourceDownloadTask> t)
shared_qobject_ptr<ResourceDownloadTask> t,
bool enabled = true)
: name(name)
, old_hash(old_h)
, old_version(old_v)
@ -46,6 +48,7 @@ class CheckUpdateTask : public Task {
, changelog(changelog)
, provider(p)
, download(t)
, enabled(enabled)
{}
};

View File

@ -393,16 +393,16 @@ QByteArray FlamePackExportTask::generateIndex()
version["version"] = minecraft->m_version;
QString id;
if (quilt != nullptr)
id = "quilt-" + quilt->getVersion();
id = "quilt-" + quilt->m_version;
else if (fabric != nullptr)
id = "fabric-" + fabric->getVersion();
id = "fabric-" + fabric->m_version;
else if (forge != nullptr)
id = "forge-" + forge->getVersion();
id = "forge-" + forge->m_version;
else if (neoforge != nullptr) {
id = "neoforge-";
if (minecraft->m_version == "1.20.1")
id += "1.20.1-";
id += neoforge->getVersion();
id += neoforge->m_version;
}
version["modLoaders"] = QJsonArray();
if (!id.isEmpty()) {

View File

@ -43,6 +43,7 @@ Modpack parseDirectory(QString path)
modpack.version = Json::requireString(root, "version", "version");
modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion");
modpack.jvmArgs = Json::ensureVariant(root, "jvmArgs", {}, "jvmArgs");
modpack.totalPlayTime = Json::requireInteger(root, "totalPlayTime", "totalPlayTime");
} catch (const Exception& e) {
qDebug() << "Couldn't load ftb instance json: " << e.cause();
return {};

View File

@ -36,6 +36,7 @@ struct Modpack {
QString name;
QString version;
QString mcVersion;
int totalPlayTime;
// not needed for instance creation
QVariant jvmArgs;

View File

@ -55,6 +55,7 @@ void PackInstallTask::copySettings()
instanceSettings->suspendSave();
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
instance.settings()->set("InstanceType", "OneSix");
instance.settings()->set("totalTimePlayed", m_pack.totalPlayTime / 1000);
if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) {
instance.settings()->set("OverrideJavaArgs", true);

View File

@ -287,16 +287,12 @@ QByteArray ModrinthPackExportTask::generateIndex()
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;
}
// a server side mod does not imply that the mod does not work on the client
// however, if a mrpack mod is marked as server-only it will not install on the client
if (iterator->side == Metadata::ModSide::ClientSide)
env["server"] = "unsupported";
fileOut["env"] = env;
fileOut["path"] = path;

View File

@ -47,11 +47,18 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
setWindowTitle(tr("Export Modrinth Pack"));
ui->summary->setText(instance->settings()->get("ExportSummary").toString());
ui->authorLabel->hide();
ui->author->hide();
ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString());
} else {
setWindowTitle(tr("Export CurseForge Pack"));
ui->summaryLabel->setText(tr("&Author"));
ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
ui->summaryLabel->hide();
ui->summary->hide();
ui->author->setText(instance->settings()->get("ExportAuthor").toString());
}
// ensure a valid pack is generated
@ -108,9 +115,13 @@ void ExportPackDialog::done(int result)
auto settings = instance->settings();
settings->set("ExportName", ui->name->text());
settings->set("ExportVersion", ui->version->text());
settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
settings->set("ExportSummary", ui->summary->toPlainText());
else
settings->set("ExportAuthor", ui->author->text());
if (result == Accepted) {
const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
const QString filename = FS::RemoveInvalidFilenameChars(name);
@ -134,10 +145,10 @@ void ExportPackDialog::done(int result)
Task* task;
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->toPlainText(), ui->optionalFiles->isChecked(),
instance, output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
} else {
task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output,
task = new FlamePackExportTask(name, ui->version->text(), ui->author->text(), ui->optionalFiles->isChecked(), instance, output,
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
}

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>650</width>
<height>510</height>
<height>532</height>
</rect>
</property>
<property name="sizeGripEnabled">
@ -19,21 +19,8 @@
<property name="title">
<string>&amp;Description</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="summaryLabel">
<property name="text">
<string>&amp;Summary</string>
</property>
<property name="buddy">
<cstring>summary</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="summary"/>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>&amp;Name</string>
@ -43,7 +30,10 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item>
<widget class="QLineEdit" name="name"/>
</item>
<item>
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>&amp;Version</string>
@ -53,16 +43,43 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="name"/>
</item>
<item row="1" column="1">
<item>
<widget class="QLineEdit" name="version">
<property name="text">
<string>1.0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="summaryLabel">
<property name="text">
<string>&amp;Summary</string>
</property>
<property name="buddy">
<cstring>summary</cstring>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="summary">
<property name="tabChangesFocus">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="authorLabel">
<property name="text">
<string>&amp;Author</string>
</property>
<property name="buddy">
<cstring>author</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="author"/>
</item>
</layout>
</widget>
</item>
@ -124,6 +141,7 @@
<tabstop>name</tabstop>
<tabstop>version</tabstop>
<tabstop>summary</tabstop>
<tabstop>author</tabstop>
<tabstop>files</tabstop>
<tabstop>optionalFiles</tabstop>
</tabstops>

View File

@ -214,19 +214,25 @@ void ModUpdateDialog::checkCandidates()
}
static FlameAPI api;
auto getRequiredBy = depTask->getRequiredBy();
auto dependencyExtraInfo = depTask->getExtraInfo();
for (auto dep : depTask->getDependecies()) {
auto changelog = dep->version.changelog;
if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model);
CheckUpdateTask::UpdatableMod updatable = {
dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type,
changelog, dep->pack->provider, download_task
};
auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString());
CheckUpdateTask::UpdatableMod updatable = { dep->pack->name,
dep->version.hash,
"",
dep->version.version,
dep->version.version_type,
changelog,
dep->pack->provider,
download_task,
!extraInfo.maybe_installed };
appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString()));
appendMod(updatable, extraInfo.required_by);
m_tasks.insert(updatable.name, updatable.download);
}
}
@ -412,7 +418,10 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R
void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy)
{
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
item_top->setCheckState(0, Qt::CheckState::Checked);
item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
if (!info.enabled) {
item_top->setToolTip(0, tr("Mod was disabled as it may be already instaled."));
}
item_top->setText(0, info.name);
item_top->setExpanded(true);

View File

@ -97,6 +97,9 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
ui->verticalLayout->insertWidget(2, m_container);
m_container->addButtons(m_buttons);
connect(m_container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* selected) {
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(creationTask && !instName().isEmpty());
});
// Bonk Qt over its stupid head and make sure it understands which button is the default one...
// See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button

View File

@ -132,7 +132,7 @@ void ResourceDownloadDialog::confirm()
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
confirm_dialog->retranslateUi(resourcesString());
QHash<QString, QStringList> getRequiredBy;
QHash<QString, GetModDependenciesTask::PackDependencyExtraInfo> dependencyExtraInfo;
QStringList depNames;
if (auto task = getModDependenciesTask(); task) {
connect(task.get(), &Task::failed, this,
@ -160,7 +160,7 @@ void ResourceDownloadDialog::confirm()
addResource(dep->pack, dep->version);
depNames << dep->pack->name;
}
getRequiredBy = task->getRequiredBy();
dependencyExtraInfo = task->getExtraInfo();
}
}
@ -169,9 +169,10 @@ void ResourceDownloadDialog::confirm()
return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0;
});
for (auto& task : selected) {
auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString());
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
ProviderCaps.name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()),
task->getVersion().version_type.toString() });
ProviderCaps.name(task->getProvider()), extraInfo.required_by,
task->getVersion().version_type.toString(), !extraInfo.maybe_installed });
}
if (confirm_dialog->exec()) {

View File

@ -35,8 +35,11 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon)
void ReviewMessageBox::appendResource(ResourceInformation&& info)
{
auto itemTop = new QTreeWidgetItem(ui->modTreeWidget);
itemTop->setCheckState(0, Qt::CheckState::Checked);
itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
itemTop->setText(0, info.name);
if (!info.enabled) {
itemTop->setToolTip(0, tr("Mod was disabled as it may be already instaled."));
}
auto filenameItem = new QTreeWidgetItem(itemTop);
filenameItem->setText(0, tr("Filename: %1").arg(info.filename));

View File

@ -20,6 +20,7 @@ class ReviewMessageBox : public QDialog {
QString provider;
QStringList required_by;
QString version_type;
bool enabled = true;
};
void appendResource(ResourceInformation&& info);

View File

@ -465,7 +465,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
widWidth = m_catPixmap.width();
if (m_catPixmap.height() < widHeight)
widHeight = m_catPixmap.height();
auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio);
auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QRect rectOfPixmap = pixmap.rect();
rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight());
painter.drawPixmap(rectOfPixmap.topLeft(), pixmap);

View File

@ -200,7 +200,9 @@ void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info)
void ImportPage::on_modpackBtn_clicked()
{
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
const QMimeType zip = QMimeDatabase().mimeTypeForName("application/zip");
auto filter = tr("Supported files") + QString(" (%1 *.mrpack)").arg(zip.globPatterns().join(" "));
filter += ";;" + zip.filterString();
//: Option for filtering for *.mrpack files when importing
filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);

View File

@ -90,6 +90,7 @@ void ModPage::filterMods()
void ModPage::triggerSearch()
{
m_filter = m_filter_widget->getFilter();
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
m_ui->packView->clearSelection();
m_ui->packDescription->clear();
m_ui->versionSelectionBox->clear();

View File

@ -23,6 +23,7 @@ ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialo
void ResourcePackResourcePage::triggerSearch()
{
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
m_ui->packView->clearSelection();
m_ui->packDescription->clear();
m_ui->versionSelectionBox->clear();

View File

@ -24,6 +24,7 @@ ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog,
void ShaderPackResourcePage::triggerSearch()
{
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
m_ui->packView->clearSelection();
m_ui->packDescription->clear();
m_ui->versionSelectionBox->clear();

View File

@ -104,6 +104,7 @@ void ModrinthPage::retranslate()
void ModrinthPage::openedImpl()
{
BasePage::openedImpl();
suggestCurrent();
triggerSearch();
}

View File

@ -36,7 +36,10 @@
#include "ui/themes/CatPack.h"
#include <QDate>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QImageReader>
#include <QRandomGenerator>
#include "FileSystem.h"
#include "Json.h"
@ -79,7 +82,7 @@ JsonCatPack::JsonCatPack(QFileInfo& manifestInfo) : BasicCatPack(manifestInfo.di
auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "CatPack JSON file");
const auto root = doc.object();
m_name = Json::requireString(root, "name", "Catpack name");
m_defaultPath = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat"));
m_default_path = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat"));
auto variants = Json::ensureArray(root, "variants", QJsonArray(), "Catpack Variants");
for (auto v : variants) {
auto variant = Json::ensureObject(v, QJsonObject(), "Cat variant");
@ -117,5 +120,21 @@ QString JsonCatPack::path(QDate now)
if (startDate <= now && now <= endDate)
return var.path;
}
return m_defaultPath;
auto dInfo = QFileInfo(m_default_path);
if (!dInfo.isDir())
return m_default_path;
QStringList supportedImageFormats;
for (auto format : QImageReader::supportedImageFormats()) {
supportedImageFormats.append("*." + format);
}
auto files = QDir(m_default_path).entryInfoList(supportedImageFormats, QDir::Files, QDir::Name);
if (files.length() == 0)
return "";
auto idx = (now.dayOfYear() - 1) % files.length();
auto isRandom = dInfo.fileName().compare("random", Qt::CaseInsensitive) == 0;
if (isRandom)
idx = QRandomGenerator::global()->bounded(0, files.length());
return files[idx].absoluteFilePath();
}

View File

@ -87,6 +87,6 @@ class JsonCatPack : public BasicCatPack {
QString path(QDate now);
private:
QString m_defaultPath;
QString m_default_path;
QList<Variant> m_variants;
};

View File

@ -178,8 +178,8 @@ QList<ITheme*> ThemeManager::getValidApplicationThemes()
QList<CatPack*> ThemeManager::getValidCatPacks()
{
QList<CatPack*> ret;
ret.reserve(m_catPacks.size());
for (auto&& [id, theme] : m_catPacks) {
ret.reserve(m_cat_packs.size());
for (auto&& [id, theme] : m_cat_packs) {
ret.append(theme.get());
}
return ret;
@ -244,8 +244,8 @@ void ThemeManager::applyCurrentlySelectedTheme(bool initial)
QString ThemeManager::getCatPack(QString catName)
{
auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
if (catIter != m_catPacks.end()) {
auto catIter = m_cat_packs.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
if (catIter != m_cat_packs.end()) {
auto& catPack = catIter->second;
themeDebugLog() << "applying catpack" << catPack->id();
return catPack->path();
@ -253,14 +253,14 @@ QString ThemeManager::getCatPack(QString catName)
themeWarningLog() << "Tried to get invalid catPack:" << catName;
}
return m_catPacks.begin()->second->path();
return m_cat_packs.begin()->second->path();
}
QString ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack)
{
QString id = catPack->id();
if (m_catPacks.find(id) == m_catPacks.end())
m_catPacks.emplace(id, std::move(catPack));
if (m_cat_packs.find(id) == m_cat_packs.end())
m_cat_packs.emplace(id, std::move(catPack));
else
themeWarningLog() << "CatPack(" << id << ") not added to prevent id duplication";
return id;

View File

@ -61,7 +61,7 @@ class ThemeManager {
QDir m_iconThemeFolder{ "iconthemes" };
QDir m_applicationThemeFolder{ "themes" };
QDir m_catPacksFolder{ "catpacks" };
std::map<QString, std::unique_ptr<CatPack>> m_catPacks;
std::map<QString, std::unique_ptr<CatPack>> m_cat_packs;
void initializeThemes();
void initializeCatPacks();

View File

@ -58,10 +58,17 @@ import org.prismlauncher.utils.Parameters;
import org.prismlauncher.utils.ReflectionUtils;
import java.lang.invoke.MethodHandle;
import java.util.Collections;
import java.util.List;
public final class StandardLauncher extends AbstractLauncher {
private final boolean quickPlaySupported;
public StandardLauncher(Parameters params) {
super(params);
List<String> traits = params.getList("traits", Collections.<String>emptyList());
quickPlaySupported = traits.contains("feature:is_quick_play_multiplayer");
}
@Override
@ -76,10 +83,16 @@ public final class StandardLauncher extends AbstractLauncher {
}
if (serverAddress != null) {
gameArgs.add("--server");
gameArgs.add(serverAddress);
gameArgs.add("--port");
gameArgs.add(serverPort);
if (quickPlaySupported) {
// as of 23w14a
gameArgs.add("--quickPlayMultiplayer");
gameArgs.add(serverAddress + ':' + serverPort);
} else {
gameArgs.add("--server");
gameArgs.add(serverAddress);
gameArgs.add("--port");
gameArgs.add(serverPort);
}
}
// find and invoke the main method

View File

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
<ws2:longPathAware>true</ws2:longPathAware>
</windowsSettings>
</application>
<assemblyIdentity name="PrismLauncher.Application.1" type="win32" version="@Launcher_VERSION_NAME4@" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>