summaryrefslogtreecommitdiffstats
path: root/backend
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2013-07-29 00:59:35 +0200
committerPetr Mrázek <peterix@gmail.com>2013-07-29 00:59:35 +0200
commit2e0cbf393a5320dbf5448ca44a9b5905314b0be8 (patch)
tree4baac9cf015ca7b15d83de33c705e0d8d4497d30 /backend
parent8808a8b108b82916eaf30f9aca50cd3ab16af230 (diff)
downloadMultiMC-2e0cbf393a5320dbf5448ca44a9b5905314b0be8.tar
MultiMC-2e0cbf393a5320dbf5448ca44a9b5905314b0be8.tar.gz
MultiMC-2e0cbf393a5320dbf5448ca44a9b5905314b0be8.tar.lz
MultiMC-2e0cbf393a5320dbf5448ca44a9b5905314b0be8.tar.xz
MultiMC-2e0cbf393a5320dbf5448ca44a9b5905314b0be8.zip
Massive renaming in the backend folder, all around restructure in the same.
Diffstat (limited to 'backend')
-rw-r--r--backend/BaseInstance.cpp199
-rw-r--r--backend/BaseInstance.h313
-rw-r--r--backend/CMakeLists.txt91
-rw-r--r--backend/InstanceFactory.cpp61
-rw-r--r--backend/InstanceFactory.h79
-rw-r--r--backend/InstanceVersion.cpp67
-rw-r--r--backend/InstanceVersion.h121
-rw-r--r--backend/LegacyInstance.cpp0
-rw-r--r--backend/LegacyInstance.h1
-rw-r--r--backend/MinecraftProcess.cpp229
-rw-r--r--backend/MinecraftProcess.h111
-rw-r--r--backend/MinecraftVersion.cpp104
-rw-r--r--backend/MinecraftVersion.h80
-rw-r--r--backend/OneSixInstance.cpp0
-rw-r--r--backend/OneSixInstance.h1
-rw-r--r--backend/OneSixVersion.cpp91
-rw-r--r--backend/OneSixVersion.h265
-rw-r--r--backend/VersionFactory.cpp194
-rw-r--r--backend/VersionFactory.h25
-rw-r--r--backend/libmmc_config.h24
-rw-r--r--backend/lists/InstVersionList.cpp129
-rw-r--r--backend/lists/InstVersionList.h121
-rw-r--r--backend/lists/InstanceList.cpp232
-rw-r--r--backend/lists/InstanceList.h92
-rw-r--r--backend/lists/LwjglVersionList.cpp204
-rw-r--r--backend/lists/LwjglVersionList.h132
-rw-r--r--backend/lists/MinecraftVersionList.cpp528
-rw-r--r--backend/lists/MinecraftVersionList.h106
-rw-r--r--backend/tasks/GameUpdateTask.cpp283
-rw-r--r--backend/tasks/GameUpdateTask.h157
-rw-r--r--backend/tasks/LoginResponse.cpp69
-rw-r--r--backend/tasks/LoginResponse.h94
-rw-r--r--backend/tasks/LoginTask.cpp121
-rw-r--r--backend/tasks/LoginTask.h49
-rw-r--r--backend/tasks/Task.cpp83
-rw-r--r--backend/tasks/Task.h78
-rw-r--r--backend/tasks/UserInfo.cpp49
-rw-r--r--backend/tasks/UserInfo.h41
38 files changed, 4624 insertions, 0 deletions
diff --git a/backend/BaseInstance.cpp b/backend/BaseInstance.cpp
new file mode 100644
index 00000000..c2ffa664
--- /dev/null
+++ b/backend/BaseInstance.cpp
@@ -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;
+}
diff --git a/backend/BaseInstance.h b/backend/BaseInstance.h
new file mode 100644
index 00000000..51f55b5e
--- /dev/null
+++ b/backend/BaseInstance.h
@@ -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;
+
diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt
new file mode 100644
index 00000000..a17b7dfe
--- /dev/null
+++ b/backend/CMakeLists.txt
@@ -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)
diff --git a/backend/InstanceFactory.cpp b/backend/InstanceFactory.cpp
new file mode 100644
index 00000000..318650ae
--- /dev/null
+++ b/backend/InstanceFactory.cpp
@@ -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;
+}
diff --git a/backend/InstanceFactory.h b/backend/InstanceFactory.h
new file mode 100644
index 00000000..0dd4c5d8
--- /dev/null
+++ b/backend/InstanceFactory.h
@@ -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;
+};
diff --git a/backend/InstanceVersion.cpp b/backend/InstanceVersion.cpp
new file mode 100644
index 00000000..1b9b0a5b
--- /dev/null
+++ b/backend/InstanceVersion.cpp
@@ -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;
+}
diff --git a/backend/InstanceVersion.h b/backend/InstanceVersion.h
new file mode 100644
index 00000000..5ebae41e
--- /dev/null
+++ b/backend/InstanceVersion.h
@@ -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;
+};
diff --git a/backend/LegacyInstance.cpp b/backend/LegacyInstance.cpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/backend/LegacyInstance.cpp
diff --git a/backend/LegacyInstance.h b/backend/LegacyInstance.h
new file mode 100644
index 00000000..7b9637ef
--- /dev/null
+++ b/backend/LegacyInstance.h
@@ -0,0 +1 @@
+#pragma once \ No newline at end of file
diff --git a/backend/MinecraftProcess.cpp b/backend/MinecraftProcess.cpp
new file mode 100644
index 00000000..e53feb5b
--- /dev/null
+++ b/backend/MinecraftProcess.cpp
@@ -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;
+}
diff --git a/backend/MinecraftProcess.h b/backend/MinecraftProcess.h
new file mode 100644
index 00000000..c846f5f4
--- /dev/null
+++ b/backend/MinecraftProcess.h
@@ -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();
+
+};
diff --git a/backend/MinecraftVersion.cpp b/backend/MinecraftVersion.cpp
new file mode 100644
index 00000000..6f3b1b86
--- /dev/null
+++ b/backend/MinecraftVersion.cpp
@@ -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;
+}
diff --git a/backend/MinecraftVersion.h b/backend/MinecraftVersion.h
new file mode 100644
index 00000000..a69b4710
--- /dev/null
+++ b/backend/MinecraftVersion.h
@@ -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;
+};
diff --git a/backend/OneSixInstance.cpp b/backend/OneSixInstance.cpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/backend/OneSixInstance.cpp
diff --git a/backend/OneSixInstance.h b/backend/OneSixInstance.h
new file mode 100644
index 00000000..7b9637ef
--- /dev/null
+++ b/backend/OneSixInstance.h
@@ -0,0 +1 @@
+#pragma once \ No newline at end of file
diff --git a/backend/OneSixVersion.cpp b/backend/OneSixVersion.cpp
new file mode 100644
index 00000000..b58870ce
--- /dev/null
+++ b/backend/OneSixVersion.cpp
@@ -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;
+}
diff --git a/backend/OneSixVersion.h b/backend/OneSixVersion.h
new file mode 100644
index 00000000..762e8f3e
--- /dev/null
+++ b/backend/OneSixVersion.h
@@ -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();
+}; \ No newline at end of file
diff --git a/backend/VersionFactory.cpp b/backend/VersionFactory.cpp
new file mode 100644
index 00000000..d0242f17
--- /dev/null
+++ b/backend/VersionFactory.cpp
@@ -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");
+}
diff --git a/backend/VersionFactory.h b/backend/VersionFactory.h
new file mode 100644
index 00000000..60e5c983
--- /dev/null
+++ b/backend/VersionFactory.h
@@ -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;
+}; \ No newline at end of file
diff --git a/backend/libmmc_config.h b/backend/libmmc_config.h
new file mode 100644
index 00000000..6d967f5f
--- /dev/null
+++ b/backend/libmmc_config.h
@@ -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
diff --git a/backend/lists/InstVersionList.cpp b/backend/lists/InstVersionList.cpp
new file mode 100644
index 00000000..c65770a9
--- /dev/null
+++ b/backend/lists/InstVersionList.cpp
@@ -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;
+}
diff --git a/backend/lists/InstVersionList.h b/backend/lists/InstVersionList.h
new file mode 100644
index 00000000..97e00383
--- /dev/null
+++ b/backend/lists/InstVersionList.h
@@ -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;
+};
diff --git a/backend/lists/InstanceList.cpp b/backend/lists/InstanceList.cpp
new file mode 100644
index 00000000..101d52c5
--- /dev/null
+++ b/backend/lists/InstanceList.cpp
@@ -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;
+ }
+ }
+}
diff --git a/backend/lists/InstanceList.h b/backend/lists/InstanceList.h
new file mode 100644
index 00000000..8c9965e5
--- /dev/null
+++ b/backend/lists/InstanceList.h
@@ -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;
+};
diff --git a/backend/lists/LwjglVersionList.cpp b/backend/lists/LwjglVersionList.cpp
new file mode 100644
index 00000000..824d0906
--- /dev/null
+++ b/backend/lists/LwjglVersionList.cpp
@@ -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);
+}
diff --git a/backend/lists/LwjglVersionList.h b/backend/lists/LwjglVersionList.h
new file mode 100644
index 00000000..f3e7799a
--- /dev/null
+++ b/backend/lists/LwjglVersionList.h
@@ -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();
+};
+
diff --git a/backend/lists/MinecraftVersionList.cpp b/backend/lists/MinecraftVersionList.cpp
new file mode 100644
index 00000000..d576397f
--- /dev/null
+++ b/backend/lists/MinecraftVersionList.cpp
@@ -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();
+ }
+}
diff --git a/backend/lists/MinecraftVersionList.h b/backend/lists/MinecraftVersionList.h
new file mode 100644
index 00000000..10570b01
--- /dev/null
+++ b/backend/lists/MinecraftVersionList.h
@@ -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;
+};
+
diff --git a/backend/tasks/GameUpdateTask.cpp b/backend/tasks/GameUpdateTask.cpp
new file mode 100644
index 00000000..0a1df0e1
--- /dev/null
+++ b/backend/tasks/GameUpdateTask.cpp
@@ -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
+}
+
diff --git a/backend/tasks/GameUpdateTask.h b/backend/tasks/GameUpdateTask.h
new file mode 100644
index 00000000..94e798f1
--- /dev/null
+++ b/backend/tasks/GameUpdateTask.h
@@ -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;
+};
+
+
diff --git a/backend/tasks/LoginResponse.cpp b/backend/tasks/LoginResponse.cpp
new file mode 100644
index 00000000..218f51c1
--- /dev/null
+++ b/backend/tasks/LoginResponse.cpp
@@ -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;
+}
diff --git a/backend/tasks/LoginResponse.h b/backend/tasks/LoginResponse.h
new file mode 100644
index 00000000..60875619
--- /dev/null
+++ b/backend/tasks/LoginResponse.h
@@ -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)
+
diff --git a/backend/tasks/LoginTask.cpp b/backend/tasks/LoginTask.cpp
new file mode 100644
index 00000000..11f58f28
--- /dev/null
+++ b/backend/tasks/LoginTask.cpp
@@ -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();
+}
diff --git a/backend/tasks/LoginTask.h b/backend/tasks/LoginTask.h
new file mode 100644
index 00000000..e23ea9e9
--- /dev/null
+++ b/backend/tasks/LoginTask.h
@@ -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
diff --git a/backend/tasks/Task.cpp b/backend/tasks/Task.cpp
new file mode 100644
index 00000000..7831ee58
--- /dev/null
+++ b/backend/tasks/Task.cpp
@@ -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);
+}
diff --git a/backend/tasks/Task.h b/backend/tasks/Task.h
new file mode 100644
index 00000000..c8c12c02
--- /dev/null
+++ b/backend/tasks/Task.h
@@ -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
diff --git a/backend/tasks/UserInfo.cpp b/backend/tasks/UserInfo.cpp
new file mode 100644
index 00000000..82225309
--- /dev/null
+++ b/backend/tasks/UserInfo.cpp
@@ -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;
+}
diff --git a/backend/tasks/UserInfo.h b/backend/tasks/UserInfo.h
new file mode 100644
index 00000000..280f98de
--- /dev/null
+++ b/backend/tasks/UserInfo.h
@@ -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