Merge branch 'PolyMC:develop' into develop

This commit is contained in:
OldWorldOrdr 2022-06-20 11:02:40 -04:00 committed by GitHub
commit 6103d86a47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 1514 additions and 500 deletions

View File

@ -1,19 +0,0 @@
name: Backport PR to stable
on:
pull_request:
branches: [ develop ]
types: [ closed ]
jobs:
release_pull_request:
if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'backport')
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Backport PR by cherry-pick-ing
uses: Nathanmalnoury/gh-backport-action@master
with:
pr_branch: 'stable'
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -28,7 +28,7 @@ jobs:
name: "Windows-x86_64" name: "Windows-x86_64"
msystem: mingw64 msystem: mingw64
- os: macos-11 - os: macos-12
macosx_deployment_target: 10.13 macosx_deployment_target: 10.13
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -206,7 +206,7 @@ jobs:
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cd ${{ env.INSTALL_DIR }} cd ${{ env.INSTALL_DIR }}
makensis -NOCD "-DVERSION=${{ env.VERSION }}" "-DMUI_ICON=${{ github.workspace }}/program_info/polymc.ico" "-XOutFile ${{ github.workspace }}/PolyMC-Setup.exe" "${{ github.workspace }}/program_info/win_install.nsi" makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
- name: Package (Linux) - name: Package (Linux)
if: runner.os == 'Linux' && matrix.appimage != true if: runner.os == 'Linux' && matrix.appimage != true

View File

@ -1,61 +0,0 @@
name: Comment on pull request
on:
workflow_run:
workflows: ['Build Application']
types: [completed]
jobs:
pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v5
with:
# This snippet is public-domain, taken from
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
script: |
async function upsertComment(owner, repo, issue_number, purpose, body) {
const {data: comments} = await github.rest.issues.listComments(
{owner, repo, issue_number});
const marker = `<!-- bot: ${purpose} -->`;
body = marker + "\n" + body;
const existing = comments.filter((c) => c.body.includes(marker));
if (existing.length > 0) {
const last = existing[existing.length - 1];
core.info(`Updating comment ${last.id}`);
await github.rest.issues.updateComment({
owner, repo,
body,
comment_id: last.id,
});
} else {
core.info(`Creating a comment in issue / PR #${issue_number}`);
await github.rest.issues.createComment({issue_number, body, owner, repo});
}
}
const {owner, repo} = context.repo;
const run_id = ${{github.event.workflow_run.id}};
const pull_requests = ${{ toJSON(github.event.workflow_run.pull_requests) }};
if (!pull_requests.length) {
return core.error("This workflow doesn't match any pull requests!");
}
const artifacts = await github.paginate(
github.rest.actions.listWorkflowRunArtifacts, {owner, repo, run_id});
if (!artifacts.length) {
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
for (const art of artifacts) {
body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
core.info("Review thread message body:", body);
for (const pr of pull_requests) {
await upsertComment(owner, repo, pr.number,
"nightly-link", body);
}

View File

@ -45,8 +45,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
# set CXXFLAGS for build targets # set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_DEBUG "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
option(ENABLE_LTO "Enable Link Time Optimization" off) option(ENABLE_LTO "Enable Link Time Optimization" off)
@ -73,8 +72,8 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 1) set(Launcher_VERSION_MAJOR 1)
set(Launcher_VERSION_MINOR 3) set(Launcher_VERSION_MINOR 4)
set(Launcher_VERSION_HOTFIX 1) set(Launcher_VERSION_HOTFIX 0)
# Build number # Build number
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")

View File

@ -96,3 +96,16 @@ If you do not agree with these terms and conditions, then remove the associated
All launcher code is available under the GPL-3.0-only license. All launcher code is available under the GPL-3.0-only license.
The logo and related assets are under the CC BY-SA 4.0 license. The logo and related assets are under the CC BY-SA 4.0 license.
## Sponsors
Thank you to all our generous backers over at Open Collective! Support PolyMC by [becoming a backer](https://opencollective.com/polymc).
[![OpenCollective Backers](https://opencollective.com/polymc/backers.svg?width=890&limit=1000)](https://opencollective.com/polymc#backers)
Also, thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/).
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/opensource/)
Additionally, thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes!
<a href="https://www.macstadium.com"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="Powered by MacStadium" width="300"></a>

View File

@ -34,11 +34,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1653326962, "lastModified": 1654665288,
"narHash": "sha256-W8feCYqKTsMre4nAEpv5Kx1PVFC+hao/LwqtB2Wci/8=", "narHash": "sha256-7blJpfoZEu7GKb84uh3io/5eSJNdaagXD9d15P9iQMs=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "41cc1d5d9584103be4108c1815c350e07c807036", "rev": "43ecbe7840d155fa933ee8a500fb00dbbc651fc8",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -154,6 +154,7 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
fflush(stderr); fflush(stderr);
} }
#ifdef LAUNCHER_WITH_UPDATER
QString getIdealPlatform(QString currentPlatform) { QString getIdealPlatform(QString currentPlatform) {
auto info = Sys::getKernelInfo(); auto info = Sys::getKernelInfo();
switch(info.kernelType) { switch(info.kernelType) {
@ -192,6 +193,7 @@ QString getIdealPlatform(QString currentPlatform) {
} }
return currentPlatform; return currentPlatform;
} }
#endif
} }
@ -711,6 +713,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Custom MSA credentials // Custom MSA credentials
m_settings->registerSetting("MSAClientIDOverride", ""); m_settings->registerSetting("MSAClientIDOverride", "");
m_settings->registerSetting("CFKeyOverride", ""); m_settings->registerSetting("CFKeyOverride", "");
m_settings->registerSetting("UserAgentOverride", "");
// Init page provider // Init page provider
{ {
@ -753,6 +756,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Translations loaded."; qDebug() << "<> Translations loaded.";
} }
#ifdef LAUNCHER_WITH_UPDATER
// initialize the updater // initialize the updater
if(BuildConfig.UPDATER_ENABLED) if(BuildConfig.UPDATER_ENABLED)
{ {
@ -762,6 +766,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
qDebug() << "<> Updater started."; qDebug() << "<> Updater started.";
} }
#endif
// Instance icons // Instance icons
{ {
@ -874,6 +879,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_mcedit.reset(new MCEditTool(m_settings)); m_mcedit.reset(new MCEditTool(m_settings));
} }
#ifdef Q_OS_MACOS
connect(this, &Application::clickedOnDock, [this]() {
this->showMainWindow();
});
#endif
connect(this, &Application::aboutToQuit, [this](){ connect(this, &Application::aboutToQuit, [this](){
if(m_instances) if(m_instances)
{ {
@ -957,6 +968,21 @@ bool Application::createSetupWizard()
return false; return false;
} }
bool Application::event(QEvent* event) {
#ifdef Q_OS_MACOS
if (event->type() == QEvent::ApplicationStateChange) {
auto ev = static_cast<QApplicationStateChangeEvent*>(event);
if (m_prevAppState == Qt::ApplicationActive
&& ev->applicationState() == Qt::ApplicationActive) {
emit clickedOnDock();
}
m_prevAppState = ev->applicationState();
}
#endif
return QApplication::event(event);
}
void Application::setupWizardFinished(int status) void Application::setupWizardFinished(int status)
{ {
qDebug() << "Wizard result =" << status; qDebug() << "Wizard result =" << status;
@ -1386,7 +1412,9 @@ MainWindow* Application::showMainWindow(bool minimized)
} }
m_mainWindow->checkInstancePathForProblems(); m_mainWindow->checkInstancePathForProblems();
#ifdef LAUNCHER_WITH_UPDATER
connect(this, &Application::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged); connect(this, &Application::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged);
#endif
connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose); connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose);
m_openWindows++; m_openWindows++;
} }
@ -1556,3 +1584,24 @@ QString Application::getCurseKey()
return BuildConfig.CURSEFORGE_API_KEY; return BuildConfig.CURSEFORGE_API_KEY;
} }
QString Application::getUserAgent()
{
QString uaOverride = m_settings->get("UserAgentOverride").toString();
if (!uaOverride.isEmpty()) {
return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString());
}
return BuildConfig.USER_AGENT;
}
QString Application::getUserAgentUncached()
{
QString uaOverride = m_settings->get("UserAgentOverride").toString();
if (!uaOverride.isEmpty()) {
uaOverride += " (Uncached)";
return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString());
}
return BuildConfig.USER_AGENT_UNCACHED;
}

View File

