Massive renaming in the backend folder, all around restructure in the same.

This commit is contained in:
Petr Mrázek
2013-07-29 00:59:35 +02:00
parent 8808a8b108
commit 2e0cbf393a
66 changed files with 491 additions and 962 deletions

199
backend/BaseInstance.cpp Normal file
View File

@ -0,0 +1,199 @@
/* Copyright 2013 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 "BaseInstance.h"
#include <QFileInfo>
#include "inisettingsobject.h"
#include "setting.h"
#include "overridesetting.h"
#include "pathutils.h"
#include <lists/MinecraftVersionList.h>
BaseInstance::BaseInstance(const QString &rootDir, QObject *parent) :
QObject(parent)
{
m_rootDir = rootDir;
m_settings = new INISettingsObject(PathCombine(rootDir, "instance.cfg"), this);
settings().registerSetting(new Setting("name", "Unnamed Instance"));
settings().registerSetting(new Setting("iconKey", "default"));
settings().registerSetting(new Setting("notes", ""));
settings().registerSetting(new Setting("NeedsRebuild", true));
settings().registerSetting(new Setting("ShouldUpdate", false));
settings().registerSetting(new Setting("JarVersion", "Unknown"));
settings().registerSetting(new Setting("LwjglVersion", "2.9.0"));
settings().registerSetting(new Setting("IntendedJarVersion", ""));
settings().registerSetting(new Setting("lastLaunchTime", 0));
// Java Settings
settings().registerSetting(new OverrideSetting("JavaPath", globalSettings->getSetting("JavaPath")));
settings().registerSetting(new OverrideSetting("JvmArgs", globalSettings->getSetting("JvmArgs")));
// Custom Commands
settings().registerSetting(new OverrideSetting("PreLaunchCommand",
globalSettings->getSetting("PreLaunchCommand")));
settings().registerSetting(new OverrideSetting("PostExitCommand",
globalSettings->getSetting("PostExitCommand")));
// Window Size
settings().registerSetting(new OverrideSetting("LaunchCompatMode", globalSettings->getSetting("LaunchCompatMode")));
settings().registerSetting(new OverrideSetting("LaunchMaximized", globalSettings->getSetting("LaunchMaximized")));
settings().registerSetting(new OverrideSetting("MinecraftWinWidth", globalSettings->getSetting("MinecraftWinWidth")));
settings().registerSetting(new OverrideSetting("MinecraftWinHeight", globalSettings->getSetting("MinecraftWinHeight")));
// Memory
settings().registerSetting(new OverrideSetting("MinMemAlloc", globalSettings->getSetting("MinMemAlloc")));
settings().registerSetting(new OverrideSetting("MaxMemAlloc", globalSettings->getSetting("MaxMemAlloc")));
// Auto login
settings().registerSetting(new OverrideSetting("AutoLogin", globalSettings->getSetting("AutoLogin")));
// Console
settings().registerSetting(new OverrideSetting("ShowConsole", globalSettings->getSetting("ShowConsole")));
settings().registerSetting(new OverrideSetting("AutoCloseConsole", globalSettings->getSetting("AutoCloseConsole")));
// Overrides
settings().registerSetting(new Setting("OverrideConsole", false));
settings().registerSetting(new Setting("OverrideWindow", false));
settings().registerSetting(new Setting("OverrideLogin", false));
settings().registerSetting(new Setting("OverrideMemory", false));
settings().registerSetting(new Setting("OverrideJava", false));
settings().registerSetting(new Setting("OverrideCommands", false));
}
QString BaseInstance::id() const
{
return QFileInfo(rootDir()).fileName();
}
QString BaseInstance::rootDir() const
{
return m_rootDir;
}
InstanceList *BaseInstance::instList() const
{
if (parent()->inherits("InstanceList"))
return (InstanceList *)parent();
else
return NULL;
}
QString BaseInstance::minecraftDir() const
{
QFileInfo mcDir(PathCombine(rootDir(), "minecraft"));
QFileInfo dotMCDir(PathCombine(rootDir(), ".minecraft"));
if (dotMCDir.exists() && !mcDir.exists())
return dotMCDir.filePath();
else
return mcDir.filePath();
}
QString BaseInstance::instModsDir() const
{
return PathCombine(rootDir(), "instMods");
}
QString BaseInstance::binDir() const
{
return PathCombine(minecraftDir(), "bin");
}
QString BaseInstance::savesDir() const
{
return PathCombine(minecraftDir(), "saves");
}
QString BaseInstance::mlModsDir() const
{
return PathCombine(minecraftDir(), "mods");
}
QString BaseInstance::coreModsDir() const
{
return PathCombine(minecraftDir(), "coremods");
}
QString BaseInstance::resourceDir() const
{
return PathCombine(minecraftDir(), "resources");
}
QString BaseInstance::screenshotsDir() const
{
return PathCombine(minecraftDir(), "screenshots");
}
QString BaseInstance::texturePacksDir() const
{
return PathCombine(minecraftDir(), "texturepacks");
}
QString BaseInstance::mcJar() const
{
return PathCombine(binDir(), "minecraft.jar");
}
QString BaseInstance::mcBackup() const
{
return PathCombine(binDir(), "mcbackup.jar");
}
QString BaseInstance::modListFile() const
{
return PathCombine(rootDir(), "modlist");
}
InstVersionList *BaseInstance::versionList() const
{
return &MinecraftVersionList::getMainList();
}
bool BaseInstance::shouldUpdateCurrentVersion() const
{
QFileInfo jar(mcJar());
return jar.lastModified().toUTC().toMSecsSinceEpoch() != lastCurrentVersionUpdate();
}
void BaseInstance::updateCurrentVersion(bool keepCurrent)
{
QFileInfo jar(mcJar());
if(!jar.exists())
{
setLastCurrentVersionUpdate(0);
setCurrentVersion("Unknown");
return;
}
qint64 time = jar.lastModified().toUTC().toMSecsSinceEpoch();
setLastCurrentVersionUpdate(time);
if (!keepCurrent)
{
// TODO: Implement GetMinecraftJarVersion function.
QString newVersion = "Unknown";//javautils::GetMinecraftJarVersion(jar.absoluteFilePath());
setCurrentVersion(newVersion);
}
}
SettingsObject &BaseInstance::settings() const
{
return *m_settings;
}

313
backend/BaseInstance.h Normal file
View File

@ -0,0 +1,313 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QDateTime>
#include <settingsobject.h>
#include "inifile.h"
#include "lists/InstVersionList.h"
#include "libmmc_config.h"
class InstanceList;
/*!
* \brief Base class for instances.
* This class implements many functions that are common between instances and
* provides a standard interface for all instances.
*
* To create a new instance type, create a new class inheriting from this class
* and implement the pure virtual functions.
*/
class LIBMULTIMC_EXPORT BaseInstance : public QObject
{
Q_OBJECT
// Properties
/*!
* The instance's ID.
* This is a unique identifier string that is, by default, set to the
* instance's folder name. It's not always the instance's folder name,
* however, as any class deriving from Instance can override the id()
* method and change how the ID is determined. The instance's ID should
* always remain constant. Undefined behavior results if an already loaded
* instance's ID changes.
*/
Q_PROPERTY(QString id READ id STORED false)
//! Path to the instance's root directory.
Q_PROPERTY(QString rootDir READ rootDir)
//! The name of the instance that is displayed to the user.
Q_PROPERTY(QString name READ name WRITE setName)
//! The instance's icon key.
Q_PROPERTY(QString iconKey READ iconKey WRITE setIconKey)
//! The instance's notes.
Q_PROPERTY(QString notes READ notes WRITE setNotes)
//! The instance's group.
Q_PROPERTY(QString group READ group WRITE setGroup)
/*!
* Gets the time that the instance was last launched.
* Stored in milliseconds since epoch.
* This value is usually used for things like sorting instances by the time
* they were last launched.
*/
Q_PROPERTY(qint64 lastLaunch READ lastLaunch WRITE setLastLaunch)
/*!
* Whether or not the instance's minecraft.jar needs to be rebuilt.
* If this is true, when the instance launches, its jar mods will be
* re-added to a fresh minecraft.jar file.
*/
Q_PROPERTY(bool shouldRebuild READ shouldRebuild WRITE setShouldRebuild)
/*!
* Whether or not Minecraft should be downloaded when the instance is launched.
* This returns true if shouldForceUpdate game is true or if the intended and
* current versions don't match.
*/
Q_PROPERTY(bool shouldUpdate READ shouldUpdate WRITE setShouldUpdate)
/*!
* The instance's current version.
* This value represents the instance's current version. If this value is
* different from the intendedVersion, the instance should be updated.
* \warning Don't change this value unless you know what you're doing.
*/
Q_PROPERTY(QString currentVersion READ currentVersion WRITE setCurrentVersion)
/*!
* The version that the user has set for this instance to use.
* If this is not the same as currentVersion, the instance's game updater
* will be run on launch.
*/
Q_PROPERTY(QString intendedVersion READ intendedVersion WRITE setIntendedVersion)
//! The version of LWJGL that this instance uses.
Q_PROPERTY(QString lwjglVersion READ lwjglVersion WRITE setLWJGLVersion)
/*!
* Gets the last time that the current version was checked.
* This is checked against the last modified time on the jar file to see if
* the current version needs to be checked again.
*/
Q_PROPERTY(qint64 lastCurrentVersionUpdate READ lastCurrentVersionUpdate WRITE setLastCurrentVersionUpdate)
// Dirs
//! Path to the instance's .minecraft folder.
Q_PROPERTY(QString minecraftDir READ minecraftDir STORED false)
//! Path to the instance's instMods folder.
Q_PROPERTY(QString instModsDir READ instModsDir STORED false)
//! Path to the instance's bin folder.
Q_PROPERTY(QString binDir READ binDir STORED false)
//! Path to the instance's saves folder.
Q_PROPERTY(QString savesDir READ savesDir STORED false)
//! Path to the instance's mods folder (.minecraft/mods)
Q_PROPERTY(QString mlModsDir READ mlModsDir STORED false)
//! Path to the instance's coremods folder.
Q_PROPERTY(QString coreModsDir READ coreModsDir STORED false)
//! Path to the instance's resources folder.
Q_PROPERTY(QString resourceDir READ resourceDir STORED false)
//! Path to the instance's screenshots folder.
Q_PROPERTY(QString screenshotsDir READ screenshotsDir STORED false)
//! Path to the instance's texturepacks folder.
Q_PROPERTY(QString texturePacksDir READ texturePacksDir STORED false)
// Files
//! Path to the instance's minecraft.jar
Q_PROPERTY(QString mcJar READ mcJar STORED false)
//! Path to the instance's mcbackup.jar
Q_PROPERTY(QString mcBackup READ mcBackup STORED false)
//! Path to the instance's modlist file.
Q_PROPERTY(QString modListFile READ modListFile STORED false)
public:
explicit BaseInstance(const QString &rootDir, QObject *parent = 0);
//////// STUFF ////////
virtual QString id() const;
virtual QString rootDir() const;
/*!
* \brief Gets the instance list that this instance is a part of.
* Returns NULL if this instance is not in a list
* (the parent is not an InstanceList).
* \return A pointer to the InstanceList containing this instance.
*/
virtual InstanceList *instList() const;
//////// INSTANCE INFO ////////
//// General Info ////
virtual QString name() const { return settings().get("name").toString(); }
virtual void setName(QString val)
{
settings().set("name", val);
emit propertiesChanged(this);
}
virtual QString iconKey() const { return settings().get("iconKey").toString(); }
virtual void setIconKey(QString val)
{
settings().set("iconKey", val);
emit propertiesChanged(this);
}
virtual QString notes() const { return settings().get("notes").toString(); }
virtual void setNotes(QString val) { settings().set("notes", val); }
virtual QString group() const { return m_group; }
virtual void setGroup(QString val)
{
m_group = val;
emit propertiesChanged(this);
}
virtual bool shouldRebuild() const { return settings().get("NeedsRebuild").toBool(); }
virtual void setShouldRebuild(bool val) { settings().set("NeedsRebuild", val); }
//// Version Stuff ////
virtual QString currentVersion() const { return settings().get("JarVersion").toString(); }
virtual void setCurrentVersion(QString val) { settings().set("JarVersion", val); }
virtual QString lwjglVersion() const { return settings().get("LwjglVersion").toString(); }
virtual void setLWJGLVersion(QString val) { settings().set("LwjglVersion", val); }
virtual QString intendedVersion() const { return settings().get("IntendedJarVersion").toString(); }
virtual void setIntendedVersion(QString val) { settings().set("IntendedJarVersion", val); }
virtual bool shouldUpdate() const
{
QVariant var = settings().get("ShouldUpdate");
if(!var.isValid() || var.toBool() == false)
{
return intendedVersion() != currentVersion();
}
return true;
}
virtual void setShouldUpdate(bool val) { settings().set("ShouldUpdate", val); }
//// Timestamps ////
virtual qint64 lastLaunch() const { return settings().get("lastLaunchTime").value<qint64>(); }
virtual void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch())
{
settings().set("lastLaunchTime", val);
emit propertiesChanged(this);
}
virtual qint64 lastCurrentVersionUpdate() const { return settings().get("lastVersionUpdate").value<qint64>(); }
virtual void setLastCurrentVersionUpdate(qint64 val) { settings().set("lastVersionUpdate", val); }
////// Directories //////
QString minecraftDir() const;
QString instModsDir() const;
QString binDir() const;
QString savesDir() const;
QString mlModsDir() const;
QString coreModsDir() const;
QString resourceDir() const;
QString screenshotsDir() const;
QString texturePacksDir() const;
////// Files //////
QString mcJar() const;
QString mcBackup() const;
QString modListFile() const;
//////// LISTS, LISTS, AND MORE LISTS ////////
/*!
* \brief Gets a pointer to this instance's version list.
* \return A pointer to the available version list for this instance.
*/
virtual InstVersionList *versionList() const;
//////// OTHER FUNCTIONS ////////
//// Version System ////
/*!
* \brief Checks whether or not the currentVersion of the instance needs to be updated.
* If this returns true, updateCurrentVersion is called. In the
* standard instance, this is determined by checking a timestamp
* stored in the instance config file against the last modified time of Minecraft.jar.
* \return True if updateCurrentVersion() should be called.
*/
virtual bool shouldUpdateCurrentVersion() const;
/*!
* \brief Updates the current version.
* This function should first set the current version timestamp
* (setCurrentVersionTimestamp()) to the current time. Next, if
* keepCurrent is false, this function should check what the
* instance's current version is and call setCurrentVersion() to
* update it. This function will automatically be called when the
* instance is loaded if shouldUpdateCurrentVersion returns true.
* \param keepCurrent If true, only the version timestamp will be updated.
*/
virtual void updateCurrentVersion(bool keepCurrent = false);
//// Settings System ////
/*!
* \brief Gets this instance's settings object.
* This settings object stores instance-specific settings.
* \return A pointer to this instance's settings object.
*/
virtual SettingsObject &settings() const;
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
*/
void propertiesChanged(BaseInstance * inst);
private:
QString m_rootDir;
QString m_group;
SettingsObject *m_settings;
};
// pointer for lazy people
typedef QSharedPointer<BaseInstance> InstancePtr;

