GH-405 ATLauncher Support
This commit is contained in:
parent
5e980ceef2
commit
ab19b86341
@ -486,6 +486,15 @@ set(TECHNIC_SOURCES
|
||||
modplatform/technic/TechnicPackProcessor.cpp
|
||||
)
|
||||
|
||||
set(ATLAUNCHER_SOURCES
|
||||
modplatform/atlauncher/ATLPackIndex.cpp
|
||||
modplatform/atlauncher/ATLPackIndex.h
|
||||
modplatform/atlauncher/ATLPackInstallTask.cpp
|
||||
modplatform/atlauncher/ATLPackInstallTask.h
|
||||
modplatform/atlauncher/ATLPackManifest.cpp
|
||||
modplatform/atlauncher/ATLPackManifest.h
|
||||
)
|
||||
|
||||
add_unit_test(Index
|
||||
SOURCES meta/Index_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
@ -518,6 +527,7 @@ set(LOGIC_SOURCES
|
||||
${FLAME_SOURCES}
|
||||
${MODPACKSCH_SOURCES}
|
||||
${TECHNIC_SOURCES}
|
||||
${ATLAUNCHER_SOURCES}
|
||||
)
|
||||
|
||||
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
|
||||
|
@ -96,6 +96,7 @@ void Env::initHttpMetaCache()
|
||||
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
|
||||
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
|
||||
m_metacache->addBase("general", QDir("cache").absolutePath());
|
||||
m_metacache->addBase("ATLauncherPacks", QDir("cache/ATLauncherPacks").absolutePath());
|
||||
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
|
||||
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
|
||||
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
|
||||
|
@ -243,6 +243,12 @@ QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QSt
|
||||
return extracted;
|
||||
}
|
||||
|
||||
// ours
|
||||
bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &target)
|
||||
{
|
||||
return JlCompress::extractFile(zip, file, target);
|
||||
}
|
||||
|
||||
// ours
|
||||
QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||
{
|
||||
@ -253,3 +259,25 @@ QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||
}
|
||||
return MMCZip::extractSubDir(&zip, "", dir);
|
||||
}
|
||||
|
||||
// ours
|
||||
QStringList MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
|
||||
{
|
||||
QuaZip zip(fileCompressed);
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return MMCZip::extractSubDir(&zip, subdir, dir);
|
||||
}
|
||||
|
||||
// ours
|
||||
bool MMCZip::extractFile(QString fileCompressed, QString file, QString target)
|
||||
{
|
||||
QuaZip zip(fileCompressed);
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return MMCZip::extractRelFile(&zip, file, target);
|
||||
}
|
||||
|
@ -59,6 +59,8 @@ namespace MMCZip
|
||||
*/
|
||||
QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
|
||||
|
||||
bool MULTIMC_LOGIC_EXPORT extractRelFile(QuaZip *zip, const QString & file, const QString &target);
|
||||
|
||||
/**
|
||||
* Extract a whole archive.
|
||||
*
|
||||
@ -67,4 +69,25 @@ namespace MMCZip
|
||||
* \return The list of the full paths of the files extracted, empty on failure.
|
||||
*/
|
||||
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
|
||||
|
||||
/**
|
||||
* Extract a subdirectory from an archive
|
||||
*
|
||||
* \param fileCompressed The name of the archive.
|
||||
* \param subdir The directory within the archive to extract
|
||||
* \param dir The directory to extract to, the current directory if left empty.
|
||||
* \return The list of the full paths of the files extracted, empty on failure.
|
||||
*/
|
||||
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString subdir, QString dir);
|
||||
|
||||
/**
|
||||
* Extract a single file from an archive into a directory
|
||||
*
|
||||
* \param fileCompressed The name of the archive.
|
||||
* \param file The file within the archive to extract
|
||||
* \param dir The directory to extract to, the current directory if left empty.
|
||||
* \return true for success or false for failure
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT extractFile(QString fileCompressed, QString file, QString dir);
|
||||
|
||||
}
|
||||
|
@ -114,6 +114,10 @@ public:
|
||||
/// get the profile component by index
|
||||
Component * getComponent(int index);
|
||||
|
||||
/// Add the component to the internal list of patches
|
||||
// todo(merged): is this the best approach
|
||||
void appendComponent(ComponentPtr component);
|
||||
|
||||
private:
|
||||
void scheduleSave();
|
||||
bool saveIsScheduled() const;
|
||||
@ -121,8 +125,6 @@ private:
|
||||
/// apply the component patches. Catches all the errors and returns true/false for success/failure
|
||||
void invalidateLaunchProfile();
|
||||
|
||||
/// Add the component to the internal list of patches
|
||||
void appendComponent(ComponentPtr component);
|
||||
/// insert component so that its index is ideally the specified one (returns real index)
|
||||
void insertComponent(size_t index, ComponentPtr component);
|
||||
|
||||
|
33
api/logic/modplatform/atlauncher/ATLPackIndex.cpp
Normal file
33
api/logic/modplatform/atlauncher/ATLPackIndex.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "ATLPackIndex.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
static void loadIndexedVersion(ATLauncher::IndexedVersion & v, QJsonObject & obj)
|
||||
{
|
||||
v.version = Json::requireString(obj, "version");
|
||||
v.minecraft = Json::requireString(obj, "minecraft");
|
||||
}
|
||||
|
||||
void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack & m, QJsonObject & obj)
|
||||
{
|
||||
m.id = Json::requireInteger(obj, "id");
|
||||
m.position = Json::requireInteger(obj, "position");
|
||||
m.name = Json::requireString(obj, "name");
|
||||
m.type = Json::requireString(obj, "type") == "private" ?
|
||||
ATLauncher::PackType::Private :
|
||||
ATLauncher::PackType::Public;
|
||||
auto versionsArr = Json::requireArray(obj, "versions");
|
||||
for (const auto versionRaw : versionsArr)
|
||||
{
|
||||
auto versionObj = Json::requireObject(versionRaw);
|
||||
ATLauncher::IndexedVersion version;
|
||||
loadIndexedVersion(version, versionObj);
|
||||
m.versions.append(version);
|
||||
}
|
||||
m.system = Json::ensureBoolean(obj, "system", false);
|
||||
m.description = Json::ensureString(obj, "description", "");
|
||||
|
||||
m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "");
|
||||
}
|
36
api/logic/modplatform/atlauncher/ATLPackIndex.h
Normal file
36
api/logic/modplatform/atlauncher/ATLPackIndex.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "ATLPackManifest.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QMetaType>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
namespace ATLauncher
|
||||
{
|
||||
|
||||
struct IndexedVersion
|
||||
{
|
||||
QString version;
|
||||
QString minecraft;
|
||||
};
|
||||
|
||||
struct IndexedPack
|
||||
{
|
||||
int id;
|
||||
int position;
|
||||
QString name;
|
||||
PackType type;
|
||||
QVector<IndexedVersion> versions;
|
||||
bool system;
|
||||
QString description;
|
||||
|
||||
QString safeName;
|
||||
};
|
||||
|
||||
MULTIMC_LOGIC_EXPORT void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(ATLauncher::IndexedPack)
|
655
api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp
Normal file
655
api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp
Normal file
@ -0,0 +1,655 @@
|
||||
#include <Env.h>
|
||||
#include <quazip.h>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <MMCZip.h>
|
||||
#include <minecraft/OneSixVersionFormat.h>
|
||||
#include <Version.h>
|
||||
#include "ATLPackInstallTask.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/Version.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
PackInstallTask::PackInstallTask(QString pack, QString version)
|
||||
{
|
||||
m_pack = pack;
|
||||
m_version_name = version;
|
||||
}
|
||||
|
||||
bool PackInstallTask::abort()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void PackInstallTask::executeTask()
|
||||
{
|
||||
auto *netJob = new NetJob("ATLauncher::VersionFetch");
|
||||
auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json")
|
||||
.arg(m_pack).arg(m_version_name);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadSucceeded()
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
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() << response;
|
||||
return;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
|
||||
ATLauncher::PackVersion version;
|
||||
try
|
||||
{
|
||||
ATLauncher::loadVersion(version, obj);
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
|
||||
return;
|
||||
}
|
||||
m_version = version;
|
||||
|
||||
auto vlist = ENV.metadataIndex()->get("net.minecraft");
|
||||
if(!vlist)
|
||||
{
|
||||
emitFailed(tr("Failed to get local metadata index for ") + "net.minecraft");
|
||||
return;
|
||||
}
|
||||
|
||||
auto ver = vlist->getVersion(m_version.minecraft);
|
||||
if (!ver) {
|
||||
emitFailed(tr("Failed to get local metadata index for ") + "net.minecraft" + " " + m_version.minecraft);
|
||||
return;
|
||||
}
|
||||
ver->load(Net::Mode::Online);
|
||||
minecraftVersion = ver;
|
||||
|
||||
if(m_version.noConfigs) {
|
||||
installMods();
|
||||
}
|
||||
else {
|
||||
installConfigs();
|
||||
}
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadFailed(QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
QString PackInstallTask::getDirForModType(ModType type, QString raw)
|
||||
{
|
||||
switch (type) {
|
||||
// Mod types that can either be ignored at this stage, or ignored
|
||||
// completely.
|
||||
case ModType::Root:
|
||||
case ModType::Extract:
|
||||
case ModType::Decomp:
|
||||
case ModType::TexturePackExtract:
|
||||
case ModType::ResourcePackExtract:
|
||||
case ModType::MCPC:
|
||||
return Q_NULLPTR;
|
||||
case ModType::Forge:
|
||||
// Forge detection happens later on, if it cannot be detected it will
|
||||
// install a jarmod component.
|
||||
case ModType::Jar:
|
||||
return "jarmods";
|
||||
case ModType::Mods:
|
||||
return "mods";
|
||||
case ModType::Flan:
|
||||
return "Flan";
|
||||
case ModType::Dependency:
|
||||
return FS::PathCombine("mods", m_version.minecraft);
|
||||
case ModType::Ic2Lib:
|
||||
return FS::PathCombine("mods", "ic2");
|
||||
case ModType::DenLib:
|
||||
return FS::PathCombine("mods", "denlib");
|
||||
case ModType::Coremods:
|
||||
return "coremods";
|
||||
case ModType::Plugins:
|
||||
return "plugins";
|
||||
case ModType::TexturePack:
|
||||
return "texturepacks";
|
||||
case ModType::ResourcePack:
|
||||
return "resourcepacks";
|
||||
case ModType::ShaderPack:
|
||||
return "shaderpacks";
|
||||
case ModType::Millenaire:
|
||||
qWarning() << "Unsupported mod type: " + raw;
|
||||
return Q_NULLPTR;
|
||||
case ModType::Unknown:
|
||||
emitFailed(tr("Unknown mod type: ") + raw);
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
QString PackInstallTask::getVersionForLoader(QString uid)
|
||||
{
|
||||
if(m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) {
|
||||
auto vlist = ENV.metadataIndex()->get(uid);
|
||||
if(!vlist)
|
||||
{
|
||||
emitFailed(tr("Failed to get local metadata index for ") + uid);
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
// todo: filter by Minecraft version
|
||||
|
||||
if(m_version.loader.recommended) {
|
||||
return vlist.get()->getRecommended().get()->descriptor();
|
||||
}
|
||||
else if(m_version.loader.latest) {
|
||||
return vlist.get()->at(0)->descriptor();
|
||||
}
|
||||
else if(m_version.loader.choose) {
|
||||
// todo: implement
|
||||
}
|
||||
}
|
||||
|
||||
return m_version.loader.version;
|
||||
}
|
||||
|
||||
QString PackInstallTask::detectLibrary(VersionLibrary library)
|
||||
{
|
||||
// Try to detect what the library is
|
||||
if (!library.server.isEmpty() && library.server.split("/").length() >= 3) {
|
||||
auto lastSlash = library.server.lastIndexOf("/");
|
||||
auto locationAndVersion = library.server.mid(0, lastSlash);
|
||||
auto fileName = library.server.mid(lastSlash + 1);
|
||||
|
||||
lastSlash = locationAndVersion.lastIndexOf("/");
|
||||
auto location = locationAndVersion.mid(0, lastSlash);
|
||||
auto version = locationAndVersion.mid(lastSlash + 1);
|
||||
|
||||
lastSlash = location.lastIndexOf("/");
|
||||
auto group = location.mid(0, lastSlash).replace("/", ".");
|
||||
auto artefact = location.mid(lastSlash + 1);
|
||||
|
||||
return group + ":" + artefact + ":" + version;
|
||||
}
|
||||
|
||||
if(library.file.contains("-")) {
|
||||
auto lastSlash = library.file.lastIndexOf("-");
|
||||
auto name = library.file.mid(0, lastSlash);
|
||||
auto version = library.file.mid(lastSlash + 1).remove(".jar");
|
||||
|
||||
if(name == QString("guava")) {
|
||||
return "com.google.guava:guava:" + version;
|
||||
}
|
||||
else if(name == QString("commons-lang3")) {
|
||||
return "org.apache.commons:commons-lang3:" + version;
|
||||
}
|
||||
}
|
||||
|
||||
return "org.multimc.atlauncher:" + library.md5 + ":1";
|
||||
}
|
||||
|
||||
bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
|
||||
{
|
||||
if(m_version.libraries.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<GradleSpecifier> exempt;
|
||||
for(const auto & componentUid : componentsToInstall.keys()) {
|
||||
auto componentVersion = componentsToInstall.value(componentUid);
|
||||
|
||||
for(const auto & library : componentVersion->data()->libraries) {
|
||||
GradleSpecifier lib(library->rawName());
|
||||
exempt.append(lib);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
for(const auto & library : minecraftVersion->data()->libraries) {
|
||||
GradleSpecifier lib(library->rawName());
|
||||
exempt.append(lib);
|
||||
}
|
||||
}
|
||||
|
||||
auto uuid = QUuid::createUuid();
|
||||
auto id = uuid.toString().remove('{').remove('}');
|
||||
auto target_id = "org.multimc.atlauncher." + id;
|
||||
|
||||
auto patchDir = FS::PathCombine(instanceRoot, "patches");
|
||||
if(!FS::ensureFolderPathExists(patchDir))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
|
||||
|
||||
auto f = std::make_shared<VersionFile>();
|
||||
f->name = m_pack + " " + m_version_name + " (libraries)";
|
||||
|
||||
for(const auto & lib : m_version.libraries) {
|
||||
auto libName = detectLibrary(lib);
|
||||
GradleSpecifier libSpecifier(libName);
|
||||
|
||||
bool libExempt = false;
|
||||
for(const auto & existingLib : exempt) {
|
||||
if(libSpecifier.matchName(existingLib)) {
|
||||
// If the pack specifies a newer version of the lib, use that!
|
||||
libExempt = Version(libSpecifier.version()) >= Version(existingLib.version());
|
||||
}
|
||||
}
|
||||
if(libExempt) continue;
|
||||
|
||||
auto library = std::make_shared<Library>();
|
||||
library->setRawName(libName);
|
||||
|
||||
switch(lib.download) {
|
||||
case DownloadType::Server:
|
||||
library->setAbsoluteUrl(BuildConfig.ATL_DOWNLOAD_SERVER_URL + lib.url);
|
||||
break;
|
||||
case DownloadType::Direct:
|
||||
library->setAbsoluteUrl(lib.url);
|
||||
break;
|
||||
case DownloadType::Browser:
|
||||
case DownloadType::Unknown:
|
||||
emitFailed(tr("Unknown or unsupported download type: ") + lib.download_raw);
|
||||
return false;
|
||||
}
|
||||
|
||||
f->libraries.append(library);
|
||||
}
|
||||
|
||||
if(f->libraries.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QFile file(patchFileName);
|
||||
if (!file.open(QFile::WriteOnly))
|
||||
{
|
||||
qCritical() << "Error opening" << file.fileName()
|
||||
<< "for reading:" << file.errorString();
|
||||
return false;
|
||||
}
|
||||
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
|
||||
file.close();
|
||||
|
||||
profile->appendComponent(new Component(profile.get(), target_id, f));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
|
||||
{
|
||||
if(m_version.mainClass == QString() && m_version.extraArguments == QString()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto uuid = QUuid::createUuid();
|
||||
auto id = uuid.toString().remove('{').remove('}');
|
||||
auto target_id = "org.multimc.atlauncher." + id;
|
||||
|
||||
auto patchDir = FS::PathCombine(instanceRoot, "patches");
|
||||
if(!FS::ensureFolderPathExists(patchDir))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
|
||||
|
||||
QStringList mainClasses;
|
||||
QStringList tweakers;
|
||||
for(const auto & componentUid : componentsToInstall.keys()) {
|
||||
auto componentVersion = componentsToInstall.value(componentUid);
|
||||
|
||||
if(componentVersion->data()->mainClass != QString("")) {
|
||||
mainClasses.append(componentVersion->data()->mainClass);
|
||||
}
|
||||
tweakers.append(componentVersion->data()->addTweakers);
|
||||
}
|
||||
|
||||
auto f = std::make_shared<VersionFile>();
|
||||
f->name = m_pack + " " + m_version_name;
|
||||
if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) {
|
||||
f->mainClass = m_version.mainClass;
|
||||
}
|
||||
|
||||
// Parse out tweakers
|
||||
auto args = m_version.extraArguments.split(" ");
|
||||
QString previous;
|
||||
for(auto arg : args) {
|
||||
if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
|
||||
auto tweakClass = arg.remove("--tweakClass=");
|
||||
if(tweakers.contains(tweakClass)) continue;
|
||||
|
||||
f->addTweakers.append(tweakClass);
|
||||
}
|
||||
previous = arg;
|
||||
}
|
||||
|
||||
if(f->mainClass == QString() && f->addTweakers.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QFile file(patchFileName);
|
||||
if (!file.open(QFile::WriteOnly))
|
||||
{
|
||||
qCritical() << "Error opening" << file.fileName()
|
||||
<< "for reading:" << file.errorString();
|
||||
return false;
|
||||
}
|
||||
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
|
||||
file.close();
|
||||
|
||||
profile->appendComponent(new Component(profile.get(), target_id, f));
|
||||
return true;
|
||||
}
|
||||
|
||||
void PackInstallTask::installConfigs()
|
||||
{
|
||||
setStatus(tr("Downloading configs..."));
|
||||
jobPtr.reset(new NetJob(tr("Config download")));
|
||||
|
||||
auto path = QString("Configs/%1/%2").arg(m_pack).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);
|
||||
auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", path);
|
||||
entry->setStale(true);
|
||||
|
||||
jobPtr->addNetAction(Net::Download::makeCached(url, entry));
|
||||
archivePath = entry->getFullPath();
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
|
||||
{
|
||||
jobPtr.reset();
|
||||
extractConfigs();
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
||||
jobPtr->start();
|
||||
}
|
||||
|
||||
void PackInstallTask::extractConfigs()
|
||||
{
|
||||
setStatus(tr("Extracting configs..."));
|
||||
|
||||
QDir extractDir(m_stagingPath);
|
||||
|
||||
QuaZip packZip(archivePath);
|
||||
if(!packZip.open(QuaZip::mdUnzip))
|
||||
{
|
||||
emitFailed(tr("Failed to open pack configs %1!").arg(archivePath));
|
||||
return;
|
||||
}
|
||||
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft");
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]()
|
||||
{
|
||||
installMods();
|
||||
});
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]()
|
||||
{
|
||||
emitAborted();
|
||||
});
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
}
|
||||
|
||||
void PackInstallTask::installMods()
|
||||
{
|
||||
setStatus(tr("Downloading mods..."));
|
||||
|
||||
jarmods.clear();
|
||||
jobPtr.reset(new NetJob(tr("Mod download")));
|
||||
for(const auto& mod : m_version.mods) {
|
||||
// skip optional mods for now
|
||||
if(mod.optional) continue;
|
||||
|
||||
QString url;
|
||||
switch(mod.download) {
|
||||
case DownloadType::Server:
|
||||
url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url;
|
||||
break;
|
||||
case DownloadType::Browser:
|
||||
emitFailed(tr("Unsupported download type: ") + mod.download_raw);
|
||||
return;
|
||||
case DownloadType::Direct:
|
||||
url = mod.url;
|
||||
break;
|
||||
case DownloadType::Unknown:
|
||||
emitFailed(tr("Unknown download type: ") + mod.download_raw);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) {
|
||||
auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", mod.url);
|
||||
entry->setStale(true);
|
||||
modsToExtract.insert(entry->getFullPath(), mod);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
jobPtr->addNetAction(dl);
|
||||
}
|
||||
else if(mod.type == ModType::Decomp) {
|
||||
auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", mod.url);
|
||||
entry->setStale(true);
|
||||
modsToDecomp.insert(entry->getFullPath(), mod);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
jobPtr->addNetAction(dl);
|
||||
}
|
||||
else {
|
||||
auto relpath = getDirForModType(mod.type, mod.type_raw);
|
||||
if(relpath == Q_NULLPTR) continue;
|
||||
auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
|
||||
|
||||
qDebug() << "Will download" << url << "to" << path;
|
||||
auto dl = Net::Download::makeFile(url, path);
|
||||
jobPtr->addNetAction(dl);
|
||||
|
||||
if(mod.type == ModType::Forge) {
|
||||
auto vlist = ENV.metadataIndex()->get("net.minecraftforge");
|
||||
if(vlist)
|
||||
{
|
||||
auto ver = vlist->getVersion(mod.version);
|
||||
if(ver) {
|
||||
ver->load(Net::Mode::Online);
|
||||
componentsToInstall.insert("net.minecraftforge", ver);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Jarmod: " + path;
|
||||
jarmods.push_back(path);
|
||||
}
|
||||
|
||||
if(mod.type == ModType::Jar) {
|
||||
qDebug() << "Jarmod: " + path;
|
||||
jarmods.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
|
||||
{
|
||||
jobPtr.reset();
|
||||
extractMods();
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
||||
jobPtr->start();
|
||||
}
|
||||
|
||||
void PackInstallTask::extractMods()
|
||||
{
|
||||
setStatus(tr("Extracting mods..."));
|
||||
|
||||
if(modsToExtract.isEmpty()) {
|
||||
decompMods();
|
||||
return;
|
||||
}
|
||||
|
||||
auto modPath = modsToExtract.firstKey();
|
||||
auto mod = modsToExtract.value(modPath);
|
||||
|
||||
QString extractToDir;
|
||||
if(mod.type == ModType::Extract) {
|
||||
extractToDir = getDirForModType(mod.extractTo, mod.extractTo_raw);
|
||||
}
|
||||
else if(mod.type == ModType::TexturePackExtract) {
|
||||
extractToDir = FS::PathCombine("texturepacks", "extracted");
|
||||
}
|
||||
else if(mod.type == ModType::ResourcePackExtract) {
|
||||
extractToDir = FS::PathCombine("resourcepacks", "extracted");
|
||||
}
|
||||
|
||||
qDebug() << "Extracting " + mod.file + " to " + extractToDir;
|
||||
|
||||
QDir extractDir(m_stagingPath);
|
||||
auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir);
|
||||
|
||||
QString folderToExtract = "";
|
||||
if(mod.type == ModType::Extract) {
|
||||
folderToExtract = mod.extractFolder;
|
||||
}
|
||||
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, modPath, folderToExtract, extractToPath);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]()
|
||||
{
|
||||
extractMods();
|
||||
});
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]()
|
||||
{
|
||||
emitAborted();
|
||||
});
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
|
||||
modsToExtract.remove(modPath);
|
||||
}
|
||||
|
||||
void PackInstallTask::decompMods()
|
||||
{
|
||||
setStatus(tr("Extracting 'decomp' mods..."));
|
||||
|
||||
if(modsToDecomp.isEmpty()) {
|
||||
install();
|
||||
return;
|
||||
}
|
||||
|
||||
auto modPath = modsToDecomp.firstKey();
|
||||
auto mod = modsToDecomp.value(modPath);
|
||||
|
||||
auto extractToDir = getDirForModType(mod.decompType, mod.decompType_raw);
|
||||
|
||||
QDir extractDir(m_stagingPath);
|
||||
auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir, mod.decompFile);
|
||||
|
||||
qWarning() << "Extracting " + mod.decompFile + " to " + extractToDir;
|
||||
|
||||
m_decompFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractFile, modPath, mod.decompFile, extractToPath);
|
||||
connect(&m_decompFutureWatcher, &QFutureWatcher<bool>::finished, this, [&]()
|
||||
{
|
||||
install();
|
||||
});
|
||||
connect(&m_decompFutureWatcher, &QFutureWatcher<bool>::canceled, this, [&]()
|
||||
{
|
||||
emitAborted();
|
||||
});
|
||||
m_decompFutureWatcher.setFuture(m_decompFuture);
|
||||
|
||||
modsToDecomp.remove(modPath);
|
||||
}
|
||||
|
||||
void PackInstallTask::install()
|
||||
{
|
||||
setStatus(tr("Installing modpack"));
|
||||
|
||||
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
|
||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
auto components = instance.getPackProfile();
|
||||
components->buildingFromScratch();
|
||||
|
||||
// Use a component to add libraries BEFORE Minecraft
|
||||
if(!createLibrariesComponent(instance.instanceRoot(), components)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Minecraft
|
||||
components->setComponentVersion("net.minecraft", m_version.minecraft, true);
|
||||
|
||||
// Loader
|
||||
if(m_version.loader.type == QString("forge"))
|
||||
{
|
||||
auto version = getVersionForLoader("net.minecraftforge");
|
||||
if(version == Q_NULLPTR) return;
|
||||
|
||||
components->setComponentVersion("net.minecraftforge", version, true);
|
||||
}
|
||||
else if(m_version.loader.type == QString("fabric"))
|
||||
{
|
||||
auto version = getVersionForLoader("net.fabricmc.fabric-loader");
|
||||
if(version == Q_NULLPTR) return;
|
||||
|
||||
components->setComponentVersion("net.fabricmc.fabric-loader", version, true);
|
||||
}
|
||||
else if(m_version.loader.type != QString())
|
||||
{
|
||||
emitFailed(tr("Unknown loader type: ") + m_version.loader.type);
|
||||
return;
|
||||
}
|
||||
|
||||
for(const auto & componentUid : componentsToInstall.keys()) {
|
||||
auto version = componentsToInstall.value(componentUid);
|
||||
components->setComponentVersion(componentUid, version->version());
|
||||
}
|
||||
|
||||
components->installJarMods(jarmods);
|
||||
|
||||
// Use a component to fill in the rest of the data
|
||||
// todo: use more detection
|
||||
if(!createPackComponent(instance.instanceRoot(), components)) {
|
||||
return;
|
||||
}
|
||||
|
||||
components->saveNow();
|
||||
|
||||
instance.setName(m_instName);
|
||||
instance.setIconKey(m_instIcon);
|
||||
instanceSettings->resumeSave();
|
||||
|
||||
jarmods.clear();
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
}
|
72
api/logic/modplatform/atlauncher/ATLPackInstallTask.h
Normal file
72
api/logic/modplatform/atlauncher/ATLPackInstallTask.h
Normal file
@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <meta/VersionList.h>
|
||||
#include "ATLPackManifest.h"
|
||||
|
||||
#include "InstanceTask.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "meta/Version.h"
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PackInstallTask(QString pack, QString version);
|
||||
virtual ~PackInstallTask(){}
|
||||
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
|
||||
private slots:
|
||||
void onDownloadSucceeded();
|
||||
void onDownloadFailed(QString reason);
|
||||
|
||||
private:
|
||||
QString getDirForModType(ModType type, QString raw);
|
||||
QString getVersionForLoader(QString uid);
|
||||
QString detectLibrary(VersionLibrary library);
|
||||
|
||||
bool createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
|
||||
bool createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
|
||||
|
||||
void installConfigs();
|
||||
void extractConfigs();
|
||||
void installMods();
|
||||
void extractMods();
|
||||
void decompMods();
|
||||
void install();
|
||||
|
||||
private:
|
||||
NetJobPtr jobPtr;
|
||||
QByteArray response;
|
||||
|
||||
QString m_pack;
|
||||
QString m_version_name;
|
||||
PackVersion m_version;
|
||||
|
||||
QMap<QString, VersionMod> modsToExtract;
|
||||
QMap<QString, VersionMod> modsToDecomp;
|
||||
|
||||
QString archivePath;
|
||||
QStringList jarmods;
|
||||
Meta::VersionPtr minecraftVersion;
|
||||
QMap<QString, Meta::VersionPtr> componentsToInstall;
|
||||
|
||||
QFuture<QStringList> m_extractFuture;
|
||||
QFutureWatcher<QStringList> m_extractFutureWatcher;
|
||||
|
||||
QFuture<bool> m_decompFuture;
|
||||
QFutureWatcher<bool> m_decompFutureWatcher;
|
||||
|
||||
};
|
||||
|
||||
}
|
180
api/logic/modplatform/atlauncher/ATLPackManifest.cpp
Normal file
180
api/logic/modplatform/atlauncher/ATLPackManifest.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include "ATLPackManifest.h"
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
static ATLauncher::DownloadType parseDownloadType(QString rawType) {
|
||||
if(rawType == QString("server")) {
|
||||
return ATLauncher::DownloadType::Server;
|
||||
}
|
||||
else if(rawType == QString("browser")) {
|
||||
return ATLauncher::DownloadType::Browser;
|
||||
}
|
||||
else if(rawType == QString("direct")) {
|
||||
return ATLauncher::DownloadType::Direct;
|
||||
}
|
||||
|
||||
return ATLauncher::DownloadType::Unknown;
|
||||
}
|
||||
|
||||
static ATLauncher::ModType parseModType(QString rawType) {
|
||||
// See https://wiki.atlauncher.com/mod_types
|
||||
if(rawType == QString("root")) {
|
||||
return ATLauncher::ModType::Root;
|
||||
}
|
||||
else if(rawType == QString("forge")) {
|
||||
return ATLauncher::ModType::Forge;
|
||||
}
|
||||
else if(rawType == QString("jar")) {
|
||||
return ATLauncher::ModType::Jar;
|
||||
}
|
||||
else if(rawType == QString("mods")) {
|
||||
return ATLauncher::ModType::Mods;
|
||||
}
|
||||
else if(rawType == QString("flan")) {
|
||||
return ATLauncher::ModType::Flan;
|
||||
}
|
||||
else if(rawType == QString("dependency") || rawType == QString("depandency")) {
|
||||
return ATLauncher::ModType::Dependency;
|
||||
}
|
||||
else if(rawType == QString("ic2lib")) {
|
||||
return ATLauncher::ModType::Ic2Lib;
|
||||
}
|
||||
else if(rawType == QString("denlib")) {
|
||||
return ATLauncher::ModType::DenLib;
|
||||
}
|
||||
else if(rawType == QString("coremods")) {
|
||||
return ATLauncher::ModType::Coremods;
|
||||
}
|
||||
else if(rawType == QString("mcpc")) {
|
||||
return ATLauncher::ModType::MCPC;
|
||||
}
|
||||
else if(rawType == QString("plugins")) {
|
||||
return ATLauncher::ModType::Plugins;
|
||||
}
|
||||
else if(rawType == QString("extract")) {
|
||||
return ATLauncher::ModType::Extract;
|
||||
}
|
||||
else if(rawType == QString("decomp")) {
|
||||
return ATLauncher::ModType::Decomp;
|
||||
}
|
||||
else if(rawType == QString("texturepack")) {
|
||||
return ATLauncher::ModType::TexturePack;
|
||||
}
|
||||
else if(rawType == QString("resourcepack")) {
|
||||
return ATLauncher::ModType::ResourcePack;
|
||||
}
|
||||
else if(rawType == QString("shaderpack")) {
|
||||
return ATLauncher::ModType::ShaderPack;
|
||||
}
|
||||
else if(rawType == QString("texturepackextract")) {
|
||||
return ATLauncher::ModType::TexturePackExtract;
|
||||
}
|
||||
else if(rawType == QString("resourcepackextract")) {
|
||||
return ATLauncher::ModType::ResourcePackExtract;
|
||||
}
|
||||
else if(rawType == QString("millenaire")) {
|
||||
return ATLauncher::ModType::Millenaire;
|
||||
}
|
||||
|
||||
return ATLauncher::ModType::Unknown;
|
||||
}
|
||||
|
||||
static void loadVersionLoader(ATLauncher::VersionLoader & p, QJsonObject & obj) {
|
||||
p.type = Json::requireString(obj, "type");
|
||||
p.latest = Json::ensureBoolean(obj, "latest", false);
|
||||
p.choose = Json::ensureBoolean(obj, "choose", false);
|
||||
p.recommended = Json::ensureBoolean(obj, "recommended", false);
|
||||
|
||||
auto metadata = Json::requireObject(obj, "metadata");
|
||||
p.version = Json::requireString(metadata, "version");
|
||||
}
|
||||
|
||||
static void loadVersionLibrary(ATLauncher::VersionLibrary & p, QJsonObject & obj) {
|
||||
p.url = Json::requireString(obj, "url");
|
||||
p.file = Json::requireString(obj, "file");
|
||||
p.md5 = Json::requireString(obj, "md5");
|
||||
|
||||
p.download_raw = Json::requireString(obj, "download");
|
||||
p.download = parseDownloadType(p.download_raw);
|
||||
|
||||
p.server = Json::ensureString(obj, "server", "");
|
||||
}
|
||||
|
||||
static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
|
||||
p.name = Json::requireString(obj, "name");
|
||||
p.version = Json::requireString(obj, "version");
|
||||
p.url = Json::requireString(obj, "url");
|
||||
p.file = Json::requireString(obj, "file");
|
||||
p.md5 = Json::ensureString(obj, "md5", "");
|
||||
|
||||
p.download_raw = Json::requireString(obj, "download");
|
||||
p.download = parseDownloadType(p.download_raw);
|
||||
|
||||
p.type_raw = Json::requireString(obj, "type");
|
||||
p.type = parseModType(p.type_raw);
|
||||
|
||||
// This contributes to the Minecraft Forge detection, where we rely on mod.type being "Forge"
|
||||
// when the mod represents Forge. As there is little difference between "Jar" and "Forge, some
|
||||
// packs regretfully use "Jar". This will correct the type to "Forge" in these cases (as best
|
||||
// it can).
|
||||
if(p.name == QString("Minecraft Forge") && p.type == ATLauncher::ModType::Jar) {
|
||||
p.type_raw = "forge";
|
||||
p.type = ATLauncher::ModType::Forge;
|
||||
}
|
||||
|
||||
if(obj.contains("extractTo")) {
|
||||
p.extractTo_raw = Json::requireString(obj, "extractTo");
|
||||
p.extractTo = parseModType(p.extractTo_raw);
|
||||
p.extractFolder = Json::ensureString(obj, "extractFolder", "").replace("%s%", "/");
|
||||
}
|
||||
|
||||
if(obj.contains("decompType")) {
|
||||
p.decompType_raw = Json::requireString(obj, "decompType");
|
||||
p.decompType = parseModType(p.decompType_raw);
|
||||
p.decompFile = Json::requireString(obj, "decompFile");
|
||||
}
|
||||
|
||||
p.optional = Json::ensureBoolean(obj, "optional", false);
|
||||
}
|
||||
|
||||
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
|
||||
{
|
||||
v.version = Json::requireString(obj, "version");
|
||||
v.minecraft = Json::requireString(obj, "minecraft");
|
||||
v.noConfigs = Json::ensureBoolean(obj, "noConfigs", false);
|
||||
|
||||
if(obj.contains("mainClass")) {
|
||||
auto main = Json::requireObject(obj, "mainClass");
|
||||
v.mainClass = Json::ensureString(main, "mainClass", "");
|
||||
}
|
||||
|
||||
if(obj.contains("extraArguments")) {
|
||||
auto arguments = Json::requireObject(obj, "extraArguments");
|
||||
v.extraArguments = Json::ensureString(arguments, "arguments", "");
|
||||
}
|
||||
|
||||
if(obj.contains("loader")) {
|
||||
auto loader = Json::requireObject(obj, "loader");
|
||||
loadVersionLoader(v.loader, loader);
|
||||
}
|
||||
|
||||
if(obj.contains("libraries")) {
|
||||
auto libraries = Json::requireArray(obj, "libraries");
|
||||
for (const auto libraryRaw : libraries)
|
||||
{
|
||||
auto libraryObj = Json::requireObject(libraryRaw);
|
||||
ATLauncher::VersionLibrary target;
|
||||
loadVersionLibrary(target, libraryObj);
|
||||
v.libraries.append(target);
|
||||
}
|
||||
}
|
||||
|
||||
auto mods = Json::requireArray(obj, "mods");
|
||||
for (const auto modRaw : mods)
|
||||
{
|
||||
auto modObj = Json::requireObject(modRaw);
|
||||
ATLauncher::VersionMod mod;
|
||||
loadVersionMod(mod, modObj);
|
||||
v.mods.append(mod);
|
||||
}
|
||||
}
|
107
api/logic/modplatform/atlauncher/ATLPackManifest.h
Normal file
107
api/logic/modplatform/atlauncher/ATLPackManifest.h
Normal file
@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QJsonObject>
|
||||
#include <multimc_logic_export.h>
|
||||
|
||||
namespace ATLauncher
|
||||
{
|
||||
|
||||
enum class PackType
|
||||
{
|
||||
Public,
|
||||
Private
|
||||
};
|
||||
|
||||
enum class ModType
|
||||
{
|
||||
Root,
|
||||
Forge,
|
||||
Jar,
|
||||
Mods,
|
||||
Flan,
|
||||
Dependency,
|
||||
Ic2Lib,
|
||||
DenLib,
|
||||
Coremods,
|
||||
MCPC,
|
||||
Plugins,
|
||||
Extract,
|
||||
Decomp,
|
||||
TexturePack,
|
||||
ResourcePack,
|
||||
ShaderPack,
|
||||
TexturePackExtract,
|
||||
ResourcePackExtract,
|
||||
Millenaire,
|
||||
Unknown
|
||||
};
|
||||
|
||||
enum class DownloadType
|
||||
{
|
||||
Server,
|
||||
Browser,
|
||||
Direct,
|
||||
Unknown
|
||||
};
|
||||
|
||||
struct VersionLoader
|
||||
{
|
||||
QString type;
|
||||
bool latest;
|
||||
bool recommended;
|
||||
bool choose;
|
||||
|
||||
QString version;
|
||||
};
|
||||
|
||||
struct VersionLibrary
|
||||
{
|
||||
QString url;
|
||||
QString file;
|
||||
QString server;
|
||||
QString md5;
|
||||
DownloadType download;
|
||||
QString download_raw;
|
||||
};
|
||||
|
||||
struct VersionMod
|
||||
{
|
||||
QString name;
|
||||
QString version;
|
||||
QString url;
|
||||
QString file;
|
||||
QString md5;
|
||||
DownloadType download;
|
||||
QString download_raw;
|
||||
ModType type;
|
||||
QString type_raw;
|
||||
|
||||
ModType extractTo;
|
||||
QString extractTo_raw;
|
||||
QString extractFolder;
|
||||
|
||||
ModType decompType;
|
||||
QString decompType_raw;
|
||||
QString decompFile;
|
||||
|
||||
bool optional;
|
||||
};
|
||||
|
||||
struct PackVersion
|
||||
{
|
||||
QString version;
|
||||
QString minecraft;
|
||||
bool noConfigs;
|
||||
QString mainClass;
|
||||
QString extraArguments;
|
||||
|
||||
VersionLoader loader;
|
||||
QVector<VersionLibrary> libraries;
|
||||
QVector<VersionMod> mods;
|
||||
};
|
||||
|
||||
MULTIMC_LOGIC_EXPORT void loadVersion(PackVersion & v, QJsonObject & obj);
|
||||
|
||||
}
|
@ -124,25 +124,38 @@ SET(MULTIMC_SOURCES
|
||||
# GUI - platform pages
|
||||
pages/modplatform/VanillaPage.cpp
|
||||
pages/modplatform/VanillaPage.h
|
||||
|
||||
pages/modplatform/atlauncher/AtlModel.cpp
|
||||
pages/modplatform/atlauncher/AtlModel.h
|
||||
pages/modplatform/atlauncher/AtlFilterModel.cpp
|
||||
pages/modplatform/atlauncher/AtlFilterModel.h
|
||||
pages/modplatform/atlauncher/AtlPage.cpp
|
||||
pages/modplatform/atlauncher/AtlPage.h
|
||||
pages/modplatform/atlauncher/AtlPage.h
|
||||
|
||||
pages/modplatform/ftb/FtbFilterModel.cpp
|
||||
pages/modplatform/ftb/FtbFilterModel.h
|
||||
pages/modplatform/ftb/FtbListModel.cpp
|
||||
pages/modplatform/ftb/FtbListModel.h
|
||||
pages/modplatform/ftb/FtbPage.cpp
|
||||
pages/modplatform/ftb/FtbPage.h
|
||||
|
||||
pages/modplatform/legacy_ftb/Page.cpp
|
||||
pages/modplatform/legacy_ftb/Page.h
|
||||
pages/modplatform/legacy_ftb/ListModel.h
|
||||
pages/modplatform/legacy_ftb/ListModel.cpp
|
||||
|
||||
pages/modplatform/twitch/TwitchData.h
|
||||
pages/modplatform/twitch/TwitchModel.cpp
|
||||
pages/modplatform/twitch/TwitchModel.h
|
||||
pages/modplatform/twitch/TwitchPage.cpp
|
||||
pages/modplatform/twitch/TwitchPage.h
|
||||
|
||||
pages/modplatform/technic/TechnicModel.cpp
|
||||
pages/modplatform/technic/TechnicModel.h
|
||||
pages/modplatform/technic/TechnicPage.cpp
|
||||
pages/modplatform/technic/TechnicPage.h
|
||||
|
||||
pages/modplatform/ImportPage.cpp
|
||||
pages/modplatform/ImportPage.h
|
||||
|
||||
@ -260,6 +273,7 @@ SET(MULTIMC_UIS
|
||||
|
||||
# Platform pages
|
||||
pages/modplatform/VanillaPage.ui
|
||||
pages/modplatform/atlauncher/AtlPage.ui
|
||||
pages/modplatform/ftb/FtbPage.ui
|
||||
pages/modplatform/legacy_ftb/Page.ui
|
||||
pages/modplatform/twitch/TwitchPage.ui
|
||||
|
@ -34,6 +34,7 @@
|
||||
|
||||
#include "widgets/PageContainer.h"
|
||||
#include <pages/modplatform/VanillaPage.h>
|
||||
#include <pages/modplatform/atlauncher/AtlPage.h>
|
||||
#include <pages/modplatform/ftb/FtbPage.h>
|
||||
#include <pages/modplatform/legacy_ftb/Page.h>
|
||||
#include <pages/modplatform/twitch/TwitchPage.h>
|
||||
@ -129,6 +130,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
|
||||
{
|
||||
new VanillaPage(this),
|
||||
importPage,
|
||||
new AtlPage(this),
|
||||
new FtbPage(this),
|
||||
new LegacyFTB::Page(this),
|
||||
technicPage,
|
||||
|
67
application/pages/modplatform/atlauncher/AtlFilterModel.cpp
Normal file
67
application/pages/modplatform/atlauncher/AtlFilterModel.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "AtlFilterModel.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <modplatform/atlauncher/ATLPackIndex.h>
|
||||
#include <Version.h>
|
||||
#include <MMCStrings.h>
|
||||
|
||||
namespace Atl {
|
||||
|
||||
FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
currentSorting = Sorting::ByPopularity;
|
||||
sortings.insert(tr("Sort by popularity"), Sorting::ByPopularity);
|
||||
sortings.insert(tr("Sort by name"), Sorting::ByName);
|
||||
sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion);
|
||||
}
|
||||
|
||||
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
|
||||
{
|
||||
return sortings;
|
||||
}
|
||||
|
||||
QString FilterModel::translateCurrentSorting()
|
||||
{
|
||||
return sortings.key(currentSorting);
|
||||
}
|
||||
|
||||
void FilterModel::setSorting(Sorting sorting)
|
||||
{
|
||||
currentSorting = sorting;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
FilterModel::Sorting FilterModel::getCurrentSorting()
|
||||
{
|
||||
return currentSorting;
|
||||
}
|
||||
|
||||
bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
ATLauncher::IndexedPack leftPack = sourceModel()->data(left, Qt::UserRole).value<ATLauncher::IndexedPack>();
|
||||
ATLauncher::IndexedPack rightPack = sourceModel()->data(right, Qt::UserRole).value<ATLauncher::IndexedPack>();
|
||||
|
||||
if (currentSorting == ByPopularity) {
|
||||
return leftPack.position > rightPack.position;
|
||||
}
|
||||
else if (currentSorting == ByGameVersion) {
|
||||
Version lv(leftPack.versions.at(0).minecraft);
|
||||
Version rv(rightPack.versions.at(0).minecraft);
|
||||
return lv < rv;
|
||||
}
|
||||
else if (currentSorting == ByName) {
|
||||
return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
|
||||
}
|
||||
|
||||
// Invalid sorting set, somehow...
|
||||
qWarning() << "Invalid sorting set!";
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
32
application/pages/modplatform/atlauncher/AtlFilterModel.h
Normal file
32
application/pages/modplatform/atlauncher/AtlFilterModel.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QSortFilterProxyModel>
|
||||
|
||||
namespace Atl {
|
||||
|
||||
class FilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FilterModel(QObject* parent = Q_NULLPTR);
|
||||
enum Sorting {
|
||||
ByPopularity,
|
||||
ByGameVersion,
|
||||
ByName,
|
||||
};
|
||||
const QMap<QString, Sorting> getAvailableSortings();
|
||||
QString translateCurrentSorting();
|
||||
void setSorting(Sorting sorting);
|
||||
Sorting getCurrentSorting();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
|
||||
private:
|
||||
QMap<QString, Sorting> sortings;
|
||||
Sorting currentSorting;
|
||||
|
||||
};
|
||||
|
||||
}
|
185
application/pages/modplatform/atlauncher/AtlModel.cpp
Normal file
185
application/pages/modplatform/atlauncher/AtlModel.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
#include "AtlModel.h"
|
||||
|
||||
#include <BuildConfig.h>
|
||||
#include <MultiMC.h>
|
||||
#include <Env.h>
|
||||
|
||||
namespace Atl {
|
||||
|
||||
ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ListModel::~ListModel()
|
||||
{
|
||||
}
|
||||
|
||||
int ListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return modpacks.size();
|
||||
}
|
||||
|
||||
int ListModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant ListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
int pos = index.row();
|
||||
if(pos >= modpacks.size() || pos < 0 || !index.isValid())
|
||||
{
|
||||
return QString("INVALID INDEX %1").arg(pos);
|
||||
}
|
||||
|
||||
ATLauncher::IndexedPack pack = modpacks.at(pos);
|
||||
if(role == Qt::DisplayRole)
|
||||
{
|
||||
return pack.name;
|
||||
}
|
||||
else if (role == Qt::ToolTipRole)
|
||||
{
|
||||
return pack.description;
|
||||
}
|
||||
else if(role == Qt::DecorationRole)
|
||||
{
|
||||
if(m_logoMap.contains(pack.safeName))
|
||||
{
|
||||
return (m_logoMap.value(pack.safeName));
|
||||
}
|
||||
auto icon = MMC->getThemedIcon("atlauncher-placeholder");
|
||||
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
|
||||
((ListModel *)this)->requestLogo(pack.safeName, url);
|
||||
|
||||
return icon;
|
||||
}
|
||||
else if(role == Qt::UserRole)
|
||||
{
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void ListModel::request()
|
||||
{
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
|
||||
auto *netJob = new NetJob("Atl::Request");
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::requestFinished);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed);
|
||||
}
|
||||
|
||||
void ListModel::requestFinished()
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
|
||||
if(parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||
qWarning() << response;
|
||||
return;
|
||||
}
|
||||
|
||||
QList<ATLauncher::IndexedPack> newList;
|
||||
|
||||
auto packs = doc.array();
|
||||
for(auto packRaw : packs) {
|
||||
auto packObj = packRaw.toObject();
|
||||
|
||||
ATLauncher::IndexedPack pack;
|
||||
ATLauncher::loadIndexedPack(pack, packObj);
|
||||
|
||||
// ignore packs without a published version
|
||||
if(pack.versions.length() == 0) continue;
|
||||
// only display public packs (for now)
|
||||
if(pack.type != ATLauncher::PackType::Public) continue;
|
||||
// ignore "system" packs (Vanilla, Vanilla with Forge, etc)
|
||||
if(pack.system) continue;
|
||||
|
||||
newList.append(pack);
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
|
||||
modpacks.append(newList);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ListModel::requestFailed(QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
}
|
||||
|
||||
void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
|
||||
{
|
||||
if(m_logoMap.contains(logo))
|
||||
{
|
||||
callback(ENV.metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
}
|
||||
else
|
||||
{
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
}
|
||||
|
||||
void ListModel::logoFailed(QString logo)
|
||||
{
|
||||
m_failedLogos.append(logo);
|
||||
m_loadingLogos.removeAll(logo);
|
||||
}
|
||||
|
||||
void ListModel::logoLoaded(QString logo, QIcon out)
|
||||
{
|
||||
m_loadingLogos.removeAll(logo);
|
||||
m_logoMap.insert(logo, out);
|
||||
|
||||
for(int i = 0; i < modpacks.size(); i++) {
|
||||
if(modpacks[i].safeName == logo) {
|
||||
emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListModel::requestLogo(QString file, QString url)
|
||||
{
|
||||
if(m_loadingLogos.contains(file) || m_failedLogos.contains(file))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = ENV.metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
|
||||
NetJob *job = new NetJob(QString("ATLauncher Icon Download %1").arg(file));
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath]
|
||||
{
|
||||
emit logoLoaded(file, QIcon(fullPath));
|
||||
if(waitingCallbacks.contains(file))
|
||||
{
|
||||
waitingCallbacks.value(file)(fullPath);
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(job, &NetJob::failed, this, [this, file]
|
||||
{
|
||||
emit logoFailed(file);
|
||||
});
|
||||
|
||||
job->start();
|
||||
|
||||
m_loadingLogos.append(file);
|
||||
}
|
||||
|
||||
}
|
52
application/pages/modplatform/atlauncher/AtlModel.h
Normal file
52
application/pages/modplatform/atlauncher/AtlModel.h
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "net/NetJob.h"
|
||||
#include <QIcon>
|
||||
#include <modplatform/atlauncher/ATLPackIndex.h>
|
||||
|
||||
namespace Atl {
|
||||
|
||||
typedef QMap<QString, QIcon> LogoMap;
|
||||
typedef std::function<void(QString)> LogoCallback;
|
||||
|
||||
class ListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ListModel(QObject *parent);
|
||||
virtual ~ListModel();
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
void request();
|
||||
|
||||
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
|
||||
|
||||
private slots:
|
||||
void requestFinished();
|
||||
void requestFailed(QString reason);
|
||||
|
||||
void logoFailed(QString logo);
|
||||
void logoLoaded(QString logo, QIcon out);
|
||||
|
||||
private:
|
||||
void requestLogo(QString file, QString url);
|
||||
|
||||
private:
|
||||
QList<ATLauncher::IndexedPack> modpacks;
|
||||
|
||||
QStringList m_failedLogos;
|
||||
QStringList m_loadingLogos;
|
||||
LogoMap m_logoMap;
|
||||
QMap<QString, LogoCallback> waitingCallbacks;
|
||||
|
||||
NetJobPtr jobPtr;
|
||||
QByteArray response;
|
||||
};
|
||||
|
||||
}
|
100
application/pages/modplatform/atlauncher/AtlPage.cpp
Normal file
100
application/pages/modplatform/atlauncher/AtlPage.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
#include "AtlPage.h"
|
||||
#include "ui_AtlPage.h"
|
||||
|
||||
#include "dialogs/NewInstanceDialog.h"
|
||||
#include <modplatform/atlauncher/ATLPackInstallTask.h>
|
||||
#include <BuildConfig.h>
|
||||
|
||||
AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::AtlPage), dialog(dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
filterModel = new Atl::FilterModel(this);
|
||||
listModel = new Atl::ListModel(this);
|
||||
filterModel->setSourceModel(listModel);
|
||||
ui->packView->setModel(filterModel);
|
||||
ui->packView->setSortingEnabled(true);
|
||||
|
||||
ui->packView->header()->hide();
|
||||
ui->packView->setIndentation(0);
|
||||
|
||||
for(int i = 0; i < filterModel->getAvailableSortings().size(); i++)
|
||||
{
|
||||
ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i));
|
||||
}
|
||||
ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting());
|
||||
|
||||
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
|
||||
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
|
||||
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
|
||||
}
|
||||
|
||||
AtlPage::~AtlPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool AtlPage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void AtlPage::openedImpl()
|
||||
{
|
||||
listModel->request();
|
||||
}
|
||||
|
||||
void AtlPage::suggestCurrent()
|
||||
{
|
||||
if(isOpened) {
|
||||
dialog->setSuggestedPack(selected.name, new ATLauncher::PackInstallTask(selected.safeName, 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)
|
||||
{
|
||||
dialog->setSuggestedIconFromFile(logo, editedLogoName);
|
||||
});
|
||||
}
|
||||
|
||||
void AtlPage::onSortingSelectionChanged(QString data)
|
||||
{
|
||||
auto toSet = filterModel->getAvailableSortings().value(data);
|
||||
filterModel->setSorting(toSet);
|
||||
}
|
||||
|
||||
void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
{
|
||||
ui->versionSelectionBox->clear();
|
||||
|
||||
if(!first.isValid())
|
||||
{
|
||||
if(isOpened)
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
selected = filterModel->data(first, Qt::UserRole).value<ATLauncher::IndexedPack>();
|
||||
|
||||
for(const auto& version : selected.versions) {
|
||||
ui->versionSelectionBox->addItem(version.version);
|
||||
}
|
||||
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void AtlPage::onVersionSelectionChanged(QString data)
|
||||
{
|
||||
if(data.isNull() || data.isEmpty())
|
||||
{
|
||||
selectedVersion = "";
|
||||
return;
|
||||
}
|
||||
|
||||
selectedVersion = data;
|
||||
suggestCurrent();
|
||||
}
|
78
application/pages/modplatform/atlauncher/AtlPage.h
Normal file
78
application/pages/modplatform/atlauncher/AtlPage.h
Normal file
@ -0,0 +1,78 @@
|
||||
/* Copyright 2013-2019 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
|
||||
|
||||
#include "AtlFilterModel.h"
|
||||
#include "AtlModel.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "MultiMC.h"
|
||||
#include "pages/BasePage.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class AtlPage;
|
||||
}
|
||||
|
||||
class NewInstanceDialog;
|
||||
|
||||
class AtlPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AtlPage(NewInstanceDialog* dialog, QWidget *parent = 0);
|
||||
virtual ~AtlPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("ATLauncher");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return MMC->getThemedIcon("atlauncher");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "atl";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "ATL-platform";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
void openedImpl() override;
|
||||
|
||||
private:
|
||||
void suggestCurrent();
|
||||
|
||||
private slots:
|
||||
void onSortingSelectionChanged(QString data);
|
||||
|
||||
void onSelectionChanged(QModelIndex first, QModelIndex second);
|
||||
void onVersionSelectionChanged(QString data);
|
||||
|
||||
private:
|
||||
Ui::AtlPage *ui = nullptr;
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
Atl::ListModel* listModel = nullptr;
|
||||
Atl::FilterModel* filterModel = nullptr;
|
||||
|
||||
ATLauncher::IndexedPack selected;
|
||||
QString selectedVersion;
|
||||
};
|
55
application/pages/modplatform/atlauncher/AtlPage.ui
Normal file
55
application/pages/modplatform/atlauncher/AtlPage.ui
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AtlPage</class>
|
||||
<widget class="QWidget" name="AtlPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>875</width>
|
||||
<height>745</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Version selected:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QComboBox" name="sortByBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QTreeView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>96</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>packView</tabstop>
|
||||
<tabstop>versionSelectionBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -17,6 +17,10 @@
|
||||
<!-- technic logo icon -->
|
||||
<file>scalable/technic.svg</file>
|
||||
|
||||
<!-- ATLauncher logo icon (and related bits) -->
|
||||
<file>scalable/atlauncher.svg</file>
|
||||
<file>scalable/atlauncher-placeholder.png</file>
|
||||
|
||||
<!-- A proxy icon. Our own. SSSsss -->
|
||||
<file>scalable/proxy.svg</file>
|
||||
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
15
application/resources/multimc/scalable/atlauncher.svg
Normal file
15
application/resources/multimc/scalable/atlauncher.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg viewBox="0 0 2084 2084" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"
|
||||
stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<g fill-rule="nonzero">
|
||||
<path d="M1041.67 81.38l272.437 159.032-825.246 478.685-272.438-157.971L1041.67 81.38zm87.28 371.074l274.024-159.032 463.937 271.945-276.14 153.73-461.821-266.643z"
|
||||
fill="#3b3b3b"/>
|
||||
<path d="M216.42 561.126v961.081l825.247 479.746V1684.95l-551.222-321.774-1.587-644.079L216.42 561.126z"
|
||||
fill="#2e2e2e"/>
|
||||
<path d="M1866.91 1517.97l-825.246 483.986v-317.003l550.164-320.714-1.058-645.139 276.14-153.73v952.6z"
|
||||
fill="#333"/>
|
||||
<path d="M1590.77 719.097l-549.106 310.112v165.393l214.246-122.984v488.757l138.599-81.106V989.451l196.261-115.563V719.097z"
|
||||
fill="#89c236"/>
|
||||
<path d="M488.858 719.097l1.587 644.079 152.353 90.118v-198.79l230.645 132.527v199.319l168.753 98.6v-655.741L488.858 719.097zm383.527 531.166l-227.471-131.466v-150.02l227.471 127.225v154.261z"
|
||||
fill="#7baf31"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -79,6 +79,8 @@ public:
|
||||
|
||||
QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/";
|
||||
|
||||
QString ATL_DOWNLOAD_SERVER_URL = "https://download.nodecdn.net/containers/atl/";
|
||||
|
||||
/**
|
||||
* \brief Converts the Version to a string.
|
||||
* \return The version number in string format (major.minor.revision.build).
|
||||
|
Loading…
Reference in New Issue
Block a user