@ -42,7 +42,10 @@
#include <QIcon> #include <QIcon>
#include <QDateTime> #include <QDateTime>
#include <QUrl> #include <QUrl>
#ifdef LAUNCHER_WITH_UPDATER
#include <updater/GoUpdate.h> #include <updater/GoUpdate.h>
#endif
#include <BaseInstance.h> #include <BaseInstance.h>
@ -94,6 +97,8 @@ public:
Application(int &argc, char **argv); Application(int &argc, char **argv);
virtual ~Application(); virtual ~Application();
bool event(QEvent* event) override;
std::shared_ptr<SettingsObject> settings() const { std::shared_ptr<SettingsObject> settings() const {
return m_settings; return m_settings;
} }
@ -156,6 +161,8 @@ public:
QString getMSAClientID(); QString getMSAClientID();
QString getCurseKey(); QString getCurseKey();
QString getUserAgent();
QString getUserAgentUncached();
/// this is the root of the 'installation'. Used for automatic updates /// this is the root of the 'installation'. Used for automatic updates
const QString &root() { const QString &root() {
@ -181,6 +188,10 @@ signals:
void globalSettingsAboutToOpen(); void globalSettingsAboutToOpen();
void globalSettingsClosed(); void globalSettingsClosed();
#ifdef Q_OS_MACOS
void clickedOnDock();
#endif
public slots: public slots:
bool launch( bool launch(
InstancePtr instance, InstancePtr instance,
@ -238,6 +249,10 @@ private:
QString m_rootPath; QString m_rootPath;
Status m_status = Application::StartingUp; Status m_status = Application::StartingUp;
#ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
#endif
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams // used on Windows to attach the standard IO streams
bool consoleAttached = false; bool consoleAttached = false;

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -59,7 +60,11 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("lastLaunchTime", 0); m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0); m_settings->registerSetting("lastTimePlayed", 0);
m_settings->registerSetting("InstanceType", "OneSix");
// NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
// a locally stored instance
if (!m_settings->getSetting("InstanceType"))
m_settings->registerSetting("InstanceType", "");
// Custom Commands // Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
@ -76,6 +81,14 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr); m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr);
// Managed Packs
m_settings->registerSetting("ManagedPack", false);
m_settings->registerSetting("ManagedPackType", "");
m_settings->registerSetting("ManagedPackID", "");
m_settings->registerSetting("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", "");
} }
QString BaseInstance::getPreLaunchCommand() QString BaseInstance::getPreLaunchCommand()
@ -93,6 +106,46 @@ QString BaseInstance::getPostExitCommand()
return settings()->get("PostExitCommand").toString(); return settings()->get("PostExitCommand").toString();
} }
bool BaseInstance::isManagedPack()
{
return settings()->get("ManagedPack").toBool();
}
QString BaseInstance::getManagedPackType()
{
return settings()->get("ManagedPackType").toString();
}
QString BaseInstance::getManagedPackID()
{
return settings()->get("ManagedPackID").toString();
}
QString BaseInstance::getManagedPackName()
{
return settings()->get("ManagedPackName").toString();
}
QString BaseInstance::getManagedPackVersionID()
{
return settings()->get("ManagedPackVersionID").toString();
}
QString BaseInstance::getManagedPackVersionName()
{
return settings()->get("ManagedPackVersionName").toString();
}
void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version)
{
settings()->set("ManagedPack", true);
settings()->set("ManagedPackType", type);
settings()->set("ManagedPackID", id);
settings()->set("ManagedPackName", name);
settings()->set("ManagedPackVersionID", versionId);
settings()->set("ManagedPackVersionName", version);
}
int BaseInstance::getConsoleMaxLines() const int BaseInstance::getConsoleMaxLines() const
{ {
auto lineSetting = settings()->getSetting("ConsoleMaxLines"); auto lineSetting = settings()->getSetting("ConsoleMaxLines");

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -139,6 +140,14 @@ public:
QString getPostExitCommand(); QString getPostExitCommand();
QString getWrapperCommand(); QString getWrapperCommand();
bool isManagedPack();
QString getManagedPackType();
QString getManagedPackID();
QString getManagedPackName();
QString getManagedPackVersionID();
QString getManagedPackVersionName();
void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version);
/// guess log level from a line of game log /// guess log level from a line of game log
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
{ {

View File

@ -156,27 +156,29 @@ set(LAUNCH_SOURCES
launch/LogModel.h launch/LogModel.h
) )
# Old update system if (Launcher_UPDATER_BASE)
set(UPDATE_SOURCES set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_WITH_UPDATER ${Launcher_APP_BINARY_DEFS}")
updater/GoUpdate.h # Old update system
updater/GoUpdate.cpp set(UPDATE_SOURCES
updater/UpdateChecker.h updater/GoUpdate.h
updater/UpdateChecker.cpp updater/GoUpdate.cpp
updater/DownloadTask.h updater/UpdateChecker.h
updater/DownloadTask.cpp updater/UpdateChecker.cpp
) updater/DownloadTask.h
updater/DownloadTask.cpp
add_unit_test(UpdateChecker
SOURCES updater/UpdateChecker_test.cpp
LIBS Launcher_logic
DATA updater/testdata
) )
add_unit_test(DownloadTask add_unit_test(UpdateChecker
SOURCES updater/DownloadTask_test.cpp SOURCES updater/UpdateChecker_test.cpp
LIBS Launcher_logic LIBS Launcher_logic
DATA updater/testdata DATA updater/testdata
) )
add_unit_test(DownloadTask
SOURCES updater/DownloadTask_test.cpp
LIBS Launcher_logic
DATA updater/testdata
)
endif()
# Backend for the news bar... there's usually no news. # Backend for the news bar... there's usually no news.
set(NEWS_SOURCES set(NEWS_SOURCES

View File

@ -454,4 +454,47 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
return false; return false;
#endif #endif
} }
QStringList listFolderPaths(QDir root)
{
auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); };
QStringList entries;
root.refresh();
for (auto entry : root.entryInfoList(QDir::Filter::Files)) {
entries.append(createAbsPath(entry));
}
for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) {
entries.append(listFolderPaths(createAbsPath(entry)));
}
return entries;
}
bool overrideFolder(QString overwritten_path, QString override_path)
{
if (!FS::ensureFolderPathExists(overwritten_path))
return false;
QStringList paths_to_override;
QDir root_override (override_path);
for (auto file : listFolderPaths(root_override)) {
QString destination = file;
destination.replace(override_path, overwritten_path);
qDebug() << QString("Applying override %1 in %2").arg(file, destination);
if (QFile::exists(destination))
QFile::remove(destination);
if (!QFile::rename(file, destination)) {
qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination);
return false;
}
}
return true;
}
} }

View File

@ -124,4 +124,8 @@ QString getDesktopDir();
// call it *name* and assign it the icon *icon* // call it *name* and assign it the icon *icon*
// return true if operation succeeded // return true if operation succeeded
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation); bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
// Overrides one folder with the contents of another, preserving items exclusive to the first folder
// Equivalent to doing QDir::rename, but allowing for overrides
bool overrideFolder(QString overwritten_path, QString override_path);
} }

View File

@ -582,10 +582,10 @@ void InstanceImportTask::processMultiMC()
emitSucceeded(); emitSucceeded();
} }
// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth() void InstanceImportTask::processModrinth()
{ {
std::vector<Modrinth::File> files; std::vector<Modrinth::File> files;
std::vector<Modrinth::File> non_whitelisted_files;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
try { try {
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
@ -600,26 +600,30 @@ void InstanceImportTask::processModrinth()
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json"); auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false; bool had_optional = false;
for (auto& modInfo : jsonFiles) { for (auto modInfo : jsonFiles) {
Modrinth::File file; Modrinth::File file;
file.path = Json::requireString(modInfo, "path"); file.path = Json::requireString(modInfo, "path");
auto env = Json::ensureObject(modInfo, "env"); auto env = Json::ensureObject(modInfo, "env");
QString support = Json::ensureString(env, "client", "unsupported"); // 'env' field is optional
if (support == "unsupported") { if (!env.isEmpty()) {
continue; QString support = Json::ensureString(env, "client", "unsupported");
} else if (support == "optional") { if (support == "unsupported") {
// TODO: Make a review dialog for choosing which ones the user wants! continue;
if (!had_optional) { } else if (support == "optional") {
had_optional = true; // TODO: Make a review dialog for choosing which ones the user wants!
auto info = CustomMessageBox::selectable( if (!had_optional) {
m_parent, tr("Optional mod detected!"), had_optional = true;
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information); auto info = CustomMessageBox::selectable(
info->exec(); m_parent, tr("Optional mod detected!"),
} tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
QMessageBox::Information);
info->exec();
}
if (file.path.endsWith(".jar")) if (file.path.endsWith(".jar"))
file.path += ".disabled"; file.path += ".disabled";
}
} }
QJsonObject hashes = Json::requireObject(modInfo, "hashes"); QJsonObject hashes = Json::requireObject(modInfo, "hashes");
@ -640,40 +644,31 @@ void InstanceImportTask::processModrinth()
} }
file.hash = QByteArray::fromHex(hash.toLatin1()); file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm; file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces) // (as Modrinth seems to incorrectly handle spaces)
auto download_arr = Json::ensureArray(modInfo, "downloads");
for(auto download : download_arr) {
qWarning() << download.toString();
bool is_last = download.toString() == download_arr.last().toString();
file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path); auto download_url = QUrl(download.toString());
if (!file.download.isValid()) { if (!download_url.isValid()) {
qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(file.download.toString(), file.path); qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL")
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); .arg(download_url.toString(), file.path);
} if(is_last && file.downloads.isEmpty())
else if (!Modrinth::validateDownloadUrl(file.download)) { throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
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); else {
file.downloads.push_back(download_url);
}
} }
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"); auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key(); QString name = it.key();
@ -701,16 +696,26 @@ void InstanceImportTask::processModrinth()
emitFailed(tr("Could not understand pack index:\n") + e.cause()); emitFailed(tr("Could not understand pack index:\n") + e.cause());
return; return;
} }
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
QString overridePath = FS::PathCombine(m_stagingPath, "overrides"); auto override_path = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(overridePath)) { if (QFile::exists(override_path)) {
QString mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); if (!QFile::rename(override_path, mcPath)) {
if (!QFile::rename(overridePath, mcPath)) {
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides"); emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
return; return;
} }
} }
// Do client overrides
auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
if (QFile::exists(client_override_path)) {
if (!FS::overrideFolder(mcPath, client_override_path)) {
emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides");
return;
}
}
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath); auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
@ -735,13 +740,24 @@ void InstanceImportTask::processModrinth()
instance.saveNow(); instance.saveNow();
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for (auto &file : files) for (auto file : files)
{ {
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
qDebug() << "Will download" << file.download << "to" << path; qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
auto dl = Net::Download::makeFile(file.download, path); auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl); m_filesNetJob->addNetAction(dl);
if (file.downloads.size() > 0) {
// FIXME: This really needs to be put into a ConcurrentTask of
// MultipleOptionsTask's , once those exist :)
connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl);
dl->succeeded();
});
}
} }
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{ {

View File

@ -547,8 +547,20 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
auto instanceRoot = FS::PathCombine(m_instDir, id); auto instanceRoot = FS::PathCombine(m_instDir, id);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg")); auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
InstancePtr inst; InstancePtr inst;
// TODO: Handle incompatible instances
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); instanceSettings->registerSetting("InstanceType", "");
QString inst_type = instanceSettings->get("InstanceType").toString();
// NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix instance
if (inst_type == "OneSix" || inst_type.isEmpty())
{
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
}
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst; return inst;
} }

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -21,12 +22,14 @@
#include "Application.h" #include "Application.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods) ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed)
: m_mod(mod), m_mod_version(version), mods(mods) : m_mod(mod), m_mod_version(version), mods(mods)
{ {
m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version)); if (is_indexed) {
m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version));
addTask(m_update_task); addTask(m_update_task);
}
m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network())); m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl)); m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl));

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -29,7 +30,7 @@ class ModFolderModel;
class ModDownloadTask : public SequentialTask { class ModDownloadTask : public SequentialTask {
Q_OBJECT Q_OBJECT
public: public:
explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods); explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed);
const QString& getFilename() const { return m_mod_version.fileName; } const QString& getFilename() const { return m_mod_version.fileName; }
private: private:

View File

@ -93,7 +93,7 @@ void UpdateController::installUpdates()
qDebug() << "Installing updates."; qDebug() << "Installing updates.";
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QString finishCmd = QApplication::applicationFilePath(); QString finishCmd = QApplication::applicationFilePath();
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined (Q_OS_OPENBSD)
QString finishCmd = FS::PathCombine(m_root, BuildConfig.LAUNCHER_NAME); QString finishCmd = FS::PathCombine(m_root, BuildConfig.LAUNCHER_NAME);
#elif defined Q_OS_MAC #elif defined Q_OS_MAC
QString finishCmd = QApplication::applicationFilePath(); QString finishCmd = QApplication::applicationFilePath();

View File

@ -56,6 +56,15 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
emit iconUpdated({}); emit iconUpdated({});
} }
void IconList::sortIconList()
{
qDebug() << "Sorting icon list...";
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
return a.m_key.localeAwareCompare(b.m_key) < 0;
});
reindex();
}
void IconList::directoryChanged(const QString &path) void IconList::directoryChanged(const QString &path)
{ {
QDir new_dir (path); QDir new_dir (path);
@ -141,6 +150,8 @@ void IconList::directoryChanged(const QString &path)
emit iconUpdated(key); emit iconUpdated(key);
} }
} }
sortIconList();
} }
void IconList::fileChanged(const QString &path) void IconList::fileChanged(const QString &path)

View File

@ -71,6 +71,7 @@ private:
// hide assign op // hide assign op
IconList &operator=(const IconList &) = delete; IconList &operator=(const IconList &) = delete;
void reindex(); void reindex();
void sortIconList();
public slots: public slots:
void directoryChanged(const QString &path); void directoryChanged(const QString &path);

View File

@ -168,6 +168,8 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride); m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride); m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
m_settings->set("InstanceType", "OneSix");
m_components.reset(new PackProfile(this)); m_components.reset(new PackProfile(this));
} }
@ -1013,7 +1015,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
{ {
if (!m_loader_mod_list) if (!m_loader_mod_list)
{ {
m_loader_mod_list.reset(new ModFolderModel(modsRoot())); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), is_indexed));
m_loader_mod_list->disableInteraction(isRunning()); m_loader_mod_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
} }
@ -1024,7 +1027,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
{ {
if (!m_core_mod_list) if (!m_core_mod_list)
{ {
m_core_mod_list.reset(new ModFolderModel(coreModsDir())); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), is_indexed));
m_core_mod_list->disableInteraction(isRunning()); m_core_mod_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
} }

View File

