@@ -0,0 +1,58 @@
#pragma once

#include <cstddef>
#include <memory>

class Usable;

/**
* Base class for things that can be used by multiple other things and we want to track the use count.
*
* @see UseLock
*/
class Usable
{
friend class UseLock;
public:
std::size_t useCount()
{
return m_useCount;
}
bool isInUse()
{
return m_useCount > 0;
}
protected:
virtual void decrementUses()
{
m_useCount--;
}
virtual void incrementUses()
{
m_useCount++;
}
private:
std::size_t m_useCount = 0;
};

/**
* Lock class to use for keeping track of uses of other things derived from Usable
*
* @see Usable
*/
class UseLock
{
public:
UseLock(std::shared_ptr<Usable> usable)
: m_usable(usable)
{
// this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate.
m_usable->incrementUses();
}
~UseLock()
{
m_usable->decrementUses();
}
private:
std::shared_ptr<Usable> m_usable;
};
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -120,4 +120,4 @@ private slots:

QTEST_GUILESS_MAIN(ModUtilsTest)

#include "tst_modutils.moc"
#include "Version_test.moc"
@@ -0,0 +1,7 @@
#include "IIconList.h"

// blargh
IIconList::~IIconList()
{
}

@@ -0,0 +1,25 @@
#pragma once

#include <QString>
#include <QStringList>
#include "multimc_logic_export.h"

enum IconType : unsigned
{
Builtin,
Transient,
FileBased,
ICONS_TOTAL,
ToBeDeleted
};

class MULTIMC_LOGIC_EXPORT IIconList
{
public:
virtual ~IIconList();
virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0;
virtual bool deleteIcon(const QString &key) = 0;
virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0;
virtual bool iconFileExists(const QString &key) const = 0;
virtual void installIcons(const QStringList &iconFiles) = 0;
};
@@ -4,7 +4,6 @@
#include <QFile>
#include <QProcess>
#include <QMap>
#include <QTemporaryFile>
#include <QCoreApplication>
#include <QDebug>

@@ -81,13 +80,14 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.id = m_id;
}
result.errorLog = m_stderr;
result.outLog = m_stdout;
qDebug() << "STDOUT" << m_stdout;
qWarning() << "STDERR" << m_stderr;
qDebug() << "Java checker finished with status " << status << " exit code " << exitcode;

if (status == QProcess::CrashExit || exitcode == 1)
{
qDebug() << "Java checker failed!";
result.validity = JavaCheckResult::Validity::Errored;
emit checkFinished(result);
return;
}
@@ -113,7 +113,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)

if(!results.contains("os.arch") || !results.contains("java.version") || !success)
{
qDebug() << "Java checker failed - couldn't extract required information.";
result.validity = JavaCheckResult::Validity::ReturnedInvalidData;
emit checkFinished(result);
return;
}
@@ -123,7 +123,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
bool is_64 = os_arch == "x86_64" || os_arch == "amd64";


result.valid = true;
result.validity = JavaCheckResult::Validity::Valid;
result.is_64bit = is_64;
result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch;
@@ -15,10 +15,16 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
QString mojangPlatform;
QString realPlatform;
JavaVersion javaVersion;
QString outLog;
QString errorLog;
bool valid = false;
bool is_64bit = false;
int id;
enum class Validity
{
Errored,
ReturnedInvalidData,
Valid
} validity = Validity::Errored;
};

typedef std::shared_ptr<QProcess> QProcessPtr;
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
#pragma once

#include <QtNetwork>
#include <QLabel>
#include "JavaChecker.h"
#include "tasks/Task.h"

File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,7 +77,7 @@ QVariant JavaInstallList::data(const QModelIndex &index, int role) const
}
}

BaseVersionList::RoleList JavaInstallList::providesRoles()
BaseVersionList::RoleList JavaInstallList::providesRoles() const
{
return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole};
}
@@ -156,7 +156,7 @@ void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
qDebug() << "Found the following valid Java installations:";
for(JavaCheckResult result : results)
{
if(result.valid)
if(result.validity == JavaCheckResult::Validity::Valid)
{
JavaInstallPtr javaVersion(new JavaInstall());

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@ class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList
virtual void sortVersions() override;

virtual QVariant data(const QModelIndex &index, int role) const override;
virtual RoleList providesRoles() override;
virtual RoleList providesRoles() const override;

public slots:
virtual void updateListData(QList<BaseVersionPtr> versions) override;
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,11 @@ JavaInstallPtr JavaUtils::GetDefaultJava()

javaVersion->id = "java";
javaVersion->arch = "unknown";
#if defined(Q_OS_WIN32)
javaVersion->path = "javaw";
#else
javaVersion->path = "java";
#endif

return javaVersion;
}
@@ -150,10 +154,12 @@ QList<QString> JavaUtils::FindJavaPaths()
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");

java_candidates.append(JRE64s);
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK64s);
java_candidates.append(JRE32s);
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK32s);
@@ -201,9 +207,36 @@ QList<QString> JavaUtils::FindJavaPaths()

QList<QString> javas;
javas.append(this->GetDefaultJava()->path);
javas.append("/opt/java/bin/java");
javas.append("/usr/bin/java");
auto scanJavaDir = [&](const QString & dirPath)
{
QDir dir(dirPath);
if(!dir.exists())
return;
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
for(auto & entry: entries)
{

QString prefix;
if(entry.isAbsolute())
{
prefix = entry.absoluteFilePath();
}
else
{
prefix = entry.filePath();
}

javas.append(FS::PathCombine(prefix, "jre/bin/java"));
javas.append(FS::PathCombine(prefix, "bin/java"));
}
};
// oracle RPMs
scanJavaDir("/usr/java");
// general locations used by distro packaging
scanJavaDir("/usr/lib/jvm");
scanJavaDir("/usr/lib32/jvm");
// javas stored in MultiMC's folder
scanJavaDir("java");
return javas;
}
#else
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
#pragma once

#include <QStringList>
#include <QWidget>

#include "JavaCheckerJob.h"
#include "JavaChecker.h"
@@ -6,7 +6,7 @@

JavaVersion & JavaVersion::operator=(const QString & javaVersionString)
{
string = javaVersionString;
m_string = javaVersionString;

auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int
{
@@ -28,12 +28,12 @@ JavaVersion & JavaVersion::operator=(const QString & javaVersionString)
pattern = QRegularExpression("(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?");
}

auto match = pattern.match(string);
parseable = match.hasMatch();
major = getCapturedInteger(match, "major");
minor = getCapturedInteger(match, "minor");
security = getCapturedInteger(match, "security");
prerelease = match.captured("prerelease");
auto match = pattern.match(m_string);
m_parseable = match.hasMatch();
m_major = getCapturedInteger(match, "major");
m_minor = getCapturedInteger(match, "minor");
m_security = getCapturedInteger(match, "security");
m_prerelease = match.captured("prerelease");
return *this;
}

@@ -44,38 +44,38 @@ JavaVersion::JavaVersion(const QString &rhs)

QString JavaVersion::toString()
{
return string;
return m_string;
}

bool JavaVersion::requiresPermGen()
{
if(parseable)
if(m_parseable)
{
return major < 8;
return m_major < 8;
}
return true;
}

