Compare commits

..

1 Commits

Author SHA1 Message Date
janrupf
3bdf63b0ee GH-2065 Strip non ASCII chars 2019-06-22 01:35:07 +02:00
37 changed files with 133 additions and 691 deletions

View File

@@ -32,7 +32,7 @@ set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader)
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif()
@@ -46,7 +46,7 @@ set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetc
######## Set version numbers ########
set(MultiMC_VERSION_MAJOR 0)
set(MultiMC_VERSION_MINOR 6)
set(MultiMC_VERSION_HOTFIX 6)
set(MultiMC_VERSION_HOTFIX 5)
# Build number
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")

View File

@@ -443,8 +443,6 @@ set(FLAME_SOURCES
modplatform/flame/PackManifest.cpp
modplatform/flame/FileResolvingTask.h
modplatform/flame/FileResolvingTask.cpp
modplatform/flame/UrlResolvingTask.h
modplatform/flame/UrlResolvingTask.cpp
)
add_unit_test(Index

View File

@@ -304,6 +304,10 @@ QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{
string[i] = replaceWith;
}
else if(string[i] > 127) // non ASCII
{
string[i] = replaceWith;
}
}
return string;
}

View File

@@ -234,7 +234,7 @@ void InstanceList::deleteInstance(const InstanceId& id)
auto inst = getInstanceById(id);
if(!inst)
{
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
qDebug() << "Cannot delete instance" << id << " No such instance is present.";
return;
}
@@ -819,7 +819,6 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst
instanceSet.insert(instID);
m_groups.insert(groupName);
emit instancesChanged();
emit instanceSelectRequest(instID);
}
saveGroupList();
return true;

View File

@@ -129,7 +129,6 @@ public:
signals:
void dataIsInvalid();
void instancesChanged();
void instanceSelectRequest(QString instanceId);
void groupsChanged(QSet<QString> groups);
public slots:

View File

@@ -635,9 +635,6 @@ void ComponentList::componentDataChanged()
qWarning() << "ComponentList got dataChenged signal from a non-Component!";
return;
}
if(objPtr->getID() == "net.minecraft") {
emit minecraftChanged();
}
// figure out which one is it... in a seriously dumb way.
int index = 0;
for (auto component: d->components)

View File

@@ -104,9 +104,6 @@ public:
/// if there is a save scheduled, do it now.
void saveNow();
signals:
void minecraftChanged();
public:
/// get the profile component by id
Component * getComponent(const QString &id);

View File