@ -1,21 +1,42 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU General Public License as published by
* You may obtain a copy of the License at * the Free Software Foundation, version 3.
* *
* http://www.apache.org/licenses/LICENSE-2.0 * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* *
* Unless required by applicable law or agreed to in writing, software * You should have received a copy of the GNU General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and * This file incorporates work covered by the following copyright and
* limitations under the License. * permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
#include "LauncherPartLaunch.h" #include "LauncherPartLaunch.h"
#include <QStandardPaths> #include <QStandardPaths>
#include <QRegularExpression>
#include "launch/LaunchTask.h" #include "launch/LaunchTask.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -58,8 +59,6 @@ Mod::Mod(const QFileInfo& file)
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)
: m_file(mods_dir.absoluteFilePath(metadata.filename)) : m_file(mods_dir.absoluteFilePath(metadata.filename))
// It is weird, but name is not reliable for comparing with the JAR files name
// FIXME: Maybe use hash when implemented?
, m_internal_id(metadata.filename) , m_internal_id(metadata.filename)
, m_name(metadata.name) , m_name(metadata.name)
{ {
@ -131,7 +130,7 @@ auto Mod::enable(bool value) -> bool
return false; return false;
} else { } else {
path += ".disabled"; path += ".disabled";
if (!file.rename(path)) if (!file.rename(path))
return false; return false;
} }
@ -145,23 +144,29 @@ auto Mod::enable(bool value) -> bool
void Mod::setStatus(ModStatus status) void Mod::setStatus(ModStatus status)
{ {
if(m_localDetails.get()) if (m_localDetails) {
m_localDetails->status = status; m_localDetails->status = status;
} else {
m_temp_status = status;
}
} }
void Mod::setMetadata(Metadata::ModStruct* metadata) void Mod::setMetadata(Metadata::ModStruct* metadata)
{ {
if(status() == ModStatus::NoMetadata) if (status() == ModStatus::NoMetadata)
setStatus(ModStatus::Installed); setStatus(ModStatus::Installed);
if(m_localDetails.get()) if (m_localDetails) {
m_localDetails->metadata.reset(metadata); m_localDetails->metadata.reset(metadata);
} else {
m_temp_metadata.reset(metadata);
}
} }
auto Mod::destroy(QDir& index_dir) -> bool auto Mod::destroy(QDir& index_dir) -> bool
{ {
auto n = name(); auto n = name();
// FIXME: This can fail to remove the metadata if the // FIXME: This can fail to remove the metadata if the
// "DontUseModMetadata" setting is on, since there could // "ModMetadataDisabled" setting is on, since there could
// be a name mismatch! // be a name mismatch!
Metadata::remove(index_dir, n); Metadata::remove(index_dir, n);
@ -205,20 +210,36 @@ auto Mod::authors() const -> QStringList
auto Mod::status() const -> ModStatus auto Mod::status() const -> ModStatus
{ {
if (!m_localDetails)
return m_temp_status;
return details().status; return details().status;
} }
auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
{
if (m_localDetails)
return m_localDetails->metadata;
return m_temp_metadata;
}
auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
{
if (m_localDetails)
return m_localDetails->metadata;
return m_temp_metadata;
}
void Mod::finishResolvingWithDetails(std::shared_ptr<ModDetails> details) void Mod::finishResolvingWithDetails(std::shared_ptr<ModDetails> details)
{ {
m_resolving = false; m_resolving = false;
m_resolved = true; m_resolved = true;
m_localDetails = details; m_localDetails = details;
if (status() != ModStatus::NoMetadata if (m_localDetails && m_temp_metadata && m_temp_metadata->isValid()) {
&& m_temp_metadata.get() m_localDetails->metadata = m_temp_metadata;
&& m_temp_metadata->isValid() && if (status() == ModStatus::NoMetadata)
m_localDetails.get()) { setStatus(ModStatus::Installed);
m_localDetails->metadata.swap(m_temp_metadata);
} }
setStatus(m_temp_status);
} }

View File

@ -73,8 +73,8 @@ public:
auto authors() const -> QStringList; auto authors() const -> QStringList;
auto status() const -> ModStatus; auto status() const -> ModStatus;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct> { return details().metadata; }; auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
auto metadata() -> std::shared_ptr<Metadata::ModStruct> { return m_localDetails->metadata; }; auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
void setStatus(ModStatus status); void setStatus(ModStatus status);
void setMetadata(Metadata::ModStruct* metadata); void setMetadata(Metadata::ModStruct* metadata);
@ -109,6 +109,10 @@ protected:
/* If the mod has metadata, this will be filled in the constructor, and passed to /* If the mod has metadata, this will be filled in the constructor, and passed to
* the ModDetails when calling finishResolvingWithDetails */ * the ModDetails when calling finishResolvingWithDetails */
std::shared_ptr<Metadata::ModStruct> m_temp_metadata; std::shared_ptr<Metadata::ModStruct> m_temp_metadata;
/* Set the mod status while it doesn't have local details just yet */
ModStatus m_temp_status = ModStatus::NotInstalled;
std::shared_ptr<ModDetails> m_localDetails; std::shared_ptr<ModDetails> m_localDetails;
bool m_enabled = true; bool m_enabled = true;

View File

@ -1,17 +1,38 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
* /*
* Licensed under the Apache License, Version 2.0 (the "License"); * PolyMC - Minecraft Launcher
* you may not use this file except in compliance with the License. * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* You may obtain a copy of the License at * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* http://www.apache.org/licenses/LICENSE-2.0 * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* Unless required by applicable law or agreed to in writing, software * the Free Software Foundation, version 3.
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * This program is distributed in the hope that it will be useful,
* See the License for the specific language governing permissions and * but WITHOUT ANY WARRANTY; without even the implied warranty of
* limitations under the License. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
*/ * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ModFolderModel.h" #include "ModFolderModel.h"
@ -28,7 +49,7 @@
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h"
ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir) ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : QAbstractListModel(), m_dir(dir), m_is_indexed(is_indexed)
{ {
FS::ensureFolderPathExists(m_dir.absolutePath()); FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
@ -82,7 +103,7 @@ bool ModFolderModel::update()
} }
auto index_dir = indexDir(); auto index_dir = indexDir();
auto task = new ModFolderLoadTask(dir(), index_dir); auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed);
m_update = task->result(); m_update = task->result();

View File

@ -1,17 +1,38 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
* /*
* Licensed under the Apache License, Version 2.0 (the "License"); * PolyMC - Minecraft Launcher
* you may not use this file except in compliance with the License. * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* You may obtain a copy of the License at * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* http://www.apache.org/licenses/LICENSE-2.0 * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* Unless required by applicable law or agreed to in writing, software * the Free Software Foundation, version 3.
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * This program is distributed in the hope that it will be useful,
* See the License for the specific language governing permissions and * but WITHOUT ANY WARRANTY; without even the implied warranty of
* limitations under the License. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
*/ * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
@ -52,7 +73,7 @@ public:
Enable, Enable,
Toggle Toggle
}; };
ModFolderModel(const QString &dir); ModFolderModel(const QString &dir, bool is_indexed = false);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
@ -146,6 +167,7 @@ protected:
bool scheduled_update = false; bool scheduled_update = false;
bool interaction_disabled = false; bool interaction_disabled = false;
QDir m_dir; QDir m_dir;
bool m_is_indexed;
QMap<QString, int> modsIndex; QMap<QString, int> modsIndex;
QMap<int, LocalModParseTask::ResultPtr> activeTickets; QMap<int, LocalModParseTask::ResultPtr> activeTickets;
int nextResolutionTicket = 0; int nextResolutionTicket = 0;

View File

@ -1,3 +1,37 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QTest> #include <QTest>
#include <QTemporaryDir> #include <QTemporaryDir>
@ -32,8 +66,11 @@ slots:
{ {
QString folder = source; QString folder = source;
QTemporaryDir tempDir; QTemporaryDir tempDir;
ModFolderModel m(tempDir.path()); QEventLoop loop;
ModFolderModel m(tempDir.path(), true);
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
m.installMod(folder); m.installMod(folder);
loop.exec();
verify(tempDir.path()); verify(tempDir.path());
} }
@ -41,8 +78,11 @@ slots:
{ {
QString folder = source + '/'; QString folder = source + '/';
QTemporaryDir tempDir; QTemporaryDir tempDir;
ModFolderModel m(tempDir.path()); QEventLoop loop;
ModFolderModel m(tempDir.path(), true);
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
m.installMod(folder); m.installMod(folder);
loop.exec();
verify(tempDir.path()); verify(tempDir.path());
} }
} }

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ResourcePackFolderModel.h" #include "ResourcePackFolderModel.h"
ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) { ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) {

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "TexturePackFolderModel.h" #include "TexturePackFolderModel.h"
TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) { TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) {

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -22,6 +23,10 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
#ifdef Q_OS_WIN32
#include <windows.h>
#endif
LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version)
: m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version) : m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version)
{ {
@ -29,17 +34,16 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack&
if (!FS::ensureFolderPathExists(index_dir.path())) { if (!FS::ensureFolderPathExists(index_dir.path())) {
emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name));
} }
#ifdef Q_OS_WIN32
SetFileAttributesA(index_dir.path().toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
#endif
} }
void LocalModUpdateTask::executeTask() void LocalModUpdateTask::executeTask()
{ {
setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name));
if(APPLICATION->settings()->get("DontUseModMetadata").toBool()){
emitSucceeded();
return;
}
auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version); auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version);
Metadata::update(m_index_dir, pw_mod); Metadata::update(m_index_dir, pw_mod);

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -38,13 +39,13 @@
#include "Application.h" #include "Application.h"
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed)
: m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result()) : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result())
{} {}
void ModFolderLoadTask::run() void ModFolderLoadTask::run()
{ {
if (!APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { if (m_is_indexed) {
// Read metadata first // Read metadata first
getFromMetadata(); getFromMetadata();
} }
@ -53,12 +54,33 @@ void ModFolderLoadTask::run()
m_mods_dir.refresh(); m_mods_dir.refresh();
for (auto entry : m_mods_dir.entryInfoList()) { for (auto entry : m_mods_dir.entryInfoList()) {
Mod mod(entry); Mod mod(entry);
if(m_result->mods.contains(mod.internal_id())){
m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); if (mod.enabled()) {
if (m_result->mods.contains(mod.internal_id())) {
m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed);
}
else {
m_result->mods[mod.internal_id()] = mod;
m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata);
}
} }
else { else {
m_result->mods[mod.internal_id()] = mod; QString chopped_id = mod.internal_id().chopped(9);
m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata); if (m_result->mods.contains(chopped_id)) {
m_result->mods[mod.internal_id()] = mod;
auto metadata = m_result->mods[chopped_id].metadata();
if (metadata) {
mod.setMetadata(new Metadata::ModStruct(*metadata));
m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed);
m_result->mods.remove(chopped_id);
}
}
else {
m_result->mods[mod.internal_id()] = mod;
m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata);
}
} }
} }

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -55,7 +56,7 @@ public:
} }
public: public:
ModFolderLoadTask(QDir& mods_dir, QDir& index_dir); ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed);
void run(); void run();
signals: signals:
void succeeded(); void succeeded();
@ -65,5 +66,6 @@ private:
private: private:
QDir& m_mods_dir, m_index_dir; QDir& m_mods_dir, m_index_dir;
bool m_is_indexed;
ResultPtr m_result; ResultPtr m_result;
}; };

View File

