Merge branch 'develop' of github.com:MultiMC/MultiMC5 into feature_news

Conflicts:
	CMakeLists.txt
	gui/MainWindow.h
This commit is contained in:
Forkk 2014-01-02 13:38:20 -06:00
commit 17f1864a71
153 changed files with 4702 additions and 2180 deletions

2
.gitignore vendored
View File

@ -18,3 +18,5 @@ tags
# YouCompleteMe config stuff. # YouCompleteMe config stuff.
.ycm_extra_conf.* .ycm_extra_conf.*
#OSX Stuff
.DS_Store

View File

@ -43,11 +43,20 @@ ENDIF()
######## 3rd Party Libs ######## ######## 3rd Party Libs ########
# Find the required Qt parts # Find the required Qt parts
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED) find_package(Qt5Widgets REQUIRED)
find_package(Qt5Network REQUIRED) find_package(Qt5Network REQUIRED)
find_package(Qt5Test REQUIRED)
find_package(Qt5Concurrent REQUIRED)
find_package(Qt5LinguistTools REQUIRED) find_package(Qt5LinguistTools REQUIRED)
include_directories(${Qt5Widgets_INCLUDE_DIRS}) include_directories(
${Qt5Core_INCLUDE_DIRS}
${Qt5Widgets_INCLUDE_DIRS}
${Qt5Concurrent_INCLUDE_DIRS}
${Qt5Network_INCLUDE_DIRS}
${Qt5Test_INCLUDE_DIRS}
)
# The Qt5 cmake files don't provide its install paths, so ask qmake. # The Qt5 cmake files don't provide its install paths, so ask qmake.
get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION) get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION)
@ -360,16 +369,22 @@ logic/OpSys.h
logic/OpSys.cpp logic/OpSys.cpp
logic/ForgeInstaller.h logic/ForgeInstaller.h
logic/ForgeInstaller.cpp logic/ForgeInstaller.cpp
logic/LiteLoaderInstaller.h
logic/LiteLoaderInstaller.cpp
# Nostalgia # Nostalgia
logic/NostalgiaInstance.h logic/NostalgiaInstance.h
logic/NostalgiaInstance.cpp logic/NostalgiaInstance.cpp
# FTB
logic/OneSixFTBInstance.h
logic/OneSixFTBInstance.cpp
logic/LegacyFTBInstance.h
logic/LegacyFTBInstance.cpp
# Lists # Lists
logic/lists/InstanceList.h logic/lists/InstanceList.h
logic/lists/InstanceList.cpp logic/lists/InstanceList.cpp
logic/lists/IconList.h
logic/lists/IconList.cpp
logic/lists/BaseVersionList.h logic/lists/BaseVersionList.h
logic/lists/BaseVersionList.cpp logic/lists/BaseVersionList.cpp
logic/lists/MinecraftVersionList.h logic/lists/MinecraftVersionList.h
@ -381,6 +396,13 @@ logic/lists/ForgeVersionList.cpp
logic/lists/JavaVersionList.h logic/lists/JavaVersionList.h
logic/lists/JavaVersionList.cpp logic/lists/JavaVersionList.cpp
# Icons
logic/icons/MMCIcon.h
logic/icons/MMCIcon.cpp
logic/icons/IconList.h
logic/icons/IconList.cpp
# misc model/view # misc model/view
logic/EnabledItemFilter.h logic/EnabledItemFilter.h
logic/EnabledItemFilter.cpp logic/EnabledItemFilter.cpp
@ -389,6 +411,10 @@ logic/EnabledItemFilter.cpp
logic/tasks/ProgressProvider.h logic/tasks/ProgressProvider.h
logic/tasks/Task.h logic/tasks/Task.h
logic/tasks/Task.cpp logic/tasks/Task.cpp
logic/tasks/ThreadTask.h
logic/tasks/ThreadTask.cpp
logic/tasks/SequentialTask.h
logic/tasks/SequentialTask.cpp
# Utilities # Utilities
logic/JavaChecker.h logic/JavaChecker.h
@ -403,6 +429,8 @@ logic/JavaCheckerJob.h
logic/JavaCheckerJob.cpp logic/JavaCheckerJob.cpp
# Assets # Assets
logic/assets/AssetsMigrateTask.h
logic/assets/AssetsMigrateTask.cpp
logic/assets/AssetsUtils.h logic/assets/AssetsUtils.h
logic/assets/AssetsUtils.cpp logic/assets/AssetsUtils.cpp
) )
@ -499,8 +527,8 @@ ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS})
# Link # Link
TARGET_LINK_LIBRARIES(MultiMC MultiMC_common) TARGET_LINK_LIBRARIES(MultiMC MultiMC_common)
TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS}) TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS})
QT5_USE_MODULES(MultiMC Core Widgets Network Xml WebKit ${MultiMC_QT_ADDITIONAL_MODULES}) QT5_USE_MODULES(MultiMC Core Widgets Network Xml WebKit Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
QT5_USE_MODULES(MultiMC_common Core Widgets Network Xml WebKit ${MultiMC_QT_ADDITIONAL_MODULES}) QT5_USE_MODULES(MultiMC_common Core Widgets Network Xml WebKit Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
ADD_DEPENDENCIES(MultiMC_common MultiMCLauncher JavaCheck) ADD_DEPENDENCIES(MultiMC_common MultiMCLauncher JavaCheck)
################################ INSTALLATION AND PACKAGING ################################ ################################ INSTALLATION AND PACKAGING ################################
@ -514,9 +542,12 @@ IF(UNIX AND APPLE)
SET(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC") SET(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC")
SET(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.") SET(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.")
SET(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5")
SET(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_REV}.${MultiMC_VERSION_BUILD}") SET(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_REV}.${MultiMC_VERSION_BUILD}")
#SET(MACOSX_BUNDLE_GUI_IDENTIFIER "") SET(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_REV}.${MultiMC_VERSION_BUILD}")
SET(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_REV}.${MultiMC_VERSION_BUILD}")
SET(MACOSX_BUNDLE_ICON_FILE MultiMC.icns) SET(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
SET(MACOSX_BUNDLE_COPYRIGHT "Copyright 2013 MultiMC Contributors")
ELSEIF(UNIX) ELSEIF(UNIX)
SET(PLUGIN_DEST_DIR plugins) SET(PLUGIN_DEST_DIR plugins)
SET(QTCONF_DEST_DIR .) SET(QTCONF_DEST_DIR .)
@ -592,6 +623,18 @@ INSTALL(
REGEX "d\\." EXCLUDE REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE REGEX "_debug\\." EXCLUDE
) )
IF(APPLE)
# Accessible plugin to make buttons look decent on osx
INSTALL(
DIRECTORY "${QT_PLUGINS_DIR}/accessible"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "quick" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
)
ENDIF()
endif() endif()
# qtconf # qtconf
@ -658,8 +701,12 @@ ELSE()
ENDIF() ENDIF()
add_custom_target (translations DEPENDS ${QM_FILES}) add_custom_target (translations DEPENDS ${QM_FILES})
IF(APPLE AND UNIX) ## OSX
install(FILES ${QM_FILES} DESTINATION MultiMC.app/Contents/MacOS/translations)
ELSE()
install(FILES ${QM_FILES} DESTINATION translations)
ENDIF()
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/translations)
# Tests # Tests
add_subdirectory(tests) add_subdirectory(tests)

View File

@ -8,11 +8,12 @@
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QMessageBox> #include <QMessageBox>
#include <QStringList> #include <QStringList>
#include <QDesktopServices>
#include "gui/dialogs/VersionSelectDialog.h" #include "gui/dialogs/VersionSelectDialog.h"
#include "logic/lists/InstanceList.h" #include "logic/lists/InstanceList.h"
#include "logic/auth/MojangAccountList.h" #include "logic/auth/MojangAccountList.h"
#include "logic/lists/IconList.h" #include "logic/icons/IconList.h"
#include "logic/lists/LwjglVersionList.h" #include "logic/lists/LwjglVersionList.h"
#include "logic/lists/MinecraftVersionList.h" #include "logic/lists/MinecraftVersionList.h"
#include "logic/lists/ForgeVersionList.h" #include "logic/lists/ForgeVersionList.h"
@ -34,16 +35,28 @@
#include <logger/QsLogDest.h> #include <logger/QsLogDest.h>
#include "config.h" #include "config.h"
#ifdef WINDOWS
#define UPDATER_BIN "updater.exe"
#elif LINUX
#define UPDATER_BIN "updater"
#elif OSX
#define UPDATER_BIN "updater"
#else
#error Unsupported operating system.
#endif
using namespace Util::Commandline; using namespace Util::Commandline;
MultiMC::MultiMC(int &argc, char **argv, const QString &root) : QApplication(argc, argv), MultiMC::MultiMC(int &argc, char **argv, const QString &root)
m_version{VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_CHANNEL, VERSION_BUILD_TYPE} : QApplication(argc, argv), m_version{VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD,
VERSION_CHANNEL, VERSION_BUILD_TYPE}
{ {
setOrganizationName("MultiMC"); setOrganizationName("MultiMC");
setApplicationName("MultiMC5"); setApplicationName("MultiMC5");
initTranslations(); initTranslations();
setAttribute(Qt::AA_UseHighDpiPixmaps);
// Don't quit on hiding the last window // Don't quit on hiding the last window
this->setQuitOnLastWindowClosed(false); this->setQuitOnLastWindowClosed(false);
@ -137,8 +150,9 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &root) : QApplication(arg
} }
// change directory // change directory
QDir::setCurrent(args["dir"].toString().isEmpty() ? QDir::setCurrent(
(root.isEmpty() ? QDir::currentPath() : QDir::current().absoluteFilePath(root)) args["dir"].toString().isEmpty()
? (root.isEmpty() ? QDir::currentPath() : QDir::current().absoluteFilePath(root))
: args["dir"].toString()); : args["dir"].toString());
// init the logger // init the logger
@ -158,7 +172,7 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &root) : QApplication(arg
m_instances.reset(new InstanceList(InstDirSetting->get().toString(), this)); m_instances.reset(new InstanceList(InstDirSetting->get().toString(), this));
QLOG_INFO() << "Loading Instances..."; QLOG_INFO() << "Loading Instances...";
m_instances->loadList(); m_instances->loadList();
connect(InstDirSetting, SIGNAL(settingChanged(const Setting &, QVariant)), connect(InstDirSetting.get(), SIGNAL(settingChanged(const Setting &, QVariant)),
m_instances.get(), SLOT(on_InstFolderChanged(const Setting &, QVariant))); m_instances.get(), SLOT(on_InstFolderChanged(const Setting &, QVariant)));
// and accounts // and accounts
@ -179,7 +193,8 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &root) : QApplication(arg
{ {
QLOG_INFO() << "No proxy found."; QLOG_INFO() << "No proxy found.";
} }
else for (auto proxy : proxies) else
for (auto proxy : proxies)
{ {
QString proxyDesc; QString proxyDesc;
if (proxy.type() == QNetworkProxy::NoProxy) if (proxy.type() == QNetworkProxy::NoProxy)
@ -285,13 +300,26 @@ void MultiMC::initTranslations()
} }
} }
void moveFile(const QString &oldName, const QString &newName)
{
QFile::remove(newName);
QFile::copy(oldName, newName);
QFile::remove(oldName);
}
void MultiMC::initLogger() void MultiMC::initLogger()
{ {
static const QString logBase = "MultiMC-%0.log";
moveFile(logBase.arg(3), logBase.arg(4));
moveFile(logBase.arg(2), logBase.arg(3));
moveFile(logBase.arg(1), logBase.arg(2));
moveFile(logBase.arg(0), logBase.arg(1));
// init the logging mechanism // init the logging mechanism
QsLogging::Logger &logger = QsLogging::Logger::instance(); QsLogging::Logger &logger = QsLogging::Logger::instance();
logger.setLoggingLevel(QsLogging::TraceLevel); logger.setLoggingLevel(QsLogging::TraceLevel);
m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination("MultiMC.log"); m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination(logBase.arg(0));
m_debugDestination = QsLogging::DestinationFactory::MakeDebugOutputDestination(); m_debugDestination = QsLogging::DestinationFactory::MakeQDebugDestination();
logger.addDestination(m_fileDestination.get()); logger.addDestination(m_fileDestination.get());
logger.addDestination(m_debugDestination.get()); logger.addDestination(m_debugDestination.get());
// log all the things // log all the things
@ -302,67 +330,110 @@ void MultiMC::initGlobalSettings()
{ {
m_settings.reset(new INISettingsObject("multimc.cfg", this)); m_settings.reset(new INISettingsObject("multimc.cfg", this));
// Updates // Updates
m_settings->registerSetting(new Setting("UseDevBuilds", false)); m_settings->registerSetting("UseDevBuilds", false);
m_settings->registerSetting(new Setting("AutoUpdate", true)); m_settings->registerSetting("AutoUpdate", true);
// Folders // FTB
m_settings->registerSetting(new Setting("InstanceDir", "instances")); m_settings->registerSetting("TrackFTBInstances", false);
m_settings->registerSetting(new Setting("CentralModsDir", "mods")); #ifdef Q_OS_LINUX
m_settings->registerSetting(new Setting("LWJGLDir", "lwjgl")); QString ftbDefault = QDir::home().absoluteFilePath(".ftblauncher");
#elif defined(Q_OS_WIN32)
QString ftbDefault = PathCombine(QDir::homePath(), "AppData/Roaming/ftblauncher");
#elif defined(Q_OS_MAC)
QString ftbDefault =
PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher");
#endif
m_settings->registerSetting("FTBLauncherRoot", ftbDefault);
// Console m_settings->registerSetting("FTBRoot");
m_settings->registerSetting(new Setting("ShowConsole", true)); if (m_settings->get("FTBRoot").isNull())
m_settings->registerSetting(new Setting("AutoCloseConsole", true));
// Console Colors
// m_settings->registerSetting(new Setting("SysMessageColor", QColor(Qt::blue)));
// m_settings->registerSetting(new Setting("StdOutColor", QColor(Qt::black)));
// m_settings->registerSetting(new Setting("StdErrColor", QColor(Qt::red)));
// Window Size
m_settings->registerSetting(new Setting("LaunchMaximized", false));
m_settings->registerSetting(new Setting("MinecraftWinWidth", 854));
m_settings->registerSetting(new Setting("MinecraftWinHeight", 480));
// Auto login
m_settings->registerSetting(new Setting("AutoLogin", false));
// Memory
m_settings->registerSetting(new Setting("MinMemAlloc", 512));
m_settings->registerSetting(new Setting("MaxMemAlloc", 1024));
m_settings->registerSetting(new Setting("PermGen", 64));
// Java Settings
m_settings->registerSetting(new Setting("JavaPath", ""));
m_settings->registerSetting(new Setting("LastHostname", ""));
m_settings->registerSetting(new Setting("JvmArgs", ""));
// Custom Commands
m_settings->registerSetting(new Setting("PreLaunchCommand", ""));
m_settings->registerSetting(new Setting("PostExitCommand", ""));
// The cat
m_settings->registerSetting(new Setting("TheCat", false));
m_settings->registerSetting(new Setting("InstSortMode", "Name"));
m_settings->registerSetting(new Setting("SelectedInstance", QString()));
// Persistent value for the client ID
m_settings->registerSetting(new Setting("YggdrasilClientToken", ""));
QString currentYggID = m_settings->get("YggdrasilClientToken").toString();
if (currentYggID.isEmpty())
{ {
QUuid uuid = QUuid::createUuid(); QString ftbRoot;
m_settings->set("YggdrasilClientToken", uuid.toString()); QFile f(QDir(m_settings->get("FTBLauncherRoot").toString())
.absoluteFilePath("ftblaunch.cfg"));
QLOG_INFO() << "Attempting to read" << f.fileName();
if (f.open(QFile::ReadOnly))
{
const QString data = QString::fromLatin1(f.readAll());
QRegularExpression exp("installPath=(.*)");
ftbRoot = QDir::cleanPath(exp.match(data).captured(1));
#ifdef Q_OS_WIN32
if (!ftbRoot.isEmpty())
{
if (ftbRoot.at(0).isLetter() && ftbRoot.size() > 1 && ftbRoot.at(1) == '/')
{
ftbRoot.remove(1, 1);
}
}
#endif
if (ftbRoot.isEmpty())
{
QLOG_INFO() << "Failed to get FTB root path";
}
else
{
QLOG_INFO() << "FTB is installed at" << ftbRoot;
m_settings->set("FTBRoot", ftbRoot);
}
}
else
{
QLOG_WARN() << "Couldn't open" << f.fileName() << ":" << f.errorString();
QLOG_WARN() << "This is perfectly normal if you don't have FTB installed";
}
} }
// Window state and geometry // Folders
m_settings->registerSetting(new Setting("MainWindowState", "")); m_settings->registerSetting("InstanceDir", "instances");
m_settings->registerSetting(new Setting("MainWindowGeometry", "")); m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods");
m_settings->registerSetting({"LWJGLDir", "LwjglDir"}, "lwjgl");
m_settings->registerSetting("IconsDir", "icons");
m_settings->registerSetting(new Setting("ConsoleWindowState", "")); // Editors
m_settings->registerSetting(new Setting("ConsoleWindowGeometry", "")); m_settings->registerSetting("JsonEditor", QString());
// Console
m_settings->registerSetting("ShowConsole", true);
m_settings->registerSetting("AutoCloseConsole", true);
// Console Colors
// m_settings->registerSetting("SysMessageColor", QColor(Qt::blue));
// m_settings->registerSetting("StdOutColor", QColor(Qt::black));
// m_settings->registerSetting("StdErrColor", QColor(Qt::red));
// Window Size
m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false);
m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854);
m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480);
// Memory
m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024);
m_settings->registerSetting("PermGen", 64);
// Java Settings
m_settings->registerSetting("JavaPath", "");
m_settings->registerSetting("LastHostname", "");
m_settings->registerSetting("JvmArgs", "");
// Custom Commands
m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, "");
m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, "");
// The cat
m_settings->registerSetting("TheCat", false);
m_settings->registerSetting("InstSortMode", "Name");
m_settings->registerSetting("SelectedInstance", QString());
// Window state and geometry
m_settings->registerSetting("MainWindowState", "");
m_settings->registerSetting("MainWindowGeometry", "");
m_settings->registerSetting("ConsoleWindowState", "");
m_settings->registerSetting("ConsoleWindowGeometry", "");
m_settings->registerSetting("SettingsGeometry", "");
} }
void MultiMC::initHttpMetaCache() void MultiMC::initHttpMetaCache()
@ -374,6 +445,7 @@ void MultiMC::initHttpMetaCache()
m_metacache->addBase("libraries", QDir("libraries").absolutePath()); m_metacache->addBase("libraries", QDir("libraries").absolutePath());
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath()); m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir(".").absolutePath());
m_metacache->Load(); m_metacache->Load();
} }
@ -422,27 +494,20 @@ std::shared_ptr<JavaVersionList> MultiMC::javalist()
return m_javalist; return m_javalist;
} }
#ifdef WINDOWS
#define UPDATER_BIN "updater.exe"
#elif LINUX
#define UPDATER_BIN "updater"
#elif OSX
#define UPDATER_BIN "updater"
#else
#error Unsupported operating system.
#endif
void MultiMC::installUpdates(const QString &updateFilesDir, bool restartOnFinish) void MultiMC::installUpdates(const QString &updateFilesDir, bool restartOnFinish)
{ {
QLOG_INFO() << "Installing updates."; QLOG_INFO() << "Installing updates.";
#if LINUX #if LINUX
// On Linux, the MultiMC executable file is actually in the bin folder inside the installation directory. // On Linux, the MultiMC executable file is actually in the bin folder inside the
// installation directory.
// This means that MultiMC's *actual* install path is the parent folder. // This means that MultiMC's *actual* install path is the parent folder.
// We need to tell the updater to run with this directory as the install path, rather than the bin folder where the executable is. // We need to tell the updater to run with this directory as the install path, rather than
// the bin folder where the executable is.
// On other operating systems, we'll just use the path to the executable. // On other operating systems, we'll just use the path to the executable.
QString appDir = QFileInfo(MMC->applicationDirPath()).dir().path(); QString appDir = QFileInfo(MMC->applicationDirPath()).dir().path();
// On Linux, we also need to set the finish command to the launch script, rather than the binary. // On Linux, we also need to set the finish command to the launch script, rather than the
// binary.
QString finishCmd = PathCombine(appDir, "MultiMC"); QString finishCmd = PathCombine(appDir, "MultiMC");
#else #else
QString appDir = MMC->applicationDirPath(); QString appDir = MMC->applicationDirPath();
@ -450,11 +515,13 @@ void MultiMC::installUpdates(const QString& updateFilesDir, bool restartOnFinish
#endif #endif
// Build the command we'll use to run the updater. // Build the command we'll use to run the updater.
// Note, the above comment about the app dir path on Linux is irrelevant here because the updater binary is always in the // Note, the above comment about the app dir path on Linux is irrelevant here because the
// updater binary is always in the
// same folder as the main binary. // same folder as the main binary.
QString updaterBinary = PathCombine(MMC->applicationDirPath(), UPDATER_BIN); QString updaterBinary = PathCombine(MMC->applicationDirPath(), UPDATER_BIN);
QStringList args; QStringList args;
// ./updater --install-dir $INSTALL_DIR --package-dir $UPDATEFILES_DIR --script $UPDATEFILES_DIR/file_list.xml --wait $PID --mode main // ./updater --install-dir $INSTALL_DIR --package-dir $UPDATEFILES_DIR --script
// $UPDATEFILES_DIR/file_list.xml --wait $PID --mode main
args << "--install-dir" << appDir; args << "--install-dir" << appDir;
args << "--package-dir" << updateFilesDir; args << "--package-dir" << updateFilesDir;
args << "--script" << PathCombine(updateFilesDir, "file_list.xml"); args << "--script" << PathCombine(updateFilesDir, "file_list.xml");
@ -464,8 +531,13 @@ void MultiMC::installUpdates(const QString& updateFilesDir, bool restartOnFinish
args << "--finish-cmd" << finishCmd; args << "--finish-cmd" << finishCmd;
QLOG_INFO() << "Running updater with command" << updaterBinary << args.join(" "); QLOG_INFO() << "Running updater with command" << updaterBinary << args.join(" ");
QFile::setPermissions(updaterBinary, (QFileDevice::Permission)0x7755);
QProcess::startDetached(updaterBinary, args); if (!QProcess::startDetached(updaterBinary, args))
{
QLOG_ERROR() << "Failed to start the updater process!";
return;
}
// Now that we've started the updater, quit MultiMC. // Now that we've started the updater, quit MultiMC.
MMC->quit(); MMC->quit();
@ -481,5 +553,18 @@ QString MultiMC::getExitUpdatePath() const
return m_updateOnExitPath; return m_updateOnExitPath;
} }
bool MultiMC::openJsonEditor(const QString &filename)
{
const QString file = QDir::current().absoluteFilePath(filename);
if (m_settings->get("JsonEditor").toString().isEmpty())
{
return QDesktopServices::openUrl(QUrl::fromLocalFile(file));
}
else
{
return QProcess::startDetached(m_settings->get("JsonEditor").toString(), QStringList()
<< file);
}
}
#include "MultiMC.moc" #include "MultiMC.moc"

View File

@ -6,7 +6,6 @@
#include "logger/QsLog.h" #include "logger/QsLog.h"
#include "logger/QsLogDest.h" #include "logger/QsLogDest.h"
class MinecraftVersionList; class MinecraftVersionList;
class LWJGLVersionList; class LWJGLVersionList;
class HttpMetaCache; class HttpMetaCache;
@ -120,6 +119,12 @@ public:
*/ */
QString getExitUpdatePath() const; QString getExitUpdatePath() const;
/*!
* Opens a json file using either a system default editor, or, if note empty, the editor
* specified in the settings
*/
bool openJsonEditor(const QString &filename);
private: private:
void initLogger(); void initLogger();
@ -130,6 +135,9 @@ private:
void initTranslations(); void initTranslations();
private: private:
friend class UpdateCheckerTest;
friend class DownloadUpdateTaskTest;
std::shared_ptr<QTranslator> m_qt_translator; std::shared_ptr<QTranslator> m_qt_translator;
std::shared_ptr<QTranslator> m_mmc_translator; std::shared_ptr<QTranslator> m_mmc_translator;
std::shared_ptr<SettingsObject> m_settings; std::shared_ptr<SettingsObject> m_settings;

27
MultiMC.manifest Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity name="MultiMC.Application.5" type="win32" version="5.0.0.0" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<description>Custom Minecraft launcher for managing multiple installs.</description>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below indicates app support for Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!--The ID below indicates app support for Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--The ID below indicates app support for Windows Developer Preview / Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
</application>
</compatibility>
</assembly>

View File

@ -11,6 +11,16 @@ Check [BUILD.md](BUILD.md) for build instructions.
## Contributing ## Contributing
The repository is currently managed by @peterix and @drayshak - we're the ones likely to review pull requests. If you'd like to contribute to the project please talk to us on IRC (Esper/#MultiMC) first! This helps us organise ideas and keep in contact with you, and we're unlikely to accept anything blindly. The repository is currently managed by @peterix and @drayshak - we're the ones likely to review pull requests. If you'd like to contribute to the project please talk to us on IRC (Esper/#MultiMC) first! This helps us organise ideas and keep in contact with you, and we're unlikely to accept anything blindly.
We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the project. We highly recommend setting it up so the project stays well formatted, but there are issues with it on Windows. If you have trouble setting it up, check [.clang-format](.clang-format) manually. We don't accept pull requests with poor formatting. If you have questions, talk to us on IRC (Esper/#MultiMC) _before_ submitting a pull request.
## Forking/Redistributing
We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
Part of the reason for using the Apache license is we don't want people using the "MultiMC" name when redistributing the project. This means people must take the time to go through the source code and remove all references to "MultiMC", including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).
Apache covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork *without* implying that you have our blessing.
## License ## License
Copyright &copy; 2013 MultiMC Contributors Copyright &copy; 2013 MultiMC Contributors

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<string>True</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
</dict>
</plist>

View File

@ -8,21 +8,36 @@
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
if (argc == 3) if (argc != 3)
{ {
try
{
unpack_200(argv[1], argv[2]);
}
catch (std::runtime_error &e)
{
std::cerr << "Bad things happened: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
else
std::cerr << "Simple pack200 unpacker!" << std::endl << "Run like this:" << std::endl std::cerr << "Simple pack200 unpacker!" << std::endl << "Run like this:" << std::endl
<< " " << argv[0] << " input.jar.lzma output.jar" << std::endl; << " " << argv[0] << " input.jar.lzma output.jar" << std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
FILE *input = fopen(argv[1], "rb");
FILE *output = fopen(argv[2], "wb");
if (!input)
{
std::cerr << "Can't open input file";
return EXIT_FAILURE;
}
if (!output)
{
fclose(output);
std::cerr << "Can't open output file";
return EXIT_FAILURE;
}
try
{
unpack_200(input, output);
}
catch (std::runtime_error &e)
{
std::cerr << "Bad things happened: " << e.what() << std::endl;
fclose(input);
fclose(output);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -34,4 +34,4 @@
* @return void * @return void
* @throw std::runtime_error for any error encountered * @throw std::runtime_error for any error encountered
*/ */
void unpack_200(std::string input_path, std::string output_path); void unpack_200(FILE * input, FILE * output);

View File

@ -94,20 +94,9 @@ static int read_magic(unpacker *u, char peek[], int peeklen)
return magic; return magic;
} }
void unpack_200(std::string input_path, std::string output_path) void unpack_200(FILE *input, FILE *output)
{ {
unpacker u; unpacker u;
FILE *input = fopen(input_path.c_str(), "rb");
if (!input)
{
throw std::runtime_error("Can't open input file" + input_path);
}
FILE *output = fopen(output_path.c_str(), "wb");
if (!output)
{
fclose(output);
throw std::runtime_error("Can't open output file" + output_path);
}
u.init(read_input_via_stdio); u.init(read_input_via_stdio);
// initialize jar output // initialize jar output

View File

@ -5,44 +5,27 @@ find_package(Qt5Core REQUIRED)
# Include Qt headers. # Include Qt headers.
include_directories(${Qt5Base_INCLUDE_DIRS}) include_directories(${Qt5Base_INCLUDE_DIRS})
include_directories(${Qt5Network_INCLUDE_DIRS})
SET(LIBSETTINGS_HEADERS
include/libsettings_config.h
include/inifile.h
include/settingsobject.h
include/setting.h
include/overridesetting.h
include/basicsettingsobject.h
include/inisettingsobject.h
include/keyring.h
)
SET(LIBSETTINGS_HEADERS_PRIVATE
src/stubkeyring.h
)
SET(LIBSETTINGS_SOURCES SET(LIBSETTINGS_SOURCES
src/inifile.cpp libsettings_config.h
src/settingsobject.cpp inifile.h
src/setting.cpp inifile.cpp
src/overridesetting.cpp
src/basicsettingsobject.cpp settingsobject.h
src/inisettingsobject.cpp settingsobject.cpp
inisettingsobject.h
inisettingsobject.cpp
src/keyring.cpp setting.h
src/stubkeyring.cpp setting.cpp
overridesetting.h
overridesetting.cpp
) )
# Set the include dir path. # Set the include dir path.
SET(LIBSETTINGS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) SET(LIBSETTINGS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE)
include_directories(${LIBSETTINGS_INCLUDE_DIR})
# Static link! # Static link!
ADD_DEFINITIONS(-DLIBSETTINGS_STATIC) ADD_DEFINITIONS(-DLIBSETTINGS_STATIC)
@ -59,6 +42,6 @@ IF(MultiMC_CODE_COVERAGE)
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage")
ENDIF(MultiMC_CODE_COVERAGE) ENDIF(MultiMC_CODE_COVERAGE)
add_library(libSettings STATIC ${LIBSETTINGS_SOURCES} ${LIBSETTINGS_HEADERS} ${LIBSETTINGS_HEADERS_PRIVATE}) add_library(libSettings STATIC ${LIBSETTINGS_SOURCES})
qt5_use_modules(libSettings Core) qt5_use_modules(libSettings Core)
target_link_libraries(libSettings) target_link_libraries(libSettings)

View File

@ -1,97 +0,0 @@
/* 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 <QString>
#include "libsettings_config.h"
/**
* @file libsettings/include/keyring.h
* Access to System Keyrings
*/
/**
* @brief The Keyring class
* the System Keyring/Keychain/Wallet/Vault/etc
*/
class LIBSETTINGS_EXPORT Keyring
{
public:
/**
* @brief virtual dtor
*/
virtual ~Keyring() {};
/**
* @brief the System Keyring instance
* @return the Keyring instance
*/
static Keyring *instance();
/**
* @brief store a password in the Keyring
* @param service the service name
* @param username the account name
* @param password the password to store
* @return success
*/
virtual bool storePassword(QString service, QString username, QString password) = 0;
/**
* @brief get a password from the Keyring
* @param service the service name
* @param username the account name
* @return the password (success=!isNull())
*/
virtual QString getPassword(QString service, QString username) = 0;
/**
* @brief lookup a password
* @param service the service name
* @param username the account name
* @return wether the password is available
*/
virtual bool hasPassword(QString service, QString username) = 0;
/**
* @brief get a list of all stored accounts.
* @param service the service name
* @return
*/
virtual QStringList getStoredAccounts(QString service) = 0;
/**
* @brief Remove the specified account from storage
* @param service the service name
* @param username the account name
* @return
*/
virtual void removeStoredAccount(QString service, QString username) = 0;
protected:
/// fall back to StubKeyring if false
virtual bool isValid()
{
return false;
}
private:
static Keyring *m_instance;
static void destroy();
};

View File

@ -13,7 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include "include/inifile.h" #include "inifile.h"
#include <QFile> #include <QFile>
#include <QTextStream> #include <QTextStream>

View File

@ -13,8 +13,8 @@
* limitations under the License. * limitations under the License.
*/ */
#include "include/inisettingsobject.h" #include "inisettingsobject.h"
#include "include/setting.h" #include "setting.h"
INISettingsObject::INISettingsObject(const QString &path, QObject *parent) INISettingsObject::INISettingsObject(const QString &path, QObject *parent)
: SettingsObject(parent) : SettingsObject(parent)
@ -32,31 +32,45 @@ void INISettingsObject::changeSetting(const Setting &setting, QVariant value)
{ {
if (contains(setting.id())) if (contains(setting.id()))
{ {
// valid value -> set the main config, remove all the sysnonyms
if (value.isValid()) if (value.isValid())
m_ini.set(setting.configKey(), value); {
auto list = setting.configKeys();
m_ini.set(list.takeFirst(), value);
for(auto iter: list)
m_ini.remove(iter);
}
// invalid -> remove all (just like resetSetting)
else else
m_ini.remove(setting.configKey()); {
for(auto iter: setting.configKeys())
m_ini.remove(iter);
}
m_ini.saveFile(m_filePath); m_ini.saveFile(m_filePath);
} }
} }
void INISettingsObject::resetSetting(const Setting &setting) void INISettingsObject::resetSetting(const Setting &setting)
{ {
// if we have the setting, remove all the synonyms. ALL OF THEM
if (contains(setting.id())) if (contains(setting.id()))
{ {
m_ini.remove(setting.configKey()); for(auto iter: setting.configKeys())
m_ini.remove(iter);
m_ini.saveFile(m_filePath); m_ini.saveFile(m_filePath);
} }
} }
QVariant INISettingsObject::retrieveValue(const Setting &setting) QVariant INISettingsObject::retrieveValue(const Setting &setting)
{ {
// if we have the setting, return value of the first matching synonym
if (contains(setting.id())) if (contains(setting.id()))
{ {
return m_ini.get(setting.configKey(), QVariant()); for(auto iter: setting.configKeys())
}
else
{ {
if(m_ini.contains(iter))
return m_ini[iter];
}
}
return QVariant(); return QVariant();
} }
}

View File

@ -26,3 +26,4 @@
#define LIBSETTINGS_EXPORT Q_DECL_IMPORT #define LIBSETTINGS_EXPORT Q_DECL_IMPORT
#endif #endif
#endif #endif

View File

@ -13,10 +13,10 @@
* limitations under the License. * limitations under the License.
*/ */
#include "include/overridesetting.h" #include "overridesetting.h"
OverrideSetting::OverrideSetting(const QString &name, Setting *other, QObject *parent) OverrideSetting::OverrideSetting(std::shared_ptr<Setting> other)
: Setting(name, QVariant(), parent) : Setting(other->configKeys(), QVariant())
{ {
m_other = other; m_other = other;
} }

View File

@ -16,6 +16,7 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <memory>
#include "setting.h" #include "setting.h"
@ -31,10 +32,10 @@ class LIBSETTINGS_EXPORT OverrideSetting : public Setting
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit OverrideSetting(const QString &name, Setting *other, QObject *parent = 0); explicit OverrideSetting(std::shared_ptr<Setting> other);
virtual QVariant defValue() const; virtual QVariant defValue() const;
protected: protected:
Setting *m_other; std::shared_ptr<Setting> m_other;
}; };

View File

@ -13,17 +13,17 @@
* limitations under the License. * limitations under the License.
*/ */
#include "include/setting.h" #include "setting.h"
#include "include/settingsobject.h" #include "settingsobject.h"
Setting::Setting(QString id, QVariant defVal, QObject *parent) Setting::Setting(QStringList synonyms, QVariant defVal)
: QObject(parent), m_id(id), m_defVal(defVal) : QObject(), m_synonyms(synonyms), m_defVal(defVal)
{ {
} }
QVariant Setting::get() const QVariant Setting::get() const
{ {
SettingsObject *sbase = qobject_cast<SettingsObject *>(parent()); SettingsObject *sbase = m_storage;
if (!sbase) if (!sbase)
{ {
return defValue(); return defValue();

View File

@ -17,6 +17,8 @@
#include <QObject> #include <QObject>
#include <QVariant> #include <QVariant>
#include <QStringList>
#include <memory>
#include "libsettings_config.h" #include "libsettings_config.h"
@ -29,11 +31,16 @@ class LIBSETTINGS_EXPORT Setting : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
/*! /**
* \brief Constructs a new Setting object with the given parent. * Construct a Setting
* \param parent The Setting's parent object. *
* Synonyms are all the possible names used in the settings object, in order of preference.
* First synonym is the ID, which identifies the setting in MultiMC.
*
* defVal is the default value that will be returned when the settings object
* doesn't have any value for this setting.
*/ */
explicit Setting(QString id, QVariant defVal = QVariant(), QObject *parent = 0); explicit Setting(QStringList synonyms, QVariant defVal = QVariant());
/*! /*!
* \brief Gets this setting's ID. * \brief Gets this setting's ID.
@ -44,7 +51,7 @@ public:
*/ */
virtual QString id() const virtual QString id() const
{ {
return m_id; return m_synonyms.first();
} }
/*! /*!
@ -53,9 +60,9 @@ public:
* the same as the setting's ID, but it can be different. * the same as the setting's ID, but it can be different.
* \return The setting's config file key. * \return The setting's config file key.
*/ */
virtual QString configKey() const virtual QStringList configKeys() const
{ {
return id(); return m_synonyms;
} }
/*! /*!
@ -67,16 +74,6 @@ public:
*/ */
virtual QVariant get() const; virtual QVariant get() const;
/*!
* \brief Gets this setting's actual value (I.E. not as a QVariant).
* This function is just shorthand for get().value<T>()
* \return The setting's actual value.
*/
template <typename T> inline T value() const
{
return get().value<T>();
}
/*! /*!
* \brief Gets this setting's default value. * \brief Gets this setting's default value.
* \return The default value of this setting. * \return The default value of this setting.
@ -111,11 +108,12 @@ slots:
* \brief Reset the setting to default * \brief Reset the setting to default
* This is done by emitting the settingReset() signal which will then be * This is done by emitting the settingReset() signal which will then be
* handled by the SettingsObject object and cause the setting to change. * handled by the SettingsObject object and cause the setting to change.
* \param value The new value.
*/ */
virtual void reset(); virtual void reset();
protected: protected:
QString m_id; friend class SettingsObject;
SettingsObject * m_storage;
QStringList m_synonyms;
QVariant m_defVal; QVariant m_defVal;
}; };

View File

@ -13,8 +13,9 @@
* limitations under the License. * limitations under the License.
*/ */
#include "include/settingsobject.h" #include "settingsobject.h"
#include "include/setting.h" #include "setting.h"
#include "overridesetting.h"
#include <QVariant> #include <QVariant>
@ -22,17 +23,49 @@ SettingsObject::SettingsObject(QObject *parent) : QObject(parent)
{ {
} }
bool SettingsObject::registerSetting(Setting *setting) SettingsObject::~SettingsObject()
{ {
// Check if setting is null or we already have a setting with the same ID. m_settings.clear();
if (!setting)
{
qDebug(QString("Failed to register setting. Setting is null.")
.arg(setting->id())
.toUtf8());
return false; // Fail
} }
std::shared_ptr<Setting> SettingsObject::registerOverride(std::shared_ptr<Setting> original)
{
if (contains(original->id()))
{
qDebug(QString("Failed to register setting %1. ID already exists.")
.arg(original->id())
.toUtf8());
return nullptr; // Fail
}
auto override = std::make_shared<OverrideSetting>(original);
override->m_storage = this;
connectSignals(*override);
m_settings.insert(override->id(), override);
return override;
}
std::shared_ptr<Setting> SettingsObject::registerSetting(QStringList synonyms, QVariant defVal)
{
if (synonyms.empty())
return nullptr;
if (contains(synonyms.first()))
{
qDebug(QString("Failed to register setting %1. ID already exists.")
.arg(synonyms.first())
.toUtf8());
return nullptr; // Fail
}
auto setting = std::make_shared<Setting>(synonyms, defVal);
setting->m_storage = this;
connectSignals(*setting);
m_settings.insert(setting->id(), setting);
return setting;
}
/*
bool SettingsObject::registerSetting(Setting *setting)
{
if (contains(setting->id())) if (contains(setting->id()))
{ {
qDebug(QString("Failed to register setting %1. ID already exists.") qDebug(QString("Failed to register setting %1. ID already exists.")
@ -50,21 +83,8 @@ bool SettingsObject::registerSetting(Setting *setting)
// qDebug(QString("Registered setting %1.").arg(setting->id()).toUtf8()); // qDebug(QString("Registered setting %1.").arg(setting->id()).toUtf8());
return true; return true;
} }
*/
void SettingsObject::unregisterSetting(Setting *setting) std::shared_ptr<Setting> SettingsObject::getSetting(const QString &id) const
{
if (!setting || !m_settings.contains(setting->id()))
return; // We can't unregister something that's not registered.
m_settings.remove(setting->id());
// Disconnect signals.
disconnectSignals(*setting);
setting->setParent(NULL); // Drop ownership.
}
Setting *SettingsObject::getSetting(const QString &id) const
{ {
// Make sure there is a setting with the given ID. // Make sure there is a setting with the given ID.
if (!m_settings.contains(id)) if (!m_settings.contains(id))
@ -75,13 +95,13 @@ Setting *SettingsObject::getSetting(const QString &id) const
QVariant SettingsObject::get(const QString &id) const QVariant SettingsObject::get(const QString &id) const
{ {
Setting *setting = getSetting(id); auto setting = getSetting(id);
return (setting ? setting->get() : QVariant()); return (setting ? setting->get() : QVariant());
} }
bool SettingsObject::set(const QString &id, QVariant value) bool SettingsObject::set(const QString &id, QVariant value)
{ {
Setting *setting = getSetting(id); auto setting = getSetting(id);
if (!setting) if (!setting)
{ {
qDebug(QString("Error changing setting %1. Setting doesn't exist.").arg(id).toUtf8()); qDebug(QString("Error changing setting %1. Setting doesn't exist.").arg(id).toUtf8());
@ -96,16 +116,11 @@ bool SettingsObject::set(const QString &id, QVariant value)
void SettingsObject::reset(const QString &id) const void SettingsObject::reset(const QString &id) const
{ {
Setting *setting = getSetting(id); auto setting = getSetting(id);
if (setting) if (setting)
setting->reset(); setting->reset();
} }
QList<Setting *> SettingsObject::getSettings()
{
return m_settings.values();
}
bool SettingsObject::contains(const QString &id) bool SettingsObject::contains(const QString &id)
{ {
return m_settings.contains(id); return m_settings.contains(id);
@ -121,16 +136,3 @@ void SettingsObject::connectSignals(const Setting &setting)
connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &))); connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &)));
connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &))); connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &)));
} }
void SettingsObject::disconnectSignals(const Setting &setting)
{
setting.disconnect(SIGNAL(settingChanged(const Setting &, QVariant)), this,
SLOT(changeSetting(const Setting &, QVariant)));
setting.disconnect(SIGNAL(settingChanged(const Setting &, QVariant)), this,
SIGNAL(settingChanged(const Setting &, QVariant)));
setting.disconnect(SIGNAL(settingReset(const Setting &, QVariant)), this,
SLOT(resetSetting(const Setting &, QVariant)));
setting.disconnect(SIGNAL(settingReset(const Setting &, QVariant)), this,
SIGNAL(settingReset(const Setting &, QVariant)));
}

View File

@ -17,6 +17,9 @@
#include <QObject> #include <QObject>
#include <QMap> #include <QMap>
#include <QStringList>
#include <QVariant>
#include <memory>
#include "libsettings_config.h" #include "libsettings_config.h"
@ -39,32 +42,37 @@ class LIBSETTINGS_EXPORT SettingsObject : public QObject
Q_OBJECT Q_OBJECT
public: public:
explicit SettingsObject(QObject *parent = 0); explicit SettingsObject(QObject *parent = 0);
virtual ~SettingsObject();
/*! /*!
* \brief Registers the given setting with this SettingsObject and connects the necessary * Registers an override setting for the given original setting in this settings object
* signals. *
* This will fail if there is already a setting with the same ID as * This will fail if there is already a setting with the same ID as
* the one that is being registered. * the one that is being registered.
* \note Registering a setting object causes the SettingsObject to take ownership * \return A valid Setting shared pointer if successful.
* of the object. This means that setting's parent will be set to the object
* it was registered with. Because the object it was registered with has taken
* ownership, it becomes responsible for managing that setting object's memory.
* \warning Do \b not delete the setting after registering it.
* \param setting A pointer to the setting that will be registered.
* \return True if successful. False if registry failed.
*/ */
virtual bool registerSetting(Setting *setting); std::shared_ptr<Setting> registerOverride(std::shared_ptr<Setting> original);
/*! /*!
* \brief Unregisters the given setting from this SettingsObject and disconnects its * Registers the given setting with this SettingsObject and connects the necessary signals.
* signals. *
* \note This does not delete the setting. Furthermore, when the setting is * This will fail if there is already a setting with the same ID as
* unregistered, the SettingsObject drops ownership of the setting. This means * the one that is being registered.
* that if you unregister a setting, its parent is set to null and you become * \return A valid Setting shared pointer if successful.
* responsible for freeing its memory.
* \param setting The setting to unregister.
*/ */
virtual void unregisterSetting(Setting *setting); std::shared_ptr<Setting> registerSetting(QStringList synonyms,
QVariant defVal = QVariant());
/*!
* Registers the given setting with this SettingsObject and connects the necessary signals.
*
* This will fail if there is already a setting with the same ID as
* the one that is being registered.
* \return A valid Setting shared pointer if successful.
*/
std::shared_ptr<Setting> registerSetting(QString id, QVariant defVal = QVariant())
{
return registerSetting(QStringList(id), defVal);
}
/*! /*!
* \brief Gets the setting with the given ID. * \brief Gets the setting with the given ID.
@ -73,18 +81,7 @@ public:
* Returns null if there is no setting with the given ID. * Returns null if there is no setting with the given ID.
* \sa operator []() * \sa operator []()
*/ */
virtual Setting *getSetting(const QString &id) const; std::shared_ptr<Setting> getSetting(const QString &id) const;
/*!
* \brief Same as getSetting()
* \param id The ID of the setting to get.
* \return A pointer to the setting with the given ID.
* \sa getSetting()
*/
inline Setting *operator[](const QString &id)
{
return getSetting(id);
}
/*! /*!
* \brief Gets the value of the setting with the given ID. * \brief Gets the value of the setting with the given ID.
@ -92,7 +89,7 @@ public:
* \return The setting's value as a QVariant. * \return The setting's value as a QVariant.
* If no setting with the given ID exists, returns an invalid QVariant. * If no setting with the given ID exists, returns an invalid QVariant.
*/ */
virtual QVariant get(const QString &id) const; QVariant get(const QString &id) const;
/*! /*!
* \brief Sets the value of the setting with the given ID. * \brief Sets the value of the setting with the given ID.
@ -101,27 +98,20 @@ public:
* \param value The new value of the setting. * \param value The new value of the setting.
* \return True if successful, false if it failed. * \return True if successful, false if it failed.
*/ */
virtual bool set(const QString &id, QVariant value); bool set(const QString &id, QVariant value);
/*! /*!
* \brief Reverts the setting with the given ID to default. * \brief Reverts the setting with the given ID to default.
* \param id The ID of the setting to reset. * \param id The ID of the setting to reset.
*/ */
virtual void reset(const QString &id) const; void reset(const QString &id) const;
/*!
* \brief Gets a QList with pointers to all of the registered settings.
* The order of the entries in the list is undefined.
* \return A QList with pointers to all registered settings.
*/
virtual QList<Setting *> getSettings();
/*! /*!
* \brief Checks if this SettingsObject contains a setting with the given ID. * \brief Checks if this SettingsObject contains a setting with the given ID.
* \param id The ID to check for. * \param id The ID to check for.
* \return True if the SettingsObject has a setting with the given ID. * \return True if the SettingsObject has a setting with the given ID.
*/ */
virtual bool contains(const QString &id); bool contains(const QString &id);
signals: signals:
/*! /*!
@ -167,13 +157,7 @@ protected:
* \brief Connects the necessary signals to the given Setting. * \brief Connects the necessary signals to the given Setting.
* \param setting The setting to connect. * \param setting The setting to connect.
*/ */
virtual void connectSignals(const Setting &setting); void connectSignals(const Setting &setting);
/*!
* \brief Disconnects signals from the given Setting.
* \param setting The setting to disconnect.
*/
virtual void disconnectSignals(const Setting &setting);
/*! /*!
* \brief Function used by Setting objects to get their values from the SettingsObject. * \brief Function used by Setting objects to get their values from the SettingsObject.
@ -185,5 +169,5 @@ protected:
friend class Setting; friend class Setting;
private: private:
QMap<QString, Setting *> m_settings; QMap<QString, std::shared_ptr<Setting>> m_settings;
}; };

View File

@ -1,63 +0,0 @@
/* 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 "include/keyring.h"
#include "osutils.h"
#include "stubkeyring.h"
// system specific keyrings
/*#if defined(OSX)
class OSXKeychain;
#define KEYRING OSXKeychain
#elif defined(LINUX)
class XDGKeyring;
#define KEYRING XDGKeyring
#elif defined(WINDOWS)
class Win32Keystore;
#define KEYRING Win32Keystore
#else
#pragma message Keyrings are not supported on your os. Falling back to the insecure StubKeyring
#endif*/
Keyring *Keyring::instance()
{
if (m_instance == nullptr)
{
#ifdef KEYRING
m_instance = new KEYRING();
if (!m_instance->isValid())
{
qWarning("Could not create SystemKeyring! falling back to StubKeyring.");
m_instance = new StubKeyring();
}
#else
qWarning("Keyrings are not supported on your OS. Fallback StubKeyring is insecure!");
m_instance = new StubKeyring();
#endif
atexit(Keyring::destroy);
}
return m_instance;
}
void Keyring::destroy()
{
delete m_instance;
}
Keyring *Keyring::m_instance;

View File

@ -1,105 +0,0 @@
/* 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 "stubkeyring.h"
#include <QStringList>
// Scrambling
// this is NOT SAFE, but it's not plain either.
int scrambler = 0x9586309;
QString scramble(QString in_)
{
QByteArray in = in_.toUtf8();
QByteArray out;
for (int i = 0; i < in.length(); i++)
out.append(in.at(i) ^ scrambler);
return QString::fromUtf8(out);
}
inline QString base64(QString in)
{
return QString(in.toUtf8().toBase64());
}
inline QString unbase64(QString in)
{
return QString::fromUtf8(QByteArray::fromBase64(in.toLatin1()));
}
inline QString scramble64(QString in)
{
return base64(scramble(in));
}
inline QString unscramble64(QString in)
{
return scramble(unbase64(in));
}
// StubKeyring implementation
inline QString generateKey(QString service, QString username)
{
return QString("%1/%2").arg(base64(service)).arg(scramble64(username));
}
bool StubKeyring::storePassword(QString service, QString username, QString password)
{
m_settings.setValue(generateKey(service, username), scramble64(password));
return true;
}
QString StubKeyring::getPassword(QString service, QString username)
{
QString key = generateKey(service, username);
if (!m_settings.contains(key))
return QString();
return unscramble64(m_settings.value(key).toString());
}
bool StubKeyring::hasPassword(QString service, QString username)
{
return m_settings.contains(generateKey(service, username));
}
QStringList StubKeyring::getStoredAccounts(QString service)
{
service = base64(service).append('/');
QStringList out;
QStringList in(m_settings.allKeys());
QStringListIterator it(in);
while (it.hasNext())
{
QString c = it.next();
if (c.startsWith(service))
out << unscramble64(c.mid(service.length()));
}
return out;
}
void StubKeyring::removeStoredAccount(QString service, QString username)
{
QString key = generateKey(service, username);
m_settings.remove(key);
}
// FIXME: this needs tweaking/changes for user account level storage
StubKeyring::StubKeyring()
:
// m_settings(QSettings::UserScope, "Orochimarufan", "Keyring")
m_settings("keyring.cfg", QSettings::IniFormat)
{
}

View File

@ -1,47 +0,0 @@
/* 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 "include/keyring.h"
#include <QSettings>
class StubKeyring : public Keyring
{
public:
/**
* @brief virtual dtor
*/
virtual ~StubKeyring() {};
virtual bool storePassword(QString service, QString username, QString password);
virtual QString getPassword(QString service, QString username);
virtual bool hasPassword(QString service, QString username);
virtual QStringList getStoredAccounts(QString service);
virtual void removeStoredAccount(QString service, QString username);
private:
friend class Keyring;
explicit StubKeyring();
virtual bool isValid()
{
return true;
}
QSettings m_settings;
};

View File

@ -23,10 +23,7 @@
QString PathCombine(QString path1, QString path2) QString PathCombine(QString path1, QString path2)
{ {
if (!path1.endsWith('/')) return QDir::cleanPath(path1 + QDir::separator() + path2);
return path1.append('/').append(path2);
else
return path1.append(path2);
} }
QString PathCombine(QString path1, QString path2, QString path3) QString PathCombine(QString path1, QString path2, QString path3)

View File

@ -58,10 +58,17 @@ ConsoleWindow::~ConsoleWindow()
void ConsoleWindow::writeColor(QString text, const char *color) void ConsoleWindow::writeColor(QString text, const char *color)
{ {
// append a paragraph // append a paragraph
if (color != nullptr) QString newtext;
ui->text->appendHtml(QString("<font color=\"%1\">%2</font>").arg(color).arg(text)); newtext += "<span style=\"";
else {
ui->text->appendPlainText(text); if(color)
newtext += QString("color:") + color + ";";
newtext += "font-family: monospace;";
}
newtext += "\">";
newtext += text.toHtmlEscaped();
newtext += "</span>";
ui->text->appendHtml(newtext);
} }
void ConsoleWindow::write(QString data, MessageLevel::Enum mode) void ConsoleWindow::write(QString data, MessageLevel::Enum mode)

View File

@ -17,11 +17,6 @@
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QPlainTextEdit" name="text"> <widget class="QPlainTextEdit" name="text">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="undoRedoEnabled"> <property name="undoRedoEnabled">
<bool>false</bool> <bool>false</bool>
</property> </property>

View File

@ -66,7 +66,7 @@
#include "logic/lists/InstanceList.h" #include "logic/lists/InstanceList.h"
#include "logic/lists/MinecraftVersionList.h" #include "logic/lists/MinecraftVersionList.h"
#include "logic/lists/LwjglVersionList.h" #include "logic/lists/LwjglVersionList.h"
#include "logic/lists/IconList.h" #include "logic/icons/IconList.h"
#include "logic/lists/JavaVersionList.h" #include "logic/lists/JavaVersionList.h"
#include "logic/auth/flows/AuthenticateTask.h" #include "logic/auth/flows/AuthenticateTask.h"
@ -90,7 +90,9 @@
#include "logic/LegacyInstance.h" #include "logic/LegacyInstance.h"
#include "logic/assets/AssetsUtils.h" #include "logic/assets/AssetsUtils.h"
#include "logic/assets/AssetsMigrateTask.h"
#include <logic/updater/UpdateChecker.h> #include <logic/updater/UpdateChecker.h>
#include <logic/tasks/ThreadTask.h>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{ {
@ -99,7 +101,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
setWindowTitle(QString("MultiMC %1").arg(MMC->version().toString())); setWindowTitle(QString("MultiMC %1").arg(MMC->version().toString()));
// OSX magic. // OSX magic.
setUnifiedTitleAndToolBarOnMac(true); // setUnifiedTitleAndToolBarOnMac(true);
// The instance action toolbar customizations // The instance action toolbar customizations
{ {
@ -178,6 +180,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
connect(view->selectionModel(), connect(view->selectionModel(),
SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this,
SLOT(instanceChanged(const QModelIndex &, const QModelIndex &))); SLOT(instanceChanged(const QModelIndex &, const QModelIndex &)));
// track icon changes and update the toolbar!
connect(MMC->icons().get(), SIGNAL(iconUpdated(QString)), SLOT(iconUpdated(QString)));
// model reset -> selection is invalid. All the instance pointers are wrong. // model reset -> selection is invalid. All the instance pointers are wrong.
// FIXME: stop using POINTERS everywhere // FIXME: stop using POINTERS everywhere
connect(MMC->instances().get(), SIGNAL(dataIsInvalid()), SLOT(selectionBad())); connect(MMC->instances().get(), SIGNAL(dataIsInvalid()), SLOT(selectionBad()));
@ -264,8 +270,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// set up the updater object. // set up the updater object.
auto updater = MMC->updateChecker(); auto updater = MMC->updateChecker();
QObject::connect(updater.get(), &UpdateChecker::updateAvailable, this, connect(updater.get(), &UpdateChecker::updateAvailable, this,
&MainWindow::updateAvailable); &MainWindow::updateAvailable);
connect(updater.get(), &UpdateChecker::noUpdateFound, [this]()
{
CustomMessageBox::selectable(
this, tr("No update found."),
tr("No MultiMC update was found!\nYou are using the latest version."))->exec();
});
// if automatic update checks are allowed, start one. // if automatic update checks are allowed, start one.
if (MMC->settings()->get("AutoUpdate").toBool()) if (MMC->settings()->get("AutoUpdate").toBool())
on_actionCheckUpdate_triggered(); on_actionCheckUpdate_triggered();
@ -292,8 +304,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// removing this looks stupid // removing this looks stupid
view->setFocus(); view->setFocus();
AssetsUtils::migrateOldAssets();
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
@ -502,7 +512,7 @@ void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit
if (installOnExit) if (installOnExit)
MMC->setUpdateOnExit(updateTask.updateFilesDir()); MMC->setUpdateOnExit(updateTask.updateFilesDir());
else else
MMC->installUpdates(updateTask.updateFilesDir()); MMC->installUpdates(updateTask.updateFilesDir(), true);
} }
} }
@ -674,6 +684,20 @@ void MainWindow::on_actionChangeInstIcon_triggered()
} }
} }
void MainWindow::iconUpdated(QString icon)
{
if(icon == m_currentInstIcon)
{
ui->actionChangeInstIcon->setIcon(MMC->icons()->getIcon(m_currentInstIcon));
}
}
void MainWindow::updateInstanceToolIcon(QString new_icon)
{
m_currentInstIcon = new_icon;
ui->actionChangeInstIcon->setIcon(MMC->icons()->getIcon(m_currentInstIcon));
}
void MainWindow::on_actionChangeInstGroup_triggered() void MainWindow::on_actionChangeInstGroup_triggered()
{ {
if (!m_selectedInstance) if (!m_selectedInstance)
@ -721,7 +745,8 @@ void MainWindow::on_actionConfig_Folder_triggered()
void MainWindow::on_actionCheckUpdate_triggered() void MainWindow::on_actionCheckUpdate_triggered()
{ {
auto updater = MMC->updateChecker(); auto updater = MMC->updateChecker();
updater->checkForUpdate();
updater->checkForUpdate(true);
} }
void MainWindow::on_actionSettings_triggered() void MainWindow::on_actionSettings_triggered()
@ -905,6 +930,8 @@ void MainWindow::doLaunch()
if (!account.get()) if (!account.get())
return; return;
QString failReason = tr("Your account is currently not logged in. Please enter "
"your password to log in again.");
// do the login. if the account has an access token, try to refresh it first. // do the login. if the account has an access token, try to refresh it first.
if (account->accountStatus() != NotVerified) if (account->accountStatus() != NotVerified)
{ {
@ -919,14 +946,29 @@ void MainWindow::doLaunch()
{ {
updateInstance(m_selectedInstance, account); updateInstance(m_selectedInstance, account);
} }
// revert from online to verified. else
account->downgrade(); {
return; if (!task->successful())
{
failReason = task->failReason();
} }
if (loginWithPassword(account, tr("Your account is currently not logged in. Please enter " if (loginWithPassword(account, failReason))
"your password to log in again.")))
updateInstance(m_selectedInstance, account); updateInstance(m_selectedInstance, account);
} }
// in any case, revert from online to verified.
account->downgrade();
}
else
{
if (loginWithPassword(account, failReason))
{
updateInstance(m_selectedInstance, account);
account->downgrade();
}
// in any case, revert from online to verified.
account->downgrade();
}
}
bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString &errorMsg) bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString &errorMsg)
{ {
@ -1042,8 +1084,19 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
VersionSelectDialog vselect(m_selectedInstance->versionList().get(), VersionSelectDialog vselect(m_selectedInstance->versionList().get(),
tr("Change Minecraft version"), this); tr("Change Minecraft version"), this);
vselect.setFilter(1, "OneSix"); vselect.setFilter(1, "OneSix");
if (vselect.exec() && vselect.selectedVersion()) if(!vselect.exec() || !vselect.selectedVersion())
return;
if (!MMC->accounts()->anyAccountIsValid())
{ {
CustomMessageBox::selectable(
this, tr("Error"),
tr("MultiMC cannot download Minecraft or update instances unless you have at least "
"one account added.\nPlease add your Mojang or Minecraft account."),
QMessageBox::Warning)->show();
return;
}
if (m_selectedInstance->versionIsCustom()) if (m_selectedInstance->versionIsCustom())
{ {
auto result = CustomMessageBox::selectable( auto result = CustomMessageBox::selectable(
@ -1057,17 +1110,8 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
return; return;
} }
m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor()); m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor());
}
if (!MMC->accounts()->anyAccountIsValid()) auto updateTask = m_selectedInstance->doUpdate(false);
{
CustomMessageBox::selectable(
this, tr("Error"),
tr("MultiMC cannot download Minecraft or update instances unless you have at least "
"one account added.\nPlease add your Mojang or Minecraft account."),
QMessageBox::Warning)->show();
return;
}
auto updateTask = m_selectedInstance->doUpdate(false /*only_prepare*/);
if (!updateTask) if (!updateTask)
{ {
return; return;
@ -1109,7 +1153,6 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
.value<void *>())) .value<void *>()))
{ {
ui->instanceToolBar->setEnabled(true); ui->instanceToolBar->setEnabled(true);
QString iconKey = m_selectedInstance->iconKey();
renameButton->setText(m_selectedInstance->name()); renameButton->setText(m_selectedInstance->name());
ui->actionChangeInstLWJGLVersion->setEnabled( ui->actionChangeInstLWJGLVersion->setEnabled(
m_selectedInstance->menuActionEnabled("actionChangeInstLWJGLVersion")); m_selectedInstance->menuActionEnabled("actionChangeInstLWJGLVersion"));
@ -1118,8 +1161,7 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
ui->actionChangeInstMCVersion->setEnabled( ui->actionChangeInstMCVersion->setEnabled(
m_selectedInstance->menuActionEnabled("actionChangeInstMCVersion")); m_selectedInstance->menuActionEnabled("actionChangeInstMCVersion"));
m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
auto ico = MMC->icons()->getIcon(iconKey); updateInstanceToolIcon(m_selectedInstance->iconKey());
ui->actionChangeInstIcon->setIcon(ico);
MMC->settings()->set("SelectedInstance", m_selectedInstance->id()); MMC->settings()->set("SelectedInstance", m_selectedInstance->id());
} }
@ -1134,12 +1176,11 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
void MainWindow::selectionBad() void MainWindow::selectionBad()
{ {
m_selectedInstance = nullptr; m_selectedInstance = nullptr;
QString iconKey = "infinity";
statusBar()->clearMessage(); statusBar()->clearMessage();
ui->instanceToolBar->setEnabled(false); ui->instanceToolBar->setEnabled(false);
renameButton->setText(tr("Rename Instance")); renameButton->setText(tr("Rename Instance"));
auto ico = MMC->icons()->getIcon(iconKey); updateInstanceToolIcon("infinity");
ui->actionChangeInstIcon->setIcon(ico);
} }
void MainWindow::on_actionEditInstNotes_triggered() void MainWindow::on_actionEditInstNotes_triggered()
@ -1162,6 +1203,32 @@ void MainWindow::instanceEnded()
this->show(); this->show();
} }
void MainWindow::checkMigrateLegacyAssets()
{
int legacyAssets = AssetsUtils::findLegacyAssets();
if(legacyAssets > 0)
{
ProgressDialog migrateDlg(this);
AssetsMigrateTask migrateTask(legacyAssets, &migrateDlg);
{
ThreadTask threadTask(&migrateTask);
if (migrateDlg.exec(&threadTask))
{
QLOG_INFO() << "Assets migration task completed successfully";
}
else
{
QLOG_INFO() << "Assets migration task reported failure";
}
}
}
else
{
QLOG_INFO() << "Didn't find any legacy assets to migrate";
}
}
void MainWindow::checkSetDefaultJava() void MainWindow::checkSetDefaultJava()
{ {
bool askForJava = false; bool askForJava = false;

View File

@ -51,6 +51,7 @@ public:
void openWebPage(QUrl url); void openWebPage(QUrl url);
void checkSetDefaultJava(); void checkSetDefaultJava();
void checkMigrateLegacyAssets();
private private
slots: slots:
@ -145,6 +146,9 @@ slots:
void assetsFailed(); void assetsFailed();
void assetsFinished(); void assetsFinished();
// called when an icon is changed in the icon model.
void iconUpdated(QString);
public public
slots: slots:
void instanceActivated(QModelIndex); void instanceActivated(QModelIndex);
@ -173,6 +177,7 @@ slots:
protected: protected:
bool eventFilter(QObject *obj, QEvent *ev); bool eventFilter(QObject *obj, QEvent *ev);
void setCatBackground(bool enabled); void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon);
private: private:
Ui::MainWindow *ui; Ui::MainWindow *ui;
@ -186,6 +191,7 @@ private:
QToolButton* newsLabel; QToolButton* newsLabel;
BaseInstance *m_selectedInstance; BaseInstance *m_selectedInstance;
QString m_currentInstIcon;
Task *m_versionLoadTask; Task *m_versionLoadTask;

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>706</width> <width>707</width>
<height>579</height> <height>593</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
@ -103,8 +103,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>688</width> <width>685</width>
<height>313</height> <height>304</height>
</rect> </rect>
</property> </property>
<attribute name="label"> <attribute name="label">
@ -113,6 +113,9 @@
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QLabel" name="aboutLabel"> <widget class="QLabel" name="aboutLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;MultiMC is a custom launcher that makes managing Minecraft easier by allowing you to have multiple instances of Minecraft at once.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;MultiMC is a custom launcher that makes managing Minecraft easier by allowing you to have multiple instances of Minecraft at once.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
@ -162,8 +165,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>688</width> <width>685</width>
<height>313</height> <height>304</height>
</rect> </rect>
</property> </property>
<attribute name="label"> <attribute name="label">
@ -179,13 +182,22 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;Andrew Okin &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:forkk@forkk.net&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;&quot;&gt;forkk@forkk.net&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt; font-weight:600;&quot;&gt;MultiMC&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;Petr Mrázek &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:peterix@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;&quot;&gt;peterix@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Andrew Okin &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:forkk@forkk.net&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;forkk@forkk.net&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;Orochimarufan &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:orochimarufan.x3@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;&quot;&gt;orochimarufan.x3@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Petr Mrázek &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:peterix@gmail.com&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;peterix@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;TakSuyu &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:taksuyu@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;&quot;&gt;taksuyu@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Sky &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://www.twitter.com/drayshak&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@drayshak&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;Sky (Drayshak) &amp;lt;&lt;/span&gt;&lt;span style=&quot; font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;&quot;&gt;multimc@bunnies.cc&lt;/span&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;Kilobyte &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:stiepen22@gmx.de&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;&quot;&gt;stiepen22@gmx.de&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt; font-weight:600;&quot;&gt;With thanks to&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Orochimarufan &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:orochimarufan.x3@gmail.com&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;orochimarufan.x3@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;TakSuyu &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:taksuyu@gmail.com&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;taksuyu@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Kilobyte &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:stiepen22@gmx.de&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;stiepen22@gmx.de&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Jan (02JanDal) &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:02jandal@gmail.com&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;02jandal@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Robotbrain &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/skylordelros&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@skylordelros&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Rootbear75 &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/rootbear75&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@rootbear75&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&amp;gt; (build server)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -206,8 +218,8 @@ p, li { white-space: pre-wrap; }
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>688</width> <width>684</width>
<height>313</height> <height>290</height>
</rect> </rect>
</property> </property>
<attribute name="label"> <attribute name="label">
@ -234,9 +246,9 @@ p, li { white-space: pre-wrap; }
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:7.8pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;MultiMC&lt;/span&gt;&lt;/p&gt; &lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;MultiMC&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Copyright 2012 MultiMC Contributors&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Copyright 2012-2014 MultiMC Contributors&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Licensed under the Apache License, Version 2.0 (the &amp;quot;License&amp;quot;);&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Licensed under the Apache License, Version 2.0 (the &amp;quot;License&amp;quot;);&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;you may not use this file except in compliance with the License.&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;you may not use this file except in compliance with the License.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;You may obtain a copy of the License at&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;You may obtain a copy of the License at&lt;/span&gt;&lt;/p&gt;
@ -361,6 +373,39 @@ p, li { white-space: pre-wrap; }
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="forkPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>684</width>
<height>290</height>
</rect>
</property>
<attribute name="label">
<string>Forking/Redistribution</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_33">
<item>
<widget class="QTextEdit" name="textEdit">
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;Part of the reason for using the Apache license is we don't want people using the &amp;quot;MultiMC&amp;quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &amp;quot;MultiMC&amp;quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork &lt;/span&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:600;&quot;&gt;without&lt;/span&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt; implying that you have our blessing.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -26,7 +26,9 @@
#include <gui/dialogs/EditAccountDialog.h> #include <gui/dialogs/EditAccountDialog.h>
#include <gui/dialogs/ProgressDialog.h> #include <gui/dialogs/ProgressDialog.h>
#include <gui/dialogs/AccountSelectDialog.h> #include <gui/dialogs/AccountSelectDialog.h>
#include "CustomMessageBox.h"
#include <logic/tasks/Task.h> #include <logic/tasks/Task.h>
#include <logic/auth/YggdrasilTask.h>
#include <MultiMC.h> #include <MultiMC.h>
@ -147,5 +149,12 @@ void AccountListDialog::addAccount(const QString& errMsg)
job->start(); job->start();
} }
else
{
auto reason = task->failReason();
auto dlg = CustomMessageBox::selectable(this, tr("Login error."), reason, QMessageBox::Critical);
dlg->exec();
delete dlg;
}
} }
} }

