Compare commits

...

23 commits

Author SHA1 Message Date
Alexandru Ionut Tripon
9a9e8573aa
[release-11.x] chore: bump version (#5376) 2026-04-12 18:38:53 +03:00
Octol1ttle
399482270f
chore: bump version
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
2026-04-12 20:35:15 +05:00
Alexandru Ionut Tripon
50df409b28
[Backport release-11.x] Updater: Do not reset current task in finished signal (#5372) 2026-04-12 11:52:52 +03:00
Octol1ttle
add3d01f84 fix(updater): do not reset current task in finished signal
The order of signals in case of a success is "succeeded"->"finished"

The "succeeded" signal may launch another download if the updater needs to fetch more pages
But if we reset the task then the newly started download will be disposed and the updater will softlock

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 9b270f783e)
2026-04-12 08:40:20 +00:00
Alexandru Ionut Tripon
a71b8d8fe3
[Backport release-11.x] enable modpack changelog for modrinth page (#5360) 2026-04-11 09:36:29 +03:00
Trial97
6d38b34c00 enable modpack changelog for modrinth page
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit f3ff0a730a)
2026-04-11 05:37:04 +00:00
Alexandru Ionut Tripon
85f19da603
[Backport release-11.x] fix pack upgrade (#5356) 2026-04-10 20:32:09 +03:00
Trial97
239be1ec43 fix pack upgrade
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit b7344af313)
2026-04-10 17:31:48 +00:00
Alexandru Ionut Tripon
34349a6810
[Backport release-11.x] Allow disabling low RAM warning (#5350) 2026-04-10 15:23:20 +03:00
Octol1ttle
fb7e4da4e6 Change LowMemWarning default to always enabled
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 4b3aedd5d0)
2026-04-10 09:31:16 +00:00
Octol1ttle
f766cdd847 change(EnsureAvailableMemory): add lenience
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 658a1391f8)
2026-04-10 09:31:16 +00:00
Octol1ttle
789c656463 feat: allow disabling low RAM warning
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit c044ed36af)
2026-04-10 09:31:16 +00:00
Alexandru Ionut Tripon
2d01dfa4f2
[Backport release-11.x] fix McClient (#5344) 2026-04-10 00:50:18 +03:00
Alexandru Ionut Tripon
9231fe8592
[Backport release-11.x] Don't count JAR mods when checking offline libraries (#5343) 2026-04-10 00:49:58 +03:00
Alexandru Ionut Tripon
25fee30f95
[Backport release-11.x] CI/Nix: Bump macOS (#5342) 2026-04-10 00:49:42 +03:00
Alexandru Ionut Tripon
ec6a173608
[Backport release-11.x] fix(PrintInstanceInfo): add break before OS info (#5341) 2026-04-10 00:49:24 +03:00
Alexandru Ionut Tripon
b7d70dc1c1
chore: bump to 11.0.1 (#5340) 2026-04-10 00:49:09 +03:00
Octol1ttle
09f0467e81 refactor: McClient
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 91616ae9b6)
2026-04-09 21:06:40 +00:00
Octol1ttle
fe02ad8524 fix(McClient): do not use unsigned type for response length
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 2fe0569bd6)
2026-04-09 21:06:40 +00:00
Octol1ttle
f66796e806 fix: don't count JAR mods when checking offline libraries
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit ec4484282c)
2026-04-09 20:56:32 +00:00
Octol1ttle
4cd8c343fe fix(CI/nix): bump macOS
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 724c9a4a2c)
2026-04-09 20:47:44 +00:00
Octol1ttle
960e1bac87 fix(PrintInstanceInfo): add break before OS info
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 4cf8cf7d18)
2026-04-09 20:46:36 +00:00
Trial97
838e7fb8d2 chore: bump to 11.0.1
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2026-04-09 23:33:42 +03:00
16 changed files with 330 additions and 280 deletions

View file

@ -88,7 +88,7 @@ jobs:
- os: ubuntu-22.04-arm - os: ubuntu-22.04-arm
system: aarch64-linux system: aarch64-linux
- os: macos-14 - os: macos-26
system: aarch64-darwin system: aarch64-darwin
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}

View file

@ -181,7 +181,7 @@ set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/"
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 11) set(Launcher_VERSION_MAJOR 11)
set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_PATCH 0) set(Launcher_VERSION_PATCH 2)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}") set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0") set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0")

View file