91
backend/CMakeLists.txt Normal file
View File

@ -0,0 +1,91 @@
project(libMultiMC)
set(CMAKE_AUTOMOC ON)
# Find Qt
find_package(Qt5Core REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5Xml REQUIRED)
# Include Qt headers.
include_directories(${Qt5Base_INCLUDE_DIRS})
include_directories(${Qt5Network_INCLUDE_DIRS})
# Include utility library.
include_directories(${CMAKE_SOURCE_DIR}/libutil/include)
# Include settings library.
include_directories(${CMAKE_SOURCE_DIR}/libsettings/include)
SET(LIBINST_HEADERS
libmmc_config.h
# Instance Stuff
BaseInstance.h
LegacyInstance.h
OneSixInstance.h
InstanceFactory.h
# Versions
InstanceVersion.h
MinecraftVersion.h
OneSixVersion.h
VersionFactory.h
# Lists
lists/InstanceList.h
lists/InstVersionList.h
lists/MinecraftVersionList.h
lists/LwjglVersionList.h
# Tasks
tasks/Task.h
tasks/LoginTask.h
tasks/LoginResponse.h
tasks/UserInfo.h
tasks/GameUpdateTask.h
MinecraftProcess.h
)
SET(LIBINST_SOURCES
# Instance Stuff
BaseInstance.cpp
LegacyInstance.cpp
OneSixInstance.cpp
InstanceFactory.cpp
# Versions
InstanceVersion.cpp
MinecraftVersion.cpp
OneSixVersion.cpp
VersionFactory.cpp
# Lists
lists/InstanceList.cpp
lists/InstVersionList.cpp
lists/MinecraftVersionList.cpp
lists/LwjglVersionList.cpp
# Tasks
tasks/Task.cpp
tasks/LoginTask.cpp
tasks/GameUpdateTask.cpp
tasks/UserInfo.cpp
tasks/LoginResponse.cpp
MinecraftProcess.cpp
)
# Set the include dir path.
SET(LIBMULTIMC_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE)
# Include self.
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_BINARY_DIR}/include)
add_definitions(-DLIBMULTIMC_LIBRARY)
add_library(backend SHARED ${LIBINST_SOURCES} ${LIBINST_HEADERS})
qt5_use_modules(backend Core Network Xml)
target_link_libraries(backend libUtil libSettings)

View File

@ -0,0 +1,61 @@
/* Copyright 2013 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 "InstanceFactory.h"
#include <QDir>
#include <QFileInfo>
#include "BaseInstance.h"
#include "inifile.h"
#include "pathutils.h"
InstanceFactory InstanceFactory::loader;
InstanceFactory::InstanceFactory() :
QObject(NULL)
{
}
InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst, const QString &instDir)
{
BaseInstance *loadedInst = new BaseInstance(instDir, this);
// TODO: Sanity checks to verify that the instance is valid.
inst = loadedInst;
return NoLoadError;
}
InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&inst, const QString &instDir)
{
QDir rootDir(instDir);
qDebug(instDir.toUtf8());
if (!rootDir.exists() && !rootDir.mkpath("."))
{
return InstanceFactory::CantCreateDir;
}
inst = new BaseInstance(instDir, this);
//FIXME: really, how do you even know?
return InstanceFactory::NoCreateError;
}

79
backend/InstanceFactory.h Normal file
View File

@ -0,0 +1,79 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QMap>
#include <QList>
#include "libmmc_config.h"
class BaseInstance;
/*!
* The \bInstanceFactory\b is a singleton that manages loading and creating instances.
*/
class LIBMULTIMC_EXPORT InstanceFactory : public QObject
{
Q_OBJECT
public:
/*!
* \brief Gets a reference to the instance loader.
*/
static InstanceFactory &get() { return loader; }
enum InstLoadError
{
NoLoadError = 0,
UnknownLoadError,
NotAnInstance
};
enum InstCreateError
{
NoCreateError = 0,
UnknownCreateError,
InstExists,
CantCreateDir
};
/*!
* \brief Creates an instance with the given type and stores it in inst.
*
* \param inst Pointer to store the created instance in.
* \param type The type of instance to create.
* \param instDir The instance's directory.
* \return An InstCreateError error code.
* - InstExists if the given instance directory is already an instance.
* - CantCreateDir if the given instance directory cannot be created.
*/
InstCreateError createInstance(BaseInstance *&inst, const QString &instDir);
/*!
* \brief Loads an instance from the given directory.
* Checks the instance's INI file to figure out what the instance's type is first.
* \param inst Pointer to store the loaded instance in.
* \param instDir The instance's directory.
* \return An InstLoadError error code.
* - NotAnInstance if the given instance directory isn't a valid instance.
*/
InstLoadError loadInstance(BaseInstance *&inst, const QString &instDir);
private:
InstanceFactory();
static InstanceFactory loader;
};

View File

@ -0,0 +1,67 @@
/* Copyright 2013 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 "InstanceVersion.h"
#include "lists/InstVersionList.h"
InstVersion::InstVersion(const QString &descriptor,
const QString &name,
qint64 timestamp,
InstVersionList *parent) :
QObject(parent), m_descriptor(descriptor), m_name(name), m_timestamp(timestamp)
{
}
InstVersion::InstVersion(const InstVersion &other, QObject *parent) :
QObject(parent ? parent : other.parent()),
m_descriptor(other.descriptor()), m_name(other.name()), m_timestamp(other.timestamp())
{
}
InstVersionList *InstVersion::versionList() const
{
// Parent should *always* be either an InstVersionList or NULL.
if (!parent() || !parent()->inherits("InstVersionList"))
return NULL;
else
return (InstVersionList *)parent();
}
bool InstVersion::isLessThan(const InstVersion &other) const
{
return timestamp() < other.timestamp();
}
bool InstVersion::isGreaterThan(const InstVersion &other) const
{
return timestamp() > other.timestamp();
}
QString InstVersion::descriptor() const
{
return m_descriptor;
}
QString InstVersion::name() const
{
return m_name;
}
qint64 InstVersion::timestamp() const
{
return m_timestamp;
}

121
backend/InstanceVersion.h Normal file
View File

@ -0,0 +1,121 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include "libmmc_config.h"
class InstVersionList;
/*!
* An abstract base class for instance versions.
* InstVersions hold information about versions such as their names, identifiers,
* types, etc.
*/
class LIBMULTIMC_EXPORT InstVersion : public QObject
{
Q_OBJECT
/*!
* A string used to identify this version in config files.
* This should be unique within the version list or shenanigans will occur.
*/
Q_PROPERTY(QString descriptor READ descriptor CONSTANT)
/*!
* The name of this version as it is displayed to the user.
* For example: "1.5.1"
*/
Q_PROPERTY(QString name READ name)
/*!
* The name of this version's type as it is displayed to the user.
* For example: "Latest Version", "Snapshot", or "MCNostalgia"
*/
Q_PROPERTY(QString typeName READ typeName)
/*!
* Gets the version's timestamp.
* This is primarily used for sorting versions in a list.
*/
Q_PROPERTY(qint64 timestamp READ timestamp)
public:
/*!
* \brief Constructs a new InstVersion with the given parent.
* The parent *must* be the InstVersionList that contains this InstVersion.
* The InstVersion will be added to the list immediately after being created.
*/
explicit InstVersion(const QString &descriptor,
const QString &name,
qint64 timestamp,
InstVersionList *parent = 0);
/*!
* Copy constructor.
* If the 'parent' parameter is not NULL, sets this version's parent to the
* specified object, rather than setting it to the same parent as the version
* we're copying from.
* \param other The version to copy.
* \param parent If not NULL, will be set as the new version object's parent.
*/
InstVersion(const InstVersion &other, QObject *parent = 0);
virtual QString descriptor() const;
virtual QString name() const;
virtual QString typeName() const = 0;
virtual qint64 timestamp() const;
virtual InstVersionList *versionList() const;
/*!
* Creates a copy of this version with a different parent.
* \param newParent The parent QObject of the copy.
* \return A new, identical copy of this version with the given parent set.
*/
virtual InstVersion *copyVersion(InstVersionList *newParent) const = 0;
/*!
* Checks if this version is less (older) than the given version.
* \param other The version to compare this one to.
* \return True if this version is older than the given version.
*/
virtual bool isLessThan(const InstVersion &other) const;
/*!
* Checks if this version is greater (newer) than the given version.
* \param other The version to compare this one to.
* \return True if this version is newer than the given version.
*/
virtual bool isGreaterThan(const InstVersion &other) const;
/*!
* \sa shouldSortBefore()
*/
virtual bool operator<(const InstVersion &rhs) { return isLessThan(rhs); }
/*!
* \sa shouldSortAfter()
*/
virtual bool operator>(const InstVersion &rhs) { return isGreaterThan(rhs); }
protected:
QString m_descriptor;
QString m_name;
qint64 m_timestamp;
};

View File

1
backend/LegacyInstance.h Normal file
View File

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

View File