@@ -648,7 +648,8 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
auto i = sessionRef.u.properties.begin();
while (i != sessionRef.u.properties.end())
{
if(i.value().length() <= 3) {
if(i.key() == "preferredLanguage")
{
++i;
continue;
}

View File

@@ -113,6 +113,9 @@ public:
virtual JavaVersion getJavaVersion() const;
signals:
void versionReloaded();
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
QStringList validLaunchMethods();

View File

@@ -89,56 +89,6 @@ void runGlxinfo(QStringList & log)
}
#endif
#ifdef Q_OS_WIN32
#include <windows.h>
namespace {
void printDisplayFlags(QStringList & log, const DWORD flags, const char * spacing)
{
QStringList flagStrings;
if(flags & DISPLAY_DEVICE_ACTIVE)
{
flags << "Active";
}
if(flags & DISPLAY_DEVICE_PRIMARY_DEVICE)
{
flags << "Primary";
}
if(flags & DISPLAY_DEVICE_MIRRORING_DRIVER)
{
flags << "Mirroring";
}
if(flagStrings.size())
{
log << QString("%1%2").arg(spacing, flagStrings.join(','));
}
}
void probeWinAPIForDevices(QStringList & log)
{
DISPLAY_DEVICEW dd;
memset(&dd, 0, sizeof(DISPLAY_DEVICEW));
int i = 0;
// enumerate devices
while(EnumDisplayDevicesW(NULL, i, &dd, 0))
{
log << "Display devices:";
log << QString("Device Name: %1 | Device String: %2").arg(QString::fromWCharArray(dd.DeviceName), QString::fromWCharArray(dd.DeviceString));
printDisplayFlags(log, dd.StateFlags, " ");
// enumerate monitors
if(EnumDisplayDevicesW(dd.DeviceName, 0, &dd, 0))
{
log << QString(" Monitor Name: %1 | Monitor String: %2").arg(QString::fromWCharArray(dd.DeviceName), QString::fromWCharArray(dd.DeviceString));
printDisplayFlags(log, dd.StateFlags, " ");
}
i++;
}
}
}
#endif
void PrintInstanceInfo::executeTask()
{
auto instance = m_parent->instance();
@@ -150,10 +100,6 @@ void PrintInstanceInfo::executeTask()
::runGlxinfo(log);
#endif
#ifdef Q_OS_WIN32
::probeWinAPIForDevices(log);
#endif
logLines(log, MessageLevel::MultiMC);
logLines(instance->verboseDescription(m_session), MessageLevel::MultiMC);
emitSucceeded();

View File

@@ -1,9 +1,7 @@
#include "FileResolvingTask.h"
#include "Json.h"
namespace {
const char * metabase = "https://cursemeta.dries007.net";
}
const char * metabase = "https://cursemeta.dries007.net";
Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess)
: m_toProcess(toProcess)
@@ -36,14 +34,70 @@ void Flame::FileResolvingTask::netJobFinished()
int index = 0;
for(auto & bytes: results)
{
auto & out = m_toProcess.files[index];
try
{
failed &= (!out.parseFromBytes(bytes));
auto doc = Json::requireDocument(bytes);
auto obj = Json::requireObject(doc);
auto & out = m_toProcess.files[index];
// result code signifies true failure.
if(obj.contains("code"))
{
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a negative result:";
qCritical() << bytes;
failed = true;
continue;
}
out.fileName = Json::requireString(obj, "FileNameOnDisk");
QString rawUrl = Json::requireString(obj, "DownloadURL");
out.url = QUrl(rawUrl, QUrl::TolerantMode);
if(!out.url.isValid())
{
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
}
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
// It is also optional
QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
if(!projObj.isEmpty())
{
QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
if(strType == "singlefile")
{
out.type = File::Type::SingleFile;
}
else if(strType == "ctoc")
{
out.type = File::Type::Ctoc;
}
else if(strType == "cmod2")
{
out.type = File::Type::Cmod2;
}
else if(strType == "mod")
{
out.type = File::Type::Mod;
}
else if(strType == "folder")
{
out.type = File::Type::Folder;
}
else if(strType == "modpack")
{
out.type = File::Type::Modpack;
}
else
{
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType;
out.type = File::Type::Unknown;
failed = true;
continue;
}
out.targetFolder = Json::ensureString(projObj, "Path", "mods");
}
out.resolved = true;
}
catch (const JSONValidationError &e)
{
auto & out = m_toProcess.files[index];
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
qCritical() << e.cause();
qCritical() << "JSON:";

View File

@@ -64,63 +64,3 @@ void Flame::loadManifest(Flame::Manifest & m, const QString &filepath)
}
loadManifestV1(m, obj);
}
bool Flame::File::parseFromBytes(const QByteArray& bytes)
{
auto doc = Json::requireDocument(bytes);
auto obj = Json::requireObject(doc);
// result code signifies true failure.
if(obj.contains("code"))
{
qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:";
qCritical() << bytes;
return false;
}
fileName = Json::requireString(obj, "FileNameOnDisk");
QString rawUrl = Json::requireString(obj, "DownloadURL");
url = QUrl(rawUrl, QUrl::TolerantMode);
if(!url.isValid())
{
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
}
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
// It is also optional
QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
if(!projObj.isEmpty())
{
QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
if(strType == "singlefile")
{
type = File::Type::SingleFile;
}
else if(strType == "ctoc")
{
type = File::Type::Ctoc;
}
else if(strType == "cmod2")
{
type = File::Type::Cmod2;
}
else if(strType == "mod")
{
type = File::Type::Mod;
}
else if(strType == "folder")
{
type = File::Type::Folder;
}
else if(strType == "modpack")
{
type = File::Type::Modpack;
}
else
{
qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType;
type = File::Type::Unknown;
return false;
}
targetFolder = Json::ensureString(projObj, "Path", "mods");
}
resolved = true;
return true;
}

View File

@@ -8,9 +8,6 @@ namespace Flame
{
struct File
{
// NOTE: throws JSONValidationError
bool parseFromBytes(const QByteArray &bytes);
int projectId = 0;
int fileId = 0;
// NOTE: the opposite to 'optional'. This is at the time of writing unused.

View File

@@ -1,175 +0,0 @@
#include "UrlResolvingTask.h"
#include <QtXml>
#include <Json.h>
namespace {
const char * metabase = "https://cursemeta.dries007.net";
}
Flame::UrlResolvingTask::UrlResolvingTask(const QString& toProcess)
: m_url(toProcess)
{
}
void Flame::UrlResolvingTask::executeTask()
{
resolveUrl();
}
void Flame::UrlResolvingTask::resolveUrl()
{
setStatus(tr("Resolving URL..."));
setProgress(0, 1);
QUrl actualUrl(m_url);
if(actualUrl.host() != "www.curseforge.com") {
emitFailed(tr("Not a Twitch URL."));
return;
}
m_dljob.reset(new NetJob("URL resolver"));
bool weAreDigging = false;
needle = QString();
if(m_url.startsWith("https://")) {
if(m_url.endsWith("?client=y")) {
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download?client=y
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088?client=y
m_url.chop(9);
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
}
if(m_url.endsWith("/download")) {
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download -> need to dig inside html...
weAreDigging = true;
needle = m_url;
needle.replace("https://", "twitch://");
needle.replace("/download", "/download-client/");
m_url.append("?client=y");
} else if (m_url.contains("/download/")) {
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
m_url.replace("/download/", "/download-client/");
}
}
else if(m_url.startsWith("twitch://")) {
// twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
m_url.replace(0, 9, "https://");
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
}
auto dl = Net::Download::makeByteArray(QUrl(m_url), &results);
m_dljob->addNetAction(dl);
if(weAreDigging) {
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processHTML);
} else {
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCCIP);
}
m_dljob->start();
}
void Flame::UrlResolvingTask::processHTML()
{
QString htmlDoc = QString::fromUtf8(results);
auto index = htmlDoc.indexOf(needle);
if(index < 0) {
emitFailed(tr("Couldn't find the needle in the haystack..."));
return;
}
auto indexStart = index;
int indexEnd = -1;
while((index + 1) < htmlDoc.size() && htmlDoc[index] != '"') {
index ++;
if(htmlDoc[index] == '"') {
indexEnd = index;
break;
}
}
if(indexEnd > 0) {
QString found = htmlDoc.mid(indexStart, indexEnd - indexStart);
qDebug() << "Found needle: " << found;
// twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
m_url = found;
resolveUrl();
return;
}
emitFailed(tr("Couldn't find the end of the needle in the haystack..."));
return;
}
void Flame::UrlResolvingTask::processCCIP()
{
QDomDocument doc;
if (!doc.setContent(results)) {
qDebug() << results;
emitFailed(tr("Resolving failed."));
return;
}
auto packageNode = doc.namedItem("package");
if(!packageNode.isElement()) {
emitFailed(tr("Resolving failed: missing package root element."));
return;
}
auto projectNode = packageNode.namedItem("project");
if(!projectNode.isElement()) {
emitFailed(tr("Resolving failed: missing project element."));
return;
}
auto attribs = projectNode.attributes();
auto projectIdNode = attribs.namedItem("id");
if(!projectIdNode.isAttr()) {
emitFailed(tr("Resolving failed: missing id attribute."));
return;
}
auto fileIdNode = attribs.namedItem("file");
if(!fileIdNode.isAttr()) {
emitFailed(tr("Resolving failed: missing file attribute."));
return;
}
auto projectId = projectIdNode.nodeValue();
auto fileId = fileIdNode.nodeValue();
bool success = true;
m_result.projectId = projectId.toInt(&success);
if(!success) {
emitFailed(tr("Failed to resove projectId as a number."));
return;
}
m_result.fileId = fileId.toInt(&success);
if(!success) {
emitFailed(tr("Failed to resove fileId as a number."));
return;
}
qDebug() << "Resolved" << m_url << "as" << m_result.projectId << "/" << m_result.fileId;
resolveIDs();
}
void Flame::UrlResolvingTask::resolveIDs()
{
setStatus(tr("Resolving mod IDs..."));
m_dljob.reset(new NetJob("Mod id resolver"));
auto projectIdStr = QString::number(m_result.projectId);
auto fileIdStr = QString::number(m_result.fileId);
QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr);
auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results);
m_dljob->addNetAction(dl);
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCursemeta);
m_dljob->start();
}
void Flame::UrlResolvingTask::processCursemeta()
{
try {
if(m_result.parseFromBytes(results)) {
emitSucceeded();
qDebug() << results;
return;
}
} catch (const JSONValidationError &e) {
qCritical() << "Resolving of" << m_result.projectId << m_result.fileId << "failed because of a parsing error:";
qCritical() << e.cause();
qCritical() << "JSON:";
qCritical() << results;
}
emitFailed(tr("Failed to resolve the modpack file."));
}