@ -735,6 +735,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512);
m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::defaultMaxJvmMem()); m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::defaultMaxJvmMem());
m_settings->registerSetting("PermGen", 128); m_settings->registerSetting("PermGen", 128);
m_settings->registerSetting("LowMemWarning", true);
// Java Settings // Java Settings
m_settings->registerSetting("JavaPath", ""); m_settings->registerSetting("JavaPath", "");

View file

@ -924,23 +924,23 @@ class InstanceStaging : public Task {
connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(child, &Task::progress, this, &InstanceStaging::setProgress);
connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress); connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress);
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
m_backoffTimer.setSingleShot(true);
} }
virtual ~InstanceStaging() {} ~InstanceStaging() override = default;
// FIXME/TODO: add ability to abort during instance commit retries // FIXME/TODO: add ability to abort during instance commit retries
bool abort() override bool abort() override
{ {
if (!canAbort()) if (!canAbort()) {
return false; return false;
}
return m_child->abort(); return m_child->abort();
} }
bool canAbort() const override { return (m_child && m_child->canAbort()); } bool canAbort() const override { return (m_child && m_child->canAbort()); }
protected: protected:
virtual void executeTask() override void executeTask() override
{ {
if (m_stagingPath.isNull()) { if (m_stagingPath.isNull()) {
emitFailed(tr("Could not create staging folder")); emitFailed(tr("Could not create staging folder"));
@ -954,10 +954,8 @@ class InstanceStaging : public Task {
private slots: private slots:
void childSucceeded() void childSucceeded()
{ {
if (!isRunning())
return;
unsigned sleepTime = backoff(); unsigned sleepTime = backoff();
if (m_parent->commitStagedInstance(m_stagingPath, *m_child.get(), m_child->group(), *m_child.get())) { if (m_parent->commitStagedInstance(m_stagingPath, *m_child, m_child->group(), *m_child)) {
m_backoffTimer.stop(); m_backoffTimer.stop();
emitSucceeded(); emitSucceeded();
return; return;

View file

@ -349,7 +349,8 @@ void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext,
QStringList& jars, QStringList& jars,
QStringList& nativeJars, QStringList& nativeJars,
const QString& overridePath, const QString& overridePath,
const QString& tempPath) const const QString& tempPath,
bool addJarMods) const
{ {
QStringList native32, native64; QStringList native32, native64;
jars.clear(); jars.clear();
@ -360,7 +361,7 @@ void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext,
// NOTE: order is important here, add main jar last to the lists // NOTE: order is important here, add main jar last to the lists
if (m_mainJar) { if (m_mainJar) {
// FIXME: HACK!! jar modding is weird and unsystematic! // FIXME: HACK!! jar modding is weird and unsystematic!
if (m_jarMods.size()) { if (m_jarMods.size() && addJarMods) {
QDir tempDir(tempPath); QDir tempDir(tempPath);
jars.append(tempDir.absoluteFilePath("minecraft.jar")); jars.append(tempDir.absoluteFilePath("minecraft.jar"));
} else { } else {

View file

@ -87,7 +87,8 @@ class LaunchProfile : public ProblemProvider {
QStringList& jars, QStringList& jars,
QStringList& nativeJars, QStringList& nativeJars,
const QString& overridePath, const QString& overridePath,
const QString& tempPath) const; const QString& tempPath,
bool addJarMods = true) const;
bool hasTrait(const QString& trait) const; bool hasTrait(const QString& trait) const;
ProblemSeverity getProblemSeverity() const override; ProblemSeverity getProblemSeverity() const override;
const QList<PatchProblem> getProblems() const override; const QList<PatchProblem> getProblems() const override;

View file

@ -209,6 +209,7 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting); m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting);
m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting); m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting); m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
m_settings->registerOverride(global_settings->getSetting("LowMemWarning"), memorySetting);
// Native library workarounds // Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);

View file

@ -30,21 +30,26 @@ void EnsureAvailableMemory::executeTask()
const uint64_t max = m_instance->settings()->get("MaxMemAlloc").toUInt(); const uint64_t max = m_instance->settings()->get("MaxMemAlloc").toUInt();
const uint64_t required = std::max(min, max); const uint64_t required = std::max(min, max);
if (required > available) { if (static_cast<double>(required) * 0.9 > static_cast<double>(available)) {
auto* dialog = CustomMessageBox::selectable( bool shouldAbort = false;
nullptr, tr("Not enough RAM"),
tr("There is not enough RAM available to launch this instance with the current memory settings.\n\n" if (m_instance->settings()->get("LowMemWarning").toBool()) {
"Required: %1 MiB\nAvailable: %2 MiB\n\n" auto* dialog = CustomMessageBox::selectable(
"Continue anyway? This may cause slowdowns in the game and your system.") nullptr, tr("Not enough RAM"),
.arg(required) tr("There is not enough RAM available to launch this instance with the current memory settings.\n\n"
.arg(available), "Required: %1 MiB\nAvailable: %2 MiB\n\n"
QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, "Continue anyway? This may cause slowdowns in the game and your system.")
QMessageBox::StandardButton::No); .arg(required)
const auto response = dialog->exec(); .arg(available),
dialog->deleteLater(); QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
QMessageBox::StandardButton::No);
shouldAbort = dialog->exec() == QMessageBox::No;
dialog->deleteLater();
}
const auto message = tr("Not enough RAM available to launch this instance"); const auto message = tr("Not enough RAM available to launch this instance");
if (response == QMessageBox::No) { if (shouldAbort) {
emit logLine(message, MessageLevel::Fatal); emit logLine(message, MessageLevel::Fatal);
emitFailed(message); emitFailed(message);
return; return;

View file

@ -27,16 +27,27 @@ void EnsureOfflineLibraries::executeTask()
{ {
const auto profile = m_instance->getPackProfile()->getProfile(); const auto profile = m_instance->getPackProfile()->getProfile();
QStringList allJars; QStringList allJars;
profile->getLibraryFiles(m_instance->runtimeContext(), allJars, allJars, m_instance->getLocalLibraryPath(), m_instance->binRoot()); profile->getLibraryFiles(m_instance->runtimeContext(), allJars, allJars, m_instance->getLocalLibraryPath(), m_instance->binRoot(),
false);
QStringList missing;
for (const auto& jar : allJars) { for (const auto& jar : allJars) {
if (!QFileInfo::exists(jar)) { if (!QFileInfo::exists(jar)) {
emit logLine(tr("This instance cannot be launched because some libraries are missing or have not been downloaded yet. Please " missing.append(jar);
"try again in online mode with a working Internet connection"),
MessageLevel::Fatal);
emitFailed("Required libraries are missing");
return;
} }
} }
emitSucceeded(); if (missing.isEmpty()) {
emitSucceeded();
return;
}
emit logLine("Missing libraries:", MessageLevel::Error);
for (const auto& jar : missing) {
emit logLine(" " + jar, MessageLevel::Error);
}
emit logLine(tr("\nThis instance cannot be launched because some libraries are missing or have not been downloaded yet. Please "
"try again in online mode with a working Internet connection"),
MessageLevel::Fatal);
emitFailed("Required libraries are missing");
} }

View file

@ -61,6 +61,7 @@ void PrintInstanceInfo::executeTask()
auto instance = m_parent->instance(); auto instance = m_parent->instance();
QStringList log; QStringList log;
log << "";
log << "OS: " + QString("%1 | %2 | %3").arg(QSysInfo::prettyProductName(), QSysInfo::kernelType(), QSysInfo::kernelVersion()); log << "OS: " + QString("%1 | %2 | %3").arg(QSysInfo::prettyProductName(), QSysInfo::kernelType(), QSysInfo::kernelVersion());
#ifdef Q_OS_FREEBSD #ifdef Q_OS_FREEBSD
::runSysctlHwModel(log); ::runSysctlHwModel(log);

View file

@ -202,23 +202,24 @@ bool ManagedPackPage::runUpdateTask(InstanceTask* task)
unique_qobject_ptr<Task> wrapped_task(APPLICATION->instances()->wrapInstanceTask(task)); unique_qobject_ptr<Task> wrapped_task(APPLICATION->instances()->wrapInstanceTask(task));
connect(task, &Task::failed, connect(wrapped_task.get(), &Task::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
connect(task, &Task::succeeded, [this, task]() { connect(wrapped_task.get(), &Task::succeeded, [this, task]() {
QStringList warnings = task->warnings(); QStringList warnings = task->warnings();
if (warnings.count()) if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
}
}); });
connect(task, &Task::aborted, [this] { connect(wrapped_task.get(), &Task::aborted, [this] {
CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information) CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)
->show(); ->show();
}); });
ProgressDialog loadDialog(this); ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort")); loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(task); loadDialog.execWithTask(wrapped_task.get());
return task->wasSuccessful(); return wrapped_task->wasSuccessful();
} }
void ManagedPackPage::suggestVersion() void ManagedPackPage::suggestVersion()
@ -260,14 +261,16 @@ void ModrinthManagedPackPage::parseManagedPack()
qDebug() << "Parsing Modrinth pack"; qDebug() << "Parsing Modrinth pack";
// No need for the extra work because we already have everything we need. // No need for the extra work because we already have everything we need.
if (m_loaded) if (m_loaded) {
return; return;
}
if (m_fetch_job && m_fetch_job->isRunning()) if (m_fetch_job && m_fetch_job->isRunning()) {
m_fetch_job->abort(); m_fetch_job->abort();
}
ResourceAPI::Callback<QVector<ModPlatform::IndexedVersion>> callbacks{}; ResourceAPI::Callback<QVector<ModPlatform::IndexedVersion>> callbacks{};
m_pack = { m_inst->getManagedPackID() }; m_pack = { .addonId = m_inst->getManagedPackID() };
// Use default if no callbacks are set // Use default if no callbacks are set
callbacks.on_succeed = [this](auto& doc) { callbacks.on_succeed = [this](auto& doc) {
@ -284,8 +287,9 @@ void ModrinthManagedPackPage::parseManagedPack()
// NOTE: the id from version isn't the same id in the modpack format spec... // NOTE: the id from version isn't the same id in the modpack format spec...
// e.g. HexMC's 4.4.0 has versionId 4.0.0 in the modpack index.............. // e.g. HexMC's 4.4.0 has versionId 4.0.0 in the modpack index..............
if (version.version == m_inst->getManagedPackVersionName()) if (version.version == m_inst->getManagedPackVersionName()) {
name = tr("%1 (Current)").arg(name); name = tr("%1 (Current)").arg(name);
}
ui->versionsComboBox->addItem(name, version.fileId); ui->versionsComboBox->addItem(name, version.fileId);
} }
@ -294,10 +298,14 @@ void ModrinthManagedPackPage::parseManagedPack()
m_loaded = true; m_loaded = true;
}; };
callbacks.on_fail = [this](QString reason, int) { setFailState(); }; callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); };
callbacks.on_abort = [this]() { setFailState(); }; callbacks.on_abort = [this]() { setFailState(); };
m_fetch_job = m_api.getProjectVersions( m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
{ std::make_shared<ModPlatform::IndexedPack>(m_pack), {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); .mcVersions = {},
.loaders = {},
.resourceType = ModPlatform::ResourceType::Modpack,
.includeChangelog = true },
std::move(callbacks));
ui->changelogTextBrowser->setText(tr("Fetching changelogs...")); ui->changelogTextBrowser->setText(tr("Fetching changelogs..."));
@ -406,14 +414,16 @@ void FlameManagedPackPage::parseManagedPack()
} }
// No need for the extra work because we already have everything we need. // No need for the extra work because we already have everything we need.
if (m_loaded) if (m_loaded) {
return; return;
}
if (m_fetch_job && m_fetch_job->isRunning()) if (m_fetch_job && m_fetch_job->isRunning()) {
m_fetch_job->abort(); m_fetch_job->abort();
}
QString id = m_inst->getManagedPackID(); QString id = m_inst->getManagedPackID();
m_pack = { id }; m_pack = { .addonId = id };
ResourceAPI::Callback<QVector<ModPlatform::IndexedVersion>> callbacks{}; ResourceAPI::Callback<QVector<ModPlatform::IndexedVersion>> callbacks{};
@ -430,8 +440,9 @@ void FlameManagedPackPage::parseManagedPack()
for (const auto& version : m_pack.versions) { for (const auto& version : m_pack.versions) {
QString name = version.getVersionDisplayString(); QString name = version.getVersionDisplayString();
if (version.fileId == m_inst->getManagedPackVersionID().toInt()) if (version.fileId == m_inst->getManagedPackVersionID().toInt()) {
name = tr("%1 (Current)").arg(name); name = tr("%1 (Current)").arg(name);
}
ui->versionsComboBox->addItem(name, QVariant(version.fileId)); ui->versionsComboBox->addItem(name, QVariant(version.fileId));
} }
@ -440,10 +451,14 @@ void FlameManagedPackPage::parseManagedPack()
m_loaded = true; m_loaded = true;
}; };
callbacks.on_fail = [this](QString reason, int) { setFailState(); }; callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); };
callbacks.on_abort = [this]() { setFailState(); }; callbacks.on_abort = [this]() { setFailState(); };
m_fetch_job = m_api.getProjectVersions( m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
{ std::make_shared<ModPlatform::IndexedPack>(m_pack), {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); .mcVersions = {},
.loaders = {},
.resourceType = ModPlatform::ResourceType::Modpack,
.includeChangelog = true },
std::move(callbacks));
m_fetch_job->start(); m_fetch_job->start();
} }

