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"
msystem: mingw64
- os: macos-11
- os: macos-12
macosx_deployment_target: 10.13
runs-on: ${{ matrix.os }}
@ -206,7 +206,7 @@ jobs:
shell: msys2 {0}
run: |
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)
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 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}")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
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(Launcher_VERSION_MAJOR 1)
set(Launcher_VERSION_MINOR 3)
set(Launcher_VERSION_HOTFIX 1)
set(Launcher_VERSION_MINOR 4)
set(Launcher_VERSION_HOTFIX 0)
# 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.
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>

6
flake.lock generated
View File

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

View File

@ -154,6 +154,7 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
fflush(stderr);
}
#ifdef LAUNCHER_WITH_UPDATER
QString getIdealPlatform(QString currentPlatform) {
auto info = Sys::getKernelInfo();
switch(info.kernelType) {
@ -192,6 +193,7 @@ QString getIdealPlatform(QString currentPlatform) {
}
return currentPlatform;
}
#endif
}
@ -711,6 +713,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Custom MSA credentials
m_settings->registerSetting("MSAClientIDOverride", "");
m_settings->registerSetting("CFKeyOverride", "");
m_settings->registerSetting("UserAgentOverride", "");
// Init page provider
{
@ -753,6 +756,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Translations loaded.";
}
#ifdef LAUNCHER_WITH_UPDATER
// initialize the updater
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));
qDebug() << "<> Updater started.";
}
#endif
// Instance icons
{
@ -874,6 +879,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_mcedit.reset(new MCEditTool(m_settings));
}
#ifdef Q_OS_MACOS
connect(this, &Application::clickedOnDock, [this]() {
this->showMainWindow();
});
#endif
connect(this, &Application::aboutToQuit, [this](){
if(m_instances)
{
@ -957,6 +968,21 @@ bool Application::createSetupWizard()
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)
{
qDebug() << "Wizard result =" << status;
@ -1386,7 +1412,9 @@ MainWindow* Application::showMainWindow(bool minimized)
}
m_mainWindow->checkInstancePathForProblems();
#ifdef LAUNCHER_WITH_UPDATER
connect(this, &Application::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged);
#endif
connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose);
m_openWindows++;
}
@ -1556,3 +1584,24 @@ QString Application::getCurseKey()
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 <QDateTime>
#include <QUrl>
#ifdef LAUNCHER_WITH_UPDATER
#include <updater/GoUpdate.h>
#endif
#include <BaseInstance.h>
@ -94,6 +97,8 @@ public:
Application(int &argc, char **argv);
virtual ~Application();
bool event(QEvent* event) override;
std::shared_ptr<SettingsObject> settings() const {
return m_settings;
}
@ -156,6 +161,8 @@ public:
QString getMSAClientID();
QString getCurseKey();
QString getUserAgent();
QString getUserAgentUncached();
/// this is the root of the 'installation'. Used for automatic updates
const QString &root() {
@ -181,6 +188,10 @@ signals:
void globalSettingsAboutToOpen();
void globalSettingsClosed();
#ifdef Q_OS_MACOS
void clickedOnDock();
#endif
public slots:
bool launch(
InstancePtr instance,
@ -238,6 +249,10 @@ private:
QString m_rootPath;
Status m_status = Application::StartingUp;
#ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
#endif
#if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams
bool consoleAttached = false;

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* 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("totalTimePlayed", 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
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("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()
@ -93,6 +106,46 @@ QString BaseInstance::getPostExitCommand()
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
{
auto lineSetting = settings()->getSetting("ConsoleMaxLines");

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* it under the terms of the GNU General Public License as published by
@ -139,6 +140,14 @@ public:
QString getPostExitCommand();
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
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
{

View File

@ -156,27 +156,29 @@ set(LAUNCH_SOURCES
launch/LogModel.h
)
# Old update system
set(UPDATE_SOURCES
updater/GoUpdate.h
updater/GoUpdate.cpp
updater/UpdateChecker.h
updater/UpdateChecker.cpp
updater/DownloadTask.h
updater/DownloadTask.cpp
)
add_unit_test(UpdateChecker
SOURCES updater/UpdateChecker_test.cpp
LIBS Launcher_logic
DATA updater/testdata
if (Launcher_UPDATER_BASE)
set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_WITH_UPDATER ${Launcher_APP_BINARY_DEFS}")
# Old update system
set(UPDATE_SOURCES
updater/GoUpdate.h
updater/GoUpdate.cpp
updater/UpdateChecker.h
updater/UpdateChecker.cpp
updater/DownloadTask.h
updater/DownloadTask.cpp
)
add_unit_test(DownloadTask
SOURCES updater/DownloadTask_test.cpp
LIBS Launcher_logic
DATA updater/testdata
add_unit_test(UpdateChecker
SOURCES updater/UpdateChecker_test.cpp
LIBS Launcher_logic
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.
set(NEWS_SOURCES

View File

@ -454,4 +454,47 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
return false;
#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*
// return true if operation succeeded
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();
}
// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth()
{
std::vector<Modrinth::File> files;
std::vector<Modrinth::File> non_whitelisted_files;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
try {
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
@ -600,26 +600,30 @@ void InstanceImportTask::processModrinth()
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false;
for (auto& modInfo : jsonFiles) {
for (auto modInfo : jsonFiles) {
Modrinth::File file;
file.path = Json::requireString(modInfo, "path");
auto env = Json::ensureObject(modInfo, "env");
QString support = Json::ensureString(env, "client", "unsupported");
if (support == "unsupported") {
continue;
} else if (support == "optional") {
// TODO: Make a review dialog for choosing which ones the user wants!
if (!had_optional) {
had_optional = true;
auto info = CustomMessageBox::selectable(
m_parent, tr("Optional mod detected!"),
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information);
info->exec();
}
// 'env' field is optional
if (!env.isEmpty()) {
QString support = Json::ensureString(env, "client", "unsupported");
if (support == "unsupported") {
continue;
} else if (support == "optional") {
// TODO: Make a review dialog for choosing which ones the user wants!
if (!had_optional) {
had_optional = true;
auto info = CustomMessageBox::selectable(
m_parent, tr("Optional mod detected!"),
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
QMessageBox::Information);
info->exec();
}
if (file.path.endsWith(".jar"))
file.path += ".disabled";
if (file.path.endsWith(".jar"))
file.path += ".disabled";
}
}
QJsonObject hashes = Json::requireObject(modInfo, "hashes");
@ -640,40 +644,31 @@ void InstanceImportTask::processModrinth()
}
file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (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()) {
qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(file.download.toString(), file.path);
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
else if (!Modrinth::validateDownloadUrl(file.download)) {
qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(file.download.toString(), file.path);
non_whitelisted_files.push_back(file);
if (!download_url.isValid()) {
qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL")
.arg(download_url.toString(), file.path);
if(is_last && file.downloads.isEmpty())
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
else {
file.downloads.push_back(download_url);
}
}
files.push_back(file);
}
if (!non_whitelisted_files.empty()) {
QString text;
for (const auto& file : non_whitelisted_files) {
text += tr("Filepath: %1<br>URL: <a href='%2'>%2</a><br>").arg(file.path, file.download.toString());
}
auto message_dialog = new ScrollMessageBox(m_parent, tr("Non-whitelisted mods found"),
tr("The following mods have URLs that are not whitelisted by Modrinth.\n"
"Proceed with caution!"),
text);
message_dialog->setModal(true);
if (message_dialog->exec() == QDialog::Rejected) {
emitFailed("Aborted");
return;
}
}
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();
@ -701,16 +696,26 @@ void InstanceImportTask::processModrinth()
emitFailed(tr("Could not understand pack index:\n") + e.cause());
return;
}
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
QString overridePath = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(overridePath)) {
QString mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
if (!QFile::rename(overridePath, mcPath)) {
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(override_path)) {
if (!QFile::rename(override_path, mcPath)) {
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
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");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
@ -735,13 +740,24 @@ void InstanceImportTask::processModrinth()
instance.saveNow();
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);
qDebug() << "Will download" << file.download << "to" << path;
auto dl = Net::Download::makeFile(file.download, path);
qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
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, [&]()
{

View File

@ -547,8 +547,20 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
auto instanceRoot = FS::PathCombine(m_instDir, id);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
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();
return inst;
}

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
@ -21,12 +22,14 @@
#include "Application.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_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->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl));

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
@ -29,7 +30,7 @@ class ModFolderModel;
class ModDownloadTask : public SequentialTask {
Q_OBJECT
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; }
private:

View File

@ -93,7 +93,7 @@ void UpdateController::installUpdates()
qDebug() << "Installing updates.";
#ifdef Q_OS_WIN
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);
#elif defined Q_OS_MAC
QString finishCmd = QApplication::applicationFilePath();

View File

@ -56,6 +56,15 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
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)
{
QDir new_dir (path);
@ -141,6 +150,8 @@ void IconList::directoryChanged(const QString &path)
emit iconUpdated(key);
}
}
sortIconList();
}
void IconList::fileChanged(const QString &path)

View File

@ -71,6 +71,7 @@ private:
// hide assign op
IconList &operator=(const IconList &) = delete;
void reindex();
void sortIconList();
public slots:
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("QuitAfterGameStop"), miscellaneousOverride);
m_settings->set("InstanceType", "OneSix");
m_components.reset(new PackProfile(this));
}
@ -1013,7 +1015,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
{
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());
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)
{
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());
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");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* 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
* 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.
* 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 "LauncherPartLaunch.h"
#include <QStandardPaths>
#include <QRegularExpression>
#include "launch/LaunchTask.h"
#include "minecraft/MinecraftInstance.h"

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
@ -58,8 +59,6 @@ Mod::Mod(const QFileInfo& file)
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)
: 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_name(metadata.name)
{
@ -131,7 +130,7 @@ auto Mod::enable(bool value) -> bool
return false;
} else {
path += ".disabled";
if (!file.rename(path))
return false;
}
@ -145,23 +144,29 @@ auto Mod::enable(bool value) -> bool
void Mod::setStatus(ModStatus status)
{
if(m_localDetails.get())
if (m_localDetails) {
m_localDetails->status = status;
} else {
m_temp_status = status;
}
}
void Mod::setMetadata(Metadata::ModStruct* metadata)
{
if(status() == ModStatus::NoMetadata)
if (status() == ModStatus::NoMetadata)
setStatus(ModStatus::Installed);
if(m_localDetails.get())
if (m_localDetails) {
m_localDetails->metadata.reset(metadata);
} else {
m_temp_metadata.reset(metadata);
}
}
auto Mod::destroy(QDir& index_dir) -> bool
{
auto n = name();
// 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!
Metadata::remove(index_dir, n);
@ -205,20 +210,36 @@ auto Mod::authors() const -> QStringList
auto Mod::status() const -> ModStatus
{
if (!m_localDetails)
return m_temp_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)
{
m_resolving = false;
m_resolved = true;
m_localDetails = details;
if (status() != ModStatus::NoMetadata
&& m_temp_metadata.get()
&& m_temp_metadata->isValid() &&
m_localDetails.get()) {
m_localDetails->metadata.swap(m_temp_metadata);
if (m_localDetails && m_temp_metadata && m_temp_metadata->isValid()) {
m_localDetails->metadata = m_temp_metadata;
if (status() == ModStatus::NoMetadata)
setStatus(ModStatus::Installed);
}
setStatus(m_temp_status);
}

View File

@ -73,8 +73,8 @@ public:
auto authors() const -> QStringList;
auto status() const -> ModStatus;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct> { return details().metadata; };
auto metadata() -> std::shared_ptr<Metadata::ModStruct> { return m_localDetails->metadata; };
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
void setStatus(ModStatus status);
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
* the ModDetails when calling finishResolvingWithDetails */
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;
bool m_enabled = true;

View File

@ -1,17 +1,38 @@
/* 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.
*/
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* 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
* 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 "ModFolderModel.h"
@ -28,7 +49,7 @@
#include "minecraft/mod/tasks/LocalModParseTask.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());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
@ -82,7 +103,7 @@ bool ModFolderModel::update()
}
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();

View File

@ -1,17 +1,38 @@
/* 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.
*/
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* 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
* 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.
*/
#pragma once
@ -52,7 +73,7 @@ public:
Enable,
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 bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
@ -146,6 +167,7 @@ protected:
bool scheduled_update = false;
bool interaction_disabled = false;
QDir m_dir;
bool m_is_indexed;
QMap<QString, int> modsIndex;
QMap<int, LocalModParseTask::ResultPtr> activeTickets;
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 <QTemporaryDir>
@ -32,8 +66,11 @@ slots:
{
QString folder = source;
QTemporaryDir tempDir;
ModFolderModel m(tempDir.path());
QEventLoop loop;
ModFolderModel m(tempDir.path(), true);
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
m.installMod(folder);
loop.exec();
verify(tempDir.path());
}
@ -41,8 +78,11 @@ slots:
{
QString folder = source + '/';
QTemporaryDir tempDir;
ModFolderModel m(tempDir.path());
QEventLoop loop;
ModFolderModel m(tempDir.path(), true);
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
m.installMod(folder);
loop.exec();
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"
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"
TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) {

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
@ -22,6 +23,10 @@
#include "FileSystem.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)
: 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())) {
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()
{
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);
Metadata::update(m_index_dir, pw_mod);

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
@ -38,13 +39,13 @@
#include "Application.h"
#include "minecraft/mod/MetadataHandler.h"
ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir)
: m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result())
ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed)
: m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result())
{}
void ModFolderLoadTask::run()
{
if (!APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
if (m_is_indexed) {
// Read metadata first
getFromMetadata();
}
@ -53,12 +54,33 @@ void ModFolderLoadTask::run()
m_mods_dir.refresh();
for (auto entry : m_mods_dir.entryInfoList()) {
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 {
m_result->mods[mod.internal_id()] = mod;
m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata);
else {
QString chopped_id = mod.internal_id().chopped(9);
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
* 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
@ -55,7 +56,7 @@ public:
}
public:
ModFolderLoadTask(QDir& mods_dir, QDir& index_dir);
ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed);
void run();
signals:
void succeeded();
@ -65,5 +66,6 @@ private:
private:
QDir& m_mods_dir, m_index_dir;
bool m_is_indexed;
ResultPtr m_result;
};

View File

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

View File

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

View File

@ -60,10 +60,11 @@ namespace ATLauncher {
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_pack = pack;
m_pack_name = packName;
m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), "");
m_version_name = version;
}
@ -81,7 +82,7 @@ void PackInstallTask::executeTask()
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network());
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));
jobPtr = netJob;
jobPtr->start();
@ -98,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded()
QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
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;
return;
}
@ -319,7 +320,7 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
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 = {
{ "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" },
@ -465,7 +466,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<
}
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)) {
f->mainClass = mainClass;
}
@ -507,9 +508,9 @@ void PackInstallTask::installConfigs()
setStatus(tr("Downloading configs..."));
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")
.arg(m_pack).arg(m_version_name);
.arg(m_pack_safe_name).arg(m_version_name);
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
entry->setStale(true);
@ -862,6 +863,7 @@ void PackInstallTask::install()
instance.setName(m_instName);
instance.setIconKey(m_instIcon);
instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name);
instanceSettings->resumeSave();
jarmods.clear();