@ -0,0 +1,229 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* 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 "MinecraftProcess.h"
#include <QDataStream>
#include <QFile>
#include <QDir>
//#include <QImage>
#include <QProcessEnvironment>
#include "BaseInstance.h"
#include "osutils.h"
#include "pathutils.h"
#include "cmdutils.h"
#define LAUNCHER_FILE "MultiMCLauncher.jar"
#define IBUS "@im=ibus"
// prepare tools
inline void MinecraftProcess::extractIcon(BaseInstance *inst, QString destination)
{
// QImage(":/icons/instances/" + inst->iconKey()).save(destination);
}
inline void MinecraftProcess::extractLauncher(QString destination)
{
QFile(":/launcher/launcher.jar").copy(destination);
}
void MinecraftProcess::prepare(BaseInstance *inst)
{
extractLauncher(PathCombine(inst->minecraftDir(), LAUNCHER_FILE));
extractIcon(inst, PathCombine(inst->minecraftDir(), "icon.png"));
}
// constructor
MinecraftProcess::MinecraftProcess(BaseInstance *inst, QString user, QString session) :
m_instance(inst), m_user(user), m_session(session)
{
connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finish(int, QProcess::ExitStatus)));
// prepare the process environment
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
#ifdef LINUX
// Strip IBus
if (env.value("XMODIFIERS").contains(IBUS))
env.insert("XMODIFIERS", env.value("XMODIFIERS").replace(IBUS, ""));
#endif
// export some infos
env.insert("INST_NAME", inst->name());
env.insert("INST_ID", inst->id());
env.insert("INST_DIR", QDir(inst->rootDir()).absolutePath());
this->setProcessEnvironment(env);
m_prepostlaunchprocess.setProcessEnvironment(env);
// set the cwd
QDir mcDir(inst->minecraftDir());
this->setWorkingDirectory(mcDir.absolutePath());
m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath());
// std channels
connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr()));
connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut()));
}
// console window
void MinecraftProcess::on_stdErr()
{
QByteArray data = readAllStandardError();
QString str = m_err_leftover + QString::fromLocal8Bit(data);
m_err_leftover.clear();
QStringList lines = str.split("\n");
bool complete = str.endsWith("\n");
for(int i = 0; i < lines.size() - 1; i++)
{
QString & line = lines[i];
MessageLevel::Enum level = MessageLevel::Error;
if(line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || line.contains("[FINEST]") )
level = MessageLevel::Message;
if(line.contains("[SEVERE]") || line.contains("[WARNING]") || line.contains("[STDERR]"))
level = MessageLevel::Error;
emit log(lines[i].toLocal8Bit(), level);
}
if(!complete)
m_err_leftover = lines.last();
}
void MinecraftProcess::on_stdOut()
{
QByteArray data = readAllStandardOutput();
QString str = m_out_leftover + QString::fromLocal8Bit(data);
m_out_leftover.clear();
QStringList lines = str.split("\n");
bool complete = str.endsWith("\n");
for(int i = 0; i < lines.size() - 1; i++)
{
QString & line = lines[i];
emit log(lines[i].toLocal8Bit(), MessageLevel::Message);
}
if(!complete)
m_out_leftover = lines.last();
}
// exit handler
void MinecraftProcess::finish(int code, ExitStatus status)
{
if (status != NormalExit)
{
//TODO: error handling
}
emit log("Minecraft exited.");
m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code));
// run post-exit
if (!m_instance->settings().get("PostExitCommand").toString().isEmpty())
{
m_prepostlaunchprocess.start(m_instance->settings().get("PostExitCommand").toString());
m_prepostlaunchprocess.waitForFinished();
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
//TODO: error handling
}
}
// if (m_console != nullptr)
// m_console->setMayClose(true);
emit ended();
}
void MinecraftProcess::launch()
{
if (!m_instance->settings().get("PreLaunchCommand").toString().isEmpty())
{
m_prepostlaunchprocess.start(m_instance->settings().get("PreLaunchCommand").toString());
m_prepostlaunchprocess.waitForFinished();
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
//TODO: error handling
return;
}
}
m_instance->setLastLaunch();
prepare(m_instance);
genArgs();
emit log(QString("Minecraft folder is: '%1'").arg(workingDirectory()));
QString JavaPath = m_instance->settings().get("JavaPath").toString();
emit log(QString("Java path: '%1'").arg(JavaPath));
emit log(QString("Arguments: '%1'").arg(m_arguments.join("' '")));
start(JavaPath, m_arguments);
if (!waitForStarted())
{
emit log("Could not launch minecraft!");
return;
//TODO: error handling
}
// if(m_console != nullptr)
// m_console->setMayClose(false);
}
void MinecraftProcess::genArgs()
{
// start fresh
m_arguments.clear();
// window size
QString windowSize;
if (m_instance->settings().get("LaunchMaximized").toBool())
windowSize = "max";
else
windowSize = QString("%1x%2").
arg(m_instance->settings().get("MinecraftWinWidth").toInt()).
arg(m_instance->settings().get("MinecraftWinHeight").toInt());
// window title
QString windowTitle;
windowTitle.append("MultiMC: ").append(m_instance->name());
// Java arguments
m_arguments.append(Util::Commandline::splitArgs(m_instance->settings().get("JvmArgs").toString()));
#ifdef OSX
// OSX dock icon and name
m_arguments << "-Xdock:icon=icon.png";
m_arguments << QString("-Xdock:name=\"%1\"").arg(windowTitle);
#endif
// lwjgl
QString lwjgl = QDir(globalSettings->get("LWJGLDir").toString() + "/" +
m_instance->lwjglVersion()).absolutePath();
// launcher arguments
m_arguments << QString("-Xms%1m").arg(m_instance->settings().get("MinMemAlloc").toInt());
m_arguments << QString("-Xmx%1m").arg(m_instance->settings().get("MaxMemAlloc").toInt());
m_arguments << "-jar" << LAUNCHER_FILE;
m_arguments << m_user;
m_arguments << m_session;
m_arguments << windowTitle;
m_arguments << windowSize;
m_arguments << lwjgl;
}

111
backend/MinecraftProcess.h Normal file
View File

@ -0,0 +1,111 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QProcess>
#include "BaseInstance.h"
#include "libmmc_config.h"
/**
* @brief the MessageLevel Enum
* defines what level a message is
*/
namespace MessageLevel {
enum LIBMULTIMC_EXPORT Enum {
MultiMC, /**< MultiMC Messages */
Debug, /**< Debug Messages */
Info, /**< Info Messages */
Message, /**< Standard Messages */
Warning, /**< Warnings */
Error, /**< Errors */
Fatal /**< Fatal Errors */
};
}
/**
* @file data/minecraftprocess.h
* @brief The MinecraftProcess class
*/
class LIBMULTIMC_EXPORT MinecraftProcess : public QProcess
{
Q_OBJECT
public:
/**
* @brief MinecraftProcess constructor
* @param inst the Instance pointer to launch
* @param user the minecraft username
* @param session the minecraft session id
* @param console the instance console window
*/
MinecraftProcess(BaseInstance *inst, QString user, QString session);
/**
* @brief launch minecraft
*/
void launch();
/**
* @brief extract the instance icon
* @param inst the instance
* @param destination the destination path
*/
static inline void extractIcon(BaseInstance *inst, QString destination);
/**
* @brief extract the MultiMC launcher.jar
* @param destination the destination path
*/
static inline void extractLauncher(QString destination);
/**
* @brief prepare the launch by extracting icon and launcher
* @param inst the instance
*/
static void prepare(BaseInstance *inst);
signals:
/**
* @brief emitted when mc has finished and the PostLaunchCommand was run
*/
void ended();
/**
* @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);
protected:
BaseInstance *m_instance;
QString m_user;
QString m_session;
QString m_err_leftover;
QString m_out_leftover;
QProcess m_prepostlaunchprocess;
QStringList m_arguments;
void genArgs();
protected slots:
void finish(int, QProcess::ExitStatus status);
void on_stdErr();
void on_stdOut();
};

View File

@ -0,0 +1,104 @@
/* Copyright 2013 Andrew Okin
*
* 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 "MinecraftVersion.h"
MinecraftVersion::MinecraftVersion(QString descriptor,
QString name,
qint64 timestamp,
QString dlUrl,
QString etag,
InstVersionList *parent) :
InstVersion(descriptor, name, timestamp, parent), m_dlUrl(dlUrl), m_etag(etag)
{
}
QString MinecraftVersion::descriptor() const
{
return m_descriptor;
}
QString MinecraftVersion::name() const
{
return m_name;
}
QString MinecraftVersion::typeName() const
{
switch (versionType())
{
case OldSnapshot:
return "Snapshot";
case Stable:
return "Stable";
case CurrentStable:
return "Current Stable";
case Snapshot:
return "Snapshot";
case MCNostalgia:
return "MCNostalgia";
default:
return QString("Unknown Type %1").arg(versionType());
}
}
qint64 MinecraftVersion::timestamp() const
{
return m_timestamp;
}
MinecraftVersion::VersionType MinecraftVersion::versionType() const
{
return m_type;
}
void MinecraftVersion::setVersionType(MinecraftVersion::VersionType typeName)
{
m_type = typeName;
}
QString MinecraftVersion::downloadURL() const
{
return m_dlUrl;
}
QString MinecraftVersion::etag() const
{
return m_etag;
}
MinecraftVersion::VersionSource MinecraftVersion::versionSource() const
{
return m_versionSource;
};
void MinecraftVersion::setVersionSource(VersionSource launcherVersion)
{
m_versionSource = launcherVersion;
}
InstVersion *MinecraftVersion::copyVersion(InstVersionList *newParent) const
{
MinecraftVersion *version = new MinecraftVersion(
descriptor(), name(), timestamp(), downloadURL(), etag(), newParent);
version->setVersionType(versionType());
version->setVersionSource(VersionSource());
return version;
}

View File

@ -0,0 +1,80 @@
/* Copyright 2013 Andrew Okin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "libmmc_config.h"
#include "InstanceVersion.h"
class LIBMULTIMC_EXPORT MinecraftVersion : public InstVersion
{
Q_OBJECT
public:
explicit MinecraftVersion(QString descriptor,
QString name,
qint64 timestamp,
QString dlUrl,
QString etag,
InstVersionList *parent = 0);
static InstVersion *mcnVersion(QString rawName, QString niceName);
enum VersionType
{
OldSnapshot,
Stable,
CurrentStable,
Snapshot,
MCNostalgia
};
enum VersionSource
{
Unknown = -1,
Legacy = 0, // the legacy launcher that's been around since ... forever
Launcher16 = 1, // current launcher as of 26/06/2013
};
virtual QString descriptor() const;
virtual QString name() const;
virtual QString typeName() const;
virtual qint64 timestamp() const;
virtual VersionType versionType() const;
virtual void setVersionType(VersionType typeName);
virtual VersionSource versionSource() const;
virtual void setVersionSource(VersionSource launcherVersion);
virtual QString downloadURL() const;
virtual QString etag() const;
virtual InstVersion *copyVersion(InstVersionList *newParent) const;
private:
/// The URL that this version will be downloaded from. maybe.
QString m_dlUrl;
/// ETag/MD5 Used to verify the integrity of the downloaded minecraft.jar.
QString m_etag;
/// This version's type. Used internally to identify what kind of version this is.
VersionType m_type;
/// Whete to get the full version info (or, where did we get this version from originally)
VersionSource m_versionSource;
};

View File

1
backend/OneSixInstance.h Normal file
View File

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

91
backend/OneSixVersion.cpp Normal file
View File

@ -0,0 +1,91 @@
#include "OneSixVersion.h"
RuleAction RuleAction_fromString(QString name)
{
if(name == "allow")
return Allow;
if(name == "disallow")
return Disallow;
return Defer;
}
OpSys OpSys_fromString(QString name)
{
if(name == "linux")
return Os_Linux;
if(name == "windows")
return Os_Windows;
if(name == "osx")
return Os_OSX;
return Os_Other;
}
void Library::finalize()
{
QStringList parts = m_name.split ( ':' );
QString relative = parts[0];
relative.replace ( '.','/' );
relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2];
if ( !m_is_native )
relative += ".jar";
else
{
if ( m_native_suffixes.contains ( currentSystem ) )
{
relative += "-" + m_native_suffixes[currentSystem] + ".jar";
}
else
{
// really, bad.
relative += ".jar";
}
}
m_storage_path = relative;
m_download_path = m_base_url + relative;
if ( m_rules.empty() )
{
m_is_active = true;
}
else
{
RuleAction result = Disallow;
for ( auto rule: m_rules )
{
RuleAction temp = rule->apply ( this );
if ( temp != Defer )
result = temp;
}
m_is_active = ( result == Allow );
}
if ( m_is_native )
{
m_is_active = m_is_active && m_native_suffixes.contains ( currentSystem );
}
}
QList<QSharedPointer<Library> > FullVersion::getActiveNormalLibs()
{
QList<QSharedPointer<Library> > output;
for ( auto lib: libraries )
{
if (lib->getIsActive() && !lib->getIsNative())
{
output.append(lib);
}
}
return output;
}
QList<QSharedPointer<Library> > FullVersion::getActiveNativeLibs()
{
QList<QSharedPointer<Library> > output;
for ( auto lib: libraries )
{
if (lib->getIsActive() && lib->getIsNative())
{
output.append(lib);
}
}
return output;
}

265
backend/OneSixVersion.h Normal file
View File

@ -0,0 +1,265 @@
#pragma once
#include <QtCore>
class Library;
enum OpSys
{
Os_Windows,
Os_Linux,
Os_OSX,
Os_Other
};
OpSys OpSys_fromString(QString);
#ifdef Q_OS_MAC
#define currentSystem Os_OSX
#endif
#ifdef Q_OS_LINUX
#define currentSystem Os_Linux
#endif
#ifdef Q_OS_WIN32
#define currentSystem Os_Windows
#endif
#ifndef currentSystem
#define currentSystem Os_Other
#endif
enum RuleAction
{
Allow,
Disallow,
Defer
};
RuleAction RuleAction_fromString(QString);
class Rule
{
protected:
RuleAction m_result;
virtual bool applies(Library * parent) = 0;
public:
Rule(RuleAction result)
:m_result(result) {}
virtual ~Rule(){};
RuleAction apply(Library * parent)
{
if(applies(parent))
return m_result;
else
return Defer;
};
};
class OsRule : public Rule
{
private:
// the OS
OpSys m_system;
// the OS version regexp
QString m_version_regexp;
protected:
virtual bool applies ( Library* )
{
return (m_system == currentSystem);
}
OsRule(RuleAction result, OpSys system, QString version_regexp)
: Rule(result), m_system(system), m_version_regexp(version_regexp) {}
public:
static QSharedPointer<OsRule> create(RuleAction result, OpSys system, QString version_regexp)
{
return QSharedPointer<OsRule> (new OsRule(result, system, version_regexp));
}
};
class ImplicitRule : public Rule
{
protected:
virtual bool applies ( Library* )
{
return true;
}
ImplicitRule(RuleAction result)
: Rule(result) {}
public:
static QSharedPointer<ImplicitRule> create(RuleAction result)
{
return QSharedPointer<ImplicitRule> (new ImplicitRule(result));
}
};
class Library
{
private:
// basic values used internally (so far)
QString m_name;
QString m_base_url;
QList<QSharedPointer<Rule> > m_rules;
// derived values used for real things
/// where to store the lib locally
QString m_storage_path;
/// where to download the lib from
QString m_download_path;
/// is this lib actuall active on the current OS?
bool m_is_active;
// native lib?
bool m_is_native;
QMap<OpSys, QString> m_native_suffixes;
public:
QStringList extract_excludes;
public:
/// Constructor
Library(QString name)
{
m_is_native = false;
m_is_native = false;
m_name = name;
m_base_url = "https://s3.amazonaws.com/Minecraft.Download/libraries/";
}
/**
* finalize the library, processing the input values into derived values and state
*
* This SHALL be called after all the values are parsed or after any further change.
*/
void finalize();
/**
* Set the library composite name
*/
void setName(QString name)
{
m_name = name;
}
/**
* Set the url base for downloads
*/
void setBaseUrl(QString base_url)
{
m_base_url = base_url;
}
/**
* Call this to mark the library as 'native' (it's a zip archive with DLLs)
*/
void setIsNative()
{
m_is_native = true;
}
/**
* Attach a name suffix to the specified OS native
*/
void addNative(OpSys os, QString suffix)
{
m_is_native = true;
m_native_suffixes[os] = suffix;
}
/**
* Set the load rules
*/
void setRules(QList<QSharedPointer<Rule> > rules)
{
m_rules = rules;
}
/**
* Returns true if the library should be loaded (or extracted, in case of natives)
*/
bool getIsActive()
{
return m_is_active;
}
/**
* Returns true if the library is native
*/
bool getIsNative()
{
return m_is_native;
}
};
class FullVersion
{
public:
/// the ID - determines which jar to use! ACTUALLY IMPORTANT!
QString id;
/// Last updated time - as a string
QString time;
/// Release time - as a string
QString releaseTime;
/// Release type - "release" or "snapshot"
QString type;
/**
* DEPRECATED: Old versions of the new vanilla launcher used this
* ex: "username_session_version"
*/
QString processArguments;
/**
* 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;
/**
* the minimum launcher version required by this version ... current is 4 (at point of writing)
*/
int minimumLauncherVersion;
/**
* The main class to load first
*/
QString mainClass;
/// the list of libs - both active and inactive, native and java
QList<QSharedPointer<Library> > libraries;
/**
* is this actually a legacy version? if so, none of the other stuff here will be ever used.
* added by FullVersionFactory
*/
bool isLegacy;
/*
FIXME: add support for those rules here? Looks like a pile of quick hacks to me though.
"rules": [
{
"action": "allow"
},
{
"action": "disallow",
"os": {
"name": "osx",
"version": "^10\\.5\\.\\d$"
}
}
],
"incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!"
}
*/
// QList<Rule> rules;
public:
FullVersion()
{
minimumLauncherVersion = 0xDEADBEEF;
isLegacy = false;
}
QList<QSharedPointer<Library> > getActiveNormalLibs();
QList<QSharedPointer<Library> > getActiveNativeLibs();
};