@ -7,6 +7,7 @@
namespace ModPlatform { namespace ModPlatform {
class ListModel; class ListModel;
struct IndexedPack;
} }
class ModAPI { class ModAPI {
@ -35,6 +36,7 @@ class ModAPI {
}; };
virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0; virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
virtual void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) = 0;
struct VersionSearchArgs { struct VersionSearchArgs {

View File

@ -44,6 +44,12 @@ struct ModpackAuthor {
QString url; QString url;
}; };
struct DonationData {
QString id;
QString platform;
QString url;
};
struct IndexedVersion { struct IndexedVersion {
QVariant addonId; QVariant addonId;
QVariant fileId; QVariant fileId;
@ -57,6 +63,15 @@ struct IndexedVersion {
QString hash; QString hash;
}; };
struct ExtraPackData {
QList<DonationData> donate;
QString issuesUrl;
QString sourceUrl;
QString wikiUrl;
QString discordUrl;
};
struct IndexedPack { struct IndexedPack {
QVariant addonId; QVariant addonId;
Provider provider; Provider provider;
@ -69,6 +84,10 @@ struct IndexedPack {
bool versionsLoaded = false; bool versionsLoaded = false;
QVector<IndexedVersion> versions; QVector<IndexedVersion> versions;
// Don't load by default, since some modplatform don't have that info
bool extraDataLoaded = true;
ExtraPackData extraData;
}; };
} // namespace ModPlatform } // namespace ModPlatform

View File

@ -60,10 +60,11 @@ namespace ATLauncher {
static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version); static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version);
PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version) PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version)
{ {
m_support = support; m_support = support;
m_pack = pack; m_pack_name = packName;
m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), "");
m_version_name = version; m_version_name = version;
} }
@ -81,7 +82,7 @@ void PackInstallTask::executeTask()
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId(); qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network()); auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network());
auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json") auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json")
.arg(m_pack).arg(m_version_name); .arg(m_pack_safe_name).arg(m_version_name);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
@ -98,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded()
QJsonParseError parse_error {}; QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) { if(parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from ATLauncher at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response; qWarning() << response;
return; return;
} }
@ -319,7 +320,7 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
auto f = std::make_shared<VersionFile>(); auto f = std::make_shared<VersionFile>();
f->name = m_pack + " " + m_version_name + " (libraries)"; f->name = m_pack_name + " " + m_version_name + " (libraries)";
const static QMap<QString, QString> liteLoaderMap = { const static QMap<QString, QString> liteLoaderMap = {
{ "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" },
@ -465,7 +466,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<
} }
auto f = std::make_shared<VersionFile>(); auto f = std::make_shared<VersionFile>();
f->name = m_pack + " " + m_version_name; f->name = m_pack_name + " " + m_version_name;
if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) { if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) {
f->mainClass = mainClass; f->mainClass = mainClass;
} }
@ -507,9 +508,9 @@ void PackInstallTask::installConfigs()
setStatus(tr("Downloading configs...")); setStatus(tr("Downloading configs..."));
jobPtr = new NetJob(tr("Config download"), APPLICATION->network()); jobPtr = new NetJob(tr("Config download"), APPLICATION->network());
auto path = QString("Configs/%1/%2.zip").arg(m_pack).arg(m_version_name); auto path = QString("Configs/%1/%2.zip").arg(m_pack_safe_name).arg(m_version_name);
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip") auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip")
.arg(m_pack).arg(m_version_name); .arg(m_pack_safe_name).arg(m_version_name);
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path); auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
entry->setStale(true); entry->setStale(true);
@ -862,6 +863,7 @@ void PackInstallTask::install()
instance.setName(m_instName); instance.setName(m_instName);
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name);
instanceSettings->resumeSave(); instanceSettings->resumeSave();
jarmods.clear(); jarmods.clear();

View File

@ -75,7 +75,7 @@ class PackInstallTask : public InstanceTask
Q_OBJECT Q_OBJECT
public: public:
explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version); explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version);
virtual ~PackInstallTask(){} virtual ~PackInstallTask(){}
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
@ -117,7 +117,8 @@ private:
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
QByteArray response; QByteArray response;
QString m_pack; QString m_pack_name;
QString m_pack_safe_name;
QString m_version_name; QString m_version_name;
PackVersion m_version; PackVersion m_version;

View File

@ -42,6 +42,11 @@ class FlameAPI : public NetworkModAPI {
.arg(gameVersionStr); .arg(gameVersionStr);
}; };
inline auto getModInfoURL(QString& id) const -> QString override
{
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
};
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
{ {
QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : "";

View File

@ -28,6 +28,27 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
packAuthor.url = Json::requireString(author, "url"); packAuthor.url = Json::requireString(author, "url");
pack.authors.append(packAuthor); pack.authors.append(packAuthor);
} }
loadExtraPackData(pack, obj);
}
void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
auto links_obj = Json::ensureObject(obj, "links");
pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
if(pack.extraData.issuesUrl.endsWith('/'))
pack.extraData.issuesUrl.chop(1);
pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
if(pack.extraData.sourceUrl.endsWith('/'))
pack.extraData.sourceUrl.chop(1);
pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
if(pack.extraData.wikiUrl.endsWith('/'))
pack.extraData.wikiUrl.chop(1);
pack.extraDataLoaded = true;
} }
static QString enumToString(int hash_algorithm) static QString enumToString(int hash_algorithm)

View File

@ -12,6 +12,7 @@
namespace FlameMod { namespace FlameMod {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr, QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network, const shared_qobject_ptr<QNetworkAccessManager>& network,

View File

@ -6,7 +6,6 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
{ {
pack.addonId = Json::requireInteger(obj, "id"); pack.addonId = Json::requireInteger(obj, "id");
pack.name = Json::requireString(obj, "name"); pack.name = Json::requireString(obj, "name");
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
pack.description = Json::ensureString(obj, "summary", ""); pack.description = Json::ensureString(obj, "summary", "");
auto logo = Json::requireObject(obj, "logo"); auto logo = Json::requireObject(obj, "logo");
@ -46,6 +45,32 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
if (!found) { if (!found) {
throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name)); throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name));
} }
loadIndexedInfo(pack, obj);
}
void Flame::loadIndexedInfo(IndexedPack& pack, QJsonObject& obj)
{
auto links_obj = Json::ensureObject(obj, "links");
pack.extra.websiteUrl = Json::ensureString(links_obj, "websiteUrl");
if(pack.extra.websiteUrl.endsWith('/'))
pack.extra.websiteUrl.chop(1);
pack.extra.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
if(pack.extra.issuesUrl.endsWith('/'))
pack.extra.issuesUrl.chop(1);
pack.extra.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
if(pack.extra.sourceUrl.endsWith('/'))
pack.extra.sourceUrl.chop(1);
pack.extra.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
if(pack.extra.wikiUrl.endsWith('/'))
pack.extra.wikiUrl.chop(1);
pack.extraInfoLoaded = true;
} }
void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)

View File

@ -20,6 +20,13 @@ struct IndexedVersion {
QString downloadUrl; QString downloadUrl;
}; };
struct ModpackExtra {
QString websiteUrl;
QString wikiUrl;
QString issuesUrl;
QString sourceUrl;
};
struct IndexedPack struct IndexedPack
{ {
int addonId; int addonId;
@ -28,13 +35,16 @@ struct IndexedPack
QList<ModpackAuthor> authors; QList<ModpackAuthor> authors;
QString logoName; QString logoName;
QString logoUrl; QString logoUrl;
QString websiteUrl;
bool versionsLoaded = false; bool versionsLoaded = false;
QVector<IndexedVersion> versions; QVector<IndexedVersion> versions;
bool extraInfoLoaded = false;
ModpackExtra extra;
}; };
void loadIndexedPack(IndexedPack & m, QJsonObject & obj); void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
void loadIndexedInfo(IndexedPack&, QJsonObject&);
void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr); void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr);
} }

View File

@ -31,6 +31,31 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const
netJob->start(); netJob->start();
} }
void NetworkModAPI::getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack)
{
auto id_str = pack.addonId.toString();
auto netJob = new NetJob(QString("%1::ModInfo").arg(id_str), APPLICATION->network());
auto searchUrl = getModInfoURL(id_str);
auto response = new QByteArray();
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob, &NetJob::succeeded, [response, &pack, caller] {
QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for " << pack.name << " at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
return;
}
caller->infoRequestFinished(doc, pack);
});
netJob->start();
}
void NetworkModAPI::getVersions(CallerType* caller, VersionSearchArgs&& args) const void NetworkModAPI::getVersions(CallerType* caller, VersionSearchArgs&& args) const
{ {
auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(caller->debugName()).arg(args.addonId), APPLICATION->network()); auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(caller->debugName()).arg(args.addonId), APPLICATION->network());

View File

@ -5,9 +5,11 @@
class NetworkModAPI : public ModAPI { class NetworkModAPI : public ModAPI {
public: public:
void searchMods(CallerType* caller, SearchArgs&& args) const override; void searchMods(CallerType* caller, SearchArgs&& args) const override;
void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) override;
void getVersions(CallerType* caller, VersionSearchArgs&& args) const override; void getVersions(CallerType* caller, VersionSearchArgs&& args) const override;
protected: protected:
virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0; virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0;
virtual auto getModInfoURL(QString& id) const -> QString = 0;
virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0; virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0;
}; };

View File