View File

@ -27,7 +27,7 @@
#include "logic/InstanceFactory.h" #include "logic/InstanceFactory.h"
#include "logic/BaseVersion.h" #include "logic/BaseVersion.h"
#include "logic/lists/IconList.h" #include "logic/icons/IconList.h"
#include "logic/lists/MinecraftVersionList.h" #include "logic/lists/MinecraftVersionList.h"
#include "logic/tasks/Task.h" #include "logic/tasks/Task.h"
#include "logic/BaseInstance.h" #include "logic/BaseInstance.h"

View File

@ -15,6 +15,8 @@
#include "EditAccountDialog.h" #include "EditAccountDialog.h"
#include "ui_EditAccountDialog.h" #include "ui_EditAccountDialog.h"
#include <QDesktopServices>
#include <QUrl>
EditAccountDialog::EditAccountDialog(const QString &text, QWidget *parent, int flags) EditAccountDialog::EditAccountDialog(const QString &text, QWidget *parent, int flags)
: QDialog(parent), ui(new Ui::EditAccountDialog) : QDialog(parent), ui(new Ui::EditAccountDialog)
@ -33,6 +35,11 @@ EditAccountDialog::~EditAccountDialog()
delete ui; delete ui;
} }
void EditAccountDialog::on_label_linkActivated(const QString &link)
{
QDesktopServices::openUrl(QUrl(link));
}
QString EditAccountDialog::username() const QString EditAccountDialog::username() const
{ {
return ui->userTextBox->text(); return ui->userTextBox->text();

View File

@ -52,6 +52,9 @@ public:
PasswordField, PasswordField,
}; };
private slots:
void on_label_linkActivated(const QString &link);
private: private:
Ui::EditAccountDialog *ui; Ui::EditAccountDialog *ui;
}; };