194
backend/VersionFactory.cpp Normal file
View File

@ -0,0 +1,194 @@
#include "VersionFactory.h"
#include "OneSixVersion.h"
// Library rules (if any)
QList<QSharedPointer<Rule> > FullVersionFactory::parse4rules(QJsonObject & baseObj)
{
QList<QSharedPointer<Rule> > rules;
auto rulesVal = baseObj.value("rules");
if(rulesVal.isArray())
{
QJsonArray ruleList = rulesVal.toArray();
for(auto ruleVal : ruleList)
{
QSharedPointer<Rule> rule;
if(!ruleVal.isObject())
continue;
auto ruleObj = ruleVal.toObject();
auto actionVal = ruleObj.value("action");
if(!actionVal.isString())
continue;
auto action = RuleAction_fromString(actionVal.toString());
if(action == Defer)
continue;
auto osVal = ruleObj.value("os");
if(!osVal.isObject())
{
// add a new implicit action rule
rules.append(ImplicitRule::create(action));
}
else
{
auto osObj = osVal.toObject();
auto osNameVal = osObj.value("name");
if(!osNameVal.isString())
continue;
OpSys requiredOs = OpSys_fromString(osNameVal.toString());
QString versionRegex = osObj.value("version").toString();
// add a new OS rule
rules.append(OsRule::create(action, requiredOs, versionRegex));
}
}
}
return rules;
}
QSharedPointer<FullVersion> FullVersionFactory::parse4(QJsonObject root, QSharedPointer<FullVersion> fullVersion)
{
fullVersion->id = root.value("id").toString();
// if it's on our legacy list, it's legacy
if(legacyWhitelist.contains(fullVersion->id))
fullVersion->isLegacy = true;
fullVersion->mainClass = root.value("mainClass").toString();
auto procArgsValue = root.value("processArguments");
if(procArgsValue.isString())
{
fullVersion->processArguments = procArgsValue.toString();
QString toCompare = fullVersion->processArguments.toLower();
if(toCompare == "legacy")
{
fullVersion->minecraftArguments = " ${auth_player_name} ${auth_session}";
fullVersion->isLegacy = true;
}
else if(toCompare == "username_session")
{
fullVersion->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}";
}
else if(toCompare == "username_session_version")
{
fullVersion->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}";
}
}
auto minecraftArgsValue = root.value("minecraftArguments");
if(minecraftArgsValue.isString())
{
fullVersion->minecraftArguments = minecraftArgsValue.toString();
}
fullVersion->releaseTime = root.value("releaseTime").toString();
fullVersion->time = root.value("time").toString();
// Iterate through the list, if it's a list.
auto librariesValue = root.value("libraries");
if(!librariesValue.isArray())
return fullVersion;
QJsonArray libList = root.value("libraries").toArray();
for (auto libVal : libList)
{
if (!libVal.isObject())
{
continue;
}
QJsonObject libObj = libVal.toObject();
// Library name
auto nameVal = libObj.value("name");
if(!nameVal.isString())
continue;
QSharedPointer<Library> library(new Library(nameVal.toString()));
// Extract excludes (if any)
auto extractVal = libObj.value("extract");
if(extractVal.isObject())
{
QStringList excludes;
auto extractObj = extractVal.toObject();
auto excludesVal = extractObj.value("exclude");
if(!excludesVal.isArray())
goto SKIP_EXTRACTS;
auto excludesList = excludesVal.toArray();
for(auto excludeVal : excludesList)
{
if(excludeVal.isString())
excludes.append(excludeVal.toString());
}
library->extract_excludes = excludes;
}
SKIP_EXTRACTS:
auto nativesVal = libObj.value("natives");
if(nativesVal.isObject())
{
library->setIsNative();
auto nativesObj = nativesVal.toObject();
auto iter = nativesObj.begin();
while(iter != nativesObj.end())
{
auto osType = OpSys_fromString(iter.key());
if(osType == Os_Other)
continue;
if(!iter.value().isString())
continue;
library->addNative(osType, iter.value().toString());
iter++;
}
}
library->setRules(parse4rules(libObj));
library->finalize();
fullVersion->libraries.append(library);
}
return fullVersion;
}
QSharedPointer<FullVersion> FullVersionFactory::parse(QByteArray data)
{
QSharedPointer<FullVersion> readVersion(new FullVersion());
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
error_string = QString( "Error reading version file :") + " " + jsonError.errorString();
m_error = FullVersionFactory::ParseError;
return QSharedPointer<FullVersion>();
}
if(!jsonDoc.isObject())
{
error_string = "Error reading version file.";
m_error = FullVersionFactory::ParseError;
return QSharedPointer<FullVersion>();
}
QJsonObject root = jsonDoc.object();
readVersion->minimumLauncherVersion = root.value("minimumLauncherVersion").toDouble();
switch(readVersion->minimumLauncherVersion)
{
case 1:
case 2:
case 3:
case 4:
return parse4(root, readVersion);
// ADD MORE HERE :D
default:
error_string = "Version file was for an unrecognized launcher version. RIP";
m_error = FullVersionFactory::UnsupportedVersion;
return QSharedPointer<FullVersion>();
}
}
FullVersionFactory::FullVersionFactory()
{
m_error = FullVersionFactory::AllOK;
legacyWhitelist.append("1.5.1");
legacyWhitelist.append("1.5.2");
}

25
backend/VersionFactory.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <QtCore>
struct FullVersion;
class Rule;
class FullVersionFactory
{
public:
enum Error
{
AllOK, // all parsed OK
ParseError, // the file was corrupted somehow
UnsupportedVersion // the file was meant for a launcher version we don't support (yet)
} m_error;
QString error_string;
public:
FullVersionFactory();
QSharedPointer<FullVersion> parse(QByteArray data);
private:
QSharedPointer<FullVersion> parse4(QJsonObject root, QSharedPointer<FullVersion> product);
QList<QSharedPointer<Rule> > parse4rules(QJsonObject & baseObj);
QStringList legacyWhitelist;
};

24
backend/libmmc_config.h Normal file
View File

@ -0,0 +1,24 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QtCore/QtGlobal>
#ifdef LIBMULTIMC_LIBRARY
# define LIBMULTIMC_EXPORT Q_DECL_EXPORT
#else
# define LIBMULTIMC_EXPORT Q_DECL_IMPORT
#endif

View File

