diff options
Diffstat (limited to 'logic')
32 files changed, 868 insertions, 393 deletions
diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index ac66a8d5..222004a3 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -26,6 +26,7 @@ #include "overridesetting.h" #include "pathutils.h" +#include <cmdutils.h> #include "lists/MinecraftVersionList.h" #include "logic/icons/IconList.h" @@ -81,6 +82,7 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, settings().registerSetting("OverrideConsole", false); settings().registerOverride(globalSettings->getSetting("ShowConsole")); settings().registerOverride(globalSettings->getSetting("AutoCloseConsole")); + settings().registerOverride(globalSettings->getSetting("LogPrePostOutput")); } void BaseInstance::iconUpdated(QString key) @@ -248,8 +250,19 @@ void BaseInstance::setName(QString val) d->m_settings->set("name", val); emit propertiesChanged(this); } + QString BaseInstance::name() const { I_D(BaseInstance); return d->m_settings->get("name").toString(); } + +QString BaseInstance::windowTitle() const +{ + return "MultiMC: " + name(); +} + +QStringList BaseInstance::extraArguments() const +{ + return Util::Commandline::splitArgs(settings().get("JvmArgs").toString()); +} diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 01d6dc7d..a861e9b2 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -57,7 +57,7 @@ public: /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to /// be unique. - QString id() const; + virtual QString id() const; /// get the type of this instance QString instanceType() const; @@ -71,6 +71,9 @@ public: QString name() const; void setName(QString val); + /// Value used for instance window titles + QString windowTitle() const; + QString iconKey() const; void setIconKey(QString val); @@ -81,6 +84,8 @@ public: void setGroupInitial(QString val); void setGroupPost(QString val); + QStringList extraArguments() const; + virtual QString intendedVersionId() const = 0; virtual bool setIntendedVersionId(QString version) = 0; diff --git a/logic/BaseVersion.h b/logic/BaseVersion.h index 1864c94c..43f5942a 100644 --- a/logic/BaseVersion.h +++ b/logic/BaseVersion.h @@ -39,6 +39,15 @@ struct BaseVersion * the kind of version this is (Stable, Beta, Snapshot, whatever) */ virtual QString typeString() const = 0; + + virtual bool operator<(BaseVersion &a) + { + return name() < a.name(); + }; + virtual bool operator>(BaseVersion &a) + { + return name() > a.name(); + }; }; typedef std::shared_ptr<BaseVersion> BaseVersionPtr; diff --git a/logic/JavaChecker.cpp b/logic/JavaChecker.cpp index 6ee7b4cf..b87ee3d5 100644 --- a/logic/JavaChecker.cpp +++ b/logic/JavaChecker.cpp @@ -20,6 +20,9 @@ void JavaChecker::performCheck() process->setArguments(args); process->setProgram(path); process->setProcessChannelMode(QProcess::SeparateChannels); + QLOG_DEBUG() << "Running java checker!"; + QLOG_DEBUG() << "Java: " + path; + QLOG_DEBUG() << "Args: {" + args.join("|") + "}"; connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); @@ -42,15 +45,19 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) result.path = path; result.id = id; } + QLOG_DEBUG() << "Java checker finished with status " << status << " exit code " << exitcode; if (status == QProcess::CrashExit || exitcode == 1) { + QLOG_DEBUG() << "Java checker failed!"; emit checkFinished(result); return; } bool success = true; QString p_stdout = _process->readAllStandardOutput(); + QLOG_DEBUG() << p_stdout; + QMap<QString, QString> results; QStringList lines = p_stdout.split("\n", QString::SkipEmptyParts); for(QString line : lines) @@ -70,6 +77,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) if(!results.contains("os.arch") || !results.contains("java.version") || !success) { + QLOG_DEBUG() << "Java checker failed - couldn't extract required information."; emit checkFinished(result); return; } @@ -84,7 +92,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) result.mojangPlatform = is_64 ? "64" : "32"; result.realPlatform = os_arch; result.javaVersion = java_version; - + QLOG_DEBUG() << "Java checker succeeded."; emit checkFinished(result); } @@ -93,7 +101,7 @@ void JavaChecker::error(QProcess::ProcessError err) if(err == QProcess::FailedToStart) { killTimer.stop(); - + QLOG_DEBUG() << "Java checker has failed to start."; JavaCheckResult result; { result.path = path; @@ -110,6 +118,7 @@ void JavaChecker::timeout() // NO MERCY. NO ABUSE. if(process) { + QLOG_DEBUG() << "Java checker has been killed by timeout."; process->kill(); } } diff --git a/logic/LegacyFTBInstance.cpp b/logic/LegacyFTBInstance.cpp index 84d5a900..6c6bd10b 100644 --- a/logic/LegacyFTBInstance.cpp +++ b/logic/LegacyFTBInstance.cpp @@ -14,3 +14,8 @@ bool LegacyFTBInstance::menuActionEnabled(QString action_name) const { return false; } + +QString LegacyFTBInstance::id() const +{ + return "FTB/" + BaseInstance::id(); +} diff --git a/logic/LegacyFTBInstance.h b/logic/LegacyFTBInstance.h index 2ae72797..70f60535 100644 --- a/logic/LegacyFTBInstance.h +++ b/logic/LegacyFTBInstance.h @@ -10,4 +10,5 @@ public: QObject *parent = 0); virtual QString getStatusbarDescription(); virtual bool menuActionEnabled(QString action_name) const; + virtual QString id() const; }; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 5ab19fc9..2828bcbf 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -47,7 +47,7 @@ std::shared_ptr<Task> LegacyInstance::doUpdate(bool only_prepare) // make sure the jar mods list is initialized by asking for it. auto list = jarModList(); // create an update task - return std::shared_ptr<Task> (new LegacyUpdate(this, only_prepare , this)); + return std::shared_ptr<Task>(new LegacyUpdate(this, only_prepare, this)); } MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account) @@ -58,58 +58,27 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account) auto pixmap = icon.pixmap(128, 128); pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); - // extract the legacy launcher - QString launcherJar = PathCombine(MMC->bin(), "jars", "MultiMCLauncher.jar"); - - // set the process arguments + // create the launch script + QString launchScript; { - QStringList args; - // window size - QString windowSize; + QString windowParams; if (settings().get("LaunchMaximized").toBool()) - windowSize = "max"; + windowParams = "max"; else - windowSize = QString("%1x%2").arg(settings().get("MinecraftWinWidth").toInt()).arg( + windowParams = QString("%1x%2").arg(settings().get("MinecraftWinWidth").toInt()).arg( settings().get("MinecraftWinHeight").toInt()); - // window title - QString windowTitle; - windowTitle.append("MultiMC: ").append(name()); - - // Java arguments - args.append(Util::Commandline::splitArgs(settings().get("JvmArgs").toString())); - -#ifdef OSX - // OSX dock icon and name - args << "-Xdock:icon=icon.png"; - args << QString("-Xdock:name=\"%1\"").arg(windowTitle); -#endif - QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion()) .absolutePath(); - - // launcher arguments - args << QString("-Xms%1m").arg(settings().get("MinMemAlloc").toInt()); - args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt()); - args << QString("-XX:PermSize=%1m").arg(settings().get("PermGen").toInt()); -/** -* HACK: Stupid hack for Intel drivers. -* See: https://mojang.atlassian.net/browse/MCL-767 -*/ -#ifdef Q_OS_WIN32 - args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" - "minecraft.exe.heapdump"); -#endif - - args << "-jar" << launcherJar; - args << account->currentProfile()->name; - args << account->sessionId(); - args << windowTitle; - args << windowSize; - args << lwjgl; - proc->setArguments(args); + launchScript += "userName " + account->currentProfile()->name + "\n"; + launchScript += "sessionId " + account->sessionId() + "\n"; + launchScript += "windowTitle " + windowTitle() + "\n"; + launchScript += "windowParams " + windowParams + "\n"; + launchScript += "lwjgl " + lwjgl + "\n"; + launchScript += "launch legacy\n"; } + proc->setLaunchScript(launchScript); // set the process work path proc->setWorkdir(minecraftRoot()); diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index 209929b7..84610021 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -14,14 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "MultiMC.h" #include "MinecraftProcess.h" #include <QDataStream> #include <QFile> #include <QDir> -//#include <QImage> #include <QProcessEnvironment> +#include <QRegularExpression> #include "BaseInstance.h" @@ -42,6 +43,7 @@ MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst) #ifdef LINUX // Strip IBus + // IBus is a Linux IME framework. For some reason, it breaks MC? if (env.value("XMODIFIERS").contains(IBUS)) env.insert("XMODIFIERS", env.value("XMODIFIERS").replace(IBUS, "")); #endif @@ -57,11 +59,15 @@ MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst) // std channels connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr())); connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut())); -} -void MinecraftProcess::setArguments(QStringList args) -{ - m_args = args; + // Log prepost launch command output (can be disabled.) + if (m_instance->settings().get("LogPrePostOutput").toBool()) + { + connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardError, + this, &MinecraftProcess::on_prepost_stdErr); + connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput, + this, &MinecraftProcess::on_prepost_stdOut); + } } void MinecraftProcess::setWorkdir(QString path) @@ -90,47 +96,145 @@ QString MinecraftProcess::censorPrivateInfo(QString in) in.replace(profileId, "<PROFILE ID>"); in.replace(profileName, "<PROFILE NAME>"); } + + auto i = m_account->user().properties.begin(); + while (i != m_account->user().properties.end()) + { + in.replace(i.value(), "<" + i.key().toUpper() + ">"); + ++i; + } + return in; } // console window +MessageLevel::Enum MinecraftProcess::guessLevel(const QString &line, MessageLevel::Enum level) +{ + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || + line.contains("[FINER]") || line.contains("[FINEST]")) + level = MessageLevel::Message; + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + level = MessageLevel::Error; + if (line.contains("[WARNING]")) + level = MessageLevel::Warning; + if (line.contains("Exception in thread") || line.contains(" at ")) + level = MessageLevel::Fatal; + if (line.contains("[DEBUG]")) + level = MessageLevel::Debug; + return level; +} + +MessageLevel::Enum MinecraftProcess::getLevel(const QString &levelName) +{ + if (levelName == "MultiMC") + return MessageLevel::MultiMC; + else if (levelName == "Debug") + return MessageLevel::Debug; + else if (levelName == "Info") + return MessageLevel::Info; + else if (levelName == "Message") + return MessageLevel::Message; + else if (levelName == "Warning") + return MessageLevel::Warning; + else if (levelName == "Error") + return MessageLevel::Error; + else if (levelName == "Fatal") + return MessageLevel::Fatal; + // Skip PrePost, it's not exposed to !![]! + else + return MessageLevel::Message; +} + +void MinecraftProcess::logOutput(const QStringList &lines, + MessageLevel::Enum defaultLevel, + bool guessLevel, bool censor) +{ + for (int i = 0; i < lines.size(); ++i) + logOutput(lines[i], defaultLevel, guessLevel, censor); +} + +void MinecraftProcess::logOutput(QString line, + MessageLevel::Enum defaultLevel, + bool guessLevel, bool censor) +{ + MessageLevel::Enum level = defaultLevel; + + // Level prefix + int endmark = line.indexOf("]!"); + if (line.startsWith("!![") && endmark != -1) + { + level = getLevel(line.left(endmark).mid(3)); + line = line.mid(endmark + 2); + } + // Guess level + else if (guessLevel) + level = this->guessLevel(line, defaultLevel); + + if (censor) + line = censorPrivateInfo(line); + + emit log(line, level); +} + void MinecraftProcess::on_stdErr() { QByteArray data = readAllStandardError(); QString str = m_err_leftover + QString::fromLocal8Bit(data); - m_err_leftover.clear(); + QStringList lines = str.split("\n"); - bool complete = str.endsWith("\n"); + m_err_leftover = lines.takeLast(); - for (int i = 0; i < lines.size() - 1; i++) - { - QString &line = lines[i]; - emit log(censorPrivateInfo(line), getLevel(line, MessageLevel::Error)); - } - if (!complete) - m_err_leftover = lines.last(); + logOutput(lines, MessageLevel::Error); } void MinecraftProcess::on_stdOut() { QByteArray data = readAllStandardOutput(); QString str = m_out_leftover + QString::fromLocal8Bit(data); - m_out_leftover.clear(); + QStringList lines = str.split("\n"); - bool complete = str.endsWith("\n"); + m_out_leftover = lines.takeLast(); - for (int i = 0; i < lines.size() - 1; i++) - { - QString &line = lines[i]; - emit log(censorPrivateInfo(line), getLevel(line, MessageLevel::Message)); - } - if (!complete) - m_out_leftover = lines.last(); + logOutput(lines); +} + +void MinecraftProcess::on_prepost_stdErr() +{ + QByteArray data = m_prepostlaunchprocess.readAllStandardError(); + QString str = m_err_leftover + QString::fromLocal8Bit(data); + + QStringList lines = str.split("\n"); + m_err_leftover = lines.takeLast(); + + logOutput(lines, MessageLevel::PrePost, false, false); +} + +void MinecraftProcess::on_prepost_stdOut() +{ + QByteArray data = m_prepostlaunchprocess.readAllStandardOutput(); + QString str = m_out_leftover + QString::fromLocal8Bit(data); + + QStringList lines = str.split("\n"); + m_out_leftover = lines.takeLast(); + + logOutput(lines, MessageLevel::PrePost, false, false); } // exit handler void MinecraftProcess::finish(int code, ExitStatus status) { + // Flush console window + if (!m_err_leftover.isEmpty()) + { + logOutput(m_err_leftover, MessageLevel::Error); + m_err_leftover.clear(); + } + if (!m_out_leftover.isEmpty()) + { + logOutput(m_out_leftover); + m_out_leftover.clear(); + } + if (!killed) { if (status == NormalExit) @@ -153,15 +257,32 @@ void MinecraftProcess::finish(int code, ExitStatus status) m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); // run post-exit - if (!m_instance->settings().get("PostExitCommand").toString().isEmpty()) + QString postlaunch_cmd = m_instance->settings().get("PostExitCommand").toString(); + if (!postlaunch_cmd.isEmpty()) { - m_prepostlaunchprocess.start(m_instance->settings().get("PostExitCommand").toString()); + emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd)); + m_prepostlaunchprocess.start(postlaunch_cmd); m_prepostlaunchprocess.waitForFinished(); + // Flush console window + if (!m_err_leftover.isEmpty()) + { + logOutput(m_err_leftover, MessageLevel::PrePost); + m_err_leftover.clear(); + } + if (!m_out_leftover.isEmpty()) + { + logOutput(m_out_leftover, MessageLevel::PrePost); + m_out_leftover.clear(); + } if (m_prepostlaunchprocess.exitStatus() != NormalExit) { + emit log(tr("Post-Launch command failed with code %1.\n\n").arg(m_prepostlaunchprocess.exitCode()), + MessageLevel::Error); emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), m_prepostlaunchprocess.exitStatus()); } + else + emit log(tr("Post-Launch command ran successfully.\n\n")); } m_instance->cleanupAfterRun(); emit ended(m_instance, code, status); @@ -175,27 +296,78 @@ void MinecraftProcess::killMinecraft() void MinecraftProcess::launch() { - if (!m_instance->settings().get("PreLaunchCommand").toString().isEmpty()) + emit log("MultiMC version: " + MMC->version().toString() + "\n\n"); + emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n"); + + QString prelaunch_cmd = m_instance->settings().get("PreLaunchCommand").toString(); + if (!prelaunch_cmd.isEmpty()) { - m_prepostlaunchprocess.start(m_instance->settings().get("PreLaunchCommand").toString()); + // Launch + emit log(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd)); + m_prepostlaunchprocess.start(prelaunch_cmd); + // Wait m_prepostlaunchprocess.waitForFinished(); + // Flush console window + if (!m_err_leftover.isEmpty()) + { + logOutput(m_err_leftover, MessageLevel::PrePost); + m_err_leftover.clear(); + } + if (!m_out_leftover.isEmpty()) + { + logOutput(m_out_leftover, MessageLevel::PrePost); + m_out_leftover.clear(); + } + // Process return values if (m_prepostlaunchprocess.exitStatus() != NormalExit) { + emit log(tr("Pre-Launch command failed with code %1.\n\n").arg(m_prepostlaunchprocess.exitCode()), + MessageLevel::Fatal); m_instance->cleanupAfterRun(); emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), m_prepostlaunchprocess.exitStatus()); return; } + else + emit log(tr("Pre-Launch command ran successfully.\n\n")); } m_instance->setLastLaunch(); + auto &settings = m_instance->settings(); + + //////////// java arguments //////////// + QStringList args; + { + // custom args go first. we want to override them if we have our own here. + args.append(m_instance->extraArguments()); + + // OSX dock icon and name + #ifdef OSX + args << "-Xdock:icon=icon.png"; + args << QString("-Xdock:name=\"%1\"").arg(m_instance->windowTitle()); + #endif + + // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 + #ifdef Q_OS_WIN32 + args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" + "minecraft.exe.heapdump"); + #endif + + args << QString("-Xms%1m").arg(settings.get("MinMemAlloc").toInt()); + args << QString("-Xmx%1m").arg(settings.get("MaxMemAlloc").toInt()); + args << QString("-XX:PermSize=%1m").arg(settings.get("PermGen").toInt()); + if(!m_nativeFolder.isEmpty()) + args << QString("-Djava.library.path=%1").arg(m_nativeFolder); + args << "-jar" << PathCombine(MMC->bin(), "jars", "NewLaunch.jar"); + } - emit log(QString("Minecraft folder is: '%1'").arg(workingDirectory())); QString JavaPath = m_instance->settings().get("JavaPath").toString(); - emit log(QString("Java path: '%1'").arg(JavaPath)); - QString allArgs = m_args.join("' '"); - emit log(QString("Arguments: '%1'").arg(censorPrivateInfo(allArgs))); - start(JavaPath, m_args); + emit log("Java path is:\n" + JavaPath + "\n\n"); + QString allArgs = args.join(", "); + emit log("Java Arguments:\n[" + censorPrivateInfo(allArgs) + "]\n\n"); + + // instantiate the launcher part + start(JavaPath, args); if (!waitForStarted()) { //: Error message displayed if instace can't start @@ -204,21 +376,7 @@ void MinecraftProcess::launch() emit launch_failed(m_instance); return; } -} - -MessageLevel::Enum MinecraftProcess::getLevel(const QString &line, MessageLevel::Enum level) -{ - - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || - line.contains("[FINER]") || line.contains("[FINEST]")) - level = MessageLevel::Message; - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - if (line.contains("[WARNING]")) - level = MessageLevel::Warning; - if (line.contains("Exception in thread") || line.contains(" at ")) - level = MessageLevel::Fatal; - if (line.contains("[DEBUG]")) - level = MessageLevel::Debug; - return level; + // send the launch script to the launcher part + QByteArray bytes = launchScript.toUtf8(); + writeData(bytes.constData(), bytes.length()); } diff --git a/logic/MinecraftProcess.h b/logic/MinecraftProcess.h index bd0151cc..70e5df52 100644 --- a/logic/MinecraftProcess.h +++ b/logic/MinecraftProcess.h @@ -18,7 +18,7 @@ #pragma once #include <QProcess> - +#include <QString> #include "BaseInstance.h" /** @@ -31,11 +31,12 @@ enum Enum { MultiMC, /**< MultiMC Messages */ Debug, /**< Debug Messages */ - Info, /**< Info Messages */ + Info, /**< Info Messages */ Message, /**< Standard Messages */ Warning, /**< Warnings */ Error, /**< Errors */ - Fatal /**< Fatal Errors */ + Fatal, /**< Fatal Errors */ + PrePost, /**< Pre/Post Launch command output */ }; } @@ -65,7 +66,15 @@ public: void setWorkdir(QString path); - void setArguments(QStringList args); + void setLaunchScript(QString script) + { + launchScript = script; + } + + void setNativeFolder(QString natives) + { + m_nativeFolder = natives; + } void killMinecraft(); @@ -104,21 +113,30 @@ signals: protected: BaseInstance *m_instance = nullptr; - QStringList m_args; QString m_err_leftover; QString m_out_leftover; QProcess m_prepostlaunchprocess; bool killed = false; MojangAccountPtr m_account; + QString launchScript; + QString m_nativeFolder; protected slots: void finish(int, QProcess::ExitStatus status); void on_stdErr(); void on_stdOut(); + void on_prepost_stdOut(); + void on_prepost_stdErr(); + void logOutput(const QStringList &lines, + MessageLevel::Enum defaultLevel = MessageLevel::Message, + bool guessLevel = true, bool censor = true); + void logOutput(QString line, + MessageLevel::Enum defaultLevel = MessageLevel::Message, + bool guessLevel = true, bool censor = true); private: QString censorPrivateInfo(QString in); - MessageLevel::Enum getLevel(const QString &message, MessageLevel::Enum defaultLevel); - + MessageLevel::Enum guessLevel(const QString &message, MessageLevel::Enum defaultLevel); + MessageLevel::Enum getLevel(const QString &levelName); }; diff --git a/logic/NagUtils.cpp b/logic/NagUtils.cpp index 6f81b3c7..c963a98a 100644 --- a/logic/NagUtils.cpp +++ b/logic/NagUtils.cpp @@ -23,15 +23,15 @@ void checkJVMArgs(QString jvmargs, QWidget *parent) if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegExp("-Xm[sx]"))) { CustomMessageBox::selectable( - parent, parent->tr("JVM arguments warning"), - parent->tr("You tried to manually set a JVM memory option (using " - " \"-XX:PermSize\", \"-Xmx\" or \"-Xms\") - there" - " are dedicated boxes for these in the settings (Java" - " tab, in the Memory group at the top).\n" - "Your manual settings will be overridden by the" - " dedicated options.\n" - "This message will be displayed until you remove them" - " from the JVM arguments."), + parent, QObject::tr("JVM arguments warning"), + QObject::tr("You tried to manually set a JVM memory option (using " + " \"-XX:PermSize\", \"-Xmx\" or \"-Xms\") - there" + " are dedicated boxes for these in the settings (Java" + " tab, in the Memory group at the top).\n" + "Your manual settings will be overridden by the" + " dedicated options.\n" + "This message will be displayed until you remove them" + " from the JVM arguments."), QMessageBox::Warning)->exec(); } } diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp index 4bb5cf42..e50a5b53 100644 --- a/logic/OneSixFTBInstance.cpp +++ b/logic/OneSixFTBInstance.cpp @@ -92,6 +92,11 @@ OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *set } } +QString OneSixFTBInstance::id() const +{ + return "FTB/" + BaseInstance::id(); +} + QString OneSixFTBInstance::getStatusbarDescription() { return "OneSix FTB: " + intendedVersionId(); diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h index 7600090c..dc028819 100644 --- a/logic/OneSixFTBInstance.h +++ b/logic/OneSixFTBInstance.h @@ -15,6 +15,8 @@ public: virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override; + virtual QString id() const; + private: std::shared_ptr<OneSixLibrary> m_forge; }; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 2392c683..3cfc1c76 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -13,12 +13,14 @@ * limitations under the License. */ +#include "MultiMC.h" #include "OneSixInstance.h" #include "OneSixInstance_p.h" #include "OneSixUpdate.h" #include "MinecraftProcess.h" #include "OneSixVersion.h" #include "JavaChecker.h" +#include "logic/icons/IconList.h" #include <setting.h> #include <pathutils.h> @@ -27,6 +29,7 @@ #include "gui/dialogs/OneSixModEditDialog.h" #include "logger/QsLog.h" #include "logic/assets/AssetsUtils.h" +#include <QIcon> OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_obj, QObject *parent) @@ -40,7 +43,7 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_o std::shared_ptr<Task> OneSixInstance::doUpdate(bool only_prepare) { - return std::shared_ptr<Task> (new OneSixUpdate(this, only_prepare)); + return std::shared_ptr<Task>(new OneSixUpdate(this, only_prepare)); } QString replaceTokensIn(QString text, QMap<QString, QString> with) @@ -78,47 +81,50 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<OneSixVersion> version) QFile indexFile(indexPath); QDir virtualRoot(PathCombine(virtualDir.path(), version->assets)); - if(!indexFile.exists()) + if (!indexFile.exists()) { QLOG_ERROR() << "No assets index file" << indexPath << "; can't reconstruct assets"; return virtualRoot; } - QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path(); + QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() + << objectDir.path() << virtualDir.path() << virtualRoot.path(); AssetsIndex index; bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index); - if(loadAssetsIndex) + if (loadAssetsIndex && index.isVirtual) { - if(index.isVirtual) - { - QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path(); + QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path(); - for(QString map : index.objects.keys()) + for (QString map : index.objects.keys()) + { + AssetObject asset_object = index.objects.value(map); + QString target_path = PathCombine(virtualRoot.path(), map); + QFile target(target_path); + + QString tlk = asset_object.hash.left(2); + + QString original_path = + PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); + QFile original(original_path); + if(!original.exists()) + continue; + if (!target.exists()) { - AssetObject asset_object = index.objects.value(map); - QString target_path = PathCombine(virtualRoot.path(), map); - QFile target(target_path); - - QString tlk = asset_object.hash.left(2); - - QString original_path = PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); - QFile original(original_path); - if(!target.exists()) - { - QFileInfo info(target_path); - QDir target_dir = info.dir(); - //QLOG_DEBUG() << target_dir; - if(!target_dir.exists()) QDir("").mkpath(target_dir.path()); - - bool couldCopy = original.copy(target_path); - QLOG_DEBUG() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy);// << original.errorString(); - } + QFileInfo info(target_path); + QDir target_dir = info.dir(); + // QLOG_DEBUG() << target_dir; + if (!target_dir.exists()) + QDir("").mkpath(target_dir.path()); + + bool couldCopy = original.copy(target_path); + QLOG_DEBUG() << " Copying" << original_path << "to" << target_path + << QString::number(couldCopy); // << original.errorString(); } - - // TODO: Write last used time to virtualRoot/.lastused } + + // TODO: Write last used time to virtualRoot/.lastused } return virtualRoot; @@ -155,7 +161,7 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) auto user = account->user(); QJsonObject userAttrs; - for(auto key: user.properties.keys()) + for (auto key : user.properties.keys()) { auto array = QJsonArray::fromStringList(user.properties.values(key)); userAttrs.insert(key, array); @@ -180,71 +186,56 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account) { I_D(OneSixInstance); - QString natives_dir_raw = PathCombine(instanceRoot(), "natives/"); + QIcon icon = MMC->icons()->getIcon(iconKey()); + auto pixmap = icon.pixmap(128, 128); + pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); auto version = d->version; if (!version) return nullptr; - - QStringList args; - args.append(Util::Commandline::splitArgs(settings().get("JvmArgs").toString())); - args << QString("-Xms%1m").arg(settings().get("MinMemAlloc").toInt()); - args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt()); - args << QString("-XX:PermSize=%1m").arg(settings().get("PermGen").toInt()); - -/** - * HACK: Stupid hack for Intel drivers. - * See: https://mojang.atlassian.net/browse/MCL-767 - */ -#ifdef Q_OS_WIN32 - args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" - "minecraft.exe.heapdump"); -#endif - - QDir natives_dir(natives_dir_raw); - args << QString("-Djava.library.path=%1").arg(natives_dir.absolutePath()); - QString classPath; + QString launchScript; { auto libs = version->getActiveNormalLibs(); for (auto lib : libs) { QFileInfo fi(QString("libraries/") + lib->storagePath()); - classPath.append(fi.absoluteFilePath()); -#ifdef Q_OS_WIN32 - classPath.append(';'); -#else - classPath.append(':'); -#endif + launchScript += "cp " + fi.absoluteFilePath() + "\n"; } QString targetstr = "versions/" + version->id + "/" + version->id + ".jar"; QFileInfo fi(targetstr); - classPath.append(fi.absoluteFilePath()); + launchScript += "cp " + fi.absoluteFilePath() + "\n"; } - if (classPath.size()) + launchScript += "mainClass " + version->mainClass + "\n"; + + for (auto param : processMinecraftArgs(account)) { - args << "-cp"; - args << classPath; + launchScript += "param " + param + "\n"; } - args << version->mainClass; - args.append(processMinecraftArgs(account)); // Set the width and height for 1.6 instances bool maximize = settings().get("LaunchMaximized").toBool(); if (maximize) { // this is probably a BAD idea - // args << QString("--fullscreen"); + // launchScript += "param --fullscreen\n"; } else { - args << QString("--width") << settings().get("MinecraftWinWidth").toString(); - args << QString("--height") << settings().get("MinecraftWinHeight").toString(); + launchScript += + "param --width\nparam " + settings().get("MinecraftWinWidth").toString() + "\n"; + launchScript += + "param --height\nparam " + settings().get("MinecraftWinHeight").toString() + "\n"; } + QDir natives_dir(PathCombine(instanceRoot(), "natives/")); + launchScript += "windowTitle " + windowTitle() + "\n"; + launchScript += "natives " + natives_dir.absolutePath() + "\n"; + launchScript += "launch onesix\n"; // create the process and set its parameters MinecraftProcess *proc = new MinecraftProcess(this); - proc->setArguments(args); proc->setWorkdir(minecraftRoot()); + proc->setLaunchScript(launchScript); + // proc->setNativeFolder(natives_dir.absolutePath()); return proc; } diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp index 4b6ed9dc..d1eceb0e 100644 --- a/logic/OneSixLibrary.cpp +++ b/logic/OneSixLibrary.cpp @@ -19,6 +19,9 @@ #include "OneSixRule.h" #include "OpSys.h" #include "logic/net/URLConstants.h" +#include <pathutils.h> +#include <JlCompress.h> +#include "logger/QsLog.h" void OneSixLibrary::finalize() { @@ -133,6 +136,90 @@ QString OneSixLibrary::hint() return m_hint; } +bool OneSixLibrary::filesExist() +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QFileInfo info32(PathCombine("libraries", cooked_storage)); + if (!info32.exists()) + { + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + QFileInfo info64(PathCombine("libraries", cooked_storage)); + if (!info64.exists()) + { + return false; + } + } + else + { + QFileInfo info(PathCombine("libraries", storage)); + if (!info.exists()) + { + return false; + } + } + return true; +} + +bool OneSixLibrary::extractTo(QString target_dir) +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QString origin = PathCombine("libraries", cooked_storage); + QString target_dir_cooked = PathCombine(target_dir, "32"); + if(!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + origin = PathCombine("libraries", cooked_storage); + target_dir_cooked = PathCombine(target_dir, "32"); + if(!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + } + else + { + if(!ensureFolderPathExists(target_dir)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir; + return false; + } + QString path = PathCombine("libraries", storage); + if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + path; + return false; + } + } + return true; +} + QJsonObject OneSixLibrary::toJson() { QJsonObject libRoot; diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h index 3f0bc83d..227cdbef 100644 --- a/logic/OneSixLibrary.h +++ b/logic/OneSixLibrary.h @@ -126,4 +126,7 @@ public: /// set a hint about how to treat the library. This is an MMC extension. void setHint(QString hint); QString hint(); + + bool extractTo(QString target_dir); + bool filesExist(); }; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index 4d93477a..0119ab07 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -54,17 +54,7 @@ void OneSixUpdate::executeTask() if (m_only_prepare) { - /* - * FIXME: in offline mode, do not proceed! - */ - setStatus(tr("Testing the Java installation...")); - QString java_path = m_inst->settings().get("JavaPath").toString(); - - checker.reset(new JavaChecker()); - connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinishedOffline(JavaCheckResult))); - checker->path = java_path; - checker->performCheck(); + prepareForLaunch(); return; } @@ -83,46 +73,8 @@ void OneSixUpdate::executeTask() } else { - checkJavaOnline(); - } -} - -void OneSixUpdate::checkJavaOnline() -{ - setStatus(tr("Testing the Java installation...")); - QString java_path = m_inst->settings().get("JavaPath").toString(); - - checker.reset(new JavaChecker()); - connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinishedOnline(JavaCheckResult))); - checker->path = java_path; - checker->performCheck(); -} - -void OneSixUpdate::checkFinishedOnline(JavaCheckResult result) -{ - if (result.valid) - { - java_is_64bit = result.is_64bit; jarlibStart(); } - else - { - emitFailed("The java binary doesn't work. Check the settings and correct the problem"); - } -} - -void OneSixUpdate::checkFinishedOffline(JavaCheckResult result) -{ - if (result.valid) - { - java_is_64bit = result.is_64bit; - prepareForLaunch(); - } - else - { - emitFailed("The java binary doesn't work. Check the settings and correct the problem"); - } } void OneSixUpdate::versionFileStart() @@ -130,7 +82,8 @@ void OneSixUpdate::versionFileStart() QLOG_INFO() << m_inst->name() << ": getting version file."; setStatus(tr("Getting the version files from Mojang...")); - QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + + targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; auto job = new NetJob("Version index"); job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); specificVersionDownloadJob.reset(job); @@ -186,7 +139,7 @@ void OneSixUpdate::versionFileFinished() } inst->reloadFullVersion(); - checkJavaOnline(); + jarlibStart(); } void OneSixUpdate::versionFileFailed() @@ -230,7 +183,7 @@ void OneSixUpdate::assetIndexFinished() { emitFailed("Failed to read the assets index!"); } - + QList<Md5EtagDownloadPtr> dls; for (auto object : index.objects.values()) { @@ -245,17 +198,17 @@ void OneSixUpdate::assetIndexFinished() dls.append(objectDL); } } - if(dls.size()) + if (dls.size()) { setStatus(tr("Getting the assets files from Mojang...")); auto job = new NetJob("Assets for " + inst->name()); - for(auto dl: dls) + for (auto dl : dls) job->addNetAction(dl); jarlibDownloadJob.reset(job); connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished())); connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetsFailed())); connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); + SIGNAL(progress(qint64, qint64))); jarlibDownloadJob->start(); return; } @@ -277,8 +230,6 @@ void OneSixUpdate::assetsFailed() emitFailed("Failed to download assets!"); } - - void OneSixUpdate::jarlibStart() { setStatus(tr("Getting the library files from Mojang...")); @@ -318,24 +269,37 @@ void OneSixUpdate::jarlibStart() { if (lib->hint() == "local") continue; - QString subst = java_is_64bit ? "64" : "32"; - QString storage = lib->storagePath(); - QString dl = lib->downloadUrl(); - storage.replace("${arch}", subst); - dl.replace("${arch}", subst); + QString raw_storage = lib->storagePath(); + QString raw_dl = lib->downloadUrl(); - auto entry = metacache->resolveEntry("libraries", storage); - if (entry->stale) + auto f = [&](QString storage, QString dl) { - if (lib->hint() == "forge-pack-xz") + auto entry = metacache->resolveEntry("libraries", storage); + if (entry->stale) { - ForgeLibs.append(ForgeXzDownload::make(storage, entry)); - } - else - { - jarlibDownloadJob->addNetAction(CacheDownload::make(dl, entry)); + if (lib->hint() == "forge-pack-xz") + { + ForgeLibs.append(ForgeXzDownload::make(storage, entry)); + } + else + { + jarlibDownloadJob->addNetAction(CacheDownload::make(dl, entry)); + } } + }; + if (raw_storage.contains("${arch}")) + { + QString cooked_storage = raw_storage; + QString cooked_dl = raw_dl; + f(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32")); + cooked_storage = raw_storage; + cooked_dl = raw_dl; + f(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64")); + } + else + { + f(raw_storage, raw_dl); } } // TODO: think about how to propagate this from the original json file... or IF AT ALL @@ -376,7 +340,6 @@ void OneSixUpdate::prepareForLaunch() // delete any leftovers, if they are present. onesix_inst->cleanupAfterRun(); - // Acquire swag QString natives_dir_raw = PathCombine(onesix_inst->instanceRoot(), "natives/"); auto version = onesix_inst->getFullVersion(); if (!version) @@ -385,38 +348,30 @@ void OneSixUpdate::prepareForLaunch() "it or changing the version."); return; } - auto libs_to_extract = version->getActiveNativeLibs(); - - // Acquire bag - bool success = ensureFolderPathExists(natives_dir_raw); - if (!success) - { - emitFailed("Could not create the native library folder:\n" + natives_dir_raw + - "\nMake sure MultiMC has appropriate permissions and there is enough space " - "on the storage device."); - return; - } - - // Put swag in the bag - QString subst = java_is_64bit ? "64" : "32"; - for (auto lib : libs_to_extract) + /* + * emitFailed("Could not create the native library folder:\n" + natives_dir_raw + + "\nMake sure MultiMC has appropriate permissions and there is enough + space " + "on the storage device."); + */ + for (auto lib : version->getActiveNativeLibs()) { - QString storage = lib->storagePath(); - storage.replace("${arch}", subst); - - QString path = "libraries/" + storage; - QLOG_INFO() << "Will extract " << path.toLocal8Bit(); - if (JlCompress::extractWithExceptions(path, natives_dir_raw, lib->extract_excludes) - .isEmpty()) + if (!lib->filesExist()) + { + emitFailed("Native library is missing some files:\n" + lib->storagePath() + + "\n\nRun the instance at least once in online mode to get all the " + "required files."); + return; + } + if (!lib->extractTo(natives_dir_raw)) { - emitFailed( - "Could not extract the native library:\n" + path + - "\nMake sure MultiMC has appropriate permissions and there is enough space " - "on the storage device."); + emitFailed("Could not extract the native library:\n" + lib->storagePath() + " to " + + natives_dir_raw + + "\n\nMake sure MultiMC has appropriate permissions and there is enough " + "space on the storage device."); return; } } - // Show them your war face! emitSucceeded(); } diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h index 00b769c7..bc717a94 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -21,7 +21,6 @@ #include "logic/net/NetJob.h" #include "logic/tasks/Task.h" -#include "logic/JavaChecker.h" class MinecraftVersion; class BaseInstance; @@ -50,10 +49,6 @@ slots: void assetsFinished(); void assetsFailed(); - void checkJavaOnline(); - void checkFinishedOnline(JavaCheckResult result); - void checkFinishedOffline(JavaCheckResult result); - // extract the appropriate libraries void prepareForLaunch(); @@ -65,7 +60,4 @@ private: std::shared_ptr<MinecraftVersion> targetVersion; BaseInstance *m_inst = nullptr; bool m_only_prepare = false; - std::shared_ptr<JavaChecker> checker; - - bool java_is_64bit = false; }; diff --git a/logic/SkinUtils.h b/logic/SkinUtils.h index 324f86b8..64353b72 100644 --- a/logic/SkinUtils.h +++ b/logic/SkinUtils.h @@ -19,5 +19,5 @@ namespace SkinUtils { -QPixmap getFaceFromCache(QString username, int height = 48, int width = 48); +QPixmap getFaceFromCache(QString username, int height = 64, int width = 64); } diff --git a/logic/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp index a462eda5..f41985ec 100644 --- a/logic/auth/MojangAccount.cpp +++ b/logic/auth/MojangAccount.cpp @@ -198,7 +198,11 @@ 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.") + { + // do nothing + } + else { m_online = false; m_accessToken = QString(); diff --git a/logic/icons/IconList.cpp b/logic/icons/IconList.cpp index cda2db7b..d76e6fbb 100644 --- a/logic/icons/IconList.cpp +++ b/logic/icons/IconList.cpp @@ -336,6 +336,23 @@ QIcon IconList::getIcon(QString key) return QIcon(); } +QIcon IconList::getBigIcon(QString key) +{ + int icon_index = getIconIndex(key); + + if (icon_index == -1) + key = "infinity"; + + // Fallback for icons that don't exist. + icon_index = getIconIndex(key); + + if (icon_index == -1) + return QIcon(); + + QPixmap bigone = icons[icon_index].icon().pixmap(256,256).scaled(256,256); + return QIcon(bigone); +} + int IconList::getIconIndex(QString key) { if (key == "default") diff --git a/logic/icons/IconList.h b/logic/icons/IconList.h index 322411d1..4ee3f782 100644 --- a/logic/icons/IconList.h +++ b/logic/icons/IconList.h @@ -34,6 +34,7 @@ public: virtual ~IconList() {}; QIcon getIcon(QString key); + QIcon getBigIcon(QString key); int getIconIndex(QString key); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; diff --git a/logic/lists/ForgeVersionList.cpp b/logic/lists/ForgeVersionList.cpp index 56eca744..4902dc64 100644 --- a/logic/lists/ForgeVersionList.cpp +++ b/logic/lists/ForgeVersionList.cpp @@ -187,7 +187,7 @@ bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out) QByteArray data; { auto dlJob = listDownload; - auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->m_target_path; + auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->getTargetFilepath(); QFile listFile(filename); if (!listFile.open(QIODevice::ReadOnly)) { @@ -303,7 +303,7 @@ bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out) QByteArray data; { auto dlJob = gradleListDownload; - auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->m_target_path; + auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->getTargetFilepath(); QFile listFile(filename); if (!listFile.open(QIODevice::ReadOnly)) { @@ -404,12 +404,8 @@ void ForgeListLoadTask::listDownloaded() { 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(); - }); + std::sort(list.begin(), list.end(), [](const BaseVersionPtr & l, const BaseVersionPtr & r) + { return (*l > *r); }); m_list->updateListData(list); diff --git a/logic/lists/ForgeVersionList.h b/logic/lists/ForgeVersionList.h index 924084ae..b19d3f56 100644 --- a/logic/lists/ForgeVersionList.h +++ b/logic/lists/ForgeVersionList.h @@ -29,25 +29,38 @@ typedef std::shared_ptr<ForgeVersion> ForgeVersionPtr; struct ForgeVersion : public BaseVersion { - virtual QString descriptor() + virtual QString descriptor() override { return filename; } ; - virtual QString name() + virtual QString name() override { return "Forge " + jobbuildver; } ; - virtual QString typeString() const + virtual QString typeString() const override { if (installer_url.isEmpty()) return "Universal"; else return "Installer"; } - ; + virtual bool operator<(BaseVersion &a) override + { + ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); + if(!pa) + return true; + return m_buildnr < pa->m_buildnr; + } + virtual bool operator>(BaseVersion &a) override + { + ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); + if(!pa) + return false; + return m_buildnr > pa->m_buildnr; + } int m_buildnr = 0; QString universal_url; QString changelog_url; diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index bfd183d9..0d4eab95 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -308,45 +308,52 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap) return; } dir.cd("ModPacks"); - auto fpath = dir.absoluteFilePath("modpacks.xml"); - QFile f(fpath); - QLOG_INFO() << "Discovering FTB instances -- " << fpath; - if (!f.open(QFile::ReadOnly)) - return; - - // read the FTB packs XML. - QXmlStreamReader reader(&f); - while (!reader.atEnd()) + auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name); + for(auto filename: allFiles) { - switch (reader.readNext()) - { - case QXmlStreamReader::StartElement: + if(!filename.endsWith(".xml")) + continue; + auto fpath = dir.absoluteFilePath(filename); + QFile f(fpath); + QLOG_INFO() << "Discovering FTB instances -- " << fpath; + if (!f.open(QFile::ReadOnly)) + continue; + + // read the FTB packs XML. + QXmlStreamReader reader(&f); + while (!reader.atEnd()) { - if (reader.name() == "modpack") + switch (reader.readNext()) { - QXmlStreamAttributes attrs = reader.attributes(); - FTBRecord record; - record.dir = attrs.value("dir").toString(); - QDir test(dataDir.absoluteFilePath(record.dir)); - if(!test.exists()) - continue; - 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); + case QXmlStreamReader::StartElement: + { + if (reader.name() == "modpack") + { + QXmlStreamAttributes attrs = reader.attributes(); + FTBRecord record; + record.dir = attrs.value("dir").toString(); + QDir test(dataDir.absoluteFilePath(record.dir)); + if(!test.exists()) + continue; + 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; } - break; - } - case QXmlStreamReader::EndElement: - break; - case QXmlStreamReader::Characters: - break; - default: - break; } + f.close(); } - f.close(); + if(!records.size()) { QLOG_INFO() << "No FTB instances to load."; diff --git a/logic/net/CacheDownload.cpp b/logic/net/CacheDownload.cpp index 6eadae39..d2a9bdee 100644 --- a/logic/net/CacheDownload.cpp +++ b/logic/net/CacheDownload.cpp @@ -33,25 +33,44 @@ CacheDownload::CacheDownload(QUrl url, MetaEntryPtr entry) void CacheDownload::start() { + m_status = Job_InProgress; if (!m_entry->stale) { + m_status = Job_Finished; emit succeeded(m_index_within_job); return; } - m_output_file.setFileName(m_target_path); + // create a new save file + m_output_file.reset(new QSaveFile(m_target_path)); + // if there already is a file and md5 checking is in effect and it can be opened if (!ensureFilePathExists(m_target_path)) { + QLOG_ERROR() << "Could not create folder for " + m_target_path; + m_status = Job_Failed; + emit failed(m_index_within_job); + return; + } + if (!m_output_file->open(QIODevice::WriteOnly)) + { + QLOG_ERROR() << "Could not open " + m_target_path + " for writing"; + m_status = Job_Failed; emit failed(m_index_within_job); return; } QLOG_INFO() << "Downloading " << m_url.toString(); QNetworkRequest request(m_url); - if (m_entry->remote_changed_timestamp.size()) - request.setRawHeader(QString("If-Modified-Since").toLatin1(), - m_entry->remote_changed_timestamp.toLatin1()); - if (m_entry->etag.size()) - request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->etag.toLatin1()); + + // check file consistency first. + QFile current(m_target_path); + if(current.exists() && current.size() != 0) + { + if (m_entry->remote_changed_timestamp.size()) + request.setRawHeader(QString("If-Modified-Since").toLatin1(), + m_entry->remote_changed_timestamp.toLatin1()); + if (m_entry->etag.size()) + request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->etag.toLatin1()); + } request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); @@ -77,76 +96,74 @@ void CacheDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) void CacheDownload::downloadError(QNetworkReply::NetworkError error) { // error happened during download. - QLOG_ERROR() << "Failed" << m_url.toString() << "with reason" << error; + QLOG_ERROR() << "Failed " << m_url.toString() << " with reason " << error; m_status = Job_Failed; } void CacheDownload::downloadFinished() { // if the download succeeded - if (m_status != Job_Failed) + if (m_status == Job_Failed) { + m_output_file->cancelWriting(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + // if we wrote any data to the save file, we try to commit the data to the real file. + if (wroteAnyData) + { // nothing went wrong... - m_status = Job_Finished; - if (m_output_file.isOpen()) + if (m_output_file->commit()) { - // save the data to the downloadable if we aren't saving to file - m_output_file.close(); + m_status = Job_Finished; m_entry->md5sum = md5sum.result().toHex().constData(); } else { - if (m_output_file.open(QIODevice::ReadOnly)) - { - m_entry->md5sum = - QCryptographicHash::hash(m_output_file.readAll(), QCryptographicHash::Md5) - .toHex() - .constData(); - m_output_file.close(); - } - } - QFileInfo output_file_info(m_target_path); - - m_entry->etag = m_reply->rawHeader("ETag").constData(); - if (m_reply->hasRawHeader("Last-Modified")) - { - m_entry->remote_changed_timestamp = m_reply->rawHeader("Last-Modified").constData(); + QLOG_ERROR() << "Failed to commit changes to " << m_target_path; + m_output_file->cancelWriting(); + m_reply.reset(); + m_status = Job_Failed; + emit failed(m_index_within_job); + return; } - m_entry->local_changed_timestamp = - output_file_info.lastModified().toUTC().toMSecsSinceEpoch(); - m_entry->stale = false; - MMC->metacache()->updateEntry(m_entry); - - m_reply.reset(); - emit succeeded(m_index_within_job); - return; } - // else the download failed else { - m_output_file.close(); - m_output_file.remove(); - m_reply.reset(); - emit failed(m_index_within_job); - return; + m_status = Job_Finished; + } + + // then get rid of the save file + m_output_file.reset(); + + QFileInfo output_file_info(m_target_path); + + m_entry->etag = m_reply->rawHeader("ETag").constData(); + if (m_reply->hasRawHeader("Last-Modified")) + { + m_entry->remote_changed_timestamp = m_reply->rawHeader("Last-Modified").constData(); } + m_entry->local_changed_timestamp = + output_file_info.lastModified().toUTC().toMSecsSinceEpoch(); + m_entry->stale = false; + MMC->metacache()->updateEntry(m_entry); + + m_reply.reset(); + emit succeeded(m_index_within_job); + return; } void CacheDownload::downloadReadyRead() { - if (!m_output_file.isOpen()) - { - if (!m_output_file.open(QIODevice::WriteOnly)) - { - /* - * Can't open the file... the job failed - */ - m_reply->abort(); - emit failed(m_index_within_job); - return; - } - } QByteArray ba = m_reply->readAll(); md5sum.addData(ba); - m_output_file.write(ba); + if (m_output_file->write(ba) != ba.size()) + { + QLOG_ERROR() << "Failed writing into " + m_target_path; + m_status = Job_Failed; + m_reply->abort(); + emit failed(m_index_within_job); + } + wroteAnyData = true; } diff --git a/logic/net/CacheDownload.h b/logic/net/CacheDownload.h index e25aecd2..154f5988 100644 --- a/logic/net/CacheDownload.h +++ b/logic/net/CacheDownload.h @@ -17,29 +17,34 @@ #include "NetAction.h" #include "HttpMetaCache.h" -#include <QFile> -#include <qcryptographichash.h> +#include <QCryptographicHash> +#include <QSaveFile> typedef std::shared_ptr<class CacheDownload> CacheDownloadPtr; class CacheDownload : public NetAction { Q_OBJECT -public: +private: MetaEntryPtr m_entry; /// if saving to file, use the one specified in this string QString m_target_path; /// this is the output file, if any - QFile m_output_file; + std::shared_ptr<QSaveFile> m_output_file; /// the hash-as-you-download QCryptographicHash md5sum; + bool wroteAnyData = false; + public: explicit CacheDownload(QUrl url, MetaEntryPtr entry); static CacheDownloadPtr make(QUrl url, MetaEntryPtr entry) { return CacheDownloadPtr(new CacheDownload(url, entry)); } - + QString getTargetFilepath() + { + return m_target_path; + } protected slots: virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); diff --git a/logic/net/URLConstants.h b/logic/net/URLConstants.h index 9579198d..8cb1f3fd 100644 --- a/logic/net/URLConstants.h +++ b/logic/net/URLConstants.h @@ -31,4 +31,6 @@ 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"); +const QString MOJANG_STATUS_URL("http://status.mojang.com/check"); +const QString MOJANG_STATUS_NEWS_URL("http://status.mojang.com/news"); } diff --git a/logic/status/StatusChecker.cpp b/logic/status/StatusChecker.cpp new file mode 100644 index 00000000..66f800ae --- /dev/null +++ b/logic/status/StatusChecker.cpp @@ -0,0 +1,137 @@ +/* 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 "StatusChecker.h" + +#include <logic/net/URLConstants.h> + +#include <QByteArray> +#include <QDomDocument> + +#include <logger/QsLog.h> + +StatusChecker::StatusChecker() +{ + +} + +void StatusChecker::reloadStatus() +{ + if (isLoadingStatus()) + { + // QLOG_INFO() << "Ignored request to reload status. Currently reloading already."; + return; + } + + // QLOG_INFO() << "Reloading status."; + + NetJob* job = new NetJob("Status JSON"); + job->addNetAction(ByteArrayDownload::make(URLConstants::MOJANG_STATUS_URL)); + QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished); + QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed); + m_statusNetJob.reset(job); + job->start(); +} + +void StatusChecker::statusDownloadFinished() +{ + QLOG_DEBUG() << "Finished loading status JSON."; + + QByteArray data; + { + ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(m_statusNetJob->first()); + data = dl->m_data; + m_statusNetJob.reset(); + } + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) + { + fail("Error parsing status JSON:" + jsonError.errorString()); + return; + } + + if (!jsonDoc.isArray()) + { + fail("Error parsing status JSON: JSON root is not an array"); + return; + } + + QJsonArray root = jsonDoc.array(); + + for(auto status = root.begin(); status != root.end(); ++status) + { + QVariantMap map = (*status).toObject().toVariantMap(); + + for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) + { + QString key = iter.key(); + QVariant value = iter.value(); + + if(value.type() == QVariant::Type::String) + { + m_statusEntries.insert(key, value.toString()); + //QLOG_DEBUG() << "Status JSON object: " << key << m_statusEntries[key]; + } + else + { + fail("Malformed status JSON: expected status type to be a string."); + return; + } + } + } + + succeed(); +} + +void StatusChecker::statusDownloadFailed() +{ + fail("Failed to load status JSON."); +} + + +QMap<QString, QString> StatusChecker::getStatusEntries() const +{ + return m_statusEntries; +} + +bool StatusChecker::isLoadingStatus() const +{ + return m_statusNetJob.get() != nullptr; +} + +QString StatusChecker::getLastLoadErrorMsg() const +{ + return m_lastLoadError; +} + +void StatusChecker::succeed() +{ + m_lastLoadError = ""; + QLOG_DEBUG() << "Status loading succeeded."; + m_statusNetJob.reset(); + emit statusLoaded(); +} + +void StatusChecker::fail(const QString& errorMsg) +{ + m_lastLoadError = errorMsg; + QLOG_DEBUG() << "Failed to load status:" << errorMsg; + m_statusNetJob.reset(); + emit statusLoadingFailed(errorMsg); +} + diff --git a/logic/status/StatusChecker.h b/logic/status/StatusChecker.h new file mode 100644 index 00000000..1cb01836 --- /dev/null +++ b/logic/status/StatusChecker.h @@ -0,0 +1,57 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QObject> +#include <QString> +#include <QList> + +#include <logic/net/NetJob.h> + +class StatusChecker : public QObject +{ + Q_OBJECT +public: + StatusChecker(); + + QString getLastLoadErrorMsg() const; + + bool isStatusLoaded() const; + + bool isLoadingStatus() const; + + QMap<QString, QString> getStatusEntries() const; + + void Q_SLOT reloadStatus(); + +signals: + void statusLoaded(); + void statusLoadingFailed(QString errorMsg); + +protected slots: + void statusDownloadFinished(); + void statusDownloadFailed(); + +protected: + QMap<QString, QString> m_statusEntries; + NetJobPtr m_statusNetJob; + bool m_loadedStatus; + QString m_lastLoadError; + + void Q_SLOT succeed(); + void Q_SLOT fail(const QString& errorMsg); +}; + diff --git a/logic/updater/NotificationChecker.cpp b/logic/updater/NotificationChecker.cpp index b2d67632..191e90a3 100644 --- a/logic/updater/NotificationChecker.cpp +++ b/logic/updater/NotificationChecker.cpp @@ -55,7 +55,7 @@ void NotificationChecker::downloadSucceeded(int) { m_entries.clear(); - QFile file(m_download->m_output_file.fileName()); + QFile file(m_download->getTargetFilepath()); if (file.open(QFile::ReadOnly)) { QJsonArray root = QJsonDocument::fromJson(file.readAll()).array(); diff --git a/logic/updater/UpdateChecker.cpp b/logic/updater/UpdateChecker.cpp index 8a280dd1..8e2aa8b3 100644 --- a/logic/updater/UpdateChecker.cpp +++ b/logic/updater/UpdateChecker.cpp @@ -30,7 +30,6 @@ UpdateChecker::UpdateChecker() { - m_currentChannel = VERSION_CHANNEL; m_channelListUrl = CHANLIST_URL; m_updateChecking = false; m_chanListLoading = false; diff --git a/logic/updater/UpdateChecker.h b/logic/updater/UpdateChecker.h index 7840cedc..3b0ee28d 100644 --- a/logic/updater/UpdateChecker.h +++ b/logic/updater/UpdateChecker.h @@ -27,7 +27,6 @@ public: UpdateChecker(); void checkForUpdate(bool notifyNoUpdate); - void setCurrentChannel(const QString &channel) { m_currentChannel = channel; } void setChannelListUrl(const QString &url) { m_channelListUrl = url; } /*! @@ -83,7 +82,6 @@ private: QString m_repoUrl; QString m_channelListUrl; - QString m_currentChannel; QList<ChannelListEntry> m_channels; |