bool JavaVersion::operator<(const JavaVersion &rhs)
{
if(parseable && rhs.parseable)
if(m_parseable && rhs.m_parseable)
{
if(major < rhs.major)
if(m_major < rhs.m_major)
return true;
if(major > rhs.major)
if(m_major > rhs.m_major)
return false;
if(minor < rhs.minor)
if(m_minor < rhs.m_minor)
return true;
if(minor > rhs.minor)
if(m_minor > rhs.m_minor)
return false;
if(security < rhs.security)
if(m_security < rhs.m_security)
return true;
if(security > rhs.security)
if(m_security > rhs.m_security)
return false;

// everything else being equal, consider prerelease status
bool thisPre = !prerelease.isEmpty();
bool rhsPre = !rhs.prerelease.isEmpty();
bool thisPre = !m_prerelease.isEmpty();
bool rhsPre = !rhs.m_prerelease.isEmpty();
if(thisPre && !rhsPre)
{
// this is a prerelease and the other one isn't -> lesser
@@ -89,21 +89,21 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
else if(thisPre && rhsPre)
{
// both are prereleases - use natural compare...
return Strings::naturalCompare(prerelease, rhs.prerelease, Qt::CaseSensitive) < 0;
return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
}
// neither is prerelease, so they are the same -> this cannot be less than rhs
return false;
}
else return Strings::naturalCompare(string, rhs.string, Qt::CaseSensitive) < 0;
else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
}

bool JavaVersion::operator==(const JavaVersion &rhs)
{
if(parseable && rhs.parseable)
if(m_parseable && rhs.m_parseable)
{
return major == rhs.major && minor == rhs.minor && security == rhs.security && prerelease == rhs.prerelease;
return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease;
}
return string == rhs.string;
return m_string == rhs.m_string;
}

bool JavaVersion::operator>(const JavaVersion &rhs)
@@ -20,11 +20,23 @@ class MULTIMC_LOGIC_EXPORT JavaVersion

QString toString();

int major()
{
return m_major;
}
int minor()
{
return m_minor;
}
int security()
{
return m_security;
}
private:
QString string;
int major = 0;
int minor = 0;
int security = 0;
bool parseable = false;
QString prerelease;
QString m_string;
int m_major = 0;
int m_minor = 0;
int m_security = 0;
bool m_parseable = false;
QString m_prerelease;
};
@@ -34,12 +34,12 @@ private
QFETCH(QString, prerelease);

JavaVersion test(string);
QCOMPARE(test.string, string);
QCOMPARE(test.m_string, string);
QCOMPARE(test.toString(), string);
QCOMPARE(test.major, major);
QCOMPARE(test.minor, minor);
QCOMPARE(test.security, security);
QCOMPARE(test.prerelease, prerelease);
QCOMPARE(test.m_major, major);
QCOMPARE(test.m_minor, minor);
QCOMPARE(test.m_security, security);
QCOMPARE(test.m_prerelease, prerelease);
}

void test_Sort_data()
@@ -113,8 +113,4 @@ private

QTEST_GUILESS_MAIN(JavaVersionTest)

#include "tst_JavaVersion.moc"


// manual testing fakery

#include "JavaVersion_test.moc"
@@ -0,0 +1,136 @@
/* Copyright 2013-2017 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.
*/

#include "CheckJava.h"
#include <launch/LaunchTask.h>
#include <FileSystem.h>
#include <QStandardPaths>
#include <QFileInfo>
#include <sys.h>

void CheckJava::executeTask()
{
auto instance = m_parent->instance();
auto settings = instance->settings();
m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool();

auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
if (realJavaPath.isEmpty())
{
if (perInstance)
{
emit logLine(
tr("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 "
"the settings.").arg(m_javaPath),
MessageLevel::Warning);
}
emitFailed(tr("Java path is not valid."));
return;
}
else
{
emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC);
}

QFileInfo javaInfo(realJavaPath);
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
auto storedArchitecture = settings->get("JavaArchitecture").toString();
auto storedVersion = settings->get("JavaVersion").toString();
m_javaUnixTime = javaUnixTime;
// if timestamps are not the same, or something is missing, check!
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0)
{
m_JavaChecker = std::make_shared<JavaChecker>();
emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath;
m_JavaChecker->performCheck();
return;
}
else
{
auto verString = instance->settings()->get("JavaVersion").toString();
auto archString = instance->settings()->get("JavaArchitecture").toString();
printJavaInfo(verString, archString);
}
emitSucceeded();
}

void CheckJava::checkJavaFinished(JavaCheckResult result)
{
switch (result.validity)
{
case JavaCheckResult::Validity::Errored:
{
// Error message displayed if java can't start
emit logLine(tr("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!"));
return;
}
case JavaCheckResult::Validity::ReturnedInvalidData:
{
emit logLine(tr("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);
emitSucceeded();
return;
}
case JavaCheckResult::Validity::Valid:
{
auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform);
instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
emitSucceeded();
return;
}
}
}

void CheckJava::printJavaInfo(const QString& version, const QString& architecture)
{
emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC);
printSystemInfo(true, architecture == "64");
}

void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
{
auto cpu64 = Sys::isCPU64bit();
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);
}
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);
}
}
}
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
#pragma once

#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>

class CheckJava: public LaunchStep
@@ -34,6 +34,10 @@ class CheckJava: public LaunchStep
private slots:
void checkJavaFinished(JavaCheckResult result);

private:
void printJavaInfo(const QString & version, const QString & architecture);
void printSystemInfo(bool javaIsKnown, bool javaIs64bit);

private:
QString m_javaPath;
qlonglong m_javaUnixTime;
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,8 @@ class LaunchStep: public Task

public slots:
virtual void proceed() {};
// called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends
virtual void finalize() {};

protected: /* data */
LaunchTask *m_parent;
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -56,6 +56,7 @@ void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)

void LaunchTask::executeTask()
{
m_instance->setCrashed(false);
if(!m_steps.size())
{
state = LaunchTask::Finished;
@@ -87,7 +88,7 @@ void LaunchTask::onStepFinished()
// end?
if(currentStep == m_steps.size() - 1)
{
emitSucceeded();
finalizeSteps(true, QString());
}
else
{
@@ -98,7 +99,23 @@ void LaunchTask::onStepFinished()
}
else
{
emitFailed(step->failReason());
finalizeSteps(false, step->failReason());
}
}

void LaunchTask::finalizeSteps(bool successful, const QString& error)
{
for(auto step = currentStep; step >= 0; step--)
{
m_steps[step]->finalize();
}
if(successful)
{
emitSucceeded();
}
else
{
emitFailed(error);
}
}

@@ -133,6 +150,26 @@ void LaunchTask::proceed()
m_steps[currentStep]->proceed();
}

bool LaunchTask::canAbort() const
{
switch(state)
{
case LaunchTask::Aborted:
case LaunchTask::Failed:
case LaunchTask::Finished:
return false;
case LaunchTask::NotStarted:
return true;
case LaunchTask::Running:
case LaunchTask::Waiting:
{
auto step = m_steps[currentStep];
return step->canAbort();
}
}
return false;
}

bool LaunchTask::abort()
{
switch(state)
@@ -167,6 +204,15 @@ bool LaunchTask::abort()
return false;
}

shared_qobject_ptr<LogModel> LaunchTask::getLogModel()
{
if(!m_logModel)
{
m_logModel.reset(new LogModel());
}
return m_logModel;
}

void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel)
{
for (auto & line: lines)
@@ -193,20 +239,20 @@ void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
// censor private user info
line = censorPrivateInfo(line);

emit log(line, level);
auto &model = *getLogModel();
model.append(level, line);
}

void LaunchTask::emitSucceeded()
{
m_instance->cleanupAfterRun();
m_instance->setRunning(false);
Task::emitSucceeded();
}

void LaunchTask::emitFailed(QString reason)
{
m_instance->cleanupAfterRun();
m_instance->setRunning(false);
m_instance->setCrashed(true);
Task::emitFailed(reason);
}

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -17,6 +17,8 @@

#pragma once
#include <QProcess>
#include <QObjectPtr.h>
#include "LogModel.h"
#include "BaseInstance.h"
#include "MessageLevel.h"
#include "LoggedProcess.h"
@@ -78,7 +80,11 @@ class MULTIMC_LOGIC_EXPORT LaunchTask: public Task
/**
* @brief abort launch
*/
virtual bool abort() override;
bool abort() override;

bool canAbort() const override;

shared_qobject_ptr<LogModel> getLogModel();

public:
QString substituteVariables(const QString &cmd) const;
@@ -98,22 +104,19 @@ class MULTIMC_LOGIC_EXPORT LaunchTask: public Task

void requestLogging();

/**
* @brief emitted when we want to log something
* @param text the text to log
* @param level the level to log at
*/
void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC);

public slots:
void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
void onReadyForLaunch();
void onStepFinished();
void onProgressReportingRequested();

private: /*methods */
void finalizeSteps(bool successful, const QString & error);

protected: /* data */
InstancePtr m_instance;
shared_qobject_ptr<LogModel> m_logModel;
QList <std::shared_ptr<LaunchStep>> m_steps;
QMap<QString, QString> m_censorFilter;
int currentStep = -1;
@@ -0,0 +1,144 @@
#include "LogModel.h"

LogModel::LogModel(QObject *parent):QAbstractListModel(parent)
{
m_content.resize(m_maxLines);
}

int LogModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;

return m_numLines;
}

QVariant LogModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_numLines)
return QVariant();

auto row = index.row();
auto realRow = (row + m_firstLine) % m_maxLines;
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
return m_content[realRow].line;
}
if(role == LevelRole)
{
return m_content[realRow].level;
}

return QVariant();
}

void LogModel::append(MessageLevel::Enum level, QString line)
{
if(m_suspended)
{
return;
}
int lineNum = (m_firstLine + m_numLines) % m_maxLines;
// overflow
if(m_numLines == m_maxLines)
{
if(m_stopOnOverflow)
{
// nothing more to do, the buffer is full
return;
}
beginRemoveRows(QModelIndex(), 0, 0);
m_firstLine = (m_firstLine + 1) % m_maxLines;
m_numLines --;
endRemoveRows();
}
else if (m_numLines == m_maxLines - 1 && m_stopOnOverflow)
{
level = MessageLevel::Fatal;
line = m_overflowMessage;
}
beginInsertRows(QModelIndex(), m_numLines, m_numLines);
m_numLines ++;
m_content[lineNum].level = level;
m_content[lineNum].line = line;
endInsertRows();
}

void LogModel::suspend(bool suspend)
{
m_suspended = suspend;
}

void LogModel::clear()
{
beginResetModel();
m_firstLine = 0;
m_numLines = 0;
endResetModel();
}

QString LogModel::toPlainText()
{
QString out;
out.reserve(m_numLines * 80);
for(int i = 0; i < m_numLines; i++)
{
QString & line = m_content[(m_firstLine + i) % m_maxLines].line;
out.append(line + '\n');
}
out.squeeze();
return out;
}

void LogModel::setMaxLines(int maxLines)
{
// no-op
if(maxLines == m_maxLines)
{
return;
}
// if it all still fits in the buffer, just resize it
if(m_firstLine + m_numLines < maxLines)
{
m_maxLines = maxLines;
m_content.resize(maxLines);
return;
}
// otherwise, we need to reorganize the data because it crosses the wrap boundary
QVector<entry> newContent;
newContent.resize(maxLines);
if(m_numLines <= maxLines)
{
// if it all fits in the new buffer, just copy it over
for(int i = 0; i < m_numLines; i++)
{
newContent[i] = m_content[(m_firstLine + i) % m_maxLines];
}
m_content.swap(newContent);
}
else
{
// if it doesn't fit, part of the data needs to be thrown away (the oldest log messages)
int lead = m_numLines - maxLines;
beginRemoveRows(QModelIndex(), 0, lead - 1);
for(int i = 0; i < maxLines; i++)
{
newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines];
}
m_numLines = m_maxLines;
m_content.swap(newContent);
endRemoveRows();
}
m_firstLine = 0;
m_maxLines = maxLines;
}

void LogModel::setStopOnOverflow(bool stop)
{
m_stopOnOverflow = stop;
}

void LogModel::setOverflowMessage(const QString& overflowMessage)
{
m_overflowMessage = overflowMessage;
}
@@ -0,0 +1,53 @@
#pragma once

#include <QAbstractListModel>
#include <QString>
#include "MessageLevel.h"

#include <multimc_logic_export.h>

class MULTIMC_LOGIC_EXPORT LogModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit LogModel(QObject *parent = 0);

int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;

void append(MessageLevel::Enum, QString line);
void clear();
void suspend(bool suspend);

QString toPlainText();

void setMaxLines(int maxLines);
void setStopOnOverflow(bool stop);
void setOverflowMessage(const QString & overflowMessage);

enum Roles
{
LevelRole = Qt::UserRole
};

private /* types */:
struct entry
{
MessageLevel::Enum level;
QString line;
};

private: /* data */
QVector <entry> m_content;
int m_maxLines = 1000;
// first line in the circular buffer
int m_firstLine = 0;
// number of lines occupied in the circular buffer
int m_numLines = 0;
bool m_stopOnOverflow = false;
QString m_overflowMessage = "OVERFLOW";
bool m_suspended = false;

private:
Q_DISABLE_COPY(LogModel)
};
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
#pragma once

#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <LoggedProcess.h>