@ -0,0 +1,129 @@
/* Copyright 2013 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 "InstVersionList.h"
#include "InstanceVersion.h"
InstVersionList::InstVersionList(QObject *parent) :
QAbstractListModel(parent)
{
}
const InstVersion *InstVersionList::findVersion(const QString &descriptor)
{
for (int i = 0; i < count(); i++)
{
if (at(i)->descriptor() == descriptor)
return at(i);
}
return NULL;
}
const InstVersion *InstVersionList::getLatestStable() const
{
if (count() <= 0)
return NULL;
else
return at(0);
}
QVariant InstVersionList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
const InstVersion *version = at(index.row());
switch (role)
{
case Qt::DisplayRole:
switch (index.column())
{
case NameColumn:
return version->name();
case TypeColumn:
return version->typeName();
case TimeColumn:
return version->timestamp();
default:
return QVariant();
}
case Qt::ToolTipRole:
return version->descriptor();
case VersionPointerRole:
return qVariantFromValue((void *) version);
default:
return QVariant();
}
}
QVariant InstVersionList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case NameColumn:
return "Name";
case TypeColumn:
return "Type";
case TimeColumn:
return "Time";
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section)
{
case NameColumn:
return "The name of the version.";
case TypeColumn:
return "The version's type.";
default:
return QVariant();
}
default:
return QVariant();
}
}
int InstVersionList::rowCount(const QModelIndex &parent) const
{
// Return count
return count();
}
int InstVersionList::columnCount(const QModelIndex &parent) const
{
return 2;
}

View File

@ -0,0 +1,121 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QVariant>
#include <QAbstractListModel>
#include "libmmc_config.h"
class InstVersion;
class Task;
/*!
* \brief Class that each instance type's version list derives from.
* Version lists are the lists that keep track of the available game versions
* for that instance. This list will not be loaded on startup. It will be loaded
* when the list's load function is called. Before using the version list, you
* should check to see if it has been loaded yet and if not, load the list.
*
* Note that this class also inherits from QAbstractListModel. Methods from that
* class determine how this version list shows up in a list view. Said methods
* all have a default implementation, but they can be overridden by plugins to
* change the behavior of the list.
*/
class LIBMULTIMC_EXPORT InstVersionList : public QAbstractListModel
{
Q_OBJECT
public:
enum ModelRoles
{
VersionPointerRole = 0x34B1CB48
};
enum VListColumns
{
// First column - Name
NameColumn = 0,
// Second column - Type
TypeColumn,
// Third column - Timestamp
TimeColumn
};
explicit InstVersionList(QObject *parent = 0);
/*!
* \brief Gets a task that will reload the version islt.
* Simply execute the task to load the list.
* The task returned by this function should reset the model when it's done.
* \return A pointer to a task that reloads the version list.
*/
virtual Task *getLoadTask() = 0;
//! Checks whether or not the list is loaded. If this returns false, the list should be loaded.
virtual bool isLoaded() = 0;
//! Gets the version at the given index.
virtual const InstVersion *at(int i) const = 0;
//! Returns the number of versions in the list.
virtual int count() const = 0;
//////// List Model Functions ////////
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
/*!
* \brief Finds a version by its descriptor.
* \param The descriptor of the version to find.
* \return A const pointer to the version with the given descriptor. NULL if
* one doesn't exist.
*/
virtual const InstVersion *findVersion(const QString &descriptor);
/*!
* \brief Gets the latest stable version of this instance type.
* This is the version that will be selected by default.
* By default, this is simply the first version in the list.
*/
virtual const InstVersion *getLatestStable() const;
/*!
* Sorts the version list.
*/
virtual void sort() = 0;
protected slots:
/*!
* Updates this list with the given list of versions.
* This is done by copying each version in the given list and inserting it
* into this one.
* We need to do this so that we can set the parents of the versions are set to this
* version list. This can't be done in the load task, because the versions the load
* task creates are on the load task's thread and Qt won't allow their parents
* to be set to something created on another thread.
* To get around that problem, we invoke this method on the GUI thread, which
* then copies the versions and sets their parents correctly.
* \param versions List of versions whose parents should be set.
*/
virtual void updateListData(QList<InstVersion *> versions) = 0;
};

View File

@ -0,0 +1,232 @@
/* Copyright 2013 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 <QDir>
#include <QFile>
#include <QDirIterator>
#include <QThread>
#include <QTextStream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include "lists/InstanceList.h"
#include "BaseInstance.h"
#include "InstanceFactory.h"
#include "pathutils.h"
const static int GROUP_FILE_FORMAT_VERSION = 1;
InstanceList::InstanceList(const QString &instDir, QObject *parent) :
QObject(parent), m_instDir("instances")
{
}
void InstanceList::loadGroupList(QMap<QString, QString> & groupMap)
{
QString groupFileName = m_instDir + "/instgroups.json";
// if there's no group file, fail
if(!QFileInfo(groupFileName).exists())
return;
QFile groupFile(groupFileName);
// if you can't open the file, fail
if (!groupFile.open(QIODevice::ReadOnly))
{
// An error occurred. Ignore it.
qDebug("Failed to read instance group file.");
return;
}
QTextStream in(&groupFile);
QString jsonStr = in.readAll();
groupFile.close();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &error);
// if the json was bad, fail
if (error.error != QJsonParseError::NoError)
{
qWarning(QString("Failed to parse instance group file: %1 at offset %2").
arg(error.errorString(), QString::number(error.offset)).toUtf8());
return;
}
// if the root of the json wasn't an object, fail
if (!jsonDoc.isObject())
{
qWarning("Invalid group file. Root entry should be an object.");
return;
}
QJsonObject rootObj = jsonDoc.object();
// Make sure the format version matches, otherwise fail.
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
return;
// Get the groups. if it's not an object, fail
if (!rootObj.value("groups").isObject())
{
qWarning("Invalid group list JSON: 'groups' should be an object.");
return;
}
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
{
QString groupName = iter.key();
// If not an object, complain and skip to the next one.
if (!iter.value().isObject())
{
qWarning(QString("Group '%1' in the group list should "
"be an object.").arg(groupName).toUtf8());
continue;
}
QJsonObject groupObj = iter.value().toObject();
if (!groupObj.value("instances").isArray())
{
qWarning(QString("Group '%1' in the group list is invalid. "
"It should contain an array "
"called 'instances'.").arg(groupName).toUtf8());
continue;
}
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (QJsonArray::iterator iter2 = instancesArray.begin();
iter2 != instancesArray.end(); iter2++)
{
groupMap[(*iter2).toString()] = groupName;
}
}
}
InstanceList::InstListError InstanceList::loadList()
{
// load the instance groups
QMap<QString, QString> groupMap;
loadGroupList(groupMap);
m_instances.clear();
QDir dir(m_instDir);
QDirIterator iter(dir);
while (iter.hasNext())
{
QString subDir = iter.next();
if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists())
continue;
BaseInstance *instPtr = NULL;
auto &loader = InstanceFactory::get();
auto error = loader.loadInstance(instPtr, subDir);
switch(error)
{
case InstanceFactory::NoLoadError:
break;
case InstanceFactory::NotAnInstance:
break;
}
if (error != InstanceFactory::NoLoadError &&
error != InstanceFactory::NotAnInstance)
{
QString errorMsg = QString("Failed to load instance %1: ").
arg(QFileInfo(subDir).baseName()).toUtf8();
switch (error)
{
default:
errorMsg += QString("Unknown instance loader error %1").
arg(error);
break;
}
qDebug(errorMsg.toUtf8());
}
else if (!instPtr)
{
qDebug(QString("Error loading instance %1. Instance loader returned null.").
arg(QFileInfo(subDir).baseName()).toUtf8());
}
else
{
QSharedPointer<BaseInstance> inst(instPtr);
auto iter = groupMap.find(inst->id());
if(iter != groupMap.end())
{
inst->setGroup((*iter));
}
qDebug(QString("Loaded instance %1").arg(inst->name()).toUtf8());
inst->setParent(this);
m_instances.append(inst);
connect(instPtr, SIGNAL(propertiesChanged(BaseInstance*)),this, SLOT(propertiesChanged(BaseInstance*)));
}
}
emit invalidated();
return NoError;
}
/// Clear all instances. Triggers notifications.
void InstanceList::clear()
{
m_instances.clear();
emit invalidated();
};
/// Add an instance. Triggers notifications, returns the new index
int InstanceList::add(InstancePtr t)
{
m_instances.append(t);
emit instanceAdded(count() - 1);
return count() - 1;
}
InstancePtr InstanceList::getInstanceById(QString instId)
{
QListIterator<InstancePtr> iter(m_instances);
InstancePtr inst;
while(iter.hasNext())
{
inst = iter.next();
if (inst->id() == instId)
break;
}
if (inst->id() != instId)
return InstancePtr();
else
return iter.peekPrevious();
}
void InstanceList::propertiesChanged(BaseInstance * inst)
{
for(int i = 0; i < m_instances.count(); i++)
{
if(inst == m_instances[i].data())
{
emit instanceChanged(i);
break;
}
}
}

View File

@ -0,0 +1,92 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QSharedPointer>
#include "BaseInstance.h"
#include "libmmc_config.h"
class BaseInstance;
class LIBMULTIMC_EXPORT InstanceList : public QObject
{
Q_OBJECT
private:
/*!
* \brief Get the instance groups
*/
void loadGroupList(QMap<QString, QString> & groupList);
public:
explicit InstanceList(const QString &instDir, QObject *parent = 0);
/*!
* \brief Error codes returned by functions in the InstanceList class.
* NoError Indicates that no error occurred.
* UnknownError indicates that an unspecified error occurred.
*/
enum InstListError
{
NoError = 0,
UnknownError
};
QString instDir() const { return m_instDir; }
/*!
* \brief Loads the instance list. Triggers notifications.
*/
InstListError loadList();
/*!
* \brief Get the instance at index
*/
InstancePtr at(int i) const
{
return m_instances.at(i);
};
/*!
* \brief Get the count of loaded instances
*/
int count() const
{
return m_instances.count();
};
/// Clear all instances. Triggers notifications.
void clear();
/// Add an instance. Triggers notifications, returns the new index
int add(InstancePtr t);
/// Get an instance by ID
InstancePtr getInstanceById (QString id);
signals:
void instanceAdded(int index);
void instanceChanged(int index);
void invalidated();
private slots:
void propertiesChanged(BaseInstance * inst);
protected:
QString m_instDir;
QList< InstancePtr > m_instances;
};

View File

@ -0,0 +1,204 @@
/* Copyright 2013 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 "LwjglVersionList.h"
#include <QtNetwork>
#include <QtXml>
#include <QRegExp>
#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss"
LWJGLVersionList mainVersionList;
LWJGLVersionList &LWJGLVersionList::get()
{
return mainVersionList;
}
LWJGLVersionList::LWJGLVersionList(QObject *parent) :
QAbstractListModel(parent)
{
setLoading(false);
}
QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
const PtrLWJGLVersion version = at(index.row());
switch (role)
{
case Qt::DisplayRole:
return version->name();
case Qt::ToolTipRole:
return version->url().toString();
default:
return QVariant();
}
}
QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
return "Version";
case Qt::ToolTipRole:
return "LWJGL version name.";
default:
return QVariant();
}
}
int LWJGLVersionList::columnCount(const QModelIndex &parent) const
{
return 1;
}
bool LWJGLVersionList::isLoading() const
{
return m_loading;
}
void LWJGLVersionList::loadList()
{
Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)");
setLoading(true);
reply = netMgr.get(QNetworkRequest(QUrl(RSS_URL)));
connect(reply, SIGNAL(finished()), SLOT(netRequestComplete()));
}
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
{
QDomNodeList elementList = parent.elementsByTagName(tagname);
if (elementList.count())
return elementList.at(0).toElement();
else
return QDomElement();
}
void LWJGLVersionList::netRequestComplete()
{
if (reply->error() == QNetworkReply::NoError)
{
QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip");
Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list",
"LWJGL regex is invalid");
QDomDocument doc;
QString xmlErrorMsg;
int errorLine;
if (!doc.setContent(reply->readAll(), false, &xmlErrorMsg, &errorLine))
{
failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg +
" at line " + QString::number(errorLine));
setLoading(false);
return;
}
QDomNodeList items = doc.elementsByTagName("item");
QList<PtrLWJGLVersion> tempList;
for (int i = 0; i < items.length(); i++)
{
Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list",
"XML element isn't an element... wat?");
QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link");
if (linkElement.isNull())
{
qWarning() << "Link element" << i << "in RSS feed doesn't exist! Skipping.";
continue;
}
QString link = linkElement.text();
// Make sure it's a download link.
if (link.endsWith("/download") && link.contains(lwjglRegex))
{
QString name = link.mid(lwjglRegex.indexIn(link));
// Subtract 4 here to remove the .zip file extension.
name = name.left(lwjglRegex.matchedLength() - 4);
QUrl url(link);
if (!url.isValid())
{
qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping.";
continue;
}
tempList.append(LWJGLVersion::Create(name, link));
}
}
beginResetModel();
m_vlist.swap(tempList);
endResetModel();
qDebug("Loaded LWJGL list.");
finished();
}
else
{
failed("Failed to load LWJGL list. Network error: " + reply->errorString());
}
setLoading(false);
reply->deleteLater();
}
const PtrLWJGLVersion LWJGLVersionList::getVersion(const QString &versionName)
{
for (int i = 0; i < count(); i++)
{
if (at(i)->name() == versionName)
return at(i);
}
return PtrLWJGLVersion();
}
void LWJGLVersionList::failed(QString msg)
{
qWarning() << msg;
emit loadListFailed(msg);
}
void LWJGLVersionList::finished()
{
emit loadListFinished();
}
void LWJGLVersionList::setLoading(bool loading)
{
m_loading = loading;
emit loadingStateUpdated(m_loading);
}

View File

@ -0,0 +1,132 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QAbstractListModel>
#include <QSharedPointer>
#include <QUrl>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "libmmc_config.h"
class LWJGLVersion;
typedef QSharedPointer<LWJGLVersion> PtrLWJGLVersion;
class LIBMULTIMC_EXPORT LWJGLVersion : public QObject
{
Q_OBJECT
/*!
* The name of the LWJGL version.
*/
Q_PROPERTY(QString name READ name)
/*!
* The URL for this version of LWJGL.
*/
Q_PROPERTY(QUrl url READ url)
LWJGLVersion(const QString &name, const QUrl &url, QObject *parent = 0) :
QObject(parent), m_name(name), m_url(url) { }
public:
static PtrLWJGLVersion Create(const QString &name, const QUrl &url, QObject *parent = 0)
{
return PtrLWJGLVersion(new LWJGLVersion(name, url, parent));
};
QString name() const { return m_name; }
QUrl url() const { return m_url; }
protected:
QString m_name;
QUrl m_url;
};
class LIBMULTIMC_EXPORT LWJGLVersionList : public QAbstractListModel
{
Q_OBJECT
public:
explicit LWJGLVersionList(QObject *parent = 0);
static LWJGLVersionList &get();
bool isLoaded() { return m_vlist.length() > 0; }
const PtrLWJGLVersion getVersion(const QString &versionName);
PtrLWJGLVersion at(int index) { return m_vlist[index]; }
const PtrLWJGLVersion at(int index) const { return m_vlist[index]; }
int count() const { return m_vlist.length(); }
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual int rowCount(const QModelIndex &parent) const { return count(); }
virtual int columnCount(const QModelIndex &parent) const;
virtual bool isLoading() const;
virtual bool errored() const { return m_errored; }
virtual QString lastErrorMsg() const { return m_lastErrorMsg; }
public slots:
/*!
* Loads the version list.
* This is done asynchronously. On success, the loadListFinished() signal will
* be emitted. The list model will be reset as well, resulting in the modelReset()
* signal being emitted. Note that the model will be reset before loadListFinished() is emitted.
* If loading the list failed, the loadListFailed(QString msg),
* signal will be emitted.
*/
virtual void loadList();
signals:
/*!
* Emitted when the list either starts or finishes loading.
* \param loading Whether or not the list is loading.
*/
void loadingStateUpdated(bool loading);
void loadListFinished();
void loadListFailed(QString msg);
private:
QList<PtrLWJGLVersion> m_vlist;
QNetworkReply *m_netReply;
QNetworkAccessManager netMgr;
QNetworkReply *reply;
bool m_loading;
bool m_errored;
QString m_lastErrorMsg;
void failed(QString msg);
void finished();
void setLoading(bool loading);
private slots:
virtual void netRequestComplete();
};