View File

@ -19,6 +19,12 @@
<property name="text"> <property name="text">
<string>Message label placeholder.</string> <string>Message label placeholder.</string>
</property> </property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -25,7 +25,7 @@
#include "gui/Platform.h" #include "gui/Platform.h"
#include "gui/widgets/InstanceDelegate.h" #include "gui/widgets/InstanceDelegate.h"
#include "logic/lists/IconList.h" #include "logic/icons/IconList.h"
IconPickerDialog::IconPickerDialog(QWidget *parent) IconPickerDialog::IconPickerDialog(QWidget *parent)
: QDialog(parent), ui(new Ui::IconPickerDialog) : QDialog(parent), ui(new Ui::IconPickerDialog)
@ -103,7 +103,7 @@ void IconPickerDialog::addNewIcon()
QString selectIcons = tr("Select Icons"); QString selectIcons = tr("Select Icons");
//: The type of icon files //: The type of icon files
QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(),
tr("Icons") + "(*.png *.jpg *.jpeg)"); tr("Icons") + "(*.png *.jpg *.jpeg *.ico)");
MMC->icons()->installIcons(fileNames); MMC->icons()->installIcons(fileNames);
} }

View File

@ -36,6 +36,9 @@ InstanceSettings::InstanceSettings(SettingsObject *obj, QWidget *parent)
{ {
MultiMCPlatform::fixWM_CLASS(this); MultiMCPlatform::fixWM_CLASS(this);
ui->setupUi(this); ui->setupUi(this);
restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("SettingsGeometry").toByteArray()));
loadSettings(); loadSettings();
} }
@ -47,7 +50,13 @@ InstanceSettings::~InstanceSettings()
void InstanceSettings::showEvent(QShowEvent *ev) void InstanceSettings::showEvent(QShowEvent *ev)
{ {
QDialog::showEvent(ev); QDialog::showEvent(ev);
adjustSize(); }
void InstanceSettings::closeEvent(QCloseEvent *ev)
{
MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
QDialog::closeEvent(ev);
} }
void InstanceSettings::on_customCommandsGroupBox_toggled(bool state) void InstanceSettings::on_customCommandsGroupBox_toggled(bool state)
@ -57,12 +66,16 @@ void InstanceSettings::on_customCommandsGroupBox_toggled(bool state)
void InstanceSettings::on_buttonBox_accepted() void InstanceSettings::on_buttonBox_accepted()
{ {
MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
applySettings(); applySettings();
accept(); accept();
} }
void InstanceSettings::on_buttonBox_rejected() void InstanceSettings::on_buttonBox_rejected()
{ {
MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
reject(); reject();
} }
@ -98,18 +111,6 @@ void InstanceSettings::applySettings()
m_obj->reset("MinecraftWinHeight"); m_obj->reset("MinecraftWinHeight");
} }
// Auto Login
bool login = ui->accountSettingsBox->isChecked();
m_obj->set("OverrideLogin", login);
if (login)
{
m_obj->set("AutoLogin", ui->autoLoginCheckBox->isChecked());
}
else
{
m_obj->reset("AutoLogin");
}
// Memory // Memory
bool memory = ui->memoryGroupBox->isChecked(); bool memory = ui->memoryGroupBox->isChecked();
m_obj->set("OverrideMemory", memory); m_obj->set("OverrideMemory", memory);
@ -170,10 +171,6 @@ void InstanceSettings::loadSettings()
ui->windowWidthSpinBox->setValue(m_obj->get("MinecraftWinWidth").toInt()); ui->windowWidthSpinBox->setValue(m_obj->get("MinecraftWinWidth").toInt());
ui->windowHeightSpinBox->setValue(m_obj->get("MinecraftWinHeight").toInt()); ui->windowHeightSpinBox->setValue(m_obj->get("MinecraftWinHeight").toInt());
// Auto Login
ui->accountSettingsBox->setChecked(m_obj->get("OverrideLogin").toBool());
ui->autoLoginCheckBox->setChecked(m_obj->get("AutoLogin").toBool());
// Memory // Memory
ui->memoryGroupBox->setChecked(m_obj->get("OverrideMemory").toBool()); ui->memoryGroupBox->setChecked(m_obj->get("OverrideMemory").toBool());
ui->minMemSpinBox->setValue(m_obj->get("MinMemAlloc").toInt()); ui->minMemSpinBox->setValue(m_obj->get("MinMemAlloc").toInt());

View File

@ -39,6 +39,7 @@ public:
protected: protected:
virtual void showEvent(QShowEvent *); virtual void showEvent(QShowEvent *);
virtual void closeEvent(QCloseEvent *);
private private
slots: slots:
void on_customCommandsGroupBox_toggled(bool arg1); void on_customCommandsGroupBox_toggled(bool arg1);

View File

@ -131,31 +131,6 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="accountSettingsBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Account Settings</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QCheckBox" name="autoLoginCheckBox">
<property name="text">
<string>Login automatically when an instance icon is double clicked?</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacerMinecraft"> <spacer name="verticalSpacerMinecraft">
<property name="orientation"> <property name="orientation">
@ -411,7 +386,6 @@
<tabstop>consoleSettingsBox</tabstop> <tabstop>consoleSettingsBox</tabstop>
<tabstop>showConsoleCheck</tabstop> <tabstop>showConsoleCheck</tabstop>
<tabstop>autoCloseConsoleCheck</tabstop> <tabstop>autoCloseConsoleCheck</tabstop>
<tabstop>accountSettingsBox</tabstop>
<tabstop>memoryGroupBox</tabstop> <tabstop>memoryGroupBox</tabstop>
<tabstop>minMemSpinBox</tabstop> <tabstop>minMemSpinBox</tabstop>
<tabstop>maxMemSpinBox</tabstop> <tabstop>maxMemSpinBox</tabstop>