class PostLaunchCommand: public LaunchStep
{
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@

#pragma once

#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include "launch/LaunchStep.h"
#include "LoggedProcess.h"

class PreLaunchCommand: public LaunchStep
{
File renamed without changes.
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
#pragma once

#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>

#include "multimc_logic_export.h"
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,12 @@

void Update::executeTask()
{
m_updateTask = m_parent->instance()->createUpdateTask();
if(m_aborted)
{
emitFailed(tr("Task aborted."));
return;
}
m_updateTask.reset(m_parent->instance()->createUpdateTask());
if(m_updateTask)
{
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
@@ -39,12 +44,37 @@ void Update::updateFinished()
{
if(m_updateTask->successful())
{
m_updateTask.reset();
emitSucceeded();
}
else
{
QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason());
m_updateTask.reset();
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
}
}

bool Update::canAbort() const
{
if(m_updateTask)
{
return m_updateTask->canAbort();
}
return true;
}


bool Update::abort()
{
m_aborted = true;
if(m_updateTask)
{
if(m_updateTask->canAbort())
{
return m_updateTask->abort();
}
}
return true;
}
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,8 @@
#pragma once

#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <QObjectPtr.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>

// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
@@ -27,15 +28,16 @@ class Update: public LaunchStep
explicit Update(LaunchTask *parent):LaunchStep(parent) {};
virtual ~Update() {};

virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
virtual void proceed();
void executeTask() override;
bool canAbort() const override;
void proceed() override;
public slots:
bool abort() override;

private slots:
void updateFinished();

private:
std::shared_ptr<Task> m_updateTask;
shared_qobject_ptr<Task> m_updateTask;
bool m_aborted = false;
};
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
* limitations under the License.
*/

#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QCryptographicHash>
@@ -23,7 +24,10 @@
#include <QDebug>

#include "AssetsUtils.h"
#include <FileSystem.h>
#include "FileSystem.h"
#include "net/Download.h"
#include "net/ChecksumValidator.h"


namespace AssetsUtils
{
@@ -32,7 +36,7 @@ namespace AssetsUtils
* Returns true on success, with index populated
* index is undefined otherwise
*/
bool loadAssetsIndexJson(QString path, AssetsIndex *index)
bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
{
/*
{
@@ -56,6 +60,7 @@ bool loadAssetsIndexJson(QString path, AssetsIndex *index)
qCritical() << "Failed to read assets index file" << path;
return false;
}
index->id = assetsId;

// Read the file and close it.
QByteArray jsonData = file.readAll();
@@ -143,7 +148,7 @@ QDir reconstructAssets(QString assetsId)
<< objectDir.path() << virtualDir.path() << virtualRoot.path();

AssetsIndex index;
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index);
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);

if (loadAssetsIndex && index.isVirtual)
{
@@ -182,3 +187,51 @@ QDir reconstructAssets(QString assetsId)
}

}

NetActionPtr AssetObject::getDownloadAction()
{
QFileInfo objectFile(getLocalPath());
if ((!objectFile.isFile()) || (objectFile.size() != size))
{
auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath());
if(hash.size())
{
auto rawHash = QByteArray::fromHex(hash.toLatin1());
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
}
objectDL->m_total_progress = size;
return objectDL;
}
return nullptr;
}

QString AssetObject::getLocalPath()
{
return "assets/objects/" + getRelPath();
}

QUrl AssetObject::getUrl()
{
return QUrl("http://resources.download.minecraft.net/" + getRelPath());
}

QString AssetObject::getRelPath()
{
return hash.left(2) + "/" + hash;
}

NetJobPtr AssetsIndex::getDownloadJob()
{
auto job = new NetJob(QObject::tr("Assets for %1").arg(id));
for (auto &object : objects.values())
{
auto dl = object.getDownloadAction();
if(dl)
{
job->addNetAction(dl);
}
}
if(job->size())
return job;
return nullptr;
}
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,22 +17,32 @@

#include <QString>
#include <QMap>
#include "net/NetAction.h"
#include "net/NetJob.h"

struct AssetObject
{
QString getRelPath();
QUrl getUrl();
QString getLocalPath();
NetActionPtr getDownloadAction();

QString hash;
qint64 size;
};

struct AssetsIndex
{
NetJobPtr getDownloadJob();

QString id;
QMap<QString, AssetObject> objects;
bool isVirtual = false;
};

namespace AssetsUtils
{
bool loadAssetsIndexJson(QString file, AssetsIndex* index);
bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index);
/// Reconstruct a virtual assets folder for the given assets ID and return the folder
QDir reconstructAssets(QString assetsId);
}
@@ -55,18 +55,23 @@ struct GradleSpecifier
}
return retval;
}
QString getFileName() const
{
QString filename = m_artifactId + '-' + m_version;
if(!m_classifier.isEmpty())
{
filename += "-" + m_classifier;
}
filename += "." + m_extension;
return filename;
}
QString toPath() const
{
if(!m_valid)
return "INVALID";
QString path = m_groupId;
path.replace('.', '/');
path += '/' + m_artifactId + '/' + m_version + '/' + m_artifactId + '-' + m_version;
if(!m_classifier.isEmpty())
{
path += "-" + m_classifier;
}
path += "." + m_extension;
path += '/' + m_artifactId + '/' + m_version + '/' + getFileName();
return path;
}
inline bool valid() const
@@ -74,4 +74,4 @@ private

QTEST_GUILESS_MAIN(GradleSpecifierTest)

#include "tst_gradlespecifier.moc"
#include "GradleSpecifier_test.moc"
File renamed without changes.
@@ -0,0 +1,295 @@
#include "Library.h"
#include "MinecraftInstance.h"

#include <net/Download.h>
#include <net/ChecksumValidator.h>
#include <minecraft/forge/ForgeXzDownload.h>
#include <Env.h>
#include <FileSystem.h>


void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32,
QStringList& native64, const QString &overridePath) const
{
bool isLocal = (hint() == "local");
auto actualPath = [&](QString relPath)
{
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
if(isLocal && !overridePath.isEmpty())
{
QString fileName = out.fileName();
auto fullPath = FS::PathCombine(overridePath, fileName);
qDebug() << fullPath;
QFileInfo fileinfo(fullPath);
if(fileinfo.exists())
{
return fileinfo.absoluteFilePath();
}
}
return out.absoluteFilePath();
};
if(m_mojangDownloads)
{
if(m_mojangDownloads->artifact)
{
auto artifact = m_mojangDownloads->artifact;
jar += actualPath(artifact->path);
}
if(!isNative())
return;
if(m_nativeClassifiers.contains(system))
{
auto nativeClassifier = m_nativeClassifiers[system];
if(nativeClassifier.contains("${arch}"))
{
auto nat32Classifier = nativeClassifier;
nat32Classifier.replace("${arch}", "32");
auto nat64Classifier = nativeClassifier;
nat64Classifier.replace("${arch}", "64");
auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier);
if(nat32info)
native32 += actualPath(nat32info->path);
auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier);
if(nat64info)
native64 += actualPath(nat64info->path);
}
else
{
native += actualPath(m_mojangDownloads->getDownloadInfo(nativeClassifier)->path);
}
}
}
else
{
QString raw_storage = storageSuffix(system);
if(isNative())
{
if (raw_storage.contains("${arch}"))
{
auto nat32Storage = raw_storage;
nat32Storage.replace("${arch}", "32");
auto nat64Storage = raw_storage;
nat64Storage.replace("${arch}", "64");
native32 += actualPath(nat32Storage);
native64 += actualPath(nat64Storage);
}
else
{
native += actualPath(raw_storage);
}
}
else
{
jar += actualPath(raw_storage);
}
}
}

QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class HttpMetaCache* cache,
QStringList& failedFiles, const QString & overridePath) const
{
QList<NetActionPtr> out;
bool isAlwaysStale = (hint() == "always-stale");
bool isLocal = (hint() == "local");
bool isForge = (hint() == "forge-pack-xz");

auto add_download = [&](QString storage, QString url, QString sha1 = QString())
{
auto entry = cache->resolveEntry("libraries", storage);
if(isAlwaysStale)
{
entry->setStale(true);
}
if (!entry->isStale())
return true;
if(isLocal)
{
if(!overridePath.isEmpty())
{
QString fileName;
int position = storage.lastIndexOf('/');
if(position == -1)
{
fileName = storage;
}
else
{
fileName = storage.mid(position);
}
auto fullPath = FS::PathCombine(overridePath, fileName);
QFileInfo fileinfo(fullPath);
if(fileinfo.exists())
{
return true;
}
}
QFileInfo fileinfo(entry->getFullPath());
if(!fileinfo.exists())
{
failedFiles.append(entry->getFullPath());
return false;
}
return true;
}
Net::Download::Options options;
if(isAlwaysStale)
{
options |= Net::Download::Option::AcceptLocalFiles;
}
if (isForge)
{
out.append(ForgeXzDownload::make(storage, entry));
}
else
{
if(sha1.size())
{
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
auto dl = Net::Download::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
out.append(dl);
}

else
out.append(Net::Download::makeCached(url, entry, options));
}
return true;
};

if(m_mojangDownloads)
{
if(m_mojangDownloads->artifact)
{
auto artifact = m_mojangDownloads->artifact;
add_download(artifact->path, artifact->url, artifact->sha1);
}
if(m_nativeClassifiers.contains(system))
{
auto nativeClassifier = m_nativeClassifiers[system];
if(nativeClassifier.contains("${arch}"))
{
auto nat32Classifier = nativeClassifier;
nat32Classifier.replace("${arch}", "32");
auto nat64Classifier = nativeClassifier;
nat64Classifier.replace("${arch}", "64");
auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier);
if(nat32info)
add_download(nat32info->path, nat32info->url, nat32info->sha1);
auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier);
if(nat64info)
add_download(nat64info->path, nat64info->url, nat64info->sha1);
}
else
{
auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier);
if(info)
{
add_download(info->path, info->url, info->sha1);
}
}
}
}
else
{
QString raw_storage = storageSuffix(system);
auto raw_dl = [&](){
if (!m_absoluteURL.isEmpty())
{
return m_absoluteURL;
}

if (m_repositoryURL.isEmpty())
{
return QString("https://" + URLConstants::LIBRARY_BASE) + raw_storage;
}

if(m_repositoryURL.endsWith('/'))
{
return m_repositoryURL + raw_storage;
}
else
{
return m_repositoryURL + QChar('/') + raw_storage;
}
}();
if (raw_storage.contains("${arch}"))
{
QString cooked_storage = raw_storage;
QString cooked_dl = raw_dl;
add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"));
cooked_storage = raw_storage;
cooked_dl = raw_dl;
add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"));
}
else
{
add_download(raw_storage, raw_dl);
}
}
return out;
}

bool Library::isActive() const
{
bool result = true;
if (m_rules.empty())
{
result = true;
}
else
{
RuleAction ruleResult = Disallow;
for (auto rule : m_rules)
{
RuleAction temp = rule->apply(this);
if (temp != Defer)
ruleResult = temp;
}
result = result && (ruleResult == Allow);
}
if (isNative())
{
result = result && m_nativeClassifiers.contains(currentSystem);
}
return result;
}

void Library::setStoragePrefix(QString prefix)
{
m_storagePrefix = prefix;
}

QString Library::defaultStoragePrefix()
{
return "libraries/";
}

QString Library::storagePrefix() const
{
if(m_storagePrefix.isEmpty())
{
return defaultStoragePrefix();
}
return m_storagePrefix;
}

QString Library::storageSuffix(OpSys system) const
{
// non-native? use only the gradle specifier
if (!isNative())
{
return m_name.toPath();
}

// otherwise native, override classifiers. Mojang HACK!
GradleSpecifier nativeSpec = m_name;
if (m_nativeClassifiers.contains(system))
{
nativeSpec.setClassifier(m_nativeClassifiers[system]);
}
else
{
nativeSpec.setClassifier("INVALID");
}
return nativeSpec.toPath();
}
@@ -1,5 +1,6 @@
#pragma once
#include <QString>
#include <net/NetAction.h>
#include <QPair>
#include <QList>
#include <QStringList>
@@ -8,38 +9,45 @@
#include <QUrl>
#include <memory>

#include "OneSixRule.h"
#include "Rule.h"
#include "minecraft/OpSys.h"
#include "GradleSpecifier.h"
#include "net/URLConstants.h"
#include "MojangDownloadInfo.h"

class RawLibrary;
typedef std::shared_ptr<RawLibrary> RawLibraryPtr;
#include "multimc_logic_export.h"

class RawLibrary
class Library;
class MinecraftInstance;

typedef std::shared_ptr<Library> LibraryPtr;

class MULTIMC_LOGIC_EXPORT Library
{
friend class OneSixVersionFormat;
friend class MojangVersionFormat;
friend class LibraryTest;
public:
RawLibrary()
Library()
{
}
RawLibrary(const QString &name)
Library(const QString &name)
{
m_name = name;
}
/// limited copy without some data. TODO: why?
static RawLibraryPtr limitedCopy(RawLibraryPtr base)
static LibraryPtr limitedCopy(LibraryPtr base)
{
auto newlib = std::make_shared<RawLibrary>();
auto newlib = std::make_shared<Library>();
newlib->m_name = base->m_name;
newlib->m_base_url = base->m_base_url;
newlib->m_repositoryURL = base->m_repositoryURL;
newlib->m_hint = base->m_hint;
newlib->m_absolute_url = base->m_absolute_url;
newlib->extract_excludes = base->extract_excludes;
newlib->m_native_classifiers = base->m_native_classifiers;
newlib->m_absoluteURL = base->m_absoluteURL;
newlib->m_extractExcludes = base->m_extractExcludes;
newlib->m_nativeClassifiers = base->m_nativeClassifiers;
newlib->m_rules = base->m_rules;
newlib->m_storagePrefix = base->m_storagePrefix;
newlib->m_mojangDownloads = base->m_mojangDownloads;
return newlib;
}

@@ -81,57 +89,35 @@ class RawLibrary
/// Returns true if the library is native
bool isNative() const
{
return m_native_classifiers.size() != 0;
return m_nativeClassifiers.size() != 0;
}

void setStoragePrefix(QString prefix = QString());

/// the default storage prefix used by MultiMC
static QString defaultStoragePrefix();

bool storagePathIsDefault() const;

/// Get the prefix - root of the storage to be used
QString storagePrefix() const;

/// Get the relative path where the library should be saved
QString storageSuffix() const;

/// Get the absolute path where the library should be saved
QString storagePath() const;

/// Set the url base for downloads
void setBaseUrl(const QString &base_url)
void setRepositoryURL(const QString &base_url)
{
m_base_url = base_url;
m_repositoryURL = base_url;
}

/// List of files this library describes. Required because of platform-specificness of native libs
QStringList files() const;

/// List Shortcut for checking if all the above files exist
bool filesExist(const QDir &base) const;
void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native,
QStringList & native32, QStringList & native64, const QString & overridePath) const;

void setAbsoluteUrl(const QString &absolute_url)
{
m_absolute_url = absolute_url;
m_absoluteURL = absolute_url;
}

QString absoluteUrl() const
void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info)
{
return m_absolute_url;
m_mojangDownloads = info;
}

void setHint(const QString &hint)
{
m_hint = hint;
}

QString hint() const
{
return m_hint;
}

/// Set the load rules
void setRules(QList<std::shared_ptr<Rule>> rules)
{
@@ -141,47 +127,61 @@ class RawLibrary
/// Returns true if the library should be loaded (or extracted, in case of natives)
bool isActive() const;

/// Get the URL to download the library from
QString url() const;
// Get a list of downloads for this library
QList<NetActionPtr> getDownloads(OpSys system, class HttpMetaCache * cache,
QStringList & failedFiles, const QString & overridePath) const;

private: /* methods */
/// the default storage prefix used by MultiMC
static QString defaultStoragePrefix();

/// Get the prefix - root of the storage to be used
QString storagePrefix() const;

/// Get the relative path where the library should be saved
QString storageSuffix(OpSys system) const;

QString hint() const
{
return m_hint;
}

protected: /* data */
/// the basic gradle dependency specifier.
GradleSpecifier m_name;
/// where to store the lib locally
QString m_storage_path;
/// is this lib actually active on the current OS?
bool m_is_active = false;

/// DEPRECATED URL prefix of the maven repo where the file can be downloaded
QString m_base_url;
QString m_repositoryURL;

/// DEPRECATED: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined
QString m_absolute_url;
QString m_absoluteURL;

public: /* data */
/**
* MultiMC-specific type hint - modifies how the library is treated
*/
QString m_hint;

/**
* storage - by default the local libraries folder in multimc, but could be elsewhere
* MultiMC specific, because of FTB.
* MultiMC specific, because of FTB.
*/
QString m_storagePrefix;

/// true if the library had an extract/excludes section (even empty)
bool applyExcludes = false;
bool m_hasExcludes = false;

/// a list of files that shouldn't be extracted from the library
QStringList extract_excludes;
QStringList m_extractExcludes;

/// native suffixes per OS
QMap<OpSys, QString> m_native_classifiers;
QMap<OpSys, QString> m_nativeClassifiers;

/// true if the library had a rules section (even empty)
bool applyRules = false;

/// rules associated with the library
QList<std::shared_ptr<Rule>> m_rules;

/// MOJANG: container with Mojang style download info
MojangLibraryDownloadInfo::Ptr m_mojangDownloads;
};
@@ -0,0 +1,271 @@
#include <QTest>
#include "TestUtil.h"

#include "minecraft/MojangVersionFormat.h"
#include "minecraft/onesix/OneSixVersionFormat.h"
#include "minecraft/Library.h"
#include "net/HttpMetaCache.h"
#include "FileSystem.h"

class LibraryTest : public QObject
{
Q_OBJECT
private:
LibraryPtr readMojangJson(const char *file)
{
auto path = QFINDTESTDATA(file);
QFile jsonFile(path);
jsonFile.open(QIODevice::ReadOnly);
auto data = jsonFile.readAll();
jsonFile.close();
return MojangVersionFormat::libraryFromJson(QJsonDocument::fromJson(data).object(), file);
}
// get absolute path to expected storage, assuming default cache prefix
QStringList getStorage(QString relative)
{
return {FS::PathCombine(cache->getBasePath("libraries"), relative)};
}
private
slots:
void initTestCase()
{
cache.reset(new HttpMetaCache());
cache->addBase("libraries", QDir("libraries").absolutePath());
dataDir = QDir("data").absolutePath();
}
void test_legacy()
{
Library test("test.package:testname:testversion");
QCOMPARE(test.artifactPrefix(), QString("test.package:testname"));
QCOMPARE(test.isNative(), false);

QStringList jar, native, native32, native64;
test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString());
QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar"));
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
void test_legacy_url()
{
QStringList failedFiles;
Library test("test.package:testname:testversion");
test.setRepositoryURL("file://foo/bar");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 1);
QCOMPARE(failedFiles, {});
NetActionPtr dl = downloads[0];
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar"));
}
void test_legacy_url_local_broken()
{
Library test("test.package:testname:testversion");
QCOMPARE(test.isNative(), false);
QStringList failedFiles;
test.setHint("local");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 0);
QCOMPARE(failedFiles, getStorage("test/package/testname/testversion/testname-testversion.jar"));
}
void test_legacy_url_local_override()
{
Library test("com.paulscode:codecwav:20101023");
QCOMPARE(test.isNative(), false);
QStringList failedFiles;
test.setHint("local");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data"));
QCOMPARE(downloads.size(), 0);
QCOMPARE(failedFiles.size(), 0);

QStringList jar, native, native32, native64;
test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
void test_legacy_native()
{
Library test("test.package:testname:testversion");
test.m_nativeClassifiers[OpSys::Os_Linux]="linux";
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar"));
QCOMPARE(native32, {});
QCOMPARE(native64, {});
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
auto dl = dls[0];
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar"));
}
}
void test_legacy_native_arch()
{
Library test("test.package:testname:testversion");
test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}";
test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}";
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar"));
}
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar"));
}
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar"));
}
}
void test_legacy_native_arch_local_override()
{
Library test("test.package:testname:testversion");
test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
test.setHint("local");
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()});
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")});
}
}
void test_onenine()
{
auto test = readMojangJson("data/lib-simple.json");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
}
test->setHint("local");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_local_override()
{
auto test = readMojangJson("data/lib-simple.json");
test->setHint("local");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_native()
{
auto test = readMojangJson("data/lib-native.json");
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar"));
QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
QCOMPARE(native32, {});
QCOMPARE(native64, {});
QStringList failedFiles;
auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar"));
QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
}
void test_onenine_native_arch()
{
auto test = readMojangJson("data/lib-native-arch.json");
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
QStringList failedFiles;
auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
}
private:
std::unique_ptr<HttpMetaCache> cache;
QString dataDir;
};

