mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2026-06-29 01:54:20 +03:00
Add Babric Mod Loader support
Assisted-by: claude:claude-4.6-sonnet Signed-off-by: Omegaplex <95503207+Omegaplexx@users.noreply.github.com>
This commit is contained in:
parent
9c2c641531
commit
abfa8b51f1
9 changed files with 708 additions and 49 deletions
|
|
@ -1095,6 +1095,10 @@ SET(LAUNCHER_SOURCES
|
|||
ui/dialogs/ResourceUpdateDialog.h
|
||||
ui/dialogs/InstallLoaderDialog.cpp
|
||||
ui/dialogs/InstallLoaderDialog.h
|
||||
|
||||
minecraft/BabricCreationTask.h
|
||||
minecraft/BabricCreationTask.cpp
|
||||
|
||||
ui/dialogs/ChooseOfflineNameDialog.cpp
|
||||
ui/dialogs/ChooseOfflineNameDialog.h
|
||||
|
||||
|
|
|
|||
341
launcher/minecraft/BabricCreationTask.cpp
Normal file
341
launcher/minecraft/BabricCreationTask.cpp
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "BabricCreationTask.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Babric component stack (mirrors babric/prism-instance on GitHub):
|
||||
//
|
||||
// org.lwjgl 2.9.4+legacyfabric.17 [local patch]
|
||||
// net.minecraft b1.7.3 [local patch]
|
||||
// babric b1.7.3 [local patch]
|
||||
// net.fabricmc.intermediary b1.7.3 [local patch]
|
||||
// net.fabricmc.fabric-loader 0.18.4 [Prism meta server]
|
||||
//
|
||||
// The four local patches are embedded directly as C++ raw string literals
|
||||
// so they are always compiled into the binary without any QRC dependency.
|
||||
// To update, replace the JSON text below and rebuild.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static constexpr const char* FABRIC_LOADER_VERSION = "0.18.4";
|
||||
|
||||
static const char* PATCH_ORG_LWJGL = R"({
|
||||
"formatVersion": 1,
|
||||
"libraries": [
|
||||
{
|
||||
"downloads": {
|
||||
"classifiers": {
|
||||
"natives-linux": {
|
||||
"sha1": "7ff832a6eb9ab6a767f1ade2b548092d0fa64795",
|
||||
"size": 10362,
|
||||
"url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar"
|
||||
},
|
||||
"natives-linux-arm32": {
|
||||
"sha1": "f3c455b71c5146acb5f8a9513247fc06db182fd5",
|
||||
"size": 4521,
|
||||
"url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-2.9.4/jinput-platform-2.0.5-natives-linux.jar"
|
||||
},
|
||||
"natives-linux-arm64": {
|
||||
"sha1": "42b388ccb7c63cec4e9f24f4dddef33325f8b212",
|
||||
"size": 10932,
|
||||
"url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-2.9.4/jinput-platform-2.0.5-natives-linux.jar"
|
||||
},
|
||||
"natives-osx": {
|
||||
"sha1": "53f9c919f34d2ca9de8c51fc4e1e8282029a9232",
|
||||
"size": 12186,
|
||||
"url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar"
|
||||
},
|
||||
"natives-osx-arm64": {
|
||||
"sha1": "5189eb40db3087fb11ca063b68fa4f4c20b199dd",
|
||||
"size": 10031,
|
||||
"url": "https://github.com/r58Playz/jinput-m1/raw/main/plugins/OSX/bin/jinput-platform-2.0.5.jar"
|
||||
},
|
||||
"natives-windows": {
|
||||
"sha1": "385ee093e01f587f30ee1c8a2ee7d408fd732e16",
|
||||
"size": 155179,
|
||||
"url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-windows.jar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract": { "exclude": ["META-INF/"] },
|
||||
"name": "net.java.jinput:jinput-platform:2.0.5",
|
||||
"natives": {
|
||||
"linux": "natives-linux",
|
||||
"linux-arm32": "natives-linux-arm32",
|
||||
"linux-arm64": "natives-linux-arm64",
|
||||
"osx": "natives-osx",
|
||||
"osx-arm64": "natives-osx-arm64",
|
||||
"windows": "natives-windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"sha1": "39c7796b469a600f72380316f6b1f11db6c2c7c4",
|
||||
"size": 208338,
|
||||
"url": "https://libraries.minecraft.net/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar"
|
||||
}
|
||||
},
|
||||
"name": "net.java.jinput:jinput:2.0.5"
|
||||
},
|
||||
{
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"sha1": "e12fe1fda814bd348c1579329c86943d2cd3c6a6",
|
||||
"size": 7508,
|
||||
"url": "https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar"
|
||||
}
|
||||
},
|
||||
"name": "net.java.jutils:jutils:1.0.0"
|
||||
},
|
||||
{
|
||||
"extract": { "exclude": ["META-INF/"] },
|
||||
"name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4+legacyfabric.17",
|
||||
"url": "https://maven.legacyfabric.net/",
|
||||
"natives": {
|
||||
"linux": "natives-linux",
|
||||
"linux-arm32": "natives-linux",
|
||||
"linux-arm64": "natives-linux",
|
||||
"osx": "natives-osx",
|
||||
"osx-arm64": "natives-osx",
|
||||
"windows": "natives-windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "org.lwjgl.lwjgl:lwjgl:2.9.4+legacyfabric.17",
|
||||
"url": "https://maven.legacyfabric.net/"
|
||||
},
|
||||
{
|
||||
"name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4+legacyfabric.17",
|
||||
"url": "https://maven.legacyfabric.net/"
|
||||
}
|
||||
],
|
||||
"name": "LWJGL 2",
|
||||
"type": "release",
|
||||
"uid": "org.lwjgl",
|
||||
"version": "2.9.4+legacyfabric.17",
|
||||
"volatile": true
|
||||
})";
|
||||
|
||||
static const char* PATCH_NET_MINECRAFT = R"({
|
||||
"+traits": ["legacyLaunch", "legacyServices", "texturepacks"],
|
||||
"assetIndex": {
|
||||
"id": "pre-1.6",
|
||||
"sha1": "3d8e55480977e32acd9844e545177e69a52f594b",
|
||||
"size": 74091,
|
||||
"totalSize": 49505710,
|
||||
"url": "https://piston-meta.mojang.com/v1/packages/3d8e55480977e32acd9844e545177e69a52f594b/pre-1.6.json"
|
||||
},
|
||||
"compatibleJavaMajors": [8, 17, 21, 25],
|
||||
"compatibleJavaName": "java-runtime-delta",
|
||||
"formatVersion": 1,
|
||||
"mainJar": {
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"sha1": "43db9b498cb67058d2e12d394e6507722e71bb45",
|
||||
"size": 1465375,
|
||||
"url": "https://launcher.mojang.com/v1/objects/43db9b498cb67058d2e12d394e6507722e71bb45/client.jar"
|
||||
}
|
||||
},
|
||||
"name": "com.mojang:minecraft:b1.7.3:client"
|
||||
},
|
||||
"minecraftArguments": "${auth_player_name} ${auth_session} --gameDir ${game_directory} --assetsDir ${game_assets}",
|
||||
"name": "Minecraft",
|
||||
"releaseTime": "2011-07-08T00:00:00+02:00",
|
||||
"requires": [{"suggests": "2.9.4+legacyfabric.17", "uid": "org.lwjgl"}],
|
||||
"type": "old_beta",
|
||||
"uid": "net.minecraft",
|
||||
"version": "b1.7.3"
|
||||
})";
|
||||
|
||||
static const char* PATCH_BABRIC = R"({
|
||||
"formatVersion": 1,
|
||||
"name": "Babric",
|
||||
"uid": "babric",
|
||||
"version": "b1.7.3",
|
||||
"minecraftArguments": "--username ${auth_player_name} --session ${auth_session} --gameDir ${game_directory} --assetsDir ${game_assets}",
|
||||
"+traits": ["noapplet"],
|
||||
"libraries": [
|
||||
{"name": "babric:log4j-config:1.0.0", "url": "https://maven.glass-launcher.net/babric/"},
|
||||
{"name": "net.minecrell:terminalconsoleappender:1.2.0", "url": "https://repo1.maven.org/maven2/"},
|
||||
{"name": "org.slf4j:slf4j-api:1.8.0-beta4"},
|
||||
{"name": "org.apache.logging.log4j:log4j-slf4j18-impl:2.16.0"},
|
||||
{"name": "org.apache.logging.log4j:log4j-api:2.16.0"},
|
||||
{"name": "org.apache.logging.log4j:log4j-core:2.16.0"},
|
||||
{"name": "com.google.code.gson:gson:2.8.9"},
|
||||
{"name": "com.google.guava:guava:31.0.1-jre"},
|
||||
{"name": "com.google.guava:failureaccess:1.0.1"},
|
||||
{"name": "org.apache.commons:commons-lang3:3.12.0"},
|
||||
{"name": "commons-io:commons-io:2.11.0"},
|
||||
{"name": "commons-codec:commons-codec:1.15"}
|
||||
]
|
||||
})";
|
||||
|
||||
static const char* PATCH_INTERMEDIARY = R"({
|
||||
"formatVersion": 1,
|
||||
"libraries": [
|
||||
{"name": "babric:intermediary-upstream:b1.7.3", "url": "https://maven.glass-launcher.net/babric"}
|
||||
],
|
||||
"name": "Intermediary Mappings",
|
||||
"releaseTime": "2024-10-18T18:57:03.000Z",
|
||||
"requires": [{"equals": "b1.7.3", "uid": "net.minecraft"}],
|
||||
"type": "release",
|
||||
"uid": "net.fabricmc.intermediary",
|
||||
"version": "b1.7.3",
|
||||
"volatile": true
|
||||
})";
|
||||
|
||||
// Ordered: org.lwjgl before net.minecraft so LWJGL 2 is on the classpath first.
|
||||
static const QList<QPair<QString, const char*>> LOCAL_PATCHES = {
|
||||
{ "org.lwjgl", PATCH_ORG_LWJGL },
|
||||
{ "net.minecraft", PATCH_NET_MINECRAFT },
|
||||
{ "babric", PATCH_BABRIC },
|
||||
{ "net.fabricmc.intermediary", PATCH_INTERMEDIARY },
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static bool writeFile(const QString& path, const QByteArray& data)
|
||||
{
|
||||
QDir().mkpath(QFileInfo(path).absolutePath());
|
||||
QFile f(path);
|
||||
if (!f.open(QFile::WriteOnly | QFile::Truncate)) {
|
||||
qCritical() << "[Babric] Cannot write" << path << ":" << f.errorString();
|
||||
return false;
|
||||
}
|
||||
const qint64 written = f.write(data);
|
||||
f.close();
|
||||
if (written != data.size()) {
|
||||
qCritical() << "[Babric] Short write to" << path;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool BabricCreationTask::installPatches(PackProfile* profile)
|
||||
{
|
||||
qDebug() << "[Babric] installPatches() start";
|
||||
|
||||
// Step 1: Write all four patch files from the embedded string literals.
|
||||
QMap<QString, QByteArray> written;
|
||||
for (const auto& [uid, json] : LOCAL_PATCHES) {
|
||||
const QByteArray data(json);
|
||||
const QString target = profile->patchFilePathForUid(uid);
|
||||
qDebug() << "[Babric] Writing" << uid << "to" << target;
|
||||
if (!writeFile(target, data)) {
|
||||
qCritical() << "[Babric] Step 1 FAILED for" << uid;
|
||||
return false;
|
||||
}
|
||||
written[uid] = data;
|
||||
}
|
||||
|
||||
// Step 2: Register component versions.
|
||||
//
|
||||
// setComponentVersion("net.minecraft", ..., important=true) calls
|
||||
// Component::revert() internally, which can delete our net.minecraft.json.
|
||||
// We re-write it immediately afterwards to be safe.
|
||||
profile->setComponentVersion("org.lwjgl", "2.9.4+legacyfabric.17");
|
||||
|
||||
profile->setComponentVersion("net.minecraft", "b1.7.3", /*important=*/true);
|
||||
writeFile(profile->patchFilePathForUid("net.minecraft"), written["net.minecraft"]);
|
||||
|
||||
profile->setComponentVersion("babric", "b1.7.3");
|
||||
profile->setComponentVersion("net.fabricmc.intermediary", "b1.7.3");
|
||||
|
||||
// Fabric Loader is fetched from the Prism meta server (no local patch).
|
||||
profile->setComponentVersion("net.fabricmc.fabric-loader",
|
||||
QLatin1String(FABRIC_LOADER_VERSION));
|
||||
|
||||
// Step 3: Re-write ALL patch files a second time to survive any
|
||||
// Component::revert() calls that setComponentVersion() may have fired above.
|
||||
// revert() deletes local patch files for UIDs that exist on the Prism meta
|
||||
// server (net.minecraft and possibly org.lwjgl).
|
||||
for (const auto& [uid, data] : written.asKeyValueRange()) {
|
||||
if (!writeFile(profile->patchFilePathForUid(uid), data))
|
||||
qWarning() << "[Babric] Failed to re-write patch for" << uid;
|
||||
}
|
||||
|
||||
// Step 4: Save immediately so the profile is consistent on disk.
|
||||
profile->saveNow();
|
||||
|
||||
qDebug() << "[Babric] installPatches() done";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void BabricCreationTask::removePatches(PackProfile* profile)
|
||||
{
|
||||
qDebug() << "[Babric] removePatches() start";
|
||||
|
||||
// Remove the four loader components.
|
||||
// Re-scan by UID each iteration because each removal shifts row indices.
|
||||
static const QList<QString> loaderUIDs = {
|
||||
"net.fabricmc.fabric-loader",
|
||||
"babric",
|
||||
"net.fabricmc.intermediary",
|
||||
"org.lwjgl",
|
||||
};
|
||||
for (const QString& uid : loaderUIDs) {
|
||||
for (int i = 0; i < profile->rowCount(); i++) {
|
||||
auto comp = profile->getComponent(i);
|
||||
if (comp && comp->getID() == uid) {
|
||||
profile->remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Also delete the local patch file explicitly.
|
||||
QFile::remove(profile->patchFilePathForUid(uid));
|
||||
}
|
||||
|
||||
// Revert net.minecraft to meta-backed state so the "Custom" label disappears.
|
||||
// Component::revert() deletes patches/net.minecraft.json and clears m_file.
|
||||
auto netMinecraft = profile->getComponent("net.minecraft");
|
||||
if (netMinecraft)
|
||||
netMinecraft->revert();
|
||||
QFile::remove(profile->patchFilePathForUid("net.minecraft"));
|
||||
|
||||
profile->saveNow();
|
||||
qDebug() << "[Babric] removePatches() done";
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<MinecraftInstance> BabricCreationTask::createInstance()
|
||||
{
|
||||
setStatus(tr("Creating Babric instance for Minecraft b1.7.3"));
|
||||
|
||||
auto inst = std::make_unique<MinecraftInstance>(
|
||||
m_globalSettings,
|
||||
std::make_unique<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")),
|
||||
m_stagingPath);
|
||||
|
||||
SettingsObject::Lock lock(inst->settings());
|
||||
|
||||
auto* profile = inst->getPackProfile();
|
||||
profile->buildingFromScratch();
|
||||
|
||||
if (!installPatches(profile)) {
|
||||
setError(tr("Failed to write Babric patch files."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inst->setName(name());
|
||||
inst->setIconKey(m_instIcon);
|
||||
return inst;
|
||||
}
|
||||
40
launcher/minecraft/BabricCreationTask.h
Normal file
40
launcher/minecraft/BabricCreationTask.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "InstanceCreationTask.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
|
||||
class BabricCreationTask final : public InstanceCreationTask {
|
||||
Q_OBJECT
|
||||
public:
|
||||
BabricCreationTask() = default;
|
||||
|
||||
/**
|
||||
* Writes the four Babric patch files from embedded string literals into
|
||||
* the instance's patches/ directory and registers all five component
|
||||
* versions in the PackProfile.
|
||||
*
|
||||
* Safe to call on an already-configured Babric instance (re-install).
|
||||
* Does NOT call resolve() — the caller is responsible for that.
|
||||
*
|
||||
* Returns false on any I/O error.
|
||||
*/
|
||||
static bool installPatches(PackProfile* profile);
|
||||
|
||||
/**
|
||||
* Removes all Babric-related components from the profile and deletes
|
||||
* their local patch files. net.minecraft is intentionally kept so the
|
||||
* user can later install a different loader or change the MC version.
|
||||
*/
|
||||
static void removePatches(PackProfile* profile);
|
||||
|
||||
std::unique_ptr<MinecraftInstance> createInstance() override;
|
||||
};
|
||||
|
|
@ -54,6 +54,7 @@ const QMap<QString, ModloaderMapEntry> Component::KNOWN_MODLOADERS = {
|
|||
{ "net.minecraftforge", { ModPlatform::Forge, { "net.neoforged", "net.fabricmc.fabric-loader", "org.quiltmc.quilt-loader" } } },
|
||||
{ "net.fabricmc.fabric-loader", { ModPlatform::Fabric, { "net.minecraftforge", "net.neoforged", "org.quiltmc.quilt-loader" } } },
|
||||
{ "org.quiltmc.quilt-loader", { ModPlatform::Quilt, { "net.minecraftforge", "net.neoforged", "net.fabricmc.fabric-loader" } } },
|
||||
{ "babric", { ModPlatform::Babric, { "net.minecraftforge", "net.neoforged", "org.quiltmc.quilt-loader" } } },
|
||||
{ "com.mumfrey.liteloader", { ModPlatform::LiteLoader, {} } }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,13 +19,15 @@
|
|||
#include "InstallLoaderDialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QMessageBox>
|
||||
#include <QVBoxLayout>
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "DesktopServices.h"
|
||||
#include "meta/Index.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/BabricCreationTask.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "ui/widgets/PageContainer.h"
|
||||
#include "ui/widgets/VersionSelectWidget.h"
|
||||
|
|
@ -33,7 +35,11 @@
|
|||
class InstallLoaderPage : public VersionSelectWidget, public BasePage {
|
||||
Q_OBJECT
|
||||
public:
|
||||
InstallLoaderPage(const QString& id, const QString& iconName, const QString& name, const Version& oldestVersion, PackProfile* profile)
|
||||
InstallLoaderPage(const QString& id,
|
||||
const QString& iconName,
|
||||
const QString& name,
|
||||
const Version& oldestVersion,
|
||||
PackProfile* profile)
|
||||
: VersionSelectWidget(nullptr), uid(id), iconName(iconName), name(name)
|
||||
{
|
||||
const QString minecraftVersion = profile->getComponentVersion("net.minecraft");
|
||||
|
|
@ -77,17 +83,89 @@ class InstallLoaderPage : public VersionSelectWidget, public BasePage {
|
|||
bool loaded = false;
|
||||
};
|
||||
|
||||
static InstallLoaderPage* pageCast(BasePage* page)
|
||||
class InstallBabricPage : public QWidget, public BasePage {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstallBabricPage(PackProfile* profile, QWidget* parent = nullptr)
|
||||
: QWidget(parent), m_profile(profile)
|
||||
{
|
||||
auto result = dynamic_cast<InstallLoaderPage*>(page);
|
||||
Q_ASSERT(result != nullptr);
|
||||
return result;
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(16, 16, 16, 16);
|
||||
layout->setSpacing(12);
|
||||
|
||||
m_label = new QLabel(this);
|
||||
m_label->setWordWrap(true);
|
||||
m_label->setOpenExternalLinks(true);
|
||||
m_label->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||
layout->addWidget(m_label);
|
||||
layout->addStretch();
|
||||
|
||||
updateLabel();
|
||||
}
|
||||
|
||||
QString id() const override { return "babric"; }
|
||||
QString displayName() const override { return tr("Babric"); }
|
||||
QIcon icon() const override { return QIcon::fromTheme("fabricmc"); }
|
||||
|
||||
bool isCompatible() const
|
||||
{
|
||||
const QString mc = m_profile->getComponentVersion("net.minecraft");
|
||||
return mc.isEmpty() || mc == "b1.7.3";
|
||||
}
|
||||
|
||||
bool install()
|
||||
{
|
||||
if (!BabricCreationTask::installPatches(m_profile))
|
||||
return false;
|
||||
m_profile->resolve(Net::Mode::Online);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void updateLabel()
|
||||
{
|
||||
if (isCompatible()) {
|
||||
m_label->setText(
|
||||
tr("<b>Babric</b> is a Fabric-based mod loader "
|
||||
"for <b>Minecraft Beta 1.7.3</b>."
|
||||
"<br><br>"
|
||||
"Clicking <b>OK</b> will:"
|
||||
"<ul>"
|
||||
"<li>Replace this instance's Minecraft version with <b>b1.7.3</b></li>"
|
||||
"<li>Install <b>Fabric Loader 0.18.4</b> and the Babric mapping layer</li>"
|
||||
"<li>Configure the mod browser to show Babric-compatible mods</li>"
|
||||
"</ul>"));
|
||||
} else {
|
||||
const QString mc = m_profile->getComponentVersion("net.minecraft");
|
||||
m_label->setText(
|
||||
tr("No versions are currently available for Minecraft %1."
|
||||
"<br><br>"
|
||||
"Babric only supports <b>Minecraft Beta 1.7.3</b>.").arg(mc));
|
||||
}
|
||||
}
|
||||
|
||||
PackProfile* m_profile = nullptr;
|
||||
QLabel* m_label = nullptr;
|
||||
};
|
||||
|
||||
|
||||
static InstallLoaderPage* asLoaderPage(BasePage* page)
|
||||
{
|
||||
return dynamic_cast<InstallLoaderPage*>(page);
|
||||
}
|
||||
|
||||
static InstallBabricPage* asBabricPage(BasePage* page)
|
||||
{
|
||||
return dynamic_cast<InstallBabricPage*>(page);
|
||||
}
|
||||
|
||||
InstallLoaderDialog::InstallLoaderDialog(PackProfile* profile, const QString& uid, QWidget* parent)
|
||||
: QDialog(parent), profile(profile), container(new PageContainer(this, QString(), this)), buttons(new QDialogButtonBox(this))
|
||||
: QDialog(parent)
|
||||
, profile(profile)
|
||||
, container(new PageContainer(this, QString(), this))
|
||||
, buttons(new QDialogButtonBox(this))
|
||||
{
|
||||
auto layout = new QVBoxLayout(this);
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
// small margins look ugly on macOS on modal windows
|
||||
#ifndef Q_OS_MACOS
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
|
@ -95,13 +173,19 @@ InstallLoaderDialog::InstallLoaderDialog(PackProfile* profile, const QString& ui
|
|||
container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
layout->addWidget(container);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout(this);
|
||||
auto* buttonLayout = new QHBoxLayout();
|
||||
// small margins look ugly on macOS on modal windows
|
||||
#ifndef Q_OS_MACOS
|
||||
buttonLayout->setContentsMargins(0, 0, 6, 6);
|
||||
#endif
|
||||
auto refreshButton = new QPushButton(tr("&Refresh"), this);
|
||||
connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(true); });
|
||||
|
||||
auto* refreshButton = new QPushButton(tr("&Refresh"), this);
|
||||
connect(refreshButton, &QPushButton::clicked, this, [this] {
|
||||
auto* page = container->selectedPage();
|
||||
if (auto* lp = asLoaderPage(page))
|
||||
lp->loadList(true);
|
||||
// Babric has no remote list to refresh.
|
||||
});
|
||||
buttonLayout->addWidget(refreshButton);
|
||||
|
||||
buttons->setOrientation(Qt::Horizontal);
|
||||
|
|
@ -118,17 +202,29 @@ InstallLoaderDialog::InstallLoaderDialog(PackProfile* profile, const QString& ui
|
|||
setWindowModality(Qt::WindowModal);
|
||||
resize(520, 347);
|
||||
|
||||
// Pre-select requested uid (if any) and wire version-selection → OK state.
|
||||
for (BasePage* page : container->getPages()) {
|
||||
if (page->id() == uid)
|
||||
if (!uid.isEmpty() && page->id() == uid)
|
||||
container->selectPage(page->id());
|
||||
|
||||
connect(pageCast(page), &VersionSelectWidget::selectedVersionChanged, this, [this, page] {
|
||||
if (page->id() == container->selectedPage()->id())
|
||||
if (auto* lp = dynamic_cast<InstallLoaderPage*>(page)) {
|
||||
connect(lp, &VersionSelectWidget::selectedVersionChanged, this, [this, page] {
|
||||
if (page == container->selectedPage())
|
||||
validate(container->selectedPage());
|
||||
});
|
||||
}
|
||||
connect(container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* current) { validate(current); });
|
||||
pageCast(container->selectedPage())->selectSearch();
|
||||
}
|
||||
|
||||
connect(container, &PageContainer::selectedPageChanged, this,
|
||||
[this, refreshButton](BasePage* /*prev*/, BasePage* current) {
|
||||
refreshButton->setVisible(!asBabricPage(current));
|
||||
validate(current);
|
||||
});
|
||||
|
||||
if (auto* lp = asLoaderPage(container->selectedPage()))
|
||||
lp->selectSearch();
|
||||
|
||||
refreshButton->setVisible(!asBabricPage(container->selectedPage()));
|
||||
validate(container->selectedPage());
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +238,8 @@ QList<BasePage*> InstallLoaderDialog::getPages()
|
|||
new InstallLoaderPage("net.fabricmc.fabric-loader", "fabricmc", tr("Fabric"), Version("1.14"), profile),
|
||||
// Quilt
|
||||
new InstallLoaderPage("org.quiltmc.quilt-loader", "quiltmc", tr("Quilt"), Version("1.14"), profile),
|
||||
// Babric
|
||||
new InstallBabricPage(profile),
|
||||
// LiteLoader
|
||||
new InstallLoaderPage("com.mumfrey.liteloader", "liteloader", tr("LiteLoader"), {}, profile)
|
||||
};
|
||||
|
|
@ -154,18 +252,36 @@ QString InstallLoaderDialog::dialogTitle()
|
|||
|
||||
void InstallLoaderDialog::validate(BasePage* page)
|
||||
{
|
||||
buttons->button(QDialogButtonBox::Ok)->setEnabled(pageCast(page)->selectedVersion() != nullptr);
|
||||
bool ok = false;
|
||||
if (auto* bp = asBabricPage(page)) {
|
||||
ok = bp->isCompatible();
|
||||
} else if (auto* lp = asLoaderPage(page)) {
|
||||
ok = lp->selectedVersion() != nullptr;
|
||||
}
|
||||
buttons->button(QDialogButtonBox::Ok)->setEnabled(ok);
|
||||
}
|
||||
|
||||
void InstallLoaderDialog::done(int result)
|
||||
{
|
||||
if (result == Accepted) {
|
||||
auto* page = pageCast(container->selectedPage());
|
||||
if (page->selectedVersion()) {
|
||||
profile->setComponentVersion(page->id(), page->selectedVersion()->descriptor());
|
||||
BasePage* page = container->selectedPage();
|
||||
|
||||
if (auto* bp = asBabricPage(page)) {
|
||||
if (!bp->install()) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Install Error"),
|
||||
tr("Failed to install Babric.\n\n"
|
||||
"Run the launcher from a terminal to see details:\n"
|
||||
" cd install && .\\prismlauncher.exe 2>&1"));
|
||||
return; // keep dialog open so user can try again or cancel
|
||||
}
|
||||
} else if (auto* lp = asLoaderPage(page)) {
|
||||
if (lp->selectedVersion()) {
|
||||
profile->setComponentVersion(lp->id(), lp->selectedVersion()->descriptor());
|
||||
profile->resolve(Net::Mode::Online);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QDialog::done(result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
#include "VersionPage.h"
|
||||
#include "meta/JsonFormat.h"
|
||||
#include "tasks/SequentialTask.h"
|
||||
#include "minecraft/BabricCreationTask.h"
|
||||
#include "ui/dialogs/InstallLoaderDialog.h"
|
||||
#include "ui_VersionPage.h"
|
||||
|
||||
|
|
@ -288,6 +289,31 @@ void VersionPage::on_actionRemove_triggered()
|
|||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
}
|
||||
// Removing any component from the Babric stack removes all of them to prevent a partially-installed, broken state.
|
||||
static const QSet<QString> babricStack = {
|
||||
"babric", "net.fabricmc.intermediary",
|
||||
"org.lwjgl", "net.fabricmc.fabric-loader",
|
||||
};
|
||||
const bool isBabricInstance = m_profile->getComponent("babric") != nullptr;
|
||||
if (isBabricInstance && babricStack.contains(component->getID())) {
|
||||
auto response =
|
||||
CustomMessageBox::selectable(
|
||||
this, tr("Remove Babric"),
|
||||
tr("Removing \"%1\" will remove all Babric components from this instance:\n"
|
||||
"Babric, Intermediary Mappings, LWJGL 2, Fabric Loader.\n\n"
|
||||
"Minecraft b1.7.3 will be kept. Are you sure?")
|
||||
.arg(component->getName()),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
BabricCreationTask::removePatches(m_profile);
|
||||
updateButtons();
|
||||
reloadPackProfile();
|
||||
m_container->refreshContainer();
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: use actual model, not reloading.
|
||||
if (!m_profile->remove(index)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file"));
|
||||
|
|
|
|||
|
|
@ -43,12 +43,27 @@
|
|||
#include "Version.h"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "minecraft/BabricCreationTask.h"
|
||||
#include "minecraft/VanillaInstanceCreationTask.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
#include <QTimer>
|
||||
|
||||
CustomPage::CustomPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog(dialog), ui(new Ui::CustomPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// babricInfoLabel and loaderStack come from CustomPage.ui —
|
||||
// no runtime widget creation or layout manipulation needed here.
|
||||
ui->babricInfoLabel->setText(
|
||||
tr("<b>Babric</b> is a Fabric-based mod loader "
|
||||
"for <b>Minecraft Beta 1.7.3</b>.<br><br>"
|
||||
"Clicking <b>OK</b> will create an instance with:<ul>"
|
||||
"<li>Minecraft b1.7.3</li>"
|
||||
"<li>Fabric Loader 0.18.4 + Babric mapping layer</li>"
|
||||
"</ul>"));
|
||||
|
||||
ui->loaderPage1->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
||||
|
||||
connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &CustomPage::setSelectedVersion);
|
||||
filterChanged();
|
||||
connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||
|
|
@ -60,9 +75,11 @@ CustomPage::CustomPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(par
|
|||
|
||||
connect(ui->loaderVersionList, &VersionSelectWidget::selectedVersionChanged, this, &CustomPage::setSelectedLoaderVersion);
|
||||
connect(ui->noneFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
|
||||
connect(ui->neoForgeFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
|
||||
connect(ui->forgeFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
|
||||
connect(ui->fabricFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
|
||||
connect(ui->quiltFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
|
||||
connect(ui->babricFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
|
||||
connect(ui->liteLoaderFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged);
|
||||
connect(ui->loaderRefreshBtn, &QPushButton::clicked, this, &CustomPage::loaderRefresh);
|
||||
}
|
||||
|
|
@ -85,7 +102,7 @@ void CustomPage::refresh()
|
|||
|
||||
void CustomPage::loaderRefresh()
|
||||
{
|
||||
if (ui->noneFilter->isChecked())
|
||||
if (ui->noneFilter->isChecked() || ui->babricFilter->isChecked())
|
||||
return;
|
||||
ui->loaderVersionList->loadList(true);
|
||||
}
|
||||
|
|
@ -107,23 +124,52 @@ void CustomPage::filterChanged()
|
|||
ui->versionList->setFilter(BaseVersionList::TypeRole, Filters::regexp(QRegularExpression(regexp)));
|
||||
}
|
||||
|
||||
void CustomPage::setLoaderWidgetMode(bool isBabric)
|
||||
{
|
||||
ui->loaderStack->setCurrentIndex(isBabric ? 1 : 0);
|
||||
ui->loaderRefreshBtn->setEnabled(!isBabric);
|
||||
|
||||
for (int i = 0; i < ui->verticalLayout_2->count(); i++) {
|
||||
if (QSpacerItem* spacer = ui->verticalLayout_2->itemAt(i)->spacerItem()) {
|
||||
spacer->changeSize(20, 0, QSizePolicy::Minimum,
|
||||
isBabric ? QSizePolicy::Fixed : QSizePolicy::Expanding);
|
||||
ui->verticalLayout_2->invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomPage::loaderFilterChanged()
|
||||
{
|
||||
QString minecraftVersion;
|
||||
if (m_selectedVersion) {
|
||||
minecraftVersion = m_selectedVersion->descriptor();
|
||||
} else {
|
||||
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, "AAA"); // empty list
|
||||
// Always sync the widget visibility first.
|
||||
setLoaderWidgetMode(ui->babricFilter->isChecked());
|
||||
|
||||
const QString minecraftVersion = m_selectedVersion ? m_selectedVersion->descriptor() : QString();
|
||||
|
||||
if (ui->noneFilter->isChecked()) {
|
||||
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, "AAA");
|
||||
ui->loaderVersionList->setEmptyString(tr("No mod loader is selected."));
|
||||
ui->loaderVersionList->setEmptyMode(VersionListView::String);
|
||||
m_selectedLoader.clear();
|
||||
suggestCurrent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ui->babricFilter->isChecked()) {
|
||||
// No meta-server lookup needed — everything is embedded.
|
||||
m_selectedLoader = "babric";
|
||||
suggestCurrent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (minecraftVersion.isEmpty()) {
|
||||
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, "AAA");
|
||||
ui->loaderVersionList->setEmptyString(tr("No Minecraft version is selected."));
|
||||
ui->loaderVersionList->setEmptyMode(VersionListView::String);
|
||||
return;
|
||||
}
|
||||
if (ui->noneFilter->isChecked()) {
|
||||
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, "AAA"); // empty list
|
||||
ui->loaderVersionList->setEmptyString(tr("No mod loader is selected."));
|
||||
ui->loaderVersionList->setEmptyMode(VersionListView::String);
|
||||
return;
|
||||
} else if (ui->neoForgeFilter->isChecked()) {
|
||||
|
||||
if (ui->neoForgeFilter->isChecked()) {
|
||||
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
|
||||
m_selectedLoader = "net.neoforged";
|
||||
} else if (ui->forgeFilter->isChecked()) {
|
||||
|
|
@ -186,7 +232,12 @@ QString CustomPage::selectedLoader() const
|
|||
|
||||
void CustomPage::suggestCurrent()
|
||||
{
|
||||
if (!isOpened) {
|
||||
if (!isOpened)
|
||||
return;
|
||||
|
||||
if (ui->babricFilter->isChecked()) {
|
||||
dialog->setSuggestedPack("b1.7.3", new BabricCreationTask());
|
||||
dialog->setSuggestedIcon("fabric");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
|
||||
#include "BaseVersion.h"
|
||||
|
|
@ -79,6 +80,8 @@ class CustomPage : public QWidget, public BasePage {
|
|||
void loaderRefresh();
|
||||
void suggestCurrent();
|
||||
|
||||
void setLoaderWidgetMode(bool isBabric);
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
|
|
|
|||
|
|
@ -154,6 +154,31 @@
|
|||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="loaderLayout">
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="loaderStack">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="loaderPage0">
|
||||
<layout class="QVBoxLayout" name="loaderPage0Layout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="VersionSelectWidget" name="loaderVersionList" native="true">
|
||||
<property name="sizePolicy">
|
||||
|
|
@ -164,6 +189,48 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="loaderPage1">
|
||||
<layout class="QVBoxLayout" name="loaderPage1Layout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="babricInfoLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Ignored">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
|
|
@ -228,6 +295,16 @@
|
|||
<string notr="true">loaderBtnGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="babricFilter">
|
||||
<property name="text">
|
||||
<string>Babric</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">loaderBtnGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="liteLoaderFilter">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue