Merge pull request #4 from MultiMC/develop

Updates from MultiMC5
This commit is contained in:
Zakhar Afonin
2020-10-10 14:29:07 +03:00
committed by GitHub
34 changed files with 1378 additions and 26 deletions

View File

@@ -451,6 +451,13 @@ set(FLAME_SOURCES
modplatform/flame/FileResolvingTask.cpp
)
set(MODPACKSCH_SOURCES
modplatform/modpacksch/FTBPackInstallTask.h
modplatform/modpacksch/FTBPackInstallTask.cpp
modplatform/modpacksch/FTBPackManifest.h
modplatform/modpacksch/FTBPackManifest.cpp
)
add_unit_test(Index
SOURCES meta/Index_test.cpp
LIBS MultiMC_logic
@@ -481,6 +488,7 @@ set(LOGIC_SOURCES
${ICONS_SOURCES}
${FTB_SOURCES}
${FLAME_SOURCES}
${MODPACKSCH_SOURCES}
)
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})

View File

@@ -97,6 +97,7 @@ void Env::initHttpMetaCache()
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("general", QDir("cache").absolutePath());
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TwitchPacks", QDir("cache/TwitchPacks").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir::currentPath());

View File

@@ -115,7 +115,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
}
}
if(!results.contains("os.arch") || !results.contains("java.version") || !success)
if(!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success)
{
result.validity = JavaCheckResult::Validity::ReturnedInvalidData;
emit checkFinished(result);
@@ -124,6 +124,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
auto os_arch = results["os.arch"];
auto java_version = results["java.version"];
auto java_vendor = results["java.vendor"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64";
@@ -132,6 +133,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch;
result.javaVersion = java_version;
result.javaVendor = java_vendor;
qDebug() << "Java checker succeeded.";
emit checkFinished(result);
}

View File

@@ -17,6 +17,7 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
QString mojangPlatform;
QString realPlatform;
JavaVersion javaVersion;
QString javaVendor;
QString outLog;
QString errorLog;
bool is_64bit = false;

View File

@@ -33,17 +33,17 @@ void CheckJava::executeTask()
if (perInstance)
{
emit logLine(
tr("The java binary \"%1\" couldn't be found. Please fix the java path "
QString("The java binary \"%1\" couldn't be found. Please fix the java path "
"override in the instance's settings or disable it.").arg(m_javaPath),
MessageLevel::Warning);
}
else
{
emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in "
emit logLine(QString("The java binary \"%1\" couldn't be found. Please set up java in "
"the settings.").arg(m_javaPath),
MessageLevel::Warning);
}
emitFailed(tr("Java path is not valid."));
emitFailed(QString("Java path is not valid."));
return;
}
else
@@ -56,12 +56,13 @@ void CheckJava::executeTask()
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
auto storedArchitecture = settings->get("JavaArchitecture").toString();
auto storedVersion = settings->get("JavaVersion").toString();
auto storedVendor = settings->get("JavaVendor").toString();
m_javaUnixTime = javaUnixTime;
// if timestamps are not the same, or something is missing, check!
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0)
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0 || storedVendor.size() == 0)
{
m_JavaChecker = new JavaChecker();
emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
emit logLine(QString("Checking Java version..."), MessageLevel::MultiMC);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath;
m_JavaChecker->performCheck();
@@ -71,7 +72,8 @@ void CheckJava::executeTask()
{
auto verString = instance->settings()->get("JavaVersion").toString();
auto archString = instance->settings()->get("JavaArchitecture").toString();
printJavaInfo(verString, archString);
auto vendorString = instance->settings()->get("JavaVendor").toString();
printJavaInfo(verString, archString, vendorString);
}
emitSucceeded();
}
@@ -83,16 +85,16 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
case JavaCheckResult::Validity::Errored:
{
// Error message displayed if java can't start
emit logLine(tr("Could not start java:"), MessageLevel::Error);
emit logLine(QString("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
printSystemInfo(false, false);
emitFailed(tr("Could not start java!"));
emitFailed(QString("Could not start java!"));
return;
}
case JavaCheckResult::Validity::ReturnedInvalidData:
{
emit logLine(tr("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error);
emit logLine(QString("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC);
printSystemInfo(false, false);
@@ -102,9 +104,10 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
case JavaCheckResult::Validity::Valid:
{
auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform);
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.javaVendor);
instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
instance->settings()->set("JavaVendor", result.javaVendor);
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
emitSucceeded();
return;
@@ -112,9 +115,9 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
}
}
void CheckJava::printJavaInfo(const QString& version, const QString& architecture)
void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString & vendor)
{
emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC);
emit logLine(QString("Java is version %1, using %2-bit architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::MultiMC);
printSystemInfo(true, architecture == "64");
}
@@ -124,13 +127,13 @@ void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
auto system64 = Sys::isSystem64bit();
if(cpu64 != system64)
{
emit logLine(tr("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
emit logLine(QString("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
}
if(javaIsKnown)
{
if(javaIs64bit != system64)
{
emit logLine(tr("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
emit logLine(QString("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
}
}
}

View File

@@ -35,7 +35,7 @@ private slots:
void checkJavaFinished(JavaCheckResult result);
private:
void printJavaInfo(const QString & version, const QString & architecture);
void printJavaInfo(const QString & version, const QString & architecture, const QString & vendor);
void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
private:

View File

@@ -100,6 +100,11 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
// DEPRECATED: Read what versions the user configuration thinks should be used
m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
m_settings->registerSetting("LWJGLVersion", "");

View File

@@ -138,14 +138,31 @@ void World::repath(const QFileInfo &file)
m_folderName = file.fileName();
if(file.isFile() && file.suffix() == "zip")
{
m_iconFile = QString();
readFromZip(file);
}
else if(file.isDir())
{
QFileInfo assumedIconPath(file.absoluteFilePath() + "/icon.png");
if(assumedIconPath.exists()) {
m_iconFile = assumedIconPath.absoluteFilePath();
}
readFromFS(file);
}
}
bool World::resetIcon()
{
if(m_iconFile.isNull()) {
return false;
}
if(QFile(m_iconFile).remove()) {
m_iconFile = QString();
return true;
}
return false;
}
void World::readFromFS(const QFileInfo &file)
{
auto bytes = getLevelDatDataFromFS(file);

View File

@@ -40,6 +40,10 @@ public:
{
return m_actualName;
}
QString iconFile() const
{
return m_iconFile;
}
QDateTime lastPlayed() const
{
return m_lastPlayed;
@@ -70,6 +74,8 @@ public:
bool replace(World &with);
// change the world's filesystem path (used by world lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
// remove the icon file, if any
bool resetIcon();
bool rename(const QString &to);
bool install(const QString &to, const QString &name= QString());
@@ -88,6 +94,7 @@ protected:
QString m_containerOffsetPath;
QString m_folderName;
QString m_actualName;
QString m_iconFile;
QDateTime levelDatTime;
QDateTime m_lastPlayed;
int64_t m_randomSeed = 0;

View File

@@ -136,6 +136,19 @@ bool WorldList::deleteWorlds(int first, int last)
return true;
}
bool WorldList::resetIcon(int row)
{
if (row >= worlds.size() || row < 0)
return false;
World &m = worlds[row];
if(m.resetIcon()) {
emit dataChanged(index(row), index(row), {WorldList::IconFileRole});
return true;
}
return false;
}
int WorldList::columnCount(const QModelIndex &parent) const
{
return 3;
@@ -195,6 +208,10 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
{
return world.lastPlayed();
}
case IconFileRole:
{
return world.iconFile();
}
default:
return QVariant();
}

View File

@@ -44,7 +44,8 @@ public:
SeedRole,
NameRole,
GameModeRole,
LastPlayedRole
LastPlayedRole,
IconFileRole
};
WorldList(const QString &dir);
@@ -81,6 +82,9 @@ public:
/// Deletes the mod at the given index.
virtual bool deleteWorld(int index);
/// Removes the world icon, if any
virtual bool resetIcon(int index);
/// Deletes all the selected mods
virtual bool deleteWorlds(int first, int last);

View File

@@ -33,7 +33,7 @@ static QString replaceSuffix (QString target, const QString &suffix, const QStri
return target + replacement;
}
static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack)
static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW)
{
QuaZip zip(source);
if(!zip.open(QuaZip::mdUnzip))
@@ -48,6 +48,13 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
do
{
QString name = zip.getCurrentFileName();
auto lowercase = name.toLower();
if (nativeGLFW && name.contains("glfw")) {
continue;
}
if (nativeOpenAL && name.contains("openal")) {
continue;
}
if(applyJnilibHack)
{
name = replaceSuffix(name, ".jnilib", ".dylib");
@@ -76,12 +83,16 @@ void ExtractNatives::executeTask()
emitSucceeded();
return;
}
auto settings = minecraftInstance->settings();
bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
auto outputPath = minecraftInstance->getNativePath();
auto javaVersion = minecraftInstance->getJavaVersion();
bool jniHackEnabled = javaVersion.major() >= 8;
for(const auto &source: toExtract)
{
if(!unzipNatives(source, outputPath, jniHackEnabled))
if(!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW))
{
auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath);
emit logLine(reason, MessageLevel::Fatal);

View File

@@ -0,0 +1,150 @@
#include "FTBPackInstallTask.h"
#include "BuildConfig.h"
#include "FileSystem.h"
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "settings/INISettingsObject.h"
namespace ModpacksCH {
PackInstallTask::PackInstallTask(Modpack pack, QString version)
{
m_pack = pack;
m_version_name = version;
}
bool PackInstallTask::abort()
{
return true;
}
void PackInstallTask::executeTask()
{
// Find pack version
bool found = false;
VersionInfo version;
for(auto vInfo : m_pack.versions) {
if (vInfo.name == m_version_name) {
found = true;
version = vInfo;
continue;
}
}
if(!found) {
emitFailed("failed to find pack version " + m_version_name);
return;
}
auto *netJob = new NetJob("ModpacksCH::VersionFetch");
auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2")
.arg(m_pack.id).arg(version.id);
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();
ModpacksCH::Version version;
try
{
ModpacksCH::loadVersion(version, obj);
}
catch (const JSONValidationError &e)
{
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
return;
}
m_version = version;
install();
}
void PackInstallTask::onDownloadFailed(QString reason)
{
jobPtr.reset();
emitFailed(reason);
}
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();
for(auto target : m_version.targets) {
if(target.type == "game" && target.name == "minecraft") {
components->setComponentVersion("net.minecraft", target.version, true);
continue;
}
}
for(auto target : m_version.targets) {
if(target.type == "modloader" && target.name == "forge") {
components->setComponentVersion("net.minecraftforge", target.version, true);
}
}
components->saveNow();
jobPtr.reset(new NetJob(tr("Mod download")));
for(auto file : m_version.files) {
if(file.serverOnly) continue;
auto relpath = FS::PathCombine("minecraft", file.path, file.name);
auto path = FS::PathCombine(m_stagingPath , relpath);
qDebug() << "Will download" << file.url << "to" << path;
auto dl = Net::Download::makeFile(file.url, path);
jobPtr->addNetAction(dl);
}
connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
{
jobPtr.reset();
emitSucceeded();
});
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
{
jobPtr.reset();
emitFailed(reason);
});
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
{
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
jobPtr->start();
instance.setName(m_instName);
instance.setIconKey(m_instIcon);
instanceSettings->resumeSave();
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include "FTBPackManifest.h"
#include "InstanceTask.h"
#include "multimc_logic_export.h"
#include "net/NetJob.h"
namespace ModpacksCH {
class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask
{
Q_OBJECT
public:
explicit PackInstallTask(Modpack pack, QString version);
virtual ~PackInstallTask(){}
bool abort() override;
protected:
virtual void executeTask() override;
private slots:
void onDownloadSucceeded();
void onDownloadFailed(QString reason);
private:
void install();
private:
NetJobPtr jobPtr;
QByteArray response;
Modpack m_pack;
QString m_version_name;
Version m_version;
};
}

View File

@@ -0,0 +1,156 @@
#include "FTBPackManifest.h"
#include "Json.h"
static void loadSpecs(ModpacksCH::Specs & s, QJsonObject & obj)
{
s.id = Json::requireInteger(obj, "id");
s.minimum = Json::requireInteger(obj, "minimum");
s.recommended = Json::requireInteger(obj, "recommended");
}
static void loadTag(ModpacksCH::Tag & t, QJsonObject & obj)
{
t.id = Json::requireInteger(obj, "id");
t.name = Json::requireString(obj, "name");
}
static void loadArt(ModpacksCH::Art & a, QJsonObject & obj)
{
a.id = Json::requireInteger(obj, "id");
a.url = Json::requireString(obj, "url");
a.type = Json::requireString(obj, "type");
a.width = Json::requireInteger(obj, "width");
a.height = Json::requireInteger(obj, "height");
a.compressed = Json::requireBoolean(obj, "compressed");
a.sha1 = Json::requireString(obj, "sha1");
a.size = Json::requireInteger(obj, "size");
a.updated = Json::requireInteger(obj, "updated");
}
static void loadAuthor(ModpacksCH::Author & a, QJsonObject & obj)
{
a.id = Json::requireInteger(obj, "id");
a.name = Json::requireString(obj, "name");
a.type = Json::requireString(obj, "type");
a.website = Json::requireString(obj, "website");
a.updated = Json::requireInteger(obj, "updated");
}
static void loadVersionInfo(ModpacksCH::VersionInfo & v, QJsonObject & obj)
{
v.id = Json::requireInteger(obj, "id");
v.name = Json::requireString(obj, "name");
v.type = Json::requireString(obj, "type");
v.updated = Json::requireInteger(obj, "updated");
auto specs = Json::requireObject(obj, "specs");
loadSpecs(v.specs, specs);
}
void ModpacksCH::loadModpack(ModpacksCH::Modpack & m, QJsonObject & obj)
{
m.id = Json::requireInteger(obj, "id");
m.name = Json::requireString(obj, "name");
m.synopsis = Json::requireString(obj, "synopsis");
m.description = Json::requireString(obj, "description");
m.type = Json::requireString(obj, "type");
m.featured = Json::requireBoolean(obj, "featured");
m.installs = Json::requireInteger(obj, "installs");
m.plays = Json::requireInteger(obj, "plays");
m.updated = Json::requireInteger(obj, "updated");
m.refreshed = Json::requireInteger(obj, "refreshed");
auto artArr = Json::requireArray(obj, "art");
for (const auto & artRaw : artArr)
{
auto artObj = Json::requireObject(artRaw);
ModpacksCH::Art art;
loadArt(art, artObj);
m.art.append(art);
}
auto authorArr = Json::requireArray(obj, "authors");
for (const auto & authorRaw : authorArr)
{
auto authorObj = Json::requireObject(authorRaw);
ModpacksCH::Author author;
loadAuthor(author, authorObj);
m.authors.append(author);
}
auto versionArr = Json::requireArray(obj, "versions");
for (const auto & versionRaw : versionArr)
{
auto versionObj = Json::requireObject(versionRaw);
ModpacksCH::VersionInfo version;
loadVersionInfo(version, versionObj);
m.versions.append(version);
}
auto tagArr = Json::requireArray(obj, "tags");
for (const auto & tagRaw : tagArr)
{
auto tagObj = Json::requireObject(tagRaw);
ModpacksCH::Tag tag;
loadTag(tag, tagObj);
m.tags.append(tag);
}
m.updated = Json::requireInteger(obj, "updated");
}
static void loadVersionTarget(ModpacksCH::VersionTarget & a, QJsonObject & obj)
{
a.id = Json::requireInteger(obj, "id");
a.name = Json::requireString(obj, "name");
a.type = Json::requireString(obj, "type");
a.version = Json::requireString(obj, "version");
a.updated = Json::requireInteger(obj, "updated");
}
static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj)
{
a.id = Json::requireInteger(obj, "id");
a.type = Json::requireString(obj, "type");
a.path = Json::requireString(obj, "path");
a.name = Json::requireString(obj, "name");
a.version = Json::requireString(obj, "version");
a.url = Json::requireString(obj, "url");
a.sha1 = Json::requireString(obj, "sha1");
a.size = Json::requireInteger(obj, "size");
a.clientOnly = Json::requireBoolean(obj, "clientonly");
a.serverOnly = Json::requireBoolean(obj, "serveronly");
a.optional = Json::requireBoolean(obj, "optional");
a.updated = Json::requireInteger(obj, "updated");
}
void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj)
{
m.id = Json::requireInteger(obj, "id");
m.parent = Json::requireInteger(obj, "parent");
m.name = Json::requireString(obj, "name");
m.type = Json::requireString(obj, "type");
m.installs = Json::requireInteger(obj, "installs");
m.plays = Json::requireInteger(obj, "plays");
m.updated = Json::requireInteger(obj, "updated");
m.refreshed = Json::requireInteger(obj, "refreshed");
auto specs = Json::requireObject(obj, "specs");
loadSpecs(m.specs, specs);
auto targetArr = Json::requireArray(obj, "targets");
for (const auto & targetRaw : targetArr)
{
auto versionObj = Json::requireObject(targetRaw);
ModpacksCH::VersionTarget target;
loadVersionTarget(target, versionObj);
m.targets.append(target);
}
auto fileArr = Json::requireArray(obj, "files");
for (const auto & fileRaw : fileArr)
{
auto fileObj = Json::requireObject(fileRaw);
ModpacksCH::VersionFile file;
loadVersionFile(file, fileObj);
m.files.append(file);
}
}
//static void loadVersionChangelog(ModpacksCH::VersionChangelog & m, QJsonObject & obj)
//{
// m.content = Json::requireString(obj, "content");
// m.updated = Json::requireInteger(obj, "updated");
//}

View File

@@ -0,0 +1,127 @@
#pragma once
#include <QString>
#include <QVector>
#include <QUrl>
#include <QJsonObject>
#include <QMetaType>
#include "multimc_logic_export.h"
namespace ModpacksCH
{
struct Specs
{
int id;
int minimum;
int recommended;
};
struct Tag
{
int id;
QString name;
};
struct Art
{
int id;
QString url;
QString type;
int width;
int height;
bool compressed;
QString sha1;
int size;
int64_t updated;
};
struct Author
{
int id;
QString name;
QString type;
QString website;
int64_t updated;
};
struct VersionInfo
{
int id;
QString name;
QString type;
int64_t updated;
Specs specs;
};
struct Modpack
{
int id;
QString name;
QString synopsis;
QString description;
QString type;
bool featured;
int installs;
int plays;
int64_t updated;
int64_t refreshed;
QVector<Art> art;
QVector<Author> authors;
QVector<VersionInfo> versions;
QVector<Tag> tags;
};
struct VersionTarget
{
int id;
QString type;
QString name;
QString version;
int64_t updated;
};
struct VersionFile
{
int id;
QString type;
QString path;
QString name;
QString version;
QString url;
QString sha1;
int size;
bool clientOnly;
bool serverOnly;
bool optional;
int64_t updated;
};
struct Version
{
int id;
int parent;
QString name;
QString type;
int installs;
int plays;
int64_t updated;
int64_t refreshed;
Specs specs;
QVector<VersionTarget> targets;
QVector<VersionFile> files;
};
struct VersionChangelog
{
QString content;
int64_t updated;
};
MULTIMC_LOGIC_EXPORT void loadModpack(Modpack & m, QJsonObject & obj);
MULTIMC_LOGIC_EXPORT void loadVersion(Version & m, QJsonObject & obj);
}
Q_DECLARE_METATYPE(ModpacksCH::Modpack)

View File

@@ -124,6 +124,10 @@ SET(MULTIMC_SOURCES
# GUI - platform pages
pages/modplatform/VanillaPage.cpp
pages/modplatform/VanillaPage.h
pages/modplatform/ftb/FtbModel.cpp
pages/modplatform/ftb/FtbModel.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
@@ -250,6 +254,7 @@ SET(MULTIMC_UIS
# Platform pages
pages/modplatform/VanillaPage.ui
pages/modplatform/ftb/FtbPage.ui
pages/modplatform/legacy_ftb/Page.ui
pages/modplatform/twitch/TwitchPage.ui
pages/modplatform/ImportPage.ui

View File

@@ -24,7 +24,8 @@ void JavaCommon::javaWasOk(QWidget *parent, JavaCheckResult result)
{
QString text;
text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version "
"reported: %2<br />").arg(result.realPlatform, result.javaVersion.toString());
"reported: %2<br />Java vendor "
"reported: %3<br />").arg(result.realPlatform, result.javaVersion.toString(), result.javaVendor);
if (result.errorLog.size())
{
auto htmlError = result.errorLog;

View File

@@ -505,9 +505,14 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("JavaTimestamp", 0);
m_settings->registerSetting("JavaArchitecture", "");
m_settings->registerSetting("JavaVersion", "");
m_settings->registerSetting("JavaVendor", "");
m_settings->registerSetting("LastHostname", "");
m_settings->registerSetting("JvmArgs", "");
// Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false);
m_settings->registerSetting("UseNativeGLFW", false);
// Minecraft launch method
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");

View File

@@ -34,6 +34,7 @@
#include "widgets/PageContainer.h"
#include <pages/modplatform/VanillaPage.h>
#include <pages/modplatform/ftb/FtbPage.h>
#include <pages/modplatform/legacy_ftb/Page.h>
#include <pages/modplatform/twitch/TwitchPage.h>
#include <pages/modplatform/ImportPage.h>
@@ -125,6 +126,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
{
new VanillaPage(this),
importPage,
new FtbPage(this),
new LegacyFTB::Page(this),
twitchPage
};

View File

@@ -63,6 +63,10 @@ void MinecraftPage::applySettings()
s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked());
s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value());
s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
// Native library workarounds
s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
}
void MinecraftPage::loadSettings()
@@ -73,4 +77,7 @@ void MinecraftPage::loadSettings()
ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool());
ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());
ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt());
ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());
ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool());
}

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>545</width>
<height>195</height>
<width>936</width>
<height>1134</height>
</rect>
</property>
<property name="sizePolicy">
@@ -111,6 +111,29 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
<property name="title">
<string>Native library workarounds</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
<string>Use system installation of GLFW</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useNativeOpenALCheck">
<property name="text">
<string>Use system installation of OpenAL</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacerMinecraft">
<property name="orientation">
@@ -135,6 +158,8 @@
<tabstop>maximizedCheckBox</tabstop>
<tabstop>windowWidthSpinBox</tabstop>
<tabstop>windowHeightSpinBox</tabstop>
<tabstop>useNativeGLFWCheck</tabstop>
<tabstop>useNativeOpenALCheck</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@@ -163,6 +163,20 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("WrapperCommand");
m_settings->reset("PostExitCommand");
}
// Workarounds
bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked();
m_settings->set("OverrideNativeWorkarounds", workarounds);
if(workarounds)
{
m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
}
else
{
m_settings->reset("UseNativeOpenAL");
m_settings->reset("UseNativeGLFW");
}
}
void InstanceSettingsPage::loadSettings()
@@ -219,6 +233,11 @@ void InstanceSettingsPage::loadSettings()
m_settings->get("WrapperCommand").toString(),
m_settings->get("PostExitCommand").toString()
);
// Workarounds
ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool());
ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool());
ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool());
}
void InstanceSettingsPage::on_javaDetectBtn_clicked()

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>738</width>
<height>804</height>
<width>691</width>
<height>581</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -364,6 +364,58 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="workaroundsPage">
<attribute name="title">
<string>Workarounds</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QGroupBox" name="nativeWorkaroundsGroupBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Native libraries</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
<string>Use system installation of GLFW</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useNativeOpenALCheck">
<property name="text">
<string>Use system installation of OpenAL</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
@@ -398,6 +450,9 @@
<tabstop>showConsoleCheck</tabstop>
<tabstop>autoCloseConsoleCheck</tabstop>
<tabstop>showConsoleErrorCheck</tabstop>
<tabstop>nativeWorkaroundsGroupBox</tabstop>
<tabstop>useNativeGLFWCheck</tabstop>
<tabstop>useNativeOpenALCheck</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@@ -31,6 +31,33 @@
#include <QProcess>
#include <FileSystem.h>
class WorldListProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
WorldListProxyModel(QObject *parent) : QSortFilterProxyModel(parent) {}
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
QModelIndex sourceIndex = mapToSource(index);
if (index.column() == 0 && role == Qt::DecorationRole)
{
WorldList *worlds = qobject_cast<WorldList *>(sourceModel());
auto iconFile = worlds->data(sourceIndex, WorldList::IconFileRole).toString();
if(iconFile.isNull()) {
// NOTE: Minecraft uses the same placeholder for servers AND worlds
return MMC->getThemedIcon("unknown_server");
}
return QIcon(iconFile);
}
return sourceIndex.data(role);
}
};
WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QWidget *parent)
: QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds)
{
@@ -38,13 +65,14 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worl
ui->toolBar->insertSpacer(ui->actionRefresh);
QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this);
WorldListProxyModel * proxy = new WorldListProxyModel(this);
proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
proxy->setSourceModel(m_worlds.get());
ui->worldTreeView->setSortingEnabled(true);
ui->worldTreeView->setModel(proxy);
ui->worldTreeView->installEventFilter(this);
ui->worldTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
ui->worldTreeView->setIconSize(QSize(64,64));
connect(ui->worldTreeView, &QTreeView::customContextMenuRequested, this, &WorldListPage::ShowContextMenu);
auto head = ui->worldTreeView->header();
@@ -142,6 +170,19 @@ void WorldListPage::on_actionView_Folder_triggered()
DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true);
}
void WorldListPage::on_actionReset_Icon_triggered()
{
auto proxiedIndex = getSelectedWorld();
if(!proxiedIndex.isValid())
return;
if(m_worlds->resetIcon(proxiedIndex.row())) {
ui->actionReset_Icon->setEnabled(false);
}
}
QModelIndex WorldListPage::getSelectedWorld()
{
auto index = ui->worldTreeView->selectionModel()->currentIndex();
@@ -255,6 +296,8 @@ void WorldListPage::worldChanged(const QModelIndex &current, const QModelIndex &
ui->actionRemove->setEnabled(enable);
ui->actionCopy->setEnabled(enable);
ui->actionRename->setEnabled(enable);
bool hasIcon = !index.data(WorldList::IconFileRole).isNull();
ui->actionReset_Icon->setEnabled(enable && hasIcon);
}
void WorldListPage::on_actionAdd_triggered()
@@ -342,3 +385,5 @@ void WorldListPage::on_actionRefresh_triggered()
{
m_worlds->update();
}
#include "WorldListPage.moc"

View File

@@ -90,6 +90,7 @@ private slots:
void on_actionRename_triggered();
void on_actionRefresh_triggered();
void on_actionView_Folder_triggered();
void on_actionReset_Icon_triggered();
void worldChanged(const QModelIndex &current, const QModelIndex &previous);
void mceditState(LoggedProcess::State state);

View File

@@ -41,6 +41,12 @@
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
@@ -79,6 +85,7 @@
<addaction name="actionCopy"/>
<addaction name="actionRemove"/>
<addaction name="actionMCEdit"/>
<addaction name="actionReset_Icon"/>
<addaction name="separator"/>
<addaction name="actionCopy_Seed"/>
<addaction name="actionRefresh"/>
@@ -124,6 +131,14 @@
<string>View Folder</string>
</property>
</action>
<action name="actionReset_Icon">
<property name="text">
<string>Reset Icon</string>
</property>
<property name="toolTip">
<string>Remove world icon to make the game re-generate it on next load.</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -0,0 +1,302 @@
#include "FtbModel.h"
#include "BuildConfig.h"
#include "Env.h"
#include "MultiMC.h"
#include "Json.h"
#include <QPainter>
namespace Ftb {
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);
}
ModpacksCH::Modpack pack = modpacks.at(pos);
if(role == Qt::DisplayRole)
{
return pack.name;
}
else if (role == Qt::ToolTipRole)
{
return pack.synopsis;
}
else if(role == Qt::DecorationRole)
{
QIcon placeholder = MMC->getThemedIcon("screenshot-placeholder");
auto iter = m_logoMap.find(pack.name);
if (iter != m_logoMap.end()) {
auto & logo = *iter;
if(!logo.result.isNull()) {
return logo.result;
}
return placeholder;
}
for(auto art : pack.art) {
if(art.type == "square") {
((ListModel *)this)->requestLogo(pack.name, art.url);
}
}
return placeholder;
}
else if(role == Qt::UserRole)
{
QVariant v;
v.setValue(pack);
return v;
}
return QVariant();
}
void ListModel::performSearch()
{
auto *netJob = new NetJob("Ftb::Search");
QString searchUrl;
if(currentSearchTerm.isEmpty()) {
searchUrl = BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/popular/plays/100";
}
else {
searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/search/25?term=%1")
.arg(currentSearchTerm);
}
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
}
void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
{
if(m_logoMap.contains(logo))
{
callback(ENV.metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
}
else
{
requestLogo(logo, logoUrl);
}
}
void ListModel::searchWithTerm(const QString &term)
{
if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) {
return;
}
currentSearchTerm = term;
if(jobPtr) {
jobPtr->abort();
searchState = ResetRequested;
return;
}
else {
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
}
performSearch();
}
void ListModel::searchRequestFinished()
{
jobPtr.reset();
remainingPacks.clear();
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 packs = doc.object().value("packs").toArray();
for(auto pack : packs) {
auto packId = pack.toInt();
remainingPacks.append(packId);
}
if(!remainingPacks.isEmpty()) {
currentPack = remainingPacks.at(0);
requestPack();
}
}
void ListModel::searchRequestFailed(QString reason)
{
jobPtr.reset();
remainingPacks.clear();
if(searchState == ResetRequested) {
beginResetModel();
modpacks.clear();
endResetModel();
performSearch();
} else {
searchState = Finished;
}
}
void ListModel::requestPack()
{
auto *netJob = new NetJob("Ftb::Search");
auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1")
.arg(currentPack);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::packRequestFinished);
QObject::connect(netJob, &NetJob::failed, this, &ListModel::packRequestFailed);
}
void ListModel::packRequestFinished()
{
jobPtr.reset();
remainingPacks.removeOne(currentPack);
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();
ModpacksCH::Modpack pack;
try
{
ModpacksCH::loadModpack(pack, obj);
}
catch (const JSONValidationError &e)
{
qDebug() << QString::fromUtf8(response);
qWarning() << "Error while reading pack manifest from FTB: " << e.cause();
return;
}
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size());
modpacks.append(pack);
endInsertRows();
if(!remainingPacks.isEmpty()) {
currentPack = remainingPacks.at(0);
requestPack();
}
}
void ListModel::packRequestFailed(QString reason)
{
jobPtr.reset();
remainingPacks.removeOne(currentPack);
}
void ListModel::logoLoaded(QString logo, bool stale)
{
auto & logoObj = m_logoMap[logo];
logoObj.downloadJob.reset();
QString smallPath = logoObj.fullpath + ".small";
QFileInfo smallInfo(smallPath);
if(stale || !smallInfo.exists()) {
QImage image(logoObj.fullpath);
if (image.isNull())
{
logoObj.failed = true;
return;
}
QImage small;
if (image.width() > image.height()) {
small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
}
else {
small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
}
QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
QImage square(QSize(256, 256), QImage::Format_ARGB32);
square.fill(Qt::transparent);
QPainter painter(&square);
painter.drawImage(offset, small);
painter.end();
square.save(logoObj.fullpath + ".small", "PNG");
}
logoObj.result = QIcon(logoObj.fullpath + ".small");
for(int i = 0; i < modpacks.size(); i++) {
if(modpacks[i].name == logo) {
emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
}
}
}
void ListModel::logoFailed(QString logo)
{
m_logoMap[logo].failed = true;
m_logoMap[logo].downloadJob.reset();
}
void ListModel::requestLogo(QString logo, QString url)
{
if(m_logoMap.contains(logo)) {
return;
}
MetaEntryPtr entry = ENV.metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
bool stale = entry->isStale();
NetJob *job = new NetJob(QString("FTB Icon Download %1").arg(logo));
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath, stale]
{
logoLoaded(logo, stale);
});
QObject::connect(job, &NetJob::failed, this, [this, logo]
{
logoFailed(logo);
});
auto &newLogoEntry = m_logoMap[logo];
newLogoEntry.downloadJob = job;
newLogoEntry.fullpath = fullPath;
job->start();
}
}

View File

@@ -0,0 +1,68 @@
#pragma once
#include <QAbstractListModel>
#include "modplatform/modpacksch/FTBPackManifest.h"
#include "net/NetJob.h"
#include <QIcon>
namespace Ftb {
struct Logo {
QString fullpath;
NetJobPtr downloadJob;
QIcon result;
bool failed = false;
};
typedef QMap<QString, Logo> 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 getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
void searchWithTerm(const QString & term);
private slots:
void performSearch();
void searchRequestFinished();
void searchRequestFailed(QString reason);
void requestPack();
void packRequestFinished();
void packRequestFailed(QString reason);
void logoFailed(QString logo);
void logoLoaded(QString logo, bool stale);
private:
void requestLogo(QString file, QString url);
private:
QList<ModpacksCH::Modpack> modpacks;
LogoMap m_logoMap;
QString currentSearchTerm;
enum SearchState {
None,
CanPossiblyFetchMore,
ResetRequested,
Finished
} searchState = None;
NetJobPtr jobPtr;
int currentPack;
QList<int> remainingPacks;
QByteArray response;
};
}

View File

@@ -0,0 +1,109 @@
#include "FtbPage.h"
#include "ui_FtbPage.h"
#include <QKeyEvent>
#include "dialogs/NewInstanceDialog.h"
#include "modplatform/modpacksch/FTBPackInstallTask.h"
FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent)
: QWidget(parent), ui(new Ui::FtbPage), dialog(dialog)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &FtbPage::triggerSearch);
ui->searchEdit->installEventFilter(this);
model = new Ftb::ListModel(this);
ui->packView->setModel(model);
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged);
}
FtbPage::~FtbPage()
{
delete ui;
}
bool FtbPage::eventFilter(QObject* watched, QEvent* event)
{
if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Return) {
triggerSearch();
keyEvent->accept();
return true;
}
}
return QWidget::eventFilter(watched, event);
}
bool FtbPage::shouldDisplay() const
{
return true;
}
void FtbPage::openedImpl()
{
dialog->setSuggestedPack();
triggerSearch();
}
void FtbPage::suggestCurrent()
{
if(isOpened)
{
dialog->setSuggestedPack(selected.name, new ModpacksCH::PackInstallTask(selected, selectedVersion));
for(auto art : selected.art) {
if(art.type == "square") {
QString editedLogoName;
editedLogoName = selected.name;
model->getLogo(selected.name, art.url, [this, editedLogoName](QString logo)
{
dialog->setSuggestedIconFromFile(logo + ".small", editedLogoName);
});
}
}
}
}
void FtbPage::triggerSearch()
{
model->searchWithTerm(ui->searchEdit->text());
}
void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex second)
{
ui->versionSelectionBox->clear();
if(!first.isValid())
{
if(isOpened)
{
dialog->setSuggestedPack();
}
return;
}
selected = model->data(first, Qt::UserRole).value<ModpacksCH::Modpack>();
// reverse foreach, so that the newest versions are first
for (auto i = selected.versions.size(); i--;) {
ui->versionSelectionBox->addItem(selected.versions.at(i).name);
}
suggestCurrent();
}
void FtbPage::onVersionSelectionChanged(QString data)
{
if(data.isNull() || data.isEmpty())
{
selectedVersion = "";
return;
}
selectedVersion = data;
suggestCurrent();
}