@ -1,18 +1,37 @@
// SPDX-License-Identifier: GPL-3.0-only
/* /*
* Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> * PolyMC - Minecraft Launcher
* Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU General Public License as published by
* You may obtain a copy of the License at * the Free Software Foundation, version 3.
* *
* http://www.apache.org/licenses/LICENSE-2.0 * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* *
* Unless required by applicable law or agreed to in writing, software * You should have received a copy of the GNU General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and * This file incorporates work covered by the following copyright and
* limitations under the License. * permission notice:
*
* Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
#include "FTBPackInstallTask.h" #include "FTBPackInstallTask.h"
@ -80,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded()
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) { if(parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response; qWarning() << response;
return; return;
} }
@ -220,6 +239,7 @@ void PackInstallTask::install()
instance.setName(m_instName); instance.setName(m_instName);
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name);
instanceSettings->resumeSave(); instanceSettings->resumeSave();
emitSucceeded(); emitSucceeded();

View File

@ -76,6 +76,11 @@ class ModrinthAPI : public NetworkModAPI {
.arg(getGameVersionsArray(args.versions)); .arg(getGameVersionsArray(args.versions));
}; };
inline auto getModInfoURL(QString& id) const -> QString override
{
return BuildConfig.MODRINTH_PROD_URL + "/project/" + id;
};
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
{ {
return QString(BuildConfig.MODRINTH_PROD_URL + return QString(BuildConfig.MODRINTH_PROD_URL +

View File

@ -48,6 +48,43 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
modAuthor.name = Json::requireString(obj, "author"); modAuthor.name = Json::requireString(obj, "author");
modAuthor.url = api.getAuthorURL(modAuthor.name); modAuthor.url = api.getAuthorURL(modAuthor.name);
pack.authors.append(modAuthor); pack.authors.append(modAuthor);
// Modrinth can have more data than what's provided by the basic search :)
pack.extraDataLoaded = false;
}
void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.extraData.issuesUrl = Json::ensureString(obj, "issues_url");
if(pack.extraData.issuesUrl.endsWith('/'))
pack.extraData.issuesUrl.chop(1);
pack.extraData.sourceUrl = Json::ensureString(obj, "source_url");
if(pack.extraData.sourceUrl.endsWith('/'))
pack.extraData.sourceUrl.chop(1);
pack.extraData.wikiUrl = Json::ensureString(obj, "wiki_url");
if(pack.extraData.wikiUrl.endsWith('/'))
pack.extraData.wikiUrl.chop(1);
pack.extraData.discordUrl = Json::ensureString(obj, "discord_url");
if(pack.extraData.discordUrl.endsWith('/'))
pack.extraData.discordUrl.chop(1);
auto donate_arr = Json::ensureArray(obj, "donation_urls");
for(auto d : donate_arr){
auto d_obj = Json::requireObject(d);
ModPlatform::DonationData donate;
donate.id = Json::ensureString(d_obj, "id");
donate.platform = Json::ensureString(d_obj, "platform");
donate.url = Json::ensureString(d_obj, "url");
pack.extraData.donate.append(donate);
}
pack.extraDataLoaded = true;
} }
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,

View File

@ -25,6 +25,7 @@
namespace Modrinth { namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr, QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network, const shared_qobject_ptr<QNetworkAccessManager>& network,

View File

@ -64,8 +64,35 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
{ {
pack.extra.body = Json::ensureString(obj, "body"); pack.extra.body = Json::ensureString(obj, "body");
pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug")); pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug"));
pack.extra.issuesUrl = Json::ensureString(obj, "issues_url");
if(pack.extra.issuesUrl.endsWith('/'))
pack.extra.issuesUrl.chop(1);
pack.extra.sourceUrl = Json::ensureString(obj, "source_url"); pack.extra.sourceUrl = Json::ensureString(obj, "source_url");
if(pack.extra.sourceUrl.endsWith('/'))
pack.extra.sourceUrl.chop(1);
pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url"); pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url");
if(pack.extra.wikiUrl.endsWith('/'))
pack.extra.wikiUrl.chop(1);
pack.extra.discordUrl = Json::ensureString(obj, "discord_url");
if(pack.extra.discordUrl.endsWith('/'))
pack.extra.discordUrl.chop(1);
auto donate_arr = Json::ensureArray(obj, "donation_urls");
for(auto d : donate_arr){
auto d_obj = Json::requireObject(d);
DonationData donate;
donate.id = Json::ensureString(d_obj, "id");
donate.platform = Json::ensureString(d_obj, "platform");
donate.url = Json::ensureString(d_obj, "url");
pack.extra.donate.append(donate);
}
pack.extraInfoLoaded = true; pack.extraInfoLoaded = true;
} }
@ -95,19 +122,6 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
pack.versionsLoaded = true; pack.versionsLoaded = true;
} }
auto validateDownloadUrl(QUrl url) -> bool
{
static QSet<QString> domainWhitelist{
"cdn.modrinth.com",
"github.com",
"raw.githubusercontent.com",
"gitlab.com"
};
auto domain = url.host();
return domainWhitelist.contains(domain);
}
auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
{ {
ModpackVersion file; ModpackVersion file;
@ -137,9 +151,6 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
auto url = Json::requireString(parent, "url"); auto url = Json::requireString(parent, "url");
if(!validateDownloadUrl(url))
continue;
file.download_url = url; file.download_url = url;
if(is_primary) if(is_primary)
break; break;

View File

@ -40,6 +40,7 @@
#include <QByteArray> #include <QByteArray>
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QQueue>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include <QVector> #include <QVector>
@ -48,22 +49,32 @@ class MinecraftInstance;
namespace Modrinth { namespace Modrinth {
struct File struct File {
{
QString path; QString path;
QCryptographicHash::Algorithm hashAlgorithm; QCryptographicHash::Algorithm hashAlgorithm;
QByteArray hash; QByteArray hash;
// TODO: should this support multiple download URLs, like the JSON does? QQueue<QUrl> downloads;
QUrl download; };
struct DonationData {
QString id;
QString platform;
QString url;
}; };
struct ModpackExtra { struct ModpackExtra {
QString body; QString body;
QString projectUrl; QString projectUrl;
QString issuesUrl;
QString sourceUrl; QString sourceUrl;
QString wikiUrl; QString wikiUrl;
QString discordUrl;
QList<DonationData> donate;
}; };
struct ModpackVersion { struct ModpackVersion {

View File

@ -1,20 +1,21 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by * This program is free software: you can redistribute it and/or modify
* the Free Software Foundation, version 3. * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, version 3.
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of * This program is distributed in the hope that it will be useful,
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * but WITHOUT ANY WARRANTY; without even the implied warranty of
* GNU General Public License for more details. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. * You should have received a copy of the GNU General Public License
*/ * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <QTemporaryDir> #include <QTemporaryDir>
#include <QTest> #include <QTest>
@ -61,7 +62,7 @@ class PackwizTest : public QObject {
QVERIFY(index_dir.entryList().contains(name_mod)); QVERIFY(index_dir.entryList().contains(name_mod));
// Try without the .pw.toml at the end // Try without the .pw.toml at the end
name_mod.chop(5); name_mod.chop(8);
auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod); auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod);

View File

@ -185,13 +185,22 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1)); components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
} }
} }
else if (libraryName.startsWith("net.minecraftforge:minecraftforge:")) else
{ {
components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2)); static QStringList possibleLoaders{
} "net.minecraftforge:minecraftforge:",
else if (libraryName.startsWith("net.fabricmc:fabric-loader:")) "net.fabricmc:fabric-loader:",
{ "org.quiltmc:quilt-loader:"
components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2)); };
for (const auto& loader : possibleLoaders)
{
if (libraryName.startsWith(loader))
{
auto loaderComponent = loader.chopped(1).replace(":", ".");
components->setComponentVersion(loaderComponent, libraryName.section(':', 2));
break;
}
}
} }
} }
} }

View File

@ -116,7 +116,7 @@ void Download::executeTask()
return; return;
} }
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
if (request.url().host().contains("api.curseforge.com")) { if (request.url().host().contains("api.curseforge.com")) {
request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8()); request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());
}; };

View File

@ -3,6 +3,7 @@
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
* Copyright (C) 2022 Swirl <swurl@swurl.xyz> * Copyright (C) 2022 Swirl <swurl@swurl.xyz>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -43,6 +44,8 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QFile> #include <QFile>
#include <QHttpPart>
#include <QUrlQuery>
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = { std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
{{"0x0.st", "https://0x0.st", ""}, {{"0x0.st", "https://0x0.st", ""},
@ -71,7 +74,7 @@ void PasteUpload::executeTask()
QNetworkRequest request{QUrl(m_uploadUrl)}; QNetworkRequest request{QUrl(m_uploadUrl)};
QNetworkReply *rep{}; QNetworkReply *rep{};
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
switch (m_pasteType) { switch (m_pasteType) {
case NullPointer: { case NullPointer: {
@ -91,7 +94,7 @@ void PasteUpload::executeTask()
break; break;
} }
case Hastebin: { case Hastebin: {
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
rep = APPLICATION->network()->post(request, m_text); rep = APPLICATION->network()->post(request, m_text);
break; break;
} }

View File

@ -173,7 +173,7 @@ namespace Net {
return; return;
} }
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
if (request.url().host().contains("api.curseforge.com")) { if (request.url().host().contains("api.curseforge.com")) {
request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8()); request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());
} }

View File

@ -55,7 +55,7 @@ void ImgurAlbumCreation::executeTask()
{ {
m_state = State::Running; m_state = State::Running;
QNetworkRequest request(m_url); QNetworkRequest request(m_url);
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str()); request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
request.setRawHeader("Accept", "application/json"); request.setRawHeader("Accept", "application/json");

View File

@ -35,6 +35,7 @@
#include "ImgurUpload.h" #include "ImgurUpload.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Application.h"
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QHttpMultiPart> #include <QHttpMultiPart>
@ -56,7 +57,7 @@ void ImgurUpload::executeTask()
finished = false; finished = false;
m_state = Task::State::Running; m_state = Task::State::Running;
QNetworkRequest request(m_url); QNetworkRequest request(m_url);
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str()); request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
request.setRawHeader("Accept", "application/json"); request.setRawHeader("Accept", "application/json");

View File

@ -52,7 +52,7 @@ QString MCEditTool::getProgramPath()
#else #else
const QString mceditPath = path(); const QString mceditPath = path();
QDir mceditDir(mceditPath); QDir mceditDir(mceditPath);
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
if (mceditDir.exists("mcedit.sh")) if (mceditDir.exists("mcedit.sh"))
{ {
return mceditDir.absoluteFilePath("mcedit.sh"); return mceditDir.absoluteFilePath("mcedit.sh");

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -38,6 +39,7 @@
#include <QClipboard> #include <QClipboard>
#include <QApplication> #include <QApplication>
#include <QFileDialog> #include <QFileDialog>
#include <QStandardPaths>
#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"

View File

@ -1,21 +1,42 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* Authors: Andrew Okin * This program is free software: you can redistribute it and/or modify
* Peterix * it under the terms of the GNU General Public License as published by
* Orochimarufan <orochimarufan.x3@gmail.com> * the Free Software Foundation, version 3.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is distributed in the hope that it will be useful,
* you may not use this file except in compliance with the License. * but WITHOUT ANY WARRANTY; without even the implied warranty of
* You may obtain a copy of the License at * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* *
* http://www.apache.org/licenses/LICENSE-2.0 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* *
* Unless required by applicable law or agreed to in writing, software * This file incorporates work covered by the following copyright and
* distributed under the License is distributed on an "AS IS" BASIS, * permission notice:
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and * Copyright 2013-2021 MultiMC Contributors
* limitations under the License. *
* Authors: Andrew Okin
* Peterix
* Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
@ -1010,6 +1031,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
} }
#ifdef LAUNCHER_WITH_UPDATER
if(BuildConfig.UPDATER_ENABLED) if(BuildConfig.UPDATER_ENABLED)
{ {
bool updatesAllowed = APPLICATION->updatesAreAllowed(); bool updatesAllowed = APPLICATION->updatesAreAllowed();
@ -1028,6 +1050,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false); updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false);
} }
} }
#endif
setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString()); setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString());
@ -1337,6 +1360,7 @@ void MainWindow::repopulateAccountsMenu()
ui->profileMenu->addAction(ui->actionManageAccounts); ui->profileMenu->addAction(ui->actionManageAccounts);
} }
#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::updatesAllowedChanged(bool allowed) void MainWindow::updatesAllowedChanged(bool allowed)
{ {
if(!BuildConfig.UPDATER_ENABLED) if(!BuildConfig.UPDATER_ENABLED)
@ -1345,6 +1369,7 @@ void MainWindow::updatesAllowedChanged(bool allowed)
} }
ui->actionCheckUpdate->setEnabled(allowed); ui->actionCheckUpdate->setEnabled(allowed);
} }
#endif
/* /*
* Assumes the sender is a QAction * Assumes the sender is a QAction
@ -1450,6 +1475,7 @@ void MainWindow::updateNewsLabel()
} }
} }
#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::updateAvailable(GoUpdate::Status status) void MainWindow::updateAvailable(GoUpdate::Status status)
{ {
if(!APPLICATION->updatesAreAllowed()) if(!APPLICATION->updatesAreAllowed())
@ -1475,6 +1501,7 @@ void MainWindow::updateNotAvailable()
UpdateDialog dlg(false, this); UpdateDialog dlg(false, this);
dlg.exec(); dlg.exec();
} }
#endif
QList<int> stringToIntList(const QString &string) QList<int> stringToIntList(const QString &string)
{ {
@ -1496,6 +1523,7 @@ QString intListToString(const QList<int> &list)
return slist.join(','); return slist.join(',');
} }
#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::downloadUpdates(GoUpdate::Status status) void MainWindow::downloadUpdates(GoUpdate::Status status)
{ {
if(!APPLICATION->updatesAreAllowed()) if(!APPLICATION->updatesAreAllowed())
@ -1529,6 +1557,7 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)
CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show(); CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show();
} }
} }
#endif
void MainWindow::onCatToggled(bool state) void MainWindow::onCatToggled(bool state)
{ {
@ -1841,6 +1870,7 @@ void MainWindow::on_actionConfig_Folder_triggered()
} }
} }
#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::checkForUpdates() void MainWindow::checkForUpdates()
{ {
if(BuildConfig.UPDATER_ENABLED) if(BuildConfig.UPDATER_ENABLED)
@ -1853,6 +1883,7 @@ void MainWindow::checkForUpdates()
qWarning() << "Updater not set up. Cannot check for updates."; qWarning() << "Updater not set up. Cannot check for updates.";
} }
} }
#endif
void MainWindow::on_actionSettings_triggered() void MainWindow::on_actionSettings_triggered()
{ {
@ -2101,6 +2132,9 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
selectionBad(); selectionBad();
return; return;
} }
if (m_selectedInstance) {
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
}
QString id = current.data(InstanceList::InstanceIDRole).toString(); QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id); m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
if (m_selectedInstance) if (m_selectedInstance)
@ -2127,6 +2161,8 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
updateToolsMenu(); updateToolsMenu();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
} }
else else
{ {
@ -2216,3 +2252,9 @@ void MainWindow::updateStatusCenter()
m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed))); m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
} }
} }
void MainWindow::refreshCurrentInstance(bool running)
{
auto current = view->selectionModel()->currentIndex();
instanceChanged(current, current);
}

View File

@ -1,16 +1,40 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU General Public License as published by
* You may obtain a copy of the License at * the Free Software Foundation, version 3.
* *
* http://www.apache.org/licenses/LICENSE-2.0 * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* *
* Unless required by applicable law or agreed to in writing, software * You should have received a copy of the GNU General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and * This file incorporates work covered by the following copyright and
* limitations under the License. * permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Andrew Okin
* Peterix
* Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
#pragma once #pragma once
@ -54,7 +78,9 @@ public:
void checkInstancePathForProblems(); void checkInstancePathForProblems();
#ifdef LAUNCHER_WITH_UPDATER
void updatesAllowedChanged(bool allowed); void updatesAllowedChanged(bool allowed);
#endif
void droppedURLs(QList<QUrl> urls); void droppedURLs(QList<QUrl> urls);
signals: signals:
@ -100,7 +126,9 @@ private slots:
void on_actionViewCentralModsFolder_triggered(); void on_actionViewCentralModsFolder_triggered();
#ifdef LAUNCHER_WITH_UPDATER
void checkForUpdates(); void checkForUpdates();
#endif
void on_actionSettings_triggered(); void on_actionSettings_triggered();
@ -167,9 +195,11 @@ private slots:
void startTask(Task *task); void startTask(Task *task);
#ifdef LAUNCHER_WITH_UPDATER
void updateAvailable(GoUpdate::Status status); void updateAvailable(GoUpdate::Status status);
void updateNotAvailable(); void updateNotAvailable();
#endif
void defaultAccountChanged(); void defaultAccountChanged();
@ -179,10 +209,12 @@ private slots:
void updateNewsLabel(); void updateNewsLabel();
#ifdef LAUNCHER_WITH_UPDATER
/*! /*!
* Runs the DownloadTask and installs updates. * Runs the DownloadTask and installs updates.
*/ */
void downloadUpdates(GoUpdate::Status status); void downloadUpdates(GoUpdate::Status status);
#endif
void konamiTriggered(); void konamiTriggered();
@ -192,6 +224,8 @@ private slots:
void keyReleaseEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override;
#endif #endif
void refreshCurrentInstance(bool running);
private: private:
void retranslateUi(); void retranslateUi();