View File

@ -0,0 +1,528 @@
/* Copyright 2013 Andrew Okin
*
* 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 "MinecraftVersionList.h"
#include <QDebug>
#include <QtXml>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QJsonParseError>
#include <QtAlgorithms>
#include <QtNetwork>
#include "netutils.h"
#define MCVLIST_URLBASE "http://s3.amazonaws.com/Minecraft.Download/versions/"
#define ASSETS_URLBASE "http://assets.minecraft.net/"
#define MCN_URLBASE "http://sonicrules.org/mcnweb.py"
MinecraftVersionList mcVList;
MinecraftVersionList::MinecraftVersionList(QObject *parent) :
InstVersionList(parent)
{
}
Task *MinecraftVersionList::getLoadTask()
{
return new MCVListLoadTask(this);
}
bool MinecraftVersionList::isLoaded()
{
return m_loaded;
}
const InstVersion *MinecraftVersionList::at(int i) const
{
return m_vlist.at(i);
}
int MinecraftVersionList::count() const
{
return m_vlist.count();
}
void MinecraftVersionList::printToStdOut() const
{
qDebug() << "---------------- Version List ----------------";
for (int i = 0; i < m_vlist.count(); i++)
{
MinecraftVersion *version = qobject_cast<MinecraftVersion *>(m_vlist.at(i));
if (!version)
continue;
qDebug() << "Version " << version->name();
qDebug() << "\tDownload: " << version->downloadURL();
qDebug() << "\tTimestamp: " << version->timestamp();
qDebug() << "\tType: " << version->typeName();
qDebug() << "----------------------------------------------";
}
}
bool cmpVersions(const InstVersion *first, const InstVersion *second)
{
return !first->isLessThan(*second);
}
void MinecraftVersionList::sort()
{
beginResetModel();
qSort(m_vlist.begin(), m_vlist.end(), cmpVersions);
endResetModel();
}
InstVersion *MinecraftVersionList::getLatestStable() const
{
for (int i = 0; i < m_vlist.length(); i++)
{
if (((MinecraftVersion *)m_vlist.at(i))->versionType() == MinecraftVersion::CurrentStable)
{
return m_vlist.at(i);
}
}
return NULL;
}
MinecraftVersionList &MinecraftVersionList::getMainList()
{
return mcVList;
}
void MinecraftVersionList::updateListData(QList<InstVersion *> versions)
{
// First, we populate a temporary list with the copies of the versions.
QList<InstVersion *> tempList;
for (int i = 0; i < versions.length(); i++)
{
InstVersion *version = versions[i]->copyVersion(this);
Q_ASSERT(version != NULL);
tempList.append(version);
}
// Now we swap the temporary list into the actual version list.
// This applies our changes to the version list immediately and still gives us
// access to the old version list so that we can delete the objects in it and
// free their memory. By doing this, we cause the version list to update as
// quickly as possible.
beginResetModel();
m_vlist.swap(tempList);
m_loaded = true;
endResetModel();
// We called swap, so all the data that was in the version list previously is now in
// tempList (and vice-versa). Now we just free the memory.
while (!tempList.isEmpty())
delete tempList.takeFirst();
// NOW SORT!!
sort();
}
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
{
QDomNodeList elementList = parent.elementsByTagName(tagname);
if (elementList.count())
return elementList.at(0).toElement();
else
return QDomElement();
}
inline QDateTime timeFromS3Time(QString str)
{
return QDateTime::fromString(str, Qt::ISODate);
}
MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist)
{
m_list = vlist;
m_currentStable = NULL;
processedAssetsReply = false;
processedMCNReply = false;
processedMCVListReply = false;
}
MCVListLoadTask::~MCVListLoadTask()
{
// delete netMgr;
}
void MCVListLoadTask::executeTask()
{
setSubStatus();
QNetworkAccessManager networkMgr;
netMgr = &networkMgr;
if (!loadFromVList())
{
qDebug() << "Failed to load from Mojang version list.";
}
if (!loadFromAssets())
{
qDebug() << "Failed to load assets version list.";
}
if (!loadMCNostalgia())
{
qDebug() << "Failed to load MCNostalgia version list.";
}
finalize();
}
void MCVListLoadTask::setSubStatus(const QString msg)
{
if (msg.isEmpty())
setStatus("Loading instance version list...");
else
setStatus("Loading instance version list: " + msg);
}
// FIXME: we should have a local cache of the version list and a local cache of version data
bool MCVListLoadTask::loadFromVList()
{
QNetworkReply *vlistReply = netMgr->get(QNetworkRequest(QUrl(QString(MCVLIST_URLBASE) +
"versions.json")));
NetUtils::waitForNetRequest(vlistReply);
switch (vlistReply->error())
{
case QNetworkReply::NoError:
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError);
if (jsonError.error == QJsonParseError::NoError)
{
Q_ASSERT_X(jsonDoc.isObject(), "loadFromVList", "jsonDoc is not an object");
QJsonObject root = jsonDoc.object();
// Get the ID of the latest release and the latest snapshot.
Q_ASSERT_X(root.value("latest").isObject(), "loadFromVList",
"version list is missing 'latest' object");
QJsonObject latest = root.value("latest").toObject();
QString latestReleaseID = latest.value("release").toString("");
QString latestSnapshotID = latest.value("snapshot").toString("");
Q_ASSERT_X(!latestReleaseID.isEmpty(), "loadFromVList", "latest release field is missing");
Q_ASSERT_X(!latestSnapshotID.isEmpty(), "loadFromVList", "latest snapshot field is missing");
// Now, get the array of versions.
Q_ASSERT_X(root.value("versions").isArray(), "loadFromVList",
"version list object is missing 'versions' array");
QJsonArray versions = root.value("versions").toArray();
for (int i = 0; i < versions.count(); i++)
{
// Load the version info.
Q_ASSERT_X(versions[i].isObject(), "loadFromVList",
QString("in versions array, index %1 is not an object").
arg(i).toUtf8());
QJsonObject version = versions[i].toObject();
QString versionID = version.value("id").toString("");
QString versionTimeStr = version.value("releaseTime").toString("");
QString versionTypeStr = version.value("type").toString("");
Q_ASSERT_X(!versionID.isEmpty(), "loadFromVList",
QString("in versions array, index %1's \"id\" field is not a valid string").
arg(i).toUtf8());
Q_ASSERT_X(!versionTimeStr.isEmpty(), "loadFromVList",
QString("in versions array, index %1's \"time\" field is not a valid string").
arg(i).toUtf8());
Q_ASSERT_X(!versionTypeStr.isEmpty(), "loadFromVList",
QString("in versions array, index %1's \"type\" field is not a valid string").
arg(i).toUtf8());
// Now, process that info and add the version to the list.
// Parse the timestamp.
QDateTime versionTime = timeFromS3Time(versionTimeStr);
Q_ASSERT_X(versionTime.isValid(), "loadFromVList",
QString("in versions array, index %1's timestamp failed to parse").
arg(i).toUtf8());
// Parse the type.
MinecraftVersion::VersionType versionType;
if (versionTypeStr == "release")
{
// Check if this version is the current stable version.
if (versionID == latestReleaseID)
versionType = MinecraftVersion::CurrentStable;
else
versionType = MinecraftVersion::Stable;
}
else if(versionTypeStr == "snapshot")
{
versionType = MinecraftVersion::Snapshot;
}
else
{
// we don't know what to do with this...
continue;
}
// Get the download URL.
QString dlUrl = QString(MCVLIST_URLBASE) + versionID + "/";
// Now, we construct the version object and add it to the list.
MinecraftVersion *mcVersion = new MinecraftVersion(
versionID, versionID, versionTime.toMSecsSinceEpoch(),
dlUrl, "");
mcVersion->setVersionSource(MinecraftVersion::Launcher16);
mcVersion->setVersionType(versionType);
tempList.append(mcVersion);
}
}
else
{
qDebug() << "Error parsing version list JSON:" << jsonError.errorString();
}
break;
}
default:
// TODO: Network error handling.
qDebug() << "Failed to load Minecraft main version list" << vlistReply->errorString();
break;
}
return true;
}
bool MCVListLoadTask::loadFromAssets()
{
setSubStatus("Loading versions from assets.minecraft.net...");
bool succeeded = false;
QNetworkReply *assetsReply = netMgr->get(QNetworkRequest(QUrl(ASSETS_URLBASE)));
NetUtils::waitForNetRequest(assetsReply);
switch (assetsReply->error())
{
case QNetworkReply::NoError:
{
// Get the XML string.
QString xmlString = assetsReply->readAll();
QString xmlErrorMsg;
QDomDocument doc;
if (!doc.setContent(xmlString, false, &xmlErrorMsg))
{
// TODO: Display error message to the user.
qDebug() << "Failed to process assets.minecraft.net. XML error:" <<
xmlErrorMsg << xmlString;
}
QDomNodeList contents = doc.elementsByTagName("Contents");
QRegExp mcRegex("/minecraft.jar$");
QRegExp snapshotRegex("[0-9][0-9]w[0-9][0-9][a-z]?|pre|rc");
for (int i = 0; i < contents.length(); i++)
{
QDomElement element = contents.at(i).toElement();
if (element.isNull())
continue;
QDomElement keyElement = getDomElementByTagName(element, "Key");
QDomElement lastmodElement = getDomElementByTagName(element, "LastModified");
QDomElement etagElement = getDomElementByTagName(element, "ETag");
if (keyElement.isNull() || lastmodElement.isNull() || etagElement.isNull())
continue;
QString key = keyElement.text();
QString lastModStr = lastmodElement.text();
QString etagStr = etagElement.text();
if (!key.contains(mcRegex))
continue;
QString versionDirName = key.left(key.length() - 14);
QString dlUrl = QString("http://assets.minecraft.net/%1/").arg(versionDirName);
QString versionName = versionDirName.replace("_", ".");
QDateTime versionTimestamp = timeFromS3Time(lastModStr);
if (!versionTimestamp.isValid())
{
qDebug(QString("Failed to parse timestamp for version %1 %2").
arg(versionName, lastModStr).toUtf8());
versionTimestamp = QDateTime::currentDateTime();
}
if (m_currentStable)
{
{
bool older = versionTimestamp.toMSecsSinceEpoch() < m_currentStable->timestamp();
bool newer = versionTimestamp.toMSecsSinceEpoch() > m_currentStable->timestamp();
bool isSnapshot = versionName.contains(snapshotRegex);
MinecraftVersion *version = new MinecraftVersion(
versionName, versionName,
versionTimestamp.toMSecsSinceEpoch(),
dlUrl, etagStr);
if (newer)
{
version->setVersionType(MinecraftVersion::Snapshot);
}
else if (older && isSnapshot)
{
version->setVersionType(MinecraftVersion::OldSnapshot);
}
else if (older)
{
version->setVersionType(MinecraftVersion::Stable);
}
else
{
// Shouldn't happen, but just in case...
version->setVersionType(MinecraftVersion::CurrentStable);
}
assetsList.push_back(version);
}
}
else // If there isn't a current stable version.
{
bool isSnapshot = versionName.contains(snapshotRegex);
MinecraftVersion *version = new MinecraftVersion(
versionName, versionName,
versionTimestamp.toMSecsSinceEpoch(),
dlUrl, etagStr);
version->setVersionType(isSnapshot? MinecraftVersion::Snapshot :
MinecraftVersion::Stable);
assetsList.push_back(version);
}
}
setSubStatus("Loaded assets.minecraft.net");
succeeded = true;
break;
}
default:
// TODO: Network error handling.
qDebug() << "Failed to load assets.minecraft.net" << assetsReply->errorString();
break;
}
processedAssetsReply = true;
updateStuff();
return succeeded;
}
bool MCVListLoadTask::loadMCNostalgia()
{
QNetworkReply *mcnReply = netMgr->get(QNetworkRequest(QUrl(QString(MCN_URLBASE) + "?pversion=1&list=True")));
NetUtils::waitForNetRequest(mcnReply);
processedMCNReply = true;
updateStuff();
return true;
}
bool MCVListLoadTask::finalize()
{
// First, we need to do some cleanup. We loaded assets versions into assetsList,
// MCNostalgia versions into mcnList and all the others into tempList. MCNostalgia
// provides some versions that are on assets.minecraft.net and we want to ignore
// those, so we remove and delete them from mcnList. assets.minecraft.net also provides
// versions that are on Mojang's version list and we want to ignore those as well.
// To start, we get a list of the descriptors in tmpList.
QStringList tlistDescriptors;
for (int i = 0; i < tempList.count(); i++)
tlistDescriptors.append(tempList.at(i)->descriptor());
// Now, we go through our assets version list and remove anything with
// a descriptor that matches one we already have in tempList.
for (int i = 0; i < assetsList.count(); i++)
if (tlistDescriptors.contains(assetsList.at(i)->descriptor()))
delete assetsList.takeAt(i--); // We need to decrement here because we're removing an item.
// We also need to rebuild the list of descriptors.
tlistDescriptors.clear();
for (int i = 0; i < tempList.count(); i++)
tlistDescriptors.append(tempList.at(i)->descriptor());
// Next, we go through our MCNostalgia version list and do the same thing.
for (int i = 0; i < mcnList.count(); i++)
if (tlistDescriptors.contains(mcnList.at(i)->descriptor()))
delete mcnList.takeAt(i--); // We need to decrement here because we're removing an item.
// Now that the duplicates are gone, we need to merge the lists. This is
// simple enough.
tempList.append(assetsList);
tempList.append(mcnList);
// We're done with these lists now, but the items have been moved over to
// tempList, so we don't need to delete them yet.
// Now, we invoke the updateListData slot on the GUI thread. This will copy all
// the versions we loaded and set their parents to the version list.
// Then, it will swap the new list with the old one and free the old list's memory.
QMetaObject::invokeMethod(m_list, "updateListData", Qt::BlockingQueuedConnection,
Q_ARG(QList<InstVersion*>, tempList));
// Once that's finished, we can delete the versions in our temp list.
while (!tempList.isEmpty())
delete tempList.takeFirst();
#ifdef PRINT_VERSIONS
m_list->printToStdOut();
#endif
return true;
}
void MCVListLoadTask::updateStuff()
{
const int totalReqs = 3;
int reqsComplete = 0;
if (processedMCVListReply)
reqsComplete++;
if (processedAssetsReply)
reqsComplete++;
if (processedMCNReply)
reqsComplete++;
calcProgress(reqsComplete, totalReqs);
if (reqsComplete >= totalReqs)
{
quit();
}
}

View File

@ -0,0 +1,106 @@
/* Copyright 2013 Andrew Okin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QNetworkAccessManager>
#include <QList>
#include "InstVersionList.h"
#include "tasks/Task.h"
#include "MinecraftVersion.h"
#include "libmmc_config.h"
class MCVListLoadTask;
class LIBMULTIMC_EXPORT MinecraftVersionList : public InstVersionList
{
Q_OBJECT
public:
friend class MCVListLoadTask;
explicit MinecraftVersionList(QObject *parent = 0);
virtual Task *getLoadTask();
virtual bool isLoaded();
virtual const InstVersion *at(int i) const;
virtual int count() const;
virtual void printToStdOut() const;
virtual void sort();
virtual InstVersion *getLatestStable() const;
/*!
* Gets the main version list instance.
*/
static MinecraftVersionList &getMainList();
protected:
QList<InstVersion *>m_vlist;
bool m_loaded;
protected slots:
virtual void updateListData(QList<InstVersion *> versions);
};
class MCVListLoadTask : public Task
{
Q_OBJECT
public:
explicit MCVListLoadTask(MinecraftVersionList *vlist);
~MCVListLoadTask();
virtual void executeTask();
protected:
void setSubStatus(const QString msg = "");
//! Loads versions from Mojang's official version list.
bool loadFromVList();
//! Loads versions from assets.minecraft.net. Any duplicates are ignored.
bool loadFromAssets();
//! Loads versions from MCNostalgia.
bool loadMCNostalgia();
//! Finalizes loading by updating the version list.
bool finalize();
void updateStuff();
QNetworkAccessManager *netMgr;
MinecraftVersionList *m_list;
QList<InstVersion *> tempList; //! < List of loaded versions
QList<InstVersion *> assetsList; //! < List of versions loaded from assets.minecraft.net
QList<InstVersion *> mcnList; //! < List of loaded MCNostalgia versions
MinecraftVersion *m_currentStable;
bool processedMCVListReply;
bool processedAssetsReply;
bool processedMCNReply;
};

