Merge branch 'develop' of github.com:MultiMC/MultiMC5 into feature_news
Conflicts: CMakeLists.txt gui/MainWindow.h
This commit is contained in:
commit
17f1864a71
2
.gitignore
vendored
2
.gitignore
vendored
@ -18,3 +18,5 @@ tags
|
||||
# YouCompleteMe config stuff.
|
||||
.ycm_extra_conf.*
|
||||
|
||||
#OSX Stuff
|
||||
.DS_Store
|
||||
|
@ -43,11 +43,20 @@ ENDIF()
|
||||
######## 3rd Party Libs ########
|
||||
|
||||
# Find the required Qt parts
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Qt5Network REQUIRED)
|
||||
find_package(Qt5Test REQUIRED)
|
||||
find_package(Qt5Concurrent 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.
|
||||
get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION)
|
||||
@ -360,16 +369,22 @@ logic/OpSys.h
|
||||
logic/OpSys.cpp
|
||||
logic/ForgeInstaller.h
|
||||
logic/ForgeInstaller.cpp
|
||||
logic/LiteLoaderInstaller.h
|
||||
logic/LiteLoaderInstaller.cpp
|
||||
|
||||
# Nostalgia
|
||||
logic/NostalgiaInstance.h
|
||||
logic/NostalgiaInstance.cpp
|
||||
|
||||
# FTB
|
||||
logic/OneSixFTBInstance.h
|
||||
logic/OneSixFTBInstance.cpp
|
||||
logic/LegacyFTBInstance.h
|
||||
logic/LegacyFTBInstance.cpp
|
||||
|
||||
# Lists
|
||||
logic/lists/InstanceList.h
|
||||
logic/lists/InstanceList.cpp
|
||||
logic/lists/IconList.h
|
||||
logic/lists/IconList.cpp
|
||||
logic/lists/BaseVersionList.h
|
||||
logic/lists/BaseVersionList.cpp
|
||||
logic/lists/MinecraftVersionList.h
|
||||
@ -381,6 +396,13 @@ logic/lists/ForgeVersionList.cpp
|
||||
logic/lists/JavaVersionList.h
|
||||
logic/lists/JavaVersionList.cpp
|
||||
|
||||
# Icons
|
||||
logic/icons/MMCIcon.h
|
||||
logic/icons/MMCIcon.cpp
|
||||
logic/icons/IconList.h
|
||||
logic/icons/IconList.cpp
|
||||
|
||||
|
||||
# misc model/view
|
||||
logic/EnabledItemFilter.h
|
||||
logic/EnabledItemFilter.cpp
|
||||
@ -389,6 +411,10 @@ logic/EnabledItemFilter.cpp
|
||||
logic/tasks/ProgressProvider.h
|
||||
logic/tasks/Task.h
|
||||
logic/tasks/Task.cpp
|
||||
logic/tasks/ThreadTask.h
|
||||
logic/tasks/ThreadTask.cpp
|
||||
logic/tasks/SequentialTask.h
|
||||
logic/tasks/SequentialTask.cpp
|
||||
|
||||
# Utilities
|
||||
logic/JavaChecker.h
|
||||
@ -403,6 +429,8 @@ logic/JavaCheckerJob.h
|
||||
logic/JavaCheckerJob.cpp
|
||||
|
||||
# Assets
|
||||
logic/assets/AssetsMigrateTask.h
|
||||
logic/assets/AssetsMigrateTask.cpp
|
||||
logic/assets/AssetsUtils.h
|
||||
logic/assets/AssetsUtils.cpp
|
||||
)
|
||||
@ -499,8 +527,8 @@ ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS})
|
||||
# Link
|
||||
TARGET_LINK_LIBRARIES(MultiMC MultiMC_common)
|
||||
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_common 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 Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
|
||||
ADD_DEPENDENCIES(MultiMC_common MultiMCLauncher JavaCheck)
|
||||
|
||||
################################ INSTALLATION AND PACKAGING ################################
|
||||
@ -514,9 +542,12 @@ IF(UNIX AND APPLE)
|
||||
|
||||
SET(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC")
|
||||
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_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_COPYRIGHT "Copyright 2013 MultiMC Contributors")
|
||||
ELSEIF(UNIX)
|
||||
SET(PLUGIN_DEST_DIR plugins)
|
||||
SET(QTCONF_DEST_DIR .)
|
||||
@ -592,6 +623,18 @@ INSTALL(
|
||||
REGEX "d\\." 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()
|
||||
|
||||
# qtconf
|
||||
@ -658,8 +701,12 @@ ELSE()
|
||||
ENDIF()
|
||||
|
||||
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
|
||||
add_subdirectory(tests)
|
||||
|
323
MultiMC.cpp
323
MultiMC.cpp
@ -8,11 +8,12 @@
|
||||
#include <QLibraryInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QStringList>
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include "gui/dialogs/VersionSelectDialog.h"
|
||||
#include "logic/lists/InstanceList.h"
|
||||
#include "logic/auth/MojangAccountList.h"
|
||||
#include "logic/lists/IconList.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
#include "logic/lists/LwjglVersionList.h"
|
||||
#include "logic/lists/MinecraftVersionList.h"
|
||||
#include "logic/lists/ForgeVersionList.h"
|
||||
@ -34,16 +35,28 @@
|
||||
#include <logger/QsLogDest.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;
|
||||
|
||||
MultiMC::MultiMC(int &argc, char **argv, const QString &root) : QApplication(argc, argv),
|
||||
m_version{VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_CHANNEL, VERSION_BUILD_TYPE}
|
||||
MultiMC::MultiMC(int &argc, char **argv, const QString &root)
|
||||
: QApplication(argc, argv), m_version{VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD,
|
||||
VERSION_CHANNEL, VERSION_BUILD_TYPE}
|
||||
{
|
||||
setOrganizationName("MultiMC");
|
||||
setApplicationName("MultiMC5");
|
||||
|
||||
initTranslations();
|
||||
|
||||
setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
// Don't quit on hiding the last window
|
||||
this->setQuitOnLastWindowClosed(false);
|
||||
|
||||
@ -137,9 +150,10 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &root) : QApplication(arg
|
||||
}
|
||||
|
||||
// change directory
|
||||
QDir::setCurrent(args["dir"].toString().isEmpty() ?
|
||||
(root.isEmpty() ? QDir::currentPath() : QDir::current().absoluteFilePath(root))
|
||||
: args["dir"].toString());
|
||||
QDir::setCurrent(
|
||||
args["dir"].toString().isEmpty()
|
||||
? (root.isEmpty() ? QDir::currentPath() : QDir::current().absoluteFilePath(root))
|
||||
: args["dir"].toString());
|
||||
|
||||
// init the logger
|
||||
initLogger();
|
||||
@ -158,7 +172,7 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &root) : QApplication(arg
|
||||
m_instances.reset(new InstanceList(InstDirSetting->get().toString(), this));
|
||||
QLOG_INFO() << "Loading Instances...";
|
||||
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)));
|
||||
|
||||
// and accounts
|
||||
@ -179,42 +193,43 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &root) : QApplication(arg
|
||||
{
|
||||
QLOG_INFO() << "No proxy found.";
|
||||
}
|
||||
else for (auto proxy : proxies)
|
||||
{
|
||||
QString proxyDesc;
|
||||
if (proxy.type() == QNetworkProxy::NoProxy)
|
||||
else
|
||||
for (auto proxy : proxies)
|
||||
{
|
||||
QLOG_INFO() << "Using no proxy is an option!";
|
||||
continue;
|
||||
QString proxyDesc;
|
||||
if (proxy.type() == QNetworkProxy::NoProxy)
|
||||
{
|
||||
QLOG_INFO() << "Using no proxy is an option!";
|
||||
continue;
|
||||
}
|
||||
switch (proxy.type())
|
||||
{
|
||||
case QNetworkProxy::DefaultProxy:
|
||||
proxyDesc = "Default proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::Socks5Proxy:
|
||||
proxyDesc = "Socks5 proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpProxy:
|
||||
proxyDesc = "HTTP proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpCachingProxy:
|
||||
proxyDesc = "HTTP caching: ";
|
||||
break;
|
||||
case QNetworkProxy::FtpCachingProxy:
|
||||
proxyDesc = "FTP caching: ";
|
||||
break;
|
||||
default:
|
||||
proxyDesc = "DERP proxy: ";
|
||||
break;
|
||||
}
|
||||
proxyDesc += QString("%3@%1:%2 pass %4")
|
||||
.arg(proxy.hostName())
|
||||
.arg(proxy.port())
|
||||
.arg(proxy.user())
|
||||
.arg(proxy.password());
|
||||
QLOG_INFO() << proxyDesc;
|
||||
}
|
||||
switch (proxy.type())
|
||||
{
|
||||
case QNetworkProxy::DefaultProxy:
|
||||
proxyDesc = "Default proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::Socks5Proxy:
|
||||
proxyDesc = "Socks5 proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpProxy:
|
||||
proxyDesc = "HTTP proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpCachingProxy:
|
||||
proxyDesc = "HTTP caching: ";
|
||||
break;
|
||||
case QNetworkProxy::FtpCachingProxy:
|
||||
proxyDesc = "FTP caching: ";
|
||||
break;
|
||||
default:
|
||||
proxyDesc = "DERP proxy: ";
|
||||
break;
|
||||
}
|
||||
proxyDesc += QString("%3@%1:%2 pass %4")
|
||||
.arg(proxy.hostName())
|
||||
.arg(proxy.port())
|
||||
.arg(proxy.user())
|
||||
.arg(proxy.password());
|
||||
QLOG_INFO() << proxyDesc;
|
||||
}
|
||||
|
||||
// create the global network manager
|
||||
m_qnam.reset(new QNetworkAccessManager(this));
|
||||
@ -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()
|
||||
{
|
||||
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
|
||||
QsLogging::Logger &logger = QsLogging::Logger::instance();
|
||||
logger.setLoggingLevel(QsLogging::TraceLevel);
|
||||
m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination("MultiMC.log");
|
||||
m_debugDestination = QsLogging::DestinationFactory::MakeDebugOutputDestination();
|
||||
m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination(logBase.arg(0));
|
||||
m_debugDestination = QsLogging::DestinationFactory::MakeQDebugDestination();
|
||||
logger.addDestination(m_fileDestination.get());
|
||||
logger.addDestination(m_debugDestination.get());
|
||||
// log all the things
|
||||
@ -302,67 +330,110 @@ void MultiMC::initGlobalSettings()
|
||||
{
|
||||
m_settings.reset(new INISettingsObject("multimc.cfg", this));
|
||||
// Updates
|
||||
m_settings->registerSetting(new Setting("UseDevBuilds", false));
|
||||
m_settings->registerSetting(new Setting("AutoUpdate", true));
|
||||
m_settings->registerSetting("UseDevBuilds", false);
|
||||
m_settings->registerSetting("AutoUpdate", true);
|
||||
|
||||
// Folders
|
||||
m_settings->registerSetting(new Setting("InstanceDir", "instances"));
|
||||
m_settings->registerSetting(new Setting("CentralModsDir", "mods"));
|
||||
m_settings->registerSetting(new Setting("LWJGLDir", "lwjgl"));
|
||||
// FTB
|
||||
m_settings->registerSetting("TrackFTBInstances", false);
|
||||
#ifdef Q_OS_LINUX
|
||||
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(new Setting("ShowConsole", true));
|
||||
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())
|
||||
m_settings->registerSetting("FTBRoot");
|
||||
if (m_settings->get("FTBRoot").isNull())
|
||||
{
|
||||
QUuid uuid = QUuid::createUuid();
|
||||
m_settings->set("YggdrasilClientToken", uuid.toString());
|
||||
QString ftbRoot;
|
||||
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
|
||||
m_settings->registerSetting(new Setting("MainWindowState", ""));
|
||||
m_settings->registerSetting(new Setting("MainWindowGeometry", ""));
|
||||
// Folders
|
||||
m_settings->registerSetting("InstanceDir", "instances");
|
||||
m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods");
|
||||
m_settings->registerSetting({"LWJGLDir", "LwjglDir"}, "lwjgl");
|
||||
m_settings->registerSetting("IconsDir", "icons");
|
||||
|
||||
m_settings->registerSetting(new Setting("ConsoleWindowState", ""));
|
||||
m_settings->registerSetting(new Setting("ConsoleWindowGeometry", ""));
|
||||
// Editors
|
||||
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()
|
||||
@ -374,6 +445,7 @@ void MultiMC::initHttpMetaCache()
|
||||
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
|
||||
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
|
||||
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
|
||||
m_metacache->addBase("root", QDir(".").absolutePath());
|
||||
m_metacache->Load();
|
||||
}
|
||||
|
||||
@ -422,27 +494,20 @@ std::shared_ptr<JavaVersionList> MultiMC::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.";
|
||||
#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.
|
||||
// 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.
|
||||
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");
|
||||
#else
|
||||
QString appDir = MMC->applicationDirPath();
|
||||
@ -450,28 +515,35 @@ void MultiMC::installUpdates(const QString& updateFilesDir, bool restartOnFinish
|
||||
#endif
|
||||
|
||||
// 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.
|
||||
QString updaterBinary = PathCombine(MMC->applicationDirPath(), UPDATER_BIN);
|
||||
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 << "--package-dir" << updateFilesDir;
|
||||
args << "--script" << PathCombine(updateFilesDir, "file_list.xml");
|
||||
args << "--wait" << QString::number(MMC->applicationPid());
|
||||
args << "--script" << PathCombine(updateFilesDir, "file_list.xml");
|
||||
args << "--wait" << QString::number(MMC->applicationPid());
|
||||
|
||||
if (restartOnFinish)
|
||||
args << "--finish-cmd" << finishCmd;
|
||||
args << "--finish-cmd" << finishCmd;
|
||||
|
||||
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.
|
||||
MMC->quit();
|
||||
}
|
||||
|
||||
void MultiMC::setUpdateOnExit(const QString& updateFilesDir)
|
||||
void MultiMC::setUpdateOnExit(const QString &updateFilesDir)
|
||||
{
|
||||
m_updateOnExitPath = updateFilesDir;
|
||||
}
|
||||
@ -481,5 +553,18 @@ QString MultiMC::getExitUpdatePath() const
|
||||
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"
|
||||
|
14
MultiMC.h
14
MultiMC.h
@ -6,7 +6,6 @@
|
||||
#include "logger/QsLog.h"
|
||||
#include "logger/QsLogDest.h"
|
||||
|
||||
|
||||
class MinecraftVersionList;
|
||||
class LWJGLVersionList;
|
||||
class HttpMetaCache;
|
||||
@ -107,12 +106,12 @@ public:
|
||||
/*!
|
||||
* Installs update from the given update files directory.
|
||||
*/
|
||||
void installUpdates(const QString& updateFilesDir, bool restartOnFinish=false);
|
||||
void installUpdates(const QString &updateFilesDir, bool restartOnFinish = false);
|
||||
|
||||
/*!
|
||||
* Sets MultiMC to install updates from the given directory when it exits.
|
||||
*/
|
||||
void setUpdateOnExit(const QString& updateFilesDir);
|
||||
void setUpdateOnExit(const QString &updateFilesDir);
|
||||
|
||||
/*!
|
||||
* Gets the path to install updates from on exit.
|
||||
@ -120,6 +119,12 @@ public:
|
||||
*/
|
||||
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:
|
||||
void initLogger();
|
||||
|
||||
@ -130,6 +135,9 @@ private:
|
||||
void initTranslations();
|
||||
|
||||
private:
|
||||
friend class UpdateCheckerTest;
|
||||
friend class DownloadUpdateTaskTest;
|
||||
|
||||
std::shared_ptr<QTranslator> m_qt_translator;
|
||||
std::shared_ptr<QTranslator> m_mmc_translator;
|
||||
std::shared_ptr<SettingsObject> m_settings;
|
||||
|
27
MultiMC.manifest
Normal file
27
MultiMC.manifest
Normal 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>
|
10
README.md
10
README.md
@ -11,6 +11,16 @@ Check [BUILD.md](BUILD.md) for build instructions.
|
||||
## 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.
|
||||
|
||||
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
|
||||
Copyright © 2013 MultiMC Contributors
|
||||
|
||||
|
40
cmake/MacOSXBundleInfo.plist.in
Normal file
40
cmake/MacOSXBundleInfo.plist.in
Normal 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>
|
@ -8,21 +8,36 @@
|
||||
|
||||
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
|
||||
<< " " << 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;
|
||||
}
|
||||
|
@ -34,4 +34,4 @@
|
||||
* @return void
|
||||
* @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);
|
||||
|
@ -94,20 +94,9 @@ static int read_magic(unpacker *u, char peek[], int peeklen)
|
||||
return magic;
|
||||
}
|
||||
|
||||
void unpack_200(std::string input_path, std::string output_path)
|
||||
void unpack_200(FILE *input, FILE *output)
|
||||
{
|
||||
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);
|
||||
|
||||
// initialize jar output
|
||||
|
@ -5,44 +5,27 @@ find_package(Qt5Core REQUIRED)
|
||||
|
||||
# Include Qt headers.
|
||||
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
|
||||
src/inifile.cpp
|
||||
libsettings_config.h
|
||||
|
||||
src/settingsobject.cpp
|
||||
src/setting.cpp
|
||||
src/overridesetting.cpp
|
||||
inifile.h
|
||||
inifile.cpp
|
||||
|
||||
src/basicsettingsobject.cpp
|
||||
src/inisettingsobject.cpp
|
||||
settingsobject.h
|
||||
settingsobject.cpp
|
||||
inisettingsobject.h
|
||||
inisettingsobject.cpp
|
||||
|
||||
src/keyring.cpp
|
||||
src/stubkeyring.cpp
|
||||
setting.h
|
||||
setting.cpp
|
||||
overridesetting.h
|
||||
overridesetting.cpp
|
||||
)
|
||||
|
||||
# Set the include dir path.
|
||||
SET(LIBSETTINGS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
|
||||
include_directories(${LIBSETTINGS_INCLUDE_DIR})
|
||||
SET(LIBSETTINGS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE)
|
||||
|
||||
# Static link!
|
||||
ADD_DEFINITIONS(-DLIBSETTINGS_STATIC)
|
||||
@ -59,6 +42,6 @@ IF(MultiMC_CODE_COVERAGE)
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --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)
|
||||
target_link_libraries(libSettings)
|
||||
|
@ -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();
|
||||
};
|
@ -13,7 +13,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "include/inifile.h"
|
||||
#include "inifile.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
@ -13,8 +13,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "include/inisettingsobject.h"
|
||||
#include "include/setting.h"
|
||||
#include "inisettingsobject.h"
|
||||
#include "setting.h"
|
||||
|
||||
INISettingsObject::INISettingsObject(const QString &path, QObject *parent)
|
||||
: SettingsObject(parent)
|
||||
@ -32,31 +32,45 @@ void INISettingsObject::changeSetting(const Setting &setting, QVariant value)
|
||||
{
|
||||
if (contains(setting.id()))
|
||||
{
|
||||
// valid value -> set the main config, remove all the sysnonyms
|
||||
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
|
||||
m_ini.remove(setting.configKey());
|
||||
{
|
||||
for(auto iter: setting.configKeys())
|
||||
m_ini.remove(iter);
|
||||
}
|
||||
m_ini.saveFile(m_filePath);
|
||||
}
|
||||
}
|
||||
|
||||
void INISettingsObject::resetSetting(const Setting &setting)
|
||||
{
|
||||
// if we have the setting, remove all the synonyms. ALL OF THEM
|
||||
if (contains(setting.id()))
|
||||
{
|
||||
m_ini.remove(setting.configKey());
|
||||
for(auto iter: setting.configKeys())
|
||||
m_ini.remove(iter);
|
||||
m_ini.saveFile(m_filePath);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant INISettingsObject::retrieveValue(const Setting &setting)
|
||||
{
|
||||
// if we have the setting, return value of the first matching synonym
|
||||
if (contains(setting.id()))
|
||||
{
|
||||
return m_ini.get(setting.configKey(), QVariant());
|
||||
}
|
||||
else
|
||||
{
|
||||
return QVariant();
|
||||
for(auto iter: setting.configKeys())
|
||||
{
|
||||
if(m_ini.contains(iter))
|
||||
return m_ini[iter];
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
@ -26,3 +26,4 @@
|
||||
#define LIBSETTINGS_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
|
@ -13,10 +13,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "include/overridesetting.h"
|
||||
#include "overridesetting.h"
|
||||
|
||||
OverrideSetting::OverrideSetting(const QString &name, Setting *other, QObject *parent)
|
||||
: Setting(name, QVariant(), parent)
|
||||
OverrideSetting::OverrideSetting(std::shared_ptr<Setting> other)
|
||||
: Setting(other->configKeys(), QVariant())
|
||||
{
|
||||
m_other = other;
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
|
||||
#include "setting.h"
|
||||
|
||||
@ -31,10 +32,10 @@ class LIBSETTINGS_EXPORT OverrideSetting : public Setting
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OverrideSetting(const QString &name, Setting *other, QObject *parent = 0);
|
||||
explicit OverrideSetting(std::shared_ptr<Setting> other);
|
||||
|
||||
virtual QVariant defValue() const;
|
||||
|
||||
protected:
|
||||
Setting *m_other;
|
||||
std::shared_ptr<Setting> m_other;
|
||||
};
|
@ -13,17 +13,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "include/setting.h"
|
||||
#include "include/settingsobject.h"
|
||||
#include "setting.h"
|
||||
#include "settingsobject.h"
|
||||
|
||||
Setting::Setting(QString id, QVariant defVal, QObject *parent)
|
||||
: QObject(parent), m_id(id), m_defVal(defVal)
|
||||
Setting::Setting(QStringList synonyms, QVariant defVal)
|
||||
: QObject(), m_synonyms(synonyms), m_defVal(defVal)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant Setting::get() const
|
||||
{
|
||||
SettingsObject *sbase = qobject_cast<SettingsObject *>(parent());
|
||||
SettingsObject *sbase = m_storage;
|
||||
if (!sbase)
|
||||
{
|
||||
return defValue();
|
@ -17,6 +17,8 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QStringList>
|
||||
#include <memory>
|
||||
|
||||
#include "libsettings_config.h"
|
||||
|
||||
@ -29,11 +31,16 @@ class LIBSETTINGS_EXPORT Setting : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/*!
|
||||
* \brief Constructs a new Setting object with the given parent.
|
||||
* \param parent The Setting's parent object.
|
||||
/**
|
||||
* Construct a Setting
|
||||
*
|
||||
* 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.
|
||||
@ -44,7 +51,7 @@ public:
|
||||
*/
|
||||
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.
|
||||
* \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;
|
||||
|
||||
/*!
|
||||
* \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.
|
||||
* \return The default value of this setting.
|
||||
@ -111,11 +108,12 @@ slots:
|
||||
* \brief Reset the setting to default
|
||||
* This is done by emitting the settingReset() signal which will then be
|
||||
* handled by the SettingsObject object and cause the setting to change.
|
||||
* \param value The new value.
|
||||
*/
|
||||
virtual void reset();
|
||||
|
||||
protected:
|
||||
QString m_id;
|
||||
friend class SettingsObject;
|
||||
SettingsObject * m_storage;
|
||||
QStringList m_synonyms;
|
||||
QVariant m_defVal;
|
||||
};
|
@ -13,8 +13,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "include/settingsobject.h"
|
||||
#include "include/setting.h"
|
||||
#include "settingsobject.h"
|
||||
#include "setting.h"
|
||||
#include "overridesetting.h"
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
@ -22,17 +23,49 @@ SettingsObject::SettingsObject(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
SettingsObject::~SettingsObject()
|
||||
{
|
||||
m_settings.clear();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Check if setting is null or we already have a setting with the same ID.
|
||||
if (!setting)
|
||||
{
|
||||
qDebug(QString("Failed to register setting. Setting is null.")
|
||||
.arg(setting->id())
|
||||
.toUtf8());
|
||||
return false; // Fail
|
||||
}
|
||||
|
||||
if (contains(setting->id()))
|
||||
{
|
||||
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());
|
||||
return true;
|
||||
}
|
||||
|
||||
void SettingsObject::unregisterSetting(Setting *setting)
|
||||
{
|
||||
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
|
||||
*/
|
||||
std::shared_ptr<Setting> SettingsObject::getSetting(const QString &id) const
|
||||
{
|
||||
// Make sure there is a setting with the given ID.
|
||||
if (!m_settings.contains(id))
|
||||
@ -75,13 +95,13 @@ Setting *SettingsObject::getSetting(const QString &id) const
|
||||
|
||||
QVariant SettingsObject::get(const QString &id) const
|
||||
{
|
||||
Setting *setting = getSetting(id);
|
||||
auto setting = getSetting(id);
|
||||
return (setting ? setting->get() : QVariant());
|
||||
}
|
||||
|
||||
bool SettingsObject::set(const QString &id, QVariant value)
|
||||
{
|
||||
Setting *setting = getSetting(id);
|
||||
auto setting = getSetting(id);
|
||||
if (!setting)
|
||||
{
|
||||
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
|
||||
{
|
||||
Setting *setting = getSetting(id);
|
||||
auto setting = getSetting(id);
|
||||
if (setting)
|
||||
setting->reset();
|
||||
}
|
||||
|
||||
QList<Setting *> SettingsObject::getSettings()
|
||||
{
|
||||
return m_settings.values();
|
||||
}
|
||||
|
||||
bool SettingsObject::contains(const QString &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)), 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)));
|
||||
}
|
@ -17,6 +17,9 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <memory>
|
||||
|
||||
#include "libsettings_config.h"
|
||||
|
||||
@ -39,32 +42,37 @@ class LIBSETTINGS_EXPORT SettingsObject : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SettingsObject(QObject *parent = 0);
|
||||
|
||||
virtual ~SettingsObject();
|
||||
/*!
|
||||
* \brief Registers the given setting with this SettingsObject and connects the necessary
|
||||
* signals.
|
||||
* Registers an override setting for the given original setting in this settings object
|
||||
*
|
||||
* This will fail if there is already a setting with the same ID as
|
||||
* the one that is being registered.
|
||||
* \note Registering a setting object causes the SettingsObject to take ownership
|
||||
* 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.
|
||||
* \return A valid Setting shared pointer if successful.
|
||||
*/
|
||||
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
|
||||
* signals.
|
||||
* \note This does not delete the setting. Furthermore, when the setting is
|
||||
* unregistered, the SettingsObject drops ownership of the setting. This means
|
||||
* that if you unregister a setting, its parent is set to null and you become
|
||||
* responsible for freeing its memory.
|
||||
* \param setting The setting to unregister.
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
@ -73,18 +81,7 @@ public:
|
||||
* Returns null if there is no setting with the given ID.
|
||||
* \sa operator []()
|
||||
*/
|
||||
virtual 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);
|
||||
}
|
||||
std::shared_ptr<Setting> getSetting(const QString &id) const;
|
||||
|
||||
/*!
|
||||
* \brief Gets the value of the setting with the given ID.
|
||||
@ -92,7 +89,7 @@ public:
|
||||
* \return The setting's value as a 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.
|
||||
@ -101,27 +98,20 @@ public:
|
||||
* \param value The new value of the setting.
|
||||
* \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.
|
||||
* \param id The ID of the setting to reset.
|
||||
*/
|
||||
virtual 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();
|
||||
void reset(const QString &id) const;
|
||||
|
||||
/*!
|
||||
* \brief Checks if this SettingsObject contains a setting with the given ID.
|
||||
* \param id The ID to check for.
|
||||
* \return True if the SettingsObject has a setting with the given ID.
|
||||
*/
|
||||
virtual bool contains(const QString &id);
|
||||
bool contains(const QString &id);
|
||||
|
||||
signals:
|
||||
/*!
|
||||
@ -167,13 +157,7 @@ protected:
|
||||
* \brief Connects the necessary signals to the given Setting.
|
||||
* \param setting The setting to connect.
|
||||
*/
|
||||
virtual void connectSignals(const Setting &setting);
|
||||
|
||||
/*!
|
||||
* \brief Disconnects signals from the given Setting.
|
||||
* \param setting The setting to disconnect.
|
||||
*/
|
||||
virtual void disconnectSignals(const Setting &setting);
|
||||
void connectSignals(const Setting &setting);
|
||||
|
||||
/*!
|
||||
* \brief Function used by Setting objects to get their values from the SettingsObject.
|
||||
@ -185,5 +169,5 @@ protected:
|
||||
friend class Setting;
|
||||
|
||||
private:
|
||||
QMap<QString, Setting *> m_settings;
|
||||
QMap<QString, std::shared_ptr<Setting>> m_settings;
|
||||
};
|
@ -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;
|
@ -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)
|
||||
{
|
||||
}
|
@ -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;
|
||||
};
|
@ -23,10 +23,7 @@
|
||||
|
||||
QString PathCombine(QString path1, QString path2)
|
||||
{
|
||||
if (!path1.endsWith('/'))
|
||||
return path1.append('/').append(path2);
|
||||
else
|
||||
return path1.append(path2);
|
||||
return QDir::cleanPath(path1 + QDir::separator() + path2);
|
||||
}
|
||||
|
||||
QString PathCombine(QString path1, QString path2, QString path3)
|
||||
|
@ -58,10 +58,17 @@ ConsoleWindow::~ConsoleWindow()
|
||||
void ConsoleWindow::writeColor(QString text, const char *color)
|
||||
{
|
||||
// append a paragraph
|
||||
if (color != nullptr)
|
||||
ui->text->appendHtml(QString("<font color=\"%1\">%2</font>").arg(color).arg(text));
|
||||
else
|
||||
ui->text->appendPlainText(text);
|
||||
QString newtext;
|
||||
newtext += "<span style=\"";
|
||||
{
|
||||
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)
|
||||
|
@ -17,11 +17,6 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="text">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="undoRedoEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
|
@ -66,7 +66,7 @@
|
||||
#include "logic/lists/InstanceList.h"
|
||||
#include "logic/lists/MinecraftVersionList.h"
|
||||
#include "logic/lists/LwjglVersionList.h"
|
||||
#include "logic/lists/IconList.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
#include "logic/lists/JavaVersionList.h"
|
||||
|
||||
#include "logic/auth/flows/AuthenticateTask.h"
|
||||
@ -90,7 +90,9 @@
|
||||
#include "logic/LegacyInstance.h"
|
||||
|
||||
#include "logic/assets/AssetsUtils.h"
|
||||
#include "logic/assets/AssetsMigrateTask.h"
|
||||
#include <logic/updater/UpdateChecker.h>
|
||||
#include <logic/tasks/ThreadTask.h>
|
||||
|
||||
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()));
|
||||
|
||||
// OSX magic.
|
||||
setUnifiedTitleAndToolBarOnMac(true);
|
||||
// setUnifiedTitleAndToolBarOnMac(true);
|
||||
|
||||
// The instance action toolbar customizations
|
||||
{
|
||||
@ -178,6 +180,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
connect(view->selectionModel(),
|
||||
SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this,
|
||||
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.
|
||||
// FIXME: stop using POINTERS everywhere
|
||||
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.
|
||||
auto updater = MMC->updateChecker();
|
||||
QObject::connect(updater.get(), &UpdateChecker::updateAvailable, this,
|
||||
&MainWindow::updateAvailable);
|
||||
connect(updater.get(), &UpdateChecker::updateAvailable, this,
|
||||
&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 (MMC->settings()->get("AutoUpdate").toBool())
|
||||
on_actionCheckUpdate_triggered();
|
||||
@ -292,8 +304,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
|
||||
// removing this looks stupid
|
||||
view->setFocus();
|
||||
|
||||
AssetsUtils::migrateOldAssets();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
@ -502,7 +512,7 @@ void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit
|
||||
if (installOnExit)
|
||||
MMC->setUpdateOnExit(updateTask.updateFilesDir());
|
||||
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()
|
||||
{
|
||||
if (!m_selectedInstance)
|
||||
@ -721,7 +745,8 @@ void MainWindow::on_actionConfig_Folder_triggered()
|
||||
void MainWindow::on_actionCheckUpdate_triggered()
|
||||
{
|
||||
auto updater = MMC->updateChecker();
|
||||
updater->checkForUpdate();
|
||||
|
||||
updater->checkForUpdate(true);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionSettings_triggered()
|
||||
@ -905,6 +930,8 @@ void MainWindow::doLaunch()
|
||||
if (!account.get())
|
||||
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.
|
||||
if (account->accountStatus() != NotVerified)
|
||||
{
|
||||
@ -919,13 +946,28 @@ void MainWindow::doLaunch()
|
||||
{
|
||||
updateInstance(m_selectedInstance, account);
|
||||
}
|
||||
// revert from online to verified.
|
||||
else
|
||||
{
|
||||
if (!task->successful())
|
||||
{
|
||||
failReason = task->failReason();
|
||||
}
|
||||
if (loginWithPassword(account, failReason))
|
||||
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();
|
||||
return;
|
||||
}
|
||||
if (loginWithPassword(account, tr("Your account is currently not logged in. Please enter "
|
||||
"your password to log in again.")))
|
||||
updateInstance(m_selectedInstance, account);
|
||||
}
|
||||
|
||||
bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString &errorMsg)
|
||||
@ -1042,22 +1084,9 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
|
||||
VersionSelectDialog vselect(m_selectedInstance->versionList().get(),
|
||||
tr("Change Minecraft version"), this);
|
||||
vselect.setFilter(1, "OneSix");
|
||||
if (vselect.exec() && vselect.selectedVersion())
|
||||
{
|
||||
if (m_selectedInstance->versionIsCustom())
|
||||
{
|
||||
auto result = CustomMessageBox::selectable(
|
||||
this, tr("Are you sure?"),
|
||||
tr("This will remove any library/version customization you did previously. "
|
||||
"This includes things like Forge install and similar."),
|
||||
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Abort,
|
||||
QMessageBox::Abort)->exec();
|
||||
if(!vselect.exec() || !vselect.selectedVersion())
|
||||
return;
|
||||
|
||||
if (result != QMessageBox::Ok)
|
||||
return;
|
||||
}
|
||||
m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor());
|
||||
}
|
||||
if (!MMC->accounts()->anyAccountIsValid())
|
||||
{
|
||||
CustomMessageBox::selectable(
|
||||
@ -1067,7 +1096,22 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
|
||||
QMessageBox::Warning)->show();
|
||||
return;
|
||||
}
|
||||
auto updateTask = m_selectedInstance->doUpdate(false /*only_prepare*/);
|
||||
|
||||
if (m_selectedInstance->versionIsCustom())
|
||||
{
|
||||
auto result = CustomMessageBox::selectable(
|
||||
this, tr("Are you sure?"),
|
||||
tr("This will remove any library/version customization you did previously. "
|
||||
"This includes things like Forge install and similar."),
|
||||
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Abort,
|
||||
QMessageBox::Abort)->exec();
|
||||
|
||||
if (result != QMessageBox::Ok)
|
||||
return;
|
||||
}
|
||||
m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor());
|
||||
|
||||
auto updateTask = m_selectedInstance->doUpdate(false);
|
||||
if (!updateTask)
|
||||
{
|
||||
return;
|
||||
@ -1109,7 +1153,6 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &
|
||||
.value<void *>()))
|
||||
{
|
||||
ui->instanceToolBar->setEnabled(true);
|
||||
QString iconKey = m_selectedInstance->iconKey();
|
||||
renameButton->setText(m_selectedInstance->name());
|
||||
ui->actionChangeInstLWJGLVersion->setEnabled(
|
||||
m_selectedInstance->menuActionEnabled("actionChangeInstLWJGLVersion"));
|
||||
@ -1118,8 +1161,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &
|
||||
ui->actionChangeInstMCVersion->setEnabled(
|
||||
m_selectedInstance->menuActionEnabled("actionChangeInstMCVersion"));
|
||||
m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
|
||||
auto ico = MMC->icons()->getIcon(iconKey);
|
||||
ui->actionChangeInstIcon->setIcon(ico);
|
||||
updateInstanceToolIcon(m_selectedInstance->iconKey());
|
||||
|
||||
MMC->settings()->set("SelectedInstance", m_selectedInstance->id());
|
||||
}
|
||||
@ -1134,12 +1176,11 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &
|
||||
void MainWindow::selectionBad()
|
||||
{
|
||||
m_selectedInstance = nullptr;
|
||||
QString iconKey = "infinity";
|
||||
|
||||
statusBar()->clearMessage();
|
||||
ui->instanceToolBar->setEnabled(false);
|
||||
renameButton->setText(tr("Rename Instance"));
|
||||
auto ico = MMC->icons()->getIcon(iconKey);
|
||||
ui->actionChangeInstIcon->setIcon(ico);
|
||||
updateInstanceToolIcon("infinity");
|
||||
}
|
||||
|
||||
void MainWindow::on_actionEditInstNotes_triggered()
|
||||
@ -1162,6 +1203,32 @@ void MainWindow::instanceEnded()
|
||||
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()
|
||||
{
|
||||
bool askForJava = false;
|
||||
|
@ -51,6 +51,7 @@ public:
|
||||
void openWebPage(QUrl url);
|
||||
|
||||
void checkSetDefaultJava();
|
||||
void checkMigrateLegacyAssets();
|
||||
|
||||
private
|
||||
slots:
|
||||
@ -145,6 +146,9 @@ slots:
|
||||
void assetsFailed();
|
||||
void assetsFinished();
|
||||
|
||||
// called when an icon is changed in the icon model.
|
||||
void iconUpdated(QString);
|
||||
|
||||
public
|
||||
slots:
|
||||
void instanceActivated(QModelIndex);
|
||||
@ -173,6 +177,7 @@ slots:
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *ev);
|
||||
void setCatBackground(bool enabled);
|
||||
void updateInstanceToolIcon(QString new_icon);
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
@ -186,6 +191,7 @@ private:
|
||||
QToolButton* newsLabel;
|
||||
|
||||
BaseInstance *m_selectedInstance;
|
||||
QString m_currentInstIcon;
|
||||
|
||||
Task *m_versionLoadTask;
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>706</width>
|
||||
<height>579</height>
|
||||
<width>707</width>
|
||||
<height>593</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
@ -103,8 +103,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>688</width>
|
||||
<height>313</height>
|
||||
<width>685</width>
|
||||
<height>304</height>
|
||||
</rect>
|
||||
</property>
|
||||
<attribute name="label">
|
||||
@ -113,6 +113,9 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="aboutLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>MultiMC is a custom launcher that makes managing Minecraft easier by allowing you to have multiple instances of Minecraft at once.</p></body></html></string>
|
||||
</property>
|
||||
@ -162,8 +165,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>688</width>
|
||||
<height>313</height>
|
||||
<width>685</width>
|
||||
<height>304</height>
|
||||
</rect>
|
||||
</property>
|
||||
<attribute name="label">
|
||||
@ -179,13 +182,22 @@
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Andrew Okin &lt;</span><a href="mailto:forkk@forkk.net"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;">forkk@forkk.net</span></a><span style=" font-family:'Ubuntu';">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Petr Mrázek &lt;</span><a href="mailto:peterix@gmail.com"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;">peterix@gmail.com</span></a><span style=" font-family:'Ubuntu';">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Orochimarufan &lt;</span><a href="mailto:orochimarufan.x3@gmail.com"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;">orochimarufan.x3@gmail.com</span></a><span style=" font-family:'Ubuntu';">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">TakSuyu &lt;</span><a href="mailto:taksuyu@gmail.com"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;">taksuyu@gmail.com</span></a><span style=" font-family:'Ubuntu';">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Sky (Drayshak) &lt;</span><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;">multimc@bunnies.cc</span><span style=" font-family:'Ubuntu';">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Kilobyte &lt;</span><a href="mailto:stiepen22@gmx.de"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;">stiepen22@gmx.de</span></a><span style=" font-family:'Ubuntu';">&gt;</span></p></body></html></string>
|
||||
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">MultiMC</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Andrew Okin &lt;</span><a href="mailto:forkk@forkk.net"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">forkk@forkk.net</span></a><span style=" font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Petr Mrázek &lt;</span><a href="mailto:peterix@gmail.com"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">peterix@gmail.com</span></a><span style=" font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Sky &lt;</span><a href="https://www.twitter.com/drayshak"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">@drayshak</span></a><span style=" font-size:10pt;">&gt;</span></p>
|
||||
<p style="-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;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt; font-weight:600;">With thanks to</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Orochimarufan &lt;</span><a href="mailto:orochimarufan.x3@gmail.com"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">orochimarufan.x3@gmail.com</span></a><span style=" font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">TakSuyu &lt;</span><a href="mailto:taksuyu@gmail.com"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">taksuyu@gmail.com</span></a><span style=" font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Kilobyte &lt;</span><a href="mailto:stiepen22@gmx.de"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">stiepen22@gmx.de</span></a><span style=" font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Jan (02JanDal) &lt;</span><a href="mailto:02jandal@gmail.com"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">02jandal@gmail.com</span></a><span style=" font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Robotbrain &lt;</span><a href="https://twitter.com/skylordelros"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">@skylordelros</span></a><span style=" font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Rootbear75 &lt;</span><a href="https://twitter.com/rootbear75"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">@rootbear75</span></a><span style=" font-size:10pt;">&gt; (build server)</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -206,8 +218,8 @@ p, li { white-space: pre-wrap; }
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>688</width>
|
||||
<height>313</height>
|
||||
<width>684</width>
|
||||
<height>290</height>
|
||||
</rect>
|
||||
</property>
|
||||
<attribute name="label">
|
||||
@ -234,9 +246,9 @@ p, li { white-space: pre-wrap; }
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
</style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:7.8pt; font-weight:400; font-style:normal;">
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">MultiMC</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2012 MultiMC Contributors</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2012-2014 MultiMC Contributors</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">you may not use this file except in compliance with the License.</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">You may obtain a copy of the License at</span></p>
|
||||
@ -361,6 +373,39 @@ p, li { white-space: pre-wrap; }
|
||||
</item>
|
||||
</layout>
|
||||
</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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">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.</span></p>
|
||||
<p style="-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;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).</span></p>
|
||||
<p style="-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;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">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 </span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:600;">without</span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;"> implying that you have our blessing.</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -26,7 +26,9 @@
|
||||
#include <gui/dialogs/EditAccountDialog.h>
|
||||
#include <gui/dialogs/ProgressDialog.h>
|
||||
#include <gui/dialogs/AccountSelectDialog.h>
|
||||
#include "CustomMessageBox.h"
|
||||
#include <logic/tasks/Task.h>
|
||||
#include <logic/auth/YggdrasilTask.h>
|
||||
|
||||
#include <MultiMC.h>
|
||||
|
||||
@ -147,5 +149,12 @@ void AccountListDialog::addAccount(const QString& errMsg)
|
||||
|
||||
job->start();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto reason = task->failReason();
|
||||
auto dlg = CustomMessageBox::selectable(this, tr("Login error."), reason, QMessageBox::Critical);
|
||||
dlg->exec();
|
||||
delete dlg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
#include "logic/InstanceFactory.h"
|
||||
#include "logic/BaseVersion.h"
|
||||
#include "logic/lists/IconList.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
#include "logic/lists/MinecraftVersionList.h"
|
||||
#include "logic/tasks/Task.h"
|
||||
#include "logic/BaseInstance.h"
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
#include "EditAccountDialog.h"
|
||||
#include "ui_EditAccountDialog.h"
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
|
||||
EditAccountDialog::EditAccountDialog(const QString &text, QWidget *parent, int flags)
|
||||
: QDialog(parent), ui(new Ui::EditAccountDialog)
|
||||
@ -33,6 +35,11 @@ EditAccountDialog::~EditAccountDialog()
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void EditAccountDialog::on_label_linkActivated(const QString &link)
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl(link));
|
||||
}
|
||||
|
||||
QString EditAccountDialog::username() const
|
||||
{
|
||||
return ui->userTextBox->text();
|
||||
|
@ -52,6 +52,9 @@ public:
|
||||
PasswordField,
|
||||
};
|
||||
|
||||
private slots:
|
||||
void on_label_linkActivated(const QString &link);
|
||||
|
||||
private:
|
||||
Ui::EditAccountDialog *ui;
|
||||
};
|
||||
|
@ -19,6 +19,12 @@
|
||||
<property name="text">
|
||||
<string>Message label placeholder.</string>
|
||||
</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>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -25,7 +25,7 @@
|
||||
#include "gui/Platform.h"
|
||||
#include "gui/widgets/InstanceDelegate.h"
|
||||
|
||||
#include "logic/lists/IconList.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
|
||||
IconPickerDialog::IconPickerDialog(QWidget *parent)
|
||||
: QDialog(parent), ui(new Ui::IconPickerDialog)
|
||||
@ -103,7 +103,7 @@ void IconPickerDialog::addNewIcon()
|
||||
QString selectIcons = tr("Select Icons");
|
||||
//: The type of icon files
|
||||
QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(),
|
||||
tr("Icons") + "(*.png *.jpg *.jpeg)");
|
||||
tr("Icons") + "(*.png *.jpg *.jpeg *.ico)");
|
||||
MMC->icons()->installIcons(fileNames);
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,9 @@ InstanceSettings::InstanceSettings(SettingsObject *obj, QWidget *parent)
|
||||
{
|
||||
MultiMCPlatform::fixWM_CLASS(this);
|
||||
ui->setupUi(this);
|
||||
|
||||
restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("SettingsGeometry").toByteArray()));
|
||||
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
@ -47,7 +50,13 @@ InstanceSettings::~InstanceSettings()
|
||||
void InstanceSettings::showEvent(QShowEvent *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)
|
||||
@ -57,12 +66,16 @@ void InstanceSettings::on_customCommandsGroupBox_toggled(bool state)
|
||||
|
||||
void InstanceSettings::on_buttonBox_accepted()
|
||||
{
|
||||
MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
|
||||
|
||||
applySettings();
|
||||
accept();
|
||||
}
|
||||
|
||||
void InstanceSettings::on_buttonBox_rejected()
|
||||
{
|
||||
MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
|
||||
|
||||
reject();
|
||||
}
|
||||
|
||||
@ -98,18 +111,6 @@ void InstanceSettings::applySettings()
|
||||
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
|
||||
bool memory = ui->memoryGroupBox->isChecked();
|
||||
m_obj->set("OverrideMemory", memory);
|
||||
@ -170,10 +171,6 @@ void InstanceSettings::loadSettings()
|
||||
ui->windowWidthSpinBox->setValue(m_obj->get("MinecraftWinWidth").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
|
||||
ui->memoryGroupBox->setChecked(m_obj->get("OverrideMemory").toBool());
|
||||
ui->minMemSpinBox->setValue(m_obj->get("MinMemAlloc").toInt());
|
||||
@ -243,4 +240,4 @@ void InstanceSettings::checkFinished(JavaCheckResult result)
|
||||
tr("The specified java binary didn't work. You should use the auto-detect feature, "
|
||||
"or set the path to the java executable."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ public:
|
||||
|
||||
protected:
|
||||
virtual void showEvent(QShowEvent *);
|
||||
virtual void closeEvent(QCloseEvent *);
|
||||
private
|
||||
slots:
|
||||
void on_customCommandsGroupBox_toggled(bool arg1);
|
||||
|
@ -131,31 +131,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<spacer name="verticalSpacerMinecraft">
|
||||
<property name="orientation">
|
||||
@ -411,7 +386,6 @@
|
||||
<tabstop>consoleSettingsBox</tabstop>
|
||||
<tabstop>showConsoleCheck</tabstop>
|
||||
<tabstop>autoCloseConsoleCheck</tabstop>
|
||||
<tabstop>accountSettingsBox</tabstop>
|
||||
<tabstop>memoryGroupBox</tabstop>
|
||||
<tabstop>minMemSpinBox</tabstop>
|
||||
<tabstop>maxMemSpinBox</tabstop>
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
#include "logic/InstanceFactory.h"
|
||||
#include "logic/BaseVersion.h"
|
||||
#include "logic/lists/IconList.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
#include "logic/lists/MinecraftVersionList.h"
|
||||
#include "logic/tasks/Task.h"
|
||||
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "logic/EnabledItemFilter.h"
|
||||
#include "logic/lists/ForgeVersionList.h"
|
||||
#include "logic/ForgeInstaller.h"
|
||||
#include "logic/LiteLoaderInstaller.h"
|
||||
|
||||
OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent)
|
||||
: QDialog(parent), ui(new Ui::OneSixModEditDialog), m_inst(inst)
|
||||
@ -95,6 +96,8 @@ void OneSixModEditDialog::updateVersionControls()
|
||||
ui->customizeBtn->setEnabled(!customVersion);
|
||||
ui->revertBtn->setEnabled(customVersion);
|
||||
ui->forgeBtn->setEnabled(true);
|
||||
ui->liteloaderBtn->setEnabled(LiteLoaderInstaller(m_inst->intendedVersionId()).canApply());
|
||||
ui->customEditorBtn->setEnabled(customVersion);
|
||||
}
|
||||
|
||||
void OneSixModEditDialog::disableVersionControls()
|
||||
@ -102,6 +105,8 @@ void OneSixModEditDialog::disableVersionControls()
|
||||
ui->customizeBtn->setEnabled(false);
|
||||
ui->revertBtn->setEnabled(false);
|
||||
ui->forgeBtn->setEnabled(false);
|
||||
ui->liteloaderBtn->setEnabled(false);
|
||||
ui->customEditorBtn->setEnabled(false);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
switch (keyEvent->key())
|
||||
|
@ -44,8 +44,10 @@ slots:
|
||||
// Questionable: SettingsDialog doesn't need this for some reason?
|
||||
void on_buttonBox_rejected();
|
||||
void on_forgeBtn_clicked();
|
||||
void on_liteloaderBtn_clicked();
|
||||
void on_customizeBtn_clicked();
|
||||
void on_revertBtn_clicked();
|
||||
void on_customEditorBtn_clicked();
|
||||
void updateVersionControls();
|
||||
void disableVersionControls();
|
||||
|
||||
|
@ -77,6 +77,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="liteloaderBtn">
|
||||
<property name="text">
|
||||
<string>Install LiteLoader</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="customizeBtn">
|
||||
<property name="toolTip">
|
||||
@ -136,6 +143,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
<spacer name="verticalSpacer_7">
|
||||
<property name="orientation">
|
||||
|
@ -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->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());
|
||||
updateCheckboxStuff();
|
||||
}
|
||||
@ -51,7 +57,13 @@ SettingsDialog::~SettingsDialog()
|
||||
void SettingsDialog::showEvent(QShowEvent *ev)
|
||||
{
|
||||
QDialog::showEvent(ev);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void SettingsDialog::closeEvent(QCloseEvent *ev)
|
||||
{
|
||||
MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64());
|
||||
|
||||
QDialog::closeEvent(ev);
|
||||
}
|
||||
|
||||
void SettingsDialog::updateCheckboxStuff()
|
||||
@ -60,6 +72,32 @@ void SettingsDialog::updateCheckboxStuff()
|
||||
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()
|
||||
{
|
||||
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Directory"),
|
||||
@ -72,6 +110,18 @@ void SettingsDialog::on_instDirBrowseBtn_clicked()
|
||||
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()
|
||||
{
|
||||
@ -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)
|
||||
{
|
||||
Q_UNUSED(checked);
|
||||
@ -108,6 +188,13 @@ void SettingsDialog::on_maximizedCheckBox_clicked(bool checked)
|
||||
void SettingsDialog::on_buttonBox_accepted()
|
||||
{
|
||||
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)
|
||||
@ -135,11 +222,29 @@ void SettingsDialog::applySettings(SettingsObject *s)
|
||||
// Updates
|
||||
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
|
||||
// TODO: Offer to move instances to new instance folder.
|
||||
s->set("InstanceDir", ui->instDirTextBox->text());
|
||||
s->set("CentralModsDir", ui->modsDirTextBox->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
|
||||
s->set("ShowConsole", ui->showConsoleCheck->isChecked());
|
||||
@ -150,9 +255,6 @@ void SettingsDialog::applySettings(SettingsObject *s)
|
||||
s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value());
|
||||
s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
|
||||
|
||||
// Auto Login
|
||||
s->set("AutoLogin", ui->autoLoginCheckBox->isChecked());
|
||||
|
||||
// Memory
|
||||
s->set("MinMemAlloc", ui->minMemSpinBox->value());
|
||||
s->set("MaxMemAlloc", ui->maxMemSpinBox->value());
|
||||
@ -188,10 +290,19 @@ void SettingsDialog::loadSettings(SettingsObject *s)
|
||||
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").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
|
||||
ui->instDirTextBox->setText(s->get("InstanceDir").toString());
|
||||
ui->modsDirTextBox->setText(s->get("CentralModsDir").toString());
|
||||
ui->lwjglDirTextBox->setText(s->get("LWJGLDir").toString());
|
||||
ui->iconsDirTextBox->setText(s->get("IconsDir").toString());
|
||||
|
||||
// Editors
|
||||
ui->jsonEditorTextBox->setText(s->get("JsonEditor").toString());
|
||||
|
||||
// Console
|
||||
ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool());
|
||||
@ -202,9 +313,6 @@ void SettingsDialog::loadSettings(SettingsObject *s)
|
||||
ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());
|
||||
ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt());
|
||||
|
||||
// Auto Login
|
||||
ui->autoLoginCheckBox->setChecked(s->get("AutoLogin").toBool());
|
||||
|
||||
// Memory
|
||||
ui->minMemSpinBox->setValue(s->get("MinMemAlloc").toInt());
|
||||
ui->maxMemSpinBox->setValue(s->get("MaxMemAlloc").toInt());
|
||||
|
@ -41,20 +41,32 @@ public:
|
||||
void loadSettings(SettingsObject *s);
|
||||
|
||||
protected:
|
||||
virtual void showEvent(QShowEvent *);
|
||||
virtual void showEvent(QShowEvent *ev);
|
||||
virtual void closeEvent(QCloseEvent *ev);
|
||||
|
||||
private
|
||||
slots:
|
||||
void on_ftbLauncherBrowseBtn_clicked();
|
||||
|
||||
void on_ftbBrowseBtn_clicked();
|
||||
|
||||
void on_instDirBrowseBtn_clicked();
|
||||
|
||||
void on_modsDirBrowseBtn_clicked();
|
||||
|
||||
void on_lwjglDirBrowseBtn_clicked();
|
||||
|
||||
|
||||
void on_jsonEditorBrowseBtn_clicked();
|
||||
|
||||
void on_iconsDirBrowseBtn_clicked();
|
||||
|
||||
void on_maximizedCheckBox_clicked(bool checked);
|
||||
|
||||
void on_buttonBox_accepted();
|
||||
|
||||
void on_buttonBox_rejected();
|
||||
|
||||
void on_javaDetectBtn_clicked();
|
||||
|
||||
void on_javaTestBtn_clicked();
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>526</width>
|
||||
<height>599</height>
|
||||
<height>628</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -39,7 +39,7 @@
|
||||
<attribute name="title">
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="generalTabLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="sortingModeBox">
|
||||
<property name="enabled">
|
||||
@ -95,6 +95,93 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<widget class="QGroupBox" name="foldersBox">
|
||||
<property name="title">
|
||||
@ -128,6 +215,9 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="modsDirTextBox"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lwjglDirTextBox"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="modsDirBrowseBtn">
|
||||
<property name="text">
|
||||
@ -142,9 +232,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lwjglDirTextBox"/>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QToolButton" name="lwjglDirBrowseBtn">
|
||||
<property name="text">
|
||||
@ -152,6 +239,49 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
@ -261,22 +391,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<spacer name="verticalSpacerMinecraft">
|
||||
<property name="orientation">
|
||||
@ -394,25 +508,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<widget class="QPushButton" name="javaDetectBtn">
|
||||
<property name="sizePolicy">
|
||||
@ -426,7 +521,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4" colspan="2">
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="javaTestBtn">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
@ -439,19 +534,48 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" colspan="2">
|
||||
<widget class="QPushButton" name="javaBrowseBtn">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelJVMArgs">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
<string>JVM arguments:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
@ -520,7 +644,7 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>settingsTabs</tabstop>
|
||||
<tabstop>settingsTab</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
<tabstop>sortLastLaunchedBtn</tabstop>
|
||||
<tabstop>sortByNameBtn</tabstop>
|
||||
|
@ -30,7 +30,7 @@ void MCModInfoFrame::updateWithMod(Mod &m)
|
||||
|
||||
QString text = "";
|
||||
QString name = "";
|
||||
if(m.name().isEmpty()) name = m.id();
|
||||
if(m.name().isEmpty()) name = m.mmc_id();
|
||||
else name = m.name();
|
||||
|
||||
if(m.homeurl().isEmpty()) text = name;
|
||||
|
@ -44,8 +44,9 @@ void ModListView::setModel ( QAbstractItemModel* model )
|
||||
QTreeView::setModel ( model );
|
||||
auto head = header();
|
||||
head->setStretchLastSection(false);
|
||||
head->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
for(int i = 1; i < head->count(); i++)
|
||||
head->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
head->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
for(int i = 2; i < head->count(); i++)
|
||||
head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||
dropIndicatorPosition();
|
||||
}
|
||||
|
@ -77,6 +77,15 @@ void DebugOutputDestination::write(const QString &message)
|
||||
QsDebugOutput::output(message);
|
||||
}
|
||||
|
||||
class QDebugDestination : public Destination
|
||||
{
|
||||
public:
|
||||
virtual void write(const QString &message)
|
||||
{
|
||||
qDebug() << message;
|
||||
};
|
||||
};
|
||||
|
||||
DestinationPtr DestinationFactory::MakeFileDestination(const QString &filePath)
|
||||
{
|
||||
return DestinationPtr(new FileDestination(filePath));
|
||||
@ -87,4 +96,9 @@ DestinationPtr DestinationFactory::MakeDebugOutputDestination()
|
||||
return DestinationPtr(new DebugOutputDestination);
|
||||
}
|
||||
|
||||
DestinationPtr DestinationFactory::MakeQDebugDestination()
|
||||
{
|
||||
return DestinationPtr(new QDebugDestination);
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
@ -47,6 +47,7 @@ class DestinationFactory
|
||||
public:
|
||||
static DestinationPtr MakeFileDestination(const QString &filePath);
|
||||
static DestinationPtr MakeDebugOutputDestination();
|
||||
static DestinationPtr MakeQDebugDestination();
|
||||
};
|
||||
|
||||
} // end namespace
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
#include "pathutils.h"
|
||||
#include "lists/MinecraftVersionList.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
|
||||
BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
|
||||
SettingsObject *settings_obj, QObject *parent)
|
||||
@ -36,10 +37,11 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
|
||||
d->m_settings = settings_obj;
|
||||
d->m_rootDir = rootDir;
|
||||
|
||||
settings().registerSetting(new Setting("name", "Unnamed Instance"));
|
||||
settings().registerSetting(new Setting("iconKey", "default"));
|
||||
settings().registerSetting(new Setting("notes", ""));
|
||||
settings().registerSetting(new Setting("lastLaunchTime", 0));
|
||||
settings().registerSetting("name", "Unnamed Instance");
|
||||
settings().registerSetting("iconKey", "default");
|
||||
connect(MMC->icons().get(), SIGNAL(iconUpdated(QString)), SLOT(iconUpdated(QString)));
|
||||
settings().registerSetting("notes", "");
|
||||
settings().registerSetting("lastLaunchTime", 0);
|
||||
|
||||
/*
|
||||
* 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),
|
||||
* [.]minecraft/bin/mcbackup.jar is the default base jar
|
||||
*/
|
||||
settings().registerSetting(new Setting("UseCustomBaseJar", true));
|
||||
settings().registerSetting(new Setting("CustomBaseJar", ""));
|
||||
settings().registerSetting("UseCustomBaseJar", true);
|
||||
settings().registerSetting("CustomBaseJar", "");
|
||||
|
||||
auto globalSettings = MMC->settings();
|
||||
|
||||
// Java Settings
|
||||
settings().registerSetting(new Setting("OverrideJava", false));
|
||||
settings().registerSetting(
|
||||
new OverrideSetting("JavaPath", globalSettings->getSetting("JavaPath")));
|
||||
settings().registerSetting(
|
||||
new OverrideSetting("JvmArgs", globalSettings->getSetting("JvmArgs")));
|
||||
settings().registerSetting("OverrideJava", false);
|
||||
settings().registerOverride(globalSettings->getSetting("JavaPath"));
|
||||
settings().registerOverride(globalSettings->getSetting("JvmArgs"));
|
||||
|
||||
// Custom Commands
|
||||
settings().registerSetting(new Setting("OverrideCommands", false));
|
||||
settings().registerSetting(new OverrideSetting(
|
||||
"PreLaunchCommand", globalSettings->getSetting("PreLaunchCommand")));
|
||||
settings().registerSetting(
|
||||
new OverrideSetting("PostExitCommand", globalSettings->getSetting("PostExitCommand")));
|
||||
settings().registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
|
||||
settings().registerOverride(globalSettings->getSetting("PreLaunchCommand"));
|
||||
settings().registerOverride(globalSettings->getSetting("PostExitCommand"));
|
||||
|
||||
// Window Size
|
||||
settings().registerSetting(new Setting("OverrideWindow", false));
|
||||
settings().registerSetting(
|
||||
new OverrideSetting("LaunchMaximized", globalSettings->getSetting("LaunchMaximized")));
|
||||
settings().registerSetting(new OverrideSetting(
|
||||
"MinecraftWinWidth", globalSettings->getSetting("MinecraftWinWidth")));
|
||||
settings().registerSetting(new OverrideSetting(
|
||||
"MinecraftWinHeight", globalSettings->getSetting("MinecraftWinHeight")));
|
||||
settings().registerSetting("OverrideWindow", false);
|
||||
settings().registerOverride(globalSettings->getSetting("LaunchMaximized"));
|
||||
settings().registerOverride(globalSettings->getSetting("MinecraftWinWidth"));
|
||||
settings().registerOverride(globalSettings->getSetting("MinecraftWinHeight"));
|
||||
|
||||
// Memory
|
||||
settings().registerSetting(new Setting("OverrideMemory", false));
|
||||
settings().registerSetting(
|
||||
new OverrideSetting("MinMemAlloc", globalSettings->getSetting("MinMemAlloc")));
|
||||
settings().registerSetting(
|
||||
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")));
|
||||
settings().registerSetting("OverrideMemory", false);
|
||||
settings().registerOverride(globalSettings->getSetting("MinMemAlloc"));
|
||||
settings().registerOverride(globalSettings->getSetting("MaxMemAlloc"));
|
||||
settings().registerOverride(globalSettings->getSetting("PermGen"));
|
||||
|
||||
// Console
|
||||
settings().registerSetting(new Setting("OverrideConsole", false));
|
||||
settings().registerSetting(
|
||||
new OverrideSetting("ShowConsole", globalSettings->getSetting("ShowConsole")));
|
||||
settings().registerSetting(new OverrideSetting(
|
||||
"AutoCloseConsole", globalSettings->getSetting("AutoCloseConsole")));
|
||||
settings().registerSetting("OverrideConsole", false);
|
||||
settings().registerOverride(globalSettings->getSetting("ShowConsole"));
|
||||
settings().registerOverride(globalSettings->getSetting("AutoCloseConsole"));
|
||||
}
|
||||
|
||||
void BaseInstance::iconUpdated(QString key)
|
||||
{
|
||||
if(iconKey() == key)
|
||||
{
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseInstance::nuke()
|
||||
|
@ -184,6 +184,9 @@ signals:
|
||||
*/
|
||||
void nuked(BaseInstance *inst);
|
||||
|
||||
protected slots:
|
||||
void iconUpdated(QString key);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<BaseInstancePrivate> inst_d;
|
||||
};
|
||||
|
@ -20,7 +20,9 @@
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "LegacyInstance.h"
|
||||
#include "LegacyFTBInstance.h"
|
||||
#include "OneSixInstance.h"
|
||||
#include "OneSixFTBInstance.h"
|
||||
#include "NostalgiaInstance.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "MinecraftVersion.h"
|
||||
@ -43,7 +45,7 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
|
||||
{
|
||||
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();
|
||||
|
||||
@ -60,6 +62,14 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
|
||||
{
|
||||
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
|
||||
{
|
||||
return InstanceFactory::UnknownLoadError;
|
||||
@ -69,7 +79,8 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
|
||||
|
||||
InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&inst,
|
||||
BaseVersionPtr version,
|
||||
const QString &instDir)
|
||||
const QString &instDir,
|
||||
const InstType type)
|
||||
{
|
||||
QDir rootDir(instDir);
|
||||
|
||||
@ -83,34 +94,65 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&
|
||||
return InstanceFactory::NoSuchVersion;
|
||||
|
||||
auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
|
||||
m_settings->registerSetting(new Setting("InstanceType", "Legacy"));
|
||||
m_settings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
switch (mcVer->type)
|
||||
if (type == NormalInst)
|
||||
{
|
||||
case MinecraftVersion::Legacy:
|
||||
m_settings->set("InstanceType", "Legacy");
|
||||
inst = new LegacyInstance(instDir, m_settings, this);
|
||||
inst->setIntendedVersionId(version->descriptor());
|
||||
inst->setShouldUseCustomBaseJar(false);
|
||||
break;
|
||||
case MinecraftVersion::OneSix:
|
||||
m_settings->set("InstanceType", "OneSix");
|
||||
inst = new OneSixInstance(instDir, m_settings, this);
|
||||
inst->setIntendedVersionId(version->descriptor());
|
||||
inst->setShouldUseCustomBaseJar(false);
|
||||
break;
|
||||
case MinecraftVersion::Nostalgia:
|
||||
m_settings->set("InstanceType", "Nostalgia");
|
||||
inst = new NostalgiaInstance(instDir, m_settings, this);
|
||||
inst->setIntendedVersionId(version->descriptor());
|
||||
inst->setShouldUseCustomBaseJar(false);
|
||||
break;
|
||||
default:
|
||||
switch (mcVer->type)
|
||||
{
|
||||
case MinecraftVersion::Legacy:
|
||||
m_settings->set("InstanceType", "Legacy");
|
||||
inst = new LegacyInstance(instDir, m_settings, this);
|
||||
inst->setIntendedVersionId(version->descriptor());
|
||||
inst->setShouldUseCustomBaseJar(false);
|
||||
break;
|
||||
case MinecraftVersion::OneSix:
|
||||
m_settings->set("InstanceType", "OneSix");
|
||||
inst = new OneSixInstance(instDir, m_settings, this);
|
||||
inst->setIntendedVersionId(version->descriptor());
|
||||
inst->setShouldUseCustomBaseJar(false);
|
||||
break;
|
||||
case MinecraftVersion::Nostalgia:
|
||||
m_settings->set("InstanceType", "Nostalgia");
|
||||
inst = new NostalgiaInstance(instDir, m_settings, this);
|
||||
inst->setIntendedVersionId(version->descriptor());
|
||||
inst->setShouldUseCustomBaseJar(false);
|
||||
break;
|
||||
default:
|
||||
{
|
||||
delete m_settings;
|
||||
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?
|
||||
return InstanceFactory::NoCreateError;
|
||||
@ -128,7 +170,17 @@ InstanceFactory::InstCreateError InstanceFactory::copyInstance(BaseInstance *&ne
|
||||
rootDir.removeRecursively();
|
||||
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);
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case NoLoadError:
|
||||
|
@ -55,18 +55,25 @@ public:
|
||||
CantCreateDir
|
||||
};
|
||||
|
||||
enum InstType
|
||||
{
|
||||
NormalInst,
|
||||
FTBInstance
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Creates a stub instance
|
||||
*
|
||||
* \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 type The type of instance to create
|
||||
* \return An InstCreateError error code.
|
||||
* - InstExists if the given instance directory is already an instance.
|
||||
* - CantCreateDir if the given instance directory cannot be created.
|
||||
*/
|
||||
InstCreateError createInstance(BaseInstance *&inst, BaseVersionPtr version,
|
||||
const QString &instDir);
|
||||
const QString &instDir, const InstType type = NormalInst);
|
||||
|
||||
/*!
|
||||
* \brief Creates a copy of an existing instance with a new name
|
||||
|
@ -99,6 +99,7 @@ void JavaChecker::error(QProcess::ProcessError err)
|
||||
if(err == QProcess::FailedToStart)
|
||||
{
|
||||
killTimer.stop();
|
||||
checkerJar.remove();
|
||||
|
||||
JavaCheckResult result;
|
||||
{
|
||||
@ -116,6 +117,5 @@ void JavaChecker::timeout()
|
||||
if(process)
|
||||
{
|
||||
process->kill();
|
||||
process.reset();
|
||||
}
|
||||
}
|
||||
|
@ -177,10 +177,10 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
#elif OSX
|
||||
QList<QString> JavaUtils::FindJavaPaths()
|
||||
{
|
||||
QLOG_INFO() << "OS X Java detection incomplete - defaulting to \"java\"";
|
||||
|
||||
QList<QString> javas;
|
||||
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;
|
||||
}
|
||||
|
16
logic/LegacyFTBInstance.cpp
Normal file
16
logic/LegacyFTBInstance.cpp
Normal 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
13
logic/LegacyFTBInstance.h
Normal 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;
|
||||
};
|
@ -27,7 +27,7 @@
|
||||
|
||||
#include "logic/MinecraftProcess.h"
|
||||
#include "logic/LegacyUpdate.h"
|
||||
#include "logic/lists/IconList.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
|
||||
#include "gui/dialogs/LegacyModEditDialog.h"
|
||||
|
||||
@ -37,11 +37,11 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings,
|
||||
QObject *parent)
|
||||
: BaseInstance(new LegacyInstancePrivate(), rootDir, settings, parent)
|
||||
{
|
||||
settings->registerSetting(new Setting("NeedsRebuild", true));
|
||||
settings->registerSetting(new Setting("ShouldUpdate", false));
|
||||
settings->registerSetting(new Setting("JarVersion", "Unknown"));
|
||||
settings->registerSetting(new Setting("LwjglVersion", "2.9.0"));
|
||||
settings->registerSetting(new Setting("IntendedJarVersion", ""));
|
||||
settings->registerSetting("NeedsRebuild", true);
|
||||
settings->registerSetting("ShouldUpdate", false);
|
||||
settings->registerSetting("JarVersion", "Unknown");
|
||||
settings->registerSetting("LwjglVersion", "2.9.0");
|
||||
settings->registerSetting("IntendedJarVersion", "");
|
||||
}
|
||||
|
||||
std::shared_ptr<Task> LegacyInstance::doUpdate(bool only_prepare)
|
||||
@ -150,6 +150,7 @@ std::shared_ptr<ModList> LegacyInstance::jarModList()
|
||||
|
||||
void LegacyInstance::jarModsChanged()
|
||||
{
|
||||
QLOG_INFO() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt.";
|
||||
setShouldRebuild(true);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ void LegacyUpdate::lwjglStart()
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("Downloading new LWJGL.");
|
||||
setStatus(tr("Downloading new LWJGL..."));
|
||||
auto version = list->getVersion(lwjglVersion);
|
||||
if (!version)
|
||||
{
|
||||
@ -144,7 +144,7 @@ void LegacyUpdate::lwjglFinished(QNetworkReply *reply)
|
||||
saveMe.open(QIODevice::WriteOnly);
|
||||
saveMe.write(m_reply->readAll());
|
||||
saveMe.close();
|
||||
setStatus("Installing new LWJGL...");
|
||||
setStatus(tr("Installing new LWJGL..."));
|
||||
extractLwjgl();
|
||||
jarStart();
|
||||
}
|
||||
@ -220,7 +220,7 @@ void LegacyUpdate::extractLwjgl()
|
||||
// Now if destFileName is still empty, go to the next file.
|
||||
if (!destFileName.isEmpty())
|
||||
{
|
||||
setStatus("Installing new LWJGL - Extracting " + name);
|
||||
setStatus(tr("Installing new LWJGL - extracting ") + name + "...");
|
||||
QFile output(destFileName);
|
||||
output.open(QIODevice::WriteOnly);
|
||||
output.write(file.readAll()); // FIXME: wste of memory!?
|
||||
@ -250,7 +250,7 @@ void LegacyUpdate::jarStart()
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("Checking for jar updates...");
|
||||
setStatus(tr("Checking for jar updates..."));
|
||||
// Make directories
|
||||
QDir binDir(inst->binDir());
|
||||
if (!binDir.exists() && !binDir.mkpath("."))
|
||||
@ -260,7 +260,7 @@ void LegacyUpdate::jarStart()
|
||||
}
|
||||
|
||||
// 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 localPath = version_id + "/" + version_id + ".jar";
|
||||
@ -294,7 +294,7 @@ void LegacyUpdate::jarFailed()
|
||||
bool LegacyUpdate::MergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
|
||||
MetainfAction metainf)
|
||||
{
|
||||
setStatus("Installing mods - Adding " + from.fileName());
|
||||
setStatus(tr("Installing mods: Adding ") + from.fileName() + " ...");
|
||||
|
||||
QuaZip modZip(from.filePath());
|
||||
modZip.open(QuaZip::mdUnzip);
|
||||
@ -380,7 +380,7 @@ void LegacyUpdate::ModTheJar()
|
||||
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()))
|
||||
{
|
||||
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
|
||||
setStatus("Installing mods - Opening minecraft.jar");
|
||||
setStatus(tr("Installing mods: Opening minecraft.jar ..."));
|
||||
|
||||
QuaZip zipOut(runnableJar.filePath());
|
||||
if (!zipOut.open(QuaZip::mdCreate))
|
||||
@ -419,10 +419,15 @@ void LegacyUpdate::ModTheJar()
|
||||
QSet<QString> addedFiles;
|
||||
|
||||
// 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--)
|
||||
{
|
||||
auto &mod = modList->operator[](i);
|
||||
|
||||
// do not merge disabled mods.
|
||||
if(!mod.enabled())
|
||||
continue;
|
||||
|
||||
if (mod.type() == Mod::MOD_ZIPFILE)
|
||||
{
|
||||
if (!MergeZipFiles(&zipOut, mod.filename(), addedFiles, LegacyUpdate::KeepMetainf))
|
||||
|
102
logic/LiteLoaderInstaller.cpp
Normal file
102
logic/LiteLoaderInstaller.cpp
Normal 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);
|
||||
}
|
@ -14,29 +14,26 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
class OneSixVersion;
|
||||
|
||||
#include "settingsobject.h"
|
||||
|
||||
#include "libsettings_config.h"
|
||||
|
||||
/*!
|
||||
* \brief A settings object that stores its settings in a QSettings object.
|
||||
*/
|
||||
class LIBSETTINGS_EXPORT BasicSettingsObject : public SettingsObject
|
||||
class LiteLoaderInstaller
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BasicSettingsObject(QObject *parent = 0);
|
||||
LiteLoaderInstaller(const QString &mcVersion);
|
||||
|
||||
protected
|
||||
slots:
|
||||
virtual void changeSetting(const Setting &setting, QVariant value);
|
||||
bool canApply() const;
|
||||
|
||||
protected:
|
||||
virtual QVariant retrieveValue(const Setting &setting);
|
||||
bool apply(std::shared_ptr<OneSixVersion> to);
|
||||
|
||||
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;
|
||||
};
|
143
logic/Mod.cpp
143
logic/Mod.cpp
@ -35,20 +35,45 @@ Mod::Mod(const QFileInfo &file)
|
||||
void Mod::repath(const QFileInfo &file)
|
||||
{
|
||||
m_file = file;
|
||||
m_name = file.completeBaseName();
|
||||
m_id = file.fileName();
|
||||
QString name_base = file.fileName();
|
||||
|
||||
m_type = Mod::MOD_UNKNOWN;
|
||||
|
||||
if (m_file.isDir())
|
||||
{
|
||||
m_type = MOD_FOLDER;
|
||||
m_name = name_base;
|
||||
m_mmc_id = name_base;
|
||||
}
|
||||
else if (m_file.isFile())
|
||||
{
|
||||
QString ext = m_file.suffix().toLower();
|
||||
if (ext == "zip" || ext == "jar")
|
||||
m_type = MOD_ZIPFILE;
|
||||
if (name_base.endsWith(".disabled"))
|
||||
{
|
||||
m_enabled = false;
|
||||
name_base.chop(9);
|
||||
}
|
||||
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_name = name_base;
|
||||
}
|
||||
|
||||
if (m_type == MOD_ZIPFILE)
|
||||
{
|
||||
QuaZip zip(m_file.filePath());
|
||||
@ -59,7 +84,7 @@ void Mod::repath(const QFileInfo &file)
|
||||
|
||||
if (zip.setCurrentFile("mcmod.info"))
|
||||
{
|
||||
if(!file.open(QIODevice::ReadOnly))
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
@ -100,6 +125,27 @@ void Mod::repath(const QFileInfo &file)
|
||||
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
|
||||
@ -114,7 +160,7 @@ void Mod::ReadMCModInfo(QByteArray contents)
|
||||
if (!arr.at(0).isObject())
|
||||
return;
|
||||
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_version = firstObj.value("version").toString();
|
||||
m_homeurl = firstObj.value("url").toString();
|
||||
@ -132,8 +178,7 @@ void Mod::ReadMCModInfo(QByteArray contents)
|
||||
}
|
||||
m_credits = firstObj.value("credits").toString();
|
||||
return;
|
||||
}
|
||||
;
|
||||
};
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||
// this is the very old format that had just the array
|
||||
@ -163,7 +208,7 @@ void Mod::ReadForgeInfo(QByteArray contents)
|
||||
{
|
||||
// Read the data
|
||||
m_name = "Minecraft Forge";
|
||||
m_id = "Forge";
|
||||
m_mod_id = "Forge";
|
||||
m_homeurl = "http://www.minecraftforge.net/forum/";
|
||||
INIFile ini;
|
||||
if (!ini.loadFile(contents))
|
||||
@ -177,15 +222,40 @@ void Mod::ReadForgeInfo(QByteArray contents)
|
||||
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)
|
||||
{
|
||||
if (!destroy())
|
||||
return false;
|
||||
bool success = false;
|
||||
auto t = with.type();
|
||||
|
||||
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)
|
||||
{
|
||||
@ -193,11 +263,17 @@ bool Mod::replace(Mod &with)
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
m_id = with.m_id;
|
||||
m_mcversion = with.m_mcversion;
|
||||
m_type = with.m_type;
|
||||
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_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;
|
||||
}
|
||||
@ -232,6 +308,7 @@ QString Mod::version() const
|
||||
switch (type())
|
||||
{
|
||||
case MOD_ZIPFILE:
|
||||
case MOD_LITEMOD:
|
||||
return m_version;
|
||||
case MOD_FOLDER:
|
||||
return "Folder";
|
||||
@ -241,3 +318,41 @@ QString Mod::version() const
|
||||
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();
|
||||
}
|
||||
|
32
logic/Mod.h
32
logic/Mod.h
@ -25,6 +25,7 @@ public:
|
||||
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_FOLDER, //!< The mod is in a folder on the filesystem.
|
||||
MOD_LITEMOD, //!< The mod is a litemod
|
||||
};
|
||||
|
||||
Mod(const QFileInfo &file);
|
||||
@ -33,9 +34,13 @@ public:
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -77,6 +82,13 @@ public:
|
||||
return m_credits;
|
||||
}
|
||||
|
||||
bool enabled() const
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
bool enable(bool value);
|
||||
|
||||
// delete all the files of this mod
|
||||
bool destroy();
|
||||
// replace this mod with a copy of the other
|
||||
@ -85,19 +97,13 @@ public:
|
||||
void repath(const QFileInfo &file);
|
||||
|
||||
// WEAK compare operator - used for replacing mods
|
||||
bool operator==(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();
|
||||
}
|
||||
bool operator==(const Mod &other) const;
|
||||
bool strongCompare(const Mod &other) const;
|
||||
|
||||
private:
|
||||
void ReadMCModInfo(QByteArray contents);
|
||||
void ReadForgeInfo(QByteArray contents);
|
||||
void ReadLiteModInfo(QByteArray contents);
|
||||
|
||||
protected:
|
||||
|
||||
@ -108,7 +114,9 @@ protected:
|
||||
*/
|
||||
|
||||
QFileInfo m_file;
|
||||
QString m_id;
|
||||
QString m_mmc_id;
|
||||
QString m_mod_id;
|
||||
bool m_enabled = true;
|
||||
QString m_name;
|
||||
QString m_version;
|
||||
QString m_mcversion;
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
#include <QString>
|
||||
#include <QFileSystemWatcher>
|
||||
#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 |
|
||||
QDir::NoSymLinks);
|
||||
m_dir.setSorting(QDir::Name);
|
||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||
m_list_id = QUuid::createUuid().toString();
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
is_watching = false;
|
||||
@ -66,52 +67,89 @@ bool ModList::update()
|
||||
if (!isValid())
|
||||
return false;
|
||||
|
||||
QList<Mod> orderedMods;
|
||||
QList<Mod> newMods;
|
||||
m_dir.refresh();
|
||||
auto folderContents = m_dir.entryInfoList();
|
||||
bool orderWasInvalid = false;
|
||||
bool orderOrStateChanged = false;
|
||||
|
||||
// first, process the ordered items (if any)
|
||||
QStringList listOrder = readListFile();
|
||||
OrderList listOrder = readListFile();
|
||||
for (auto item : listOrder)
|
||||
{
|
||||
QFileInfo info(m_dir.filePath(item));
|
||||
int idx = folderContents.indexOf(info);
|
||||
QFileInfo infoEnabled(m_dir.filePath(item.id));
|
||||
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 (idx != -1)
|
||||
{
|
||||
// remove from the actual folder contents list
|
||||
folderContents.takeAt(idx);
|
||||
// append the new mod
|
||||
newMods.append(Mod(info));
|
||||
orderedMods.append(Mod(info));
|
||||
if (isEnabled != item.enabled)
|
||||
orderOrStateChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
orderWasInvalid = true;
|
||||
orderOrStateChanged = true;
|
||||
}
|
||||
}
|
||||
for (auto entry : folderContents)
|
||||
// if there are any untracked files...
|
||||
if (folderContents.size())
|
||||
{
|
||||
newMods.append(Mod(entry));
|
||||
}
|
||||
if (mods.size() != newMods.size())
|
||||
{
|
||||
orderWasInvalid = true;
|
||||
}
|
||||
else
|
||||
for (int i = 0; i < mods.size(); i++)
|
||||
// the order surely changed!
|
||||
for (auto entry : folderContents)
|
||||
{
|
||||
if (!mods[i].strongCompare(newMods[i]))
|
||||
{
|
||||
orderWasInvalid = true;
|
||||
break;
|
||||
}
|
||||
newMods.append(Mod(entry));
|
||||
}
|
||||
beginResetModel();
|
||||
mods.swap(newMods);
|
||||
endResetModel();
|
||||
if (orderWasInvalid)
|
||||
std::sort(newMods.begin(), newMods.end(), [](const Mod & left, const Mod & right)
|
||||
{ return left.name().localeAwareCompare(right.name()) <= 0; });
|
||||
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
|
||||
for (int i = 0; i < mods.size(); i++)
|
||||
{
|
||||
if (!mods[i].strongCompare(orderedMods[i]))
|
||||
{
|
||||
orderOrStateChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
beginResetModel();
|
||||
mods.swap(orderedMods);
|
||||
endResetModel();
|
||||
if (orderOrStateChanged && !m_list_file.isEmpty())
|
||||
{
|
||||
QLOG_INFO() << "Mod list " << m_list_file << " changed!";
|
||||
saveListFile();
|
||||
emit changed();
|
||||
}
|
||||
@ -123,17 +161,19 @@ void ModList::directoryChanged(QString path)
|
||||
update();
|
||||
}
|
||||
|
||||
QStringList ModList::readListFile()
|
||||
ModList::OrderList ModList::readListFile()
|
||||
{
|
||||
QStringList stringList;
|
||||
OrderList itemList;
|
||||
if (m_list_file.isNull() || m_list_file.isEmpty())
|
||||
return stringList;
|
||||
return itemList;
|
||||
|
||||
QFile textFile(m_list_file);
|
||||
if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return QStringList();
|
||||
return OrderList();
|
||||
|
||||
QTextStream textStream(&textFile);
|
||||
QTextStream textStream;
|
||||
textStream.setAutoDetectUnicode(true);
|
||||
textStream.setDevice(&textFile);
|
||||
while (true)
|
||||
{
|
||||
QString line = textStream.readLine();
|
||||
@ -141,11 +181,18 @@ QStringList ModList::readListFile()
|
||||
break;
|
||||
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();
|
||||
return stringList;
|
||||
return itemList;
|
||||
}
|
||||
|
||||
bool ModList::saveListFile()
|
||||
@ -155,12 +202,16 @@ bool ModList::saveListFile()
|
||||
QFile textFile(m_list_file);
|
||||
if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
|
||||
return false;
|
||||
QTextStream textStream(&textFile);
|
||||
QTextStream textStream;
|
||||
textStream.setGenerateByteOrderMark(true);
|
||||
textStream.setCodec("UTF-8");
|
||||
textStream.setDevice(&textFile);
|
||||
for (auto mod : mods)
|
||||
{
|
||||
auto pathname = mod.filename();
|
||||
QString filename = pathname.fileName();
|
||||
textStream << filename << endl;
|
||||
textStream << mod.mmc_id();
|
||||
if (!mod.enabled())
|
||||
textStream << ".disabled";
|
||||
textStream << endl;
|
||||
}
|
||||
textFile.close();
|
||||
return false;
|
||||
@ -185,6 +236,9 @@ bool ModList::installMod(const QFileInfo &filename, int index)
|
||||
int idx = mods.indexOf(m);
|
||||
if (idx != -1)
|
||||
{
|
||||
int idx2 = mods.indexOf(m,idx+1);
|
||||
if(idx2 != -1)
|
||||
return false;
|
||||
if (mods[idx].replace(m))
|
||||
{
|
||||
|
||||
@ -201,7 +255,7 @@ bool ModList::installMod(const QFileInfo &filename, int index)
|
||||
auto type = m.type();
|
||||
if (type == Mod::MOD_UNKNOWN)
|
||||
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());
|
||||
if (!QFile::copy(filename.filePath(), newpath))
|
||||
@ -327,7 +381,7 @@ bool ModList::moveModsDown(int first, int last)
|
||||
|
||||
int ModList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
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())
|
||||
return QVariant();
|
||||
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
switch (column)
|
||||
switch (role)
|
||||
{
|
||||
case 0:
|
||||
return mods[row].name();
|
||||
case 1:
|
||||
return mods[row].version();
|
||||
case 2:
|
||||
return mods[row].mcversion();
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case NameColumn:
|
||||
return mods[row].name();
|
||||
case VersionColumn:
|
||||
return mods[row].version();
|
||||
|
||||
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:
|
||||
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
|
||||
{
|
||||
if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
|
||||
return QVariant();
|
||||
switch (section)
|
||||
switch (role)
|
||||
{
|
||||
case 0:
|
||||
return QString("Name");
|
||||
case 1:
|
||||
return QString("Version");
|
||||
case 2:
|
||||
return QString("Minecraft");
|
||||
case Qt::DisplayRole:
|
||||
switch (section)
|
||||
{
|
||||
case ActiveColumn:
|
||||
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 QString();
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags ModList::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
|
||||
return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
|
||||
defaultFlags;
|
||||
else
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
}
|
||||
@ -456,6 +563,14 @@ bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row
|
||||
QString filename = url.toLocalFile();
|
||||
installMod(filename, row);
|
||||
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)
|
||||
startWatching();
|
||||
|
@ -34,9 +34,18 @@ class ModList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
ActiveColumn = 0,
|
||||
NameColumn,
|
||||
VersionColumn
|
||||
};
|
||||
ModList(const QString &dir, const QString &list_file = QString());
|
||||
|
||||
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
|
||||
{
|
||||
return size();
|
||||
@ -59,7 +68,6 @@ public:
|
||||
{
|
||||
return mods[index];
|
||||
}
|
||||
;
|
||||
|
||||
/// Reloads the mod list and returns true if the list changed.
|
||||
virtual bool update();
|
||||
@ -119,7 +127,13 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
QStringList readListFile();
|
||||
struct OrderItem
|
||||
{
|
||||
QString id;
|
||||
bool enabled = false;
|
||||
};
|
||||
typedef QList<OrderItem> OrderList;
|
||||
OrderList readListFile();
|
||||
bool saveListFile();
|
||||
private
|
||||
slots:
|
||||
|
120
logic/OneSixFTBInstance.cpp
Normal file
120
logic/OneSixFTBInstance.cpp
Normal 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
20
logic/OneSixFTBInstance.h
Normal 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;
|
||||
};
|
@ -33,8 +33,8 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_o
|
||||
: BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent)
|
||||
{
|
||||
I_D(OneSixInstance);
|
||||
d->m_settings->registerSetting(new Setting("IntendedVersion", ""));
|
||||
d->m_settings->registerSetting(new Setting("ShouldUpdate", false));
|
||||
d->m_settings->registerSetting("IntendedVersion", "");
|
||||
d->m_settings->registerSetting("ShouldUpdate", false);
|
||||
reloadFullVersion();
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,12 @@ public:
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
/// Returns the raw name field
|
||||
QString rawName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QJsonObject toJson();
|
||||
|
||||
/**
|
||||
|
@ -57,7 +57,7 @@ void OneSixUpdate::executeTask()
|
||||
/*
|
||||
* 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();
|
||||
|
||||
checker.reset(new JavaChecker());
|
||||
@ -89,7 +89,7 @@ void OneSixUpdate::executeTask()
|
||||
|
||||
void OneSixUpdate::checkJavaOnline()
|
||||
{
|
||||
setStatus("Testing the Java installation.");
|
||||
setStatus(tr("Testing the Java installation..."));
|
||||
QString java_path = m_inst->settings().get("JavaPath").toString();
|
||||
|
||||
checker.reset(new JavaChecker());
|
||||
@ -128,7 +128,7 @@ void OneSixUpdate::checkFinishedOffline(JavaCheckResult result)
|
||||
void OneSixUpdate::versionFileStart()
|
||||
{
|
||||
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";
|
||||
auto job = new NetJob("Version index");
|
||||
@ -196,7 +196,7 @@ void OneSixUpdate::versionFileFailed()
|
||||
|
||||
void OneSixUpdate::assetIndexStart()
|
||||
{
|
||||
setStatus("Updating asset index.");
|
||||
setStatus(tr("Updating assets index..."));
|
||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||
std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
|
||||
QString assetName = version->assets;
|
||||
@ -247,7 +247,7 @@ void OneSixUpdate::assetIndexFinished()
|
||||
}
|
||||
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());
|
||||
for(auto dl: dls)
|
||||
job->addNetAction(dl);
|
||||
@ -281,7 +281,7 @@ void OneSixUpdate::assetsFailed()
|
||||
|
||||
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";
|
||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||
bool successful = inst->reloadFullVersion();
|
||||
@ -369,7 +369,7 @@ void OneSixUpdate::jarlibFailed()
|
||||
|
||||
void OneSixUpdate::prepareForLaunch()
|
||||
{
|
||||
setStatus("Preparing for launch.");
|
||||
setStatus(tr("Preparing for launch..."));
|
||||
QLOG_INFO() << m_inst->name() << ": preparing for launch";
|
||||
auto onesix_inst = (OneSixInstance *)m_inst;
|
||||
|
||||
|
143
logic/assets/AssetsMigrateTask.cpp
Normal file
143
logic/assets/AssetsMigrateTask.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
18
logic/assets/AssetsMigrateTask.h
Normal file
18
logic/assets/AssetsMigrateTask.h
Normal 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;
|
||||
};
|
@ -25,23 +25,18 @@
|
||||
|
||||
namespace AssetsUtils
|
||||
{
|
||||
void migrateOldAssets()
|
||||
int findLegacyAssets()
|
||||
{
|
||||
QDir assets_dir("assets");
|
||||
if (!assets_dir.exists())
|
||||
return;
|
||||
return 0;
|
||||
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;
|
||||
int found = 0;
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
QString currentDir = iterator.next();
|
||||
@ -56,79 +51,11 @@ void migrateOldAssets()
|
||||
|
||||
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.";
|
||||
}
|
||||
found++;
|
||||
}
|
||||
}
|
||||
|
||||
if (successes + failures == 0)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -34,6 +34,6 @@ struct AssetsIndex
|
||||
|
||||
namespace AssetsUtils
|
||||
{
|
||||
void migrateOldAssets();
|
||||
bool loadAssetsIndexJson(QString file, AssetsIndex* index);
|
||||
int findLegacyAssets();
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
|
||||
// The JSON object must at least have a username for it to be valid.
|
||||
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;
|
||||
}
|
||||
|
||||
@ -43,7 +44,8 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
|
||||
QJsonArray profileArray = object.value("profiles").toArray();
|
||||
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;
|
||||
}
|
||||
|
||||
@ -63,7 +65,7 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
|
||||
}
|
||||
|
||||
MojangAccountPtr account(new MojangAccount());
|
||||
if(object.value("user").isObject())
|
||||
if (object.value("user").isObject())
|
||||
{
|
||||
User u;
|
||||
QJsonObject userStructure = object.value("user").toObject();
|
||||
@ -92,7 +94,7 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
|
||||
return account;
|
||||
}
|
||||
|
||||
MojangAccountPtr MojangAccount::createFromUsername(const QString& username)
|
||||
MojangAccountPtr MojangAccount::createFromUsername(const QString &username)
|
||||
{
|
||||
MojangAccountPtr account(new MojangAccount());
|
||||
account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
|
||||
@ -152,27 +154,27 @@ bool MojangAccount::setCurrentProfile(const QString &profileId)
|
||||
return false;
|
||||
}
|
||||
|
||||
const AccountProfile* MojangAccount::currentProfile() const
|
||||
const AccountProfile *MojangAccount::currentProfile() const
|
||||
{
|
||||
if(m_currentProfile == -1)
|
||||
if (m_currentProfile == -1)
|
||||
return nullptr;
|
||||
return &m_profiles[m_currentProfile];
|
||||
}
|
||||
|
||||
AccountStatus MojangAccount::accountStatus() const
|
||||
{
|
||||
if(m_accessToken.isEmpty())
|
||||
if (m_accessToken.isEmpty())
|
||||
return NotVerified;
|
||||
if(!m_online)
|
||||
if (!m_online)
|
||||
return Verified;
|
||||
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;
|
||||
if(password.isEmpty())
|
||||
if (password.isEmpty())
|
||||
{
|
||||
m_currentTask.reset(new RefreshTask(this));
|
||||
}
|
||||
@ -196,7 +198,7 @@ void MojangAccount::authFailed(QString reason)
|
||||
{
|
||||
// This is emitted when the yggdrasil tasks time out or are cancelled.
|
||||
// -> we treat the error as no-op
|
||||
if(reason != "Yggdrasil task cancelled.")
|
||||
if (reason != "Yggdrasil task cancelled.")
|
||||
{
|
||||
m_online = false;
|
||||
m_accessToken = QString();
|
||||
|
@ -95,7 +95,7 @@ public: /* manipulation */
|
||||
* Attempt to login. Empty password means we use the token.
|
||||
* 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()
|
||||
{
|
||||
|
@ -48,6 +48,7 @@ void YggdrasilTask::executeTask()
|
||||
connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply);
|
||||
connect(m_netReply, &QNetworkReply::uploadProgress, 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.start(timeout_max);
|
||||
counter.setSingleShot(false);
|
||||
@ -75,16 +76,45 @@ void YggdrasilTask::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()
|
||||
{
|
||||
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
|
||||
if (m_netReply->error() >= QNetworkReply::ConnectionRefusedError &&
|
||||
m_netReply->error() <= QNetworkReply::UnknownNetworkError)
|
||||
{
|
||||
// WARNING/FIXME: the value here is used in MojangAccount to detect the cancel/timeout
|
||||
emitFailed("Yggdrasil task cancelled.");
|
||||
QLOG_ERROR() << "Yggdrasil task cancelled because of: " << m_netReply->error() << " : "
|
||||
<< m_netReply->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -172,10 +202,10 @@ QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const
|
||||
switch (state)
|
||||
{
|
||||
case STATE_SENDING_REQUEST:
|
||||
return tr("Sending request to auth servers.");
|
||||
return tr("Sending request to auth servers...");
|
||||
case STATE_PROCESSING_RESPONSE:
|
||||
return tr("Processing response from servers.");
|
||||
return tr("Processing response from servers...");
|
||||
default:
|
||||
return tr("Processing. Please wait.");
|
||||
return tr("Processing. Please wait...");
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <qsslerror.h>
|
||||
|
||||
#include "logic/auth/MojangAccount.h"
|
||||
|
||||
@ -99,6 +100,7 @@ slots:
|
||||
void processReply();
|
||||
void refreshTimers(qint64, qint64);
|
||||
void heartbeat();
|
||||
void sslErrors(QList<QSslError>);
|
||||
|
||||
public
|
||||
slots:
|
||||
|
@ -194,9 +194,9 @@ QString AuthenticateTask::getStateMessage(const YggdrasilTask::State state) cons
|
||||
switch (state)
|
||||
{
|
||||
case STATE_SENDING_REQUEST:
|
||||
return tr("Authenticating: Sending request.");
|
||||
return tr("Authenticating: Sending request...");
|
||||
case STATE_PROCESSING_RESPONSE:
|
||||
return tr("Authenticating: Processing response.");
|
||||
return tr("Authenticating: Processing response...");
|
||||
default:
|
||||
return YggdrasilTask::getStateMessage(state);
|
||||
}
|
||||
|
@ -145,9 +145,9 @@ QString RefreshTask::getStateMessage(const YggdrasilTask::State state) const
|
||||
switch (state)
|
||||
{
|
||||
case STATE_SENDING_REQUEST:
|
||||
return tr("Refreshing login token.");
|
||||
return tr("Refreshing login token...");
|
||||
case STATE_PROCESSING_RESPONSE:
|
||||
return tr("Refreshing login token: Processing response.");
|
||||
return tr("Refreshing login token: Processing response...");
|
||||
default:
|
||||
return YggdrasilTask::getStateMessage(state);
|
||||
}
|
||||
|
@ -55,9 +55,9 @@ QString ValidateTask::getStateMessage(const YggdrasilTask::State state) const
|
||||
switch (state)
|
||||
{
|
||||
case STATE_SENDING_REQUEST:
|
||||
return tr("Validating Access Token: Sending request.");
|
||||
return tr("Validating access token: Sending request...");
|
||||
case STATE_PROCESSING_RESPONSE:
|
||||
return tr("Validating Access Token: Processing response.");
|
||||
return tr("Validating access token: Processing response...");
|
||||
default:
|
||||
return YggdrasilTask::getStateMessage(state);
|
||||
}
|
||||
|
351
logic/icons/IconList.cpp
Normal file
351
logic/icons/IconList.cpp
Normal 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"
|
@ -17,15 +17,21 @@
|
||||
|
||||
#include <QMutex>
|
||||
#include <QAbstractListModel>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QtGui/QIcon>
|
||||
#include <memory>
|
||||
#include "MMCIcon.h"
|
||||
#include "setting.h"
|
||||
|
||||
class Private;
|
||||
class QFileSystemWatcher;
|
||||
|
||||
class IconList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
IconList();
|
||||
virtual ~IconList();
|
||||
explicit IconList(QObject *parent = 0);
|
||||
virtual ~IconList() {};
|
||||
|
||||
QIcon getIcon(QString key);
|
||||
int getIconIndex(QString key);
|
||||
@ -33,7 +39,7 @@ public:
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) 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);
|
||||
|
||||
virtual QStringList mimeTypes() const;
|
||||
@ -44,11 +50,28 @@ public:
|
||||
|
||||
void installIcons(QStringList iconFiles);
|
||||
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
||||
signals:
|
||||
void iconUpdated(QString key);
|
||||
|
||||
private:
|
||||
// hide copy constructor
|
||||
IconList(const IconList &) = delete;
|
||||
// hide assign op
|
||||
IconList &operator=(const IconList &) = delete;
|
||||
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
89
logic/icons/MMCIcon.cpp
Normal 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;
|
||||
}
|
@ -13,32 +13,40 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "include/basicsettingsobject.h"
|
||||
#include "include/setting.h"
|
||||
|
||||
BasicSettingsObject::BasicSettingsObject(QObject *parent) : SettingsObject(parent)
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QIcon>
|
||||
struct MMCImage
|
||||
{
|
||||
}
|
||||
QIcon icon;
|
||||
QString filename;
|
||||
QDateTime changed;
|
||||
bool present() const
|
||||
{
|
||||
return !icon.isNull();
|
||||
}
|
||||
};
|
||||
|
||||
void BasicSettingsObject::changeSetting(const Setting &setting, QVariant value)
|
||||
struct MMCIcon
|
||||
{
|
||||
if (contains(setting.id()))
|
||||
enum Type : unsigned
|
||||
{
|
||||
if (value.isValid())
|
||||
config.setValue(setting.configKey(), value);
|
||||
else
|
||||
config.remove(setting.configKey());
|
||||
}
|
||||
}
|
||||
Builtin,
|
||||
Transient,
|
||||
FileBased,
|
||||
ICONS_TOTAL,
|
||||
ToBeDeleted
|
||||
};
|
||||
QString m_key;
|
||||
QString m_name;
|
||||
MMCImage m_images[ICONS_TOTAL];
|
||||
Type m_current_type = ToBeDeleted;
|
||||
|
||||
QVariant BasicSettingsObject::retrieveValue(const Setting &setting)
|
||||
{
|
||||
if (contains(setting.id()))
|
||||
{
|
||||
return config.value(setting.configKey());
|
||||
}
|
||||
else
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
Type type() const;
|
||||
QString name() const;
|
||||
bool has(Type _type) const;
|
||||
QIcon icon() const;
|
||||
void remove(Type rm_type);
|
||||
void replace(Type new_type, QIcon icon, QString path = QString());
|
||||
};
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "ForgeVersionList.h"
|
||||
#include <logic/net/NetJob.h>
|
||||
#include <logic/net/URLConstants.h>
|
||||
#include "MultiMC.h"
|
||||
|
||||
#include <QtNetwork>
|
||||
@ -23,8 +24,6 @@
|
||||
|
||||
#include "logger/QsLog.h"
|
||||
|
||||
#define JSON_URL "http://files.minecraftforge.net/minecraftforge/json"
|
||||
|
||||
ForgeVersionList::ForgeVersionList(QObject *parent) : BaseVersionList(parent)
|
||||
{
|
||||
}
|
||||
@ -159,44 +158,43 @@ ForgeListLoadTask::ForgeListLoadTask(ForgeVersionList *vlist) : Task()
|
||||
|
||||
void ForgeListLoadTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Fetching Forge version lists..."));
|
||||
auto job = new NetJob("Version index");
|
||||
// we do not care if the version is stale or not.
|
||||
auto forgeListEntry = MMC->metacache()->resolveEntry("minecraftforge", "list.json");
|
||||
auto gradleForgeListEntry = MMC->metacache()->resolveEntry("minecraftforge", "json");
|
||||
|
||||
// verify by poking the server.
|
||||
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);
|
||||
connect(listJob.get(), SIGNAL(succeeded()), SLOT(list_downloaded()));
|
||||
connect(listJob.get(), SIGNAL(failed()), SLOT(list_failed()));
|
||||
connect(listJob.get(), SIGNAL(succeeded()), SLOT(listDownloaded()));
|
||||
connect(listJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
|
||||
listJob->start();
|
||||
}
|
||||
|
||||
void ForgeListLoadTask::list_failed()
|
||||
{
|
||||
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()
|
||||
bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out)
|
||||
{
|
||||
QByteArray data;
|
||||
{
|
||||
auto DlJob = listJob->first();
|
||||
auto filename = std::dynamic_pointer_cast<CacheDownload>(DlJob)->m_target_path;
|
||||
auto dlJob = listDownload;
|
||||
auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->m_target_path;
|
||||
QFile listFile(filename);
|
||||
if (!listFile.open(QIODevice::ReadOnly))
|
||||
return;
|
||||
{
|
||||
return false;
|
||||
}
|
||||
data = listFile.readAll();
|
||||
DlJob.reset();
|
||||
dlJob.reset();
|
||||
}
|
||||
|
||||
QJsonParseError jsonError;
|
||||
@ -205,13 +203,13 @@ void ForgeListLoadTask::list_downloaded()
|
||||
if (jsonError.error != QJsonParseError::NoError)
|
||||
{
|
||||
emitFailed("Error parsing version list JSON:" + jsonError.errorString());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
emitFailed("Error parsing version list JSON: jsonDoc is not an object");
|
||||
return;
|
||||
emitFailed("Error parsing version list JSON: JSON root is not an object");
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject root = jsonDoc.object();
|
||||
@ -221,11 +219,10 @@ void ForgeListLoadTask::list_downloaded()
|
||||
{
|
||||
emitFailed(
|
||||
"Error parsing version list JSON: version list object is missing 'builds' array");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
QJsonArray builds = root.value("builds").toArray();
|
||||
|
||||
QList<BaseVersionPtr> tempList;
|
||||
for (int i = 0; i < builds.count(); i++)
|
||||
{
|
||||
// Load the version info.
|
||||
@ -246,7 +243,9 @@ void ForgeListLoadTask::list_downloaded()
|
||||
for (int j = 0; j < files.count(); j++)
|
||||
{
|
||||
if (!files[j].isObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
QJsonObject file = files[j].toObject();
|
||||
buildtype = file.value("buildtype").toString();
|
||||
if ((buildtype == "client" || buildtype == "universal") && !valid)
|
||||
@ -262,7 +261,9 @@ void ForgeListLoadTask::list_downloaded()
|
||||
{
|
||||
QString ext = file.value("ext").toString();
|
||||
if (ext.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
changelog_url = file.value("url").toString();
|
||||
}
|
||||
else if (buildtype == "installer")
|
||||
@ -282,15 +283,161 @@ void ForgeListLoadTask::list_downloaded()
|
||||
fVersion->jobbuildver = jobbuildver;
|
||||
fVersion->mcver = mcver;
|
||||
if (installer_filename.isEmpty())
|
||||
{
|
||||
fVersion->filename = filename;
|
||||
}
|
||||
else
|
||||
{
|
||||
fVersion->filename = installer_filename;
|
||||
}
|
||||
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();
|
||||
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.";
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ public:
|
||||
protected:
|
||||
QList<BaseVersionPtr> m_vlist;
|
||||
|
||||
bool m_loaded;
|
||||
bool m_loaded = false;
|
||||
|
||||
protected
|
||||
slots:
|
||||
@ -98,10 +98,18 @@ public:
|
||||
|
||||
protected
|
||||
slots:
|
||||
void list_downloaded();
|
||||
void list_failed();
|
||||
void listDownloaded();
|
||||
void listFailed();
|
||||
void gradleListFailed();
|
||||
|
||||
protected:
|
||||
NetJobPtr listJob;
|
||||
ForgeVersionList *m_list;
|
||||
|
||||
CacheDownloadPtr listDownload;
|
||||
CacheDownloadPtr gradleListDownload;
|
||||
|
||||
private:
|
||||
bool parseForgeList(QList<BaseVersionPtr> &out);
|
||||
bool parseForgeGradleList(QList<BaseVersionPtr> &out);
|
||||
};
|
||||
|
@ -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"
|
@ -22,11 +22,14 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QRegularExpression>
|
||||
#include <pathutils.h>
|
||||
|
||||
#include "MultiMC.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/InstanceFactory.h"
|
||||
#include "logger/QsLog.h"
|
||||
@ -42,6 +45,9 @@ InstanceList::InstanceList(const QString &instDir, QObject *parent)
|
||||
{
|
||||
QDir::current().mkpath(m_instDir);
|
||||
}
|
||||
|
||||
connect(MMC->minecraftlist().get(), &MinecraftVersionList::modelReset, this,
|
||||
&InstanceList::loadList);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
// load the instance groups
|
||||
@ -285,57 +410,27 @@ InstanceList::InstListError InstanceList::loadList()
|
||||
beginResetModel();
|
||||
|
||||
m_instances.clear();
|
||||
QDir dir(m_instDir);
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
|
||||
QDirIterator::FollowSymlinks);
|
||||
while (iter.hasNext())
|
||||
|
||||
{
|
||||
QString subDir = iter.next();
|
||||
if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists())
|
||||
continue;
|
||||
|
||||
BaseInstance *instPtr = NULL;
|
||||
auto &loader = InstanceFactory::get();
|
||||
auto error = loader.loadInstance(instPtr, subDir);
|
||||
|
||||
if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance)
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
|
||||
QDirIterator::FollowSymlinks);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
QString errorMsg = QString("Failed to load instance %1: ")
|
||||
.arg(QFileInfo(subDir).baseName())
|
||||
.toUtf8();
|
||||
QString subDir = iter.next();
|
||||
if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists())
|
||||
continue;
|
||||
|
||||
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 *)));
|
||||
BaseInstance *instPtr = NULL;
|
||||
auto error = InstanceFactory::get().loadInstance(instPtr, subDir);
|
||||
continueProcessInstance(instPtr, error, subDir, groupMap);
|
||||
}
|
||||
}
|
||||
|
||||
if (MMC->settings()->get("TrackFTBInstances").toBool())
|
||||
{
|
||||
loadForgeInstances(groupMap);
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
emit dataIsInvalid();
|
||||
return NoError;
|
||||
@ -409,6 +504,47 @@ int InstanceList::getInstIndex(BaseInstance *inst) const
|
||||
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)
|
||||
{
|
||||
int i = getInstIndex(inst);
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
class BaseInstance;
|
||||
|
||||
class QDir;
|
||||
|
||||
class InstanceList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -65,11 +67,6 @@ public:
|
||||
return m_instDir;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Loads the instance list. Triggers notifications.
|
||||
*/
|
||||
InstListError loadList();
|
||||
|
||||
/*!
|
||||
* \brief Get the instance at index
|
||||
*/
|
||||
@ -108,6 +105,12 @@ public
|
||||
slots:
|
||||
void on_InstFolderChanged(const Setting &setting, QVariant value);
|
||||
|
||||
/*!
|
||||
* \brief Loads the instance list. Triggers notifications.
|
||||
*/
|
||||
InstListError loadList();
|
||||
void loadForgeInstances(QMap<QString, QString> groupMap);
|
||||
|
||||
private
|
||||
slots:
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
@ -117,6 +120,9 @@ slots:
|
||||
private:
|
||||
int getInstIndex(BaseInstance *inst) const;
|
||||
|
||||
void continueProcessInstance(BaseInstance *instPtr, const int error, const QDir &dir,
|
||||
QMap<QString, QString> &groupMap);
|
||||
|
||||
protected:
|
||||
QString m_instDir;
|
||||
QList<InstancePtr> m_instances;
|
||||
|
@ -172,14 +172,14 @@ JavaListLoadTask::~JavaListLoadTask()
|
||||
|
||||
void JavaListLoadTask::executeTask()
|
||||
{
|
||||
setStatus("Detecting Java installations...");
|
||||
setStatus(tr("Detecting Java installations..."));
|
||||
|
||||
JavaUtils ju;
|
||||
QList<QString> candidate_paths = ju.FindJavaPaths();
|
||||
|
||||
auto job = new JavaCheckerJob("Java detection");
|
||||
connect(job, SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
|
||||
connect(job, SIGNAL(progress(int, int)), this, SLOT(checkerProgress(int, int)));
|
||||
m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
|
||||
connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
|
||||
connect(m_job.get(), SIGNAL(progress(int, int)), this, SLOT(checkerProgress(int, int)));
|
||||
|
||||
QLOG_DEBUG() << "Probing the following Java paths: ";
|
||||
for(QString candidate : candidate_paths)
|
||||
@ -188,10 +188,10 @@ void JavaListLoadTask::executeTask()
|
||||
|
||||
auto candidate_checker = new JavaChecker();
|
||||
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)
|
||||
@ -203,6 +203,7 @@ void JavaListLoadTask::checkerProgress(int current, int total)
|
||||
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
|
||||
{
|
||||
QList<JavaVersionPtr> candidates;
|
||||
m_job.reset();
|
||||
|
||||
QLOG_DEBUG() << "Found the following valid Java installations:";
|
||||
for(JavaCheckResult result : results)
|
||||
|
@ -90,6 +90,7 @@ public slots:
|
||||
void checkerProgress(int current, int total);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<JavaCheckerJob> m_job;
|
||||
JavaVersionList *m_list;
|
||||
JavaVersion *m_currentRecommended;
|
||||
};
|
||||
|
@ -139,7 +139,7 @@ MCVListLoadTask::~MCVListLoadTask()
|
||||
|
||||
void MCVListLoadTask::executeTask()
|
||||
{
|
||||
setStatus("Loading instance version list...");
|
||||
setStatus(tr("Loading instance version list..."));
|
||||
auto worker = MMC->qnam();
|
||||
vlistReply = worker->get(QNetworkRequest(QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json")));
|
||||
connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded()));
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <QCryptographicHash>
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include "logger/QsLog.h"
|
||||
|
||||
ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : NetAction()
|
||||
@ -310,16 +311,51 @@ void ForgeXzDownload::decompressAndInstall()
|
||||
m_pack200_xz_file.remove();
|
||||
|
||||
// revert pack200
|
||||
pack200_file.close();
|
||||
QString pack_name = pack200_file.fileName();
|
||||
pack200_file.seek(0);
|
||||
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
|
||||
{
|
||||
unpack_200(pack_name.toStdString(), m_target_path.toStdString());
|
||||
unpack_200(file_in, file_out);
|
||||
}
|
||||
catch (std::runtime_error &err)
|
||||
{
|
||||
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);
|
||||
if (f.exists())
|
||||
f.remove();
|
||||
|
@ -23,7 +23,6 @@ MD5EtagDownload::MD5EtagDownload(QUrl url, QString target_path) : NetAction()
|
||||
{
|
||||
m_url = url;
|
||||
m_target_path = target_path;
|
||||
m_check_md5 = false;
|
||||
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 (m_output_file.exists() && m_output_file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
// check the md5 against the expected one
|
||||
QString hash =
|
||||
// get the md5 of the local file.
|
||||
m_local_md5 =
|
||||
QCryptographicHash::hash(m_output_file.readAll(), QCryptographicHash::Md5)
|
||||
.toHex()
|
||||
.constData();
|
||||
m_output_file.close();
|
||||
// skip this file if they match
|
||||
if (m_check_md5 && hash == m_expected_md5)
|
||||
// if we are expecting some md5sum, compare it with the local one
|
||||
if (!m_expected_md5.isEmpty())
|
||||
{
|
||||
QLOG_INFO() << "Skipping " << m_url.toString() << ": md5 match.";
|
||||
emit succeeded(m_index_within_job);
|
||||
return;
|
||||
// skip if they match
|
||||
if(m_local_md5 == m_expected_md5)
|
||||
{
|
||||
QLOG_INFO() << "Skipping " << m_url.toString() << ": md5 match.";
|
||||
emit succeeded(m_index_within_job);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_expected_md5 = hash;
|
||||
// no expected md5. we use the local md5sum as an ETag
|
||||
}
|
||||
}
|
||||
if (!ensureFilePathExists(filename))
|
||||
@ -58,9 +61,18 @@ void MD5EtagDownload::start()
|
||||
return;
|
||||
}
|
||||
|
||||
QLOG_INFO() << "Downloading " << m_url.toString() << " expecting " << m_expected_md5;
|
||||
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)");
|
||||
|
||||
// Go ahead and try to open the file.
|
||||
@ -107,7 +119,10 @@ void MD5EtagDownload::downloadFinished()
|
||||
m_status = Job_Finished;
|
||||
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();
|
||||
|
||||
m_reply.reset();
|
||||
emit succeeded(m_index_within_job);
|
||||
return;
|
||||
@ -116,6 +131,7 @@ void MD5EtagDownload::downloadFinished()
|
||||
else
|
||||
{
|
||||
m_output_file.close();
|
||||
m_output_file.remove();
|
||||
m_reply.reset();
|
||||
emit failed(m_index_within_job);
|
||||
return;
|
||||
|
@ -23,12 +23,10 @@ class MD5EtagDownload : public NetAction
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/// if true, check the md5sum against a provided md5sum
|
||||
/// 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
|
||||
/// the expected md5 checksum. Only set from outside
|
||||
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
|
||||
QString m_target_path;
|
||||
/// this is the output file, if any
|
||||
|
@ -29,4 +29,6 @@ const QString RESOURCE_BASE("resources.download.minecraft.net/");
|
||||
const QString LIBRARY_BASE("libraries.minecraft.net/");
|
||||
const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/");
|
||||
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
Loading…
Reference in New Issue
Block a user