View File

@ -75,9 +75,9 @@ APIPage::APIPage(QWidget *parent) :
// This function needs to be called even when the ComboBox's index is still in its default state. // This function needs to be called even when the ComboBox's index is still in its default state.
updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex()); updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex());
ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry));
ui->tabWidget->tabBar()->hide();
ui->metaURL->setPlaceholderText(BuildConfig.META_URL); ui->metaURL->setPlaceholderText(BuildConfig.META_URL);
ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT);
loadSettings(); loadSettings();
@ -139,6 +139,8 @@ void APIPage::loadSettings()
ui->metaURL->setText(metaURL); ui->metaURL->setText(metaURL);
QString curseKey = s->get("CFKeyOverride").toString(); QString curseKey = s->get("CFKeyOverride").toString();
ui->curseKey->setText(curseKey); ui->curseKey->setText(curseKey);
QString customUserAgent = s->get("UserAgentOverride").toString();
ui->userAgentLineEdit->setText(customUserAgent);
} }
void APIPage::applySettings() void APIPage::applySettings()
@ -167,6 +169,7 @@ void APIPage::applySettings()
s->set("MetaURLOverride", metaURL); s->set("MetaURLOverride", metaURL);
QString curseKey = ui->curseKey->text(); QString curseKey = ui->curseKey->text();
s->set("CFKeyOverride", curseKey); s->set("CFKeyOverride", curseKey);
s->set("UserAgentOverride", ui->userAgentLineEdit->text());
} }
bool APIPage::apply() bool APIPage::apply()

View File

@ -30,7 +30,7 @@
</property> </property>
<widget class="QWidget" name="tab"> <widget class="QWidget" name="tab">
<attribute name="title"> <attribute name="title">
<string notr="true">Tab 1</string> <string notr="true">Services</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
@ -85,51 +85,6 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_msa">
<property name="title">
<string>&amp;Microsoft Authentication</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Note: you probably don't need to set this if logging in via Microsoft Authentication already works.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="msaClientID">
<property name="placeholderText">
<string>(Default)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Enter a custom client ID for Microsoft Authentication here. </string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_meta"> <widget class="QGroupBox" name="groupBox_meta">
<property name="title"> <property name="title">
@ -175,6 +130,71 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>API Keys</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QGroupBox" name="groupBox_msa">
<property name="title">
<string>&amp;Microsoft Authentication</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Note: you probably don't need to set this if logging in via Microsoft Authentication already works.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="msaClientID">
<property name="placeholderText">
<string>(Default)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Enter a custom client ID for Microsoft Authentication here. </string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_curse"> <widget class="QGroupBox" name="groupBox_curse">
<property name="enabled"> <property name="enabled">
@ -183,25 +203,15 @@
<property name="title"> <property name="title">
<string>&amp;CurseForge Core API</string> <string>&amp;CurseForge Core API</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_6"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="0" column="0">
<widget class="QLabel" name="label_8"> <widget class="QLabel" name="label_8">
<property name="text"> <property name="text">
<string>Note: you probably don't need to set this if CurseForge already works.</string> <string>Note: you probably don't need to set this if CurseForge already works.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="2" column="0">
<widget class="QLineEdit" name="curseKey">
<property name="enabled">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>(Default)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_7"> <widget class="QLabel" name="label_7">
<property name="text"> <property name="text">
<string>Enter a custom API Key for CurseForge here. </string> <string>Enter a custom API Key for CurseForge here. </string>
@ -217,6 +227,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QLineEdit" name="curseKey">
<property name="enabled">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>(Default)</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -235,6 +255,51 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Miscellaneous</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QGroupBox" name="groupBox_ua">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>User Agent</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLineEdit" name="userAgentLineEdit"/>
</item>
<item>
<widget class="QLabel" name="userAgentLabel">
<property name="text">
<string>Enter a custom User Agent here. The special string $LAUNCHER_VER will be replaced with the version of the launcher.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by

View File

@ -78,6 +78,7 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
m_languageModel = APPLICATION->translations(); m_languageModel = APPLICATION->translations();
loadSettings(); loadSettings();
#ifdef LAUNCHER_WITH_UPDATER
if(BuildConfig.UPDATER_ENABLED) if(BuildConfig.UPDATER_ENABLED)
{ {
QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList); QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList);
@ -90,11 +91,9 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
{ {
APPLICATION->updateChecker()->updateChanList(false); APPLICATION->updateChecker()->updateChanList(false);
} }
ui->updateSettingsBox->setHidden(false);
} }
else #endif
{
ui->updateSettingsBox->setHidden(true);
}
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
} }
@ -189,6 +188,7 @@ void LauncherPage::on_metadataDisableBtn_clicked()
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
} }
#ifdef LAUNCHER_WITH_UPDATER
void LauncherPage::refreshUpdateChannelList() void LauncherPage::refreshUpdateChannelList()
{ {
// Stop listening for selection changes. It's going to change a lot while we update it and // Stop listening for selection changes. It's going to change a lot while we update it and
@ -260,6 +260,7 @@ void LauncherPage::refreshUpdateChannelDesc()
m_currentUpdateChannel = selected.id; m_currentUpdateChannel = selected.id;
} }
} }
#endif
void LauncherPage::applySettings() void LauncherPage::applySettings()
{ {
@ -450,7 +451,7 @@ void LauncherPage::loadSettings()
} }
// Mods // Mods
ui->metadataDisableBtn->setChecked(s->get("DontUseModMetadata").toBool()); ui->metadataDisableBtn->setChecked(s->get("ModMetadataDisabled").toBool());
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
} }

View File

@ -90,6 +90,7 @@ slots:
void on_iconsDirBrowseBtn_clicked(); void on_iconsDirBrowseBtn_clicked();
void on_metadataDisableBtn_clicked(); void on_metadataDisableBtn_clicked();
#ifdef LAUNCHER_WITH_UPDATER
/*! /*!
* Updates the list of update channels in the combo box. * Updates the list of update channels in the combo box.
*/ */
@ -100,13 +101,13 @@ slots:
*/ */
void refreshUpdateChannelDesc(); void refreshUpdateChannelDesc();
void updateChannelSelectionChanged(int index);
#endif
/*! /*!
* Updates the font preview * Updates the font preview
*/ */
void refreshFontPreview(); void refreshFontPreview();
void updateChannelSelectionChanged(int index);
private: private:
Ui::LauncherPage *ui; Ui::LauncherPage *ui;

View File

@ -50,6 +50,9 @@
<property name="title"> <property name="title">
<string>Update Settings</string> <string>Update Settings</string>
</property> </property>
<property name="visible">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7"> <layout class="QVBoxLayout" name="verticalLayout_7">
<item> <item>
<widget class="QCheckBox" name="autoUpdateCheckBox"> <widget class="QCheckBox" name="autoUpdateCheckBox">

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -39,6 +40,7 @@
#include "Application.h" #include "Application.h"
#include <QIcon> #include <QIcon>
#include <QIdentityProxyModel>
#include <QScrollBar> #include <QScrollBar>
#include <QShortcut> #include <QShortcut>

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by

View File

@ -41,6 +41,7 @@
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
#include <Application.h> #include <Application.h>
#include <QSortFilterProxyModel>
class ModFolderModel; class ModFolderModel;
namespace Ui namespace Ui

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by

View File

@ -35,6 +35,7 @@
#pragma once #pragma once
#include <QItemSelection>
#include <QMainWindow> #include <QMainWindow>
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -47,6 +48,7 @@
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QMenu> #include <QMenu>
#include <QTimer>
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things. static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -46,6 +47,7 @@
#include <QInputDialog> #include <QInputDialog>
#include <QProcess> #include <QProcess>
#include <Qt> #include <Qt>
#include <QSortFilterProxyModel>
#include "tools/MCEditTool.h" #include "tools/MCEditTool.h"
#include "FileSystem.h" #include "FileSystem.h"

View File