View File

@ -19,7 +19,7 @@
#include "logic/InstanceFactory.h" #include "logic/InstanceFactory.h"
#include "logic/BaseVersion.h" #include "logic/BaseVersion.h"
#include "logic/lists/IconList.h" #include "logic/icons/IconList.h"
#include "logic/lists/MinecraftVersionList.h" #include "logic/lists/MinecraftVersionList.h"
#include "logic/tasks/Task.h" #include "logic/tasks/Task.h"

View File

@ -38,6 +38,7 @@
#include "logic/EnabledItemFilter.h" #include "logic/EnabledItemFilter.h"
#include "logic/lists/ForgeVersionList.h" #include "logic/lists/ForgeVersionList.h"
#include "logic/ForgeInstaller.h" #include "logic/ForgeInstaller.h"
#include "logic/LiteLoaderInstaller.h"
OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent) OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent)
: QDialog(parent), ui(new Ui::OneSixModEditDialog), m_inst(inst) : QDialog(parent), ui(new Ui::OneSixModEditDialog), m_inst(inst)
@ -95,6 +96,8 @@ void OneSixModEditDialog::updateVersionControls()
ui->customizeBtn->setEnabled(!customVersion); ui->customizeBtn->setEnabled(!customVersion);
ui->revertBtn->setEnabled(customVersion); ui->revertBtn->setEnabled(customVersion);
ui->forgeBtn->setEnabled(true); ui->forgeBtn->setEnabled(true);
ui->liteloaderBtn->setEnabled(LiteLoaderInstaller(m_inst->intendedVersionId()).canApply());
ui->customEditorBtn->setEnabled(customVersion);
} }
void OneSixModEditDialog::disableVersionControls() void OneSixModEditDialog::disableVersionControls()
@ -102,6 +105,8 @@ void OneSixModEditDialog::disableVersionControls()
ui->customizeBtn->setEnabled(false); ui->customizeBtn->setEnabled(false);
ui->revertBtn->setEnabled(false); ui->revertBtn->setEnabled(false);
ui->forgeBtn->setEnabled(false); ui->forgeBtn->setEnabled(false);
ui->liteloaderBtn->setEnabled(false);
ui->customEditorBtn->setEnabled(false);
} }
void OneSixModEditDialog::on_customizeBtn_clicked() void OneSixModEditDialog::on_customizeBtn_clicked()
@ -131,6 +136,17 @@ void OneSixModEditDialog::on_revertBtn_clicked()
} }
} }
void OneSixModEditDialog::on_customEditorBtn_clicked()
{
if (m_inst->versionIsCustom())
{
if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/custom.json"))
{
QMessageBox::warning(this, tr("Error"), tr("Unable to open custom.json, check the settings"));
}
}
}
void OneSixModEditDialog::on_forgeBtn_clicked() void OneSixModEditDialog::on_forgeBtn_clicked()
{ {
VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this);
@ -204,6 +220,32 @@ void OneSixModEditDialog::on_forgeBtn_clicked()
} }
} }
void OneSixModEditDialog::on_liteloaderBtn_clicked()
{
LiteLoaderInstaller liteloader(m_inst->intendedVersionId());
if (!liteloader.canApply())
{
QMessageBox::critical(
this, tr("LiteLoader"),
tr("There is no information available on how to install LiteLoader "
"into this version of Minecraft"));
return;
}
if (!m_inst->versionIsCustom())
{
m_inst->customizeVersion();
m_version = m_inst->getFullVersion();
main_model->setSourceModel(m_version.get());
updateVersionControls();
}
if (!liteloader.apply(m_version))
{
QMessageBox::critical(
this, tr("LiteLoader"),
tr("For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details."));
}
}
bool OneSixModEditDialog::loaderListFilter(QKeyEvent *keyEvent) bool OneSixModEditDialog::loaderListFilter(QKeyEvent *keyEvent)
{ {
switch (keyEvent->key()) switch (keyEvent->key())

View File

@ -44,8 +44,10 @@ slots:
// Questionable: SettingsDialog doesn't need this for some reason? // Questionable: SettingsDialog doesn't need this for some reason?
void on_buttonBox_rejected(); void on_buttonBox_rejected();
void on_forgeBtn_clicked(); void on_forgeBtn_clicked();
void on_liteloaderBtn_clicked();
void on_customizeBtn_clicked(); void on_customizeBtn_clicked();
void on_revertBtn_clicked(); void on_revertBtn_clicked();
void on_customEditorBtn_clicked();
void updateVersionControls(); void updateVersionControls();
void disableVersionControls(); void disableVersionControls();

View File

@ -77,6 +77,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="liteloaderBtn">
<property name="text">
<string>Install LiteLoader</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="customizeBtn"> <widget class="QPushButton" name="customizeBtn">
<property name="toolTip"> <property name="toolTip">
@ -136,6 +143,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="customEditorBtn">
<property name="text">
<string>Open custom.json</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer_7"> <spacer name="verticalSpacer_7">
<property name="orientation"> <property name="orientation">

View File

@ -40,6 +40,12 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Se
ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name);
ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch);
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
ui->jsonEditorTextBox->setClearButtonEnabled(true);
#endif
restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("SettingsGeometry").toByteArray()));
loadSettings(MMC->settings().get()); loadSettings(MMC->settings().get());
updateCheckboxStuff(); updateCheckboxStuff();
} }
@ -51,7 +57,13 @@ SettingsDialog::~SettingsDialog()
void SettingsDialog::showEvent(QShowEvent *ev) void SettingsDialog::showEvent(QShowEvent *ev)
{ {
QDialog::showEvent(ev); QDialog::showEvent(ev);
adjustSize(); }
void SettingsDialog::closeEvent(QCloseEvent *ev)
{
MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
QDialog::closeEvent(ev);
} }
void SettingsDialog::updateCheckboxStuff() void SettingsDialog::updateCheckboxStuff()
@ -60,6 +72,32 @@ void SettingsDialog::updateCheckboxStuff()
ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked());
} }
void SettingsDialog::on_ftbLauncherBrowseBtn_clicked()
{
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("FTB Launcher Directory"),
ui->ftbLauncherBox->text());
QString cooked_dir = NormalizePath(raw_dir);
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
{
ui->ftbLauncherBox->setText(cooked_dir);
}
}
void SettingsDialog::on_ftbBrowseBtn_clicked()
{
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("FTB Directory"),
ui->ftbBox->text());
QString cooked_dir = NormalizePath(raw_dir);
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
{
ui->ftbBox->setText(cooked_dir);
}
}
void SettingsDialog::on_instDirBrowseBtn_clicked() void SettingsDialog::on_instDirBrowseBtn_clicked()
{ {
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Directory"), QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Directory"),
@ -72,6 +110,18 @@ void SettingsDialog::on_instDirBrowseBtn_clicked()
ui->instDirTextBox->setText(cooked_dir); ui->instDirTextBox->setText(cooked_dir);
} }
} }
void SettingsDialog::on_iconsDirBrowseBtn_clicked()
{
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Directory"),
ui->iconsDirTextBox->text());
QString cooked_dir = NormalizePath(raw_dir);
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
{
ui->iconsDirTextBox->setText(cooked_dir);
}
}
void SettingsDialog::on_modsDirBrowseBtn_clicked() void SettingsDialog::on_modsDirBrowseBtn_clicked()
{ {
@ -99,6 +149,36 @@ void SettingsDialog::on_lwjglDirBrowseBtn_clicked()
} }
} }
void SettingsDialog::on_jsonEditorBrowseBtn_clicked()
{
QString raw_file = QFileDialog::getOpenFileName(
this, tr("JSON Editor"),
ui->jsonEditorTextBox->text().isEmpty()
#if defined(Q_OS_LINUX)
? QString("/usr/bin")
#else
? QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).first()
#endif
: ui->jsonEditorTextBox->text());
QString cooked_file = NormalizePath(raw_file);
if (cooked_file.isEmpty())
{
return;
}
// it has to exist and be an executable
if (QFileInfo(cooked_file).exists() &&
QFileInfo(cooked_file).isExecutable())
{
ui->jsonEditorTextBox->setText(cooked_file);
}
else
{
QMessageBox::warning(this, tr("Invalid"), tr("The file chosen does not seem to be an executable"));
}
}
void SettingsDialog::on_maximizedCheckBox_clicked(bool checked) void SettingsDialog::on_maximizedCheckBox_clicked(bool checked)
{ {
Q_UNUSED(checked); Q_UNUSED(checked);
@ -108,6 +188,13 @@ void SettingsDialog::on_maximizedCheckBox_clicked(bool checked)
void SettingsDialog::on_buttonBox_accepted() void SettingsDialog::on_buttonBox_accepted()
{ {
applySettings(MMC->settings().get()); applySettings(MMC->settings().get());
MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
}
void SettingsDialog::on_buttonBox_rejected()
{
MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
} }
void SettingsDialog::applySettings(SettingsObject *s) void SettingsDialog::applySettings(SettingsObject *s)
@ -135,11 +222,29 @@ void SettingsDialog::applySettings(SettingsObject *s)
// Updates // Updates
s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
// FTB
s->set("TrackFTBInstances", ui->trackFtbBox->isChecked());
s->set("FTBLauncherRoot", ui->ftbLauncherBox->text());
s->set("FTBRoot", ui->ftbBox->text());
// Folders // Folders
// TODO: Offer to move instances to new instance folder. // TODO: Offer to move instances to new instance folder.
s->set("InstanceDir", ui->instDirTextBox->text()); s->set("InstanceDir", ui->instDirTextBox->text());
s->set("CentralModsDir", ui->modsDirTextBox->text()); s->set("CentralModsDir", ui->modsDirTextBox->text());
s->set("LWJGLDir", ui->lwjglDirTextBox->text()); s->set("LWJGLDir", ui->lwjglDirTextBox->text());
s->set("IconsDir", ui->iconsDirTextBox->text());
// Editors
QString jsonEditor = ui->jsonEditorTextBox->text();
if (!jsonEditor.isEmpty() && (!QFileInfo(jsonEditor).exists() || !QFileInfo(jsonEditor).isExecutable()))
{
QString found = QStandardPaths::findExecutable(jsonEditor);
if (!found.isEmpty())
{
jsonEditor = found;
}
}
s->set("JsonEditor", jsonEditor);
// Console // Console
s->set("ShowConsole", ui->showConsoleCheck->isChecked()); s->set("ShowConsole", ui->showConsoleCheck->isChecked());
@ -150,9 +255,6 @@ void SettingsDialog::applySettings(SettingsObject *s)
s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value());
s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
// Auto Login
s->set("AutoLogin", ui->autoLoginCheckBox->isChecked());
// Memory // Memory
s->set("MinMemAlloc", ui->minMemSpinBox->value()); s->set("MinMemAlloc", ui->minMemSpinBox->value());
s->set("MaxMemAlloc", ui->maxMemSpinBox->value()); s->set("MaxMemAlloc", ui->maxMemSpinBox->value());
@ -188,10 +290,19 @@ void SettingsDialog::loadSettings(SettingsObject *s)
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
ui->devBuildsCheckBox->setChecked(s->get("UseDevBuilds").toBool()); ui->devBuildsCheckBox->setChecked(s->get("UseDevBuilds").toBool());
// FTB
ui->trackFtbBox->setChecked(s->get("TrackFTBInstances").toBool());
ui->ftbLauncherBox->setText(s->get("FTBLauncherRoot").toString());
ui->ftbBox->setText(s->get("FTBRoot").toString());
// Folders // Folders
ui->instDirTextBox->setText(s->get("InstanceDir").toString()); ui->instDirTextBox->setText(s->get("InstanceDir").toString());
ui->modsDirTextBox->setText(s->get("CentralModsDir").toString()); ui->modsDirTextBox->setText(s->get("CentralModsDir").toString());
ui->lwjglDirTextBox->setText(s->get("LWJGLDir").toString()); ui->lwjglDirTextBox->setText(s->get("LWJGLDir").toString());
ui->iconsDirTextBox->setText(s->get("IconsDir").toString());
// Editors
ui->jsonEditorTextBox->setText(s->get("JsonEditor").toString());
// Console // Console
ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool()); ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool());
@ -202,9 +313,6 @@ void SettingsDialog::loadSettings(SettingsObject *s)
ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());
ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt());
// Auto Login
ui->autoLoginCheckBox->setChecked(s->get("AutoLogin").toBool());
// Memory // Memory
ui->minMemSpinBox->setValue(s->get("MinMemAlloc").toInt()); ui->minMemSpinBox->setValue(s->get("MinMemAlloc").toInt());
ui->maxMemSpinBox->setValue(s->get("MaxMemAlloc").toInt()); ui->maxMemSpinBox->setValue(s->get("MaxMemAlloc").toInt());

View File

@ -41,20 +41,32 @@ public:
void loadSettings(SettingsObject *s); void loadSettings(SettingsObject *s);
protected: protected:
virtual void showEvent(QShowEvent *); virtual void showEvent(QShowEvent *ev);
virtual void closeEvent(QCloseEvent *ev);
private private
slots: slots:
void on_ftbLauncherBrowseBtn_clicked();
void on_ftbBrowseBtn_clicked();
void on_instDirBrowseBtn_clicked(); void on_instDirBrowseBtn_clicked();
void on_modsDirBrowseBtn_clicked(); void on_modsDirBrowseBtn_clicked();
void on_lwjglDirBrowseBtn_clicked(); void on_lwjglDirBrowseBtn_clicked();
void on_jsonEditorBrowseBtn_clicked();
void on_iconsDirBrowseBtn_clicked();
void on_maximizedCheckBox_clicked(bool checked); void on_maximizedCheckBox_clicked(bool checked);
void on_buttonBox_accepted(); void on_buttonBox_accepted();
void on_buttonBox_rejected();
void on_javaDetectBtn_clicked(); void on_javaDetectBtn_clicked();
void on_javaTestBtn_clicked(); void on_javaTestBtn_clicked();

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>526</width> <width>526</width>
<height>599</height> <height>628</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -39,7 +39,7 @@
<attribute name="title"> <attribute name="title">
<string>General</string> <string>General</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="generalTabLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QGroupBox" name="sortingModeBox"> <widget class="QGroupBox" name="sortingModeBox">
<property name="enabled"> <property name="enabled">
@ -95,6 +95,93 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>FTB</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="2">
<widget class="QPushButton" name="ftbLauncherBrowseBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>28</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Launcher:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="ftbLauncherBox">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="trackFtbBox">
<property name="text">
<string>Track FTB instances</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="ftbBox"/>
</item>
<item row="3" column="2">
<widget class="QPushButton" name="ftbBrowseBtn">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>28</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Files:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="foldersBox"> <widget class="QGroupBox" name="foldersBox">
<property name="title"> <property name="title">
@ -128,6 +215,9 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="modsDirTextBox"/> <widget class="QLineEdit" name="modsDirTextBox"/>
</item> </item>
<item row="2" column="1">
<widget class="QLineEdit" name="lwjglDirTextBox"/>
</item>
<item row="1" column="2"> <item row="1" column="2">
<widget class="QToolButton" name="modsDirBrowseBtn"> <widget class="QToolButton" name="modsDirBrowseBtn">
<property name="text"> <property name="text">
@ -142,9 +232,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QLineEdit" name="lwjglDirTextBox"/>
</item>
<item row="2" column="2"> <item row="2" column="2">
<widget class="QToolButton" name="lwjglDirBrowseBtn"> <widget class="QToolButton" name="lwjglDirBrowseBtn">
<property name="text"> <property name="text">
@ -152,6 +239,49 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1">
<widget class="QLineEdit" name="iconsDirTextBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelIconsDir">
<property name="text">
<string>Icons:</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QToolButton" name="iconsDirBrowseBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="editorsBox">
<property name="title">
<string>External Editors (leave empty for system default)</string>
</property>
<layout class="QGridLayout" name="foldersBoxLayout_2">
<item row="0" column="1">
<widget class="QLineEdit" name="jsonEditorTextBox"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelJsonEditor">
<property name="text">
<string>JSON Editor:</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="jsonEditorBrowseBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -261,22 +391,6 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="accountSettingsBox">
<property name="title">
<string>Account Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="autoLoginCheckBox">
<property name="text">
<string>Login automatically when an instance icon is double clicked?</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacerMinecraft"> <spacer name="verticalSpacerMinecraft">
<property name="orientation"> <property name="orientation">
@ -394,25 +508,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>JVM arguments:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="5">
<widget class="QLineEdit" name="javaPathTextBox"/>
</item>
<item row="3" column="1" colspan="5">
<widget class="QLineEdit" name="jvmArgsTextBox"/>
</item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QPushButton" name="javaDetectBtn"> <widget class="QPushButton" name="javaDetectBtn">
<property name="sizePolicy"> <property name="sizePolicy">
@ -426,7 +521,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="4" colspan="2"> <item row="1" column="2">
<widget class="QPushButton" name="javaTestBtn"> <widget class="QPushButton" name="javaTestBtn">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -439,19 +534,48 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2" colspan="2"> <item row="2" column="0">
<widget class="QPushButton" name="javaBrowseBtn"> <widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Browse...</string> <string>JVM arguments:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="javaPathTextBox"/>
</item>
<item>
<widget class="QPushButton" name="javaBrowseBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>28</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="jvmArgsTextBox"/>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -520,7 +644,7 @@
</layout> </layout>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>settingsTabs</tabstop> <tabstop>settingsTab</tabstop>
<tabstop>buttonBox</tabstop> <tabstop>buttonBox</tabstop>
<tabstop>sortLastLaunchedBtn</tabstop> <tabstop>sortLastLaunchedBtn</tabstop>
<tabstop>sortByNameBtn</tabstop> <tabstop>sortByNameBtn</tabstop>

View File

@ -30,7 +30,7 @@ void MCModInfoFrame::updateWithMod(Mod &m)
QString text = ""; QString text = "";
QString name = ""; QString name = "";
if(m.name().isEmpty()) name = m.id(); if(m.name().isEmpty()) name = m.mmc_id();
else name = m.name(); else name = m.name();
if(m.homeurl().isEmpty()) text = name; if(m.homeurl().isEmpty()) text = name;

View File

@ -44,8 +44,9 @@ void ModListView::setModel ( QAbstractItemModel* model )
QTreeView::setModel ( model ); QTreeView::setModel ( model );
auto head = header(); auto head = header();
head->setStretchLastSection(false); head->setStretchLastSection(false);
head->setSectionResizeMode(0, QHeaderView::Stretch); head->setSectionResizeMode(0, QHeaderView::ResizeToContents);
for(int i = 1; i < head->count(); i++) head->setSectionResizeMode(1, QHeaderView::Stretch);
for(int i = 2; i < head->count(); i++)
head->setSectionResizeMode(i, QHeaderView::ResizeToContents); head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
dropIndicatorPosition(); dropIndicatorPosition();
} }

View File

@ -77,6 +77,15 @@ void DebugOutputDestination::write(const QString &message)
QsDebugOutput::output(message); QsDebugOutput::output(message);
} }
class QDebugDestination : public Destination
{
public:
virtual void write(const QString &message)
{
qDebug() << message;
};
};
DestinationPtr DestinationFactory::MakeFileDestination(const QString &filePath) DestinationPtr DestinationFactory::MakeFileDestination(const QString &filePath)
{ {
return DestinationPtr(new FileDestination(filePath)); return DestinationPtr(new FileDestination(filePath));
@ -87,4 +96,9 @@ DestinationPtr DestinationFactory::MakeDebugOutputDestination()
return DestinationPtr(new DebugOutputDestination); return DestinationPtr(new DebugOutputDestination);
} }
DestinationPtr DestinationFactory::MakeQDebugDestination()
{
return DestinationPtr(new QDebugDestination);
}
} // end namespace } // end namespace

View File

@ -47,6 +47,7 @@ class DestinationFactory
public: public:
static DestinationPtr MakeFileDestination(const QString &filePath); static DestinationPtr MakeFileDestination(const QString &filePath);
static DestinationPtr MakeDebugOutputDestination(); static DestinationPtr MakeDebugOutputDestination();
static DestinationPtr MakeQDebugDestination();
}; };
} // end namespace } // end namespace

View File

@ -27,6 +27,7 @@
#include "pathutils.h" #include "pathutils.h"
#include "lists/MinecraftVersionList.h" #include "lists/MinecraftVersionList.h"
#include "logic/icons/IconList.h"
BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
SettingsObject *settings_obj, QObject *parent) SettingsObject *settings_obj, QObject *parent)
@ -36,10 +37,11 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
d->m_settings = settings_obj; d->m_settings = settings_obj;
d->m_rootDir = rootDir; d->m_rootDir = rootDir;
settings().registerSetting(new Setting("name", "Unnamed Instance")); settings().registerSetting("name", "Unnamed Instance");
settings().registerSetting(new Setting("iconKey", "default")); settings().registerSetting("iconKey", "default");
settings().registerSetting(new Setting("notes", "")); connect(MMC->icons().get(), SIGNAL(iconUpdated(QString)), SLOT(iconUpdated(QString)));
settings().registerSetting(new Setting("lastLaunchTime", 0)); settings().registerSetting("notes", "");
settings().registerSetting("lastLaunchTime", 0);
/* /*
* custom base jar has no default. it is determined in code... see the accessor methods for * custom base jar has no default. it is determined in code... see the accessor methods for
@ -48,54 +50,45 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
* for instances that DO NOT have the CustomBaseJar setting (legacy instances), * for instances that DO NOT have the CustomBaseJar setting (legacy instances),
* [.]minecraft/bin/mcbackup.jar is the default base jar * [.]minecraft/bin/mcbackup.jar is the default base jar
*/ */
settings().registerSetting(new Setting("UseCustomBaseJar", true)); settings().registerSetting("UseCustomBaseJar", true);
settings().registerSetting(new Setting("CustomBaseJar", "")); settings().registerSetting("CustomBaseJar", "");
auto globalSettings = MMC->settings(); auto globalSettings = MMC->settings();
// Java Settings // Java Settings
settings().registerSetting(new Setting("OverrideJava", false)); settings().registerSetting("OverrideJava", false);
settings().registerSetting( settings().registerOverride(globalSettings->getSetting("JavaPath"));
new OverrideSetting("JavaPath", globalSettings->getSetting("JavaPath"))); settings().registerOverride(globalSettings->getSetting("JvmArgs"));
settings().registerSetting(
new OverrideSetting("JvmArgs", globalSettings->getSetting("JvmArgs")));
// Custom Commands // Custom Commands
settings().registerSetting(new Setting("OverrideCommands", false)); settings().registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
settings().registerSetting(new OverrideSetting( settings().registerOverride(globalSettings->getSetting("PreLaunchCommand"));
"PreLaunchCommand", globalSettings->getSetting("PreLaunchCommand"))); settings().registerOverride(globalSettings->getSetting("PostExitCommand"));
settings().registerSetting(
new OverrideSetting("PostExitCommand", globalSettings->getSetting("PostExitCommand")));
// Window Size // Window Size
settings().registerSetting(new Setting("OverrideWindow", false)); settings().registerSetting("OverrideWindow", false);
settings().registerSetting( settings().registerOverride(globalSettings->getSetting("LaunchMaximized"));
new OverrideSetting("LaunchMaximized", globalSettings->getSetting("LaunchMaximized"))); settings().registerOverride(globalSettings->getSetting("MinecraftWinWidth"));
settings().registerSetting(new OverrideSetting( settings().registerOverride(globalSettings->getSetting("MinecraftWinHeight"));
"MinecraftWinWidth", globalSettings->getSetting("MinecraftWinWidth")));
settings().registerSetting(new OverrideSetting(
"MinecraftWinHeight", globalSettings->getSetting("MinecraftWinHeight")));
// Memory // Memory
settings().registerSetting(new Setting("OverrideMemory", false)); settings().registerSetting("OverrideMemory", false);
settings().registerSetting( settings().registerOverride(globalSettings->getSetting("MinMemAlloc"));
new OverrideSetting("MinMemAlloc", globalSettings->getSetting("MinMemAlloc"))); settings().registerOverride(globalSettings->getSetting("MaxMemAlloc"));
settings().registerSetting( settings().registerOverride(globalSettings->getSetting("PermGen"));
new OverrideSetting("MaxMemAlloc", globalSettings->getSetting("MaxMemAlloc")));
settings().registerSetting(
new OverrideSetting("PermGen", globalSettings->getSetting("PermGen")));
// Auto login
settings().registerSetting(new Setting("OverrideLogin", false));
settings().registerSetting(
new OverrideSetting("AutoLogin", globalSettings->getSetting("AutoLogin")));
// Console // Console
settings().registerSetting(new Setting("OverrideConsole", false)); settings().registerSetting("OverrideConsole", false);
settings().registerSetting( settings().registerOverride(globalSettings->getSetting("ShowConsole"));
new OverrideSetting("ShowConsole", globalSettings->getSetting("ShowConsole"))); settings().registerOverride(globalSettings->getSetting("AutoCloseConsole"));
settings().registerSetting(new OverrideSetting( }
"AutoCloseConsole", globalSettings->getSetting("AutoCloseConsole")));
void BaseInstance::iconUpdated(QString key)
{
if(iconKey() == key)
{
emit propertiesChanged(this);
}
} }
void BaseInstance::nuke() void BaseInstance::nuke()

View File

@ -184,6 +184,9 @@ signals:
*/ */
void nuked(BaseInstance *inst); void nuked(BaseInstance *inst);
protected slots:
void iconUpdated(QString key);
protected: protected:
std::shared_ptr<BaseInstancePrivate> inst_d; std::shared_ptr<BaseInstancePrivate> inst_d;
}; };

View File

