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

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

2
.gitignore vendored
View File

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

View File

@ -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)

View File

@ -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"

View File

@ -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
View File

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

View File

@ -11,6 +11,16 @@ Check [BUILD.md](BUILD.md) for build instructions.
## Contributing
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 &copy; 2013 MultiMC Contributors

View File

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

View File

@ -8,21 +8,36 @@
int main(int argc, char **argv)
{
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;
}

View File

@ -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);

View File

@ -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

View File

@ -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)

View File

@ -1,97 +0,0 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QString>
#include "libsettings_config.h"
/**
* @file libsettings/include/keyring.h
* Access to System Keyrings
*/
/**
* @brief The Keyring class
* the System Keyring/Keychain/Wallet/Vault/etc
*/
class LIBSETTINGS_EXPORT Keyring
{
public:
/**
* @brief virtual dtor
*/
virtual ~Keyring() {};
/**
* @brief the System Keyring instance
* @return the Keyring instance
*/
static Keyring *instance();
/**
* @brief store a password in the Keyring
* @param service the service name
* @param username the account name
* @param password the password to store
* @return success
*/
virtual bool storePassword(QString service, QString username, QString password) = 0;
/**
* @brief get a password from the Keyring
* @param service the service name
* @param username the account name
* @return the password (success=!isNull())
*/
virtual QString getPassword(QString service, QString username) = 0;
/**
* @brief lookup a password
* @param service the service name
* @param username the account name
* @return wether the password is available
*/
virtual bool hasPassword(QString service, QString username) = 0;
/**
* @brief get a list of all stored accounts.
* @param service the service name
* @return
*/
virtual QStringList getStoredAccounts(QString service) = 0;
/**
* @brief Remove the specified account from storage
* @param service the service name
* @param username the account name
* @return
*/
virtual void removeStoredAccount(QString service, QString username) = 0;
protected:
/// fall back to StubKeyring if false
virtual bool isValid()
{
return false;
}
private:
static Keyring *m_instance;
static void destroy();
};

View File

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

View File

@ -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();
}

View File

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

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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();

View File

@ -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;
};

View File

@ -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)));
}

View File

@ -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;
};

View File

@ -1,63 +0,0 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "include/keyring.h"
#include "osutils.h"
#include "stubkeyring.h"
// system specific keyrings
/*#if defined(OSX)
class OSXKeychain;
#define KEYRING OSXKeychain
#elif defined(LINUX)
class XDGKeyring;
#define KEYRING XDGKeyring
#elif defined(WINDOWS)
class Win32Keystore;
#define KEYRING Win32Keystore
#else
#pragma message Keyrings are not supported on your os. Falling back to the insecure StubKeyring
#endif*/
Keyring *Keyring::instance()
{
if (m_instance == nullptr)
{
#ifdef KEYRING
m_instance = new KEYRING();
if (!m_instance->isValid())
{
qWarning("Could not create SystemKeyring! falling back to StubKeyring.");
m_instance = new StubKeyring();
}
#else
qWarning("Keyrings are not supported on your OS. Fallback StubKeyring is insecure!");
m_instance = new StubKeyring();
#endif
atexit(Keyring::destroy);
}
return m_instance;
}
void Keyring::destroy()
{
delete m_instance;
}
Keyring *Keyring::m_instance;

View File