View File

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

View File

@ -42,6 +42,11 @@ class FlameAPI : public NetworkModAPI {
.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
{
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");
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)

View File

@ -12,6 +12,7 @@
namespace FlameMod {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
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.name = Json::requireString(obj, "name");
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
pack.description = Json::ensureString(obj, "summary", "");
auto logo = Json::requireObject(obj, "logo");
@ -46,6 +45,32 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
if (!found) {
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)

View File

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

View File

@ -31,6 +31,31 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const
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
{
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 {
public:
void searchMods(CallerType* caller, SearchArgs&& args) const override;
void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) override;
void getVersions(CallerType* caller, VersionSearchArgs&& args) const override;
protected:
virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0;
virtual auto getModInfoURL(QString& id) 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>
* Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* 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
* 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.
*
* 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
* 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.
* 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 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"
@ -80,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded()
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
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;
return;
}
@ -220,6 +239,7 @@ void PackInstallTask::install()
instance.setName(m_instName);
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();
emitSucceeded();

View File

@ -76,6 +76,11 @@ class ModrinthAPI : public NetworkModAPI {
.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
{
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.url = api.getAuthorURL(modAuthor.name);
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,

View File

@ -25,6 +25,7 @@
namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
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.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");
if(pack.extra.sourceUrl.endsWith('/'))
pack.extra.sourceUrl.chop(1);
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;
}
@ -95,19 +122,6 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
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
{
ModpackVersion file;
@ -137,9 +151,6 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
auto url = Json::requireString(parent, "url");
if(!validateDownloadUrl(url))
continue;
file.download_url = url;
if(is_primary)
break;

View File

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

View File

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

View File

@ -185,13 +185,22 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
}
}
else if (libraryName.startsWith("net.minecraftforge:minecraftforge:"))
else
{
components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2));
}
else if (libraryName.startsWith("net.fabricmc:fabric-loader:"))
{
components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2));
static QStringList possibleLoaders{
"net.minecraftforge:minecraftforge:",
"net.fabricmc:fabric-loader:",
"org.quiltmc:quilt-loader:"
};
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;
}
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT);
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
if (request.url().host().contains("api.curseforge.com")) {
request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());
};