@ -20,7 +20,9 @@
#include "BaseInstance.h" #include "BaseInstance.h"
#include "LegacyInstance.h" #include "LegacyInstance.h"
#include "LegacyFTBInstance.h"
#include "OneSixInstance.h" #include "OneSixInstance.h"
#include "OneSixFTBInstance.h"
#include "NostalgiaInstance.h" #include "NostalgiaInstance.h"
#include "BaseVersion.h" #include "BaseVersion.h"
#include "MinecraftVersion.h" #include "MinecraftVersion.h"
@ -43,7 +45,7 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
{ {
auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg")); auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
m_settings->registerSetting(new Setting("InstanceType", "Legacy")); m_settings->registerSetting("InstanceType", "Legacy");
QString inst_type = m_settings->get("InstanceType").toString(); QString inst_type = m_settings->get("InstanceType").toString();
@ -60,6 +62,14 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
{ {
inst = new NostalgiaInstance(instDir, m_settings, this); inst = new NostalgiaInstance(instDir, m_settings, this);
} }
else if (inst_type == "LegacyFTB")
{
inst = new LegacyFTBInstance(instDir, m_settings, this);
}
else if (inst_type == "OneSixFTB")
{
inst = new OneSixFTBInstance(instDir, m_settings, this);
}
else else
{ {
return InstanceFactory::UnknownLoadError; return InstanceFactory::UnknownLoadError;
@ -69,7 +79,8 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&inst, InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&inst,
BaseVersionPtr version, BaseVersionPtr version,
const QString &instDir) const QString &instDir,
const InstType type)
{ {
QDir rootDir(instDir); QDir rootDir(instDir);
@ -83,8 +94,10 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&
return InstanceFactory::NoSuchVersion; return InstanceFactory::NoSuchVersion;
auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg")); auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
m_settings->registerSetting(new Setting("InstanceType", "Legacy")); m_settings->registerSetting("InstanceType", "Legacy");
if (type == NormalInst)
{
switch (mcVer->type) switch (mcVer->type)
{ {
case MinecraftVersion::Legacy: case MinecraftVersion::Legacy:
@ -111,6 +124,35 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&
return InstanceFactory::NoSuchVersion; return InstanceFactory::NoSuchVersion;
} }
} }
}
else if (type == FTBInstance)
{
switch (mcVer->type)
{
case MinecraftVersion::Legacy:
m_settings->set("InstanceType", "LegacyFTB");
inst = new LegacyFTBInstance(instDir, m_settings, this);
inst->setIntendedVersionId(version->descriptor());
inst->setShouldUseCustomBaseJar(false);
break;
case MinecraftVersion::OneSix:
m_settings->set("InstanceType", "OneSixFTB");
inst = new OneSixFTBInstance(instDir, m_settings, this);
inst->setIntendedVersionId(version->descriptor());
inst->setShouldUseCustomBaseJar(false);
break;
default:
{
delete m_settings;
return InstanceFactory::NoSuchVersion;
}
}
}
else
{
delete m_settings;
return InstanceFactory::NoSuchVersion;
}
// FIXME: really, how do you even know? // FIXME: really, how do you even know?
return InstanceFactory::NoCreateError; return InstanceFactory::NoCreateError;
@ -128,7 +170,17 @@ InstanceFactory::InstCreateError InstanceFactory::copyInstance(BaseInstance *&ne
rootDir.removeRecursively(); rootDir.removeRecursively();
return InstanceFactory::CantCreateDir; return InstanceFactory::CantCreateDir;
} }
auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
m_settings->registerSetting("InstanceType", "Legacy");
QString inst_type = m_settings->get("InstanceType").toString();
if(inst_type == "OneSixFTB")
m_settings->set("InstanceType", "OneSix");
if(inst_type == "LegacyFTB")
m_settings->set("InstanceType", "Legacy");
auto error = loadInstance(newInstance, instDir); auto error = loadInstance(newInstance, instDir);
switch (error) switch (error)
{ {
case NoLoadError: case NoLoadError:

View File

@ -55,18 +55,25 @@ public:
CantCreateDir CantCreateDir
}; };
enum InstType
{
NormalInst,
FTBInstance
};
/*! /*!
* \brief Creates a stub instance * \brief Creates a stub instance
* *
* \param inst Pointer to store the created instance in. * \param inst Pointer to store the created instance in.
* \param inst Game version to use for the instance * \param version Game version to use for the instance
* \param instDir The new instance's directory. * \param instDir The new instance's directory.
* \param type The type of instance to create
* \return An InstCreateError error code. * \return An InstCreateError error code.
* - InstExists if the given instance directory is already an instance. * - InstExists if the given instance directory is already an instance.
* - CantCreateDir if the given instance directory cannot be created. * - CantCreateDir if the given instance directory cannot be created.
*/ */
InstCreateError createInstance(BaseInstance *&inst, BaseVersionPtr version, InstCreateError createInstance(BaseInstance *&inst, BaseVersionPtr version,
const QString &instDir); const QString &instDir, const InstType type = NormalInst);
/*! /*!
* \brief Creates a copy of an existing instance with a new name * \brief Creates a copy of an existing instance with a new name

View File

@ -99,6 +99,7 @@ void JavaChecker::error(QProcess::ProcessError err)
if(err == QProcess::FailedToStart) if(err == QProcess::FailedToStart)
{ {
killTimer.stop(); killTimer.stop();
checkerJar.remove();
JavaCheckResult result; JavaCheckResult result;
{ {
@ -116,6 +117,5 @@ void JavaChecker::timeout()
if(process) if(process)
{ {
process->kill(); process->kill();
process.reset();
} }
} }

View File

@ -177,10 +177,10 @@ QList<QString> JavaUtils::FindJavaPaths()
#elif OSX #elif OSX
QList<QString> JavaUtils::FindJavaPaths() QList<QString> JavaUtils::FindJavaPaths()
{ {
QLOG_INFO() << "OS X Java detection incomplete - defaulting to \"java\"";
QList<QString> javas; QList<QString> javas;
javas.append(this->GetDefaultJava()->path); javas.append(this->GetDefaultJava()->path);
javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java");
javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
return javas; return javas;
} }

View File

@ -0,0 +1,16 @@
#include "LegacyFTBInstance.h"
LegacyFTBInstance::LegacyFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) :
LegacyInstance(rootDir, settings, parent)
{
}
QString LegacyFTBInstance::getStatusbarDescription()
{
return "Legacy FTB: " + intendedVersionId();
}
bool LegacyFTBInstance::menuActionEnabled(QString action_name) const
{
return false;
}

13
logic/LegacyFTBInstance.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include "LegacyInstance.h"
class LegacyFTBInstance : public LegacyInstance
{
Q_OBJECT
public:
explicit LegacyFTBInstance(const QString &rootDir, SettingsObject *settings,
QObject *parent = 0);
virtual QString getStatusbarDescription();
virtual bool menuActionEnabled(QString action_name) const;
};

View File

@ -27,7 +27,7 @@
#include "logic/MinecraftProcess.h" #include "logic/MinecraftProcess.h"
#include "logic/LegacyUpdate.h" #include "logic/LegacyUpdate.h"
#include "logic/lists/IconList.h" #include "logic/icons/IconList.h"
#include "gui/dialogs/LegacyModEditDialog.h" #include "gui/dialogs/LegacyModEditDialog.h"
@ -37,11 +37,11 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings,
QObject *parent) QObject *parent)
: BaseInstance(new LegacyInstancePrivate(), rootDir, settings, parent) : BaseInstance(new LegacyInstancePrivate(), rootDir, settings, parent)
{ {
settings->registerSetting(new Setting("NeedsRebuild", true)); settings->registerSetting("NeedsRebuild", true);
settings->registerSetting(new Setting("ShouldUpdate", false)); settings->registerSetting("ShouldUpdate", false);
settings->registerSetting(new Setting("JarVersion", "Unknown")); settings->registerSetting("JarVersion", "Unknown");
settings->registerSetting(new Setting("LwjglVersion", "2.9.0")); settings->registerSetting("LwjglVersion", "2.9.0");
settings->registerSetting(new Setting("IntendedJarVersion", "")); settings->registerSetting("IntendedJarVersion", "");
} }
std::shared_ptr<Task> LegacyInstance::doUpdate(bool only_prepare) std::shared_ptr<Task> LegacyInstance::doUpdate(bool only_prepare)
@ -150,6 +150,7 @@ std::shared_ptr<ModList> LegacyInstance::jarModList()
void LegacyInstance::jarModsChanged() void LegacyInstance::jarModsChanged()
{ {
QLOG_INFO() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt.";
setShouldRebuild(true); setShouldRebuild(true);
} }

View File

@ -76,7 +76,7 @@ void LegacyUpdate::lwjglStart()
return; return;
} }
setStatus("Downloading new LWJGL."); setStatus(tr("Downloading new LWJGL..."));
auto version = list->getVersion(lwjglVersion); auto version = list->getVersion(lwjglVersion);
if (!version) if (!version)
{ {
@ -144,7 +144,7 @@ void LegacyUpdate::lwjglFinished(QNetworkReply *reply)
saveMe.open(QIODevice::WriteOnly); saveMe.open(QIODevice::WriteOnly);
saveMe.write(m_reply->readAll()); saveMe.write(m_reply->readAll());
saveMe.close(); saveMe.close();
setStatus("Installing new LWJGL..."); setStatus(tr("Installing new LWJGL..."));
extractLwjgl(); extractLwjgl();
jarStart(); jarStart();
} }
@ -220,7 +220,7 @@ void LegacyUpdate::extractLwjgl()
// Now if destFileName is still empty, go to the next file. // Now if destFileName is still empty, go to the next file.
if (!destFileName.isEmpty()) if (!destFileName.isEmpty())
{ {
setStatus("Installing new LWJGL - Extracting " + name); setStatus(tr("Installing new LWJGL - extracting ") + name + "...");
QFile output(destFileName); QFile output(destFileName);
output.open(QIODevice::WriteOnly); output.open(QIODevice::WriteOnly);
output.write(file.readAll()); // FIXME: wste of memory!? output.write(file.readAll()); // FIXME: wste of memory!?
@ -250,7 +250,7 @@ void LegacyUpdate::jarStart()
return; return;
} }
setStatus("Checking for jar updates..."); setStatus(tr("Checking for jar updates..."));
// Make directories // Make directories
QDir binDir(inst->binDir()); QDir binDir(inst->binDir());
if (!binDir.exists() && !binDir.mkpath(".")) if (!binDir.exists() && !binDir.mkpath("."))
@ -260,7 +260,7 @@ void LegacyUpdate::jarStart()
} }
// Build a list of URLs that will need to be downloaded. // Build a list of URLs that will need to be downloaded.
setStatus("Downloading new minecraft.jar"); setStatus(tr("Downloading new minecraft.jar ..."));
QString version_id = inst->intendedVersionId(); QString version_id = inst->intendedVersionId();
QString localPath = version_id + "/" + version_id + ".jar"; QString localPath = version_id + "/" + version_id + ".jar";
@ -294,7 +294,7 @@ void LegacyUpdate::jarFailed()
bool LegacyUpdate::MergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, bool LegacyUpdate::MergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
MetainfAction metainf) MetainfAction metainf)
{ {
setStatus("Installing mods - Adding " + from.fileName()); setStatus(tr("Installing mods: Adding ") + from.fileName() + " ...");
QuaZip modZip(from.filePath()); QuaZip modZip(from.filePath());
modZip.open(QuaZip::mdUnzip); modZip.open(QuaZip::mdUnzip);
@ -380,7 +380,7 @@ void LegacyUpdate::ModTheJar()
return; return;
} }
setStatus("Installing mods - backing up minecraft.jar..."); setStatus(tr("Installing mods: Backing up minecraft.jar ..."));
if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath()))
{ {
emitFailed("It seems both the active and base jar are gone. A fresh base jar will " emitFailed("It seems both the active and base jar are gone. A fresh base jar will "
@ -405,7 +405,7 @@ void LegacyUpdate::ModTheJar()
} }
// TaskStep(); // STEP 1 // TaskStep(); // STEP 1
setStatus("Installing mods - Opening minecraft.jar"); setStatus(tr("Installing mods: Opening minecraft.jar ..."));
QuaZip zipOut(runnableJar.filePath()); QuaZip zipOut(runnableJar.filePath());
if (!zipOut.open(QuaZip::mdCreate)) if (!zipOut.open(QuaZip::mdCreate))
@ -419,10 +419,15 @@ void LegacyUpdate::ModTheJar()
QSet<QString> addedFiles; QSet<QString> addedFiles;
// Modify the jar // Modify the jar
setStatus("Installing mods - Adding mod files..."); setStatus(tr("Installing mods: Adding mod files..."));
for (int i = modList->size() - 1; i >= 0; i--) for (int i = modList->size() - 1; i >= 0; i--)
{ {
auto &mod = modList->operator[](i); auto &mod = modList->operator[](i);
// do not merge disabled mods.
if(!mod.enabled())
continue;
if (mod.type() == Mod::MOD_ZIPFILE) if (mod.type() == Mod::MOD_ZIPFILE)
{ {
if (!MergeZipFiles(&zipOut, mod.filename(), addedFiles, LegacyUpdate::KeepMetainf)) if (!MergeZipFiles(&zipOut, mod.filename(), addedFiles, LegacyUpdate::KeepMetainf))

View File

@ -0,0 +1,102 @@
/* 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 "LiteLoaderInstaller.h"
#include "OneSixVersion.h"
#include "OneSixLibrary.h"
QMap<QString, QString> LiteLoaderInstaller::m_launcherWrapperVersionMapping;
LiteLoaderInstaller::LiteLoaderInstaller(const QString &mcVersion) : m_mcVersion(mcVersion)
{
if (m_launcherWrapperVersionMapping.isEmpty())
{
m_launcherWrapperVersionMapping["1.6.2"] = "1.3";
m_launcherWrapperVersionMapping["1.6.4"] = "1.8";
//m_launcherWrapperVersionMapping["1.7.2"] = "1.8";
//m_launcherWrapperVersionMapping["1.7.4"] = "1.8";
}
}
bool LiteLoaderInstaller::canApply() const
{
return m_launcherWrapperVersionMapping.contains(m_mcVersion);
}
bool LiteLoaderInstaller::apply(std::shared_ptr<OneSixVersion> to)
{
to->externalUpdateStart();
applyLaunchwrapper(to);
applyLiteLoader(to);
to->mainClass = "net.minecraft.launchwrapper.Launch";
if (!to->minecraftArguments.contains(
" --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker"))
{
to->minecraftArguments.append(
" --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker");
}
to->externalUpdateFinish();
return to->toOriginalFile();
}
void LiteLoaderInstaller::applyLaunchwrapper(std::shared_ptr<OneSixVersion> to)
{
const QString intendedVersion = m_launcherWrapperVersionMapping[m_mcVersion];
QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries);
while (it.hasNext())
{
it.next();
if (it.value()->rawName().startsWith("net.minecraft:launchwrapper:"))
{
if (it.value()->version() >= intendedVersion)
{
return;
}
else
{
it.remove();
}
}
}
std::shared_ptr<OneSixLibrary> lib(new OneSixLibrary(
"net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[m_mcVersion]));
lib->finalize();
to->libraries.prepend(lib);
}
void LiteLoaderInstaller::applyLiteLoader(std::shared_ptr<OneSixVersion> to)
{
QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries);
while (it.hasNext())
{
it.next();
if (it.value()->rawName().startsWith("com.mumfrey:liteloader:"))
{
it.remove();
}
}
std::shared_ptr<OneSixLibrary> lib(
new OneSixLibrary("com.mumfrey:liteloader:" + m_mcVersion));
lib->setBaseUrl("http://dl.liteloader.com/versions/");
lib->finalize();
to->libraries.prepend(lib);
}

View File

@ -14,29 +14,26 @@
*/ */
#pragma once #pragma once
#include <QString>
#include <QMap>
#include <memory>
#include <QObject> class OneSixVersion;
#include <QSettings>
#include "settingsobject.h" class LiteLoaderInstaller
#include "libsettings_config.h"
/*!
* \brief A settings object that stores its settings in a QSettings object.
*/
class LIBSETTINGS_EXPORT BasicSettingsObject : public SettingsObject
{ {
Q_OBJECT
public: public:
explicit BasicSettingsObject(QObject *parent = 0); LiteLoaderInstaller(const QString &mcVersion);
protected bool canApply() const;
slots:
virtual void changeSetting(const Setting &setting, QVariant value);
protected: bool apply(std::shared_ptr<OneSixVersion> to);
virtual QVariant retrieveValue(const Setting &setting);
QSettings config; private:
QString m_mcVersion;
void applyLaunchwrapper(std::shared_ptr<OneSixVersion> to);
void applyLiteLoader(std::shared_ptr<OneSixVersion> to);
static QMap<QString, QString> m_launcherWrapperVersionMapping;
}; };

View File

@ -35,20 +35,45 @@ Mod::Mod(const QFileInfo &file)
void Mod::repath(const QFileInfo &file) void Mod::repath(const QFileInfo &file)
{ {
m_file = file; m_file = file;
m_name = file.completeBaseName(); QString name_base = file.fileName();
m_id = file.fileName();
m_type = Mod::MOD_UNKNOWN; m_type = Mod::MOD_UNKNOWN;
if (m_file.isDir()) if (m_file.isDir())
{
m_type = MOD_FOLDER; m_type = MOD_FOLDER;
m_name = name_base;
m_mmc_id = name_base;
}
else if (m_file.isFile()) else if (m_file.isFile())
{ {
QString ext = m_file.suffix().toLower(); if (name_base.endsWith(".disabled"))
if (ext == "zip" || ext == "jar") {
m_type = MOD_ZIPFILE; m_enabled = false;
name_base.chop(9);
}
else else
{
m_enabled = true;
}
m_mmc_id = name_base;
if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
{
m_type = MOD_ZIPFILE;
name_base.chop(4);
}
else if (name_base.endsWith(".litemod"))
{
m_type = MOD_LITEMOD;
name_base.chop(8);
}
else
{
m_type = MOD_SINGLEFILE; m_type = MOD_SINGLEFILE;
} }
m_name = name_base;
}
if (m_type == MOD_ZIPFILE) if (m_type == MOD_ZIPFILE)
{ {
QuaZip zip(m_file.filePath()); QuaZip zip(m_file.filePath());
@ -100,6 +125,27 @@ void Mod::repath(const QFileInfo &file)
ReadMCModInfo(data); ReadMCModInfo(data);
} }
} }
else if (m_type == MOD_LITEMOD)
{
QuaZip zip(m_file.filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
QuaZipFile file(&zip);
if (zip.setCurrentFile("litemod.json"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadLiteModInfo(file.readAll());
file.close();
}
zip.close();
}
} }
// NEW format // NEW format
@ -114,7 +160,7 @@ void Mod::ReadMCModInfo(QByteArray contents)
if (!arr.at(0).isObject()) if (!arr.at(0).isObject())
return; return;
auto firstObj = arr.at(0).toObject(); auto firstObj = arr.at(0).toObject();
m_id = firstObj.value("modid").toString(); m_mod_id = firstObj.value("modid").toString();
m_name = firstObj.value("name").toString(); m_name = firstObj.value("name").toString();
m_version = firstObj.value("version").toString(); m_version = firstObj.value("version").toString();
m_homeurl = firstObj.value("url").toString(); m_homeurl = firstObj.value("url").toString();
@ -132,8 +178,7 @@ void Mod::ReadMCModInfo(QByteArray contents)
} }
m_credits = firstObj.value("credits").toString(); m_credits = firstObj.value("credits").toString();
return; return;
} };
;
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
// this is the very old format that had just the array // this is the very old format that had just the array
@ -163,7 +208,7 @@ void Mod::ReadForgeInfo(QByteArray contents)
{ {
// Read the data // Read the data
m_name = "Minecraft Forge"; m_name = "Minecraft Forge";
m_id = "Forge"; m_mod_id = "Forge";
m_homeurl = "http://www.minecraftforge.net/forum/"; m_homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini; INIFile ini;
if (!ini.loadFile(contents)) if (!ini.loadFile(contents))
@ -177,15 +222,40 @@ void Mod::ReadForgeInfo(QByteArray contents)
m_version = major + "." + minor + "." + revision + "." + build; m_version = major + "." + minor + "." + revision + "." + build;
} }
void Mod::ReadLiteModInfo(QByteArray contents)
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
auto object = jsonDoc.object();
if(object.contains("name"))
{
m_mod_id = m_name = object.value("name").toString();
}
if(object.contains("version"))
{
m_version=object.value("version").toString("");
}
else
{
m_version=object.value("revision").toString("");
}
m_mcversion = object.value("mcversion").toString();
m_authors = object.value("author").toString();
m_description = object.value("description").toString();
m_homeurl = object.value("url").toString();
}
bool Mod::replace(Mod &with) bool Mod::replace(Mod &with)
{ {
if (!destroy()) if (!destroy())
return false; return false;
bool success = false; bool success = false;
auto t = with.type(); auto t = with.type();
if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE) if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE)
{ {
success = QFile::copy(with.m_file.filePath(), m_file.path()); QLOG_DEBUG() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath();
success = QFile::copy(with.m_file.filePath(), m_file.filePath());
} }
if (t == MOD_FOLDER) if (t == MOD_FOLDER)
{ {
@ -193,11 +263,17 @@ bool Mod::replace(Mod &with)
} }
if (success) if (success)
{ {
m_id = with.m_id;
m_mcversion = with.m_mcversion;
m_type = with.m_type;
m_name = with.m_name; m_name = with.m_name;
m_mmc_id = with.m_mmc_id;
m_mod_id = with.m_mod_id;
m_version = with.m_version; m_version = with.m_version;
m_mcversion = with.m_mcversion;
m_description = with.m_description;
m_authors = with.m_authors;
m_credits = with.m_credits;
m_homeurl = with.m_homeurl;
m_type = with.m_type;
m_file.refresh();
} }
return success; return success;
} }
@ -232,6 +308,7 @@ QString Mod::version() const
switch (type()) switch (type())
{ {
case MOD_ZIPFILE: case MOD_ZIPFILE:
case MOD_LITEMOD:
return m_version; return m_version;
case MOD_FOLDER: case MOD_FOLDER:
return "Folder"; return "Folder";
@ -241,3 +318,41 @@ QString Mod::version() const
return "VOID"; return "VOID";
} }
} }
bool Mod::enable(bool value)
{
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
return false;
if (m_enabled == value)
return false;
QString path = m_file.absoluteFilePath();
if (value)
{
QFile foo(path);
if (!path.endsWith(".disabled"))
return false;
path.chop(9);
if (!foo.rename(path))
return false;
}
else
{
QFile foo(path);
path += ".disabled";
if (!foo.rename(path))
return false;
}
m_file = QFileInfo(path);
m_enabled = value;
return true;
}
bool Mod::operator==(const Mod &other) const
{
return mmc_id() == other.mmc_id();
}
bool Mod::strongCompare(const Mod &other) const
{
return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type();
}

View File

@ -25,6 +25,7 @@ public:
MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). MOD_SINGLEFILE, //!< The mod is a single file (not a zip file).
MOD_FOLDER, //!< The mod is in a folder on the filesystem. MOD_FOLDER, //!< The mod is in a folder on the filesystem.
MOD_LITEMOD, //!< The mod is a litemod
}; };
Mod(const QFileInfo &file); Mod(const QFileInfo &file);
@ -33,9 +34,13 @@ public:
{ {
return m_file; return m_file;
} }
QString id() const QString mmc_id() const
{ {
return m_id; return m_mmc_id;
}
QString mod_id() const
{
return m_mod_id;
} }
ModType type() const ModType type() const
{ {
@ -77,6 +82,13 @@ public:
return m_credits; return m_credits;
} }
bool enabled() const
{
return m_enabled;
}
bool enable(bool value);
// delete all the files of this mod // delete all the files of this mod
bool destroy(); bool destroy();
// replace this mod with a copy of the other // replace this mod with a copy of the other
@ -85,19 +97,13 @@ public:
void repath(const QFileInfo &file); void repath(const QFileInfo &file);
// WEAK compare operator - used for replacing mods // WEAK compare operator - used for replacing mods
bool operator==(const Mod &other) const bool operator==(const Mod &other) const;
{ bool strongCompare(const Mod &other) const;
return filename() == other.filename();
}
bool strongCompare(const Mod &other) const
{
return filename() == other.filename() && id() == other.id() &&
version() == other.version() && type() == other.type();
}
private: private:
void ReadMCModInfo(QByteArray contents); void ReadMCModInfo(QByteArray contents);
void ReadForgeInfo(QByteArray contents); void ReadForgeInfo(QByteArray contents);
void ReadLiteModInfo(QByteArray contents);
protected: protected:
@ -108,7 +114,9 @@ protected:
*/ */
QFileInfo m_file; QFileInfo m_file;
QString m_id; QString m_mmc_id;
QString m_mod_id;
bool m_enabled = true;
QString m_name; QString m_name;
QString m_version; QString m_version;
QString m_mcversion; QString m_mcversion;

View File