@ -1,105 +0,0 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "stubkeyring.h"
#include <QStringList>
// Scrambling
// this is NOT SAFE, but it's not plain either.
int scrambler = 0x9586309;
QString scramble(QString in_)
{
QByteArray in = in_.toUtf8();
QByteArray out;
for (int i = 0; i < in.length(); i++)
out.append(in.at(i) ^ scrambler);
return QString::fromUtf8(out);
}
inline QString base64(QString in)
{
return QString(in.toUtf8().toBase64());
}
inline QString unbase64(QString in)
{
return QString::fromUtf8(QByteArray::fromBase64(in.toLatin1()));
}
inline QString scramble64(QString in)
{
return base64(scramble(in));
}
inline QString unscramble64(QString in)
{
return scramble(unbase64(in));
}
// StubKeyring implementation
inline QString generateKey(QString service, QString username)
{
return QString("%1/%2").arg(base64(service)).arg(scramble64(username));
}
bool StubKeyring::storePassword(QString service, QString username, QString password)
{
m_settings.setValue(generateKey(service, username), scramble64(password));
return true;
}
QString StubKeyring::getPassword(QString service, QString username)
{
QString key = generateKey(service, username);
if (!m_settings.contains(key))
return QString();
return unscramble64(m_settings.value(key).toString());
}
bool StubKeyring::hasPassword(QString service, QString username)
{
return m_settings.contains(generateKey(service, username));
}
QStringList StubKeyring::getStoredAccounts(QString service)
{
service = base64(service).append('/');
QStringList out;
QStringList in(m_settings.allKeys());
QStringListIterator it(in);
while (it.hasNext())
{
QString c = it.next();
if (c.startsWith(service))
out << unscramble64(c.mid(service.length()));
}
return out;
}
void StubKeyring::removeStoredAccount(QString service, QString username)
{
QString key = generateKey(service, username);
m_settings.remove(key);
}
// FIXME: this needs tweaking/changes for user account level storage
StubKeyring::StubKeyring()
:
// m_settings(QSettings::UserScope, "Orochimarufan", "Keyring")
m_settings("keyring.cfg", QSettings::IniFormat)
{
}

View File

@ -1,47 +0,0 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "include/keyring.h"
#include <QSettings>
class StubKeyring : public Keyring
{
public:
/**
* @brief virtual dtor
*/
virtual ~StubKeyring() {};
virtual bool storePassword(QString service, QString username, QString password);
virtual QString getPassword(QString service, QString username);
virtual bool hasPassword(QString service, QString username);
virtual QStringList getStoredAccounts(QString service);
virtual void removeStoredAccount(QString service, QString username);
private:
friend class Keyring;
explicit StubKeyring();
virtual bool isValid()
{
return true;
}
QSettings m_settings;
};

View File

@ -23,10 +23,7 @@
QString PathCombine(QString path1, QString path2)
{
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)

View File

@ -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)

View File

@ -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>

View File

@ -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 &current, 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 &current, 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 &current, 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;

View File

@ -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;

View File

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

View File

@ -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;
}
}
}

View File

@ -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"

View File

@ -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();

View File

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

View File

@ -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>

View File

@ -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);
}

View File

@ -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());

View File

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

View File

@ -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>

View File

@ -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"

View File

@ -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())

View File

@ -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();

View File

@ -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">

View File

@ -40,6 +40,12 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Se
ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name);
ui->sortingModeGroup->setId(ui->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());

View File

@ -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();

View File

@ -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>

View File

@ -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;

View File

@ -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();
}

View File

@ -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

View File

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

View File

@ -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()

View File

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

View File

@ -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:

View File

@ -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

View File

@ -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();
}
}

View File

@ -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;
}

View File

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

13
logic/LegacyFTBInstance.h Normal file
View File

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

View File

@ -27,7 +27,7 @@
#include "logic/MinecraftProcess.h"
#include "logic/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);
}

View File

@ -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))

View File

@ -0,0 +1,102 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "LiteLoaderInstaller.h"
#include "OneSixVersion.h"
#include "OneSixLibrary.h"
QMap<QString, QString> LiteLoaderInstaller::m_launcherWrapperVersionMapping;
LiteLoaderInstaller::LiteLoaderInstaller(const QString &mcVersion) : m_mcVersion(mcVersion)
{
if (m_launcherWrapperVersionMapping.isEmpty())
{
m_launcherWrapperVersionMapping["1.6.2"] = "1.3";
m_launcherWrapperVersionMapping["1.6.4"] = "1.8";
//m_launcherWrapperVersionMapping["1.7.2"] = "1.8";
//m_launcherWrapperVersionMapping["1.7.4"] = "1.8";
}
}
bool LiteLoaderInstaller::canApply() const
{
return m_launcherWrapperVersionMapping.contains(m_mcVersion);
}
bool LiteLoaderInstaller::apply(std::shared_ptr<OneSixVersion> to)
{
to->externalUpdateStart();
applyLaunchwrapper(to);
applyLiteLoader(to);
to->mainClass = "net.minecraft.launchwrapper.Launch";
if (!to->minecraftArguments.contains(
" --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker"))
{
to->minecraftArguments.append(
" --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker");
}
to->externalUpdateFinish();
return to->toOriginalFile();
}
void LiteLoaderInstaller::applyLaunchwrapper(std::shared_ptr<OneSixVersion> to)
{
const QString intendedVersion = m_launcherWrapperVersionMapping[m_mcVersion];
QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries);
while (it.hasNext())
{
it.next();
if (it.value()->rawName().startsWith("net.minecraft:launchwrapper:"))
{
if (it.value()->version() >= intendedVersion)
{
return;
}
else
{
it.remove();
}
}
}
std::shared_ptr<OneSixLibrary> lib(new OneSixLibrary(
"net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[m_mcVersion]));
lib->finalize();
to->libraries.prepend(lib);
}
void LiteLoaderInstaller::applyLiteLoader(std::shared_ptr<OneSixVersion> to)
{
QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries);
while (it.hasNext())
{
it.next();
if (it.value()->rawName().startsWith("com.mumfrey:liteloader:"))
{
it.remove();
}
}
std::shared_ptr<OneSixLibrary> lib(
new OneSixLibrary("com.mumfrey:liteloader:" + m_mcVersion));
lib->setBaseUrl("http://dl.liteloader.com/versions/");
lib->finalize();
to->libraries.prepend(lib);
}