QTEST_GUILESS_MAIN(LibraryTest)

#include "Library_test.moc"
@@ -1,4 +1,7 @@
#include "MinecraftInstance.h"
#include <minecraft/launch/CreateServerResourcePacksFolder.h>
#include <minecraft/launch/ExtractNatives.h>
#include <minecraft/launch/PrintInstanceInfo.h>
#include <settings/Setting.h>
#include "settings/SettingsObject.h"
#include "Env.h"
@@ -9,6 +12,18 @@
#include <FileSystem.h>
#include <java/JavaVersion.h>

#include "launch/LaunchTask.h"
#include "launch/steps/PostLaunchCommand.h"
#include "launch/steps/Update.h"
#include "launch/steps/PreLaunchCommand.h"
#include "launch/steps/TextPrint.h"
#include "minecraft/launch/LauncherPartLaunch.h"
#include "minecraft/launch/ModMinecraftJar.h"
#include "minecraft/launch/ClaimAccount.h"
#include "java/launch/CheckJava.h"

#include <icons/IIconList.h>

#define IBUS "@im=ibus"

// all of this because keeping things compatible with deprecated old settings
@@ -52,6 +67,7 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
// special!
m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation);
m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation);
m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation);

// Window Size
auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
@@ -64,6 +80,10 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting);
m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting);
m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting);