View File

@ -3,6 +3,7 @@
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
* 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
* it under the terms of the GNU General Public License as published by
@ -43,6 +44,8 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QFile>
#include <QHttpPart>
#include <QUrlQuery>
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
{{"0x0.st", "https://0x0.st", ""},
@ -71,7 +74,7 @@ void PasteUpload::executeTask()
QNetworkRequest request{QUrl(m_uploadUrl)};
QNetworkReply *rep{};
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
switch (m_pasteType) {
case NullPointer: {
@ -91,7 +94,7 @@ void PasteUpload::executeTask()
break;
}
case Hastebin: {
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
rep = APPLICATION->network()->post(request, m_text);
break;
}

View File

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

View File

@ -55,7 +55,7 @@ void ImgurAlbumCreation::executeTask()
{
m_state = State::Running;
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.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
request.setRawHeader("Accept", "application/json");

View File

@ -35,6 +35,7 @@
#include "ImgurUpload.h"
#include "BuildConfig.h"
#include "Application.h"
#include <QNetworkRequest>
#include <QHttpMultiPart>
@ -56,7 +57,7 @@ void ImgurUpload::executeTask()
finished = false;
m_state = Task::State::Running;
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("Accept", "application/json");

View File

@ -52,7 +52,7 @@ QString MCEditTool::getProgramPath()
#else
const QString mceditPath = path();
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"))
{
return mceditDir.absoluteFilePath("mcedit.sh");

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* it under the terms of the GNU General Public License as published by
@ -38,6 +39,7 @@
#include <QClipboard>
#include <QApplication>
#include <QFileDialog>
#include <QStandardPaths>
#include "ui/dialogs/ProgressDialog.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
* Peterix
* Orochimarufan <orochimarufan.x3@gmail.com>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* 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
* 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.
*
* 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
* 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.
* This file incorporates work covered by the following copyright and
* 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.
*/
#include "Application.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)
{
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);
}
}
#endif
setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString());
@ -1337,6 +1360,7 @@ void MainWindow::repopulateAccountsMenu()
ui->profileMenu->addAction(ui->actionManageAccounts);
}
#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::updatesAllowedChanged(bool allowed)
{
if(!BuildConfig.UPDATER_ENABLED)
@ -1345,6 +1369,7 @@ void MainWindow::updatesAllowedChanged(bool allowed)
}
ui->actionCheckUpdate->setEnabled(allowed);
}
#endif
/*
* Assumes the sender is a QAction
@ -1450,6 +1475,7 @@ void MainWindow::updateNewsLabel()
}
}
#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::updateAvailable(GoUpdate::Status status)
{
if(!APPLICATION->updatesAreAllowed())
@ -1475,6 +1501,7 @@ void MainWindow::updateNotAvailable()
UpdateDialog dlg(false, this);
dlg.exec();
}
#endif
QList<int> stringToIntList(const QString &string)
{
@ -1496,6 +1523,7 @@ QString intListToString(const QList<int> &list)
return slist.join(',');
}
#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::downloadUpdates(GoUpdate::Status status)
{
if(!APPLICATION->updatesAreAllowed())
@ -1529,6 +1557,7 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)
CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show();
}
}
#endif
void MainWindow::onCatToggled(bool state)
{
@ -1841,6 +1870,7 @@ void MainWindow::on_actionConfig_Folder_triggered()
}
}
#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::checkForUpdates()
{
if(BuildConfig.UPDATER_ENABLED)
@ -1853,6 +1883,7 @@ void MainWindow::checkForUpdates()
qWarning() << "Updater not set up. Cannot check for updates.";
}
}
#endif
void MainWindow::on_actionSettings_triggered()
{
@ -2101,6 +2132,9 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
selectionBad();
return;
}
if (m_selectedInstance) {
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
}
QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
if (m_selectedInstance)
@ -2127,6 +2161,8 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
updateToolsMenu();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
}
else
{
@ -2216,3 +2252,9 @@ void MainWindow::updateStatusCenter()
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");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* 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
* 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.
* 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
*
* 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
@ -54,7 +78,9 @@ public:
void checkInstancePathForProblems();
#ifdef LAUNCHER_WITH_UPDATER
void updatesAllowedChanged(bool allowed);
#endif
void droppedURLs(QList<QUrl> urls);
signals:
@ -100,7 +126,9 @@ private slots:
void on_actionViewCentralModsFolder_triggered();
#ifdef LAUNCHER_WITH_UPDATER
void checkForUpdates();
#endif
void on_actionSettings_triggered();
@ -167,9 +195,11 @@ private slots:
void startTask(Task *task);
#ifdef LAUNCHER_WITH_UPDATER
void updateAvailable(GoUpdate::Status status);
void updateNotAvailable();
#endif
void defaultAccountChanged();
@ -179,10 +209,12 @@ private slots:
void updateNewsLabel();
#ifdef LAUNCHER_WITH_UPDATER
/*!
* Runs the DownloadTask and installs updates.
*/
void downloadUpdates(GoUpdate::Status status);
#endif
void konamiTriggered();
@ -192,6 +224,8 @@ private slots:
void keyReleaseEvent(QKeyEvent *event) override;
#endif
void refreshCurrentInstance(bool running);
private:
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.
updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex());
ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry));
ui->tabWidget->tabBar()->hide();
ui->metaURL->setPlaceholderText(BuildConfig.META_URL);
ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT);
loadSettings();
@ -139,6 +139,8 @@ void APIPage::loadSettings()
ui->metaURL->setText(metaURL);
QString curseKey = s->get("CFKeyOverride").toString();
ui->curseKey->setText(curseKey);
QString customUserAgent = s->get("UserAgentOverride").toString();
ui->userAgentLineEdit->setText(customUserAgent);
}
void APIPage::applySettings()
@ -167,6 +169,7 @@ void APIPage::applySettings()
s->set("MetaURLOverride", metaURL);
QString curseKey = ui->curseKey->text();
s->set("CFKeyOverride", curseKey);
s->set("UserAgentOverride", ui->userAgentLineEdit->text());
}
bool APIPage::apply()