@ -110,11 +110,13 @@ void ImportPage::updateState()
{ {
// FIXME: actually do some validation of what's inside here... this is fake AF // FIXME: actually do some validation of what's inside here... this is fake AF
QFileInfo fi(input); QFileInfo fi(input);
// mrpack is a modrinth pack
// Allow non-latin people to use ZIP files! // Allow non-latin people to use ZIP files!
auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip"); bool isZip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
if(fi.exists() && (zip || fi.suffix() == "mrpack")) // mrpack is a modrinth pack
bool isMRPack = fi.suffix() == "mrpack";
if(fi.exists() && (isZip || isMRPack))
{ {
QFileInfo fi(url.fileName()); QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this)); dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
@ -149,7 +151,8 @@ void ImportPage::setUrl(const QString& url)
void ImportPage::on_modpackBtn_clicked() void ImportPage::on_modpackBtn_clicked()
{ {
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
filter += ";;" + tr("Modrinth pack (*.mrpack)"); //: Option for filtering for *.mrpack files when importing
filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter); const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
if (url.isValid()) if (url.isValid())
{ {

View File

@ -96,6 +96,11 @@ void ListModel::performPaginatedSearch()
this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }); this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() });
} }
void ListModel::requestModInfo(ModPlatform::IndexedPack& current)
{
m_parent->apiProvider()->getModInfo(this, current);
}
void ListModel::refresh() void ListModel::refresh()
{ {
if (jobPtr) { if (jobPtr) {
@ -242,6 +247,21 @@ void ListModel::searchRequestFailed(QString reason)
} }
} }
void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack)
{
qDebug() << "Loading mod info";
try {
auto obj = Json::requireObject(doc);
loadExtraPackInfo(pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading " << debugName() << " mod info: " << e.cause();
}
m_parent->updateUi();
}
void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId) void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId)
{ {
auto& current = m_parent->getCurrent(); auto& current = m_parent->getCurrent();

View File

@ -36,9 +36,11 @@ class ListModel : public QAbstractListModel {
void fetchMore(const QModelIndex& parent) override; void fetchMore(const QModelIndex& parent) override;
void refresh(); void refresh();
void searchWithTerm(const QString& term, const int sort, const bool filter_changed); void searchWithTerm(const QString& term, const int sort, const bool filter_changed);
void requestModInfo(ModPlatform::IndexedPack& current);
void requestModVersions(const ModPlatform::IndexedPack& current); void requestModVersions(const ModPlatform::IndexedPack& current);
virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) {};
virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0;
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
@ -49,6 +51,8 @@ class ListModel : public QAbstractListModel {
void searchRequestFinished(QJsonDocument& doc); void searchRequestFinished(QJsonDocument& doc);
void searchRequestFailed(QString reason); void searchRequestFailed(QString reason);
void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack);
void versionRequestSucceeded(QJsonDocument doc, QString addonId); void versionRequestSucceeded(QJsonDocument doc, QString addonId);
protected slots: protected slots:

View File

@ -1,4 +1,40 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ModPage.h" #include "ModPage.h"
#include "Application.h"
#include "ui_ModPage.h" #include "ui_ModPage.h"
#include <QKeyEvent> #include <QKeyEvent>
@ -94,28 +130,6 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
if (!first.isValid()) { return; } if (!first.isValid()) { return; }
current = listModel->data(first, Qt::UserRole).value<ModPlatform::IndexedPack>(); current = listModel->data(first, Qt::UserRole).value<ModPlatform::IndexedPack>();
QString text = "";
QString name = current.name;
if (current.websiteUrl.isEmpty())
text = name;
else
text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
if (!current.authors.empty()) {
auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString {
if (author.url.isEmpty()) { return author.name; }
return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
};
QStringList authorStrs;
for (auto& author : current.authors) {
authorStrs.push_back(authorToStr(author));
}
text += "<br>" + tr(" by ") + authorStrs.join(", ");
}
text += "<br><br>";
ui->packDescription->setHtml(text + current.description);
if (!current.versionsLoaded) { if (!current.versionsLoaded) {
qDebug() << QString("Loading %1 mod versions").arg(debugName()); qDebug() << QString("Loading %1 mod versions").arg(debugName());
@ -132,6 +146,13 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
updateSelectionButton(); updateSelectionButton();
} }
if(!current.extraDataLoaded){
qDebug() << QString("Loading %1 mod info").arg(debugName());
listModel->requestModInfo(current);
}
updateUi();
} }
void ModPage::onVersionSelectionChanged(QString data) void ModPage::onVersionSelectionChanged(QString data)
@ -150,7 +171,8 @@ void ModPage::onModSelected()
if (dialog->isModSelected(current.name, version.fileName)) { if (dialog->isModSelected(current.name, version.fileName)) {
dialog->removeSelectedMod(current.name); dialog->removeSelectedMod(current.name);
} else { } else {
dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods)); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods, is_indexed));
} }
updateSelectionButton(); updateSelectionButton();
@ -207,3 +229,61 @@ void ModPage::updateSelectionButton()
ui->modSelectionButton->setText(tr("Deselect mod for download")); ui->modSelectionButton->setText(tr("Deselect mod for download"));
} }
} }
void ModPage::updateUi()
{
QString text = "";
QString name = current.name;
if (current.websiteUrl.isEmpty())
text = name;
else
text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
if (!current.authors.empty()) {
auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString {
if (author.url.isEmpty()) { return author.name; }
return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
};
QStringList authorStrs;
for (auto& author : current.authors) {
authorStrs.push_back(authorToStr(author));
}
text += "<br>" + tr(" by ") + authorStrs.join(", ");
}
if(current.extraDataLoaded) {
if (!current.extraData.donate.isEmpty()) {
text += "<br><br>" + tr("Donate information: ");
auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
return QString("<a href=\"%1\">%2</a>").arg(donate.url, donate.platform);
};
QStringList donates;
for (auto& donate : current.extraData.donate) {
donates.append(donateToStr(donate));
}
text += donates.join(", ");
}
if (!current.extraData.issuesUrl.isEmpty()
|| !current.extraData.sourceUrl.isEmpty()
|| !current.extraData.wikiUrl.isEmpty()
|| !current.extraData.discordUrl.isEmpty()) {
text += "<br><br>" + tr("External links:") + "<br>";
}
if (!current.extraData.issuesUrl.isEmpty())
text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extraData.issuesUrl) + "<br>";
if (!current.extraData.wikiUrl.isEmpty())
text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extraData.wikiUrl) + "<br>";
if (!current.extraData.sourceUrl.isEmpty())
text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extraData.sourceUrl) + "<br>";
if (!current.extraData.discordUrl.isEmpty())
text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current.extraData.discordUrl) + "<br>";
}
text += "<hr>";
ui->packDescription->setHtml(text + current.description);
}

View File

@ -36,10 +36,12 @@ class ModPage : public QWidget, public BasePage {
void retranslate() override; void retranslate() override;
void updateUi();
auto shouldDisplay() const -> bool override = 0; auto shouldDisplay() const -> bool override = 0;
virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0; virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0;
auto apiProvider() const -> const ModAPI* { return api.get(); }; auto apiProvider() -> ModAPI* { return api.get(); };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; } auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
auto getDialog() const -> const ModDownloadDialog* { return dialog; } auto getDialog() const -> const ModDownloadDialog* { return dialog; }

View File

@ -117,7 +117,7 @@ void AtlPage::suggestCurrent()
return; return;
} }
dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.safeName, selectedVersion)); dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.name, selectedVersion));
auto editedLogoName = selected.safeName; auto editedLogoName = selected.safeName;
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower()); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());
listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo) listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo)

View File

@ -119,29 +119,6 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
} }
current = listModel->data(first, Qt::UserRole).value<Flame::IndexedPack>(); current = listModel->data(first, Qt::UserRole).value<Flame::IndexedPack>();
QString text = "";
QString name = current.name;
if (current.websiteUrl.isEmpty())
text = name;
else
text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
if (!current.authors.empty()) {
auto authorToStr = [](Flame::ModpackAuthor& author) {
if (author.url.isEmpty()) {
return author.name;
}
return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
};
QStringList authorStrs;
for (auto& author : current.authors) {
authorStrs.push_back(authorToStr(author));
}
text += "<br>" + tr(" by ") + authorStrs.join(", ");
}
text += "<br><br>";
ui->packDescription->setHtml(text + current.description);
if (current.versionsLoaded == false) { if (current.versionsLoaded == false) {
qDebug() << "Loading flame modpack versions"; qDebug() << "Loading flame modpack versions";
@ -188,6 +165,8 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
suggestCurrent(); suggestCurrent();
} }
updateUi();
} }
void FlamePage::suggestCurrent() void FlamePage::suggestCurrent()
@ -217,3 +196,46 @@ void FlamePage::onVersionSelectionChanged(QString data)
selectedVersion = ui->versionSelectionBox->currentData().toString(); selectedVersion = ui->versionSelectionBox->currentData().toString();
suggestCurrent(); suggestCurrent();
} }
void FlamePage::updateUi()
{
QString text = "";
QString name = current.name;
if (current.extra.websiteUrl.isEmpty())
text = name;
else
text = "<a href=\"" + current.extra.websiteUrl + "\">" + name + "</a>";
if (!current.authors.empty()) {
auto authorToStr = [](Flame::ModpackAuthor& author) {
if (author.url.isEmpty()) {
return author.name;
}
return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
};
QStringList authorStrs;
for (auto& author : current.authors) {
authorStrs.push_back(authorToStr(author));
}
text += "<br>" + tr(" by ") + authorStrs.join(", ");
}
if(current.extraInfoLoaded) {
if (!current.extra.issuesUrl.isEmpty()
|| !current.extra.sourceUrl.isEmpty()
|| !current.extra.wikiUrl.isEmpty()) {
text += "<br><br>" + tr("External links:") + "<br>";
}
if (!current.extra.issuesUrl.isEmpty())
text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extra.issuesUrl) + "<br>";
if (!current.extra.wikiUrl.isEmpty())
text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extra.wikiUrl) + "<br>";
if (!current.extra.sourceUrl.isEmpty())
text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>";
}
text += "<hr>";
ui->packDescription->setHtml(text + current.description);
}

View File

@ -79,6 +79,8 @@ public:
virtual bool shouldDisplay() const override; virtual bool shouldDisplay() const override;
void retranslate() override; void retranslate() override;
void updateUi();
void openedImpl() override; void openedImpl() override;
bool eventFilter(QObject * watched, QEvent * event) override; bool eventFilter(QObject * watched, QEvent * event) override;

View File