@ -19,6 +19,7 @@
#include <QMimeData> #include <QMimeData>
#include <QUrl> #include <QUrl>
#include <QUuid> #include <QUuid>
#include <QString>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include "logger/QsLog.h" #include "logger/QsLog.h"
@ -27,7 +28,7 @@ ModList::ModList(const QString &dir, const QString &list_file)
{ {
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
QDir::NoSymLinks); QDir::NoSymLinks);
m_dir.setSorting(QDir::Name); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_list_id = QUuid::createUuid().toString(); m_list_id = QUuid::createUuid().toString();
m_watcher = new QFileSystemWatcher(this); m_watcher = new QFileSystemWatcher(this);
is_watching = false; is_watching = false;
@ -66,52 +67,89 @@ bool ModList::update()
if (!isValid()) if (!isValid())
return false; return false;
QList<Mod> orderedMods;
QList<Mod> newMods; QList<Mod> newMods;
m_dir.refresh(); m_dir.refresh();
auto folderContents = m_dir.entryInfoList(); auto folderContents = m_dir.entryInfoList();
bool orderWasInvalid = false; bool orderOrStateChanged = false;
// first, process the ordered items (if any) // first, process the ordered items (if any)
QStringList listOrder = readListFile(); OrderList listOrder = readListFile();
for (auto item : listOrder) for (auto item : listOrder)
{ {
QFileInfo info(m_dir.filePath(item)); QFileInfo infoEnabled(m_dir.filePath(item.id));
int idx = folderContents.indexOf(info); QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled"));
int idxEnabled = folderContents.indexOf(infoEnabled);
int idxDisabled = folderContents.indexOf(infoDisabled);
bool isEnabled;
// if both enabled and disabled versions are present, it's a special case...
if (idxEnabled >= 0 && idxDisabled >= 0)
{
// we only process the one we actually have in the order file.
// and exactly as we have it.
// THIS IS A CORNER CASE
isEnabled = item.enabled;
}
else
{
// only one is present.
// we pick the one that we found.
// we assume the mod was enabled/disabled by external means
isEnabled = idxEnabled >= 0;
}
int idx = isEnabled ? idxEnabled : idxDisabled;
QFileInfo & info = isEnabled ? infoEnabled : infoDisabled;
// if the file from the index file exists // if the file from the index file exists
if (idx != -1) if (idx != -1)
{ {
// remove from the actual folder contents list // remove from the actual folder contents list
folderContents.takeAt(idx); folderContents.takeAt(idx);
// append the new mod // append the new mod
newMods.append(Mod(info)); orderedMods.append(Mod(info));
if (isEnabled != item.enabled)
orderOrStateChanged = true;
} }
else else
{ {
orderWasInvalid = true; orderOrStateChanged = true;
} }
} }
// if there are any untracked files...
if (folderContents.size())
{
// the order surely changed!
for (auto entry : folderContents) for (auto entry : folderContents)
{ {
newMods.append(Mod(entry)); newMods.append(Mod(entry));
} }
if (mods.size() != newMods.size()) std::sort(newMods.begin(), newMods.end(), [](const Mod & left, const Mod & right)
{ { return left.name().localeAwareCompare(right.name()) <= 0; });
orderWasInvalid = true; orderedMods.append(newMods);
orderOrStateChanged = true;
} }
// otherwise, if we were already tracking some mods
else if (mods.size())
{
// if the number doesn't match, order changed.
if (mods.size() != orderedMods.size())
orderOrStateChanged = true;
// if it does match, compare the mods themselves
else else
for (int i = 0; i < mods.size(); i++) for (int i = 0; i < mods.size(); i++)
{ {
if (!mods[i].strongCompare(newMods[i])) if (!mods[i].strongCompare(orderedMods[i]))
{ {
orderWasInvalid = true; orderOrStateChanged = true;
break; break;
} }
} }
}
beginResetModel(); beginResetModel();
mods.swap(newMods); mods.swap(orderedMods);
endResetModel(); endResetModel();
if (orderWasInvalid) if (orderOrStateChanged && !m_list_file.isEmpty())
{ {
QLOG_INFO() << "Mod list " << m_list_file << " changed!";
saveListFile(); saveListFile();
emit changed(); emit changed();
} }
@ -123,17 +161,19 @@ void ModList::directoryChanged(QString path)
update(); update();
} }
QStringList ModList::readListFile() ModList::OrderList ModList::readListFile()
{ {
QStringList stringList; OrderList itemList;
if (m_list_file.isNull() || m_list_file.isEmpty()) if (m_list_file.isNull() || m_list_file.isEmpty())
return stringList; return itemList;
QFile textFile(m_list_file); QFile textFile(m_list_file);
if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
return QStringList(); return OrderList();
QTextStream textStream(&textFile); QTextStream textStream;
textStream.setAutoDetectUnicode(true);
textStream.setDevice(&textFile);
while (true) while (true)
{ {
QString line = textStream.readLine(); QString line = textStream.readLine();
@ -141,11 +181,18 @@ QStringList ModList::readListFile()
break; break;
else else
{ {
stringList.append(line); OrderItem it;
it.enabled = !line.endsWith(".disabled");
if (!it.enabled)
{
line.chop(9);
}
it.id = line;
itemList.append(it);
} }
} }
textFile.close(); textFile.close();
return stringList; return itemList;
} }
bool ModList::saveListFile() bool ModList::saveListFile()
@ -155,12 +202,16 @@ bool ModList::saveListFile()
QFile textFile(m_list_file); QFile textFile(m_list_file);
if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
return false; return false;
QTextStream textStream(&textFile); QTextStream textStream;
textStream.setGenerateByteOrderMark(true);
textStream.setCodec("UTF-8");
textStream.setDevice(&textFile);
for (auto mod : mods) for (auto mod : mods)
{ {
auto pathname = mod.filename(); textStream << mod.mmc_id();
QString filename = pathname.fileName(); if (!mod.enabled())
textStream << filename << endl; textStream << ".disabled";
textStream << endl;
} }
textFile.close(); textFile.close();
return false; return false;
@ -185,6 +236,9 @@ bool ModList::installMod(const QFileInfo &filename, int index)
int idx = mods.indexOf(m); int idx = mods.indexOf(m);
if (idx != -1) if (idx != -1)
{ {
int idx2 = mods.indexOf(m,idx+1);
if(idx2 != -1)
return false;
if (mods[idx].replace(m)) if (mods[idx].replace(m))
{ {
@ -201,7 +255,7 @@ bool ModList::installMod(const QFileInfo &filename, int index)
auto type = m.type(); auto type = m.type();
if (type == Mod::MOD_UNKNOWN) if (type == Mod::MOD_UNKNOWN)
return false; return false;
if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE) if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
{ {
QString newpath = PathCombine(m_dir.path(), filename.fileName()); QString newpath = PathCombine(m_dir.path(), filename.fileName());
if (!QFile::copy(filename.filePath(), newpath)) if (!QFile::copy(filename.filePath(), newpath))
@ -327,7 +381,7 @@ bool ModList::moveModsDown(int first, int last)
int ModList::columnCount(const QModelIndex &parent) const int ModList::columnCount(const QModelIndex &parent) const
{ {
return 2; return 3;
} }
QVariant ModList::data(const QModelIndex &index, int role) const QVariant ModList::data(const QModelIndex &index, int role) const
@ -341,43 +395,96 @@ QVariant ModList::data(const QModelIndex &index, int role) const
if (row < 0 || row >= mods.size()) if (row < 0 || row >= mods.size())
return QVariant(); return QVariant();
if (role != Qt::DisplayRole) switch (role)
return QVariant();
switch (column)
{ {
case 0: case Qt::DisplayRole:
switch (index.column())
{
case NameColumn:
return mods[row].name(); return mods[row].name();
case 1: case VersionColumn:
return mods[row].version(); return mods[row].version();
case 2:
return mods[row].mcversion(); default:
return QVariant();
}
case Qt::ToolTipRole:
return mods[row].mmc_id();
case Qt::CheckStateRole:
switch (index.column())
{
case ActiveColumn:
return mods[row].enabled();
default:
return QVariant();
}
default: default:
return QVariant(); return QVariant();
} }
} }
bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return false;
}
if (role == Qt::CheckStateRole)
{
auto &mod = mods[index.row()];
if (mod.enable(!mod.enabled()))
{
emit dataChanged(index, index);
return true;
}
}
return false;
}
QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const
{ {
if (role != Qt::DisplayRole || orientation != Qt::Horizontal) switch (role)
return QVariant(); {
case Qt::DisplayRole:
switch (section) switch (section)
{ {
case 0: case ActiveColumn:
return QString("Name");
case 1:
return QString("Version");
case 2:
return QString("Minecraft");
}
return QString(); return QString();
case NameColumn:
return QString("Name");
case VersionColumn:
return QString("Version");
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section)
{
case ActiveColumn:
return "Is the mod enabled?";
case NameColumn:
return "The name of the mod.";
case VersionColumn:
return "The version of the mod.";
default:
return QVariant();
}
default:
return QVariant();
}
return QVariant();
} }
Qt::ItemFlags ModList::flags(const QModelIndex &index) const Qt::ItemFlags ModList::flags(const QModelIndex &index) const
{ {
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid()) if (index.isValid())
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
defaultFlags;
else else
return Qt::ItemIsDropEnabled | defaultFlags; return Qt::ItemIsDropEnabled | defaultFlags;
} }
@ -456,6 +563,14 @@ bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row
QString filename = url.toLocalFile(); QString filename = url.toLocalFile();
installMod(filename, row); installMod(filename, row);
QLOG_INFO() << "installing: " << filename; QLOG_INFO() << "installing: " << filename;
// if there is no ordering, re-sort the list
if (m_list_file.isEmpty())
{
beginResetModel();
std::sort(mods.begin(), mods.end(), [](const Mod & left, const Mod & right)
{ return left.name().localeAwareCompare(right.name()) <= 0; });
endResetModel();
}
} }
if (was_watching) if (was_watching)
startWatching(); startWatching();

View File

@ -34,9 +34,18 @@ class ModList : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
public: public:
enum Columns
{
ActiveColumn = 0,
NameColumn,
VersionColumn
};
ModList(const QString &dir, const QString &list_file = QString()); ModList(const QString &dir, const QString &list_file = QString());
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
{ {
return size(); return size();
@ -59,7 +68,6 @@ public:
{ {
return mods[index]; return mods[index];
} }
;
/// Reloads the mod list and returns true if the list changed. /// Reloads the mod list and returns true if the list changed.
virtual bool update(); virtual bool update();
@ -119,7 +127,13 @@ public:
} }
private: private:
QStringList readListFile(); struct OrderItem
{
QString id;
bool enabled = false;
};
typedef QList<OrderItem> OrderList;
OrderList readListFile();
bool saveListFile(); bool saveListFile();
private private
slots: slots:

120
logic/OneSixFTBInstance.cpp Normal file
View File

@ -0,0 +1,120 @@
#include "OneSixFTBInstance.h"
#include "OneSixVersion.h"
#include "OneSixLibrary.h"
#include "tasks/SequentialTask.h"
#include "ForgeInstaller.h"
#include "lists/ForgeVersionList.h"
#include "MultiMC.h"
class OneSixFTBInstanceForge : public Task
{
Q_OBJECT
public:
explicit OneSixFTBInstanceForge(const QString &version, OneSixFTBInstance *inst, QObject *parent = 0) :
Task(parent), instance(inst), version("Forge " + version)
{
}
void executeTask()
{
for (int i = 0; i < MMC->forgelist()->count(); ++i)
{
if (MMC->forgelist()->at(i)->name() == version)
{
forgeVersion = std::dynamic_pointer_cast<ForgeVersion>(MMC->forgelist()->at(i));
break;
}
}
if (!forgeVersion)
{
emitFailed(QString("Couldn't find forge version ") + version );
return;
}
entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename);
if (entry->stale)
{
setStatus(tr("Downloading Forge..."));
fjob = new NetJob("Forge download");
fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry));
connect(fjob, &NetJob::failed, [this](){emitFailed(m_failReason);});
connect(fjob, &NetJob::succeeded, this, &OneSixFTBInstanceForge::installForge);
connect(fjob, &NetJob::progress, [this](qint64 c, qint64 total){ setProgress(100 * c / total); });
fjob->start();
}
else
{
installForge();
}
}
private
slots:
void installForge()
{
setStatus(tr("Installing Forge..."));
QString forgePath = entry->getFullPath();
ForgeInstaller forge(forgePath, forgeVersion->universal_url);
if (!instance->reloadFullVersion())
{
emitFailed(tr("Couldn't load the version config"));
return;
}
instance->revertCustomVersion();
instance->customizeVersion();
auto version = instance->getFullVersion();
if (!forge.apply(version))
{
emitFailed(tr("Couldn't install Forge"));
return;
}
emitSucceeded();
}
private:
OneSixFTBInstance *instance;
QString version;
ForgeVersionPtr forgeVersion;
MetaEntryPtr entry;
NetJob *fjob;
};
OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) :
OneSixInstance(rootDir, settings, parent)
{
QFile f(QDir(minecraftRoot()).absoluteFilePath("pack.json"));
if (f.open(QFile::ReadOnly))
{
QString data = QString::fromUtf8(f.readAll());
QRegularExpressionMatch match = QRegularExpression("net.minecraftforge:minecraftforge:[\\.\\d]*").match(data);
m_forge.reset(new OneSixLibrary(match.captured()));
m_forge->finalize();
}
}
QString OneSixFTBInstance::getStatusbarDescription()
{
return "OneSix FTB: " + intendedVersionId();
}
bool OneSixFTBInstance::menuActionEnabled(QString action_name) const
{
return false;
}
std::shared_ptr<Task> OneSixFTBInstance::doUpdate(bool only_prepare)
{
std::shared_ptr<SequentialTask> task;
task.reset(new SequentialTask(this));
if (!MMC->forgelist()->isLoaded())
{
task->addTask(std::shared_ptr<Task>(MMC->forgelist()->getLoadTask()));
}
task->addTask(OneSixInstance::doUpdate(only_prepare));
task->addTask(std::shared_ptr<Task>(new OneSixFTBInstanceForge(m_forge->version(), this, this)));
//FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again.
//TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly.
task->addTask(OneSixInstance::doUpdate(only_prepare));
return task;
}
#include "OneSixFTBInstance.moc"

20
logic/OneSixFTBInstance.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include "OneSixInstance.h"
class OneSixLibrary;
class OneSixFTBInstance : public OneSixInstance
{
Q_OBJECT
public:
explicit OneSixFTBInstance(const QString &rootDir, SettingsObject *settings,
QObject *parent = 0);
virtual QString getStatusbarDescription();
virtual bool menuActionEnabled(QString action_name) const;
virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override;
private:
std::shared_ptr<OneSixLibrary> m_forge;
};

View File

@ -33,8 +33,8 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_o
: BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent) : BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent)
{ {
I_D(OneSixInstance); I_D(OneSixInstance);
d->m_settings->registerSetting(new Setting("IntendedVersion", "")); d->m_settings->registerSetting("IntendedVersion", "");
d->m_settings->registerSetting(new Setting("ShouldUpdate", false)); d->m_settings->registerSetting("ShouldUpdate", false);
reloadFullVersion(); reloadFullVersion();
} }

View File