View File

@ -30,7 +30,7 @@
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string notr="true">Tab 1</string>
<string notr="true">Services</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
@ -85,51 +85,6 @@
</layout>
</widget>
</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>
<widget class="QGroupBox" name="groupBox_meta">
<property name="title">
@ -175,6 +130,71 @@
</layout>
</widget>
</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>
<widget class="QGroupBox" name="groupBox_curse">
<property name="enabled">
@ -183,25 +203,15 @@
<property name="title">
<string>&amp;CurseForge Core API</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Note: you probably don't need to set this if CurseForge already works.</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="curseKey">
<property name="enabled">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>(Default)</string>
</property>
</widget>
</item>
<item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Enter a custom API Key for CurseForge here. </string>
@ -217,6 +227,16 @@
</property>
</widget>
</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>
</widget>
</item>
@ -235,6 +255,51 @@
</item>
</layout>
</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>
</item>
</layout>

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* it under the terms of the GNU General Public License as published by

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* 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();
loadSettings();
#ifdef LAUNCHER_WITH_UPDATER
if(BuildConfig.UPDATER_ENABLED)
{
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);
}
ui->updateSettingsBox->setHidden(false);
}
else
{
ui->updateSettingsBox->setHidden(true);
}
#endif
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), 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());
}
#ifdef LAUNCHER_WITH_UPDATER
void LauncherPage::refreshUpdateChannelList()
{
// 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;
}
}
#endif
void LauncherPage::applySettings()
{
@ -450,7 +451,7 @@ void LauncherPage::loadSettings()
}
// Mods
ui->metadataDisableBtn->setChecked(s->get("DontUseModMetadata").toBool());
ui->metadataDisableBtn->setChecked(s->get("ModMetadataDisabled").toBool());
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
}