// Minecraft launch method
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
}

QString MinecraftInstance::minecraftRoot() const
@@ -77,6 +97,11 @@ QString MinecraftInstance::minecraftRoot() const
return mcDir.filePath();
}

QString MinecraftInstance::binRoot() const
{
return FS::PathCombine(minecraftRoot(), "bin");
}

std::shared_ptr< BaseVersionList > MinecraftInstance::versionList() const
{
return ENV.getVersionList("net.minecraft");
@@ -105,7 +130,7 @@ QStringList MinecraftInstance::javaArguments() const
args << QString("-Xmx%1m").arg(settings()->get("MaxMemAlloc").toInt());

// No PermGen in newer java.
JavaVersion javaVersion(settings()->get("JavaVersion").toString());
JavaVersion javaVersion = getJavaVersion();
if(javaVersion.requiresPermGen())
{
auto permgen = settings()->get("PermGen").toInt();
@@ -116,7 +141,6 @@ QStringList MinecraftInstance::javaArguments() const
}

args << "-Duser.language=en";
args << "-jar" << FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "NewLaunch.jar");

return args;
}
@@ -256,6 +280,11 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
auto i = sessionRef.u.properties.begin();
while (i != sessionRef.u.properties.end())
{
if(i.key() == "preferredLanguage")
{
++i;
continue;
}
addToFilter(i.value(), "<" + i.key().toUpper() + ">");
++i;
}
@@ -346,7 +375,7 @@ QString MinecraftInstance::prettifyTimeDuration(int64_t duration)
QString MinecraftInstance::getStatusbarDescription()
{
QStringList traits;
if (flags() & VersionBrokenFlag)
if (hasVersionBroken())
{
traits.append(tr("broken"));
}
@@ -357,13 +386,111 @@ QString MinecraftInstance::getStatusbarDescription()
{
description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed())));
}
/*
if(traits.size())
if(hasCrashed())
{
description.append(QString(" (%1)").arg(traits.join(", ")));
description.append(tr(", has crashed."));
}
*/
return description;
}

std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
{
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr()));
auto pptr = process.get();

ENV.icons()->saveIcon(iconKey(), FS::PathCombine(minecraftRoot(), "icon.png"), "PNG");

// print a header
{
process->appendStep(std::make_shared<TextPrint>(pptr, "Minecraft folder is:\n" + minecraftRoot() + "\n\n", MessageLevel::MultiMC));
}

// check java
{
auto step = std::make_shared<CheckJava>(pptr);
process->appendStep(step);
}

// check launch method
QStringList validMethods = validLaunchMethods();
QString method = launchMethod();
if(!validMethods.contains(method))
{
process->appendStep(std::make_shared<TextPrint>(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
return process;
}

// run pre-launch command if that's needed
if(getPreLaunchCommand().size())
{
auto step = std::make_shared<PreLaunchCommand>(pptr);
step->setWorkingDirectory(minecraftRoot());
process->appendStep(step);
}

// if we aren't in offline mode,.
if(session->status != AuthSession::PlayableOffline)
{
process->appendStep(std::make_shared<ClaimAccount>(pptr, session));
process->appendStep(std::make_shared<Update>(pptr));
}

// if there are any jar mods
if(getJarMods().size())
{
auto step = std::make_shared<ModMinecraftJar>(pptr);
process->appendStep(step);
}

// print some instance info here...
{
auto step = std::make_shared<PrintInstanceInfo>(pptr, session);
process->appendStep(step);
}

// create the server-resource-packs folder (workaround for Minecraft bug MCL-3732)
{
auto step = std::make_shared<CreateServerResourcePacksFolder>(pptr);
process->appendStep(step);
}

// extract native jars if needed
{
auto step = std::make_shared<ExtractNatives>(pptr);
process->appendStep(step);
}

{
// actually launch the game
auto step = createMainLaunchStep(pptr, session);
process->appendStep(step);
}

// run post-exit command if that's needed
if(getPostExitCommand().size())
{
auto step = std::make_shared<PostLaunchCommand>(pptr);
step->setWorkingDirectory(minecraftRoot());
process->appendStep(step);
}
if (session)
{
process->setCensorFilter(createCensorFilterFromSession(session));
}
m_launchProcess = process;
emit launchTaskChanged(m_launchProcess);
return m_launchProcess;
}

QString MinecraftInstance::launchMethod()
{
return m_settings->get("MCLaunchMethod").toString();
}

JavaVersion MinecraftInstance::getJavaVersion() const
{
return JavaVersion(settings()->get("JavaVersion").toString());
}


#include "MinecraftInstance.moc"
@@ -1,12 +1,14 @@
#pragma once
#include "BaseInstance.h"
#include <java/JavaVersion.h>
#include "minecraft/Mod.h"
#include <QProcess>

#include "multimc_logic_export.h"

class ModList;
class WorldList;
class LaunchStep;

class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance
{
@@ -17,6 +19,9 @@ class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance
/// Path to the instance's minecraft directory.
QString minecraftRoot() const;

/// Path to the instance's minecraft/bin directory.
QString binRoot() const;

////// Mod Lists //////
virtual std::shared_ptr<ModList> resourcePackList() const
{
@@ -36,7 +41,7 @@ class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance
return QList<Mod>();
}

/// get the launch script to be used with this
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
virtual QString createLaunchScript(AuthSessionPtr session) = 0;

//FIXME: nuke?
@@ -60,8 +65,24 @@ class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance

virtual QString getStatusbarDescription() override;

virtual QStringList getClassPath() const = 0;
virtual QStringList getNativeJars() const = 0;

virtual QString getMainClass() const = 0;

virtual QString getNativePath() const = 0;

virtual QString getLocalLibraryPath() const = 0;

virtual QStringList processMinecraftArgs(AuthSessionPtr account) const = 0;

virtual JavaVersion getJavaVersion() const;

protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
virtual QStringList validLaunchMethods() = 0;
virtual QString launchMethod();
virtual std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) = 0;
private:
QString prettifyTimeDuration(int64_t duration);
};