View File

@@ -1,43 +0,0 @@
#pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
#include "PackManifest.h"
#include "multimc_logic_export.h"
namespace Flame
{
class MULTIMC_LOGIC_EXPORT UrlResolvingTask : public Task
{
Q_OBJECT
public:
explicit UrlResolvingTask(const QString &toProcess);
virtual ~UrlResolvingTask() {};
const Flame::File &getResults() const
{
return m_result;
}
protected:
virtual void executeTask() override;
protected slots:
void processCCIP();
void processHTML();
void processCursemeta();
private:
void resolveUrl();
void resolveIDs();
private: /* data */
QString m_url;
QString needle;
Flame::File m_result;
QByteArray results;
NetJobPtr m_dljob;
};
}

View File

@@ -36,10 +36,8 @@ QString INIFile::unescape(QString orig)
{
if(c == 'n')
out += '\n';
else if(c == 't')
else if (c == 't')
out += '\t';
else if(c == '#')
out += '#';
else
out += c;
prev = 0;
@@ -69,8 +67,6 @@ QString INIFile::escape(QString orig)
out += "\\t";
else if(c == '\\')
out += "\\\\";
else if(c == '#')
out += "\\#";
else
out += c;
}
@@ -124,15 +120,7 @@ bool INIFile::loadFile(QByteArray file)
{
QString &lineRaw = lines[i];
// Ignore comments.
int commentIndex = 0;
QString line = lineRaw;
// Search for comments until no more escaped # are available
while((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) {
if(commentIndex > 0 && line.at(commentIndex - 1) == '\\') {
continue;
}
line = line.left(lineRaw.indexOf('#')).trimmed();
}
QString line = lineRaw.left(lineRaw.indexOf('#')).trimmed();
int eqPos = line.indexOf('=');
if (eqPos == -1)

View File

@@ -26,7 +26,6 @@ slots:
QTest::newRow("Plain text") << "Lorem ipsum dolor sit amet.";
QTest::newRow("Escape sequences") << "Lorem\n\t\n\\n\\tAAZ\nipsum dolor\n\nsit amet.";
QTest::newRow("Escape sequences 2") << "\"\n\n\"";
QTest::newRow("Hashtags") << "some data#something";
}
void test_Escape()
{
@@ -41,7 +40,7 @@ slots:
void test_SaveLoad()
{
QString a = "a";
QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\#thisIsNotAComment";
QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\";
QString filename = "test_SaveLoad.ini";
// save

View File

@@ -131,14 +131,7 @@ void DownloadTask::processDownloadedVersionInfo()
QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged);
QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed);
if(netJob->size() == 1) // Translation issues... see https://github.com/MultiMC/MultiMC5/issues/1701
{
setStatus(tr("Downloading one update file."));
}
else
{
setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
}
setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
qDebug() << "Begin downloading update files to" << m_updateFilesDir.path();
m_filesNetJob = netJob;
m_filesNetJob->start();

View File

@@ -702,9 +702,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
// model reset -> selection is invalid. All the instance pointers are wrong.
connect(MMC->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad);
// handle newly added instances
connect(MMC->instances().get(), &InstanceList::instanceSelectRequest, this, &MainWindow::instanceSelectRequest);
// When the global settings page closes, we want to know about it and update our state
connect(MMC, &MultiMC::globalSettingsClosed, this, &MainWindow::globalSettingsClosed);
@@ -1667,7 +1664,6 @@ void MainWindow::on_actionDeleteInstance_triggered()
{
return;
}
auto id = m_selectedInstance->id();
auto response = CustomMessageBox::selectable(
this,
tr("CAREFUL!"),
@@ -1678,7 +1674,7 @@ void MainWindow::on_actionDeleteInstance_triggered()
)->exec();
if (response == QMessageBox::Yes)
{
MMC->instances()->deleteInstance(id);
MMC->instances()->deleteInstance(m_selectedInstance->id());
}
}
@@ -1840,11 +1836,6 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
}
}
void MainWindow::instanceSelectRequest(QString id)
{
setSelectedInstanceById(id);
}
void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
auto current = view->selectionModel()->currentIndex();