View File

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

View File

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

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* it under the terms of the GNU General Public License as published by
@ -39,6 +40,7 @@
#include "Application.h"
#include <QIcon>
#include <QIdentityProxyModel>
#include <QScrollBar>
#include <QShortcut>

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* 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 <Application.h>
#include <QSortFilterProxyModel>
class ModFolderModel;
namespace Ui

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* it under the terms of the GNU General Public License as published by

View File

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

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* it under the terms of the GNU General Public License as published by
@ -47,6 +48,7 @@
#include <QFileSystemWatcher>
#include <QMenu>
#include <QTimer>
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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
* it under the terms of the GNU General Public License as published by
@ -46,6 +47,7 @@
#include <QInputDialog>
#include <QProcess>
#include <Qt>
#include <QSortFilterProxyModel>
#include "tools/MCEditTool.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
QFileInfo fi(input);
// mrpack is a modrinth pack
// Allow non-latin people to use ZIP files!
auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
if(fi.exists() && (zip || fi.suffix() == "mrpack"))
bool isZip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
// mrpack is a modrinth pack
bool isMRPack = fi.suffix() == "mrpack";
if(fi.exists() && (isZip || isMRPack))
{
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
@ -149,7 +151,8 @@ void ImportPage::setUrl(const QString& url)
void ImportPage::on_modpackBtn_clicked()
{
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);
if (url.isValid())
{

View File

@ -96,6 +96,11 @@ void ListModel::performPaginatedSearch()
this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() });
}
void ListModel::requestModInfo(ModPlatform::IndexedPack& current)
{
m_parent->apiProvider()->getModInfo(this, current);
}
void ListModel::refresh()
{
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)
{
auto& current = m_parent->getCurrent();

View File

@ -36,9 +36,11 @@ class ListModel : public QAbstractListModel {
void fetchMore(const QModelIndex& parent) override;
void refresh();
void searchWithTerm(const QString& term, const int sort, const bool filter_changed);
void requestModInfo(ModPlatform::IndexedPack& current);
void requestModVersions(const ModPlatform::IndexedPack& current);
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;
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
@ -49,6 +51,8 @@ class ListModel : public QAbstractListModel {
void searchRequestFinished(QJsonDocument& doc);
void searchRequestFailed(QString reason);
void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack);
void versionRequestSucceeded(QJsonDocument doc, QString addonId);
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 "Application.h"
#include "ui_ModPage.h"
#include <QKeyEvent>
@ -94,28 +130,6 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
if (!first.isValid()) { return; }
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) {
qDebug() << QString("Loading %1 mod versions").arg(debugName());
@ -132,6 +146,13 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
updateSelectionButton();
}
if(!current.extraDataLoaded){
qDebug() << QString("Loading %1 mod info").arg(debugName());
listModel->requestModInfo(current);
}
updateUi();
}
void ModPage::onVersionSelectionChanged(QString data)
@ -150,7 +171,8 @@ void ModPage::onModSelected()
if (dialog->isModSelected(current.name, version.fileName)) {
dialog->removeSelectedMod(current.name);
} 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();
@ -207,3 +229,61 @@ void ModPage::updateSelectionButton()
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 updateUi();
auto shouldDisplay() const -> bool override = 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 getDialog() const -> const ModDownloadDialog* { return dialog; }