View File

@@ -0,0 +1,77 @@
/* 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 "FtbModel.h"
#include <QWidget>
#include "MultiMC.h"
#include "pages/BasePage.h"
#include "tasks/Task.h"
namespace Ui
{
class FtbPage;
}
class NewInstanceDialog;
class FtbPage : public QWidget, public BasePage
{
Q_OBJECT
public:
explicit FtbPage(NewInstanceDialog* dialog, QWidget *parent = 0);
virtual ~FtbPage();
virtual QString displayName() const override
{
return tr("FTB");
}
virtual QIcon icon() const override
{
return MMC->getThemedIcon("ftb_logo");
}
virtual QString id() const override
{
return "ftb";
}
virtual QString helpPage() const override
{
return "FTB-platform";
}
virtual bool shouldDisplay() const override;
void openedImpl() override;
bool eventFilter(QObject * watched, QEvent * event) override;
private:
void suggestCurrent();
private slots:
void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data);
private:
Ui::FtbPage *ui = nullptr;
NewInstanceDialog* dialog = nullptr;
Ftb::ListModel* model = nullptr;
ModpacksCH::Modpack selected;
QString selectedVersion;
};

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FtbPage</class>
<widget class="QWidget" name="FtbPage">
<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">
<widget class="QListView" name="packView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="searchButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLineEdit" name="searchEdit"/>
</item>
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0" rowminimumheight="0" columnminimumwidth="0,0">
<item row="0" column="1">
<widget class="QComboBox" name="versionSelectionBox"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Version selected:</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>searchEdit</tabstop>
<tabstop>searchButton</tabstop>
<tabstop>packView</tabstop>
<tabstop>versionSelectionBox</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -75,6 +75,8 @@ public:
QString FMLLIBS_FORGE_BASE_URL = "https://files.minecraftforge.net/fmllibs/";
QString TRANSLATIONS_BASE_URL = "https://files.multimc.org/translations/";
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/";
/**

View File

@@ -2,7 +2,7 @@ import java.lang.Integer;
public class JavaCheck
{
private static final String[] keys = {"os.arch", "java.version"};
private static final String[] keys = {"os.arch", "java.version", "java.vendor"};
public static void main (String [] args)
{
int ret = 0;