View File

@@ -152,8 +152,6 @@ private slots:
void instanceChanged(const QModelIndex &current, const QModelIndex &previous);
void instanceSelectRequest(QString id);
void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void selectionBad();

View File

@@ -39,8 +39,6 @@
#include <pages/modplatform/ImportPage.h>
#include <pages/modplatform/TechnicPage.h>
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog)
{
@@ -96,15 +94,8 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
if(!url.isEmpty())
{
QUrl actualUrl(url);
if(actualUrl.host() == "www.curseforge.com") {
m_container->selectPage("twitch");
twitchPage->setUrl(url);
}
else {
m_container->selectPage("import");
importPage->setUrl(url);
}
m_container->selectPage("import");
importPage->setUrl(url);
}
updateDialogState();
@@ -128,13 +119,13 @@ void NewInstanceDialog::accept()
QList<BasePage *> NewInstanceDialog::getPages()
{
importPage = new ImportPage(this);
twitchPage = new TwitchPage(this);
return
{
new VanillaPage(this),
new FTBPage(this),
importPage,
twitchPage,
new FTBPage(this)
new TwitchPage(this),
new TechnicPage(this)
};
}

View File

@@ -29,7 +29,6 @@ class NewInstanceDialog;
class PageContainer;
class QDialogButtonBox;
class ImportPage;
class TwitchPage;
class NewInstanceDialog : public QDialog, public BasePageProvider
{
@@ -68,7 +67,6 @@ private:
QString InstIconKey;
ImportPage *importPage = nullptr;
TwitchPage *twitchPage = nullptr;
std::unique_ptr<InstanceTask> creationTask;
bool importIcon = false;

View File

@@ -220,8 +220,6 @@ VisualGroup *GroupView::categoryAt(const QPoint &pos, VisualGroup::HitResults &
QString GroupView::groupNameAt(const QPoint &point)
{
executeDelayedItemsLayout();
VisualGroup::HitResults hitresult;
auto group = categoryAt(point + offset(), hitresult);
if(group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit)))
@@ -248,7 +246,7 @@ int GroupView::itemWidth() const
void GroupView::mousePressEvent(QMouseEvent *event)
{
executeDelayedItemsLayout();
// endCategoryEditor();
QPoint visualPos = event->pos();
QPoint geometryPos = event->pos() + offset();
@@ -297,8 +295,6 @@ void GroupView::mousePressEvent(QMouseEvent *event)
void GroupView::mouseMoveEvent(QMouseEvent *event)
{
executeDelayedItemsLayout();
QPoint topLeft;
QPoint visualPos = event->pos();
QPoint geometryPos = event->pos() + offset();
@@ -355,8 +351,6 @@ void GroupView::mouseMoveEvent(QMouseEvent *event)
void GroupView::mouseReleaseEvent(QMouseEvent *event)
{
executeDelayedItemsLayout();
QPoint visualPos = event->pos();
QPoint geometryPos = event->pos() + offset();
QPersistentModelIndex index = indexAt(visualPos);
@@ -411,8 +405,6 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event)
void GroupView::mouseDoubleClickEvent(QMouseEvent *event)
{
executeDelayedItemsLayout();
QModelIndex index = indexAt(event->pos());
if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index))
{
@@ -536,8 +528,6 @@ void GroupView::resizeEvent(QResizeEvent *event)
void GroupView::dragEnterEvent(QDragEnterEvent *event)
{
executeDelayedItemsLayout();
if (!isDragEventAccepted(event))
{
return;
@@ -549,8 +539,6 @@ void GroupView::dragEnterEvent(QDragEnterEvent *event)
void GroupView::dragMoveEvent(QDragMoveEvent *event)
{
executeDelayedItemsLayout();
if (!isDragEventAccepted(event))
{
return;
@@ -562,16 +550,12 @@ void GroupView::dragMoveEvent(QDragMoveEvent *event)
void GroupView::dragLeaveEvent(QDragLeaveEvent *event)
{
executeDelayedItemsLayout();
m_lastDragPosition = QPoint();
viewport()->update();
}
void GroupView::dropEvent(QDropEvent *event)
{
executeDelayedItemsLayout();
m_lastDragPosition = QPoint();
stopAutoScroll();
@@ -622,8 +606,6 @@ void GroupView::dropEvent(QDropEvent *event)
void GroupView::startDrag(Qt::DropActions supportedActions)
{
executeDelayedItemsLayout();
QModelIndexList indexes = selectionModel()->selectedIndexes();
if(indexes.count() == 0)
return;
@@ -669,15 +651,11 @@ void GroupView::startDrag(Qt::DropActions supportedActions)
QRect GroupView::visualRect(const QModelIndex &index) const
{
const_cast<GroupView*>(this)->executeDelayedItemsLayout();
return geometryRect(index).translated(-offset());
}
QRect GroupView::geometryRect(const QModelIndex &index) const
{
const_cast<GroupView*>(this)->executeDelayedItemsLayout();
if (!index.isValid() || isIndexHidden(index) || index.column() > 0)
{
return QRect();
@@ -717,10 +695,9 @@ QModelIndex GroupView::indexAt(const QPoint &point) const
return QModelIndex();
}
void GroupView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands)
void GroupView::setSelection(const QRect &rect,
const QItemSelectionModel::SelectionFlags commands)
{
executeDelayedItemsLayout();
for (int i = 0; i < model()->rowCount(); ++i)
{
QModelIndex index = model()->index(i, 0);
@@ -755,7 +732,8 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons
return pixmap;
}
QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const
QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices,
QRect *r) const
{
Q_ASSERT(r);
QRect &rect = *r;

View File

@@ -30,7 +30,6 @@ int main(int argc, char *argv[])
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
// initialize Qt

View File

@@ -1,11 +1,11 @@
Package: multimc
Version: 1.3-1
Version: 1.2-1
Architecture: all
Maintainer: Petr Mrázek <peterix@gmail.com>
Section: games
Priority: optional
Installed-Size: 75
Depends: zenity, desktop-file-utils, qt5-default
Depends: zenity, desktop-file-utils
Recommends: openjdk-8-jre
Homepage: http://multimc.org
Description: A local install wrapper for MultiMC

View File

@@ -4,9 +4,9 @@ A simple ubuntu package for MultiMC that wraps the contains a script that downlo
It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting.
# How to build this?
You need dpkg utils. Rename the `multimc` folder to `multimc_1.3-1` and then run:
You need dpkg utils. Rename the `multimc` folder to `multimc_1.2-1` and then run:
```
fakeroot dpkg-deb --build multimc_1.3-1
fakeroot dpkg-deb --build multimc_1.2-1
```
Replace the version with whatever is appropriate.

View File

@@ -20,7 +20,6 @@
#include "settings/SettingsObject.h"
#include "MultiMC.h"
#include "Env.h"
ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage)
{
@@ -76,9 +75,6 @@ void ProxyPage::applySettings()
s->set("ProxyPort", ui->proxyPortEdit->value());
s->set("ProxyUser", ui->proxyUserEdit->text());
s->set("ProxyPass", ui->proxyPassEdit->text());
ENV.updateProxySettings(proxyType, ui->proxyAddrEdit->text(), ui->proxyPortEdit->value(),
ui->proxyUserEdit->text(), ui->proxyPassEdit->text());
}
void ProxyPage::loadSettings()
{

View File

@@ -286,38 +286,6 @@ void ScreenshotsPage::on_uploadBtn_clicked()
QList<ScreenshotPtr> uploaded;
auto job = NetJobPtr(new NetJob("Screenshot Upload"));
if(selection.size() < 2)
{
auto item = selection.at(0);
auto info = m_model->fileInfo(item);
auto screenshot = std::make_shared<ScreenShot>(info);
job->addNetAction(ImgurUpload::make(screenshot));
m_uploadActive = true;
ProgressDialog dialog(this);
if(dialog.execWithTask(job.get()) != QDialog::Accepted)
{
CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"),
tr("Unknown error"), QMessageBox::Warning)->exec();
}
else
{
auto link = screenshot->m_url;
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(link);
CustomMessageBox::selectable(
this,
tr("Upload finished"),
tr("The <a href=\"%1\">link to the uploaded screenshot</a> has been placed in your clipboard.")
.arg(link),
QMessageBox::Information
)->exec();
}
m_uploadActive = false;
return;
}
for (auto item : selection)
{
auto info = m_model->fileInfo(item);

View File

@@ -41,7 +41,6 @@
#include "minecraft/Mod.h"
#include "icons/IconList.h"
#include "Exception.h"
#include "Version.h"
#include "MultiMC.h"
@@ -109,18 +108,26 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
reloadComponentList();
auto proxy = new IconProxy(ui->packageView);
proxy->setSourceModel(m_profile.get());
ui->packageView->setModel(proxy);
ui->packageView->installEventFilter(this);
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
auto smodel = ui->packageView->selectionModel();
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
updateVersionControls();
preselect(0);
connect(m_profile.get(), &ComponentList::minecraftChanged, this, &VersionPage::updateVersionControls);
if (m_profile)
{
auto proxy = new IconProxy(ui->packageView);
proxy->setSourceModel(m_profile.get());
ui->packageView->setModel(proxy);
ui->packageView->installEventFilter(this);
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
auto smodel = ui->packageView->selectionModel();
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
updateVersionControls();
// select first item.
preselect(0);
}
else
{
disableVersionControls();
}
connect(m_inst, &MinecraftInstance::versionReloaded, this,
&VersionPage::updateVersionControls);
}
VersionPage::~VersionPage()
@@ -173,21 +180,18 @@ void VersionPage::packageCurrent(const QModelIndex &current, const QModelIndex &
void VersionPage::updateVersionControls()
{
// FIXME: this is a dirty hack
if(m_profile) {
auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft"));
bool newCraft = minecraftVersion >= Version("1.14");
bool oldCraft = minecraftVersion <= Version("1.12.2");
ui->fabricBtn->setEnabled(newCraft);
ui->forgeBtn->setEnabled(oldCraft);
ui->liteloaderBtn->setEnabled(oldCraft);
}
else {
ui->fabricBtn->setEnabled(false);
ui->forgeBtn->setEnabled(false);
ui->liteloaderBtn->setEnabled(false);
ui->reloadBtn->setEnabled(false);
}
ui->fabricBtn->setEnabled(true);
ui->forgeBtn->setEnabled(true);
ui->liteloaderBtn->setEnabled(true);
updateButtons();
}
void VersionPage::disableVersionControls()
{
ui->fabricBtn->setEnabled(false);
ui->forgeBtn->setEnabled(false);
ui->liteloaderBtn->setEnabled(false);
ui->reloadBtn->setEnabled(false);
updateButtons();
}

View File

@@ -66,6 +66,7 @@ private slots:
void on_downloadBtn_clicked();
void updateVersionControls();
void disableVersionControls();
void on_changeVersionBtn_clicked();
private:

View File

@@ -75,11 +75,6 @@ void ImportPage::updateState()
}
else
{
if(input.endsWith("?client=y")) {
input.chop(9);
input.append("/file");
url = QUrl::fromUserInput(input);
}
// hook, line and sinker.
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));

View File

@@ -3,13 +3,11 @@
#include "MultiMC.h"
#include "dialogs/NewInstanceDialog.h"
#include <InstanceImportTask.h>
TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent)
: QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog)
{
ui->setupUi(this);
connect(ui->checkButton, &QPushButton::clicked, this, &TwitchPage::triggerCheck);
}
TwitchPage::~TwitchPage()
@@ -19,42 +17,10 @@ TwitchPage::~TwitchPage()
bool TwitchPage::shouldDisplay() const
{
return true;
return false;
}
void TwitchPage::openedImpl()
{
dialog->setSuggestedPack();
}
void TwitchPage::triggerCheck(bool)
{
if(m_modIdResolver) {
return;
}
auto task = new Flame::UrlResolvingTask(ui->lineEdit->text());
connect(task, &Task::finished, this, &TwitchPage::checkDone);
m_modIdResolver.reset(task);
task->start();
}
void TwitchPage::setUrl(const QString& url)
{
ui->lineEdit->setText(url);
triggerCheck(true);
}
void TwitchPage::checkDone()
{
auto result = m_modIdResolver->getResults();
auto formatted = QString("Project %1, File %2").arg(result.projectId).arg(result.fileId);
if(result.resolved && result.type == Flame::File::Type::Modpack) {
ui->twitchLabel->setText(formatted);
QFileInfo fi(result.fileName);
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(result.url));
} else {
ui->twitchLabel->setPixmap(QPixmap(QString::fromUtf8(":/assets/deadglitch")));
dialog->setSuggestedPack();
}
m_modIdResolver.reset();
}

View File

@@ -20,7 +20,6 @@
#include "pages/BasePage.h"
#include <MultiMC.h>
#include "tasks/Task.h"
#include "modplatform/flame/UrlResolvingTask.h"
namespace Ui
{
@@ -38,7 +37,7 @@ public:
virtual ~TwitchPage();
virtual QString displayName() const override
{
return tr("Twitch URL");
return tr("Twitch");
}
virtual QIcon icon() const override
{
@@ -56,14 +55,7 @@ public:
void openedImpl() override;
void setUrl(const QString & url);
private slots:
void triggerCheck(bool checked);
void checkDone();
private:
Ui::TwitchPage *ui = nullptr;
NewInstanceDialog* dialog = nullptr;
shared_qobject_ptr<Flame::UrlResolvingTask> m_modIdResolver;
};

View File

@@ -10,25 +10,9 @@
<height>405</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Twitch URL:</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QLabel" name="twitchLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>40</pointsize>
@@ -42,19 +26,8 @@
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="checkButton">
<property name="text">
<string>Check</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>lineEdit</tabstop>
<tabstop>checkButton</tabstop>
</tabstops>
<resources>
<include location="../../resources/assets/assets.qrc"/>
</resources>

View File

@@ -23,7 +23,6 @@ VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent)
connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh);
}
@@ -59,8 +58,6 @@ void VanillaPage::filterChanged()
out << "(old_snapshot)";
if(ui->releaseFilter->isChecked())
out << "(release)";
if(ui->experimentsFilter->isChecked())
out << "(experiment)";
auto regexp = out.join('|');
ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false));
}

View File

@@ -98,16 +98,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="experimentsFilter">
<property name="text">
<string>Experiments</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
@@ -154,16 +144,6 @@
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>releaseFilter</tabstop>
<tabstop>snapshotFilter</tabstop>
<tabstop>oldSnapshotFilter</tabstop>
<tabstop>betaFilter</tabstop>
<tabstop>alphaFilter</tabstop>
<tabstop>experimentsFilter</tabstop>
<tabstop>refreshBtn</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -1,91 +1,8 @@
# MultiMC 0.6.6
This release is mostly the smaller things that have accumulated over time, along with a big change in linux packaging.
No 1.13+ Forge news yet. That's going to be a major overhaul of many of the internals of MultiMC.
## **IMPORTANT**
On linux, MultiMC no longer bundles the Qt libraries. This fixes many issues, but it might not run after the update unless you have the required libraries installed.
Make sure you have the following packages before you update:
- Arch: `qt5-base`
- Debian/Ubuntu: `qt5-default`
- CentOS/RHEL/Fedora: `qt5-qtbase-gui`
- Suse: `libqt5-qtbase`
MultiMC on linux is built with Qt 5.4 and older versions of Qt will not work.
This should be a massive improvement to system integration on linux and resolves GH-1784, GH-2605, GH-1979, GH-2271, GH-1992, GH-1816 and their many duplicates.
### New or changed features
- GH-2487: No is now the default button when deleting instances.
- It is now possible to launch with profilers in offline mode.
- Massively improved support for icon formats when importing and exporting instances.
All of the formats MultiMC supports are now supported in exported instances too, instead of just PNG.
- Added the pocket fox icon.
We still have the big one under the staircase. It's cute. Just hide your chickens.
- Global settings can be opened from instance settings where appropriate.
Many people use the instance overrides where using the global settings would be more appropriate. Hopefully this makes it clearer that the instance settings are overrides for the global settings.
- Added direct Fabric loader support.
Much overdue. It's good. Fabric mod metadata is also supported in the mod pages.
- MultiMC now recognizes the new `experimental` Minecraft versions.
Go mess with the combat experiment. It's interesting.
- Added Twitch URL as an option to the Add Instance dialog.
You can now drag the purple download buttons from CurseForge into MultiMC and get a modpack out of it. Much easier!
### Bugfixes
- Translation folder is now created sooner, making first launch translation fetch work again.
- GH-2716: MultiMC will no longer try to censor values shorter than 4 characters in logs.
It was actually leaking information and destroying the logs instead of helping.
- GH-2551: Trim server name and IP before saving them.
- GH-2591: Fix multiple potential memory leaks and crashes related to destroying objects with Qt memory lifecycle model.
- `run.sh` on linux now passes all arguments to MultiMC.
- Adding a disabled mod duplicate now replaces the existing mod.
- GH-2592: Newly created instances are now selected again. This was a very old regression.
- GH-689: MultiMC no longer creates an imgur album for single screenshot uploads.
- GH-1813: `#` is now saved properly when used in instance notes.
- GH-2515: Deleting an instance externally while the delete dialog is open no longer leads to some other instance being deleted when you click OK.
- GH-2499: Proxy settings are applied immediately and no longer need an application restart.
- GH-1701: When downloading updates, the text now reflects the number of downloaded files better.
- Icon scaling issues on macOS should now be fixed.
# Previous releases
## MultiMC 0.6.5
# MultiMC 0.6.5
Finalizing the translation workflow improvements and adding fixes for sounds missing in old game versions.
#### New or changed features
### New or changed features
- UI for the language settings has been unified across the application
@@ -95,6 +12,7 @@ Finalizing the translation workflow improvements and adding fixes for sounds mis
Also, a minor issue with the reconstruction being done twice per launch has been fixed.
# Previous releases
## MultiMC 0.6.4
@@ -1170,4 +1088,4 @@ Long time coming, this release brought a lot of incremental improvements and fix
- Added additional information to the about dialog.
## MultiMC 0.0
- Initial release.
- Initial release.