View File

@ -117,7 +117,7 @@ void AtlPage::suggestCurrent()
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 url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());
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>();
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) {
qDebug() << "Loading flame modpack versions";
@ -188,6 +165,8 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
suggestCurrent();
}
updateUi();
}
void FlamePage::suggestCurrent()
@ -217,3 +196,46 @@ void FlamePage::onVersionSelectionChanged(QString data)
selectedVersion = ui->versionSelectionBox->currentData().toString();
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;
void retranslate() override;
void updateUi();
void openedImpl() override;
bool eventFilter(QObject * watched, QEvent * event) override;

View File

@ -122,10 +122,10 @@ void ListModel::requestFinished()
jobPtr.reset();
remainingPacks.clear();
QJsonParseError parse_error;
QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
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;
return;
}
@ -169,7 +169,7 @@ void ListModel::packRequestFinished()
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
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;
return;
}
@ -184,7 +184,7 @@ void ListModel::packRequestFinished()
catch (const JSONValidationError &e)
{
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;
}
@ -192,7 +192,7 @@ void ListModel::packRequestFinished()
// ignore those "dud" packs.
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
{
@ -270,7 +270,7 @@ void ListModel::requestLogo(QString logo, QString url)
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));
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 "Application.h"
@ -11,6 +46,8 @@
#include <BuildConfig.h>
#include <net/NetJob.h>
namespace LegacyFTB {
FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)