View file

@ -1,18 +1,17 @@
#include "McClient.h"
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QObject> #include <QObject>
#include <QTcpSocket> #include <QTcpSocket>
#include <utility>
#include <Exception.h> #include "Exception.h"
#include "Json.h" #include "Json.h"
#include "McClient.h"
// 7 first bits McClient::McClient(QObject* parent, QString domain, QString ip, const uint16_t port)
#define SEGMENT_BITS 0x7F : QObject(parent), m_domain(std::move(domain)), m_ip(std::move(ip)), m_port(port)
// last bit {}
#define CONTINUE_BIT 0x80
McClient::McClient(QObject* parent, QString domain, QString ip, short port) : QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {}
void McClient::getStatusData() void McClient::getStatusData()
{ {
@ -33,13 +32,12 @@ void McClient::getStatusData()
void McClient::sendRequest() void McClient::sendRequest()
{ {
QByteArray data; QByteArray data;
writeVarInt(data, 0x00); // packet ID writeVarInt(data, 0x00); // packet ID
writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1) writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1)
writeVarInt(data, m_domain.size()); // server address length writeString(data, m_domain); // server address
writeString(data, m_domain.toStdString()); // server address writeUInt16(data, m_port); // server port
writeFixedInt(data, m_port, 2); // server port writeVarInt(data, 0x01); // next state
writeVarInt(data, 0x01); // next state writePacketToSocket(data); // send handshake packet
writePacketToSocket(data); // send handshake packet
writeVarInt(data, 0x00); // packet ID writeVarInt(data, 0x00); // packet ID
writePacketToSocket(data); // send status packet writePacketToSocket(data); // send status packet
@ -47,17 +45,17 @@ void McClient::sendRequest()
void McClient::readRawResponse() void McClient::readRawResponse()
{ {
if (m_responseReadState == 2) { if (m_responseReadState == ResponseReadState::Finished) {
return; return;
} }
m_resp.append(m_socket.readAll()); m_resp.append(m_socket.readAll());
if (m_responseReadState == 0 && m_resp.size() >= 5) { if (m_responseReadState == ResponseReadState::Waiting && m_resp.size() >= 5) {
m_wantedRespLength = readVarInt(m_resp); m_wantedRespLength = readVarInt(m_resp);
m_responseReadState = 1; m_responseReadState = ResponseReadState::GotLength;
} }
if (m_responseReadState == 1 && m_resp.size() >= m_wantedRespLength) { if (m_responseReadState == ResponseReadState::GotLength && m_resp.size() >= m_wantedRespLength) {
if (m_resp.size() > m_wantedRespLength) { if (m_resp.size() > m_wantedRespLength) {
qDebug().nospace() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs " qDebug().nospace() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs "
<< m_resp.size() << " received)"; << m_resp.size() << " received)";
@ -67,7 +65,7 @@ void McClient::readRawResponse()
} catch (const Exception& e) { } catch (const Exception& e) {
emitFail(e.cause()); emitFail(e.cause());
} }
m_responseReadState = 2; m_responseReadState = ResponseReadState::Finished;
} }
} }
@ -75,7 +73,7 @@ void McClient::parseResponse()
{ {
qDebug() << "Received response successfully"; qDebug() << "Received response successfully";
int packetID = readVarInt(m_resp); const int packetID = readVarInt(m_resp);
if (packetID != 0x00) { if (packetID != 0x00) {
throw Exception(QString("Packet ID doesn't match expected value (0x00 vs 0x%1)").arg(packetID, 0, 16)); throw Exception(QString("Packet ID doesn't match expected value (0x00 vs 0x%1)").arg(packetID, 0, 16));
} }
@ -84,7 +82,7 @@ void McClient::parseResponse()
// 'resp' should now be the JSON string // 'resp' should now be the JSON string
QJsonParseError parseError; QJsonParseError parseError;
QJsonDocument doc = Json::parseUntilGarbage(m_resp, &parseError); const QJsonDocument doc = Json::parseUntilGarbage(m_resp, &parseError);
if (parseError.error != QJsonParseError::NoError) { if (parseError.error != QJsonParseError::NoError) {
qDebug() << "Failed to parse JSON:" << parseError.errorString(); qDebug() << "Failed to parse JSON:" << parseError.errorString();
emitFail(parseError.errorString()); emitFail(parseError.errorString());
@ -93,18 +91,23 @@ void McClient::parseResponse()
emitSucceed(doc.object()); emitSucceed(doc.object());
} }
// NOLINTBEGIN(*-signed-bitwise)
// From https://wiki.vg/Protocol#VarInt_and_VarLong // From https://wiki.vg/Protocol#VarInt_and_VarLong
constexpr uint8_t g_varIntValueMask = 0x7F;
constexpr uint8_t g_varIntContinue = 0x80;
void McClient::writeVarInt(QByteArray& data, int value) void McClient::writeVarInt(QByteArray& data, int value)
{ {
while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits while ((value & ~g_varIntValueMask) != 0) { // check if the value is too big to fit in 7 bits
// Write 7 bits // Write 7 bits
data.append((value & SEGMENT_BITS) | CONTINUE_BIT); data.append(static_cast<uint8_t>((value & ~g_varIntValueMask) | g_varIntContinue)); // NOLINT(*-narrowing-conversions)
// Erase theses 7 bits from the value to write // Erase theses 7 bits from the value to write
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
value >>= 7; value >>= 7;
} }
data.append(value); data.append(static_cast<uint8_t>(value)); // NOLINT(*-narrowing-conversions)
} }
// From https://wiki.vg/Protocol#VarInt_and_VarLong // From https://wiki.vg/Protocol#VarInt_and_VarLong
@ -112,53 +115,56 @@ int McClient::readVarInt(QByteArray& data)
{ {
int value = 0; int value = 0;
int position = 0; int position = 0;
char currentByte;
while (position < 32) { while (position < 32) {
currentByte = readByte(data); const uint8_t currentByte = readByte(data);
value |= (currentByte & SEGMENT_BITS) << position; value |= (currentByte & g_varIntValueMask) << position;
if ((currentByte & CONTINUE_BIT) == 0) if ((currentByte & g_varIntContinue) == 0) {
break; break;
}
position += 7; position += 7;
} }
if (position >= 32) if (position >= 32) {
throw Exception("VarInt is too big"); throw Exception("VarInt is too big");
}
return value; return value;
} }
char McClient::readByte(QByteArray& data) // NOLINTEND(*-signed-bitwise)
uint8_t McClient::readByte(QByteArray& data)
{ {
if (data.isEmpty()) { if (data.isEmpty()) {
throw Exception("No more bytes to read"); throw Exception("No more bytes to read");
} }
char byte = data.at(0); const uint8_t byte = data.at(0);
data.remove(0, 1); data.remove(0, 1);
return byte; return byte;
} }
// write number with specified size in big endian format void McClient::writeUInt16(QByteArray& data, const uint16_t value)
void McClient::writeFixedInt(QByteArray& data, int value, int size)
{ {
for (int i = size - 1; i >= 0; i--) { QDataStream stream(&data, QIODeviceBase::Append);
data.append((value >> (i * 8)) & 0xFF); stream.setByteOrder(QDataStream::BigEndian);
} stream << value;
} }
void McClient::writeString(QByteArray& data, const std::string& value) void McClient::writeString(QByteArray& data, const QString& value)
{ {
data.append(value.c_str()); writeVarInt(data, static_cast<int32_t>(value.size()));
data.append(value.toUtf8());
} }
void McClient::writePacketToSocket(QByteArray& data) void McClient::writePacketToSocket(QByteArray& data)
{ {
// we prefix the packet with its length // we prefix the packet with its length
QByteArray dataWithSize; QByteArray dataWithSize;
writeVarInt(dataWithSize, data.size()); writeVarInt(dataWithSize, static_cast<int32_t>(data.size()));
dataWithSize.append(data); dataWithSize.append(data);
// write it to the socket // write it to the socket
@ -168,7 +174,7 @@ void McClient::writePacketToSocket(QByteArray& data)
data.clear(); data.clear();
} }
void McClient::emitFail(QString error) void McClient::emitFail(const QString& error)
{ {
qDebug() << "Minecraft server ping for status error:" << error; qDebug() << "Minecraft server ping for status error:" << error;
emit failed(error); emit failed(error);
@ -177,6 +183,6 @@ void McClient::emitFail(QString error)
void McClient::emitSucceed(QJsonObject data) void McClient::emitSucceed(QJsonObject data)
{ {
emit succeeded(data); emit succeeded(std::move(data));
emit finished(); emit finished();
} }

View file

@ -1,53 +1,54 @@
#pragma once #pragma once
#include <QFuture> #include <QFuture>
#include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QObject> #include <QObject>
#include <QTcpSocket> #include <QTcpSocket>
#include <Exception.h>
// Client for the Minecraft protocol // Client for the Minecraft protocol
class McClient : public QObject { class McClient : public QObject {
Q_OBJECT Q_OBJECT
QString m_domain;
QString m_ip;
short m_port;
QTcpSocket m_socket;
// 0: did not start reading the response yet
// 1: read the response length, still reading the response
// 2: finished reading the response
unsigned m_responseReadState = 0;
unsigned m_wantedRespLength = 0;
QByteArray m_resp;
public: public:
explicit McClient(QObject* parent, QString domain, QString ip, short port); explicit McClient(QObject* parent, QString domain, QString ip, uint16_t port);
//! Read status data of the server, and calls the succeeded() signal with the parsed JSON data //! Read status data of the server, and calls the succeeded() signal with the parsed JSON data
void getStatusData(); void getStatusData();
signals:
void succeeded(QJsonObject data);
void failed(QString error);
void finished();
private:
static uint8_t readByte(QByteArray& data);
static int readVarInt(QByteArray& data);
static void writeUInt16(QByteArray& data, uint16_t value);
static void writeString(QByteArray& data, const QString& value);
static void writeVarInt(QByteArray& data, int value);
private: private:
void sendRequest(); void sendRequest();
//! Accumulate data until we have a full response, then call parseResponse() once //! Accumulate data until we have a full response, then call parseResponse() once
void readRawResponse(); void readRawResponse();
void parseResponse(); void parseResponse();
void writeVarInt(QByteArray& data, int value);
int readVarInt(QByteArray& data);
char readByte(QByteArray& data);
//! write number with specified size in big endian format
void writeFixedInt(QByteArray& data, int value, int size);
void writeString(QByteArray& data, const std::string& value);
void writePacketToSocket(QByteArray& data); void writePacketToSocket(QByteArray& data);
void emitFail(QString error); void emitFail(const QString& error);
void emitSucceed(QJsonObject data); void emitSucceed(QJsonObject data);
signals: private:
void succeeded(QJsonObject data); enum class ResponseReadState : uint8_t {
void failed(QString error); Waiting,
void finished(); GotLength,
Finished
};
QString m_domain;
QString m_ip;
uint16_t m_port;
QTcpSocket m_socket;
ResponseReadState m_responseReadState = ResponseReadState::Waiting;
int32_t m_wantedRespLength = 0;
QByteArray m_resp;
}; };

View file

@ -151,6 +151,7 @@ void JavaSettingsWidget::loadSettings()
m_ui->maxMemSpinBox->setValue(min); m_ui->maxMemSpinBox->setValue(min);
} }
m_ui->permGenSpinBox->setValue(settings->get("PermGen").toInt()); m_ui->permGenSpinBox->setValue(settings->get("PermGen").toInt());
m_ui->lowMemWarningCheckBox->setChecked(settings->get("LowMemWarning").toBool());
// Java arguments // Java arguments
m_ui->javaArgumentsGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideJavaArgs").toBool()); m_ui->javaArgumentsGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideJavaArgs").toBool());
@ -205,10 +206,12 @@ void JavaSettingsWidget::saveSettings()
settings->set("MaxMemAlloc", min); settings->set("MaxMemAlloc", min);
} }
settings->set("PermGen", m_ui->permGenSpinBox->value()); settings->set("PermGen", m_ui->permGenSpinBox->value());
settings->set("LowMemWarning", m_ui->lowMemWarningCheckBox->isChecked());
} else { } else {
settings->reset("MinMemAlloc"); settings->reset("MinMemAlloc");
settings->reset("MaxMemAlloc"); settings->reset("MaxMemAlloc");
settings->reset("PermGen"); settings->reset("PermGen");
settings->reset("LowMemWarning");
} }
// Java arguments // Java arguments