@ -122,10 +122,10 @@ void ListModel::requestFinished()
jobPtr.reset(); jobPtr.reset();
remainingPacks.clear(); remainingPacks.clear();
QJsonParseError parse_error; QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) { if(parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response; qWarning() << response;
return; return;
} }
@ -169,7 +169,7 @@ void ListModel::packRequestFinished()
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) { if(parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response; qWarning() << response;
return; return;
} }
@ -184,7 +184,7 @@ void ListModel::packRequestFinished()
catch (const JSONValidationError &e) catch (const JSONValidationError &e)
{ {
qDebug() << QString::fromUtf8(response); qDebug() << QString::fromUtf8(response);
qWarning() << "Error while reading pack manifest from FTB: " << e.cause(); qWarning() << "Error while reading pack manifest from ModpacksCH: " << e.cause();
return; return;
} }
@ -192,7 +192,7 @@ void ListModel::packRequestFinished()
// ignore those "dud" packs. // ignore those "dud" packs.
if (pack.versions.empty()) if (pack.versions.empty())
{ {
qWarning() << "FTB Pack " << pack.id << " ignored. reason: lacking any versions"; qWarning() << "ModpacksCH Pack " << pack.id << " ignored. reason: lacking any versions";
} }
else else
{ {
@ -270,7 +270,7 @@ void ListModel::requestLogo(QString logo, QString url)
bool stale = entry->isStale(); bool stale = entry->isStale();
NetJob *job = new NetJob(QString("FTB Icon Download %1").arg(logo), APPLICATION->network()); NetJob *job = new NetJob(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ListModel.h" #include "ListModel.h"
#include "Application.h" #include "Application.h"
@ -11,6 +46,8 @@
#include <BuildConfig.h> #include <BuildConfig.h>
#include <net/NetJob.h>
namespace LegacyFTB { namespace LegacyFTB {
FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)

View File

@ -30,6 +30,11 @@ void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
Modrinth::loadIndexedPack(m, obj); Modrinth::loadIndexedPack(m, obj);
} }
void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
Modrinth::loadExtraPackData(m, obj);
}
void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{ {
Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);

View File

@ -31,6 +31,7 @@ class ListModel : public ModPlatform::ListModel {
private: private:
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by

View File

@ -39,6 +39,7 @@
#include "modplatform/modrinth/ModrinthPackManifest.h" #include "modplatform/modrinth/ModrinthPackManifest.h"
#include "ui/pages/modplatform/modrinth/ModrinthPage.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "net/NetJob.h"
class ModPage; class ModPage;
class Version; class Version;

View File

@ -224,7 +224,37 @@ void ModrinthPage::updateUI()
// TODO: Implement multiple authors with links // TODO: Implement multiple authors with links
text += "<br>" + tr(" by ") + QString("<a href=%1>%2</a>").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); text += "<br>" + tr(" by ") + QString("<a href=%1>%2</a>").arg(std::get<1>(current.author).toString(), std::get<0>(current.author));
text += "<br>"; if(current.extraInfoLoaded) {
if (!current.extra.donate.isEmpty()) {
text += "<br><br>" + tr("Donate information: ");
auto donateToStr = [](Modrinth::DonationData& donate) -> QString {
return QString("<a href=\"%1\">%2</a>").arg(donate.url, donate.platform);
};
QStringList donates;
for (auto& donate : current.extra.donate) {
donates.append(donateToStr(donate));
}
text += donates.join(", ");
}
if (!current.extra.issuesUrl.isEmpty()
|| !current.extra.sourceUrl.isEmpty()
|| !current.extra.wikiUrl.isEmpty()
|| !current.extra.discordUrl.isEmpty()) {
text += "<br><br>" + tr("External links:") + "<br>";
}
if (!current.extra.issuesUrl.isEmpty())
text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extra.issuesUrl) + "<br>";
if (!current.extra.wikiUrl.isEmpty())
text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extra.wikiUrl) + "<br>";
if (!current.extra.sourceUrl.isEmpty())
text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>";
if (!current.extra.discordUrl.isEmpty())
text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current.extra.discordUrl) + "<br>";
}
text += "<hr>";
HoeDown h; HoeDown h;
text += h.process(current.extra.body.toUtf8()); text += h.process(current.extra.body.toUtf8());

View File

@ -60,7 +60,11 @@
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QTextBrowser" name="packDescription"/> <widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item> </item>
</layout> </layout>
</item> </item>

View File

@ -24,24 +24,65 @@ import java.net.URL;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
/*
* WARNING: This class is reflectively accessed by legacy Forge versions.
* Changing field and method declarations without further testing is not recommended.
*/
public final class Launcher extends Applet implements AppletStub { public final class Launcher extends Applet implements AppletStub {
private final Map<String, String> params = new TreeMap<>(); private final Map<String, String> params = new TreeMap<>();
private final Applet wrappedApplet; private Applet wrappedApplet;
private URL documentBase;
private boolean active = false; private boolean active = false;
public Launcher(Applet applet) { public Launcher(Applet applet) {
this(applet, null);
}
public Launcher(Applet applet, URL documentBase) {
this.setLayout(new BorderLayout()); this.setLayout(new BorderLayout());
this.add(applet, "Center"); this.add(applet, "Center");
this.wrappedApplet = applet; this.wrappedApplet = applet;
try {
if (documentBase != null) {
this.documentBase = documentBase;
} else if (applet.getClass().getPackage().getName().startsWith("com.mojang")) {
// Special case only for Classic versions
this.documentBase = new URL("http", "www.minecraft.net", 80, "/game/");
} else {
this.documentBase = new URL("http://www.minecraft.net/game/");
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
} }
public void setParameter(String name, String value) public void replace(Applet applet) {
{ this.wrappedApplet = applet;
applet.setStub(this);
applet.setSize(getWidth(), getHeight());
this.setLayout(new BorderLayout());
this.add(applet, "Center");
applet.init();
active = true;
applet.start();
validate();
}
public void setParameter(String name, String value) {
params.put(name, value); params.put(name, value);
} }
@ -54,7 +95,7 @@ public final class Launcher extends Applet implements AppletStub {
try { try {
return super.getParameter(name); return super.getParameter(name);
} catch (Exception ignore) {} } catch (Exception ignored) {}
return null; return null;
} }
@ -108,25 +149,13 @@ public final class Launcher extends Applet implements AppletStub {
try { try {
return new URL("http://www.minecraft.net/game/"); return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); throw new RuntimeException(e);
} }
return null;
} }
@Override @Override
public URL getDocumentBase() { public URL getDocumentBase() {
try { return documentBase;
// Special case only for Classic versions
if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang"))
return new URL("http", "www.minecraft.net", 80, "/game/");
return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
} }
@Override @Override

View File

@ -14,6 +14,7 @@
, quazip , quazip
, libGL , libGL
, msaClientID ? "" , msaClientID ? ""
, extraJDKs ? [ ]
# flake # flake
, self , self
@ -36,6 +37,8 @@ let
# This variable will be passed to Minecraft by PolyMC # This variable will be passed to Minecraft by PolyMC
gameLibraryPath = libpath + ":/run/opengl-driver/lib"; gameLibraryPath = libpath + ":/run/opengl-driver/lib";
javaPaths = lib.makeSearchPath "bin/java" ([ jdk jdk8 ] ++ extraJDKs);
in in
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
@ -67,7 +70,7 @@ stdenv.mkDerivation rec {
# xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
wrapQtApp $out/bin/polymc \ wrapQtApp $out/bin/polymc \
--set GAME_LIBRARY_PATH ${gameLibraryPath} \ --set GAME_LIBRARY_PATH ${gameLibraryPath} \
--prefix POLYMC_JAVA_PATHS : ${jdk}/lib/openjdk/bin/java:${jdk8}/lib/openjdk/bin/java \ --prefix POLYMC_JAVA_PATHS : ${javaPaths} \
--prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]} --prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]}
''; '';

View File

@ -14,6 +14,8 @@ set(Launcher_MetaInfo "program_info/org.polymc.PolyMC.metainfo.xml" PARENT_SCOPE
set(Launcher_ManPage "program_info/polymc.6.txt" PARENT_SCOPE) set(Launcher_ManPage "program_info/polymc.6.txt" PARENT_SCOPE)
set(Launcher_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE) set(Launcher_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE)
set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE)
set(Launcher_Branding_ICO "program_info/polymc.ico")
set(Launcher_Branding_ICO "${Launcher_Branding_ICO}" PARENT_SCOPE)
set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE) set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE)
set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE) set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE)
@ -24,3 +26,4 @@ configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml)
configure_file(polymc.rc.in polymc.rc @ONLY) configure_file(polymc.rc.in polymc.rc @ONLY)
configure_file(polymc.manifest.in polymc.manifest @ONLY) configure_file(polymc.manifest.in polymc.manifest @ONLY)
configure_file(polymc.ico polymc.ico COPYONLY) configure_file(polymc.ico polymc.ico COPYONLY)
configure_file(win_install.nsi.in win_install.nsi @ONLY)

View File

@ -4,10 +4,13 @@
Unicode true Unicode true
Name "PolyMC" Name "@Launcher_CommonName@"
InstallDir "$LOCALAPPDATA\Programs\PolyMC" InstallDir "$LOCALAPPDATA\Programs\@Launcher_CommonName@"
InstallDirRegKey HKCU "Software\PolyMC" "InstallDir" InstallDirRegKey HKCU "Software\@Launcher_CommonName@" "InstallDir"
RequestExecutionLevel user RequestExecutionLevel user
OutFile "../@Launcher_CommonName@-Setup.exe"
!define MUI_ICON "../@Launcher_Branding_ICO@"
;-------------------------------- ;--------------------------------
@ -18,7 +21,7 @@ RequestExecutionLevel user
!insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN "$InstDir\polymc.exe" !define MUI_FINISHPAGE_RUN "$InstDir\@Launcher_APP_BINARY_NAME@.exe"
!insertmacro MUI_PAGE_FINISH !insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_CONFIRM
@ -98,16 +101,23 @@ RequestExecutionLevel user
;-------------------------------- ;--------------------------------
; Version info
VIProductVersion "@Launcher_RELEASE_VERSION_NAME4@"
VIFileVersion "@Launcher_RELEASE_VERSION_NAME4@"
VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@"
;--------------------------------
; The stuff to install ; The stuff to install
Section "PolyMC" Section "@Launcher_CommonName@"
SectionIn RO SectionIn RO
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F' nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F'
SetOutPath $INSTDIR SetOutPath $INSTDIR
File "polymc.exe" File "@Launcher_APP_BINARY_NAME@.exe"
File "qt.conf" File "qt.conf"
File *.dll File *.dll
File /r "iconengines" File /r "iconengines"
@ -117,20 +127,20 @@ Section "PolyMC"
File /r "styles" File /r "styles"
; Write the installation path into the registry ; Write the installation path into the registry
WriteRegStr HKCU Software\PolyMC "InstallDir" "$INSTDIR" WriteRegStr HKCU Software\@Launcher_CommonName@ "InstallDir" "$INSTDIR"
; Write the uninstall keys for Windows ; Write the uninstall keys for Windows
${GetParameters} $R0 ${GetParameters} $R0
${GetOptions} $R0 "/NoUninstaller" $R1 ${GetOptions} $R0 "/NoUninstaller" $R1
${If} ${Errors} ${If} ${Errors}
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC" WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_CommonName@"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe" WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe"
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors" WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors"
WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}" WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0 IntFmt $0 "0x%08X" $0
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0"
@ -143,13 +153,13 @@ SectionEnd
Section "Start Menu Shortcut" SM_SHORTCUTS Section "Start Menu Shortcut" SM_SHORTCUTS
CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 CreateShortcut "$SMPROGRAMS\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0
SectionEnd SectionEnd
Section "Desktop Shortcut" DESKTOP_SHORTCUTS Section "Desktop Shortcut" DESKTOP_SHORTCUTS
CreateShortcut "$DESKTOP\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 CreateShortcut "$DESKTOP\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0
SectionEnd SectionEnd
@ -159,12 +169,12 @@ SectionEnd
Section "Uninstall" Section "Uninstall"
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F' nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F'
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
DeleteRegKey HKCU SOFTWARE\PolyMC DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@
Delete $INSTDIR\polymc.exe Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe
Delete $INSTDIR\uninstall.exe Delete $INSTDIR\uninstall.exe
Delete $INSTDIR\portable.txt Delete $INSTDIR\portable.txt
@ -220,8 +230,8 @@ Section "Uninstall"
RMDir /r $INSTDIR\platforms RMDir /r $INSTDIR\platforms
RMDir /r $INSTDIR\styles RMDir /r $INSTDIR\styles
Delete "$SMPROGRAMS\PolyMC.lnk" Delete "$SMPROGRAMS\@Launcher_CommonName@.lnk"
Delete "$DESKTOP\PolyMC.lnk" Delete "$DESKTOP\@Launcher_CommonName@.lnk"
RMDir "$INSTDIR" RMDir "$INSTDIR"