Large diffs are not rendered by default.

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,9 +21,10 @@
#include <QList>
#include <memory>

#include "RawLibrary.h"
#include "Library.h"
#include "VersionFile.h"
#include "JarMod.h"
#include "MojangDownloadInfo.h"

#include "multimc_logic_export.h"

@@ -34,10 +35,10 @@ class OneSixInstance;
class MULTIMC_LOGIC_EXPORT MinecraftProfile : public QAbstractListModel
{
Q_OBJECT
friend class ProfileStrategy;

public:
explicit MinecraftProfile(ProfileStrategy *strategy);
virtual ~MinecraftProfile();

void setStrategy(ProfileStrategy * strategy);
ProfileStrategy *strategy();
@@ -82,25 +83,40 @@ class MULTIMC_LOGIC_EXPORT MinecraftProfile : public QAbstractListModel
/// clear the profile
void clear();

/// apply the patches. Throws all sorts of errors.
void reapply();

/// apply the patches. Catches all the errors and returns true/false for success/failure
bool reapplySafe();

/// do a finalization step (should always be done after applying all patches to profile)
void finalize();
bool reapplyPatches();

public: /* application of profile variables from patches */
void applyMinecraftVersion(const QString& id);
void applyMainClass(const QString& mainClass);
void applyAppletClass(const QString& appletClass);
void applyMinecraftArguments(const QString& minecraftArguments);
void applyMinecraftVersionType(const QString& type);
void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets);
void applyTraits(const QSet<QString> &traits);
void applyTweakers(const QStringList &tweakers);
void applyJarMods(const QList<JarmodPtr> &jarMods);
void applyLibrary(LibraryPtr library);
void applyProblemSeverity(ProblemSeverity severity);
void applyMojangDownload(const QString & key, MojangDownloadInfo::Ptr download);

public: /* getters for profile variables */
QString getMinecraftVersion() const;
QString getMainClass() const;
QString getAppletClass() const;
QString getMinecraftVersionType() const;
MojangAssetIndexInfo::Ptr getMinecraftAssets() const;
QString getMinecraftArguments() const;
const QSet<QString> & getTraits() const;
const QStringList & getTweakers() const;
const QList<JarmodPtr> & getJarMods() const;
const QList<LibraryPtr> & getLibraries() const;
void getLibraryFiles(const QString & architecture, QStringList & jars, QStringList & nativeJars, const QString & overridePath) const;
QString getMainJarUrl() const;
bool hasTrait(const QString & trait) const;
ProblemSeverity getProblemSeverity() const;

public:
/// get all java libraries that belong to the classpath
QList<RawLibraryPtr> getActiveNormalLibs();

/// get all native libraries that need to be available to the process
QList<RawLibraryPtr> getActiveNativeLibs();

/// get file ID of the patch file at #
QString versionFileId(const int index) const;

/// get the profile patch by id
ProfilePatchPtr versionPatch(const QString &id);

@@ -110,69 +126,52 @@ class MULTIMC_LOGIC_EXPORT MinecraftProfile : public QAbstractListModel
/// save the current patch order
void saveCurrentOrder() const;

public: /* only use in ProfileStrategy */
/// Remove all the patches
void clearPatches();

/// Add the patch object to the internal list of patches
void appendPatch(ProfilePatchPtr patch);

public: /* data */
/// the ID - determines which jar to use! ACTUALLY IMPORTANT!
QString id;

/// the time this version was actually released by Mojang, as string and as QDateTime
QString m_releaseTimeString;
QDateTime m_releaseTime;

/// the time this version was last updated by Mojang, as string and as QDateTime
QString m_updateTimeString;
QDateTime m_updateTime;
private: /* data */
/// the version of Minecraft - jar to use
QString m_minecraftVersion;

/// Release type - "release" or "snapshot"
QString type;
QString m_minecraftVersionType;

/// Assets type - "legacy" or a version ID
QString assets;
/**
* DEPRECATED: Old versions of the new vanilla launcher used this
* ex: "username_session_version"
*/
QString processArguments;
/// Same as above, but only for vanilla
QString vanillaProcessArguments;
MojangAssetIndexInfo::Ptr m_minecraftAssets;

// Mojang: list of 'downloads' - client jar, server jar, windows server exe, maybe more.
QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;

/**
* arguments that should be used for launching minecraft
*
* ex: "--username ${auth_player_name} --session ${auth_session}
* --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}"
*/
QString minecraftArguments;
/// Same as above, but only for vanilla
QString vanillaMinecraftArguments;
/**
* A list of all tweaker classes
*/
QStringList tweakers;
/**
* The main class to load first
*/
QString mainClass;
/**
* The applet class, for some very old minecraft releases
*/
QString appletClass;
QString m_minecraftArguments;

/// the list of libs - both active and inactive, native and java
QList<RawLibraryPtr> libraries;
/// A list of all tweaker classes
QStringList m_tweakers;

/// same, but only vanilla.
QList<RawLibraryPtr> vanillaLibraries;
/// The main class to load first
QString m_mainClass;

/// The applet class, for some very old minecraft releases
QString m_appletClass;

/// the list of libraries
QList<LibraryPtr> m_libraries;

/// traits, collected from all the version files (version files can only add)
QSet<QString> traits;
QSet<QString> m_traits;

/// A list of jar mods. version files can add those.
QList<JarmodPtr> jarMods;
QList<JarmodPtr> m_jarMods;

ProblemSeverity m_problemSeverity = PROBLEM_NONE;

/*
FIXME: add support for those rules here? Looks like a pile of quick hacks to me though.
@@ -194,7 +193,10 @@ class MULTIMC_LOGIC_EXPORT MinecraftProfile : public QAbstractListModel
}
*/
// QList<Rule> rules;
private:
QList<ProfilePatchPtr> VersionPatches;

/// list of attached profile patches
QList<ProfilePatchPtr> m_patches;

/// strategy used for profile operations
ProfileStrategy *m_strategy = nullptr;
};
@@ -43,6 +43,11 @@ QString MinecraftVersion::typeString() const
}
}

VersionSource MinecraftVersion::getVersionSource()
{
return m_versionSource;
}

bool MinecraftVersion::hasJarMods()
{
return false;
@@ -53,37 +58,26 @@ bool MinecraftVersion::isMinecraftVersion()
return true;
}

void MinecraftVersion::applyFileTo(MinecraftProfile *version)
void MinecraftVersion::applyFileTo(MinecraftProfile *profile)
{
if(m_versionSource == Local && getVersionFile())
if(m_versionSource == VersionSource::Local && getVersionFile())
{
getVersionFile()->applyTo(version);
getVersionFile()->applyTo(profile);
}
else
{
throw VersionIncomplete(QObject::tr("Can't apply incomplete/builtin Minecraft version %1").arg(m_name));
}
}
/*
QJsonDocument MinecraftVersion::toJson(bool saveOrder)
{
if(m_versionSource == Local && getVersionFile())
{
return getVersionFile()->toJson(saveOrder);
}
else
{
throw VersionIncomplete(QObject::tr("Can't write incomplete/builtin Minecraft version %1").arg(m_name));
}
}
*/