View File

@ -0,0 +1,283 @@
/* Copyright 2013 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 "GameUpdateTask.h"
#include <QtNetwork>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QDataStream>
#include <QDebug>
#include "lists/MinecraftVersionList.h"
#include "VersionFactory.h"
#include "OneSixVersion.h"
#include "pathutils.h"
GameUpdateTask::GameUpdateTask(const LoginResponse &response, BaseInstance *inst, QObject *parent) :
Task(parent), m_response(response)
{
m_inst = inst;
m_updateState = StateInit;
}
void GameUpdateTask::executeTask()
{
updateStatus();
// Get a pointer to the version object that corresponds to the instance's version.
targetVersion = (MinecraftVersion *)MinecraftVersionList::getMainList().
findVersion(m_inst->intendedVersion());
if(targetVersion == NULL)
{
//Q_ASSERT_X(targetVersion != NULL, "game update", "instance's intended version is not an actual version");
setState(StateFinished);
emit gameUpdateComplete(m_response);
return;
}
/////////////////////////
// BUILD DOWNLOAD LIST //
/////////////////////////
// Build a list of URLs that will need to be downloaded.
setState(StateDetermineURLs);
if (targetVersion->versionSource() == MinecraftVersion::Launcher16)
{
determineNewVersion();
}
else
{
getLegacyJar();
}
QEventLoop loop;
loop.exec();
}
void GameUpdateTask::determineNewVersion()
{
QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/");
urlstr += targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json";
auto dljob = DownloadJob::create(QUrl(urlstr));
specificVersionDownloadJob.reset(new JobList());
specificVersionDownloadJob->add(dljob);
connect(specificVersionDownloadJob.data(), SIGNAL(finished()), SLOT(versionFileFinished()));
connect(specificVersionDownloadJob.data(), SIGNAL(failed()), SLOT(versionFileFailed()));
connect(specificVersionDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64)));
download_queue.enqueue(specificVersionDownloadJob);
}
void GameUpdateTask::versionFileFinished()
{
JobPtr firstJob = specificVersionDownloadJob->getFirstJob();
auto DlJob = firstJob.dynamicCast<DownloadJob>();
FullVersionFactory parser;
auto version = parser.parse(DlJob->m_data);
if(!version)
{
error(parser.error_string);
exit(0);
}
if(version->isLegacy)
{
getLegacyJar();
return;
}
// save the version file in $instanceId/version.json and versions/$version/$version.json
QString version_id = targetVersion->descriptor();
QString mc_dir = m_inst->minecraftDir();
QString inst_dir = m_inst->rootDir();
QString version1 = PathCombine(inst_dir, "/version.json");
QString version2 = QString("versions/") + version_id + "/" + version_id + ".json";
DownloadJob::ensurePathExists(version1);
DownloadJob::ensurePathExists(version2);
QFile vfile1 (version1);
QFile vfile2 (version2);
vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly );
vfile2.open(QIODevice::Truncate | QIODevice::WriteOnly );
vfile1.write(DlJob->m_data);
vfile2.write(DlJob->m_data);
vfile1.close();
vfile2.close();
// download the right jar, save it in versions/$version/$version.jar
QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/");
urlstr += targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".jar";
QString targetstr ("versions/");
targetstr += targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".jar";
auto dljob = DownloadJob::create(QUrl(urlstr), targetstr);
jarlibDownloadJob.reset(new JobList());
jarlibDownloadJob->add(dljob);
connect(jarlibDownloadJob.data(), SIGNAL(finished()), SLOT(jarlibFinished()));
connect(jarlibDownloadJob.data(), SIGNAL(failed()), SLOT(jarlibFailed()));
connect(jarlibDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64)));
// determine and download all the libraries, save them in libraries/whatever...
download_queue.enqueue(jarlibDownloadJob);
}
void GameUpdateTask::jarlibFinished()
{
m_inst->setCurrentVersion(targetVersion->descriptor());
m_inst->setShouldUpdate(false);
// m_inst->setIsForNewLauncher(true);
exit(1);
}
void GameUpdateTask::jarlibFailed()
{
error("Failed to download the binary garbage. Try again. Maybe. IF YOU DARE");
exit(0);
}
void GameUpdateTask::versionFileFailed()
{
error("Failed to download the version description. Try again.");
exit(0);
}
// this is legacy minecraft...
void GameUpdateTask::getLegacyJar()
{
// Make directories
QDir binDir(m_inst->binDir());
if (!binDir.exists() && !binDir.mkpath("."))
{
error("Failed to create bin folder.");
return;
}
// Add the URL for minecraft.jar
// This will be either 'minecraft' or the version number, depending on where
// we're downloading from.
QString jarFilename = "minecraft";
if (targetVersion->versionSource() == MinecraftVersion::Launcher16)
{
jarFilename = targetVersion->descriptor();
}
QUrl mcJarURL = targetVersion->downloadURL() + jarFilename + ".jar";
qDebug() << mcJarURL.toString();
auto dljob = DownloadJob::create(mcJarURL, PathCombine(m_inst->minecraftDir(), "bin/minecraft.jar"));
legacyDownloadJob.reset(new JobList());
legacyDownloadJob->add(dljob);
connect(legacyDownloadJob.data(), SIGNAL(finished()), SLOT(legacyJarFinished()));
connect(legacyDownloadJob.data(), SIGNAL(failed()), SLOT(legacyJarFailed()));
connect(legacyDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64)));
download_queue.enqueue(legacyDownloadJob);
}
void GameUpdateTask::legacyJarFinished()
{
setState(StateFinished);
emit gameUpdateComplete(m_response);
// m_inst->setIsForNewLauncher(true);
exit(1);
}
void GameUpdateTask::legacyJarFailed()
{
emit gameUpdateError("failed to download the minecraft.jar");
exit(0);
}
int GameUpdateTask::state() const
{
return m_updateState;
}
void GameUpdateTask::setState(int state, bool resetSubStatus)
{
m_updateState = state;
if (resetSubStatus)
setSubStatus("");
else // We only need to update if we're not resetting substatus becasue setSubStatus updates status for us.
updateStatus();
}
QString GameUpdateTask::subStatus() const
{
return m_subStatusMsg;
}
void GameUpdateTask::setSubStatus(const QString &msg)
{
m_subStatusMsg = msg;
updateStatus();
}
QString GameUpdateTask::getStateMessage(int state)
{
switch (state)
{
case StateInit:
return "Initializing";
case StateDetermineURLs:
return "Determining files to download";
case StateDownloadFiles:
return "Downloading files";
case StateInstall:
return "Installing";
case StateFinished:
return "Finished";
default:
return "Downloading instance files";
}
}
void GameUpdateTask::updateStatus()
{
QString newStatus;
newStatus = getStateMessage(state());
if (!subStatus().isEmpty())
newStatus += ": " + subStatus();
else
newStatus += "...";
setStatus(newStatus);
}
void GameUpdateTask::error(const QString &msg)
{
emit gameUpdateError(msg);
}
void GameUpdateTask::updateDownloadProgress(qint64 current, qint64 total)
{
// The progress on the current file is current / total
float currentDLProgress = (float) current / (float) total;
setProgress((int)(currentDLProgress * 100)); // convert to percentage
}

View File

@ -0,0 +1,157 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QList>
#include <QNetworkAccessManager>
#include <QUrl>
#include "dlqueue.h"
#include "Task.h"
#include "tasks/LoginResponse.h"
#include "BaseInstance.h"
#include "libmmc_config.h"
class MinecraftVersion;
/*!
* The game update task is the task that handles downloading instances' files.
*/
class LIBMULTIMC_EXPORT GameUpdateTask : public Task
{
Q_OBJECT
/*!
* The task's state.
* A certain state message will be shown depending on what this is set to.
*/
Q_PROPERTY(int state READ state WRITE setState)
/*!
* The substatus message.
* This will be next to the the state message in the task's status.
*/
Q_PROPERTY(QString subStatus READ subStatus WRITE setSubStatus)
public:
explicit GameUpdateTask(const LoginResponse &response, BaseInstance *inst, QObject *parent = 0);
/////////////////////////
// EXECUTION FUNCTIONS //
/////////////////////////
virtual void executeTask();
//////////////////////
// STATE AND STATUS //
//////////////////////
virtual int state() const;
virtual void setState(int state, bool resetSubStatus = true);
virtual QString subStatus() const;
virtual void setSubStatus(const QString &msg);
/*!
* Gets the message that will be displated for the given state.
*/
virtual QString getStateMessage(int state);
private:
void getLegacyJar();
void determineNewVersion();
public slots:
/*!
* Updates the status message based on the state and substatus message.
*/
virtual void updateStatus();
virtual void error(const QString &msg);
private slots:
void updateDownloadProgress(qint64 current, qint64 total);
void legacyJarFinished();
void legacyJarFailed();
void versionFileFinished();
void versionFileFailed();
void jarlibFinished();
void jarlibFailed();
signals:
/*!
* \brief Signal emitted when the game update is complete.
* \param response The login response received from login task.
*/
void gameUpdateComplete(const LoginResponse &response);
/*!
* \brief Signal emitted if an error occurrs during the update.
* \param errorMsg An error message to be displayed to the user.
*/
void gameUpdateError(const QString &errorMsg);
private:
///////////
// STUFF //
///////////
BaseInstance *m_inst;
LoginResponse m_response;
////////////////////////////
// STATE AND STATUS STUFF //
////////////////////////////
int m_updateState;
QString m_subStatusMsg;
enum UpdateState
{
// Initializing
StateInit = 0,
// Determining files to download
StateDetermineURLs,
// Downloading files
StateDownloadFiles,
// Installing files
StateInstall,
// Finished
StateFinished
};
JobListPtr legacyDownloadJob;
JobListPtr specificVersionDownloadJob;
JobListPtr jarlibDownloadJob;
JobListQueue download_queue;
// target version, determined during this task
MinecraftVersion *targetVersion;
};