@ -68,6 +68,12 @@ public:
m_name = name; m_name = name;
} }
/// Returns the raw name field
QString rawName() const
{
return m_name;
}
QJsonObject toJson(); QJsonObject toJson();
/** /**

View File

@ -57,7 +57,7 @@ void OneSixUpdate::executeTask()
/* /*
* FIXME: in offline mode, do not proceed! * FIXME: in offline mode, do not proceed!
*/ */
setStatus("Testing the Java installation."); setStatus(tr("Testing the Java installation..."));
QString java_path = m_inst->settings().get("JavaPath").toString(); QString java_path = m_inst->settings().get("JavaPath").toString();
checker.reset(new JavaChecker()); checker.reset(new JavaChecker());
@ -89,7 +89,7 @@ void OneSixUpdate::executeTask()
void OneSixUpdate::checkJavaOnline() void OneSixUpdate::checkJavaOnline()
{ {
setStatus("Testing the Java installation."); setStatus(tr("Testing the Java installation..."));
QString java_path = m_inst->settings().get("JavaPath").toString(); QString java_path = m_inst->settings().get("JavaPath").toString();
checker.reset(new JavaChecker()); checker.reset(new JavaChecker());
@ -128,7 +128,7 @@ void OneSixUpdate::checkFinishedOffline(JavaCheckResult result)
void OneSixUpdate::versionFileStart() void OneSixUpdate::versionFileStart()
{ {
QLOG_INFO() << m_inst->name() << ": getting version file."; QLOG_INFO() << m_inst->name() << ": getting version file.";
setStatus("Getting the version files from Mojang."); setStatus(tr("Getting the version files from Mojang..."));
QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json";
auto job = new NetJob("Version index"); auto job = new NetJob("Version index");
@ -196,7 +196,7 @@ void OneSixUpdate::versionFileFailed()
void OneSixUpdate::assetIndexStart() void OneSixUpdate::assetIndexStart()
{ {
setStatus("Updating asset index."); setStatus(tr("Updating assets index..."));
OneSixInstance *inst = (OneSixInstance *)m_inst; OneSixInstance *inst = (OneSixInstance *)m_inst;
std::shared_ptr<OneSixVersion> version = inst->getFullVersion(); std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
QString assetName = version->assets; QString assetName = version->assets;
@ -247,7 +247,7 @@ void OneSixUpdate::assetIndexFinished()
} }
if(dls.size()) if(dls.size())
{ {
setStatus("Getting the assets files from Mojang..."); setStatus(tr("Getting the assets files from Mojang..."));
auto job = new NetJob("Assets for " + inst->name()); auto job = new NetJob("Assets for " + inst->name());
for(auto dl: dls) for(auto dl: dls)
job->addNetAction(dl); job->addNetAction(dl);
@ -281,7 +281,7 @@ void OneSixUpdate::assetsFailed()
void OneSixUpdate::jarlibStart() void OneSixUpdate::jarlibStart()
{ {
setStatus("Getting the library files from Mojang."); setStatus(tr("Getting the library files from Mojang..."));
QLOG_INFO() << m_inst->name() << ": downloading libraries"; QLOG_INFO() << m_inst->name() << ": downloading libraries";
OneSixInstance *inst = (OneSixInstance *)m_inst; OneSixInstance *inst = (OneSixInstance *)m_inst;
bool successful = inst->reloadFullVersion(); bool successful = inst->reloadFullVersion();
@ -369,7 +369,7 @@ void OneSixUpdate::jarlibFailed()
void OneSixUpdate::prepareForLaunch() void OneSixUpdate::prepareForLaunch()
{ {
setStatus("Preparing for launch."); setStatus(tr("Preparing for launch..."));
QLOG_INFO() << m_inst->name() << ": preparing for launch"; QLOG_INFO() << m_inst->name() << ": preparing for launch";
auto onesix_inst = (OneSixInstance *)m_inst; auto onesix_inst = (OneSixInstance *)m_inst;

View File

@ -0,0 +1,143 @@
#include "AssetsMigrateTask.h"
#include "MultiMC.h"
#include "logger/QsLog.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <QDirIterator>
#include <QCryptographicHash>
#include "gui/dialogs/CustomMessageBox.h"
#include <QDesktopServices>
AssetsMigrateTask::AssetsMigrateTask(int expected, QObject *parent)
: Task(parent)
{
this->m_expected = expected;
}
void AssetsMigrateTask::executeTask()
{
this->setStatus(tr("Migrating legacy assets..."));
this->setProgress(0);
QDir assets_dir("assets");
if (!assets_dir.exists())
{
emitFailed("Assets directory didn't exist");
return;
}
assets_dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
int base_length = assets_dir.path().length();
QList<QString> blacklist = {"indexes", "objects", "virtual"};
if (!assets_dir.exists("objects"))
assets_dir.mkdir("objects");
QDir objects_dir("assets/objects");
QDirIterator iterator(assets_dir, QDirIterator::Subdirectories);
int successes = 0;
int failures = 0;
while (iterator.hasNext())
{
QString currentDir = iterator.next();
currentDir = currentDir.remove(0, base_length + 1);
bool ignore = false;
for (QString blacklisted : blacklist)
{
if (currentDir.startsWith(blacklisted))
ignore = true;
}
if (!iterator.fileInfo().isDir() && !ignore)
{
QString filename = iterator.filePath();
QFile input(filename);
input.open(QIODevice::ReadOnly | QIODevice::WriteOnly);
QString sha1sum =
QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1)
.toHex()
.constData();
QString object_name = filename.remove(0, base_length + 1);
QLOG_DEBUG() << "Processing" << object_name << ":" << sha1sum << input.size();
QString object_tlk = sha1sum.left(2);
QString object_tlk_dir = objects_dir.path() + "/" + object_tlk;
QDir tlk_dir(object_tlk_dir);
if (!tlk_dir.exists())
objects_dir.mkdir(object_tlk);
QString new_filename = tlk_dir.path() + "/" + sha1sum;
QFile new_object(new_filename);
if (!new_object.exists())
{
bool rename_success = input.rename(new_filename);
QLOG_DEBUG() << " Doesn't exist, copying to" << new_filename << ":"
<< QString::number(rename_success);
if (rename_success)
successes++;
else
failures++;
}
else
{
input.remove();
QLOG_DEBUG() << " Already exists, deleting original and not copying.";
}
this->setProgress(100 * ((successes + failures) / (float) this->m_expected));
}
}
if (successes + failures == 0)
{
this->setProgress(100);
QLOG_DEBUG() << "No legacy assets needed importing.";
}
else
{
QLOG_DEBUG() << "Finished copying legacy assets:" << successes << "successes and"
<< failures << "failures.";
this->setStatus("Cleaning up legacy assets...");
this->setProgress(100);
QDirIterator cleanup_iterator(assets_dir);
while (cleanup_iterator.hasNext())
{
QString currentDir = cleanup_iterator.next();
currentDir = currentDir.remove(0, base_length + 1);
bool ignore = false;
for (QString blacklisted : blacklist)
{
if (currentDir.startsWith(blacklisted))
ignore = true;
}
if (cleanup_iterator.fileInfo().isDir() && !ignore)
{
QString path = cleanup_iterator.filePath();
QDir folder(path);
QLOG_DEBUG() << "Cleaning up legacy assets folder:" << path;
folder.removeRecursively();
}
}
}
if(failures > 0)
{
emitFailed(QString("Failed to migrate %1 legacy assets").arg(failures));
}
else
{
emitSucceeded();
}
}

View File

@ -0,0 +1,18 @@
#pragma once
#include "logic/tasks/Task.h"
#include <QMessageBox>
#include <QNetworkReply>
#include <memory>
class AssetsMigrateTask : public Task
{
Q_OBJECT
public:
explicit AssetsMigrateTask(int expected, QObject* parent=0);
protected:
virtual void executeTask();
private:
int m_expected;
};

View File

@ -25,23 +25,18 @@
namespace AssetsUtils namespace AssetsUtils
{ {
void migrateOldAssets() int findLegacyAssets()
{ {
QDir assets_dir("assets"); QDir assets_dir("assets");
if (!assets_dir.exists()) if (!assets_dir.exists())
return; return 0;
assets_dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); assets_dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
int base_length = assets_dir.path().length(); int base_length = assets_dir.path().length();
QList<QString> blacklist = {"indexes", "objects", "virtual"}; QList<QString> blacklist = {"indexes", "objects", "virtual"};
if (!assets_dir.exists("objects"))
assets_dir.mkdir("objects");
QDir objects_dir("assets/objects");
QDirIterator iterator(assets_dir, QDirIterator::Subdirectories); QDirIterator iterator(assets_dir, QDirIterator::Subdirectories);
int successes = 0; int found = 0;
int failures = 0;
while (iterator.hasNext()) while (iterator.hasNext())
{ {
QString currentDir = iterator.next(); QString currentDir = iterator.next();
@ -56,79 +51,11 @@ void migrateOldAssets()
if (!iterator.fileInfo().isDir() && !ignore) if (!iterator.fileInfo().isDir() && !ignore)
{ {
QString filename = iterator.filePath(); found++;
QFile input(filename);
input.open(QIODevice::ReadOnly | QIODevice::WriteOnly);
QString sha1sum =
QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1)
.toHex()
.constData();
QString object_name = filename.remove(0, base_length + 1);
QLOG_DEBUG() << "Processing" << object_name << ":" << sha1sum << input.size();
QString object_tlk = sha1sum.left(2);
QString object_tlk_dir = objects_dir.path() + "/" + object_tlk;
QDir tlk_dir(object_tlk_dir);
if (!tlk_dir.exists())
objects_dir.mkdir(object_tlk);
QString new_filename = tlk_dir.path() + "/" + sha1sum;
QFile new_object(new_filename);
if (!new_object.exists())
{
bool rename_success = input.rename(new_filename);
QLOG_DEBUG() << " Doesn't exist, copying to" << new_filename << ":"
<< QString::number(rename_success);
if (rename_success)
successes++;
else
failures++;
}
else
{
input.remove();
QLOG_DEBUG() << " Already exists, deleting original and not copying.";
}
} }
} }
if (successes + failures == 0) return found;
{
QLOG_DEBUG() << "No legacy assets needed importing.";
}
else
{
QLOG_DEBUG() << "Finished copying legacy assets:" << successes << "successes and"
<< failures << "failures.";
QDirIterator cleanup_iterator(assets_dir);
while (cleanup_iterator.hasNext())
{
QString currentDir = cleanup_iterator.next();
currentDir = currentDir.remove(0, base_length + 1);
bool ignore = false;
for (QString blacklisted : blacklist)
{
if (currentDir.startsWith(blacklisted))
ignore = true;
}
if (cleanup_iterator.fileInfo().isDir() && !ignore)
{
QString path = cleanup_iterator.filePath();
QDir folder(path);
QLOG_DEBUG() << "Cleaning up legacy assets folder:" << path;
folder.removeRecursively();
}
}
}
} }
/* /*

View File

@ -34,6 +34,6 @@ struct AssetsIndex
namespace AssetsUtils namespace AssetsUtils
{ {
void migrateOldAssets();
bool loadAssetsIndexJson(QString file, AssetsIndex* index); bool loadAssetsIndexJson(QString file, AssetsIndex* index);
int findLegacyAssets();
} }

View File

@ -32,7 +32,8 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
// The JSON object must at least have a username for it to be valid. // The JSON object must at least have a username for it to be valid.
if (!object.value("username").isString()) if (!object.value("username").isString())
{ {
QLOG_ERROR() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type."; QLOG_ERROR() << "Can't load Mojang account info from JSON object. Username field is "
"missing or of the wrong type.";
return nullptr; return nullptr;
} }
@ -43,7 +44,8 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
QJsonArray profileArray = object.value("profiles").toArray(); QJsonArray profileArray = object.value("profiles").toArray();
if (profileArray.size() < 1) if (profileArray.size() < 1)
{ {
QLOG_ERROR() << "Can't load Mojang account with username \"" << username << "\". No profiles found."; QLOG_ERROR() << "Can't load Mojang account with username \"" << username
<< "\". No profiles found.";
return nullptr; return nullptr;
} }
@ -168,7 +170,7 @@ AccountStatus MojangAccount::accountStatus() const
return Online; return Online;
} }
std::shared_ptr<Task> MojangAccount::login(QString password) std::shared_ptr<YggdrasilTask> MojangAccount::login(QString password)
{ {
if (m_currentTask) if (m_currentTask)
return m_currentTask; return m_currentTask;

View File

@ -95,7 +95,7 @@ public: /* manipulation */
* Attempt to login. Empty password means we use the token. * Attempt to login. Empty password means we use the token.
* If the attempt fails because we already are performing some task, it returns false. * If the attempt fails because we already are performing some task, it returns false.
*/ */
std::shared_ptr<Task> login(QString password = QString()); std::shared_ptr<YggdrasilTask> login(QString password = QString());
void downgrade() void downgrade()
{ {

View File

@ -48,6 +48,7 @@ void YggdrasilTask::executeTask()
connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply); connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply);
connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers); connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers);
connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers); connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers);
connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors);
timeout_keeper.setSingleShot(true); timeout_keeper.setSingleShot(true);
timeout_keeper.start(timeout_max); timeout_keeper.start(timeout_max);
counter.setSingleShot(false); counter.setSingleShot(false);
@ -75,16 +76,45 @@ void YggdrasilTask::abort()
m_netReply->abort(); m_netReply->abort();
} }
void YggdrasilTask::sslErrors(QList<QSslError> errors)
{
int i = 1;
for (auto error : errors)
{
QLOG_ERROR() << "LOGIN SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
QLOG_ERROR() << "Certificate in question:\n" << cert.toText();
i++;
}
}
void YggdrasilTask::processReply() void YggdrasilTask::processReply()
{ {
setStatus(getStateMessage(STATE_PROCESSING_RESPONSE)); setStatus(getStateMessage(STATE_PROCESSING_RESPONSE));
if (m_netReply->error() == QNetworkReply::SslHandshakeFailedError)
{
emitFailed(
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
"<ul>"
"<li>You use Windows XP and need to <a "
"href=\"http://www.microsoft.com/en-us/download/details.aspx?id=38918\">update "
"your root certificates</a></li>"
"<li>Some device on your network is interfering with SSL traffic. In that case, "
"you have bigger worries than Minecraft not starting.</li>"
"<li>Possibly something else. Check the MultiMC log file for details</li>"
"</ul>"));
return;
}
// any network errors lead to offline mode right now // any network errors lead to offline mode right now
if (m_netReply->error() >= QNetworkReply::ConnectionRefusedError && if (m_netReply->error() >= QNetworkReply::ConnectionRefusedError &&
m_netReply->error() <= QNetworkReply::UnknownNetworkError) m_netReply->error() <= QNetworkReply::UnknownNetworkError)
{ {
// WARNING/FIXME: the value here is used in MojangAccount to detect the cancel/timeout // WARNING/FIXME: the value here is used in MojangAccount to detect the cancel/timeout
emitFailed("Yggdrasil task cancelled."); emitFailed("Yggdrasil task cancelled.");
QLOG_ERROR() << "Yggdrasil task cancelled because of: " << m_netReply->error() << " : "
<< m_netReply->errorString();
return; return;
} }
@ -172,10 +202,10 @@ QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const
switch (state) switch (state)
{ {
case STATE_SENDING_REQUEST: case STATE_SENDING_REQUEST:
return tr("Sending request to auth servers."); return tr("Sending request to auth servers...");
case STATE_PROCESSING_RESPONSE: case STATE_PROCESSING_RESPONSE:
return tr("Processing response from servers."); return tr("Processing response from servers...");
default: default:
return tr("Processing. Please wait."); return tr("Processing. Please wait...");
} }
} }

View File

@ -20,6 +20,7 @@
#include <QString> #include <QString>
#include <QJsonObject> #include <QJsonObject>
#include <QTimer> #include <QTimer>
#include <qsslerror.h>
#include "logic/auth/MojangAccount.h" #include "logic/auth/MojangAccount.h"
@ -99,6 +100,7 @@ slots:
void processReply(); void processReply();
void refreshTimers(qint64, qint64); void refreshTimers(qint64, qint64);
void heartbeat(); void heartbeat();
void sslErrors(QList<QSslError>);
public public
slots: slots:

View File

@ -194,9 +194,9 @@ QString AuthenticateTask::getStateMessage(const YggdrasilTask::State state) cons
switch (state) switch (state)
{ {
case STATE_SENDING_REQUEST: case STATE_SENDING_REQUEST:
return tr("Authenticating: Sending request."); return tr("Authenticating: Sending request...");
case STATE_PROCESSING_RESPONSE: case STATE_PROCESSING_RESPONSE:
return tr("Authenticating: Processing response."); return tr("Authenticating: Processing response...");
default: default:
return YggdrasilTask::getStateMessage(state); return YggdrasilTask::getStateMessage(state);
} }

View File

@ -145,9 +145,9 @@ QString RefreshTask::getStateMessage(const YggdrasilTask::State state) const
switch (state) switch (state)
{ {
case STATE_SENDING_REQUEST: case STATE_SENDING_REQUEST:
return tr("Refreshing login token."); return tr("Refreshing login token...");
case STATE_PROCESSING_RESPONSE: case STATE_PROCESSING_RESPONSE:
return tr("Refreshing login token: Processing response."); return tr("Refreshing login token: Processing response...");
default: default:
return YggdrasilTask::getStateMessage(state); return YggdrasilTask::getStateMessage(state);
} }

View File

@ -55,9 +55,9 @@ QString ValidateTask::getStateMessage(const YggdrasilTask::State state) const
switch (state) switch (state)
{ {
case STATE_SENDING_REQUEST: case STATE_SENDING_REQUEST:
return tr("Validating Access Token: Sending request."); return tr("Validating access token: Sending request...");
case STATE_PROCESSING_RESPONSE: case STATE_PROCESSING_RESPONSE:
return tr("Validating Access Token: Processing response."); return tr("Validating access token: Processing response...");
default: default:
return YggdrasilTask::getStateMessage(state); return YggdrasilTask::getStateMessage(state);
} }

351
logic/icons/IconList.cpp Normal file
View File

@ -0,0 +1,351 @@
/* 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 "IconList.h"
#include <pathutils.h>
#include <settingsobject.h>
#include <QMap>
#include <QEventLoop>
#include <QMimeData>
#include <QUrl>
#include <QFileSystemWatcher>
#include <MultiMC.h>
#include <setting.h>
#define MAX_SIZE 1024
IconList::IconList(QObject *parent) : QAbstractListModel(parent)
{
// add builtin icons
QDir instance_icons(":/icons/instances/");
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list)
{
QString key = file_info.baseName();
addIcon(key, key, file_info.absoluteFilePath(), MMCIcon::Builtin);
}
m_watcher.reset(new QFileSystemWatcher());
is_watching = false;
connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
SLOT(directoryChanged(QString)));
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
auto setting = MMC->settings()->getSetting("IconsDir");
QString path = setting->get().toString();
connect(setting.get(), SIGNAL(settingChanged(const Setting &, QVariant)),
SLOT(settingChanged(const Setting &, QVariant)));
directoryChanged(path);
}
void IconList::directoryChanged(const QString &path)
{
QDir new_dir (path);
if(m_dir.absolutePath() != new_dir.absolutePath())
{
m_dir.setPath(path);
m_dir.refresh();
if(is_watching)
stopWatching();
startWatching();
}
if(!m_dir.exists())
if(!ensureFolderPathExists(m_dir.absolutePath()))
return;
m_dir.refresh();
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
for (auto it = new_list.begin(); it != new_list.end(); it++)
{
QString &foo = (*it);
foo = m_dir.filePath(foo);
}
auto new_set = new_list.toSet();
QList<QString> current_list;
for (auto &it : icons)
{
if (!it.has(MMCIcon::FileBased))
continue;
current_list.push_back(it.m_images[MMCIcon::FileBased].filename);
}
QSet<QString> current_set = current_list.toSet();
QSet<QString> to_remove = current_set;
to_remove -= new_set;
QSet<QString> to_add = new_set;
to_add -= current_set;
for (auto remove : to_remove)
{
QLOG_INFO() << "Removing " << remove;
QFileInfo rmfile(remove);
QString key = rmfile.baseName();
int idx = getIconIndex(key);
if (idx == -1)
continue;
icons[idx].remove(MMCIcon::FileBased);
if (icons[idx].type() == MMCIcon::ToBeDeleted)
{
beginRemoveRows(QModelIndex(), idx, idx);
icons.remove(idx);
reindex();
endRemoveRows();
}
else
{
dataChanged(index(idx), index(idx));
}
m_watcher->removePath(remove);
emit iconUpdated(key);
}
for (auto add : to_add)
{
QLOG_INFO() << "Adding " << add;
QFileInfo addfile(add);
QString key = addfile.baseName();
if (addIcon(key, QString(), addfile.filePath(), MMCIcon::FileBased))
{
m_watcher->addPath(add);
emit iconUpdated(key);
}
}
}
void IconList::fileChanged(const QString &path)
{
QLOG_INFO() << "Checking " << path;
QFileInfo checkfile(path);
if (!checkfile.exists())
return;
QString key = checkfile.baseName();
int idx = getIconIndex(key);
if (idx == -1)
return;
QIcon icon(path);
if (!icon.availableSizes().size())
return;
icons[idx].m_images[MMCIcon::FileBased].icon = icon;
dataChanged(index(idx), index(idx));
emit iconUpdated(key);
}
void IconList::settingChanged(const Setting &setting, QVariant value)
{
if(setting.id() != "IconsDir")
return;
directoryChanged(value.toString());
}
void IconList::startWatching()
{
auto abs_path = m_dir.absolutePath();
ensureFolderPathExists(abs_path);
is_watching = m_watcher->addPath(abs_path);
if (is_watching)
{
QLOG_INFO() << "Started watching " << abs_path;
}
else
{
QLOG_INFO() << "Failed to start watching " << abs_path;
}
}
void IconList::stopWatching()
{
m_watcher->removePaths(m_watcher->files());
m_watcher->removePaths(m_watcher->directories());
is_watching = false;
}
QStringList IconList::mimeTypes() const
{
QStringList types;
types << "text/uri-list";
return types;
}
Qt::DropActions IconList::supportedDropActions() const
{
return Qt::CopyAction;
}
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
// check if the action is supported
if (!data || !(action & supportedDropActions()))
return false;
// files dropped from outside?
if (data->hasUrls())
{
auto urls = data->urls();
QStringList iconFiles;
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
continue;
iconFiles += url.toLocalFile();
}
installIcons(iconFiles);
return true;
}
return false;
}
Qt::ItemFlags IconList::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
QVariant IconList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
if (row < 0 || row >= icons.size())
return QVariant();
switch (role)
{
case Qt::DecorationRole:
return icons[row].icon();
case Qt::DisplayRole:
return icons[row].name();
case Qt::UserRole:
return icons[row].m_key;
default:
return QVariant();
}
}
int IconList::rowCount(const QModelIndex &parent) const
{
return icons.size();
}
void IconList::installIcons(QStringList iconFiles)
{
for (QString file : iconFiles)
{
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
QString target = PathCombine("icons", fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
continue;
if (!QFile::copy(file, target))
continue;
}
}
bool IconList::deleteIcon(QString key)
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
return false;
auto &iconEntry = icons[iconIdx];
if (iconEntry.has(MMCIcon::FileBased))
{
return QFile::remove(iconEntry.m_images[MMCIcon::FileBased].filename);
}
return false;
}
bool IconList::addIcon(QString key, QString name, QString path, MMCIcon::Type type)
{
// replace the icon even? is the input valid?
QIcon icon(path);
if (!icon.availableSizes().size())
return false;
auto iter = name_index.find(key);
if (iter != name_index.end())
{
auto &oldOne = icons[*iter];
oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter));
return true;
}
else
{
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = name;
mmc_icon.m_key = key;
mmc_icon.replace(type, icon, path);
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
}
endInsertRows();
return true;
}
}
void IconList::reindex()
{
name_index.clear();
int i = 0;
for (auto &iter : icons)
{
name_index[iter.m_key] = i;
i++;
}
}
QIcon IconList::getIcon(QString key)
{
int icon_index = getIconIndex(key);
if (icon_index != -1)
return icons[icon_index].icon();
// Fallback for icons that don't exist.
icon_index = getIconIndex("infinity");
if (icon_index != -1)
return icons[icon_index].icon();
return QIcon();
}
int IconList::getIconIndex(QString key)
{
if (key == "default")
key = "infinity";
auto iter = name_index.find(key);
if (iter != name_index.end())
return *iter;
return -1;
}
//#include "IconList.moc"

View File

@ -17,15 +17,21 @@
#include <QMutex> #include <QMutex>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QFile>
#include <QDir>
#include <QtGui/QIcon> #include <QtGui/QIcon>
#include <memory>
#include "MMCIcon.h"
#include "setting.h"
class Private; class QFileSystemWatcher;
class IconList : public QAbstractListModel class IconList : public QAbstractListModel
{ {
Q_OBJECT
public: public:
IconList(); explicit IconList(QObject *parent = 0);
virtual ~IconList(); virtual ~IconList() {};
QIcon getIcon(QString key); QIcon getIcon(QString key);
int getIconIndex(QString key); int getIconIndex(QString key);
@ -33,7 +39,7 @@ public:
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
bool addIcon(QString key, QString name, QString path, bool is_builtin = false); bool addIcon(QString key, QString name, QString path, MMCIcon::Type type);
bool deleteIcon(QString key); bool deleteIcon(QString key);
virtual QStringList mimeTypes() const; virtual QStringList mimeTypes() const;
@ -44,11 +50,28 @@ public:
void installIcons(QStringList iconFiles); void installIcons(QStringList iconFiles);
void startWatching();
void stopWatching();
signals:
void iconUpdated(QString key);
private: private:
// hide copy constructor // hide copy constructor
IconList(const IconList &) = delete; IconList(const IconList &) = delete;
// hide assign op // hide assign op
IconList &operator=(const IconList &) = delete; IconList &operator=(const IconList &) = delete;
void reindex(); void reindex();
Private *d;
protected
slots:
void directoryChanged(const QString &path);
void fileChanged(const QString &path);
void settingChanged(const Setting & setting, QVariant value);
private:
std::shared_ptr<QFileSystemWatcher> m_watcher;
bool is_watching;
QMap<QString, int> name_index;
QVector<MMCIcon> icons;
QDir m_dir;
}; };

89
logic/icons/MMCIcon.cpp Normal file
View File

@ -0,0 +1,89 @@
/* 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 "MMCIcon.h"
#include <QFileInfo>
MMCIcon::Type operator--(MMCIcon::Type &t, int)
{
MMCIcon::Type temp = t;
switch (t)
{
case MMCIcon::Type::Builtin:
t = MMCIcon::Type::ToBeDeleted;
break;
case MMCIcon::Type::Transient:
t = MMCIcon::Type::Builtin;
break;
case MMCIcon::Type::FileBased:
t = MMCIcon::Type::Transient;
break;
default:
{
}
}
return temp;
}
MMCIcon::Type MMCIcon::type() const
{
return m_current_type;
}
QString MMCIcon::name() const
{
if (m_name.size())
return m_name;
return m_key;
}
bool MMCIcon::has(MMCIcon::Type _type) const
{
return m_images[_type].present();
}
QIcon MMCIcon::icon() const
{
if (m_current_type == Type::ToBeDeleted)
return QIcon();
return m_images[m_current_type].icon;
}
void MMCIcon::remove(Type rm_type)
{
m_images[rm_type].filename = QString();
m_images[rm_type].icon = QIcon();
for (auto iter = rm_type; iter != Type::ToBeDeleted; iter--)
{
if (m_images[iter].present())
{
m_current_type = iter;
return;
}
}
m_current_type = Type::ToBeDeleted;
}
void MMCIcon::replace(MMCIcon::Type new_type, QIcon icon, QString path)
{
QFileInfo foo(path);
if (new_type > m_current_type || m_current_type == MMCIcon::ToBeDeleted)
{
m_current_type = new_type;
}
m_images[new_type].icon = icon;
m_images[new_type].changed = foo.lastModified();
m_images[new_type].filename = path;
}

View File

@ -13,32 +13,40 @@
* limitations under the License. * limitations under the License.
*/ */
#include "include/basicsettingsobject.h" #pragma once
#include "include/setting.h" #include <QString>
#include <QDateTime>
#include <QIcon>
struct MMCImage
{
QIcon icon;
QString filename;
QDateTime changed;
bool present() const
{
return !icon.isNull();
}
};
BasicSettingsObject::BasicSettingsObject(QObject *parent) : SettingsObject(parent) struct MMCIcon
{ {
} enum Type : unsigned
{
Builtin,
Transient,
FileBased,
ICONS_TOTAL,
ToBeDeleted
};
QString m_key;
QString m_name;
MMCImage m_images[ICONS_TOTAL];
Type m_current_type = ToBeDeleted;
void BasicSettingsObject::changeSetting(const Setting &setting, QVariant value) Type type() const;
{ QString name() const;
if (contains(setting.id())) bool has(Type _type) const;
{ QIcon icon() const;
if (value.isValid()) void remove(Type rm_type);
config.setValue(setting.configKey(), value); void replace(Type new_type, QIcon icon, QString path = QString());
else };
config.remove(setting.configKey());
}
}
QVariant BasicSettingsObject::retrieveValue(const Setting &setting)
{
if (contains(setting.id()))
{
return config.value(setting.configKey());
}
else
{
return QVariant();
}
}

View File

@ -15,6 +15,7 @@
#include "ForgeVersionList.h" #include "ForgeVersionList.h"
#include <logic/net/NetJob.h> #include <logic/net/NetJob.h>
#include <logic/net/URLConstants.h>
#include "MultiMC.h" #include "MultiMC.h"
#include <QtNetwork> #include <QtNetwork>
@ -23,8 +24,6 @@
#include "logger/QsLog.h" #include "logger/QsLog.h"
#define JSON_URL "http://files.minecraftforge.net/minecraftforge/json"
ForgeVersionList::ForgeVersionList(QObject *parent) : BaseVersionList(parent) ForgeVersionList::ForgeVersionList(QObject *parent) : BaseVersionList(parent)
{ {
} }
@ -159,44 +158,43 @@ ForgeListLoadTask::ForgeListLoadTask(ForgeVersionList *vlist) : Task()
void ForgeListLoadTask::executeTask() void ForgeListLoadTask::executeTask()
{ {
setStatus(tr("Fetching Forge version lists..."));
auto job = new NetJob("Version index"); auto job = new NetJob("Version index");
// we do not care if the version is stale or not. // we do not care if the version is stale or not.
auto forgeListEntry = MMC->metacache()->resolveEntry("minecraftforge", "list.json"); auto forgeListEntry = MMC->metacache()->resolveEntry("minecraftforge", "list.json");
auto gradleForgeListEntry = MMC->metacache()->resolveEntry("minecraftforge", "json");
// verify by poking the server. // verify by poking the server.
forgeListEntry->stale = true; forgeListEntry->stale = true;
gradleForgeListEntry->stale = true;
job->addNetAction(listDownload = CacheDownload::make(QUrl(URLConstants::FORGE_LEGACY_URL),
forgeListEntry));
job->addNetAction(gradleListDownload = CacheDownload::make(
QUrl(URLConstants::FORGE_GRADLE_URL), gradleForgeListEntry));
connect(listDownload.get(), SIGNAL(failed(int)), SLOT(listFailed()));
connect(gradleListDownload.get(), SIGNAL(failed(int)), SLOT(gradleListFailed()));
job->addNetAction(CacheDownload::make(QUrl(JSON_URL), forgeListEntry));
listJob.reset(job); listJob.reset(job);
connect(listJob.get(), SIGNAL(succeeded()), SLOT(list_downloaded())); connect(listJob.get(), SIGNAL(succeeded()), SLOT(listDownloaded()));
connect(listJob.get(), SIGNAL(failed()), SLOT(list_failed()));
connect(listJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); connect(listJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
listJob->start(); listJob->start();
} }
void ForgeListLoadTask::list_failed() bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out)
{
auto DlJob = listJob->first();
auto reply = DlJob->m_reply;
if (reply)
{
QLOG_ERROR() << "Getting forge version list failed: " << reply->errorString();
}
else
QLOG_ERROR() << "Getting forge version list failed for reasons unknown.";
}
void ForgeListLoadTask::list_downloaded()
{ {
QByteArray data; QByteArray data;
{ {
auto DlJob = listJob->first(); auto dlJob = listDownload;
auto filename = std::dynamic_pointer_cast<CacheDownload>(DlJob)->m_target_path; auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->m_target_path;
QFile listFile(filename); QFile listFile(filename);
if (!listFile.open(QIODevice::ReadOnly)) if (!listFile.open(QIODevice::ReadOnly))
return; {
return false;
}
data = listFile.readAll(); data = listFile.readAll();
DlJob.reset(); dlJob.reset();
} }
QJsonParseError jsonError; QJsonParseError jsonError;
@ -205,13 +203,13 @@ void ForgeListLoadTask::list_downloaded()
if (jsonError.error != QJsonParseError::NoError) if (jsonError.error != QJsonParseError::NoError)
{ {
emitFailed("Error parsing version list JSON:" + jsonError.errorString()); emitFailed("Error parsing version list JSON:" + jsonError.errorString());
return; return false;
} }
if (!jsonDoc.isObject()) if (!jsonDoc.isObject())
{ {
emitFailed("Error parsing version list JSON: jsonDoc is not an object"); emitFailed("Error parsing version list JSON: JSON root is not an object");
return; return false;
} }
QJsonObject root = jsonDoc.object(); QJsonObject root = jsonDoc.object();
@ -221,11 +219,10 @@ void ForgeListLoadTask::list_downloaded()
{ {
emitFailed( emitFailed(
"Error parsing version list JSON: version list object is missing 'builds' array"); "Error parsing version list JSON: version list object is missing 'builds' array");
return; return false;
} }
QJsonArray builds = root.value("builds").toArray(); QJsonArray builds = root.value("builds").toArray();
QList<BaseVersionPtr> tempList;
for (int i = 0; i < builds.count(); i++) for (int i = 0; i < builds.count(); i++)
{ {
// Load the version info. // Load the version info.
@ -246,7 +243,9 @@ void ForgeListLoadTask::list_downloaded()
for (int j = 0; j < files.count(); j++) for (int j = 0; j < files.count(); j++)
{ {
if (!files[j].isObject()) if (!files[j].isObject())
{
continue; continue;
}
QJsonObject file = files[j].toObject(); QJsonObject file = files[j].toObject();
buildtype = file.value("buildtype").toString(); buildtype = file.value("buildtype").toString();
if ((buildtype == "client" || buildtype == "universal") && !valid) if ((buildtype == "client" || buildtype == "universal") && !valid)
@ -262,7 +261,9 @@ void ForgeListLoadTask::list_downloaded()
{ {
QString ext = file.value("ext").toString(); QString ext = file.value("ext").toString();
if (ext.isEmpty()) if (ext.isEmpty())
{
continue; continue;
}
changelog_url = file.value("url").toString(); changelog_url = file.value("url").toString();
} }
else if (buildtype == "installer") else if (buildtype == "installer")
@ -282,15 +283,161 @@ void ForgeListLoadTask::list_downloaded()
fVersion->jobbuildver = jobbuildver; fVersion->jobbuildver = jobbuildver;
fVersion->mcver = mcver; fVersion->mcver = mcver;
if (installer_filename.isEmpty()) if (installer_filename.isEmpty())
{
fVersion->filename = filename; fVersion->filename = filename;
}
else else
{
fVersion->filename = installer_filename; fVersion->filename = installer_filename;
}
fVersion->m_buildnr = build_nr; fVersion->m_buildnr = build_nr;
tempList.append(fVersion); out.append(fVersion);
} }
} }
m_list->updateListData(tempList);
return true;
}
bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out)
{
QByteArray data;
{
auto dlJob = gradleListDownload;
auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->m_target_path;
QFile listFile(filename);
if (!listFile.open(QIODevice::ReadOnly))
{
return false;
}
data = listFile.readAll();
dlJob.reset();
}
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
emitFailed("Error parsing gradle version list JSON:" + jsonError.errorString());
return false;
}
if (!jsonDoc.isObject())
{
emitFailed("Error parsing gradle version list JSON: JSON root is not an object");
return false;
}
QJsonObject root = jsonDoc.object();
// we probably could hard code these, but it might still be worth doing it this way
const QString webpath = root.value("webpath").toString();
const QString artifact = root.value("artifact").toString();
QJsonObject numbers = root.value("number").toObject();
for (auto it = numbers.begin(); it != numbers.end(); ++it)
{
QJsonObject number = it.value().toObject();
std::shared_ptr<ForgeVersion> fVersion(new ForgeVersion());
fVersion->m_buildnr = number.value("build").toDouble();
fVersion->jobbuildver = number.value("version").toString();
fVersion->mcver = number.value("mcversion").toString();
fVersion->filename = "";
QString filename, installer_filename;
QJsonArray files = number.value("files").toArray();
for (auto fIt = files.begin(); fIt != files.end(); ++fIt)
{
// TODO with gradle we also get checksums, use them
QJsonArray file = (*fIt).toArray();
if (file.size() < 3)
{
continue;
}
if (file.at(1).toString() == "installer")
{
fVersion->installer_url = QString("%1/%2-%3/%4-%2-%3-installer.%5").arg(
webpath, fVersion->mcver, fVersion->jobbuildver, artifact,
file.at(0).toString());
installer_filename = QString("%1-%2-%3-installer.%4").arg(
artifact, fVersion->mcver, fVersion->jobbuildver, file.at(0).toString());
}
else if (file.at(1).toString() == "universal")
{
fVersion->universal_url = QString("%1/%2-%3/%4-%2-%3-universal.%5").arg(
webpath, fVersion->mcver, fVersion->jobbuildver, artifact,
file.at(0).toString());
filename = QString("%1-%2-%3-universal.%4").arg(
artifact, fVersion->mcver, fVersion->jobbuildver, file.at(0).toString());
}
else if (file.at(1).toString() == "changelog")
{
fVersion->changelog_url = QString("%1/%2-%3/%4-%2-%3-changelog.%5").arg(
webpath, fVersion->mcver, fVersion->jobbuildver, artifact,
file.at(0).toString());
}
}
if (fVersion->installer_url.isEmpty() && fVersion->universal_url.isEmpty())
{
continue;
}
fVersion->filename = fVersion->installer_url.isEmpty() ? filename : installer_filename;
out.append(fVersion);
}
return true;
}
void ForgeListLoadTask::listDownloaded()
{
QList<BaseVersionPtr> list;
bool ret = true;
if (!parseForgeList(list))
{
ret = false;
}
if (!parseForgeGradleList(list))
{
ret = false;
}
if (!ret)
{
return;
}
qSort(list.begin(), list.end(), [](const BaseVersionPtr & p1, const BaseVersionPtr & p2)
{
// TODO better comparison (takes major/minor/build number into account)
return p1->name() > p2->name();
});
m_list->updateListData(list);
emitSucceeded(); emitSucceeded();
return; return;
} }
void ForgeListLoadTask::listFailed()
{
auto reply = listDownload->m_reply;
if (reply)
{
QLOG_ERROR() << "Getting forge version list failed: " << reply->errorString();
}
else
{
QLOG_ERROR() << "Getting forge version list failed for reasons unknown.";
}
}
void ForgeListLoadTask::gradleListFailed()
{
auto reply = gradleListDownload->m_reply;
if (reply)
{
QLOG_ERROR() << "Getting forge version list failed: " << reply->errorString();
}
else
{
QLOG_ERROR() << "Getting forge version list failed for reasons unknown.";
}
}