View file

@ -55,7 +55,7 @@
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@ -86,7 +86,7 @@
<item> <item>
<spacer name="horizontalSpacer_7"> <spacer name="horizontalSpacer_7">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@ -101,10 +101,10 @@
<item row="9" column="0"> <item row="9" column="0">
<spacer name="verticalSpacer_4"> <spacer name="verticalSpacer_4">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
<property name="sizeType"> <property name="sizeType">
<enum>QSizePolicy::Fixed</enum> <enum>QSizePolicy::Policy::Fixed</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@ -160,10 +160,10 @@
<item row="3" column="0"> <item row="3" column="0">
<spacer name="verticalSpacer_5"> <spacer name="verticalSpacer_5">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
<property name="sizeType"> <property name="sizeType">
<enum>QSizePolicy::Fixed</enum> <enum>QSizePolicy::Policy::Fixed</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@ -190,156 +190,166 @@
<property name="checked"> <property name="checked">
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item row="2" column="2"> <item>
<widget class="QLabel" name="label_3"> <layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="labelMinMem">
<property name="text">
<string>M&amp;inimum Memory Usage:</string>
</property>
<property name="buddy">
<cstring>minMemSpinBox</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QSpinBox" name="minMemSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>The amount of memory Minecraft is started with.</string>
</property>
<property name="suffix">
<string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>8</number>
</property>
<property name="maximum">
<number>1048576</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>256</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>(-Xms)</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelMaxMem">
<property name="text">
<string>Ma&amp;ximum Memory Usage:</string>
</property>
<property name="buddy">
<cstring>maxMemSpinBox</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QSpinBox" name="maxMemSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>The maximum amount of memory Minecraft is allowed to use.</string>
</property>
<property name="suffix">
<string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>8</number>
</property>
<property name="maximum">
<number>1048576</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>1024</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>(-Xmx)</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&amp;PermGen Size:</string>
</property>
<property name="buddy">
<cstring>permGenSpinBox</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QSpinBox" name="permGenSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>The amount of memory available to store loaded Java classes.</string>
</property>
<property name="suffix">
<string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>4</number>
</property>
<property name="maximum">
<number>1048576</number>
</property>
<property name="singleStep">
<number>8</number>
</property>
<property name="value">
<number>64</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>(-XX:PermSize)</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="lowMemWarningCheckBox">
<property name="text"> <property name="text">
<string>(-XX:PermSize)</string> <string>Warn when there is not enough memory available</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item>
<widget class="QSpinBox" name="permGenSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>The amount of memory available to store loaded Java classes.</string>
</property>
<property name="suffix">
<string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>4</number>
</property>
<property name="maximum">
<number>1048576</number>
</property>
<property name="singleStep">
<number>8</number>
</property>
<property name="value">
<number>64</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="maxMemSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>The maximum amount of memory Minecraft is allowed to use.</string>
</property>
<property name="suffix">
<string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>8</number>
</property>
<property name="maximum">
<number>1048576</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>1024</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>(-Xmx)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="minMemSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>The amount of memory Minecraft is started with.</string>
</property>
<property name="suffix">
<string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>8</number>
</property>
<property name="maximum">
<number>1048576</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>256</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&amp;PermGen Size:</string>
</property>
<property name="buddy">
<cstring>permGenSpinBox</cstring>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label">
<property name="text">
<string>(-Xms)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelMaxMem">
<property name="text">
<string>Ma&amp;ximum Memory Usage:</string>
</property>
<property name="buddy">
<cstring>maxMemSpinBox</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelMinMem">
<property name="text">
<string>M&amp;inimum Memory Usage:</string>
</property>
<property name="buddy">
<cstring>minMemSpinBox</cstring>
</property>
</widget>
</item>
<item row="0" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0" colspan="4">
<widget class="QLabel" name="labelMaxMemNotice"> <widget class="QLabel" name="labelMaxMemNotice">
<property name="text"> <property name="text">
<string>Memory Notice</string> <string>Memory Notice</string>
@ -382,9 +392,7 @@
<tabstop>autodownloadJavaCheckBox</tabstop> <tabstop>autodownloadJavaCheckBox</tabstop>
<tabstop>javaTestBtn</tabstop> <tabstop>javaTestBtn</tabstop>
<tabstop>javaDownloadBtn</tabstop> <tabstop>javaDownloadBtn</tabstop>
<tabstop>minMemSpinBox</tabstop>
<tabstop>maxMemSpinBox</tabstop> <tabstop>maxMemSpinBox</tabstop>
<tabstop>permGenSpinBox</tabstop>
<tabstop>jvmArgsTextBox</tabstop> <tabstop>jvmArgsTextBox</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>

View file

@ -1160,8 +1160,6 @@ void PrismUpdaterApp::downloadReleasePage(const QString& api_url, int page)
m_current_task.reset(download); m_current_task.reset(download);
connect(download.get(), &Net::Download::finished, this, [this]() { connect(download.get(), &Net::Download::finished, this, [this]() {
qDebug() << "Download" << m_current_task->getUid().toString() << "finished"; qDebug() << "Download" << m_current_task->getUid().toString() << "finished";
m_current_task.reset();
m_current_url = "";
}); });
QCoreApplication::processEvents(); QCoreApplication::processEvents();