View File

@ -0,0 +1,69 @@
/* Copyright 2013 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 "tasks/LoginResponse.h"
LoginResponse::LoginResponse(const QString& username, const QString& sessionID,
qint64 latestVersion, QObject *parent) :
QObject(parent)
{
this->m_username = username;
this->m_sessionID = sessionID;
this->m_latestVersion = latestVersion;
}
LoginResponse::LoginResponse()
{
this->m_username = "";
this->m_sessionID = "";
this->m_latestVersion = 0;
}
LoginResponse::LoginResponse(const LoginResponse &other)
{
this->m_username = other.username();
this->m_sessionID = other.sessionID();
this->m_latestVersion = other.latestVersion();
}
QString LoginResponse::username() const
{
return m_username;
}
void LoginResponse::setUsername(const QString& username)
{
this->m_username = username;
}
QString LoginResponse::sessionID() const
{
return m_sessionID;
}
void LoginResponse::setSessionID(const QString& sessionID)
{
this->m_sessionID = sessionID;
}
qint64 LoginResponse::latestVersion() const
{
return m_latestVersion;
}
void LoginResponse::setLatestVersion(qint64 v)
{
this->m_latestVersion = v;
}

View File

@ -0,0 +1,94 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include "libmmc_config.h"
/*!
* \brief The LoginResponse class represents a response received from Minecraft's login servers.
*/
class LIBMULTIMC_EXPORT LoginResponse : public QObject
{
Q_OBJECT
public:
/*!
* \brief Creates a new instance of the LoginResponse class.
* \param username The user's username.
* \param sessionID The user's session ID.
* \param latestVersion The latest version of Minecraft.
* \param parent The parent object.
*/
explicit LoginResponse(const QString &username, const QString &sessionID,
qint64 latestVersion, QObject *parent = 0);
LoginResponse();
LoginResponse(const LoginResponse& other);
/*!
* \brief Gets the username.
* This one should go without saying.
* \return The username.
* \sa setUsername()
*/
QString username() const;
/*!
* \brief setUsername Sets the username.
* \param username The new username.
* \sa username()
*/
void setUsername(const QString& username);
/*!
* \brief Gets the session ID.
* \return The session ID.
* \sa setSessionID()
*/
QString sessionID() const;
/*!
* \brief Sets the session ID.
* \param sessionID The new session ID.
* \sa sessionID()
*/
void setSessionID(const QString& sessionID);
/*!
* \brief Gets the latest version.
* This is a value returned by the login servers when a user logs in.
* \return The latest version.
* \sa setLatestVersion()
*/
qint64 latestVersion() const;
/*!
* \brief Sets the latest version.
* \param v The new latest version.
* \sa latestVersion()
*/
void setLatestVersion(qint64 v);
private:
QString m_username;
QString m_sessionID;
qint64 m_latestVersion;
};
Q_DECLARE_METATYPE(LoginResponse)

121
backend/tasks/LoginTask.cpp Normal file
View File

@ -0,0 +1,121 @@
/* Copyright 2013 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 "LoginTask.h"
#include <QStringList>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QUrl>
#include <QUrlQuery>
LoginTask::LoginTask( const UserInfo& uInfo, QObject* parent ) :
Task(parent), uInfo(uInfo)
{
}
void LoginTask::executeTask()
{
setStatus("Logging in...");
QNetworkAccessManager netMgr;
connect(&netMgr, SIGNAL(finished(QNetworkReply*)),
SLOT(processNetReply(QNetworkReply*)));
QUrl loginURL("https://login.minecraft.net/");
QNetworkRequest netRequest(loginURL);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
QUrlQuery params;
params.addQueryItem("user", uInfo.username());
params.addQueryItem("password", uInfo.password());
params.addQueryItem("version", "13");
netReply = netMgr.post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8());
exec();
}
void LoginTask::processNetReply(QNetworkReply *reply)
{
// Check for errors.
switch (reply->error())
{
case QNetworkReply::NoError:
{
// Check the response code.
int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (responseCode == 200)
{
QString responseStr(reply->readAll());
QStringList strings = responseStr.split(":");
if (strings.count() >= 4)
{
bool parseSuccess;
qint64 latestVersion = strings[0].toLongLong(&parseSuccess);
if (parseSuccess)
{
// strings[1] is the download ticket. It isn't used anymore.
QString username = strings[2];
QString sessionID = strings[3];
LoginResponse response(username, sessionID, latestVersion);
emit loginComplete(response);
}
else
{
emit loginFailed("Failed to parse Minecraft version string.");
}
}
else
{
if (responseStr.toLower() == "bad login")
emit loginFailed("Invalid username or password.");
else if (responseStr.toLower() == "old version")
emit loginFailed("Launcher outdated, please update.");
else
emit loginFailed("Login failed: " + responseStr);
}
}
else if (responseCode == 503)
{
emit loginFailed("The login servers are currently unavailable. "
"Check http://help.mojang.com/ for more info.");
}
else
{
emit loginFailed(QString("Login failed: Unknown HTTP error %1 occurred.").
arg(QString::number(responseCode)));
}
break;
}
case QNetworkReply::OperationCanceledError:
emit loginFailed("Login canceled.");
break;
default:
emit loginFailed("Login failed: " + reply->errorString());
break;
}
quit();
}

49
backend/tasks/LoginTask.h Normal file
View File

@ -0,0 +1,49 @@
/* Copyright 2013 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.
*/
#ifndef LOGINTASK_H
#define LOGINTASK_H
#include "Task.h"
#include "UserInfo.h"
#include "tasks/LoginResponse.h"
#include "libmmc_config.h"
//class QNetworkAccessManager;
class QNetworkReply;
class LIBMULTIMC_EXPORT LoginTask : public Task
{
Q_OBJECT
public:
explicit LoginTask(const UserInfo& uInfo, QObject *parent = 0);
public slots:
void processNetReply(QNetworkReply* reply);
signals:
void loginComplete(LoginResponse loginResponse);
void loginFailed(const QString& errorMsg);
protected:
void executeTask();
QNetworkReply* netReply;
UserInfo uInfo;
};
#endif // LOGINTASK_H

83
backend/tasks/Task.cpp Normal file
View File

@ -0,0 +1,83 @@
/* Copyright 2013 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 "Task.h"
Task::Task(QObject *parent) :
QThread(parent)
{
}
QString Task::getStatus() const
{
return status;
}
void Task::setStatus(const QString &status)
{
this->status = status;
emitStatusChange(status);
}
int Task::getProgress() const
{
return progress;
}
void Task::calcProgress(int parts, int whole)
{
setProgress((int)((((float)parts) / ((float)whole))*100)); // Not sure if C++ or LISP...
}
void Task::setProgress(int progress)
{
this->progress = progress;
emitProgressChange(progress);
}
void Task::startTask()
{
start();
}
void Task::run()
{
emitStarted();
executeTask();
emitEnded();
}
void Task::emitStarted()
{
emit started();
emit started(this);
}
void Task::emitEnded()
{
emit ended();
emit ended(this);
}
void Task::emitStatusChange(const QString &status)
{
emit statusChanged(status);
}
void Task::emitProgressChange(int progress)
{
emit progressChanged(progress);
}

78
backend/tasks/Task.h Normal file
View File

@ -0,0 +1,78 @@
/* Copyright 2013 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.
*/
#ifndef TASK_H
#define TASK_H
#include <QObject>
#include <QThread>
#include <QString>
#include "libmmc_config.h"
class LIBMULTIMC_EXPORT Task : public QThread
{
Q_OBJECT
public:
explicit Task(QObject *parent = 0);
// Starts the task.
void startTask();
QString getStatus() const;
int getProgress() const;
/*!
* \brief Calculates and sets the task's progress based on the number of parts completed out of the total number to complete.
* This is essentially just shorthand for setProgress((parts / whole) * 100);
* \param parts The parts out of the whole completed. This parameter should
* be less than whole. If it is greater than whole, progress is set to 100.
* \param whole The total number of things that need to be completed.
*/
void calcProgress(int parts, int whole);
public slots:
void setStatus(const QString& status);
void setProgress(int progress);
signals:
void started(Task* task);
void ended(Task* task);
void started();
void ended();
void statusChanged(Task* task, const QString& status);
void progressChanged(Task* task, int progress);
void statusChanged(const QString& status);
void progressChanged(int progress);
protected:
virtual void run();
virtual void executeTask() = 0;
virtual void emitStarted();
virtual void emitEnded();
virtual void emitStatusChange(const QString &status);
virtual void emitProgressChange(int progress);
QString status;
int progress;
};
#endif // TASK_H

View File

@ -0,0 +1,49 @@
/* Copyright 2013 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 "UserInfo.h"
UserInfo::UserInfo(const QString &username, const QString &password, QObject *parent) :
QObject(parent)
{
this->m_username = username;
this->m_password = password;
}
UserInfo::UserInfo(const UserInfo &other)
{
this->m_username = other.m_username;
this->m_password = other.m_password;
}
QString UserInfo::username() const
{
return m_username;
}
void UserInfo::setUsername(const QString &username)
{
this->m_username = username;
}
QString UserInfo::password() const
{
return m_password;
}
void UserInfo::setPassword(const QString &password)
{
this->m_password = password;
}

41
backend/tasks/UserInfo.h Normal file
View File

@ -0,0 +1,41 @@
/* Copyright 2013 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.
*/
#ifndef USERINFO_H
#define USERINFO_H
#include <QObject>
#include "libmmc_config.h"
class LIBMULTIMC_EXPORT UserInfo : public QObject
{
Q_OBJECT
public:
explicit UserInfo(const QString& username, const QString& password, QObject *parent = 0);
explicit UserInfo(const UserInfo& other);
QString username() const;
void setUsername(const QString& username);
QString password() const;
void setPassword(const QString& password);
protected:
QString m_username;
QString m_password;
};
#endif // USERINFO_H