View File

@ -30,6 +30,11 @@ void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& 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)
{
Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);

View File

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

View File

@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* 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

View File

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

View File

@ -224,7 +224,37 @@ void ModrinthPage::updateUI()
// 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>";
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;
text += h.process(current.extra.body.toUtf8());

View File

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

View File

@ -24,24 +24,65 @@ import java.net.URL;
import java.util.Map;
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 {
private final Map<String, String> params = new TreeMap<>();
private final Applet wrappedApplet;
private Applet wrappedApplet;
private URL documentBase;
private boolean active = false;
public Launcher(Applet applet) {
this(applet, null);
}
public Launcher(Applet applet, URL documentBase) {
this.setLayout(new BorderLayout());
this.add(applet, "Center");
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);
}
@ -54,7 +95,7 @@ public final class Launcher extends Applet implements AppletStub {
try {
return super.getParameter(name);
} catch (Exception ignore) {}
} catch (Exception ignored) {}
return null;
}
@ -108,25 +149,13 @@ public final class Launcher extends Applet implements AppletStub {
try {
return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return null;
}
@Override
public URL getDocumentBase() {
try {
// 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;
return documentBase;
}
@Override

View File

@ -14,6 +14,7 @@
, quazip
, libGL
, msaClientID ? ""
, extraJDKs ? [ ]
# flake
, self
@ -36,6 +37,8 @@ let
# This variable will be passed to Minecraft by PolyMC
gameLibraryPath = libpath + ":/run/opengl-driver/lib";
javaPaths = lib.makeSearchPath "bin/java" ([ jdk jdk8 ] ++ extraJDKs);
in
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
wrapQtApp $out/bin/polymc \
--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 ]}
'';

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_SVG "program_info/org.polymc.PolyMC.svg" 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_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.manifest.in polymc.manifest @ONLY)
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
Name "PolyMC"
InstallDir "$LOCALAPPDATA\Programs\PolyMC"
InstallDirRegKey HKCU "Software\PolyMC" "InstallDir"
Name "@Launcher_CommonName@"
InstallDir "$LOCALAPPDATA\Programs\@Launcher_CommonName@"
InstallDirRegKey HKCU "Software\@Launcher_CommonName@" "InstallDir"
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_DIRECTORY
!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_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
Section "PolyMC"
Section "@Launcher_CommonName@"
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
File "polymc.exe"
File "@Launcher_APP_BINARY_NAME@.exe"
File "qt.conf"
File *.dll
File /r "iconengines"
@ -117,20 +127,20 @@ Section "PolyMC"
File /r "styles"
; 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
${GetParameters} $R0
${GetOptions} $R0 "/NoUninstaller" $R1
${If} ${Errors}
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe"
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_CommonName@"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe"
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors"
WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}"
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors"
WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0"
@ -143,13 +153,13 @@ SectionEnd
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
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
@ -159,12 +169,12 @@ SectionEnd
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\PolyMC
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@
Delete $INSTDIR\polymc.exe
Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe
Delete $INSTDIR\uninstall.exe
Delete $INSTDIR\portable.txt
@ -220,8 +230,8 @@ Section "Uninstall"
RMDir /r $INSTDIR\platforms
RMDir /r $INSTDIR\styles
Delete "$SMPROGRAMS\PolyMC.lnk"
Delete "$DESKTOP\PolyMC.lnk"
Delete "$SMPROGRAMS\@Launcher_CommonName@.lnk"
Delete "$DESKTOP\@Launcher_CommonName@.lnk"
RMDir "$INSTDIR"