View File

@ -14,29 +14,26 @@
*/
#pragma once
#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;
};

View File

@ -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();
}

View File

@ -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;

View File

@ -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();

View File

@ -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
View File

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

20
logic/OneSixFTBInstance.h Normal file
View File

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

View File

@ -33,8 +33,8 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_o
: BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent)
{
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();
}

View File

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

View File

@ -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;

View File

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

View File

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

View File

@ -25,23 +25,18 @@
namespace AssetsUtils
{
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;
}
/*

View File

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

View File

@ -32,7 +32,8 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
// The JSON object must at least have a username for it to be valid.
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();

View File

@ -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()
{

View File

@ -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...");
}
}

View File

@ -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:

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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
View File

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

View File

@ -17,15 +17,21 @@
#include <QMutex>
#include <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
View File

@ -0,0 +1,89 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MMCIcon.h"
#include <QFileInfo>
MMCIcon::Type operator--(MMCIcon::Type &t, int)
{
MMCIcon::Type temp = t;
switch (t)
{
case MMCIcon::Type::Builtin:
t = MMCIcon::Type::ToBeDeleted;
break;
case MMCIcon::Type::Transient:
t = MMCIcon::Type::Builtin;
break;
case MMCIcon::Type::FileBased:
t = MMCIcon::Type::Transient;
break;
default:
{
}
}
return temp;
}
MMCIcon::Type MMCIcon::type() const
{
return m_current_type;
}
QString MMCIcon::name() const
{
if (m_name.size())
return m_name;
return m_key;
}
bool MMCIcon::has(MMCIcon::Type _type) const
{
return m_images[_type].present();
}
QIcon MMCIcon::icon() const
{
if (m_current_type == Type::ToBeDeleted)
return QIcon();
return m_images[m_current_type].icon;
}
void MMCIcon::remove(Type rm_type)
{
m_images[rm_type].filename = QString();
m_images[rm_type].icon = QIcon();
for (auto iter = rm_type; iter != Type::ToBeDeleted; iter--)
{
if (m_images[iter].present())
{
m_current_type = iter;
return;
}
}
m_current_type = Type::ToBeDeleted;
}
void MMCIcon::replace(MMCIcon::Type new_type, QIcon icon, QString path)
{
QFileInfo foo(path);
if (new_type > m_current_type || m_current_type == MMCIcon::ToBeDeleted)
{
m_current_type = new_type;
}
m_images[new_type].icon = icon;
m_images[new_type].changed = foo.lastModified();
m_images[new_type].filename = path;
}

View File

@ -13,32 +13,40 @@
* limitations under the License.
*/
#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());
};

View File

@ -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.";
}
}

View File

@ -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);
};

View File