QString MinecraftVersion::getUrl() const
{
// legacy fallback
if(m_versionFileURL.isEmpty())
{
return QString("http://") + URLConstants::AWS_DOWNLOAD_VERSIONS + m_descriptor + "/" + m_descriptor + ".json";
}
// current
return m_versionFileURL;
}

@@ -126,11 +120,11 @@ bool MinecraftVersion::isCustomizable()
{
switch(m_versionSource)
{
case Local:
case Remote:
case VersionSource::Local:
case VersionSource::Remote:
// locally cached file, or a remote file that we can acquire can be customized
return true;
case Builtin:
case VersionSource::Builtin:
// builtins do not follow the normal OneSix format. They are not customizable.
default:
// Everything else is undefined and therefore not customizable.
@@ -141,7 +135,7 @@ bool MinecraftVersion::isCustomizable()

const QList<PatchProblem> &MinecraftVersion::getProblems()
{
if(m_versionSource != Builtin && getVersionFile())
if(m_versionSource != VersionSource::Builtin && getVersionFile())
{
return getVersionFile()->getProblems();
}
@@ -150,59 +144,34 @@ const QList<PatchProblem> &MinecraftVersion::getProblems()

ProblemSeverity MinecraftVersion::getProblemSeverity()
{
if(m_versionSource != Builtin && getVersionFile())
if(m_versionSource != VersionSource::Builtin && getVersionFile())
{
return getVersionFile()->getProblemSeverity();
}
return ProfilePatch::getProblemSeverity();
}

void MinecraftVersion::applyTo(MinecraftProfile *version)
void MinecraftVersion::applyTo(MinecraftProfile *profile)
{
// do we have this one cached?
if (m_versionSource == Local)
if (m_versionSource == VersionSource::Local)
{
applyFileTo(version);
applyFileTo(profile);
return;
}
// if not builtin, do not proceed any further.
if (m_versionSource != Builtin)
if (m_versionSource != VersionSource::Builtin)
{
throw VersionIncomplete(QObject::tr(
"Minecraft version %1 could not be applied: version files are missing.").arg(m_descriptor));
}
if (!m_descriptor.isNull())
{
version->id = m_descriptor;
}
if (!m_mainClass.isNull())
{
version->mainClass = m_mainClass;
}
if (!m_appletClass.isNull())
{
version->appletClass = m_appletClass;
}
if (!m_processArguments.isNull())
{
version->vanillaProcessArguments = m_processArguments;
version->processArguments = m_processArguments;
}
if (!m_type.isNull())
{
version->type = m_type;
}
if (!m_releaseTimeString.isNull())
{
version->m_releaseTimeString = m_releaseTimeString;
version->m_releaseTime = m_releaseTime;
}
if (!m_updateTimeString.isNull())
{
version->m_updateTimeString = m_updateTimeString;
version->m_updateTime = m_updateTime;
}
version->traits.unite(m_traits);
profile->applyMinecraftVersion(m_descriptor);
profile->applyMainClass(m_mainClass);
profile->applyAppletClass(m_appletClass);
profile->applyMinecraftArguments(" ${auth_player_name} ${auth_session}"); // all builtin versions are legacy
profile->applyMinecraftVersionType(m_type);
profile->applyTraits(m_traits);
profile->applyProblemSeverity(m_problemSeverity);
}

int MinecraftVersion::getOrder()
@@ -220,35 +189,42 @@ QList<JarmodPtr> MinecraftVersion::getJarMods()
return QList<JarmodPtr>();
}

QString MinecraftVersion::getPatchName()
QString MinecraftVersion::getName()
{
return "Minecraft";
}
QString MinecraftVersion::getPatchVersion()
QString MinecraftVersion::getVersion()
{
return m_descriptor;
}
QString MinecraftVersion::getPatchID()
QString MinecraftVersion::getID()
{
return "net.minecraft";
}
QString MinecraftVersion::getPatchFilename()
QString MinecraftVersion::getFilename()
{
return QString();
}
QDateTime MinecraftVersion::getReleaseDateTime()
{
return m_releaseTime;
}


bool MinecraftVersion::needsUpdate()
{
return m_versionSource == Remote || hasUpdate();
return m_versionSource == VersionSource::Remote || hasUpdate();
}

bool MinecraftVersion::hasUpdate()
{
return m_versionSource == Remote || (m_versionSource == Local && upstreamUpdate);
return m_versionSource == VersionSource::Remote || (m_versionSource == VersionSource::Local && upstreamUpdate);
}

bool MinecraftVersion::isCustom()
{
// if we add any other source types, this will evaluate to false for them.
return m_versionSource != Builtin && m_versionSource != Local && m_versionSource != Remote;
return m_versionSource != VersionSource::Builtin
&& m_versionSource != VersionSource::Local
&& m_versionSource != VersionSource::Remote;
}
@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@
#include "BaseVersion.h"
#include "ProfilePatch.h"
#include "VersionFile.h"
#include "VersionSource.h"

#include "multimc_logic_export.h"

@@ -32,21 +31,26 @@ typedef std::shared_ptr<MinecraftVersion> MinecraftVersionPtr;

class MULTIMC_LOGIC_EXPORT MinecraftVersion : public BaseVersion, public ProfilePatch
{
friend class MinecraftVersionList;

public: /* methods */
bool usesLegacyLauncher();
virtual QString descriptor() override;
virtual QString name() override;
virtual QString typeString() const override;
virtual bool hasJarMods() override;
virtual bool isMinecraftVersion() override;
virtual void applyTo(MinecraftProfile *version) override;
virtual void applyTo(MinecraftProfile *profile) override;
virtual int getOrder() override;
virtual void setOrder(int order) override;
virtual QList<JarmodPtr> getJarMods() override;
virtual QString getPatchID() override;
virtual QString getPatchVersion() override;
virtual QString getPatchName() override;
virtual QString getPatchFilename() override;
virtual QString getID() override;
virtual QString getVersion() override;
virtual QString getName() override;
virtual QString getFilename() override;
QDateTime getReleaseDateTime() override;
VersionSource getVersionSource() override;

bool needsUpdate();
bool hasUpdate();
virtual bool isCustom() override;
@@ -72,7 +76,7 @@ class MULTIMC_LOGIC_EXPORT MinecraftVersion : public BaseVersion, public Profile
return true;
}

VersionFilePtr getVersionFile();
virtual VersionFilePtr getVersionFile() override;

// virtual QJsonDocument toJson(bool saveOrder) override;

@@ -82,10 +86,10 @@ class MULTIMC_LOGIC_EXPORT MinecraftVersion : public BaseVersion, public Profile
virtual ProblemSeverity getProblemSeverity() override;

private: /* methods */
void applyFileTo(MinecraftProfile *version);
void applyFileTo(MinecraftProfile *profile);

public: /* data */
VersionSource m_versionSource = Builtin;
protected: /* data */
VersionSource m_versionSource = VersionSource::Builtin;

/// The URL that this version will be downloaded from.
QString m_versionFileURL;
@@ -105,18 +109,13 @@ class MULTIMC_LOGIC_EXPORT MinecraftVersion : public BaseVersion, public Profile
/// The applet class this version uses (if any, can be empty).
QString m_appletClass;

/// The process arguments used by this version
QString m_processArguments;

/// The type of this release
QString m_type;

/// the time this version was actually released by Mojang, as string and as QDateTime
QString m_releaseTimeString;
/// the time this version was actually released by Mojang
QDateTime m_releaseTime;

/// the time this version was last updated by Mojang, as string and as QDateTime
QString m_updateTimeString;
/// the time this version was last updated by Mojang
QDateTime m_updateTime;

/// MD5 hash of the minecraft jar
@@ -128,7 +127,6 @@ class MULTIMC_LOGIC_EXPORT MinecraftVersion : public BaseVersion, public Profile
/// an update available from Mojang
MinecraftVersionPtr upstreamUpdate;

private: /* data */
QDateTime m_loadedVersionFileTimestamp;
mutable VersionFilePtr m_loadedVersionFile;
};