Merge branch 'PolyMC:develop' into macos-app-heuristic

This commit is contained in:
Ryan Cao 2022-06-01 00:12:14 +08:00 committed by GitHub
commit e06bf17d13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 1314 additions and 472 deletions

16
.clang-format Normal file
View File

@ -0,0 +1,16 @@
---
Language: Cpp
BasedOnStyle: Chromium
IndentWidth: 4
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AllowShortIfStatementsOnASingleLine: false
BraceWrapping:
AfterFunction: true
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBraces: Custom
BreakConstructorInitializers: BeforeComma
ColumnLimit: 140
Cpp11BracedListStyle: false

View File

@ -113,12 +113,15 @@ jobs:
if: runner.os == 'Linux' && matrix.appimage == true
run: |
sudo add-apt-repository ppa:savoury1/qt-5-15
sudo add-apt-repository ppa:savoury1/kde-5-80
sudo add-apt-repository ppa:savoury1/gpg
sudo add-apt-repository ppa:savoury1/ffmpeg4
- name: Install Qt (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get -y update
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build qt5-image-formats-plugins
- name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.appimage == true

1
.gitignore vendored
View File

@ -15,7 +15,6 @@ CMakeLists.txt.user.*
/.settings
/.idea
/.vscode
.clang-format
cmake-build-*/
Debug

View File

@ -74,7 +74,7 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 1)
set(Launcher_VERSION_MINOR 3)
set(Launcher_VERSION_HOTFIX 0)
set(Launcher_VERSION_HOTFIX 1)
# Build number
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
@ -91,13 +91,6 @@ set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch L
# Imgur API Client ID
set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
# MSA Client ID
set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
# CurseForge API Key
# CHANGE THIS IF YOU FORK THIS PROJECT!
set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key")
# Bug tracker URL
set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.")
@ -119,6 +112,22 @@ set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PolyMCLauncher/" CACHE STRI
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build against")
# API Keys
# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service
# of these platforms, please change these API keys beforehand.
# Be aware that if you were to use these API keys for malicious purposes they might get revoked, which might cause
# breakage to thousands of users.
# If you don't plan to use these features of this software, you can just remove these values.
# By using this key in your builds you accept the terms of use laid down in
# https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use
set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
# By using this key in your builds you accept the terms and conditions laid down in
# https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions
# NOTE: CurseForge requires you to change this if you make any kind of derivative work.
set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key")
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
@ -128,6 +137,8 @@ message(STATUS "Git commit: ${Launcher_GIT_COMMIT}")
message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}")
set(Launcher_RELEASE_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
set(Launcher_RELEASE_VERSION_NAME4 "${Launcher_RELEASE_VERSION_NAME}.0")
set(Launcher_RELEASE_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_HOTFIX},0")
string(TIMESTAMP TODAY "%Y-%m-%d")
set(Launcher_RELEASE_TIMESTAMP "${TODAY}")
@ -143,7 +154,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(QuaZip-Qt5 1.3)
find_package(QuaZip-Qt5 1.3 QUIET)
endif()
if (NOT QuaZip-Qt5_FOUND)
set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE)

View File