View File

@ -80,7 +80,7 @@ public:
protected: protected:
QList<BaseVersionPtr> m_vlist; QList<BaseVersionPtr> m_vlist;
bool m_loaded; bool m_loaded = false;
protected protected
slots: slots:
@ -98,10 +98,18 @@ public:
protected protected
slots: slots:
void list_downloaded(); void listDownloaded();
void list_failed(); void listFailed();
void gradleListFailed();
protected: protected:
NetJobPtr listJob; NetJobPtr listJob;
ForgeVersionList *m_list; ForgeVersionList *m_list;
CacheDownloadPtr listDownload;
CacheDownloadPtr gradleListDownload;
private:
bool parseForgeList(QList<BaseVersionPtr> &out);
bool parseForgeGradleList(QList<BaseVersionPtr> &out);
}; };

View File

@ -1,271 +0,0 @@
/* 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 "IconList.h"
#include <pathutils.h>
#include <QMap>
#include <QEventLoop>
#include <QDir>
#include <QMimeData>
#include <QUrl>
#define MAX_SIZE 1024
struct entry
{
QString key;
QString name;
QIcon icon;
bool is_builtin;
QString filename;
};
class Private : public QObject
{
Q_OBJECT
public:
QMap<QString, int> index;
QVector<entry> icons;
Private()
{
}
};
IconList::IconList() : QAbstractListModel(), d(new Private())
{
QDir instance_icons(":/icons/instances/");
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list)
{
QString key = file_info.baseName();
addIcon(key, key, file_info.absoluteFilePath(), true);
}
// FIXME: get from settings
ensureFolderPathExists("icons");
QDir user_icons("icons");
file_info_list = user_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list)
{
QString filename = file_info.absoluteFilePath();
QString key = file_info.baseName();
addIcon(key, key, filename);
}
}
IconList::~IconList()
{
delete d;
d = nullptr;
}
QStringList IconList::mimeTypes() const
{
QStringList types;
types << "text/uri-list";
return types;
}
Qt::DropActions IconList::supportedDropActions() const
{
return Qt::CopyAction;
}
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
// check if the action is supported
if (!data || !(action & supportedDropActions()))
return false;
// files dropped from outside?
if (data->hasUrls())
{
/*
bool was_watching = is_watching;
if(was_watching)
stopWatching();
*/
auto urls = data->urls();
QStringList iconFiles;
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
continue;
iconFiles += url.toLocalFile();
}
installIcons(iconFiles);
/*
if(was_watching)
startWatching();
*/
return true;
}
return false;
}
Qt::ItemFlags IconList::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
QVariant IconList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
if (row < 0 || row >= d->icons.size())
return QVariant();
switch (role)
{
case Qt::DecorationRole:
return d->icons[row].icon;
case Qt::DisplayRole:
return d->icons[row].name;
case Qt::UserRole:
return d->icons[row].key;
default:
return QVariant();
}
}
int IconList::rowCount(const QModelIndex &parent) const
{
return d->icons.size();
}
void IconList::installIcons(QStringList iconFiles)
{
for (QString file : iconFiles)
{
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
QString target = PathCombine("icons", fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg")
continue;
if (!QFile::copy(file, target))
continue;
QString key = fileinfo.baseName();
addIcon(key, key, target);
}
}
bool IconList::deleteIcon(QString key)
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
return false;
auto &iconEntry = d->icons[iconIdx];
if (iconEntry.is_builtin)
return false;
if (QFile::remove(iconEntry.filename))
{
beginRemoveRows(QModelIndex(), iconIdx, iconIdx);
d->icons.remove(iconIdx);
reindex();
endRemoveRows();
}
return true;
}
bool IconList::addIcon(QString key, QString name, QString path, bool is_builtin)
{
auto iter = d->index.find(key);
if (iter != d->index.end())
{
if (d->icons[*iter].is_builtin)
return false;
QIcon icon(path);
if (icon.isNull())
return false;
auto &oldOne = d->icons[*iter];
if (!QFile::remove(oldOne.filename))
return false;
// replace the icon
oldOne = {key, name, icon, is_builtin, path};
dataChanged(index(*iter), index(*iter));
return true;
}
else
{
QIcon icon(path);
if (icon.isNull())
return false;
// add a new icon
beginInsertRows(QModelIndex(), d->icons.size(), d->icons.size());
d->icons.push_back({key, name, icon, is_builtin, path});
d->index[key] = d->icons.size() - 1;
endInsertRows();
return true;
}
}
void IconList::reindex()
{
d->index.clear();
int i = 0;
for (auto &iter : d->icons)
{
d->index[iter.key] = i;
i++;
}
}
QIcon IconList::getIcon(QString key)
{
int icon_index = getIconIndex(key);
if (icon_index != -1)
return d->icons[icon_index].icon;
// Fallback for icons that don't exist.
icon_index = getIconIndex("infinity");
if (icon_index != -1)
return d->icons[icon_index].icon;
return QIcon();
}
int IconList::getIconIndex(QString key)
{
if (key == "default")
key = "infinity";
auto iter = d->index.find(key);
if (iter != d->index.end())
return *iter;
return -1;
}
#include "IconList.moc"

View File

@ -22,11 +22,14 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonArray> #include <QJsonArray>
#include <QXmlStreamReader>
#include <QRegularExpression>
#include <pathutils.h> #include <pathutils.h>
#include "MultiMC.h" #include "MultiMC.h"
#include "logic/lists/InstanceList.h" #include "logic/lists/InstanceList.h"
#include "logic/lists/IconList.h" #include "logic/icons/IconList.h"
#include "logic/lists/MinecraftVersionList.h"
#include "logic/BaseInstance.h" #include "logic/BaseInstance.h"
#include "logic/InstanceFactory.h" #include "logic/InstanceFactory.h"
#include "logger/QsLog.h" #include "logger/QsLog.h"
@ -42,6 +45,9 @@ InstanceList::InstanceList(const QString &instDir, QObject *parent)
{ {
QDir::current().mkpath(m_instDir); QDir::current().mkpath(m_instDir);
} }
connect(MMC->minecraftlist().get(), &MinecraftVersionList::modelReset, this,
&InstanceList::loadList);
} }
InstanceList::~InstanceList() InstanceList::~InstanceList()
@ -276,6 +282,125 @@ void InstanceList::loadGroupList(QMap<QString, QString> &groupMap)
} }
} }
struct FTBRecord
{
QString dir;
QString name;
QString logo;
QString mcVersion;
QString description;
};
void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
{
QList<FTBRecord> records;
QDir dir = QDir(MMC->settings()->get("FTBLauncherRoot").toString());
QDir dataDir = QDir(MMC->settings()->get("FTBRoot").toString());
if (!dir.exists())
{
QLOG_INFO() << "The FTB launcher directory specified does not exist. Please check your "
"settings.";
return;
}
else if (!dataDir.exists())
{
QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings";
return;
}
dir.cd("ModPacks");
QFile f(dir.absoluteFilePath("modpacks.xml"));
if (!f.open(QFile::ReadOnly))
return;
// read the FTB packs XML.
QXmlStreamReader reader(&f);
while (!reader.atEnd())
{
switch (reader.readNext())
{
case QXmlStreamReader::StartElement:
{
if (reader.name() == "modpack")
{
QXmlStreamAttributes attrs = reader.attributes();
FTBRecord record;
record.dir = attrs.value("dir").toString();
record.name = attrs.value("name").toString();
record.logo = attrs.value("logo").toString();
record.mcVersion = attrs.value("mcVersion").toString();
record.description = attrs.value("description").toString();
records.append(record);
}
break;
}
case QXmlStreamReader::EndElement:
break;
case QXmlStreamReader::Characters:
break;
default:
break;
}
}
f.close();
// process the records we acquired.
for (auto record : records)
{
auto instanceDir = dataDir.absoluteFilePath(record.dir);
auto templateDir = dir.absoluteFilePath(record.dir);
if (!QFileInfo(instanceDir).exists())
{
continue;
}
QString iconKey = record.logo;
iconKey.remove(QRegularExpression("\\..*"));
MMC->icons()->addIcon(iconKey, iconKey, PathCombine(templateDir, record.logo),
MMCIcon::Transient);
if (!QFileInfo(PathCombine(instanceDir, "instance.cfg")).exists())
{
BaseInstance *instPtr = NULL;
auto &factory = InstanceFactory::get();
auto version = MMC->minecraftlist()->findVersion(record.mcVersion);
if (!version)
{
QLOG_ERROR() << "Can't load instance " << instanceDir
<< " because minecraft version " << record.mcVersion
<< " can't be resolved.";
continue;
}
auto error = factory.createInstance(instPtr, version, instanceDir,
InstanceFactory::FTBInstance);
if (!instPtr || error != InstanceFactory::NoCreateError)
continue;
instPtr->setGroupInitial("FTB");
instPtr->setName(record.name);
instPtr->setIconKey(iconKey);
instPtr->setIntendedVersionId(record.mcVersion);
instPtr->setNotes(record.description);
continueProcessInstance(instPtr, error, instanceDir, groupMap);
}
else
{
BaseInstance *instPtr = NULL;
auto error = InstanceFactory::get().loadInstance(instPtr, instanceDir);
if (!instPtr || error != InstanceFactory::NoCreateError)
continue;
instPtr->setGroupInitial("FTB");
instPtr->setName(record.name);
instPtr->setIconKey(iconKey);
if (instPtr->intendedVersionId() != record.mcVersion)
instPtr->setIntendedVersionId(record.mcVersion);
instPtr->setNotes(record.description);
continueProcessInstance(instPtr, error, instanceDir, groupMap);
}
}
}
InstanceList::InstListError InstanceList::loadList() InstanceList::InstListError InstanceList::loadList()
{ {
// load the instance groups // load the instance groups
@ -285,7 +410,8 @@ InstanceList::InstListError InstanceList::loadList()
beginResetModel(); beginResetModel();
m_instances.clear(); m_instances.clear();
QDir dir(m_instDir);
{
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
QDirIterator::FollowSymlinks); QDirIterator::FollowSymlinks);
while (iter.hasNext()) while (iter.hasNext())
@ -295,47 +421,16 @@ InstanceList::InstListError InstanceList::loadList()
continue; continue;
BaseInstance *instPtr = NULL; BaseInstance *instPtr = NULL;
auto &loader = InstanceFactory::get(); auto error = InstanceFactory::get().loadInstance(instPtr, subDir);
auto error = loader.loadInstance(instPtr, subDir); continueProcessInstance(instPtr, error, subDir, groupMap);
}
}
if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance) if (MMC->settings()->get("TrackFTBInstances").toBool())
{ {
QString errorMsg = QString("Failed to load instance %1: ") loadForgeInstances(groupMap);
.arg(QFileInfo(subDir).baseName()) }
.toUtf8();
switch (error)
{
default:
errorMsg += QString("Unknown instance loader error %1").arg(error);
break;
}
QLOG_ERROR() << errorMsg.toUtf8();
}
else if (!instPtr)
{
QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.")
.arg(QFileInfo(subDir).baseName())
.toUtf8();
}
else
{
std::shared_ptr<BaseInstance> inst(instPtr);
auto iter = groupMap.find(inst->id());
if (iter != groupMap.end())
{
inst->setGroupInitial((*iter));
}
QLOG_INFO() << "Loaded instance " << inst->name();
inst->setParent(this);
m_instances.append(inst);
connect(instPtr, SIGNAL(propertiesChanged(BaseInstance *)), this,
SLOT(propertiesChanged(BaseInstance *)));
connect(instPtr, SIGNAL(groupChanged()), this, SLOT(groupChanged()));
connect(instPtr, SIGNAL(nuked(BaseInstance *)), this,
SLOT(instanceNuked(BaseInstance *)));
}
}
endResetModel(); endResetModel();
emit dataIsInvalid(); emit dataIsInvalid();
return NoError; return NoError;
@ -409,6 +504,47 @@ int InstanceList::getInstIndex(BaseInstance *inst) const
return -1; return -1;
} }
void InstanceList::continueProcessInstance(BaseInstance *instPtr, const int error,
const QDir &dir, QMap<QString, QString> &groupMap)
{
if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance)
{
QString errorMsg = QString("Failed to load instance %1: ")
.arg(QFileInfo(dir.absolutePath()).baseName())
.toUtf8();
switch (error)
{
default:
errorMsg += QString("Unknown instance loader error %1").arg(error);
break;
}
QLOG_ERROR() << errorMsg.toUtf8();
}
else if (!instPtr)
{
QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.")
.arg(QFileInfo(dir.absolutePath()).baseName())
.toUtf8();
}
else
{
auto iter = groupMap.find(instPtr->id());
if (iter != groupMap.end())
{
instPtr->setGroupInitial((*iter));
}
QLOG_INFO() << "Loaded instance " << instPtr->name();
instPtr->setParent(this);
m_instances.append(std::shared_ptr<BaseInstance>(instPtr));
connect(instPtr, SIGNAL(propertiesChanged(BaseInstance *)), this,
SLOT(propertiesChanged(BaseInstance *)));
connect(instPtr, SIGNAL(groupChanged()), this, SLOT(groupChanged()));
connect(instPtr, SIGNAL(nuked(BaseInstance *)), this,
SLOT(instanceNuked(BaseInstance *)));
}
}
void InstanceList::instanceNuked(BaseInstance *inst) void InstanceList::instanceNuked(BaseInstance *inst)
{ {
int i = getInstIndex(inst); int i = getInstIndex(inst);

View File

@ -25,6 +25,8 @@
class BaseInstance; class BaseInstance;
class QDir;
class InstanceList : public QAbstractListModel class InstanceList : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
@ -65,11 +67,6 @@ public:
return m_instDir; return m_instDir;
} }
/*!
* \brief Loads the instance list. Triggers notifications.
*/
InstListError loadList();
/*! /*!
* \brief Get the instance at index * \brief Get the instance at index
*/ */
@ -108,6 +105,12 @@ public
slots: slots:
void on_InstFolderChanged(const Setting &setting, QVariant value); void on_InstFolderChanged(const Setting &setting, QVariant value);
/*!
* \brief Loads the instance list. Triggers notifications.
*/
InstListError loadList();
void loadForgeInstances(QMap<QString, QString> groupMap);
private private
slots: slots:
void propertiesChanged(BaseInstance *inst); void propertiesChanged(BaseInstance *inst);
@ -117,6 +120,9 @@ slots:
private: private:
int getInstIndex(BaseInstance *inst) const; int getInstIndex(BaseInstance *inst) const;
void continueProcessInstance(BaseInstance *instPtr, const int error, const QDir &dir,
QMap<QString, QString> &groupMap);
protected: protected:
QString m_instDir; QString m_instDir;
QList<InstancePtr> m_instances; QList<InstancePtr> m_instances;

View File

@ -172,14 +172,14 @@ JavaListLoadTask::~JavaListLoadTask()
void JavaListLoadTask::executeTask() void JavaListLoadTask::executeTask()
{ {
setStatus("Detecting Java installations..."); setStatus(tr("Detecting Java installations..."));
JavaUtils ju; JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths(); QList<QString> candidate_paths = ju.FindJavaPaths();
auto job = new JavaCheckerJob("Java detection"); m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
connect(job, SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>))); connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
connect(job, SIGNAL(progress(int, int)), this, SLOT(checkerProgress(int, int))); connect(m_job.get(), SIGNAL(progress(int, int)), this, SLOT(checkerProgress(int, int)));
QLOG_DEBUG() << "Probing the following Java paths: "; QLOG_DEBUG() << "Probing the following Java paths: ";
for(QString candidate : candidate_paths) for(QString candidate : candidate_paths)
@ -188,10 +188,10 @@ void JavaListLoadTask::executeTask()
auto candidate_checker = new JavaChecker(); auto candidate_checker = new JavaChecker();
candidate_checker->path = candidate; candidate_checker->path = candidate;
job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
} }
job->start(); m_job->start();
} }
void JavaListLoadTask::checkerProgress(int current, int total) void JavaListLoadTask::checkerProgress(int current, int total)
@ -203,6 +203,7 @@ void JavaListLoadTask::checkerProgress(int current, int total)
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results) void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
{ {
QList<JavaVersionPtr> candidates; QList<JavaVersionPtr> candidates;
m_job.reset();
QLOG_DEBUG() << "Found the following valid Java installations:"; QLOG_DEBUG() << "Found the following valid Java installations:";
for(JavaCheckResult result : results) for(JavaCheckResult result : results)

View File

@ -90,6 +90,7 @@ public slots:
void checkerProgress(int current, int total); void checkerProgress(int current, int total);
protected: protected:
std::shared_ptr<JavaCheckerJob> m_job;
JavaVersionList *m_list; JavaVersionList *m_list;
JavaVersion *m_currentRecommended; JavaVersion *m_currentRecommended;
}; };

View File

@ -139,7 +139,7 @@ MCVListLoadTask::~MCVListLoadTask()
void MCVListLoadTask::executeTask() void MCVListLoadTask::executeTask()
{ {
setStatus("Loading instance version list..."); setStatus(tr("Loading instance version list..."));
auto worker = MMC->qnam(); auto worker = MMC->qnam();
vlistReply = worker->get(QNetworkRequest(QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json"))); vlistReply = worker->get(QNetworkRequest(QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json")));
connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded()));

View File

@ -20,6 +20,7 @@
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QFileInfo> #include <QFileInfo>
#include <QDateTime> #include <QDateTime>
#include <QDir>
#include "logger/QsLog.h" #include "logger/QsLog.h"
ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : NetAction() ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : NetAction()
@ -310,16 +311,51 @@ void ForgeXzDownload::decompressAndInstall()
m_pack200_xz_file.remove(); m_pack200_xz_file.remove();
// revert pack200 // revert pack200
pack200_file.close(); pack200_file.seek(0);
QString pack_name = pack200_file.fileName(); int handle_in = pack200_file.handle();
// FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects.
if(handle_in == -1)
{
QLOG_ERROR() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
FILE * file_in = fdopen(handle_in,"r");
if(!file_in)
{
QLOG_ERROR() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
QFile qfile_out(m_target_path);
if(!qfile_out.open(QIODevice::WriteOnly))
{
QLOG_ERROR() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
int handle_out = qfile_out.handle();
if(handle_out == -1)
{
QLOG_ERROR() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
FILE * file_out = fdopen(handle_out,"w");
if(!file_out)
{
QLOG_ERROR() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
try try
{ {
unpack_200(pack_name.toStdString(), m_target_path.toStdString()); unpack_200(file_in, file_out);
} }
catch (std::runtime_error &err) catch (std::runtime_error &err)
{ {
m_status = Job_Failed; m_status = Job_Failed;
QLOG_ERROR() << "Error unpacking " << pack_name.toUtf8() << " : " << err.what(); QLOG_ERROR() << "Error unpacking " << pack200_file.fileName() << " : " << err.what();
QFile f(m_target_path); QFile f(m_target_path);
if (f.exists()) if (f.exists())
f.remove(); f.remove();

View File

@ -23,7 +23,6 @@ MD5EtagDownload::MD5EtagDownload(QUrl url, QString target_path) : NetAction()
{ {
m_url = url; m_url = url;
m_target_path = target_path; m_target_path = target_path;
m_check_md5 = false;
m_status = Job_NotStarted; m_status = Job_NotStarted;
} }
@ -34,22 +33,26 @@ void MD5EtagDownload::start()
// if there already is a file and md5 checking is in effect and it can be opened // if there already is a file and md5 checking is in effect and it can be opened
if (m_output_file.exists() && m_output_file.open(QIODevice::ReadOnly)) if (m_output_file.exists() && m_output_file.open(QIODevice::ReadOnly))
{ {
// check the md5 against the expected one // get the md5 of the local file.
QString hash = m_local_md5 =
QCryptographicHash::hash(m_output_file.readAll(), QCryptographicHash::Md5) QCryptographicHash::hash(m_output_file.readAll(), QCryptographicHash::Md5)
.toHex() .toHex()
.constData(); .constData();
m_output_file.close(); m_output_file.close();
// skip this file if they match // if we are expecting some md5sum, compare it with the local one
if (m_check_md5 && hash == m_expected_md5) if (!m_expected_md5.isEmpty())
{
// skip if they match
if(m_local_md5 == m_expected_md5)
{ {
QLOG_INFO() << "Skipping " << m_url.toString() << ": md5 match."; QLOG_INFO() << "Skipping " << m_url.toString() << ": md5 match.";
emit succeeded(m_index_within_job); emit succeeded(m_index_within_job);
return; return;
} }
}
else else
{ {
m_expected_md5 = hash; // no expected md5. we use the local md5sum as an ETag
} }
} }
if (!ensureFilePathExists(filename)) if (!ensureFilePathExists(filename))
@ -58,9 +61,18 @@ void MD5EtagDownload::start()
return; return;
} }
QLOG_INFO() << "Downloading " << m_url.toString() << " expecting " << m_expected_md5;
QNetworkRequest request(m_url); QNetworkRequest request(m_url);
request.setRawHeader(QString("If-None-Match").toLatin1(), m_expected_md5.toLatin1());
QLOG_INFO() << "Downloading " << m_url.toString() << " got " << m_local_md5;
if(!m_local_md5.isEmpty())
{
QLOG_INFO() << "Got " << m_local_md5;
request.setRawHeader(QString("If-None-Match").toLatin1(), m_local_md5.toLatin1());
}
if(!m_expected_md5.isEmpty())
QLOG_INFO() << "Expecting " << m_expected_md5;
request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
// Go ahead and try to open the file. // Go ahead and try to open the file.
@ -107,7 +119,10 @@ void MD5EtagDownload::downloadFinished()
m_status = Job_Finished; m_status = Job_Finished;
m_output_file.close(); m_output_file.close();
// FIXME: compare with the real written data md5sum
// this is just an ETag
QLOG_INFO() << "Finished " << m_url.toString() << " got " << m_reply->rawHeader("ETag").constData(); QLOG_INFO() << "Finished " << m_url.toString() << " got " << m_reply->rawHeader("ETag").constData();
m_reply.reset(); m_reply.reset();
emit succeeded(m_index_within_job); emit succeeded(m_index_within_job);
return; return;
@ -116,6 +131,7 @@ void MD5EtagDownload::downloadFinished()
else else
{ {
m_output_file.close(); m_output_file.close();
m_output_file.remove();
m_reply.reset(); m_reply.reset();
emit failed(m_index_within_job); emit failed(m_index_within_job);
return; return;

View File

@ -23,12 +23,10 @@ class MD5EtagDownload : public NetAction
{ {
Q_OBJECT Q_OBJECT
public: public:
/// if true, check the md5sum against a provided md5sum /// the expected md5 checksum. Only set from outside
/// also, if a file exists, perform an md5sum first and don't download only if they don't
/// match
bool m_check_md5;
/// the expected md5 checksum
QString m_expected_md5; QString m_expected_md5;
/// the md5 checksum of a file that already exists.
QString m_local_md5;
/// if saving to file, use the one specified in this string /// if saving to file, use the one specified in this string
QString m_target_path; QString m_target_path;
/// this is the output file, if any /// this is the output file, if any

View File

@ -29,4 +29,6 @@ const QString RESOURCE_BASE("resources.download.minecraft.net/");
const QString LIBRARY_BASE("libraries.minecraft.net/"); const QString LIBRARY_BASE("libraries.minecraft.net/");
const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/"); const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/");
const QString AUTH_BASE("authserver.mojang.com/"); const QString AUTH_BASE("authserver.mojang.com/");
const QString FORGE_LEGACY_URL("http://files.minecraftforge.net/minecraftforge/json");
const QString FORGE_GRADLE_URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/json");
} }

Some files were not shown because too many files have changed in this diff Show More