@ -1,271 +0,0 @@
/* Copyright 2013 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "IconList.h"
#include <pathutils.h>
#include <QMap>
#include <QEventLoop>
#include <QDir>
#include <QMimeData>
#include <QUrl>
#define MAX_SIZE 1024
struct entry
{
QString key;
QString name;
QIcon icon;
bool is_builtin;
QString filename;
};
class Private : public QObject
{
Q_OBJECT
public:
QMap<QString, int> index;
QVector<entry> icons;
Private()
{
}
};
IconList::IconList() : QAbstractListModel(), d(new Private())
{
QDir instance_icons(":/icons/instances/");
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list)
{
QString key = file_info.baseName();
addIcon(key, key, file_info.absoluteFilePath(), true);
}
// FIXME: get from settings
ensureFolderPathExists("icons");
QDir user_icons("icons");
file_info_list = user_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list)
{
QString filename = file_info.absoluteFilePath();
QString key = file_info.baseName();
addIcon(key, key, filename);
}
}
IconList::~IconList()
{
delete d;
d = nullptr;
}
QStringList IconList::mimeTypes() const
{
QStringList types;
types << "text/uri-list";
return types;
}
Qt::DropActions IconList::supportedDropActions() const
{
return Qt::CopyAction;
}
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
// check if the action is supported
if (!data || !(action & supportedDropActions()))
return false;
// files dropped from outside?
if (data->hasUrls())
{
/*
bool was_watching = is_watching;
if(was_watching)
stopWatching();
*/
auto urls = data->urls();
QStringList iconFiles;
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
continue;
iconFiles += url.toLocalFile();
}
installIcons(iconFiles);
/*
if(was_watching)
startWatching();
*/
return true;
}
return false;
}
Qt::ItemFlags IconList::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
QVariant IconList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
if (row < 0 || row >= d->icons.size())
return QVariant();
switch (role)
{
case Qt::DecorationRole:
return d->icons[row].icon;
case Qt::DisplayRole:
return d->icons[row].name;
case Qt::UserRole:
return d->icons[row].key;
default:
return QVariant();
}
}
int IconList::rowCount(const QModelIndex &parent) const
{
return d->icons.size();
}
void IconList::installIcons(QStringList iconFiles)
{
for (QString file : iconFiles)
{
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
QString target = PathCombine("icons", fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg")
continue;
if (!QFile::copy(file, target))
continue;
QString key = fileinfo.baseName();
addIcon(key, key, target);
}
}
bool IconList::deleteIcon(QString key)
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
return false;
auto &iconEntry = d->icons[iconIdx];
if (iconEntry.is_builtin)
return false;
if (QFile::remove(iconEntry.filename))
{
beginRemoveRows(QModelIndex(), iconIdx, iconIdx);
d->icons.remove(iconIdx);
reindex();
endRemoveRows();
}
return true;
}
bool IconList::addIcon(QString key, QString name, QString path, bool is_builtin)
{
auto iter = d->index.find(key);
if (iter != d->index.end())
{
if (d->icons[*iter].is_builtin)
return false;
QIcon icon(path);
if (icon.isNull())
return false;
auto &oldOne = d->icons[*iter];
if (!QFile::remove(oldOne.filename))
return false;
// replace the icon
oldOne = {key, name, icon, is_builtin, path};
dataChanged(index(*iter), index(*iter));
return true;
}
else
{
QIcon icon(path);
if (icon.isNull())
return false;
// add a new icon
beginInsertRows(QModelIndex(), d->icons.size(), d->icons.size());
d->icons.push_back({key, name, icon, is_builtin, path});
d->index[key] = d->icons.size() - 1;
endInsertRows();
return true;
}
}
void IconList::reindex()
{
d->index.clear();
int i = 0;
for (auto &iter : d->icons)
{
d->index[iter.key] = i;
i++;
}
}
QIcon IconList::getIcon(QString key)
{
int icon_index = getIconIndex(key);
if (icon_index != -1)
return d->icons[icon_index].icon;
// Fallback for icons that don't exist.
icon_index = getIconIndex("infinity");
if (icon_index != -1)
return d->icons[icon_index].icon;
return QIcon();
}
int IconList::getIconIndex(QString key)
{
if (key == "default")
key = "infinity";
auto iter = d->index.find(key);
if (iter != d->index.end())
return *iter;
return -1;
}
#include "IconList.moc"

View File

@ -22,11 +22,14 @@
#include <QJsonDocument>
#include <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);

View File

@ -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;

View File

@ -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)

View File

@ -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;
};

View File

@ -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()));

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -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