@ -80,10 +80,19 @@ To modify download information or change packaging information send a pull reque
## Forking/Redistributing/Custom builds policy
Do whatever you want, we don't care. Just follow the license. If you have any questions about this feel free to ask in an issue.
We don't care what you do with your fork/custom build as long as you do the following as a basic courtesy:
- Follow the terms of the [license](LICENSE) (not just a courtesy, but also a legal responsibility)
- Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org).
- Go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
If you have any questions or want any clarification on the above conditions please make an issue and ask us.
Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)
If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`).
All launcher code is available under the GPL-3.0-only license.
[Source for the website](https://github.com/PolyMC/polymc.github.io) is hosted under the AGPL-3.0-or-later License.
The logo and related assets are under the CC BY-SA 4.0 license.

View File

@ -1 +1 @@
(import packages/nix/flake-compat.nix).defaultNix
(import nix/flake-compat.nix).defaultNix

31
flake.lock generated
View File

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1648199409,
"narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=",
"lastModified": 1650374568,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "64a525ee38886ab9028e6f61790de0832aa3ef03",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"type": "github"
},
"original": {
@ -34,11 +34,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1648219316,
"narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=",
"lastModified": 1653326962,
"narHash": "sha256-W8feCYqKTsMre4nAEpv5Kx1PVFC+hao/LwqtB2Wci/8=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634",
"rev": "41cc1d5d9584103be4108c1815c350e07c807036",
"type": "github"
},
"original": {
@ -48,28 +48,11 @@
"type": "github"
}
},
"quazip": {
"flake": false,
"locked": {
"lastModified": 1643049383,
"narHash": "sha256-LcJY6yd6GyeL7X5MP4L94diceM1TYespWByliBsjK98=",
"owner": "stachenov",
"repo": "quazip",
"rev": "09ec1d10c6d627f895109b21728dda000cbfa7d1",
"type": "github"
},
"original": {
"owner": "stachenov",
"repo": "quazip",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus",
"nixpkgs": "nixpkgs",
"quazip": "quazip"
"nixpkgs": "nixpkgs"
}
}
},

View File

@ -5,10 +5,9 @@
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
libnbtplusplus = { url = "github:multimc/libnbtplusplus"; flake = false; };
quazip = { url = "github:stachenov/quazip"; flake = false; };
};
outputs = { self, nixpkgs, libnbtplusplus, quazip, ... }:
outputs = { self, nixpkgs, libnbtplusplus, ... }:
let
# Generate a user-friendly version number.
version = builtins.substring 0 8 self.lastModifiedDate;
@ -23,7 +22,11 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
in
{
packages = forAllSystems (system: { polymc = pkgs.${system}.libsForQt5.callPackage ./packages/nix/polymc { inherit version self quazip libnbtplusplus; }; });
packages = forAllSystems (system: {
polymc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
polymc-qt6 = pkgs.${system}.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
});
defaultPackage = forAllSystems (system: self.packages.${system}.polymc);
apps = forAllSystems (system: { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; });

View File

@ -128,6 +128,8 @@ set(NET_SOURCES
net/PasteUpload.h
net/Sink.h
net/Validator.h
net/Upload.cpp
net/Upload.h
)
# Game launch logic
@ -837,6 +839,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/SkinUploadDialog.h
ui/dialogs/ModDownloadDialog.cpp
ui/dialogs/ModDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp
ui/dialogs/ScrollMessageBox.h
# GUI - widgets
ui/widgets/Common.cpp
@ -940,6 +944,7 @@ qt5_wrap_ui(LAUNCHER_UI
ui/dialogs/LoginDialog.ui
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
)
qt5_add_resources(LAUNCHER_RESOURCES
@ -958,7 +963,7 @@ qt5_add_resources(LAUNCHER_RESOURCES
######## Windows resource files ########
if(WIN32)
set(LAUNCHER_RCS ../${Launcher_Branding_WindowsRC})
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
endif()
# Add executable

View File

@ -60,9 +60,9 @@
#include "net/ChecksumValidator.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ScrollMessageBox.h"
#include <algorithm>
#include <iterator>
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
{
@ -72,6 +72,7 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
bool InstanceImportTask::abort()
{
if (m_filesNetJob)
m_filesNetJob->abort();
m_extractFuture.cancel();
@ -135,18 +136,20 @@ void InstanceImportTask::processZipPack()
return;
}
QStringList blacklist = {"instance.cfg", "manifest.json"};
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json");
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
QString modrinthFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "modrinth.index.json");
QuaZipDir packZipDir(m_packZip.get());
// https://docs.modrinth.com/docs/modpacks/format_definition/#storage
bool modrinthFound = packZipDir.exists("/modrinth.index.json");
bool technicFound = packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json");
QString root;
if(!mmcFound.isNull())
// NOTE: Prioritize modpack platforms that aren't searched for recursively.
// Especially Flame has a very common filename for its manifest, which may appear inside overrides for example
if(modrinthFound)
{
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcFound;
root = mmcFound;
m_modpackType = ModpackType::MultiMC;
// process as Modrinth pack
qDebug() << "Modrinth:" << modrinthFound;
m_modpackType = ModpackType::Modrinth;
}
else if (technicFound)
{
@ -156,19 +159,25 @@ void InstanceImportTask::processZipPack()
extractDir.cd(".minecraft");
m_modpackType = ModpackType::Technic;
}
else if(!flameFound.isNull())
else
{
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
if (!mmcRoot.isNull())
{
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcRoot;
root = mmcRoot;
m_modpackType = ModpackType::MultiMC;
}
else if(!flameRoot.isNull())
{
// process as Flame pack
qDebug() << "Flame:" << flameFound;
root = flameFound;
qDebug() << "Flame:" << flameRoot;
root = flameRoot;
m_modpackType = ModpackType::Flame;
}
else if(!modrinthFound.isNull())
{
// process as Modrinth pack
qDebug() << "Modrinth:" << modrinthFound;
root = modrinthFound;
m_modpackType = ModpackType::Modrinth;
}
if(m_modpackType == ModpackType::Unknown)
{
@ -385,35 +394,58 @@ void InstanceImportTask::processFlame()
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]()
{
auto results = m_modIdResolver->getResults();
//first check for blocked mods
QString text;
auto anyBlocked = false;
for(const auto& result: results.files.values()) {
if (!result.resolved || result.url.isEmpty()) {
text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
anyBlocked = true;
}
}
if(anyBlocked) {
qWarning() << "Blocked mods found, displaying mod list";
auto message_dialog = new ScrollMessageBox(m_parent,
tr("Blocked mods found"),
tr("The following mods were blocked on third party launchers.<br/>"
"You will need to manually download them and add them to the modpack"),
text);
message_dialog->setModal(true);
message_dialog->show();
connect(message_dialog, &QDialog::rejected, [&]() {
m_modIdResolver.reset();
emitFailed("Canceled");
});
connect(message_dialog, &QDialog::accepted, [&]() {
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for(auto result: results.files)
{
for (const auto &result: m_modIdResolver->getResults().files) {
QString filename = result.fileName;
if(!result.required)
{
if (!result.required) {
filename += ".disabled";
}
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
auto path = FS::PathCombine(m_stagingPath, relpath);
switch(result.type)
{
case Flame::File::Type::Folder:
{
switch (result.type) {
case Flame::File::Type::Folder: {
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fall-through intentional, we treat these as plain old mods and dump them wherever.
}
case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod:
{
case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) {
qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path);
m_filesNetJob->addNetAction(dl);
}
break;
}
case Flame::File::Type::Modpack:
logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath));
logWarning(
tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
relpath));
break;
case Flame::File::Type::Cmod2:
case Flame::File::Type::Ctoc:
@ -423,23 +455,75 @@ void InstanceImportTask::processFlame()
}
}
m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
m_filesNetJob.reset();
emitSucceeded();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason)
{
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
m_filesNetJob.reset();
emitFailed(reason);
});
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
{
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
});
}else{
//TODO extract to function ?
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for (const auto &result: m_modIdResolver->getResults().files) {
QString filename = result.fileName;
if (!result.required) {
filename += ".disabled";
}
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
auto path = FS::PathCombine(m_stagingPath, relpath);
switch (result.type) {
case Flame::File::Type::Folder: {
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fall-through intentional, we treat these as plain old mods and dump them wherever.
}
case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) {
qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path);
m_filesNetJob->addNetAction(dl);
}
break;
}
case Flame::File::Type::Modpack:
logWarning(
tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
relpath));
break;
case Flame::File::Type::Cmod2:
case Flame::File::Type::Ctoc:
case Flame::File::Type::Unknown:
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
break;
}
}
m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
m_filesNetJob.reset();
emitSucceeded();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
m_filesNetJob.reset();
emitFailed(reason);
});
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
}
}
);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
@ -501,6 +585,7 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth()
{
std::vector<Modrinth::File> files;
std::vector<Modrinth::File> non_whitelisted_files;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
try {
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
@ -515,11 +600,11 @@ void InstanceImportTask::processModrinth()
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false;
for (auto& obj : jsonFiles) {
for (auto& modInfo : jsonFiles) {
Modrinth::File file;
file.path = Json::requireString(obj, "path");
file.path = Json::requireString(modInfo, "path");
auto env = Json::ensureObject(obj, "env");
auto env = Json::ensureObject(modInfo, "env");
QString support = Json::ensureString(env, "client", "unsupported");
if (support == "unsupported") {
continue;
@ -537,7 +622,7 @@ void InstanceImportTask::processModrinth()
file.path += ".disabled";
}
QJsonObject hashes = Json::requireObject(obj, "hashes");
QJsonObject hashes = Json::requireObject(modInfo, "hashes");
QString hash;
QCryptographicHash::Algorithm hashAlgorithm;
hash = Json::ensureString(hashes, "sha1");
@ -557,13 +642,38 @@ void InstanceImportTask::processModrinth()
file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces)
file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path);
if (!file.download.isValid() || !Modrinth::validateDownloadUrl(file.download)) {
throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL");
file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path);
if (!file.download.isValid()) {
qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(file.download.toString(), file.path);
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
else if (!Modrinth::validateDownloadUrl(file.download)) {
qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(file.download.toString(), file.path);
non_whitelisted_files.push_back(file);
}
files.push_back(file);
}
if (!non_whitelisted_files.empty()) {
QString text;
for (const auto& file : non_whitelisted_files) {
text += tr("Filepath: %1<br>URL: <a href='%2'>%2</a><br>").arg(file.path, file.download.toString());
}
auto message_dialog = new ScrollMessageBox(m_parent, tr("Non-whitelisted mods found"),
tr("The following mods have URLs that are not whitelisted by Modrinth.\n"
"Proceed with caution!"),
text);
message_dialog->setModal(true);
if (message_dialog->exec() == QDialog::Rejected) {
emitFailed("Aborted");
return;
}
}
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();

View File

@ -42,6 +42,7 @@
#include <QFutureWatcher>
#include "settings/SettingsObject.h"
#include "QObjectPtr.h"
#include "modplatform/flame/PackManifest.h"
#include <nonstd/optional>
@ -59,6 +60,10 @@ public:
bool canAbort() const override { return true; }
bool abort() override;
const QVector<Flame::File> &getBlockedFiles() const
{
return m_blockedMods;
}
protected:
//! Entry point for tasks.
@ -87,6 +92,7 @@ private: /* data */
std::unique_ptr<QuaZip> m_packZip;
QFuture<nonstd::optional<QStringList>> m_extractFuture;
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
QVector<Flame::File> m_blockedMods;
enum class ModpackType{
Unknown,
MultiMC,

View File

@ -273,7 +273,7 @@ void IconList::installIcons(const QStringList &iconFiles)
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
@ -290,7 +290,7 @@ void IconList::installIcon(const QString &file, const QString &name)
if(!fileinfo.isReadable() || !fileinfo.isFile())
return;
QString target = FS::PathCombine(m_dir.dirName(), name);
QString target = FS::PathCombine(getDirectory(), name);
QFile::copy(file, target);
}

View File

@ -68,7 +68,7 @@ class ModAPI {
{
QString s;
for(auto& ver : mcVersions){
s += QString("%1,").arg(ver.toString());
s += QString("\"%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); //remove last comma
return s;

View File

@ -414,7 +414,31 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
{
if(m_version.mainClass == QString() && m_version.extraArguments == QString()) {
if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) {
return true;
}
auto mainClass = m_version.mainClass.mainClass;
auto extraArguments = m_version.extraArguments.arguments;
auto hasMainClassDepends = !m_version.mainClass.depends.isEmpty();
auto hasExtraArgumentsDepends = !m_version.extraArguments.depends.isEmpty();
if (hasMainClassDepends || hasExtraArgumentsDepends) {
QSet<QString> mods;
for (const auto& item : m_version.mods) {
mods.insert(item.name);
}
if (hasMainClassDepends && !mods.contains(m_version.mainClass.depends)) {
mainClass = "";
}
if (hasExtraArgumentsDepends && !mods.contains(m_version.extraArguments.depends)) {
extraArguments = "";
}
}
if (mainClass.isEmpty() && extraArguments.isEmpty()) {
return true;
}
@ -442,12 +466,12 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<
auto f = std::make_shared<VersionFile>();
f->name = m_pack + " " + m_version_name;
if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) {
f->mainClass = m_version.mainClass;
if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) {
f->mainClass = mainClass;
}
// Parse out tweakers
auto args = m_version.extraArguments.split(" ");
auto args = extraArguments.split(" ");
QString previous;
for(auto arg : args) {
if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
@ -757,6 +781,17 @@ bool PackInstallTask::extractMods(
for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
auto &from = iter.key();
auto &to = iter.value();
// If the file already exists, assume the mod is the correct copy - and remove
// the copy from the Configs.zip
QFileInfo fileInfo(to);
if (fileInfo.exists()) {
if (!QFile::remove(to)) {
qWarning() << "Failed to delete" << to;
return false;
}
}
FS::copy fileCopyOperation(from, to);
if(!fileCopyOperation()) {
qWarning() << "Failed to copy" << from << "to" << to;

View File

@ -212,6 +212,18 @@ static void loadVersionMessages(ATLauncher::VersionMessages& m, QJsonObject& obj
m.update = Json::ensureString(obj, "update", "");
}
static void loadVersionMainClass(ATLauncher::PackVersionMainClass& m, QJsonObject& obj)
{
m.mainClass = Json::ensureString(obj, "mainClass", "");
m.depends = Json::ensureString(obj, "depends", "");
}
static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a, QJsonObject& obj)
{
a.arguments = Json::ensureString(obj, "arguments", "");
a.depends = Json::ensureString(obj, "depends", "");
}
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
{
v.version = Json::requireString(obj, "version");
@ -220,12 +232,12 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
if(obj.contains("mainClass")) {
auto main = Json::requireObject(obj, "mainClass");
v.mainClass = Json::ensureString(main, "mainClass", "");
loadVersionMainClass(v.mainClass, main);
}
if(obj.contains("extraArguments")) {
auto arguments = Json::requireObject(obj, "extraArguments");
v.extraArguments = Json::ensureString(arguments, "arguments", "");
loadVersionExtraArguments(v.extraArguments, arguments);
}
if(obj.contains("loader")) {

View File

@ -150,13 +150,25 @@ struct VersionMessages
QString update;
};
struct PackVersionMainClass
{
QString mainClass;
QString depends;
};
struct PackVersionExtraArguments
{
QString arguments;
QString depends;
};
struct PackVersion
{
QString version;
QString minecraft;
bool noConfigs;
QString mainClass;
QString extraArguments;
PackVersionMainClass mainClass;
PackVersionExtraArguments extraArguments;
VersionLoader loader;
QVector<VersionLibrary> libraries;

View File

@ -1,7 +1,9 @@
#include "FileResolvingTask.h"
#include "Json.h"
Flame::FileResolvingTask::FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest& toProcess)
#include "Json.h"
#include "net/Upload.h"
Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess)
: m_network(network), m_toProcess(toProcess)
{}
@ -10,40 +12,116 @@ void Flame::FileResolvingTask::executeTask()
setStatus(tr("Resolving mod IDs..."));
setProgress(0, m_toProcess.files.size());
m_dljob = new NetJob("Mod id resolver", m_network);
results.resize(m_toProcess.files.size());
int index = 0;
for (auto& file : m_toProcess.files) {
auto projectIdStr = QString::number(file.projectId);
auto fileIdStr = QString::number(file.fileId);
QString metaurl = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(projectIdStr, fileIdStr);
auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]);
result.reset(new QByteArray());
//build json data to send
QJsonObject object;
object["fileIds"] = QJsonArray::fromVariantList(std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) {
l.push_back(s.fileId);
return l;
}));
QByteArray data = Json::toText(object);
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data);
m_dljob->addNetAction(dl);
index++;
}
connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished);
m_dljob->start();
}
void Flame::FileResolvingTask::netJobFinished()
{
bool failed = false;
int index = 0;
for (auto& bytes : results) {
auto& out = m_toProcess.files[index];
// job to check modrinth for blocked projects
auto job = new NetJob("Modrinth check", m_network);
blockedProjects = QMap<File *,QByteArray *>();
auto doc = Json::requireDocument(*result);
auto array = Json::requireArray(doc.object()["data"]);
for (QJsonValueRef file : array) {
auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
auto& out = m_toProcess.files[fileid];
try {
failed &= (!out.parseFromBytes(bytes));
out.parseFromObject(Json::requireObject(file));
} catch (const JSONValidationError& e) {
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
qCritical() << e.cause();
qCritical() << "JSON:";
qCritical() << bytes;
failed = true;
qDebug() << "Blocked mod on curseforge" << out.fileName;
auto hash = out.hash;
if(!hash.isEmpty()) {
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = new QByteArray();
auto dl = Net::Download::makeByteArray(QUrl(url), output);
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() {
out.resolved = true;
});
job->addNetAction(dl);
blockedProjects.insert(&out, output);
}
}
index++;
}
if (!failed) {
connect(job, &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished);
job->start();
}
void Flame::FileResolvingTask::modrinthCheckFinished() {
qDebug() << "Finished with blocked mods : " << blockedProjects.size();
for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) {
auto &out = *it;
auto bytes = blockedProjects[out];
if (!out->resolved) {
delete bytes;
continue;
}
QJsonDocument doc = QJsonDocument::fromJson(*bytes);
auto obj = doc.object();
auto array = Json::requireArray(obj,"files");
for (auto file: array) {
auto fileObj = Json::requireObject(file);
auto primary = Json::requireBoolean(fileObj,"primary");
if (primary) {
out->url = Json::requireUrl(fileObj,"url");
qDebug() << "Found alternative on modrinth " << out->fileName;
break;
}
}
delete bytes;
}
//copy to an output list and filter out projects found on modrinth
auto block = new QList<File *>();
auto it = blockedProjects.keys();
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
return !f->resolved;
});
//Display not found mods early
if (!block->empty()) {
//blocked mods found, we need the slug for displaying.... we need another job :D !
auto slugJob = new NetJob("Slug Job", m_network);
auto slugs = QVector<QByteArray>(block->size());
auto index = 0;
for (auto fileInfo: *block) {
auto projectId = fileInfo->projectId;
slugs[index] = QByteArray();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::Download::makeByteArray(url, &slugs[index]);
slugJob->addNetAction(dl);
index++;
}
connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() {
slugJob->deleteLater();
auto index = 0;
for (const auto &slugResult: slugs) {
auto json = QJsonDocument::fromJson(slugResult);
auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
"websiteUrl");
auto mod = block->at(index);
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
mod->websiteUrl = link;
index++;
}
emitSucceeded();
});
slugJob->start();
} else {
emitFailed(tr("Some mod ID resolving tasks failed."));
emitSucceeded();
}
}

View File

@ -10,7 +10,7 @@ class FileResolvingTask : public Task
{
Q_OBJECT
public:
explicit FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest &toProcess);
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess);
virtual ~FileResolvingTask() {};
const Flame::Manifest &getResults() const
@ -27,7 +27,11 @@ protected slots:
private: /* data */
shared_qobject_ptr<QNetworkAccessManager> m_network;
Flame::Manifest m_toProcess;
QVector<QByteArray> results;
std::shared_ptr<QByteArray> result;
NetJob::Ptr m_dljob;
void modrinthCheckFinished();
QMap<File *, QByteArray *> blockedProjects;
};
}

View File

@ -65,17 +65,13 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
// pick the latest version supported
file.mcVersion = versionArray[0].toString();
file.version = Json::requireString(version, "displayName");
file.fileName = Json::requireString(version, "fileName");
file.downloadUrl = Json::ensureString(version, "downloadUrl");
if(file.downloadUrl.isEmpty()){
//FIXME : HACK, MAY NOT WORK FOR LONG
file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3")
.arg(QString::number(QString::number(file.fileId).leftRef(4).toInt())
,QString::number(QString::number(file.fileId).rightRef(3).toInt())
,QUrl::toPercentEncoding(file.fileName));
}
// only add if we have a download URL (third party distribution is enabled)
if (!file.downloadUrl.isEmpty()) {
unsortedVersions.append(file);
}
}
auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; };
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);

View File

@ -18,7 +18,6 @@ struct IndexedVersion {
QString version;
QString mcVersion;
QString downloadUrl;
QString fileName;
};
struct IndexedPack

View File

@ -41,7 +41,7 @@ static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest)
auto obj = Json::requireObject(item);
Flame::File file;
loadFileV1(file, obj);
m.files.append(file);
m.files.insert(file.fileId,file);
}
m.overrides = Json::ensureString(manifest, "overrides", "overrides");
}
@ -61,21 +61,9 @@ void Flame::loadManifest(Flame::Manifest& m, const QString& filepath)
loadManifestV1(m, obj);
}
bool Flame::File::parseFromBytes(const QByteArray& bytes)
bool Flame::File::parseFromObject(const QJsonObject& obj)
{
auto doc = Json::requireDocument(bytes);
if (!doc.isObject()) {
throw JSONValidationError(QString("data is not an object? that's not supposed to happen"));
}
auto obj = Json::ensureObject(doc.object(), "data");
fileName = Json::requireString(obj, "fileName");
QString rawUrl = Json::requireString(obj, "downloadUrl");
url = QUrl(rawUrl, QUrl::TolerantMode);
if (!url.isValid()) {
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
}
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
// It is also optional
type = File::Type::SingleFile;
@ -87,6 +75,25 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes)
// this is probably a mod, dunno what else could modpacks download
targetFolder = "mods";
}
// get the hash
hash = QString();
auto hashes = Json::ensureArray(obj, "hashes");
for(QJsonValueRef item : hashes) {
auto hobj = Json::requireObject(item);
auto algo = Json::requireInteger(hobj, "algo");
auto value = Json::requireString(hobj, "value");
if (algo == 1) {
hash = value;
}
}
// may throw, if the project is blocked
QString rawUrl = Json::ensureString(obj, "downloadUrl");
url = QUrl(rawUrl, QUrl::TolerantMode);
if (!url.isValid()) {
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
}
resolved = true;
return true;

View File

@ -2,19 +2,24 @@
#include <QString>
#include <QVector>
#include <QMap>
#include <QUrl>
#include <QJsonObject>
namespace Flame
{
struct File
{
// NOTE: throws JSONValidationError
bool parseFromBytes(const QByteArray &bytes);
bool parseFromObject(const QJsonObject& object);
int projectId = 0;
int fileId = 0;
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
bool required = true;
QString hash;
// NOTE: only set on blocked files ! Empty otherwise.
QString websiteUrl;
// our
bool resolved = false;
@ -54,7 +59,8 @@ struct Manifest
QString name;
QString version;
QString author;
QVector<Flame::File> files;
//File id -> File
QMap<int,Flame::File> files;
QString overrides;
};

View File

@ -79,11 +79,11 @@ class ModrinthAPI : public NetworkModAPI {
{
return QString(BuildConfig.MODRINTH_PROD_URL +
"/project/%1/version?"
"game_versions=[%2]"
"game_versions=[%2]&"
"loaders=[\"%3\"]")
.arg(args.addonId)
.arg(getGameVersionsString(args.mcVersions))
.arg(getModLoaderStrings(args.loaders).join("\",\""));
.arg(args.addonId,
getGameVersionsString(args.mcVersions),
getModLoaderStrings(args.loaders).join("\",\""));
};
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString

View File

@ -42,6 +42,8 @@
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include <QSet>
static ModrinthAPI api;
namespace Modrinth {
@ -95,19 +97,15 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
auto validateDownloadUrl(QUrl url) -> bool
{
auto domain = url.host();
if(domain == "cdn.modrinth.com")
return true;
if(domain == "edge.forgecdn.net")
return true;
if(domain == "media.forgecdn.net")
return true;
if(domain == "github.com")
return true;
if(domain == "raw.githubusercontent.com")
return true;
static QSet<QString> domainWhitelist{
"cdn.modrinth.com",
"github.com",
"raw.githubusercontent.com",
"gitlab.com"
};
return false;
auto domain = url.host();
return domainWhitelist.contains(domain);
}
auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion

199
launcher/net/Upload.cpp Normal file
View File

@ -0,0 +1,199 @@
//
// Created by timoreo on 20/05/22.
//
#include "Upload.h"
#include <utility>
#include "ByteArraySink.h"
#include "BuildConfig.h"
#include "Application.h"
namespace Net {
void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
setProgress(bytesReceived, bytesTotal);
}
void Upload::downloadError(QNetworkReply::NetworkError error) {
if (error == QNetworkReply::OperationCanceledError) {
qCritical() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser;
} else {
// error happened during download.
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
}
}
void Upload::sslErrors(const QList<QSslError> &errors) {
int i = 1;
for (const auto& error : errors) {
qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
}
bool Upload::handleRedirect()
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
if (!m_reply->hasRawHeader("Location")) {
// no redirect -> it's fine to continue
return false;
}
// there is a Location header, but it's not correct. we need to apply some workarounds...
QByteArray redirectBA = m_reply->rawHeader("Location");
if (redirectBA.size() == 0) {
// empty, yet present redirect header? WTF?
return false;
}
QString redirectStr = QString::fromUtf8(redirectBA);
if (redirectStr.startsWith("//")) {
/*
* IF the URL begins with //, we need to insert the URL scheme.
* See: https://bugreports.qt.io/browse/QTBUG-41061
* See: http://tools.ietf.org/html/rfc3986#section-4.2
*/
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
} else if (redirectStr.startsWith("/")) {
/*
* IF the URL begins with /, we need to process it as a relative URL
*/
auto url = m_reply->url();
url.setPath(redirectStr, QUrl::TolerantMode);
redirectStr = url.toString();
}
/*
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
* FIXME: report Qt bug for this
*/
redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) {
qWarning() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure);
return false;
}
qDebug() << "Fixed location header:" << redirect;
} else {
qDebug() << "Location header:" << redirect;
}
m_url = QUrl(redirect.toString());
qDebug() << "Following redirect to " << m_url.toString();
startAction(m_network);
return true;
}
void Upload::downloadFinished() {
// handle HTTP redirection first
// very unlikely for post requests, still can happen
if (handleRedirect()) {
qDebug() << "Upload redirected:" << m_url.toString();
return;
}
// if the download failed before this point ...
if (m_state == State::Succeeded) {
qDebug() << "Upload failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit succeeded();
return;
} else if (m_state == State::Failed) {
qDebug() << "Upload failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
} else if (m_state == State::AbortedByUser) {
qDebug() << "Upload aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
return;
}
// make sure we got all the remaining data, if any
auto data = m_reply->readAll();
if (data.size()) {
qDebug() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data);
}
// otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qDebug() << "Upload failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
}
m_reply.reset();
qDebug() << "Upload succeeded:" << m_url.toString();
emit succeeded();
}
void Upload::downloadReadyRead() {
if (m_state == State::Running) {
auto data = m_reply->readAll();
m_state = m_sink->write(data);
}
}
void Upload::executeTask() {
setStatus(tr("Uploading %1").arg(m_url.toString()));
if (m_state == State::AbortedByUser) {
qWarning() << "Attempt to start an aborted Upload:" << m_url.toString();
emit aborted();
return;
}
QNetworkRequest request(m_url);
m_state = m_sink->init(request);
switch (m_state) {
case State::Succeeded:
emitSucceeded();
qDebug() << "Upload cache hit " << m_url.toString();
return;
case State::Running:
qDebug() << "Uploading " << m_url.toString();
break;
case State::Inactive:
case State::Failed:
emitFailed("");
return;
case State::AbortedByUser:
emitAborted();
return;
}
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT);
if (request.url().host().contains("api.curseforge.com")) {
request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());
}
//TODO other types of post requests ?
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* rep = m_network->post(request, m_post_data);
m_reply.reset(rep);
connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
}
Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data) {
auto* up = new Upload();
up->m_url = std::move(url);
up->m_sink.reset(new ByteArraySink(output));
up->m_post_data = std::move(m_post_data);
return up;
}
} // Net

31
launcher/net/Upload.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include "NetAction.h"
#include "Sink.h"
namespace Net {
class Upload : public NetAction {
Q_OBJECT
public:
static Upload::Ptr makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data);
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void sslErrors(const QList<QSslError> & errors);
void downloadFinished() override;
void downloadReadyRead() override;
public slots:
void executeTask() override;
private:
std::unique_ptr<Sink> m_sink;
QByteArray m_post_data;
bool handleRedirect();
};
} // Net

View File

@ -33,10 +33,21 @@ void SequentialTask::executeTask()
bool SequentialTask::abort()
{
bool succeeded = true;
for (auto& task : m_queue) {
if (!task->abort()) succeeded = false;
if(m_currentIndex == -1 || m_currentIndex >= m_queue.size()) {
if(m_currentIndex == -1) {
// Don't call emitAborted() here, we want to bypass the 'is the task running' check
emit aborted();
emit finished();
}
m_queue.clear();
return true;
}
bool succeeded = m_queue[m_currentIndex]->abort();
m_queue.clear();
if(succeeded)
emitAborted();
return succeeded;
}
@ -76,7 +87,7 @@ void SequentialTask::subTaskProgress(qint64 current, qint64 total)
setProgress(0, 100);
return;
}
setProgress(m_currentIndex, m_queue.count());
setProgress(m_currentIndex + 1, m_queue.count());
m_stepProgress = current;
m_stepTotalProgress = total;

View File

@ -77,18 +77,20 @@ void ModDownloadDialog::confirm()
auto keys = modTask.keys();
keys.sort(Qt::CaseInsensitive);
auto confirm_dialog = ReviewMessageBox::create(
this,
tr("Confirm mods to download")
);
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm mods to download"));
for (auto& task : keys) {
confirm_dialog->appendMod(task, modTask.find(task).value()->getFilename());
confirm_dialog->appendMod({ task, modTask.find(task).value()->getFilename() });
}
connect(confirm_dialog, &QDialog::accepted, this, &ModDownloadDialog::accept);
if (confirm_dialog->exec()) {
auto deselected = confirm_dialog->deselectedMods();
for (auto name : deselected) {
modTask.remove(name);
}
confirm_dialog->open();
this->accept();
}
}
void ModDownloadDialog::accept()
@ -132,6 +134,12 @@ bool ModDownloadDialog::isModSelected(const QString &name, const QString& filena
return iter != modTask.end() && (iter.value()->getFilename() == filename);
}
bool ModDownloadDialog::isModSelected(const QString &name) const
{
auto iter = modTask.find(name);
return iter != modTask.end();
}
ModDownloadDialog::~ModDownloadDialog()
{
}

View File

@ -32,6 +32,7 @@ public:
void addSelectedMod(const QString & name = QString(), ModDownloadTask * task = nullptr);
void removeSelectedMod(const QString & name = QString());
bool isModSelected(const QString & name, const QString & filename) const;
bool isModSelected(const QString & name) const;
const QList<ModDownloadTask*> getTasks();
const std::shared_ptr<ModFolderModel> &mods;
@ -41,8 +42,6 @@ public slots:
void accept() override;
void reject() override;
//private slots:
private:
Ui::ModDownloadDialog *ui = nullptr;
PageContainer * m_container = nullptr;

View File

@ -16,8 +16,8 @@
#include "ProgressDialog.h"
#include "ui_ProgressDialog.h"
#include <QKeyEvent>
#include <QDebug>
#include <QKeyEvent>
#include "tasks/Task.h"
@ -44,6 +44,7 @@ void ProgressDialog::on_skipButton_clicked(bool checked)
{
Q_UNUSED(checked);
task->abort();
QDialog::reject();
}
ProgressDialog::~ProgressDialog()
@ -63,14 +64,12 @@ int ProgressDialog::execWithTask(Task *task)
this->task = task;
QDialog::DialogCode result;
if(!task)
{
if (!task) {
qDebug() << "Programmer error: progress dialog created with null task.";
return Accepted;
}
if(handleImmediateResult(result))
{
if (handleImmediateResult(result)) {
return result;
}
@ -79,8 +78,11 @@ int ProgressDialog::execWithTask(Task *task)
connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString)));
connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded()));
connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString&)));
connect(task, SIGNAL(stepStatus(QString)), SLOT(changeStatus(const QString&)));
connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64)));
connect(task, &Task::aborted, [this] { onTaskFailed(tr("Aborted by user")); });
m_is_multi_step = task->isMultiStep();
if (!m_is_multi_step) {
ui->globalStatusLabel->setHidden(true);
@ -88,22 +90,16 @@ int ProgressDialog::execWithTask(Task *task)
}
// if this didn't connect to an already running task, invoke start
if(!task->isRunning())
{
if (!task->isRunning()) {
task->start();
}
if(task->isRunning())
{
if (task->isRunning()) {
changeProgress(task->getProgress(), task->getTotalProgress());
changeStatus(task->getStatus());
return QDialog::exec();
}
else if(handleImmediateResult(result))
{
} else if (handleImmediateResult(result)) {
return result;
}
else
{
} else {
return QDialog::Rejected;
}
}
@ -122,14 +118,10 @@ int ProgressDialog::execWithTask(std::unique_ptr<Task> &task)
bool ProgressDialog::handleImmediateResult(QDialog::DialogCode& result)
{
if(task->isFinished())
{
if(task->wasSuccessful())
{
if (task->isFinished()) {
if (task->wasSuccessful()) {
result = QDialog::Accepted;
}
else
{
} else {
result = QDialog::Rejected;
}
return true;
@ -142,9 +134,7 @@ Task *ProgressDialog::getTask()
return task;
}
void ProgressDialog::onTaskStarted()
{
}
void ProgressDialog::onTaskStarted() {}
void ProgressDialog::onTaskFailed(QString failure)
{
@ -158,8 +148,9 @@ void ProgressDialog::onTaskSucceeded()
void ProgressDialog::changeStatus(const QString& status)
{
ui->globalStatusLabel->setText(task->getStatus());
ui->statusLabel->setText(task->getStepStatus());
ui->globalStatusLabel->setText(status);
updateSize();
}
@ -171,8 +162,7 @@ void ProgressDialog::changeProgress(qint64 current, qint64 total)
if (!m_is_multi_step) {
ui->taskProgressBar->setMaximum(total);
ui->taskProgressBar->setValue(current);
}
else{
} else {
ui->taskProgressBar->setMaximum(task->getStepProgress());
ui->taskProgressBar->setValue(task->getStepTotalProgress());
}
@ -180,15 +170,11 @@ void ProgressDialog::changeProgress(qint64 current, qint64 total)
void ProgressDialog::keyPressEvent(QKeyEvent* e)
{
if(ui->skipButton->isVisible())
{
if (e->key() == Qt::Key_Escape)
{
if (ui->skipButton->isVisible()) {
if (e->key() == Qt::Key_Escape) {
on_skipButton_clicked(true);
return;
}
else if(e->key() == Qt::Key_Tab)
{
} else if (e->key() == Qt::Key_Tab) {
ui->skipButton->setFocusPolicy(Qt::StrongFocus);
ui->skipButton->setFocus();
ui->skipButton->setAutoDefault(true);
@ -201,12 +187,9 @@ void ProgressDialog::keyPressEvent(QKeyEvent *e)
void ProgressDialog::closeEvent(QCloseEvent* e)
{
if (task && task->isRunning())
{
if (task && task->isRunning()) {
e->ignore();
}
else
{
} else {
QDialog::closeEvent(e);
}
}

View File

@ -40,6 +40,12 @@
</item>
<item row="2" column="0">
<widget class="QLabel" name="statusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Task Status...</string>
</property>

View File

@ -5,6 +5,9 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QStrin
: QDialog(parent), ui(new Ui::ReviewMessageBox)
{
ui->setupUi(this);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ReviewMessageBox::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ReviewMessageBox::reject);
}
ReviewMessageBox::~ReviewMessageBox()
@ -17,15 +20,33 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon)
return new ReviewMessageBox(parent, title, icon);
}
void ReviewMessageBox::appendMod(const QString& name, const QString& filename)
void ReviewMessageBox::appendMod(ModInformation&& info)
{
auto itemTop = new QTreeWidgetItem(ui->modTreeWidget);
itemTop->setText(0, name);
itemTop->setCheckState(0, Qt::CheckState::Checked);
itemTop->setText(0, info.name);
auto filenameItem = new QTreeWidgetItem(itemTop);
filenameItem->setText(0, tr("Filename: %1").arg(filename));
filenameItem->setText(0, tr("Filename: %1").arg(info.filename));
itemTop->insertChildren(0, { filenameItem });
ui->modTreeWidget->addTopLevelItem(itemTop);
}
auto ReviewMessageBox::deselectedMods() -> QStringList
{
QStringList list;
auto* item = ui->modTreeWidget->topLevelItem(0);
for (int i = 0; item != nullptr; ++i) {
if (item->checkState(0) == Qt::CheckState::Unchecked) {
list.append(item->text(0));
}
item = ui->modTreeWidget->topLevelItem(i);
}
return list;
}

View File

@ -6,17 +6,23 @@ namespace Ui {
class ReviewMessageBox;
}
class ReviewMessageBox final : public QDialog {
class ReviewMessageBox : public QDialog {
Q_OBJECT
public:
static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*;
void appendMod(const QString& name, const QString& filename);
using ModInformation = struct {
QString name;
QString filename;
};
void appendMod(ModInformation&& info);
auto deselectedMods() -> QStringList;
~ReviewMessageBox();
private:
protected:
ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon);
Ui::ReviewMessageBox* ui;

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>500</width>
<height>350</height>
</rect>
</property>
<property name="windowTitle">
@ -20,24 +20,7 @@
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>You're about to download the following mods:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QTreeWidget" name="modTreeWidget">
<property name="alternatingRowColors">
<bool>true</bool>
@ -58,41 +41,33 @@
</column>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="explainLabel">
<property name="text">
<string>You're about to download the following mods:</string>
</property>
</widget>
</item>
<item row="5" column="0" rowspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="onlyCheckedLabel">
<property name="text">
<string>Only mods with a check will be downloaded!</string>
</property>
</widget>
</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>ReviewMessageBox</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>200</x>
<y>265</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ReviewMessageBox</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>200</x>
<y>265</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
</ui>

View File

@ -0,0 +1,15 @@
#include "ScrollMessageBox.h"
#include "ui_ScrollMessageBox.h"
ScrollMessageBox::ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body) :
QDialog(parent), ui(new Ui::ScrollMessageBox) {
ui->setupUi(this);
this->setWindowTitle(title);
ui->label->setText(text);
ui->textBrowser->setText(body);
}
ScrollMessageBox::~ScrollMessageBox() {
delete ui;
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <QDialog>
QT_BEGIN_NAMESPACE
namespace Ui { class ScrollMessageBox; }
QT_END_NAMESPACE
class ScrollMessageBox : public QDialog {
Q_OBJECT
public:
ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body);
~ScrollMessageBox() override;
private:
Ui::ScrollMessageBox *ui;
};

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScrollMessageBox</class>
<widget class="QDialog" name="ScrollMessageBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>455</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">ScrollMessageBox</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string notr="true"/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QTextBrowser" name="textBrowser">
<property name="acceptRichText">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ScrollMessageBox</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>425</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>227</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ScrollMessageBox</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>425</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>227</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -48,6 +48,7 @@
#include "tools/BaseProfiler.h"
#include "Application.h"
#include "net/PasteUpload.h"
#include "BuildConfig.h"
APIPage::APIPage(QWidget *parent) :
QWidget(parent),
@ -76,6 +77,8 @@ APIPage::APIPage(QWidget *parent) :
ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry));
ui->tabWidget->tabBar()->hide();
ui->metaURL->setPlaceholderText(BuildConfig.META_URL);
loadSettings();
resetBaseURLNote();

View File

@ -36,13 +36,16 @@
<item>
<widget class="QGroupBox" name="groupBox_paste">
<property name="title">
<string>Pastebin Service</string>
<string>&amp;Pastebin Service</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="pasteServiceTypeLabel">
<property name="text">
<string>Paste Service Type</string>
<string>Paste Service &amp;Type</string>
</property>
<property name="buddy">
<cstring>pasteTypeComboBox</cstring>
</property>
</widget>
</item>
@ -52,7 +55,10 @@
<item>
<widget class="QLabel" name="baseURLLabel">
<property name="text">
<string>Base URL</string>
<string>Base &amp;URL</string>
</property>
<property name="buddy">
<cstring>baseURLEntry</cstring>
</property>
</widget>
</item>
@ -146,7 +152,7 @@
<item>
<widget class="QLineEdit" name="metaURL">
<property name="placeholderText">
<string>(Default)</string>
<string/>
</property>
</widget>
</item>

View File

@ -95,7 +95,7 @@ void JavaPage::applySettings()
// Java Settings
s->set("JavaPath", ui->javaPathTextBox->text());
s->set("JvmArgs", ui->jvmArgsTextBox->text());
s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " "));
s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked());
s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked());
JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget());
@ -120,7 +120,7 @@ void JavaPage::loadSettings()
// Java Settings
ui->javaPathTextBox->setText(s->get("JavaPath").toString());
ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString());
ui->jvmArgsTextBox->setPlainText(s->get("JvmArgs").toString());
ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool());
ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool());
}
@ -166,7 +166,7 @@ void JavaPage::on_javaTestBtn_clicked()
return;
}
checker.reset(new JavaCommon::TestCheck(
this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->text(),
this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "),
ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value()));
connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished()));
checker->run();

View File

@ -150,6 +150,35 @@
<string>Java Runtime</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="1">
<widget class="QPushButton" name="javaDetectBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Auto-detect...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>JVM arguments:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelJavaPath">
<property name="sizePolicy">
@ -166,40 +195,8 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>J&amp;VM arguments:</string>
</property>
<property name="buddy">
<cstring>jvmArgsTextBox</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
</property>
<property name="text">
<string>&amp;Skip Java compatibility checks</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="javaDetectBtn">
<item row="3" column="2">
<widget class="QPushButton" name="javaTestBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -207,7 +204,7 @@
</sizepolicy>
</property>
<property name="text">
<string>&amp;Auto-detect...</string>
<string>&amp;Test</string>
</property>
</widget>
</item>
@ -237,22 +234,22 @@
</item>
</layout>
</item>
<item row="3" column="2">
<widget class="QPushButton" name="javaTestBtn">
<item row="4" column="1">
<widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
</property>
<property name="text">
<string>&amp;Test</string>
<string>&amp;Skip Java compatibility checks</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="jvmArgsTextBox"/>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="skipJavaWizardCheckbox">
<property name="toolTip">
@ -263,6 +260,25 @@
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QPlainTextEdit" name="jvmArgsTextBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -291,7 +307,6 @@
<tabstop>permGenSpinBox</tabstop>
<tabstop>javaBrowseBtn</tabstop>
<tabstop>javaPathTextBox</tabstop>
<tabstop>jvmArgsTextBox</tabstop>
<tabstop>javaDetectBtn</tabstop>
<tabstop>javaTestBtn</tabstop>
<tabstop>tabWidget</tabstop>

View File

@ -402,6 +402,10 @@ void ModFolderPage::on_actionInstall_mods_triggered()
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); }
@ -411,6 +415,7 @@ void ModFolderPage::on_actionInstall_mods_triggered()
for (auto task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);

View File

@ -117,7 +117,7 @@ void ImportPage::updateState()
if(fi.exists() && (zip || fi.suffix() == "mrpack"))
{
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
dialog->setSuggestedIcon("default");
}
}
@ -130,7 +130,7 @@ void ImportPage::updateState()
}
// hook, line and sinker.
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
dialog->setSuggestedIcon("default");
}
}

View File

@ -38,9 +38,11 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
}
ModPlatform::IndexedPack pack = modpacks.at(pos);
if (role == Qt::DisplayRole) {
switch (role) {
case Qt::DisplayRole: {
return pack.name;
} else if (role == Qt::ToolTipRole) {
}
case Qt::ToolTipRole: {
if (pack.description.length() > 100) {
// some magic to prevent to long tooltips and replace html linebreaks
QString edit = pack.description.left(97);
@ -48,18 +50,33 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
return edit;
}
return pack.description;
} else if (role == Qt::DecorationRole) {
}
case Qt::DecorationRole: {
if (m_logoMap.contains(pack.logoName)) {
return (m_logoMap.value(pack.logoName));
}
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
// un-const-ify this
((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
return icon;
} else if (role == Qt::UserRole) {
}
case Qt::UserRole: {
QVariant v;
v.setValue(pack);
return v;
}
case Qt::FontRole: {
QFont font;
if (m_parent->getDialog()->isModSelected(pack.name)) {
font.setBold(true);
font.setUnderline(true);
}
return font;
}
default:
break;
}
return {};
}

View File

@ -41,6 +41,7 @@ class ModPage : public QWidget, public BasePage {
auto apiProvider() const -> const ModAPI* { return api.get(); };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
auto getDialog() const -> const ModDownloadDialog* { return dialog; }
auto getCurrent() -> ModPlatform::IndexedPack& { return current; }
void updateModVersions(int prev_count = -1);

View File

@ -201,7 +201,7 @@ void FlamePage::suggestCurrent()
return;
}
dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion));
dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion,this));
QString editedLogoName;
editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0);
listModel->getLogo(current.logoName, current.logoUrl,

View File

@ -175,7 +175,7 @@ void Page::suggestCurrent()
return;
}
dialog->setSuggestedPack(selected.name, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
dialog->setSuggestedPack(selected.name + " " + selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
QString editedLogoName;
if(selected.logo.toLower().startsWith("ftb"))
{

View File

@ -1,5 +1,5 @@
{ lib
, mkDerivation
{ stdenv
, lib
, fetchFromGitHub
, cmake
, ninja
@ -7,10 +7,11 @@
, jdk
, zlib
, file
, makeWrapper
, wrapQtAppsHook
, xorg
, libpulseaudio
, qtbase
, quazip
, libGL
, msaClientID ? ""
@ -18,7 +19,7 @@
, self
, version
, libnbtplusplus
, quazip
, enableLTO ? false
}:
let
@ -37,41 +38,34 @@ let
gameLibraryPath = libpath + ":/run/opengl-driver/lib";
in
mkDerivation rec {
stdenv.mkDerivation rec {
pname = "polymc";
inherit version;
src = lib.cleanSource self;
nativeBuildInputs = [ cmake ninja file makeWrapper ];
buildInputs = [ qtbase jdk zlib ];
nativeBuildInputs = [ cmake ninja jdk file wrapQtAppsHook ];
buildInputs = [ qtbase quazip zlib ];
dontWrapQtApps = true;
postPatch = lib.optionalString (msaClientID != "") ''
# add client ID
substituteInPlace CMakeLists.txt \
--replace '17b47edd-c884-4997-926d-9e7f9a6b4647' '${msaClientID}'
'';
postUnpack = ''
# Copy submodules inputs
rm -rf source/libraries/{libnbtplusplus,quazip}
mkdir source/libraries/{libnbtplusplus,quazip}
# Copy libnbtplusplus
rm -rf source/libraries/libnbtplusplus
mkdir source/libraries/libnbtplusplus
cp -a ${libnbtplusplus}/* source/libraries/libnbtplusplus
cp -a ${quazip}/* source/libraries/quazip
chmod a+r+w source/libraries/{libnbtplusplus,quazip}/*
chmod a+r+w source/libraries/libnbtplusplus/*
'';
cmakeFlags = [
"-GNinja"
"-DLauncher_PORTABLE=OFF"
];
"-DLauncher_QT_VERSION_MAJOR=${lib.versions.major qtbase.version}"
] ++ lib.optionals enableLTO [ "-DENABLE_LTO=on" ]
++ lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ];
postInstall = ''
# xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
wrapProgram $out/bin/polymc \
"''${qtWrapperArgs[@]}" \
wrapQtApp $out/bin/polymc \
--set GAME_LIBRARY_PATH ${gameLibraryPath} \
--prefix POLYMC_JAVA_PATHS : ${jdk}/lib/openjdk/bin/java:${jdk8}/lib/openjdk/bin/java \
--prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]}

View File

@ -1,9 +1,9 @@
let
lock = builtins.fromJSON (builtins.readFile ../../flake.lock);
lock = builtins.fromJSON (builtins.readFile ../flake.lock);
inherit (lock.nodes.flake-compat.locked) rev narHash;
flake-compat = fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${rev}.tar.gz";
sha256 = narHash;
};
in
import flake-compat { src = ../..; }
import flake-compat { src = ../.; }

View File

@ -21,3 +21,6 @@ set(Launcher_Portable_File "program_info/portable.txt" PARENT_SCOPE)
configure_file(org.polymc.PolyMC.desktop.in org.polymc.PolyMC.desktop)
configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml)
configure_file(polymc.rc.in polymc.rc @ONLY)
configure_file(polymc.manifest.in polymc.manifest @ONLY)
configure_file(polymc.ico polymc.ico COPYONLY)

View File

@ -1,29 +1,171 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_68_227)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M924 356.627C924 346.845 924.004 337.062 923.944 327.279C923.895 319.038 923.8 310.799 923.576 302.562C923.092 284.609 922.033 266.502 918.84 248.749C915.602 230.741 910.314 213.98 901.981 197.617C893.789 181.534 883.088 166.817 870.32 154.058C857.555 141.299 842.834 130.605 826.746 122.418C810.366 114.083 793.587 108.797 775.558 105.56C757.803 102.372 739.691 101.315 721.738 100.83C713.495 100.607 705.253 100.513 697.008 100.462C687.22 100.402 677.432 100.407 667.644 100.407L553.997 100H468.997L357.361 100.407C347.554 100.407 337.747 100.402 327.94 100.462C319.678 100.513 311.42 100.607 303.161 100.83C285.167 101.315 267.014 102.373 249.217 105.565C231.164 108.801 214.36 114.085 197.958 122.414C181.835 130.602 167.083 141.297 154.291 154.058C141.501 166.816 130.78 181.53 122.573 197.61C114.217 213.981 108.919 230.752 105.673 248.77C102.477 266.516 101.418 284.617 100.931 302.562C100.709 310.8 100.613 319.039 100.563 327.279C100.503 337.063 100 349.216 100 358.999L100.003 469.089L100 554.998L100.508 667.427C100.508 677.223 100.504 687.019 100.563 696.815C100.613 705.067 100.709 713.317 100.932 721.566C101.418 739.542 102.479 757.675 105.678 775.452C108.923 793.484 114.22 810.269 122.569 826.653C130.777 842.759 141.5 857.495 154.291 870.272C167.082 883.049 181.83 893.757 197.95 901.956C214.362 910.302 231.174 915.595 249.238 918.836C267.027 922.029 285.174 923.088 303.161 923.573C311.42 923.796 319.679 923.891 327.941 923.941C337.748 924.001 347.554 923.997 357.361 923.997L470.006 924H555.217L667.644 923.996C677.432 923.996 687.22 924.001 697.008 923.941C705.253 923.891 713.495 923.796 721.738 923.573C739.698 923.087 757.816 922.027 775.579 918.832C793.597 915.591 810.368 910.3 826.739 901.959C842.831 893.761 857.554 883.051 870.32 870.272C883.086 857.497 893.786 842.763 901.978 826.66C910.316 810.268 915.604 793.475 918.844 775.431C922.034 757.661 923.092 739.535 923.577 721.566C923.8 713.316 923.895 705.066 923.944 696.815C924.005 687.019 924 677.223 924 667.427C924 667.427 923.994 556.983 923.994 554.998V468.999C923.994 467.533 924 356.627 924 356.627Z" fill="url(#paint0_linear_68_227)"/>
<svg
width="1024"
height="1024"
viewBox="0 0 1024 1024"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g filter="url(#filter0_d_102_69)">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M924 354.627C924 344.845 924.004 335.062 923.944 325.279C923.895 317.038 923.8 308.799 923.576 300.562C923.092 282.609 922.033 264.502 918.84 246.749C915.602 228.741 910.314 211.98 901.981 195.617C893.789 179.534 883.088 164.817 870.32 152.058C857.555 139.299 842.834 128.605 826.746 120.418C810.366 112.083 793.587 106.797 775.558 103.56C757.803 100.372 739.691 99.315 721.738 98.83C713.495 98.607 705.253 98.513 697.008 98.462C687.22 98.402 677.432 98.407 667.644 98.407L553.997 98H468.997L357.361 98.407C347.554 98.407 337.747 98.402 327.94 98.462C319.678 98.513 311.42 98.607 303.161 98.83C285.167 99.315 267.014 100.373 249.217 103.565C231.164 106.801 214.36 112.085 197.958 120.414C181.835 128.602 167.083 139.297 154.291 152.058C141.501 164.816 130.78 179.53 122.573 195.61C114.217 211.981 108.919 228.752 105.673 246.77C102.477 264.516 101.418 282.617 100.931 300.562C100.709 308.8 100.613 317.039 100.563 325.279C100.503 335.063 100 347.216 100 356.999L100.003 467.089L100 552.998L100.508 665.427C100.508 675.223 100.504 685.019 100.563 694.815C100.613 703.067 100.709 711.317 100.932 719.566C101.418 737.542 102.479 755.675 105.678 773.452C108.923 791.484 114.22 808.269 122.569 824.653C130.777 840.759 141.5 855.495 154.291 868.272C167.082 881.049 181.83 891.757 197.95 899.956C214.362 908.302 231.174 913.595 249.238 916.836C267.027 920.029 285.174 921.088 303.161 921.573C311.42 921.796 319.679 921.891 327.941 921.941C337.748 922.001 347.554 921.997 357.361 921.997L470.006 922H555.217L667.644 921.996C677.432 921.996 687.22 922.001 697.008 921.941C705.253 921.891 713.495 921.796 721.738 921.573C739.698 921.087 757.816 920.027 775.579 916.832C793.597 913.591 810.368 908.3 826.739 899.959C842.831 891.761 857.554 881.051 870.32 868.272C883.086 855.497 893.786 840.763 901.978 824.66C910.316 808.268 915.604 791.475 918.844 773.431C922.034 755.661 923.092 737.535 923.577 719.566C923.8 711.316 923.895 703.066 923.944 694.815C924.005 685.019 924 675.223 924 665.427C924 665.427 923.994 554.983 923.994 552.998V466.999C923.994 465.533 924 354.627 924 354.627Z"
fill="url(#paint0_linear_102_69)"
/>
</g>
<path d="M338.18 779.507C338.18 779.507 338.18 653.214 512.004 653.214C685.874 653.214 685.827 779.507 685.827 779.507H338.18Z" fill="#765338"/>
<path d="M512.007 653.221L338.183 779.514L230.752 448.878L512.007 653.221Z" fill="#B7835A"/>
<path d="M512.007 653.221L793.263 448.878L685.831 779.514L512.007 653.221Z" fill="#5B422D"/>
<path d="M524.909 662.576L512.005 671.951L499.101 662.576C499.101 653.201 512.005 653.201 512.005 653.201C512.005 653.201 524.909 653.201 524.909 662.576Z" fill="#72B147"/>
<path d="M512.007 653.221C512.007 653.221 512.007 448.878 793.263 448.878L785.288 473.423L752.741 515.819L720.194 520.716L687.647 563.113L655.1 568.009L622.553 610.406L590.006 615.302L557.459 657.699L524.912 662.595L512.007 653.221Z" fill="#5A9A30"/>
<path d="M499.102 662.576L466.555 657.679L434.008 615.283L401.461 610.386L368.914 567.99L336.367 563.093L303.82 520.697L271.273 515.8L238.726 473.403L230.751 448.859C512.007 448.859 512.007 653.202 512.007 653.202L499.102 662.576Z" fill="#88B858"/>
<path d="M230.75 448.861L512.006 653.204L793.262 448.861L512.006 244.518L230.75 448.861Z" fill="url(#paint1_linear_68_227)"/>
<mask
id="mask0_102_69"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="100"
y="98"
width="824"
height="824"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M924 354.627C924 344.845 924.004 335.062 923.944 325.279C923.895 317.038 923.8 308.799 923.576 300.562C923.092 282.609 922.033 264.502 918.84 246.749C915.602 228.741 910.314 211.98 901.981 195.617C893.789 179.534 883.088 164.817 870.32 152.058C857.555 139.299 842.834 128.605 826.746 120.418C810.366 112.083 793.587 106.797 775.558 103.56C757.803 100.372 739.691 99.315 721.738 98.83C713.495 98.607 705.253 98.513 697.008 98.462C687.22 98.402 677.432 98.407 667.644 98.407L553.997 98H468.997L357.361 98.407C347.554 98.407 337.747 98.402 327.94 98.462C319.678 98.513 311.42 98.607 303.161 98.83C285.167 99.315 267.014 100.373 249.217 103.565C231.164 106.801 214.36 112.085 197.958 120.414C181.835 128.602 167.083 139.297 154.291 152.058C141.501 164.816 130.78 179.53 122.573 195.61C114.217 211.981 108.919 228.752 105.673 246.77C102.477 264.516 101.418 282.617 100.931 300.562C100.709 308.8 100.613 317.039 100.563 325.279C100.503 335.063 100 347.216 100 356.999L100.003 467.089L100 552.998L100.508 665.427C100.508 675.223 100.504 685.019 100.563 694.815C100.613 703.067 100.709 711.317 100.932 719.566C101.418 737.542 102.479 755.675 105.678 773.452C108.923 791.484 114.22 808.269 122.569 824.653C130.777 840.759 141.5 855.495 154.291 868.272C167.082 881.049 181.83 891.757 197.95 899.956C214.362 908.302 231.174 913.595 249.238 916.836C267.027 920.029 285.174 921.088 303.161 921.573C311.42 921.796 319.679 921.891 327.941 921.941C337.748 922.001 347.554 921.997 357.361 921.997L470.006 922H555.217L667.644 921.996C677.432 921.996 687.22 922.001 697.008 921.941C705.253 921.891 713.495 921.796 721.738 921.573C739.698 921.087 757.816 920.027 775.579 916.832C793.597 913.591 810.368 908.3 826.739 899.959C842.831 891.761 857.554 881.051 870.32 868.272C883.086 855.497 893.786 840.763 901.978 824.66C910.316 808.268 915.604 791.475 918.844 773.431C922.034 755.661 923.092 737.535 923.577 719.566C923.8 711.316 923.895 703.066 923.944 694.815C924.005 685.019 924 675.223 924 665.427C924 665.427 923.994 554.983 923.994 552.998V466.999C923.994 465.533 924 354.627 924 354.627Z"
fill="white"
/>
</mask>
<g mask="url(#mask0_102_69)">
<rect
x="42"
y="36"
width="914"
height="914"
fill="url(#paint1_linear_102_69)"
/>
<g filter="url(#filter1_b_102_69)">
<rect
x="100"
y="98"
width="824"
height="824"
rx="126"
fill="black"
fill-opacity="0.01"
/>
</g>
</g>
<path
d="M367.15 732.923C367.15 732.923 367.15 627.678 512.003 627.678C656.895 627.678 656.856 732.923 656.856 732.923H367.15Z"
fill="#765338"
/>
<path
d="M512.006 627.684L367.153 732.929L277.626 457.399L512.006 627.684Z"
fill="#B7835A"
/>
<path
d="M512.006 627.684L746.385 457.399L656.859 732.929L512.006 627.684Z"
fill="#5B422D"
/>
<path
d="M522.757 635.48L512.004 643.292L501.25 635.48C501.25 627.667 512.004 627.667 512.004 627.667C512.004 627.667 522.757 627.667 522.757 635.48Z"
fill="#72B147"
/>
<path
d="M512.006 627.684C512.006 627.684 512.006 457.399 746.385 457.399L739.74 477.852L712.617 513.183L685.495 517.263L658.372 552.594L631.25 556.674L604.127 592.005L577.005 596.085L549.882 631.416L522.76 635.496L512.006 627.684Z"
fill="#5A9A30"
/>
<path
d="M501.252 635.48L474.129 631.399L447.007 596.069L419.884 591.988L392.762 556.658L365.639 552.578L338.517 517.247L311.394 513.167L284.272 477.836L277.626 457.382C512.006 457.382 512.006 627.668 512.006 627.668L501.252 635.48Z"
fill="#88B858"
/>
<path
d="M277.625 457.384L512.005 627.67L746.385 457.384L512.005 287.098L277.625 457.384Z"
fill="url(#paint2_linear_102_69)"
/>
<defs>
<filter id="filter0_d_68_227" x="90" y="100" width="844" height="844" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<filter
id="filter0_d_102_69"
x="90"
y="98"
width="844"
height="844"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="10" />
<feGaussianBlur stdDeviation="5" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_68_227"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_68_227" result="shape"/>
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_102_69"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_102_69"
result="shape"
/>
</filter>
<linearGradient id="paint0_linear_68_227" x1="512" y1="100" x2="512" y2="924" gradientUnits="userSpaceOnUse">
<stop stop-color="#292929"/>
<stop offset="1" stop-color="#171717"/>
<filter
id="filter1_b_102_69"
x="89.1269"
y="87.1269"
width="845.746"
height="845.746"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImage" stdDeviation="5.43656" />
<feComposite
in2="SourceAlpha"
operator="in"
result="effect1_backgroundBlur_102_69"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_backgroundBlur_102_69"
result="shape"
/>
</filter>
<linearGradient
id="paint0_linear_102_69"
x1="-181.14"
y1="98"
x2="-181.14"
y2="1484.28"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="0.489516" stop-color="#EFEFEF" />
<stop offset="1" stop-color="#C0C0C0" />
</linearGradient>
<linearGradient id="paint1_linear_68_227" x1="371.378" y1="346.687" x2="652.619" y2="551.034" gradientUnits="userSpaceOnUse">
<linearGradient
id="paint1_linear_102_69"
x1="928.377"
y1="992.826"
x2="928.377"
y2="134.072"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F6F3F3" />
<stop offset="1" stop-color="white" />
</linearGradient>
<linearGradient
id="paint2_linear_102_69"
x1="394.815"
y1="372.239"
x2="629.182"
y2="542.528"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#88B858" />
<stop offset="0.5" stop-color="#72B147" />
<stop offset="1" stop-color="#5A9A30" />

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -28,23 +28,23 @@
<screenshots>
<screenshot type="default">
<caption>The main PolyMC window</caption>
<image type="source" width="1011" height="994">https://polymc.org/img/screenshots/LauncherDark.png</image>
<image type="source" width="931" height="759">https://polymc.org/img/screenshots/LauncherDark.png</image>
</screenshot>
<screenshot>
<caption>Modpack installation</caption>
<image type="source" width="911" height="682">https://polymc.org/img/screenshots/ModpackInstallDark.png</image>
<image type="source" width="860" height="848">https://polymc.org/img/screenshots/ModpackInstallDark.png</image>
</screenshot>
<screenshot>
<caption>Mod installation</caption>
<image type="source" width="987" height="723">https://polymc.org/img/screenshots/ModInstallDark.png</image>
<image type="source" width="1018" height="858">https://polymc.org/img/screenshots/ModInstallDark.png</image>
</screenshot>
<screenshot>
<caption>Instance management</caption>
<image type="source" width="902" height="920">https://polymc.org/img/screenshots/PropertiesDark.png</image>
<image type="source" width="777" height="693">https://polymc.org/img/screenshots/PropertiesDark.png</image>
</screenshot>
<screenshot>
<caption>Cat :)</caption>
<image type="source" width="1011" height="994">https://polymc.org/img/screenshots/LauncherCatDark.png</image>
<image type="source" width="931" height="759">https://polymc.org/img/screenshots/LauncherCatDark.png</image>
</screenshot>
</screenshots>
<releases>

Binary file not shown.

View File

@ -1,6 +1,6 @@
<?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">
<assemblyIdentity name="PolyMC.Application.1" type="win32" version="1.0.0.0" />
<assemblyIdentity name="PolyMC.Application.1" type="win32" version="@Launcher_RELEASE_VERSION_NAME4@" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
@ -16,15 +16,13 @@
<description>Custom Minecraft launcher for managing multiple installs.</description>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below indicates app support for Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!--The ID below indicates app support for Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--The ID below indicates app support for Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!--The ID below indicates app support for Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!--The ID below indicates app support for Windows 10 -->
<!--The ID below indicates app support for Windows 10/11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>

View File

@ -7,7 +7,7 @@ IDI_ICON1 ICON DISCARDABLE "polymc.ico"
1 RT_MANIFEST "polymc.manifest"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
FILEVERSION @Launcher_RELEASE_VERSION_NAME4_COMMA@
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
BEGIN
@ -17,9 +17,9 @@ BEGIN
BEGIN
VALUE "CompanyName", "MultiMC & PolyMC Contributors"
VALUE "FileDescription", "PolyMC"
VALUE "FileVersion", "1.0.0.0"
VALUE "FileVersion", "@Launcher_RELEASE_VERSION_NAME4@"
VALUE "ProductName", "PolyMC"
VALUE "ProductVersion", "1"
VALUE "ProductVersion", "@Launcher_RELEASE_VERSION_NAME4@"
END
END
BLOCK "VarFileInfo"

View File

@ -141,12 +141,18 @@ Section "PolyMC"
SectionEnd
Section "Start Menu Shortcuts" SHORTCUTS
Section "Start Menu Shortcut" SM_SHORTCUTS
CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0
SectionEnd
Section "Desktop Shortcut" DESKTOP_SHORTCUTS
CreateShortcut "$DESKTOP\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0
SectionEnd
;--------------------------------
; Uninstaller
@ -215,6 +221,7 @@ Section "Uninstall"
RMDir /r $INSTDIR\styles
Delete "$SMPROGRAMS\PolyMC.lnk"
Delete "$DESKTOP\PolyMC.lnk"
RMDir "$INSTDIR"
@ -228,6 +235,7 @@ Function .onInit
${GetParameters} $R0
${GetOptions} $R0 "/NoShortcuts" $R1
${IfNot} ${Errors}
!insertmacro UnselectSection ${SHORTCUTS}
!insertmacro UnselectSection ${SM_SHORTCUTS}
!insertmacro UnselectSection ${DESKTOP_SHORTCUTS}
${EndIf}
FunctionEnd