SCRATCH move things to the right places
This commit is contained in:
217
logic/minecraft/AssetsUtils.cpp
Normal file
217
logic/minecraft/AssetsUtils.cpp
Normal file
@ -0,0 +1,217 @@
|
||||
/* Copyright 2013-2015 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QVariant>
|
||||
#include <QDebug>
|
||||
|
||||
#include "AssetsUtils.h"
|
||||
#include <pathutils.h>
|
||||
|
||||
namespace AssetsUtils
|
||||
{
|
||||
int findLegacyAssets()
|
||||
{
|
||||
QDir assets_dir("assets");
|
||||
if (!assets_dir.exists())
|
||||
return 0;
|
||||
assets_dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
|
||||
int base_length = assets_dir.path().length();
|
||||
|
||||
QList<QString> blacklist = {"indexes", "objects", "virtual"};
|
||||
|
||||
QDirIterator iterator(assets_dir, QDirIterator::Subdirectories);
|
||||
int found = 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)
|
||||
{
|
||||
found++;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true on success, with index populated
|
||||
* index is undefined otherwise
|
||||
*/
|
||||
bool loadAssetsIndexJson(QString path, AssetsIndex *index)
|
||||
{
|
||||
/*
|
||||
{
|
||||
"objects": {
|
||||
"icons/icon_16x16.png": {
|
||||
"hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a",
|
||||
"size": 3665
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
QFile file(path);
|
||||
|
||||
// Try to open the file and fail if we can't.
|
||||
// TODO: We should probably report this error to the user.
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qCritical() << "Failed to read assets index file" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the file and close it.
|
||||
QByteArray jsonData = file.readAll();
|
||||
file.close();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
|
||||
|
||||
// Fail if the JSON is invalid.
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
{
|
||||
qCritical() << "Failed to parse assets index file:" << parseError.errorString()
|
||||
<< "at offset " << QString::number(parseError.offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the root is an object.
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
qCritical() << "Invalid assets index JSON: Root should be an array.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject root = jsonDoc.object();
|
||||
|
||||
QJsonValue isVirtual = root.value("virtual");
|
||||
if (!isVirtual.isUndefined())
|
||||
{
|
||||
index->isVirtual = isVirtual.toBool(false);
|
||||
}
|
||||
|
||||
QJsonValue objects = root.value("objects");
|
||||
QVariantMap map = objects.toVariant().toMap();
|
||||
|
||||
for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
|
||||
{
|
||||
// qDebug() << iter.key();
|
||||
|
||||
QVariant variant = iter.value();
|
||||
QVariantMap nested_objects = variant.toMap();
|
||||
|
||||
AssetObject object;
|
||||
|
||||
for (QVariantMap::const_iterator nested_iter = nested_objects.begin();
|
||||
nested_iter != nested_objects.end(); ++nested_iter)
|
||||
{
|
||||
// qDebug() << nested_iter.key() << nested_iter.value().toString();
|
||||
QString key = nested_iter.key();
|
||||
QVariant value = nested_iter.value();
|
||||
|
||||
if (key == "hash")
|
||||
{
|
||||
object.hash = value.toString();
|
||||
}
|
||||
else if (key == "size")
|
||||
{
|
||||
object.size = value.toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
index->objects.insert(iter.key(), object);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QDir reconstructAssets(QString assetsId)
|
||||
{
|
||||
QDir assetsDir = QDir("assets/");
|
||||
QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes"));
|
||||
QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects"));
|
||||
QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual"));
|
||||
|
||||
QString indexPath = PathCombine(indexDir.path(), assetsId + ".json");
|
||||
QFile indexFile(indexPath);
|
||||
QDir virtualRoot(PathCombine(virtualDir.path(), assetsId));
|
||||
|
||||
if (!indexFile.exists())
|
||||
{
|
||||
qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets";
|
||||
return virtualRoot;
|
||||
}
|
||||
|
||||
qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path()
|
||||
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
|
||||
|
||||
AssetsIndex index;
|
||||
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index);
|
||||
|
||||
if (loadAssetsIndex && index.isVirtual)
|
||||
{
|
||||
qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path();
|
||||
|
||||
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())
|
||||
{
|
||||
QFileInfo info(target_path);
|
||||
QDir target_dir = info.dir();
|
||||
// qDebug() << target_dir;
|
||||
if (!target_dir.exists())
|
||||
QDir("").mkpath(target_dir.path());
|
||||
|
||||
bool couldCopy = original.copy(target_path);
|
||||
qDebug() << " Copying" << original_path << "to" << target_path
|
||||
<< QString::number(couldCopy); // << original.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Write last used time to virtualRoot/.lastused
|
||||
}
|
||||
|
||||
return virtualRoot;
|
||||
}
|
||||
|
||||
}
|
39
logic/minecraft/AssetsUtils.h
Normal file
39
logic/minecraft/AssetsUtils.h
Normal file
@ -0,0 +1,39 @@
|
||||
/* Copyright 2013-2015 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 <QString>
|
||||
#include <QMap>
|
||||
|
||||
struct AssetObject
|
||||
{
|
||||
QString hash;
|
||||
qint64 size;
|
||||
};
|
||||
|
||||
struct AssetsIndex
|
||||
{
|
||||
QMap<QString, AssetObject> objects;
|
||||
bool isVirtual = false;
|
||||
};
|
||||
|
||||
namespace AssetsUtils
|
||||
{
|
||||
bool loadAssetsIndexJson(QString file, AssetsIndex* index);
|
||||
int findLegacyAssets();
|
||||
/// Reconstruct a virtual assets folder for the given assets ID and return the folder
|
||||
QDir reconstructAssets(QString assetsId);
|
||||
}
|
158
logic/minecraft/JarUtils.cpp
Normal file
158
logic/minecraft/JarUtils.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
#include "logic/minecraft/JarUtils.h"
|
||||
#include <quazip.h>
|
||||
#include <quazipfile.h>
|
||||
#include <JlCompress.h>
|
||||
#include <QDebug>
|
||||
|
||||
namespace JarUtils {
|
||||
|
||||
bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
|
||||
std::function<bool(QString)> filter)
|
||||
{
|
||||
QuaZip modZip(from.filePath());
|
||||
modZip.open(QuaZip::mdUnzip);
|
||||
|
||||
QuaZipFile fileInsideMod(&modZip);
|
||||
QuaZipFile zipOutFile(into);
|
||||
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
|
||||
{
|
||||
QString filename = modZip.getCurrentFileName();
|
||||
if (!filter(filename))
|
||||
{
|
||||
qDebug() << "Skipping file " << filename << " from "
|
||||
<< from.fileName() << " - filtered";
|
||||
continue;
|
||||
}
|
||||
if (contained.contains(filename))
|
||||
{
|
||||
qDebug() << "Skipping already contained file " << filename << " from "
|
||||
<< from.fileName();
|
||||
continue;
|
||||
}
|
||||
contained.insert(filename);
|
||||
|
||||
if (!fileInsideMod.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qCritical() << "Failed to open " << filename << " from " << from.fileName();
|
||||
return false;
|
||||
}
|
||||
|
||||
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
|
||||
|
||||
if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
|
||||
{
|
||||
qCritical() << "Failed to open " << filename << " in the jar";
|
||||
fileInsideMod.close();
|
||||
return false;
|
||||
}
|
||||
if (!JlCompress::copyData(fileInsideMod, zipOutFile))
|
||||
{
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
qCritical() << "Failed to copy data of " << filename << " into the jar";
|
||||
return false;
|
||||
}
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods)
|
||||
{
|
||||
QuaZip zipOut(targetJarPath);
|
||||
if (!zipOut.open(QuaZip::mdCreate))
|
||||
{
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to open the minecraft.jar for modding";
|
||||
return false;
|
||||
}
|
||||
// Files already added to the jar.
|
||||
// These files will be skipped.
|
||||
QSet<QString> addedFiles;
|
||||
|
||||
// Modify the jar
|
||||
QListIterator<Mod> i(mods);
|
||||
i.toBack();
|
||||
while (i.hasPrevious())
|
||||
{
|
||||
const Mod &mod = i.previous();
|
||||
// do not merge disabled mods.
|
||||
if (!mod.enabled())
|
||||
continue;
|
||||
if (mod.type() == Mod::MOD_ZIPFILE)
|
||||
{
|
||||
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles, noFilter))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_SINGLEFILE)
|
||||
{
|
||||
auto filename = mod.filename();
|
||||
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(),
|
||||
filename.fileName()))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
addedFiles.insert(filename.fileName());
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_FOLDER)
|
||||
{
|
||||
auto filename = mod.filename();
|
||||
QString what_to_zip = filename.absoluteFilePath();
|
||||
QDir dir(what_to_zip);
|
||||
dir.cdUp();
|
||||
QString parent_dir = dir.absolutePath();
|
||||
if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, true, addedFiles))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Adding folder " << filename.fileName() << " from "
|
||||
<< filename.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, metaInfFilter))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to insert minecraft.jar contents.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recompress the jar
|
||||
zipOut.close();
|
||||
if (zipOut.getZipError() != 0)
|
||||
{
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to finalize minecraft.jar!";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool noFilter(QString)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool metaInfFilter(QString key)
|
||||
{
|
||||
if(key.contains("META-INF"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
18
logic/minecraft/JarUtils.h
Normal file
18
logic/minecraft/JarUtils.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QFileInfo>
|
||||
#include <QSet>
|
||||
#include "Mod.h"
|
||||
#include <functional>
|
||||
|
||||
class QuaZip;
|
||||
namespace JarUtils
|
||||
{
|
||||
bool noFilter(QString);
|
||||
bool metaInfFilter(QString key);
|
||||
|
||||
bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
|
||||
std::function<bool(QString)> filter);
|
||||
|
||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
|
||||
}
|
346
logic/minecraft/LegacyInstance.cpp
Normal file
346
logic/minecraft/LegacyInstance.cpp
Normal file
@ -0,0 +1,346 @@
|
||||
/* Copyright 2013-2015 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 <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QImage>
|
||||
#include <logic/settings/Setting.h>
|
||||
#include <pathutils.h>
|
||||
#include <cmdutils.h>
|
||||
|
||||
#include "LegacyInstance.h"
|
||||
|
||||
#include "logic/minecraft/LegacyUpdate.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
#include "logic/minecraft/MinecraftProcess.h"
|
||||
#include "gui/pages/LegacyUpgradePage.h"
|
||||
#include "gui/pages/ModFolderPage.h"
|
||||
#include "gui/pages/LegacyJarModPage.h"
|
||||
#include <gui/pages/TexturePackPage.h>
|
||||
#include <gui/pages/InstanceSettingsPage.h>
|
||||
#include <gui/pages/NotesPage.h>
|
||||
#include <gui/pages/ScreenshotsPage.h>
|
||||
|
||||
LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
|
||||
: MinecraftInstance(globalSettings, settings, rootDir)
|
||||
{
|
||||
m_lwjglFolderSetting = globalSettings->getSetting("LWJGLDir");
|
||||
settings->registerSetting("NeedsRebuild", true);
|
||||
settings->registerSetting("ShouldUpdate", false);
|
||||
settings->registerSetting("JarVersion", "Unknown");
|
||||
settings->registerSetting("LwjglVersion", "2.9.0");
|
||||
settings->registerSetting("IntendedJarVersion", "");
|
||||
/*
|
||||
* custom base jar has no default. it is determined in code... see the accessor methods for
|
||||
*it
|
||||
*
|
||||
* for instances that DO NOT have the CustomBaseJar setting (legacy instances),
|
||||
* [.]minecraft/bin/mcbackup.jar is the default base jar
|
||||
*/
|
||||
settings->registerSetting("UseCustomBaseJar", true);
|
||||
settings->registerSetting("CustomBaseJar", "");
|
||||
}
|
||||
|
||||
QList<BasePage *> LegacyInstance::getPages()
|
||||
{
|
||||
QList<BasePage *> values;
|
||||
// FIXME: actually implement the legacy instance upgrade, then enable this.
|
||||
//values.append(new LegacyUpgradePage(this));
|
||||
values.append(new LegacyJarModPage(this));
|
||||
values.append(new ModFolderPage(this, loaderModList(), "mods", "loadermods", tr("Loader mods"),
|
||||
"Loader-mods"));
|
||||
values.append(new ModFolderPage(this, coreModList(), "coremods", "coremods", tr("Core mods"),
|
||||
"Loader-mods"));
|
||||
values.append(new TexturePackPage(this));
|
||||
values.append(new NotesPage(this));
|
||||
values.append(new ScreenshotsPage(PathCombine(minecraftRoot(), "screenshots")));
|
||||
values.append(new InstanceSettingsPage(this));
|
||||
return values;
|
||||
}
|
||||
|
||||
QString LegacyInstance::dialogTitle()
|
||||
{
|
||||
return tr("Edit Instance (%1)").arg(name());
|
||||
}
|
||||
|
||||
QString LegacyInstance::baseJar() const
|
||||
{
|
||||
bool customJar = m_settings->get("UseCustomBaseJar").toBool();
|
||||
if (customJar)
|
||||
{
|
||||
return customBaseJar();
|
||||
}
|
||||
else
|
||||
return defaultBaseJar();
|
||||
}
|
||||
|
||||
QString LegacyInstance::customBaseJar() const
|
||||
{
|
||||
QString value = m_settings->get("CustomBaseJar").toString();
|
||||
if (value.isNull() || value.isEmpty())
|
||||
{
|
||||
return defaultCustomBaseJar();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void LegacyInstance::setCustomBaseJar(QString val)
|
||||
{
|
||||
if (val.isNull() || val.isEmpty() || val == defaultCustomBaseJar())
|
||||
m_settings->reset("CustomBaseJar");
|
||||
else
|
||||
m_settings->set("CustomBaseJar", val);
|
||||
}
|
||||
|
||||
void LegacyInstance::setShouldUseCustomBaseJar(bool val)
|
||||
{
|
||||
m_settings->set("UseCustomBaseJar", val);
|
||||
}
|
||||
|
||||
bool LegacyInstance::shouldUseCustomBaseJar() const
|
||||
{
|
||||
return m_settings->get("UseCustomBaseJar").toBool();
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<Task> LegacyInstance::doUpdate()
|
||||
{
|
||||
// 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, this));
|
||||
}
|
||||
|
||||
BaseProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account)
|
||||
{
|
||||
QString launchScript;
|
||||
QIcon icon = ENV.icons()->getIcon(iconKey());
|
||||
auto pixmap = icon.pixmap(128, 128);
|
||||
pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG");
|
||||
|
||||
// create the launch script
|
||||
{
|
||||
// window size
|
||||
QString windowParams;
|
||||
if (settings().get("LaunchMaximized").toBool())
|
||||
windowParams = "max";
|
||||
else
|
||||
windowParams = QString("%1x%2")
|
||||
.arg(settings().get("MinecraftWinWidth").toInt())
|
||||
.arg(settings().get("MinecraftWinHeight").toInt());
|
||||
|
||||
QString lwjgl = QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion())
|
||||
.absolutePath();
|
||||
launchScript += "userName " + account->player_name + "\n";
|
||||
launchScript += "sessionId " + account->session + "\n";
|
||||
launchScript += "windowTitle " + windowTitle() + "\n";
|
||||
launchScript += "windowParams " + windowParams + "\n";
|
||||
launchScript += "lwjgl " + lwjgl + "\n";
|
||||
launchScript += "launcher legacy\n";
|
||||
}
|
||||
auto process = MinecraftProcess::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr()));
|
||||
process->setLaunchScript(launchScript);
|
||||
process->setWorkdir(minecraftRoot());
|
||||
process->setLogin(account);
|
||||
return process;
|
||||
}
|
||||
|
||||
void LegacyInstance::cleanupAfterRun()
|
||||
{
|
||||
// FIXME: delete the launcher and icons and whatnot.
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> LegacyInstance::coreModList() const
|
||||
{
|
||||
if (!core_mod_list)
|
||||
{
|
||||
core_mod_list.reset(new ModList(coreModsDir()));
|
||||
}
|
||||
core_mod_list->update();
|
||||
return core_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> LegacyInstance::jarModList() const
|
||||
{
|
||||
if (!jar_mod_list)
|
||||
{
|
||||
auto list = new ModList(jarModsDir(), modListFile());
|
||||
connect(list, SIGNAL(changed()), SLOT(jarModsChanged()));
|
||||
jar_mod_list.reset(list);
|
||||
}
|
||||
jar_mod_list->update();
|
||||
return jar_mod_list;
|
||||
}
|
||||
|
||||
QList<Mod> LegacyInstance::getJarMods() const
|
||||
{
|
||||
return jarModList()->allMods();
|
||||
}
|
||||
|
||||
void LegacyInstance::jarModsChanged()
|
||||
{
|
||||
qDebug() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt.";
|
||||
setShouldRebuild(true);
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> LegacyInstance::loaderModList() const
|
||||
{
|
||||
if (!loader_mod_list)
|
||||
{
|
||||
loader_mod_list.reset(new ModList(loaderModsDir()));
|
||||
}
|
||||
loader_mod_list->update();
|
||||
return loader_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> LegacyInstance::texturePackList() const
|
||||
{
|
||||
if (!texture_pack_list)
|
||||
{
|
||||
texture_pack_list.reset(new ModList(texturePacksDir()));
|
||||
}
|
||||
texture_pack_list->update();
|
||||
return texture_pack_list;
|
||||
}
|
||||
|
||||
QString LegacyInstance::jarModsDir() const
|
||||
{
|
||||
return PathCombine(instanceRoot(), "instMods");
|
||||
}
|
||||
|
||||
QString LegacyInstance::binDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "bin");
|
||||
}
|
||||
|
||||
QString LegacyInstance::libDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "lib");
|
||||
}
|
||||
|
||||
QString LegacyInstance::savesDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "saves");
|
||||
}
|
||||
|
||||
QString LegacyInstance::loaderModsDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "mods");
|
||||
}
|
||||
|
||||
QString LegacyInstance::coreModsDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "coremods");
|
||||
}
|
||||
|
||||
QString LegacyInstance::resourceDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "resources");
|
||||
}
|
||||
QString LegacyInstance::texturePacksDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "texturepacks");
|
||||
}
|
||||
|
||||
QString LegacyInstance::runnableJar() const
|
||||
{
|
||||
return PathCombine(binDir(), "minecraft.jar");
|
||||
}
|
||||
|
||||
QString LegacyInstance::modListFile() const
|
||||
{
|
||||
return PathCombine(instanceRoot(), "modlist");
|
||||
}
|
||||
|
||||
QString LegacyInstance::instanceConfigFolder() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "config");
|
||||
}
|
||||
|
||||
bool LegacyInstance::shouldRebuild() const
|
||||
{
|
||||
return m_settings->get("NeedsRebuild").toBool();
|
||||
}
|
||||
|
||||
void LegacyInstance::setShouldRebuild(bool val)
|
||||
{
|
||||
m_settings->set("NeedsRebuild", val);
|
||||
}
|
||||
|
||||
QString LegacyInstance::currentVersionId() const
|
||||
{
|
||||
return m_settings->get("JarVersion").toString();
|
||||
}
|
||||
|
||||
QString LegacyInstance::lwjglVersion() const
|
||||
{
|
||||
return m_settings->get("LwjglVersion").toString();
|
||||
}
|
||||
|
||||
void LegacyInstance::setLWJGLVersion(QString val)
|
||||
{
|
||||
m_settings->set("LwjglVersion", val);
|
||||
}
|
||||
|
||||
QString LegacyInstance::intendedVersionId() const
|
||||
{
|
||||
return m_settings->get("IntendedJarVersion").toString();
|
||||
}
|
||||
|
||||
bool LegacyInstance::setIntendedVersionId(QString version)
|
||||
{
|
||||
settings().set("IntendedJarVersion", version);
|
||||
setShouldUpdate(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LegacyInstance::shouldUpdate() const
|
||||
{
|
||||
QVariant var = settings().get("ShouldUpdate");
|
||||
if (!var.isValid() || var.toBool() == false)
|
||||
{
|
||||
return intendedVersionId() != currentVersionId();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LegacyInstance::setShouldUpdate(bool val)
|
||||
{
|
||||
settings().set("ShouldUpdate", val);
|
||||
}
|
||||
|
||||
QString LegacyInstance::defaultBaseJar() const
|
||||
{
|
||||
return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar";
|
||||
}
|
||||
|
||||
QString LegacyInstance::defaultCustomBaseJar() const
|
||||
{
|
||||
return PathCombine(binDir(), "mcbackup.jar");
|
||||
}
|
||||
|
||||
QString LegacyInstance::getStatusbarDescription()
|
||||
{
|
||||
if (flags() & VersionBrokenFlag)
|
||||
{
|
||||
return tr("Legacy : %1 (broken)").arg(intendedVersionId());
|
||||
}
|
||||
return tr("Legacy : %1").arg(intendedVersionId());
|
||||
}
|
||||
|
||||
QString LegacyInstance::lwjglFolder() const
|
||||
{
|
||||
return m_lwjglFolderSetting->get().toString();
|
||||
}
|
127
logic/minecraft/LegacyInstance.h
Normal file
127
logic/minecraft/LegacyInstance.h
Normal file
@ -0,0 +1,127 @@
|
||||
/* Copyright 2013-2015 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 "logic/minecraft/MinecraftInstance.h"
|
||||
#include "gui/pages/BasePageProvider.h"
|
||||
|
||||
class ModList;
|
||||
class Task;
|
||||
|
||||
class LegacyInstance : public MinecraftInstance, public BasePageProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
|
||||
|
||||
virtual void init() {};
|
||||
|
||||
/// Path to the instance's minecraft.jar
|
||||
QString runnableJar() const;
|
||||
|
||||
//! Path to the instance's modlist file.
|
||||
QString modListFile() const;
|
||||
|
||||
////// Edit Instance Dialog stuff //////
|
||||
virtual QList<BasePage *> getPages();
|
||||
virtual QString dialogTitle();
|
||||
|
||||
////// Mod Lists //////
|
||||
std::shared_ptr<ModList> jarModList() const ;
|
||||
virtual QList< Mod > getJarMods() const override;
|
||||
std::shared_ptr<ModList> coreModList() const;
|
||||
std::shared_ptr<ModList> loaderModList() const;
|
||||
std::shared_ptr<ModList> texturePackList() const override;
|
||||
|
||||
////// Directories //////
|
||||
QString libDir() const;
|
||||
QString savesDir() const;
|
||||
QString texturePacksDir() const;
|
||||
QString jarModsDir() const;
|
||||
QString binDir() const;
|
||||
QString loaderModsDir() const;
|
||||
QString coreModsDir() const;
|
||||
QString resourceDir() const;
|
||||
virtual QString instanceConfigFolder() const override;
|
||||
|
||||
/// Get the curent base jar of this instance. By default, it's the
|
||||
/// versions/$version/$version.jar
|
||||
QString baseJar() const;
|
||||
|
||||
/// the default base jar of this instance
|
||||
QString defaultBaseJar() const;
|
||||
/// the default custom base jar of this instance
|
||||
QString defaultCustomBaseJar() const;
|
||||
|
||||
/*!
|
||||
* Whether or not custom base jar is used
|
||||
*/
|
||||
bool shouldUseCustomBaseJar() const;
|
||||
void setShouldUseCustomBaseJar(bool val);
|
||||
|
||||
/*!
|
||||
* The value of the custom base jar
|
||||
*/
|
||||
QString customBaseJar() const;
|
||||
void setCustomBaseJar(QString val);
|
||||
|
||||
/*!
|
||||
* Whether or not the instance's minecraft.jar needs to be rebuilt.
|
||||
* If this is true, when the instance launches, its jar mods will be
|
||||
* re-added to a fresh minecraft.jar file.
|
||||
*/
|
||||
bool shouldRebuild() const;
|
||||
void setShouldRebuild(bool val);
|
||||
|
||||
virtual QString currentVersionId() const override;
|
||||
|
||||
//! The version of LWJGL that this instance uses.
|
||||
QString lwjglVersion() const;
|
||||
|
||||
//! Where the lwjgl versions foor this instance can be found... HACK HACK HACK
|
||||
QString lwjglFolder() const;
|
||||
|
||||
/// st the version of LWJGL libs this instance will use
|
||||
void setLWJGLVersion(QString val);
|
||||
|
||||
virtual QString intendedVersionId() const override;
|
||||
virtual bool setIntendedVersionId(QString version) override;
|
||||
|
||||
virtual QSet<QString> traits()
|
||||
{
|
||||
return {"legacy-instance", "texturepacks"};
|
||||
};
|
||||
|
||||
virtual bool shouldUpdate() const override;
|
||||
virtual void setShouldUpdate(bool val) override;
|
||||
virtual std::shared_ptr<Task> doUpdate() override;
|
||||
|
||||
virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override;
|
||||
virtual void cleanupAfterRun() override;
|
||||
|
||||
virtual QString getStatusbarDescription() override;
|
||||
|
||||
protected:
|
||||
mutable std::shared_ptr<ModList> jar_mod_list;
|
||||
mutable std::shared_ptr<ModList> core_mod_list;
|
||||
mutable std::shared_ptr<ModList> loader_mod_list;
|
||||
mutable std::shared_ptr<ModList> texture_pack_list;
|
||||
std::shared_ptr<Setting> m_lwjglFolderSetting;
|
||||
protected
|
||||
slots:
|
||||
virtual void jarModsChanged();
|
||||
};
|
467
logic/minecraft/LegacyUpdate.cpp
Normal file
467
logic/minecraft/LegacyUpdate.cpp
Normal file
@ -0,0 +1,467 @@
|
||||
/* Copyright 2013-2015 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 <QStringList>
|
||||
#include <pathutils.h>
|
||||
#include <quazip.h>
|
||||
#include <quazipfile.h>
|
||||
#include <JlCompress.h>
|
||||
#include <QDebug>
|
||||
|
||||
#include "logic/Env.h"
|
||||
#include "logic/BaseInstance.h"
|
||||
#include "logic/net/URLConstants.h"
|
||||
|
||||
#include "logic/minecraft/JarUtils.h"
|
||||
#include "logic/minecraft/LegacyUpdate.h"
|
||||
#include "logic/minecraft/LwjglVersionList.h"
|
||||
#include "logic/minecraft/MinecraftVersionList.h"
|
||||
#include "logic/minecraft/ModList.h"
|
||||
#include "logic/minecraft/LegacyInstance.h"
|
||||
|
||||
LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
|
||||
{
|
||||
}
|
||||
|
||||
void LegacyUpdate::executeTask()
|
||||
{
|
||||
fmllibsStart();
|
||||
}
|
||||
|
||||
void LegacyUpdate::fmllibsStart()
|
||||
{
|
||||
// Get the mod list
|
||||
LegacyInstance *inst = (LegacyInstance *)m_inst;
|
||||
auto modList = inst->jarModList();
|
||||
|
||||
bool forge_present = false;
|
||||
|
||||
QString version = inst->intendedVersionId();
|
||||
auto & fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
|
||||
if (!fmlLibsMapping.contains(version))
|
||||
{
|
||||
lwjglStart();
|
||||
return;
|
||||
}
|
||||
|
||||
auto &libList = fmlLibsMapping[version];
|
||||
|
||||
// determine if we need some libs for FML or forge
|
||||
setStatus(tr("Checking for FML libraries..."));
|
||||
for (unsigned i = 0; i < modList->size(); i++)
|
||||
{
|
||||
auto &mod = modList->operator[](i);
|
||||
|
||||
// do not use disabled mods.
|
||||
if (!mod.enabled())
|
||||
continue;
|
||||
|
||||
if (mod.type() != Mod::MOD_ZIPFILE)
|
||||
continue;
|
||||
|
||||
if (mod.mmc_id().contains("forge", Qt::CaseInsensitive))
|
||||
{
|
||||
forge_present = true;
|
||||
break;
|
||||
}
|
||||
if (mod.mmc_id().contains("fml", Qt::CaseInsensitive))
|
||||
{
|
||||
forge_present = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// we don't...
|
||||
if (!forge_present)
|
||||
{
|
||||
lwjglStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// now check the lib folder inside the instance for files.
|
||||
for (auto &lib : libList)
|
||||
{
|
||||
QFileInfo libInfo(PathCombine(inst->libDir(), lib.filename));
|
||||
if (libInfo.exists())
|
||||
continue;
|
||||
fmlLibsToProcess.append(lib);
|
||||
}
|
||||
|
||||
// if everything is in place, there's nothing to do here...
|
||||
if (fmlLibsToProcess.isEmpty())
|
||||
{
|
||||
lwjglStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// download missing libs to our place
|
||||
setStatus(tr("Dowloading FML libraries..."));
|
||||
auto dljob = new NetJob("FML libraries");
|
||||
auto metacache = ENV.metacache();
|
||||
for (auto &lib : fmlLibsToProcess)
|
||||
{
|
||||
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
|
||||
QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename
|
||||
: URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename;
|
||||
dljob->addNetAction(CacheDownload::make(QUrl(urlString), entry));
|
||||
}
|
||||
|
||||
connect(dljob, SIGNAL(succeeded()), SLOT(fmllibsFinished()));
|
||||
connect(dljob, SIGNAL(failed()), SLOT(fmllibsFailed()));
|
||||
connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
|
||||
legacyDownloadJob.reset(dljob);
|
||||
legacyDownloadJob->start();
|
||||
}
|
||||
|
||||
void LegacyUpdate::fmllibsFinished()
|
||||
{
|
||||
legacyDownloadJob.reset();
|
||||
if(!fmlLibsToProcess.isEmpty())
|
||||
{
|
||||
setStatus(tr("Copying FML libraries into the instance..."));
|
||||
LegacyInstance *inst = (LegacyInstance *)m_inst;
|
||||
auto metacache = ENV.metacache();
|
||||
int index = 0;
|
||||
for (auto &lib : fmlLibsToProcess)
|
||||
{
|
||||
progress(index, fmlLibsToProcess.size());
|
||||
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
|
||||
auto path = PathCombine(inst->libDir(), lib.filename);
|
||||
if(!ensureFilePathExists(path))
|
||||
{
|
||||
emitFailed(tr("Failed creating FML library folder inside the instance."));
|
||||
return;
|
||||
}
|
||||
if (!QFile::copy(entry->getFullPath(), PathCombine(inst->libDir(), lib.filename)))
|
||||
{
|
||||
emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename));
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
progress(index, fmlLibsToProcess.size());
|
||||
}
|
||||
lwjglStart();
|
||||
}
|
||||
|
||||
void LegacyUpdate::fmllibsFailed()
|
||||
{
|
||||
emitFailed("Game update failed: it was impossible to fetch the required FML libraries.");
|
||||
return;
|
||||
}
|
||||
|
||||
void LegacyUpdate::lwjglStart()
|
||||
{
|
||||
LegacyInstance *inst = (LegacyInstance *)m_inst;
|
||||
|
||||
lwjglVersion = inst->lwjglVersion();
|
||||
lwjglTargetPath = PathCombine(inst->lwjglFolder(), lwjglVersion);
|
||||
lwjglNativesPath = PathCombine(lwjglTargetPath, "natives");
|
||||
|
||||
// if the 'done' file exists, we don't have to download this again
|
||||
QFileInfo doneFile(PathCombine(lwjglTargetPath, "done"));
|
||||
if (doneFile.exists())
|
||||
{
|
||||
jarStart();
|
||||
return;
|
||||
}
|
||||
|
||||
auto list = std::dynamic_pointer_cast<LWJGLVersionList>(ENV.getVersionList("org.lwjgl.legacy"));
|
||||
if (!list->isLoaded())
|
||||
{
|
||||
emitFailed("Too soon! Let the LWJGL list load :)");
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus(tr("Downloading new LWJGL..."));
|
||||
auto version = std::dynamic_pointer_cast<LWJGLVersion>(list->findVersion(lwjglVersion));
|
||||
if (!version)
|
||||
{
|
||||
emitFailed("Game update failed: the selected LWJGL version is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
QString url = version->url();
|
||||
QUrl realUrl(url);
|
||||
QString hostname = realUrl.host();
|
||||
auto worker = ENV.qnam();
|
||||
QNetworkRequest req(realUrl);
|
||||
req.setRawHeader("Host", hostname.toLatin1());
|
||||
req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
|
||||
QNetworkReply *rep = worker->get(req);
|
||||
|
||||
m_reply = std::shared_ptr<QNetworkReply>(rep);
|
||||
connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
|
||||
connect(worker.get(), SIGNAL(finished(QNetworkReply *)),
|
||||
SLOT(lwjglFinished(QNetworkReply *)));
|
||||
}
|
||||
|
||||
void LegacyUpdate::lwjglFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (m_reply.get() != reply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
emitFailed("Failed to download: " + reply->errorString() +
|
||||
"\nSometimes you have to wait a bit if you download many LWJGL versions in "
|
||||
"a row. YMMV");
|
||||
return;
|
||||
}
|
||||
auto worker = ENV.qnam();
|
||||
// Here i check if there is a cookie for me in the reply and extract it
|
||||
QList<QNetworkCookie> cookies =
|
||||
qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader));
|
||||
if (cookies.count() != 0)
|
||||
{
|
||||
// you must tell which cookie goes with which url
|
||||
worker->cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net"));
|
||||
}
|
||||
|
||||
// here you can check for the 302 or whatever other header i need
|
||||
QVariant newLoc = reply->header(QNetworkRequest::LocationHeader);
|
||||
if (newLoc.isValid())
|
||||
{
|
||||
QString redirectedTo = reply->header(QNetworkRequest::LocationHeader).toString();
|
||||
QUrl realUrl(redirectedTo);
|
||||
QString hostname = realUrl.host();
|
||||
QNetworkRequest req(redirectedTo);
|
||||
req.setRawHeader("Host", hostname.toLatin1());
|
||||
req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
|
||||
QNetworkReply *rep = worker->get(req);
|
||||
connect(rep, SIGNAL(downloadProgress(qint64, qint64)),
|
||||
SIGNAL(progress(qint64, qint64)));
|
||||
m_reply = std::shared_ptr<QNetworkReply>(rep);
|
||||
return;
|
||||
}
|
||||
QFile saveMe("lwjgl.zip");
|
||||
saveMe.open(QIODevice::WriteOnly);
|
||||
saveMe.write(m_reply->readAll());
|
||||
saveMe.close();
|
||||
setStatus(tr("Installing new LWJGL..."));
|
||||
extractLwjgl();
|
||||
jarStart();
|
||||
}
|
||||
void LegacyUpdate::extractLwjgl()
|
||||
{
|
||||
// make sure the directories are there
|
||||
|
||||
bool success = ensureFolderPathExists(lwjglNativesPath);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
emitFailed("Failed to extract the lwjgl libs - error when creating required folders.");
|
||||
return;
|
||||
}
|
||||
|
||||
QuaZip zip("lwjgl.zip");
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
{
|
||||
emitFailed("Failed to extract the lwjgl libs - not a valid archive.");
|
||||
return;
|
||||
}
|
||||
|
||||
// and now we are going to access files inside it
|
||||
QuaZipFile file(&zip);
|
||||
const QString jarNames[] = {"jinput.jar", "lwjgl_util.jar", "lwjgl.jar"};
|
||||
for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile())
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
emitFailed("Failed to extract the lwjgl libs - error while reading archive.");
|
||||
return;
|
||||
}
|
||||
QuaZipFileInfo info;
|
||||
QString name = file.getActualFileName();
|
||||
if (name.endsWith('/'))
|
||||
{
|
||||
file.close();
|
||||
continue;
|
||||
}
|
||||
QString destFileName;
|
||||
// Look for the jars
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (name.endsWith(jarNames[i]))
|
||||
{
|
||||
destFileName = PathCombine(lwjglTargetPath, jarNames[i]);
|
||||
}
|
||||
}
|
||||
// Not found? look for the natives
|
||||
if (destFileName.isEmpty())
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
QString nativesDir = "windows";
|
||||
#else
|
||||
#ifdef Q_OS_MAC
|
||||
QString nativesDir = "macosx";
|
||||
#else
|
||||
QString nativesDir = "linux";
|
||||
#endif
|
||||
#endif
|
||||
if (name.contains(nativesDir))
|
||||
{
|
||||
int lastSlash = name.lastIndexOf('/');
|
||||
int lastBackSlash = name.lastIndexOf('\\');
|
||||
if (lastSlash != -1)
|
||||
name = name.mid(lastSlash + 1);
|
||||
else if (lastBackSlash != -1)
|
||||
name = name.mid(lastBackSlash + 1);
|
||||
destFileName = PathCombine(lwjglNativesPath, name);
|
||||
}
|
||||
}
|
||||
// Now if destFileName is still empty, go to the next file.
|
||||
if (!destFileName.isEmpty())
|
||||
{
|
||||
setStatus(tr("Installing new LWJGL - extracting ") + name + "...");
|
||||
QFile output(destFileName);
|
||||
output.open(QIODevice::WriteOnly);
|
||||
output.write(file.readAll()); // FIXME: wste of memory!?
|
||||
output.close();
|
||||
}
|
||||
file.close(); // do not forget to close!
|
||||
}
|
||||
zip.close();
|
||||
m_reply.reset();
|
||||
QFile doneFile(PathCombine(lwjglTargetPath, "done"));
|
||||
doneFile.open(QIODevice::WriteOnly);
|
||||
doneFile.write("done.");
|
||||
doneFile.close();
|
||||
}
|
||||
|
||||
void LegacyUpdate::lwjglFailed()
|
||||
{
|
||||
emitFailed("Bad stuff happened while trying to get the lwjgl libs...");
|
||||
}
|
||||
|
||||
void LegacyUpdate::jarStart()
|
||||
{
|
||||
LegacyInstance *inst = (LegacyInstance *)m_inst;
|
||||
if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar())
|
||||
{
|
||||
ModTheJar();
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus(tr("Checking for jar updates..."));
|
||||
// Make directories
|
||||
QDir binDir(inst->binDir());
|
||||
if (!binDir.exists() && !binDir.mkpath("."))
|
||||
{
|
||||
emitFailed("Failed to create bin folder.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a list of URLs that will need to be downloaded.
|
||||
setStatus(tr("Downloading new minecraft.jar ..."));
|
||||
|
||||
QString version_id = inst->intendedVersionId();
|
||||
QString localPath = version_id + "/" + version_id + ".jar";
|
||||
QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + localPath;
|
||||
|
||||
auto dljob = new NetJob("Minecraft.jar for version " + version_id);
|
||||
|
||||
auto metacache = ENV.metacache();
|
||||
auto entry = metacache->resolveEntry("versions", localPath);
|
||||
dljob->addNetAction(CacheDownload::make(QUrl(urlstr), entry));
|
||||
connect(dljob, SIGNAL(succeeded()), SLOT(jarFinished()));
|
||||
connect(dljob, SIGNAL(failed()), SLOT(jarFailed()));
|
||||
connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
|
||||
legacyDownloadJob.reset(dljob);
|
||||
legacyDownloadJob->start();
|
||||
}
|
||||
|
||||
void LegacyUpdate::jarFinished()
|
||||
{
|
||||
// process the jar
|
||||
ModTheJar();
|
||||
}
|
||||
|
||||
void LegacyUpdate::jarFailed()
|
||||
{
|
||||
// bad, bad
|
||||
emitFailed("Failed to download the minecraft jar. Try again later.");
|
||||
}
|
||||
|
||||
void LegacyUpdate::ModTheJar()
|
||||
{
|
||||
LegacyInstance *inst = (LegacyInstance *)m_inst;
|
||||
|
||||
if (!inst->shouldRebuild())
|
||||
{
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the mod list
|
||||
auto modList = inst->getJarMods();
|
||||
|
||||
QFileInfo runnableJar(inst->runnableJar());
|
||||
QFileInfo baseJar(inst->baseJar());
|
||||
bool base_is_custom = inst->shouldUseCustomBaseJar();
|
||||
|
||||
// Nothing to do if there are no jar mods to install, no backup and just the mc jar
|
||||
if (base_is_custom)
|
||||
{
|
||||
// yes, this can happen if the instance only has the runnable jar and not the base jar
|
||||
// it *could* be assumed that such an instance is vanilla, but that wouldn't be safe
|
||||
// because that's not something mmc4 guarantees
|
||||
if (runnableJar.isFile() && !baseJar.exists() && modList.empty())
|
||||
{
|
||||
inst->setShouldRebuild(false);
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
|
||||
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 "
|
||||
"be used on next run.");
|
||||
inst->setShouldRebuild(true);
|
||||
inst->setShouldUpdate(true);
|
||||
inst->setShouldUseCustomBaseJar(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!baseJar.exists())
|
||||
{
|
||||
emitFailed("The base jar " + baseJar.filePath() + " does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
if (runnableJar.exists() && !QFile::remove(runnableJar.filePath()))
|
||||
{
|
||||
emitFailed("Failed to delete old minecraft.jar");
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus(tr("Installing mods: Opening minecraft.jar ..."));
|
||||
|
||||
QString outputJarPath = runnableJar.filePath();
|
||||
QString inputJarPath = baseJar.filePath();
|
||||
|
||||
if(!JarUtils::createModdedJar(inputJarPath, outputJarPath, modList))
|
||||
{
|
||||
emitFailed(tr("Failed to create the custom Minecraft jar file."));
|
||||
return;
|
||||
}
|
||||
inst->setShouldRebuild(false);
|
||||
// inst->UpdateVersion(true);
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
72
logic/minecraft/LegacyUpdate.h
Normal file
72
logic/minecraft/LegacyUpdate.h
Normal file
@ -0,0 +1,72 @@
|
||||
/* Copyright 2013-2015 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "logic/net/NetJob.h"
|
||||
#include "logic/tasks/Task.h"
|
||||
#include "logic/minecraft/VersionFilterData.h"
|
||||
|
||||
class MinecraftVersion;
|
||||
class BaseInstance;
|
||||
class QuaZip;
|
||||
class Mod;
|
||||
|
||||
class LegacyUpdate : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0);
|
||||
virtual void executeTask();
|
||||
|
||||
private
|
||||
slots:
|
||||
void lwjglStart();
|
||||
void lwjglFinished(QNetworkReply *);
|
||||
void lwjglFailed();
|
||||
|
||||
void jarStart();
|
||||
void jarFinished();
|
||||
void jarFailed();
|
||||
|
||||
void fmllibsStart();
|
||||
void fmllibsFinished();
|
||||
void fmllibsFailed();
|
||||
|
||||
void extractLwjgl();
|
||||
|
||||
void ModTheJar();
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<QNetworkReply> m_reply;
|
||||
|
||||
// target version, determined during this task
|
||||
// MinecraftVersion *targetVersion;
|
||||
QString lwjglURL;
|
||||
QString lwjglVersion;
|
||||
|
||||
QString lwjglTargetPath;
|
||||
QString lwjglNativesPath;
|
||||
|
||||
private:
|
||||
NetJobPtr legacyDownloadJob;
|
||||
BaseInstance *m_inst = nullptr;
|
||||
QList<FMLlib> fmlLibsToProcess;
|
||||
};
|
189
logic/minecraft/LwjglVersionList.cpp
Normal file
189
logic/minecraft/LwjglVersionList.cpp
Normal file
@ -0,0 +1,189 @@
|
||||
/* Copyright 2013-2015 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "LwjglVersionList.h"
|
||||
#include "logic/Env.h"
|
||||
|
||||
#include <QtNetwork>
|
||||
#include <QtXml>
|
||||
#include <QRegExp>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#define RSS_URL "http://sourceforge.net/projects/java-game-lib/rss"
|
||||
|
||||
LWJGLVersionList::LWJGLVersionList(QObject *parent) : BaseVersionList(parent)
|
||||
{
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
|
||||
const PtrLWJGLVersion version = m_vlist.at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
return version->name();
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
return version->url();
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
return tr("Version");
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
return tr("LWJGL version name.");
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int LWJGLVersionList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool LWJGLVersionList::isLoading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void LWJGLVersionList::loadList()
|
||||
{
|
||||
Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)");
|
||||
|
||||
setLoading(true);
|
||||
auto worker = ENV.qnam();
|
||||
QNetworkRequest req(QUrl(RSS_URL));
|
||||
req.setRawHeader("Accept", "application/rss+xml, text/xml, */*");
|
||||
req.setRawHeader("User-Agent", "MultiMC/5.0 (Uncached)");
|
||||
reply = worker->get(req);
|
||||
connect(reply, SIGNAL(finished()), SLOT(netRequestComplete()));
|
||||
}
|
||||
|
||||
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
|
||||
{
|
||||
QDomNodeList elementList = parent.elementsByTagName(tagname);
|
||||
if (elementList.count())
|
||||
return elementList.at(0).toElement();
|
||||
else
|
||||
return QDomElement();
|
||||
}
|
||||
|
||||
void LWJGLVersionList::netRequestComplete()
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NoError)
|
||||
{
|
||||
QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip");
|
||||
Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", "LWJGL regex is invalid");
|
||||
|
||||
QDomDocument doc;
|
||||
|
||||
QString xmlErrorMsg;
|
||||
int errorLine;
|
||||
auto rawData = reply->readAll();
|
||||
if (!doc.setContent(rawData, false, &xmlErrorMsg, &errorLine))
|
||||
{
|
||||
failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " +
|
||||
QString::number(errorLine));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QDomNodeList items = doc.elementsByTagName("item");
|
||||
|
||||
QList<PtrLWJGLVersion> tempList;
|
||||
|
||||
for (int i = 0; i < items.length(); i++)
|
||||
{
|
||||
Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list",
|
||||
"XML element isn't an element... wat?");
|
||||
|
||||
QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link");
|
||||
if (linkElement.isNull())
|
||||
{
|
||||
qDebug() << "Link element" << i << "in RSS feed doesn't exist! Skipping.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QString link = linkElement.text();
|
||||
|
||||
// Make sure it's a download link.
|
||||
if (link.endsWith("/download") && link.contains(lwjglRegex))
|
||||
{
|
||||
QString name = link.mid(lwjglRegex.indexIn(link) + 6);
|
||||
// Subtract 4 here to remove the .zip file extension.
|
||||
name = name.left(lwjglRegex.matchedLength() - 10);
|
||||
|
||||
QUrl url(link);
|
||||
if (!url.isValid())
|
||||
{
|
||||
qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping.";
|
||||
continue;
|
||||
}
|
||||
qDebug() << "Discovered LWGL version" << name << "at" << link;
|
||||
tempList.append(std::make_shared<LWJGLVersion>(name, link));
|
||||
}
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
m_vlist.swap(tempList);
|
||||
endResetModel();
|
||||
|
||||
qDebug() << "Loaded LWJGL list.";
|
||||
finished();
|
||||
}
|
||||
else
|
||||
{
|
||||
failed("Failed to load LWJGL list. Network error: " + reply->errorString());
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void LWJGLVersionList::failed(QString msg)
|
||||
{
|
||||
qCritical() << msg;
|
||||
emit loadListFailed(msg);
|
||||
}
|
||||
|
||||
void LWJGLVersionList::finished()
|
||||
{
|
||||
emit loadListFinished();
|
||||
}
|
||||
|
||||
void LWJGLVersionList::setLoading(bool loading)
|
||||
{
|
||||
m_loading = loading;
|
||||
emit loadingStateUpdated(m_loading);
|
||||
}
|
154
logic/minecraft/LwjglVersionList.h
Normal file
154
logic/minecraft/LwjglVersionList.h
Normal file
@ -0,0 +1,154 @@
|
||||
/* Copyright 2013-2015 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QAbstractListModel>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <memory>
|
||||
|
||||
#include "logic/BaseVersion.h"
|
||||
#include "logic/BaseVersionList.h"
|
||||
|
||||
class LWJGLVersion;
|
||||
typedef std::shared_ptr<LWJGLVersion> PtrLWJGLVersion;
|
||||
|
||||
class LWJGLVersion : public BaseVersion
|
||||
{
|
||||
public:
|
||||
LWJGLVersion(const QString &name, const QString &url)
|
||||
: m_name(name), m_url(url)
|
||||
{
|
||||
}
|
||||
|
||||
virtual QString descriptor()
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
virtual QString name()
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
virtual QString typeString() const
|
||||
{
|
||||
return QObject::tr("Upstream");
|
||||
}
|
||||
|
||||
QString url() const
|
||||
{
|
||||
return m_url;
|
||||
}
|
||||
|
||||
protected:
|
||||
QString m_name;
|
||||
QString m_url;
|
||||
};
|
||||
|
||||
class LWJGLVersionList : public BaseVersionList
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LWJGLVersionList(QObject *parent = 0);
|
||||
|
||||
bool isLoaded()
|
||||
{
|
||||
return m_vlist.length() > 0;
|
||||
}
|
||||
virtual const BaseVersionPtr at(int i) const override
|
||||
{
|
||||
return m_vlist[i];
|
||||
}
|
||||
|
||||
virtual Task* getLoadTask()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual void sort() {};
|
||||
|
||||
virtual void updateListData(QList< BaseVersionPtr > versions) {};
|
||||
|
||||
int count() const
|
||||
{
|
||||
return m_vlist.length();
|
||||
}
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role) const;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
virtual int rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return count();
|
||||
}
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
|
||||
virtual bool isLoading() const;
|
||||
virtual bool errored() const
|
||||
{
|
||||
return m_errored;
|
||||
}
|
||||
|
||||
virtual QString lastErrorMsg() const
|
||||
{
|
||||
return m_lastErrorMsg;
|
||||
}
|
||||
|
||||
public
|
||||
slots:
|
||||
/*!
|
||||
* Loads the version list.
|
||||
* This is done asynchronously. On success, the loadListFinished() signal will
|
||||
* be emitted. The list model will be reset as well, resulting in the modelReset()
|
||||
* signal being emitted. Note that the model will be reset before loadListFinished() is
|
||||
* emitted.
|
||||
* If loading the list failed, the loadListFailed(QString msg),
|
||||
* signal will be emitted.
|
||||
*/
|
||||
virtual void loadList();
|
||||
|
||||
signals:
|
||||
/*!
|
||||
* Emitted when the list either starts or finishes loading.
|
||||
* \param loading Whether or not the list is loading.
|
||||
*/
|
||||
void loadingStateUpdated(bool loading);
|
||||
|
||||
void loadListFinished();
|
||||
|
||||
void loadListFailed(QString msg);
|
||||
|
||||
private:
|
||||
QList<PtrLWJGLVersion> m_vlist;
|
||||
|
||||
QNetworkReply *m_netReply;
|
||||
QNetworkReply *reply;
|
||||
|
||||
bool m_loading;
|
||||
bool m_errored;
|
||||
QString m_lastErrorMsg;
|
||||
|
||||
void failed(QString msg);
|
||||
|
||||
void finished();
|
||||
|
||||
void setLoading(bool loading);
|
||||
|
||||
private
|
||||
slots:
|
||||
virtual void netRequestComplete();
|
||||
};
|
@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
#include "logic/BaseInstance.h"
|
||||
#include "logic/minecraft/Mod.h"
|
||||
|
||||
class ModList;
|
||||
|
||||
class MinecraftInstance: public BaseInstance
|
||||
{
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include "logic/minecraft/VersionBuilder.h"
|
||||
#include "ProfileUtils.h"
|
||||
#include "NullProfileStrategy.h"
|
||||
#include "logic/OneSixInstance.h"
|
||||
|
||||
MinecraftProfile::MinecraftProfile(ProfileStrategy *strategy)
|
||||
: QAbstractListModel()
|
||||
|
377
logic/minecraft/Mod.cpp
Normal file
377
logic/minecraft/Mod.cpp
Normal file
@ -0,0 +1,377 @@
|
||||
/* Copyright 2013-2015 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <quazip.h>
|
||||
#include <quazipfile.h>
|
||||
|
||||
#include "Mod.h"
|
||||
#include <pathutils.h>
|
||||
#include "logic/settings/INIFile.h"
|
||||
#include <QDebug>
|
||||
|
||||
Mod::Mod(const QFileInfo &file)
|
||||
{
|
||||
repath(file);
|
||||
}
|
||||
|
||||
void Mod::repath(const QFileInfo &file)
|
||||
{
|
||||
m_file = file;
|
||||
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())
|
||||
{
|
||||
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());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return;
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile("mcmod.info"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
ReadMCModInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
else if (zip.setCurrentFile("forgeversion.properties"))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
ReadForgeInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
zip.close();
|
||||
}
|
||||
else if (m_type == MOD_FOLDER)
|
||||
{
|
||||
QFileInfo mcmod_info(PathCombine(m_file.filePath(), "mcmod.info"));
|
||||
if (mcmod_info.isFile())
|
||||
{
|
||||
QFile mcmod(mcmod_info.filePath());
|
||||
if (!mcmod.open(QIODevice::ReadOnly))
|
||||
return;
|
||||
auto data = mcmod.readAll();
|
||||
if (data.isEmpty() || data.isNull())
|
||||
return;
|
||||
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
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
|
||||
|
||||
// OLD format:
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
|
||||
void Mod::ReadMCModInfo(QByteArray contents)
|
||||
{
|
||||
auto getInfoFromArray = [&](QJsonArray arr)->void
|
||||
{
|
||||
if (!arr.at(0).isObject())
|
||||
return;
|
||||
auto firstObj = arr.at(0).toObject();
|
||||
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();
|
||||
m_updateurl = firstObj.value("updateUrl").toString();
|
||||
m_homeurl = m_homeurl.trimmed();
|
||||
if(!m_homeurl.isEmpty())
|
||||
{
|
||||
// fix up url.
|
||||
if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") &&
|
||||
!m_homeurl.startsWith("ftp://"))
|
||||
{
|
||||
m_homeurl.prepend("http://");
|
||||
}
|
||||
}
|
||||
m_description = firstObj.value("description").toString();
|
||||
QJsonArray authors = firstObj.value("authorList").toArray();
|
||||
if (authors.size() == 0)
|
||||
authors = firstObj.value("authors").toArray();
|
||||
|
||||
if (authors.size() == 0)
|
||||
m_authors = "";
|
||||
else if (authors.size() >= 1)
|
||||
{
|
||||
m_authors = authors.at(0).toString();
|
||||
for (int i = 1; i < authors.size(); i++)
|
||||
{
|
||||
m_authors += ", " + authors.at(i).toString();
|
||||
}
|
||||
}
|
||||
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
|
||||
if (jsonDoc.isArray())
|
||||
{
|
||||
getInfoFromArray(jsonDoc.array());
|
||||
}
|
||||
else if (jsonDoc.isObject())
|
||||
{
|
||||
auto val = jsonDoc.object().value("modinfoversion");
|
||||
if(val.isUndefined())
|
||||
val = jsonDoc.object().value("modListVersion");
|
||||
int version = val.toDouble();
|
||||
if (version != 2)
|
||||
{
|
||||
qCritical() << "BAD stuff happened to mod json:";
|
||||
qCritical() << contents;
|
||||
return;
|
||||
}
|
||||
auto arrVal = jsonDoc.object().value("modlist");
|
||||
if(arrVal.isUndefined())
|
||||
arrVal = jsonDoc.object().value("modList");
|
||||
if (arrVal.isArray())
|
||||
{
|
||||
getInfoFromArray(arrVal.toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mod::ReadForgeInfo(QByteArray contents)
|
||||
{
|
||||
// Read the data
|
||||
m_name = "Minecraft Forge";
|
||||
m_mod_id = "Forge";
|
||||
m_homeurl = "http://www.minecraftforge.net/forum/";
|
||||
INIFile ini;
|
||||
if (!ini.loadFile(contents))
|
||||
return;
|
||||
|
||||
QString major = ini.get("forge.major.number", "0").toString();
|
||||
QString minor = ini.get("forge.minor.number", "0").toString();
|
||||
QString revision = ini.get("forge.revision.number", "0").toString();
|
||||
QString build = ini.get("forge.build.number", "0").toString();
|
||||
|
||||
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 || t == MOD_LITEMOD)
|
||||
{
|
||||
qDebug() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath();
|
||||
success = QFile::copy(with.m_file.filePath(), m_file.filePath());
|
||||
}
|
||||
if (t == MOD_FOLDER)
|
||||
{
|
||||
success = copyPath(with.m_file.filePath(), m_file.path());
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
bool Mod::destroy()
|
||||
{
|
||||
if (m_type == MOD_FOLDER)
|
||||
{
|
||||
QDir d(m_file.filePath());
|
||||
if (d.removeRecursively())
|
||||
{
|
||||
m_type = MOD_UNKNOWN;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD)
|
||||
{
|
||||
QFile f(m_file.filePath());
|
||||
if (f.remove())
|
||||
{
|
||||
m_type = MOD_UNKNOWN;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Mod::version() const
|
||||
{
|
||||
switch (type())
|
||||
{
|
||||
case MOD_ZIPFILE:
|
||||
case MOD_LITEMOD:
|
||||
return m_version;
|
||||
case MOD_FOLDER:
|
||||
return "Folder";
|
||||
case MOD_SINGLEFILE:
|
||||
return "File";
|
||||
default:
|
||||
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();
|
||||
}
|
130
logic/minecraft/Mod.h
Normal file
130
logic/minecraft/Mod.h
Normal file
@ -0,0 +1,130 @@
|
||||
/* Copyright 2013-2015 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 <QFileInfo>
|
||||
|
||||
class Mod
|
||||
{
|
||||
public:
|
||||
enum ModType
|
||||
{
|
||||
MOD_UNKNOWN, //!< Indicates an unspecified mod type.
|
||||
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);
|
||||
|
||||
QFileInfo filename() const
|
||||
{
|
||||
return m_file;
|
||||
}
|
||||
QString mmc_id() const
|
||||
{
|
||||
return m_mmc_id;
|
||||
}
|
||||
QString mod_id() const
|
||||
{
|
||||
return m_mod_id;
|
||||
}
|
||||
ModType type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
QString mcversion() const
|
||||
{
|
||||
return m_mcversion;
|
||||
}
|
||||
;
|
||||
bool valid()
|
||||
{
|
||||
return m_type != MOD_UNKNOWN;
|
||||
}
|
||||
QString name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString version() const;
|
||||
|
||||
QString homeurl() const
|
||||
{
|
||||
return m_homeurl;
|
||||
}
|
||||
|
||||
QString description() const
|
||||
{
|
||||
return m_description;
|
||||
}
|
||||
|
||||
QString authors() const
|
||||
{
|
||||
return m_authors;
|
||||
}
|
||||
|
||||
QString credits() const
|
||||
{
|
||||
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
|
||||
bool replace(Mod &with);
|
||||
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
|
||||
void repath(const QFileInfo &file);
|
||||
|
||||
// WEAK compare operator - used for replacing mods
|
||||
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:
|
||||
|
||||
// FIXME: what do do with those? HMM...
|
||||
/*
|
||||
void ReadModInfoData(QString info);
|
||||
void ReadForgeInfoData(QString infoFileData);
|
||||
*/
|
||||
|
||||
QFileInfo m_file;
|
||||
QString m_mmc_id;
|
||||
QString m_mod_id;
|
||||
bool m_enabled = true;
|
||||
QString m_name;
|
||||
QString m_version;
|
||||
QString m_mcversion;
|
||||
QString m_homeurl;
|
||||
QString m_updateurl;
|
||||
QString m_description;
|
||||
QString m_authors;
|
||||
QString m_credits;
|
||||
|
||||
ModType m_type;
|
||||
};
|
610
logic/minecraft/ModList.cpp
Normal file
610
logic/minecraft/ModList.cpp
Normal file
@ -0,0 +1,610 @@
|
||||
/* Copyright 2013-2015 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 "ModList.h"
|
||||
#include <pathutils.h>
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
#include <QString>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QDebug>
|
||||
|
||||
ModList::ModList(const QString &dir, const QString &list_file)
|
||||
: QAbstractListModel(), m_dir(dir), m_list_file(list_file)
|
||||
{
|
||||
ensureFolderPathExists(m_dir.absolutePath());
|
||||
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
|
||||
QDir::NoSymLinks);
|
||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||
m_list_id = QUuid::createUuid().toString();
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
is_watching = false;
|
||||
connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
|
||||
SLOT(directoryChanged(QString)));
|
||||
}
|
||||
|
||||
void ModList::startWatching()
|
||||
{
|
||||
is_watching = m_watcher->addPath(m_dir.absolutePath());
|
||||
if (is_watching)
|
||||
{
|
||||
qDebug() << "Started watching " << m_dir.absolutePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Failed to start watching " << m_dir.absolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
void ModList::stopWatching()
|
||||
{
|
||||
is_watching = !m_watcher->removePath(m_dir.absolutePath());
|
||||
if (!is_watching)
|
||||
{
|
||||
qDebug() << "Stopped watching " << m_dir.absolutePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
void ModList::internalSort(QList<Mod> &what)
|
||||
{
|
||||
auto predicate = [](const Mod &left, const Mod &right)
|
||||
{
|
||||
if (left.name() == right.name())
|
||||
{
|
||||
return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0;
|
||||
}
|
||||
return left.name().localeAwareCompare(right.name()) < 0;
|
||||
};
|
||||
std::sort(what.begin(), what.end(), predicate);
|
||||
}
|
||||
|
||||
bool ModList::update()
|
||||
{
|
||||
if (!isValid())
|
||||
return false;
|
||||
|
||||
QList<Mod> orderedMods;
|
||||
QList<Mod> newMods;
|
||||
m_dir.refresh();
|
||||
auto folderContents = m_dir.entryInfoList();
|
||||
bool orderOrStateChanged = false;
|
||||
|
||||
// first, process the ordered items (if any)
|
||||
OrderList listOrder = readListFile();
|
||||
for (auto item : listOrder)
|
||||
{
|
||||
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
|
||||
orderedMods.append(Mod(info));
|
||||
if (isEnabled != item.enabled)
|
||||
orderOrStateChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
orderOrStateChanged = true;
|
||||
}
|
||||
}
|
||||
// if there are any untracked files...
|
||||
if (folderContents.size())
|
||||
{
|
||||
// the order surely changed!
|
||||
for (auto entry : folderContents)
|
||||
{
|
||||
newMods.append(Mod(entry));
|
||||
}
|
||||
internalSort(newMods);
|
||||
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())
|
||||
{
|
||||
qDebug() << "Mod list " << m_list_file << " changed!";
|
||||
saveListFile();
|
||||
emit changed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModList::directoryChanged(QString path)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
ModList::OrderList ModList::readListFile()
|
||||
{
|
||||
OrderList itemList;
|
||||
if (m_list_file.isNull() || m_list_file.isEmpty())
|
||||
return itemList;
|
||||
|
||||
QFile textFile(m_list_file);
|
||||
if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return OrderList();
|
||||
|
||||
QTextStream textStream;
|
||||
textStream.setAutoDetectUnicode(true);
|
||||
textStream.setDevice(&textFile);
|
||||
while (true)
|
||||
{
|
||||
QString line = textStream.readLine();
|
||||
if (line.isNull() || line.isEmpty())
|
||||
break;
|
||||
else
|
||||
{
|
||||
OrderItem it;
|
||||
it.enabled = !line.endsWith(".disabled");
|
||||
if (!it.enabled)
|
||||
{
|
||||
line.chop(9);
|
||||
}
|
||||
it.id = line;
|
||||
itemList.append(it);
|
||||
}
|
||||
}
|
||||
textFile.close();
|
||||
return itemList;
|
||||
}
|
||||
|
||||
bool ModList::saveListFile()
|
||||
{
|
||||
if (m_list_file.isNull() || m_list_file.isEmpty())
|
||||
return false;
|
||||
QFile textFile(m_list_file);
|
||||
if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
|
||||
return false;
|
||||
QTextStream textStream;
|
||||
textStream.setGenerateByteOrderMark(true);
|
||||
textStream.setCodec("UTF-8");
|
||||
textStream.setDevice(&textFile);
|
||||
for (auto mod : mods)
|
||||
{
|
||||
textStream << mod.mmc_id();
|
||||
if (!mod.enabled())
|
||||
textStream << ".disabled";
|
||||
textStream << endl;
|
||||
}
|
||||
textFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModList::isValid()
|
||||
{
|
||||
return m_dir.exists() && m_dir.isReadable();
|
||||
}
|
||||
|
||||
bool ModList::installMod(const QFileInfo &filename, int index)
|
||||
{
|
||||
if (!filename.exists() || !filename.isReadable() || index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Mod m(filename);
|
||||
if (!m.valid())
|
||||
return false;
|
||||
|
||||
// if it's already there, replace the original mod (in place)
|
||||
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))
|
||||
{
|
||||
|
||||
auto left = this->index(index);
|
||||
auto right = this->index(index, columnCount(QModelIndex()) - 1);
|
||||
emit dataChanged(left, right);
|
||||
saveListFile();
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto type = m.type();
|
||||
if (type == Mod::MOD_UNKNOWN)
|
||||
return false;
|
||||
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))
|
||||
return false;
|
||||
m.repath(newpath);
|
||||
beginInsertRows(QModelIndex(), index, index);
|
||||
mods.insert(index, m);
|
||||
endInsertRows();
|
||||
saveListFile();
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
else if (type == Mod::MOD_FOLDER)
|
||||
{
|
||||
|
||||
QString from = filename.filePath();
|
||||
QString to = PathCombine(m_dir.path(), filename.fileName());
|
||||
if (!copyPath(from, to))
|
||||
return false;
|
||||
m.repath(to);
|
||||
beginInsertRows(QModelIndex(), index, index);
|
||||
mods.insert(index, m);
|
||||
endInsertRows();
|
||||
saveListFile();
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModList::deleteMod(int index)
|
||||
{
|
||||
if (index >= mods.size() || index < 0)
|
||||
return false;
|
||||
Mod &m = mods[index];
|
||||
if (m.destroy())
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
mods.removeAt(index);
|
||||
endRemoveRows();
|
||||
saveListFile();
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModList::deleteMods(int first, int last)
|
||||
{
|
||||
for (int i = first; i <= last; i++)
|
||||
{
|
||||
Mod &m = mods[i];
|
||||
m.destroy();
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), first, last);
|
||||
mods.erase(mods.begin() + first, mods.begin() + last + 1);
|
||||
endRemoveRows();
|
||||
saveListFile();
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModList::moveModTo(int from, int to)
|
||||
{
|
||||
if (from < 0 || from >= mods.size())
|
||||
return false;
|
||||
if (to >= rowCount())
|
||||
to = rowCount() - 1;
|
||||
if (to == -1)
|
||||
to = rowCount() - 1;
|
||||
if (from == to)
|
||||
return false;
|
||||
int togap = to > from ? to + 1 : to;
|
||||
beginMoveRows(QModelIndex(), from, from, QModelIndex(), togap);
|
||||
mods.move(from, to);
|
||||
endMoveRows();
|
||||
saveListFile();
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModList::moveModUp(int from)
|
||||
{
|
||||
if (from > 0)
|
||||
return moveModTo(from, from - 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModList::moveModsUp(int first, int last)
|
||||
{
|
||||
if (first == 0)
|
||||
return false;
|
||||
|
||||
beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1);
|
||||
mods.move(first - 1, last);
|
||||
endMoveRows();
|
||||
saveListFile();
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModList::moveModDown(int from)
|
||||
{
|
||||
if (from < 0)
|
||||
return false;
|
||||
if (from < mods.size() - 1)
|
||||
return moveModTo(from, from + 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModList::moveModsDown(int first, int last)
|
||||
{
|
||||
if (last == mods.size() - 1)
|
||||
return false;
|
||||
|
||||
beginMoveRows(QModelIndex(), first, last, QModelIndex(), last + 2);
|
||||
mods.move(last + 1, first);
|
||||
endMoveRows();
|
||||
saveListFile();
|
||||
emit changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
int ModList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
QVariant ModList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
int row = index.row();
|
||||
int column = index.column();
|
||||
|
||||
if (row < 0 || row >= mods.size())
|
||||
return QVariant();
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (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 (column)
|
||||
{
|
||||
case ActiveColumn:
|
||||
return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
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
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (section)
|
||||
{
|
||||
case ActiveColumn:
|
||||
return QString();
|
||||
case NameColumn:
|
||||
return tr("Name");
|
||||
case VersionColumn:
|
||||
return tr("Version");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section)
|
||||
{
|
||||
case ActiveColumn:
|
||||
return tr("Is the mod enabled?");
|
||||
case NameColumn:
|
||||
return tr("The name of the mod.");
|
||||
case VersionColumn:
|
||||
return tr("The version of the mod.");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags ModList::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
|
||||
defaultFlags;
|
||||
else
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
}
|
||||
|
||||
QStringList ModList::mimeTypes() const
|
||||
{
|
||||
QStringList types;
|
||||
types << "text/uri-list";
|
||||
types << "text/plain";
|
||||
return types;
|
||||
}
|
||||
|
||||
Qt::DropActions ModList::supportedDropActions() const
|
||||
{
|
||||
// copy from outside, move from within and other mod lists
|
||||
return Qt::CopyAction | Qt::MoveAction;
|
||||
}
|
||||
|
||||
Qt::DropActions ModList::supportedDragActions() const
|
||||
{
|
||||
// move to other mod lists or VOID
|
||||
return Qt::MoveAction;
|
||||
}
|
||||
|
||||
QMimeData *ModList::mimeData(const QModelIndexList &indexes) const
|
||||
{
|
||||
QMimeData *data = new QMimeData();
|
||||
|
||||
if (indexes.size() == 0)
|
||||
return data;
|
||||
|
||||
auto idx = indexes[0];
|
||||
int row = idx.row();
|
||||
if (row < 0 || row >= mods.size())
|
||||
return data;
|
||||
|
||||
QStringList params;
|
||||
params << m_list_id << QString::number(row);
|
||||
data->setText(params.join('|'));
|
||||
return data;
|
||||
}
|
||||
bool ModList::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;
|
||||
if (parent.isValid())
|
||||
{
|
||||
row = parent.row();
|
||||
column = parent.column();
|
||||
}
|
||||
|
||||
if (row > rowCount())
|
||||
row = rowCount();
|
||||
if (row == -1)
|
||||
row = rowCount();
|
||||
if (column == -1)
|
||||
column = 0;
|
||||
qDebug() << "Drop row: " << row << " column: " << column;
|
||||
|
||||
// files dropped from outside?
|
||||
if (data->hasUrls())
|
||||
{
|
||||
bool was_watching = is_watching;
|
||||
if (was_watching)
|
||||
stopWatching();
|
||||
auto urls = data->urls();
|
||||
for (auto url : urls)
|
||||
{
|
||||
// only local files may be dropped...
|
||||
if (!url.isLocalFile())
|
||||
continue;
|
||||
QString filename = url.toLocalFile();
|
||||
installMod(filename, row);
|
||||
qDebug() << "installing: " << filename;
|
||||
// if there is no ordering, re-sort the list
|
||||
if (m_list_file.isEmpty())
|
||||
{
|
||||
beginResetModel();
|
||||
internalSort(mods);
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
if (was_watching)
|
||||
startWatching();
|
||||
return true;
|
||||
}
|
||||
else if (data->hasText())
|
||||
{
|
||||
QString sourcestr = data->text();
|
||||
auto list = sourcestr.split('|');
|
||||
if (list.size() != 2)
|
||||
return false;
|
||||
QString remoteId = list[0];
|
||||
int remoteIndex = list[1].toInt();
|
||||
qDebug() << "move: " << sourcestr;
|
||||
// no moving of things between two lists
|
||||
if (remoteId != m_list_id)
|
||||
return false;
|
||||
// no point moving to the same place...
|
||||
if (row == remoteIndex)
|
||||
return false;
|
||||
// otherwise, move the mod :D
|
||||
moveModTo(remoteIndex, row);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
158
logic/minecraft/ModList.h
Normal file
158
logic/minecraft/ModList.h
Normal file
@ -0,0 +1,158 @@
|
||||
/* Copyright 2013-2015 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 <QList>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "logic/minecraft/Mod.h"
|
||||
|
||||
class LegacyInstance;
|
||||
class BaseInstance;
|
||||
class QFileSystemWatcher;
|
||||
|
||||
/**
|
||||
* A legacy mod list.
|
||||
* Backed by a folder.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const;
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return mods.size();
|
||||
}
|
||||
;
|
||||
bool empty() const
|
||||
{
|
||||
return size() == 0;
|
||||
}
|
||||
Mod &operator[](size_t index)
|
||||
{
|
||||
return mods[index];
|
||||
}
|
||||
|
||||
/// Reloads the mod list and returns true if the list changed.
|
||||
virtual bool update();
|
||||
|
||||
/**
|
||||
* Adds the given mod to the list at the given index - if the list supports custom ordering
|
||||
*/
|
||||
virtual bool installMod(const QFileInfo &filename, int index = 0);
|
||||
|
||||
/// Deletes the mod at the given index.
|
||||
virtual bool deleteMod(int index);
|
||||
|
||||
/// Deletes all the selected mods
|
||||
virtual bool deleteMods(int first, int last);
|
||||
|
||||
/**
|
||||
* move the mod at index to the position N
|
||||
* 0 is the beginning of the list, length() is the end of the list.
|
||||
*/
|
||||
virtual bool moveModTo(int from, int to);
|
||||
|
||||
/**
|
||||
* move the mod at index one position upwards
|
||||
*/
|
||||
virtual bool moveModUp(int from);
|
||||
virtual bool moveModsUp(int first, int last);
|
||||
|
||||
/**
|
||||
* move the mod at index one position downwards
|
||||
*/
|
||||
virtual bool moveModDown(int from);
|
||||
virtual bool moveModsDown(int first, int last);
|
||||
|
||||
/// flags, mostly to support drag&drop
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
/// get data for drag action
|
||||
virtual QMimeData *mimeData(const QModelIndexList &indexes) const;
|
||||
/// get the supported mime types
|
||||
virtual QStringList mimeTypes() const;
|
||||
/// process data from drop action
|
||||
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent);
|
||||
/// what drag actions do we support?
|
||||
virtual Qt::DropActions supportedDragActions() const;
|
||||
|
||||
/// what drop actions do we support?
|
||||
virtual Qt::DropActions supportedDropActions() const;
|
||||
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
||||
virtual bool isValid();
|
||||
|
||||
QDir dir()
|
||||
{
|
||||
return m_dir;
|
||||
}
|
||||
|
||||
const QList<Mod> & allMods()
|
||||
{
|
||||
return mods;
|
||||
}
|
||||
|
||||
private:
|
||||
void internalSort(QList<Mod> & what);
|
||||
struct OrderItem
|
||||
{
|
||||
QString id;
|
||||
bool enabled = false;
|
||||
};
|
||||
typedef QList<OrderItem> OrderList;
|
||||
OrderList readListFile();
|
||||
bool saveListFile();
|
||||
private
|
||||
slots:
|
||||
void directoryChanged(QString path);
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
|
||||
protected:
|
||||
QFileSystemWatcher *m_watcher;
|
||||
bool is_watching;
|
||||
QDir m_dir;
|
||||
QString m_list_file;
|
||||
QString m_list_id;
|
||||
QList<Mod> mods;
|
||||
};
|
472
logic/minecraft/OneSixInstance.cpp
Normal file
472
logic/minecraft/OneSixInstance.cpp
Normal file
@ -0,0 +1,472 @@
|
||||
/* Copyright 2013-2015 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 <QIcon>
|
||||
#include <pathutils.h>
|
||||
#include <QDebug>
|
||||
#include "MMCError.h"
|
||||
|
||||
#include "logic/minecraft/OneSixInstance.h"
|
||||
|
||||
#include "logic/minecraft/OneSixUpdate.h"
|
||||
#include "logic/minecraft/MinecraftProfile.h"
|
||||
#include "logic/minecraft/VersionBuildError.h"
|
||||
#include "logic/minecraft/MinecraftProcess.h"
|
||||
#include "logic/minecraft/OneSixProfileStrategy.h"
|
||||
|
||||
#include "logic/minecraft/AssetsUtils.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
#include "gui/pagedialog/PageDialog.h"
|
||||
#include "gui/pages/VersionPage.h"
|
||||
#include "gui/pages/ModFolderPage.h"
|
||||
#include "gui/pages/ResourcePackPage.h"
|
||||
#include "gui/pages/TexturePackPage.h"
|
||||
#include "gui/pages/InstanceSettingsPage.h"
|
||||
#include "gui/pages/NotesPage.h"
|
||||
#include "gui/pages/ScreenshotsPage.h"
|
||||
#include "gui/pages/OtherLogsPage.h"
|
||||
OneSixInstance::OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
|
||||
: MinecraftInstance(globalSettings, settings, rootDir)
|
||||
{
|
||||
m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
|
||||
}
|
||||
|
||||
void OneSixInstance::init()
|
||||
{
|
||||
createProfile();
|
||||
}
|
||||
|
||||
void OneSixInstance::createProfile()
|
||||
{
|
||||
m_version.reset(new MinecraftProfile(new OneSixProfileStrategy(this)));
|
||||
}
|
||||
|
||||
|
||||
QList<BasePage *> OneSixInstance::getPages()
|
||||
{
|
||||
QList<BasePage *> values;
|
||||
values.append(new VersionPage(this));
|
||||
values.append(new ModFolderPage(this, loaderModList(), "mods", "loadermods",
|
||||
tr("Loader mods"), "Loader-mods"));
|
||||
values.append(new CoreModFolderPage(this, coreModList(), "coremods", "coremods",
|
||||
tr("Core mods"), "Core-mods"));
|
||||
values.append(new ResourcePackPage(this));
|
||||
values.append(new TexturePackPage(this));
|
||||
values.append(new NotesPage(this));
|
||||
values.append(new ScreenshotsPage(PathCombine(minecraftRoot(), "screenshots")));
|
||||
values.append(new InstanceSettingsPage(this));
|
||||
values.append(new OtherLogsPage(minecraftRoot()));
|
||||
return values;
|
||||
}
|
||||
|
||||
QString OneSixInstance::dialogTitle()
|
||||
{
|
||||
return tr("Edit Instance (%1)").arg(name());
|
||||
}
|
||||
|
||||
QSet<QString> OneSixInstance::traits()
|
||||
{
|
||||
auto version = getMinecraftProfile();
|
||||
if (!version)
|
||||
{
|
||||
return {"version-incomplete"};
|
||||
}
|
||||
else
|
||||
return version->traits;
|
||||
}
|
||||
|
||||
std::shared_ptr<Task> OneSixInstance::doUpdate()
|
||||
{
|
||||
return std::shared_ptr<Task>(new OneSixUpdate(this));
|
||||
}
|
||||
|
||||
QString replaceTokensIn(QString text, QMap<QString, QString> with)
|
||||
{
|
||||
QString result;
|
||||
QRegExp token_regexp("\\$\\{(.+)\\}");
|
||||
token_regexp.setMinimal(true);
|
||||
QStringList list;
|
||||
int tail = 0;
|
||||
int head = 0;
|
||||
while ((head = token_regexp.indexIn(text, head)) != -1)
|
||||
{
|
||||
result.append(text.mid(tail, head - tail));
|
||||
QString key = token_regexp.cap(1);
|
||||
auto iter = with.find(key);
|
||||
if (iter != with.end())
|
||||
{
|
||||
result.append(*iter);
|
||||
}
|
||||
head += token_regexp.matchedLength();
|
||||
tail = head;
|
||||
}
|
||||
result.append(text.mid(tail));
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session)
|
||||
{
|
||||
QString args_pattern = m_version->minecraftArguments;
|
||||
for (auto tweaker : m_version->tweakers)
|
||||
{
|
||||
args_pattern += " --tweakClass " + tweaker;
|
||||
}
|
||||
|
||||
QMap<QString, QString> token_mapping;
|
||||
// yggdrasil!
|
||||
token_mapping["auth_username"] = session->username;
|
||||
token_mapping["auth_session"] = session->session;
|
||||
token_mapping["auth_access_token"] = session->access_token;
|
||||
token_mapping["auth_player_name"] = session->player_name;
|
||||
token_mapping["auth_uuid"] = session->uuid;
|
||||
|
||||
// blatant self-promotion.
|
||||
token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5";
|
||||
|
||||
QString absRootDir = QDir(minecraftRoot()).absolutePath();
|
||||
token_mapping["game_directory"] = absRootDir;
|
||||
QString absAssetsDir = QDir("assets/").absolutePath();
|
||||
token_mapping["game_assets"] = AssetsUtils::reconstructAssets(m_version->assets).absolutePath();
|
||||
|
||||
token_mapping["user_properties"] = session->serializeUserProperties();
|
||||
token_mapping["user_type"] = session->user_type;
|
||||
// 1.7.3+ assets tokens
|
||||
token_mapping["assets_root"] = absAssetsDir;
|
||||
token_mapping["assets_index_name"] = m_version->assets;
|
||||
|
||||
QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
|
||||
for (int i = 0; i < parts.length(); i++)
|
||||
{
|
||||
parts[i] = replaceTokensIn(parts[i], token_mapping);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
BaseProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session)
|
||||
{
|
||||
QString launchScript;
|
||||
QIcon icon = ENV.icons()->getIcon(iconKey());
|
||||
auto pixmap = icon.pixmap(128, 128);
|
||||
pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG");
|
||||
|
||||
if (!m_version)
|
||||
return nullptr;
|
||||
|
||||
// libraries and class path.
|
||||
{
|
||||
auto libs = m_version->getActiveNormalLibs();
|
||||
for (auto lib : libs)
|
||||
{
|
||||
launchScript += "cp " + librariesPath().absoluteFilePath(lib->storagePath()) + "\n";
|
||||
}
|
||||
auto jarMods = getJarMods();
|
||||
if (!jarMods.isEmpty())
|
||||
{
|
||||
launchScript += "cp " + QDir(instanceRoot()).absoluteFilePath("temp.jar") + "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
QString relpath = m_version->id + "/" + m_version->id + ".jar";
|
||||
launchScript += "cp " + versionsPath().absoluteFilePath(relpath) + "\n";
|
||||
}
|
||||
}
|
||||
if (!m_version->mainClass.isEmpty())
|
||||
{
|
||||
launchScript += "mainClass " + m_version->mainClass + "\n";
|
||||
}
|
||||
if (!m_version->appletClass.isEmpty())
|
||||
{
|
||||
launchScript += "appletClass " + m_version->appletClass + "\n";
|
||||
}
|
||||
|
||||
// generic minecraft params
|
||||
for (auto param : processMinecraftArgs(session))
|
||||
{
|
||||
launchScript += "param " + param + "\n";
|
||||
}
|
||||
|
||||
// window size, title and state, legacy
|
||||
{
|
||||
QString windowParams;
|
||||
if (settings().get("LaunchMaximized").toBool())
|
||||
windowParams = "max";
|
||||
else
|
||||
windowParams = QString("%1x%2")
|
||||
.arg(settings().get("MinecraftWinWidth").toInt())
|
||||
.arg(settings().get("MinecraftWinHeight").toInt());
|
||||
launchScript += "windowTitle " + windowTitle() + "\n";
|
||||
launchScript += "windowParams " + windowParams + "\n";
|
||||
}
|
||||
|
||||
// legacy auth
|
||||
{
|
||||
launchScript += "userName " + session->player_name + "\n";
|
||||
launchScript += "sessionId " + session->session + "\n";
|
||||
}
|
||||
|
||||
// native libraries (mostly LWJGL)
|
||||
{
|
||||
QDir natives_dir(PathCombine(instanceRoot(), "natives/"));
|
||||
for (auto native : m_version->getActiveNativeLibs())
|
||||
{
|
||||
QFileInfo finfo(PathCombine("libraries", native->storagePath()));
|
||||
launchScript += "ext " + finfo.absoluteFilePath() + "\n";
|
||||
}
|
||||
launchScript += "natives " + natives_dir.absolutePath() + "\n";
|
||||
}
|
||||
|
||||
// traits. including legacyLaunch and others ;)
|
||||
for (auto trait : m_version->traits)
|
||||
{
|
||||
launchScript += "traits " + trait + "\n";
|
||||
}
|
||||
launchScript += "launcher onesix\n";
|
||||
|
||||
auto process = MinecraftProcess::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr()));
|
||||
process->setLaunchScript(launchScript);
|
||||
process->setWorkdir(minecraftRoot());
|
||||
process->setLogin(session);
|
||||
return process;
|
||||
}
|
||||
|
||||
void OneSixInstance::cleanupAfterRun()
|
||||
{
|
||||
QString target_dir = PathCombine(instanceRoot(), "natives/");
|
||||
QDir dir(target_dir);
|
||||
dir.removeRecursively();
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> OneSixInstance::loaderModList() const
|
||||
{
|
||||
if (!m_loader_mod_list)
|
||||
{
|
||||
m_loader_mod_list.reset(new ModList(loaderModsDir()));
|
||||
}
|
||||
m_loader_mod_list->update();
|
||||
return m_loader_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> OneSixInstance::coreModList() const
|
||||
{
|
||||
if (!m_core_mod_list)
|
||||
{
|
||||
m_core_mod_list.reset(new ModList(coreModsDir()));
|
||||
}
|
||||
m_core_mod_list->update();
|
||||
return m_core_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> OneSixInstance::resourcePackList() const
|
||||
{
|
||||
if (!m_resource_pack_list)
|
||||
{
|
||||
m_resource_pack_list.reset(new ModList(resourcePacksDir()));
|
||||
}
|
||||
m_resource_pack_list->update();
|
||||
return m_resource_pack_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> OneSixInstance::texturePackList() const
|
||||
{
|
||||
if (!m_texture_pack_list)
|
||||
{
|
||||
m_texture_pack_list.reset(new ModList(texturePacksDir()));
|
||||
}
|
||||
m_texture_pack_list->update();
|
||||
return m_texture_pack_list;
|
||||
}
|
||||
|
||||
bool OneSixInstance::setIntendedVersionId(QString version)
|
||||
{
|
||||
settings().set("IntendedVersion", version);
|
||||
if(getMinecraftProfile())
|
||||
{
|
||||
clearProfile();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QList< Mod > OneSixInstance::getJarMods() const
|
||||
{
|
||||
QList<Mod> mods;
|
||||
for (auto jarmod : m_version->jarMods)
|
||||
{
|
||||
QString filePath = jarmodsPath().absoluteFilePath(jarmod->name);
|
||||
mods.push_back(Mod(QFileInfo(filePath)));
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
|
||||
QString OneSixInstance::intendedVersionId() const
|
||||
{
|
||||
return settings().get("IntendedVersion").toString();
|
||||
}
|
||||
|
||||
void OneSixInstance::setShouldUpdate(bool)
|
||||
{
|
||||
}
|
||||
|
||||
bool OneSixInstance::shouldUpdate() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QString OneSixInstance::currentVersionId() const
|
||||
{
|
||||
return intendedVersionId();
|
||||
}
|
||||
|
||||
void OneSixInstance::reloadProfile()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_version->reload();
|
||||
unsetFlag(VersionBrokenFlag);
|
||||
emit versionReloaded();
|
||||
}
|
||||
catch (VersionIncomplete &error)
|
||||
{
|
||||
}
|
||||
catch (MMCError &error)
|
||||
{
|
||||
m_version->clear();
|
||||
setFlag(VersionBrokenFlag);
|
||||
// TODO: rethrow to show some error message(s)?
|
||||
emit versionReloaded();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void OneSixInstance::clearProfile()
|
||||
{
|
||||
m_version->clear();
|
||||
emit versionReloaded();
|
||||
}
|
||||
|
||||
std::shared_ptr<MinecraftProfile> OneSixInstance::getMinecraftProfile() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
QString OneSixInstance::getStatusbarDescription()
|
||||
{
|
||||
QStringList traits;
|
||||
if (flags() & VersionBrokenFlag)
|
||||
{
|
||||
traits.append(tr("broken"));
|
||||
}
|
||||
|
||||
if (traits.size())
|
||||
{
|
||||
return tr("Minecraft %1 (%2)").arg(intendedVersionId()).arg(traits.join(", "));
|
||||
}
|
||||
else
|
||||
{
|
||||
return tr("Minecraft %1").arg(intendedVersionId());
|
||||
}
|
||||
}
|
||||
|
||||
QDir OneSixInstance::librariesPath() const
|
||||
{
|
||||
return QDir::current().absoluteFilePath("libraries");
|
||||
}
|
||||
|
||||
QDir OneSixInstance::jarmodsPath() const
|
||||
{
|
||||
return QDir(jarModsDir());
|
||||
}
|
||||
|
||||
QDir OneSixInstance::versionsPath() const
|
||||
{
|
||||
return QDir::current().absoluteFilePath("versions");
|
||||
}
|
||||
|
||||
bool OneSixInstance::providesVersionFile() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OneSixInstance::reload()
|
||||
{
|
||||
if (BaseInstance::reload())
|
||||
{
|
||||
try
|
||||
{
|
||||
reloadProfile();
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString OneSixInstance::loaderModsDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "mods");
|
||||
}
|
||||
|
||||
QString OneSixInstance::coreModsDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "coremods");
|
||||
}
|
||||
|
||||
QString OneSixInstance::resourcePacksDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "resourcepacks");
|
||||
}
|
||||
|
||||
QString OneSixInstance::texturePacksDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "texturepacks");
|
||||
}
|
||||
|
||||
QString OneSixInstance::instanceConfigFolder() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "config");
|
||||
}
|
||||
|
||||
QString OneSixInstance::jarModsDir() const
|
||||
{
|
||||
return PathCombine(instanceRoot(), "jarmods");
|
||||
}
|
||||
|
||||
QString OneSixInstance::libDir() const
|
||||
{
|
||||
return PathCombine(minecraftRoot(), "lib");
|
||||
}
|
||||
|
||||
QStringList OneSixInstance::extraArguments() const
|
||||
{
|
||||
auto list = BaseInstance::extraArguments();
|
||||
auto version = getMinecraftProfile();
|
||||
if (!version)
|
||||
return list;
|
||||
auto jarMods = getJarMods();
|
||||
if (!jarMods.isEmpty())
|
||||
{
|
||||
list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true",
|
||||
"-Dfml.ignorePatchDiscrepancies=true"});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
std::shared_ptr<OneSixInstance> OneSixInstance::getSharedPtr()
|
||||
{
|
||||
return std::dynamic_pointer_cast<OneSixInstance>(BaseInstance::getSharedPtr());
|
||||
}
|
109
logic/minecraft/OneSixInstance.h
Normal file
109
logic/minecraft/OneSixInstance.h
Normal file
@ -0,0 +1,109 @@
|
||||
/* Copyright 2013-2015 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 "logic/minecraft/MinecraftInstance.h"
|
||||
|
||||
#include "logic/minecraft/MinecraftProfile.h"
|
||||
#include "logic/minecraft/ModList.h"
|
||||
#include "gui/pages/BasePageProvider.h"
|
||||
|
||||
class OneSixInstance : public MinecraftInstance, public BasePageProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
|
||||
virtual ~OneSixInstance(){};
|
||||
|
||||
virtual void init();
|
||||
|
||||
////// Edit Instance Dialog stuff //////
|
||||
virtual QList<BasePage *> getPages();
|
||||
virtual QString dialogTitle();
|
||||
|
||||
////// Mod Lists //////
|
||||
std::shared_ptr<ModList> loaderModList() const;
|
||||
std::shared_ptr<ModList> coreModList() const;
|
||||
std::shared_ptr<ModList> resourcePackList() const override;
|
||||
std::shared_ptr<ModList> texturePackList() const override;
|
||||
virtual QList<Mod> getJarMods() const override;
|
||||
virtual void createProfile();
|
||||
|
||||
virtual QSet<QString> traits();
|
||||
|
||||
////// Directories and files //////
|
||||
QString jarModsDir() const;
|
||||
QString resourcePacksDir() const;
|
||||
QString texturePacksDir() const;
|
||||
QString loaderModsDir() const;
|
||||
QString coreModsDir() const;
|
||||
QString libDir() const;
|
||||
virtual QString instanceConfigFolder() const override;
|
||||
|
||||
virtual std::shared_ptr<Task> doUpdate() override;
|
||||
virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override;
|
||||
|
||||
virtual void cleanupAfterRun() override;
|
||||
|
||||
virtual QString intendedVersionId() const override;
|
||||
virtual bool setIntendedVersionId(QString version) override;
|
||||
|
||||
virtual QString currentVersionId() const override;
|
||||
|
||||
virtual bool shouldUpdate() const override;
|
||||
virtual void setShouldUpdate(bool val) override;
|
||||
|
||||
/**
|
||||
* reload the profile, including version json files.
|
||||
*
|
||||
* throws various exceptions :3
|
||||
*/
|
||||
void reloadProfile();
|
||||
|
||||
/// clears all version information in preparation for an update
|
||||
void clearProfile();
|
||||
|
||||
/// get the current full version info
|
||||
std::shared_ptr<MinecraftProfile> getMinecraftProfile() const;
|
||||
|
||||
virtual QString getStatusbarDescription() override;
|
||||
|
||||
virtual QDir jarmodsPath() const;
|
||||
virtual QDir librariesPath() const;
|
||||
virtual QDir versionsPath() const;
|
||||
virtual bool providesVersionFile() const;
|
||||
|
||||
bool reload() override;
|
||||
|
||||
virtual QStringList extraArguments() const override;
|
||||
|
||||
std::shared_ptr<OneSixInstance> getSharedPtr();
|
||||
|
||||
signals:
|
||||
void versionReloaded();
|
||||
|
||||
private:
|
||||
QStringList processMinecraftArgs(AuthSessionPtr account);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MinecraftProfile> m_version;
|
||||
mutable std::shared_ptr<ModList> m_loader_mod_list;
|
||||
mutable std::shared_ptr<ModList> m_core_mod_list;
|
||||
mutable std::shared_ptr<ModList> m_resource_pack_list;
|
||||
mutable std::shared_ptr<ModList> m_texture_pack_list;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<OneSixInstance>)
|
@ -1,6 +1,6 @@
|
||||
#include "logic/minecraft/OneSixProfileStrategy.h"
|
||||
#include "logic/minecraft/VersionBuildError.h"
|
||||
#include "logic/OneSixInstance.h"
|
||||
#include "logic/minecraft/OneSixInstance.h"
|
||||
#include "logic/minecraft/MinecraftVersionList.h"
|
||||
#include "logic/Env.h"
|
||||
|
||||
|
445
logic/minecraft/OneSixUpdate.cpp
Normal file
445
logic/minecraft/OneSixUpdate.cpp
Normal file
@ -0,0 +1,445 @@
|
||||
/* Copyright 2013-2015 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 "logic/Env.h"
|
||||
#include "OneSixUpdate.h"
|
||||
|
||||
#include <QtNetwork>
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QTextStream>
|
||||
#include <QDataStream>
|
||||
#include <pathutils.h>
|
||||
#include <JlCompress.h>
|
||||
|
||||
#include "logic/BaseInstance.h"
|
||||
#include "logic/minecraft/MinecraftVersionList.h"
|
||||
#include "logic/minecraft/MinecraftProfile.h"
|
||||
#include "logic/minecraft/OneSixLibrary.h"
|
||||
#include "logic/minecraft/OneSixInstance.h"
|
||||
#include "logic/forge/ForgeMirrors.h"
|
||||
#include "logic/net/URLConstants.h"
|
||||
#include "logic/minecraft/AssetsUtils.h"
|
||||
#include "logic/minecraft/JarUtils.h"
|
||||
|
||||
OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
|
||||
{
|
||||
}
|
||||
|
||||
void OneSixUpdate::executeTask()
|
||||
{
|
||||
// Make directories
|
||||
QDir mcDir(m_inst->minecraftRoot());
|
||||
if (!mcDir.exists() && !mcDir.mkpath("."))
|
||||
{
|
||||
emitFailed(tr("Failed to create folder for minecraft binaries."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a pointer to the version object that corresponds to the instance's version.
|
||||
targetVersion = std::dynamic_pointer_cast<MinecraftVersion>(
|
||||
ENV.getVersion("net.minecraft", m_inst->intendedVersionId()));
|
||||
if (targetVersion == nullptr)
|
||||
{
|
||||
// don't do anything if it was invalid
|
||||
emitFailed(tr("The specified Minecraft version is invalid. Choose a different one."));
|
||||
return;
|
||||
}
|
||||
if (m_inst->providesVersionFile() || !targetVersion->needsUpdate())
|
||||
{
|
||||
qDebug() << "Instance either provides a version file or doesn't need an update.";
|
||||
jarlibStart();
|
||||
return;
|
||||
}
|
||||
versionUpdateTask = std::dynamic_pointer_cast<MinecraftVersionList>(ENV.getVersionList("net.minecraft"))->createUpdateTask(m_inst->intendedVersionId());
|
||||
if (!versionUpdateTask)
|
||||
{
|
||||
qDebug() << "Didn't spawn an update task.";
|
||||
jarlibStart();
|
||||
return;
|
||||
}
|
||||
connect(versionUpdateTask.get(), SIGNAL(succeeded()), SLOT(jarlibStart()));
|
||||
connect(versionUpdateTask.get(), SIGNAL(failed(QString)), SLOT(versionUpdateFailed(QString)));
|
||||
connect(versionUpdateTask.get(), SIGNAL(progress(qint64, qint64)),
|
||||
SIGNAL(progress(qint64, qint64)));
|
||||
setStatus(tr("Getting the version files from Mojang..."));
|
||||
versionUpdateTask->start();
|
||||
}
|
||||
|
||||
void OneSixUpdate::versionUpdateFailed(QString reason)
|
||||
{
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
void OneSixUpdate::assetIndexStart()
|
||||
{
|
||||
setStatus(tr("Updating assets index..."));
|
||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||
std::shared_ptr<MinecraftProfile> version = inst->getMinecraftProfile();
|
||||
QString assetName = version->assets;
|
||||
QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json";
|
||||
QString localPath = assetName + ".json";
|
||||
auto job = new NetJob(tr("Asset index for %1").arg(inst->name()));
|
||||
|
||||
auto metacache = ENV.metacache();
|
||||
auto entry = metacache->resolveEntry("asset_indexes", localPath);
|
||||
job->addNetAction(CacheDownload::make(indexUrl, entry));
|
||||
jarlibDownloadJob.reset(job);
|
||||
|
||||
connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished()));
|
||||
connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetIndexFailed()));
|
||||
connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)),
|
||||
SIGNAL(progress(qint64, qint64)));
|
||||
|
||||
jarlibDownloadJob->start();
|
||||
}
|
||||
|
||||
void OneSixUpdate::assetIndexFinished()
|
||||
{
|
||||
AssetsIndex index;
|
||||
|
||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||
std::shared_ptr<MinecraftProfile> version = inst->getMinecraftProfile();
|
||||
QString assetName = version->assets;
|
||||
|
||||
QString asset_fname = "assets/indexes/" + assetName + ".json";
|
||||
if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index))
|
||||
{
|
||||
auto metacache = ENV.metacache();
|
||||
auto entry = metacache->resolveEntry("asset_indexes", assetName + ".json");
|
||||
metacache->evictEntry(entry);
|
||||
emitFailed(tr("Failed to read the assets index!"));
|
||||
}
|
||||
|
||||
QList<Md5EtagDownloadPtr> dls;
|
||||
for (auto object : index.objects.values())
|
||||
{
|
||||
QString objectName = object.hash.left(2) + "/" + object.hash;
|
||||
QFileInfo objectFile("assets/objects/" + objectName);
|
||||
if ((!objectFile.isFile()) || (objectFile.size() != object.size))
|
||||
{
|
||||
auto objectDL = MD5EtagDownload::make(
|
||||
QUrl("http://" + URLConstants::RESOURCE_BASE + objectName),
|
||||
objectFile.filePath());
|
||||
objectDL->m_total_progress = object.size;
|
||||
dls.append(objectDL);
|
||||
}
|
||||
}
|
||||
if (dls.size())
|
||||
{
|
||||
setStatus(tr("Getting the assets files from Mojang..."));
|
||||
auto job = new NetJob(tr("Assets for %1").arg(inst->name()));
|
||||
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)));
|
||||
jarlibDownloadJob->start();
|
||||
return;
|
||||
}
|
||||
assetsFinished();
|
||||
}
|
||||
|
||||
void OneSixUpdate::assetIndexFailed()
|
||||
{
|
||||
emitFailed(tr("Failed to download the assets index!"));
|
||||
}
|
||||
|
||||
void OneSixUpdate::assetsFinished()
|
||||
{
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
void OneSixUpdate::assetsFailed()
|
||||
{
|
||||
emitFailed(tr("Failed to download assets!"));
|
||||
}
|
||||
|
||||
void OneSixUpdate::jarlibStart()
|
||||
{
|
||||
setStatus(tr("Getting the library files from Mojang..."));
|
||||
qDebug() << m_inst->name() << ": downloading libraries";
|
||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||
try
|
||||
{
|
||||
inst->reloadProfile();
|
||||
}
|
||||
catch (MMCError &e)
|
||||
{
|
||||
emitFailed(e.cause());
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
emitFailed(tr("Failed to load the version description file for reasons unknown."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a list of URLs that will need to be downloaded.
|
||||
std::shared_ptr<MinecraftProfile> version = inst->getMinecraftProfile();
|
||||
// minecraft.jar for this version
|
||||
{
|
||||
QString version_id = version->id;
|
||||
QString localPath = version_id + "/" + version_id + ".jar";
|
||||
QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + localPath;
|
||||
|
||||
auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name()));
|
||||
|
||||
auto metacache = ENV.metacache();
|
||||
auto entry = metacache->resolveEntry("versions", localPath);
|
||||
job->addNetAction(CacheDownload::make(QUrl(urlstr), entry));
|
||||
jarHashOnEntry = entry->md5sum;
|
||||
|
||||
jarlibDownloadJob.reset(job);
|
||||
}
|
||||
|
||||
auto libs = version->getActiveNativeLibs();
|
||||
libs.append(version->getActiveNormalLibs());
|
||||
|
||||
auto metacache = ENV.metacache();
|
||||
QList<ForgeXzDownloadPtr> ForgeLibs;
|
||||
QList<std::shared_ptr<OneSixLibrary>> brokenLocalLibs;
|
||||
|
||||
for (auto lib : libs)
|
||||
{
|
||||
if (lib->hint() == "local")
|
||||
{
|
||||
if (!lib->filesExist(m_inst->librariesPath()))
|
||||
brokenLocalLibs.append(lib);
|
||||
continue;
|
||||
}
|
||||
|
||||
QString raw_storage = lib->storagePath();
|
||||
QString raw_dl = lib->downloadUrl();
|
||||
|
||||
auto f = [&](QString storage, QString dl)
|
||||
{
|
||||
auto entry = metacache->resolveEntry("libraries", storage);
|
||||
if (entry->stale)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (!brokenLocalLibs.empty())
|
||||
{
|
||||
jarlibDownloadJob.reset();
|
||||
QStringList failed;
|
||||
for (auto brokenLib : brokenLocalLibs)
|
||||
{
|
||||
failed.append(brokenLib->files());
|
||||
}
|
||||
QString failed_all = failed.join("\n");
|
||||
emitFailed(tr("Some libraries marked as 'local' are missing their jar "
|
||||
"files:\n%1\n\nYou'll have to correct this problem manually. If this is "
|
||||
"an externally tracked instance, make sure to run it at least once "
|
||||
"outside of MultiMC.").arg(failed_all));
|
||||
return;
|
||||
}
|
||||
// TODO: think about how to propagate this from the original json file... or IF AT ALL
|
||||
QString forgeMirrorList = "http://files.minecraftforge.net/mirror-brand.list";
|
||||
if (!ForgeLibs.empty())
|
||||
{
|
||||
jarlibDownloadJob->addNetAction(
|
||||
ForgeMirrors::make(ForgeLibs, jarlibDownloadJob, forgeMirrorList));
|
||||
}
|
||||
|
||||
connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished()));
|
||||
connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(jarlibFailed()));
|
||||
connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)),
|
||||
SIGNAL(progress(qint64, qint64)));
|
||||
|
||||
jarlibDownloadJob->start();
|
||||
}
|
||||
|
||||
void OneSixUpdate::jarlibFinished()
|
||||
{
|
||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||
std::shared_ptr<MinecraftProfile> version = inst->getMinecraftProfile();
|
||||
|
||||
// nuke obsolete stripped jar(s) if needed
|
||||
QString version_id = version->id;
|
||||
QString strippedPath = version_id + "/" + version_id + "-stripped.jar";
|
||||
QFile strippedJar(strippedPath);
|
||||
if(strippedJar.exists())
|
||||
{
|
||||
strippedJar.remove();
|
||||
}
|
||||
auto finalJarPath = QDir(m_inst->instanceRoot()).absoluteFilePath("temp.jar");
|
||||
QFile finalJar(finalJarPath);
|
||||
if(finalJar.exists())
|
||||
{
|
||||
if(!finalJar.remove())
|
||||
{
|
||||
emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// create temporary modded jar, if needed
|
||||
auto jarMods = inst->getJarMods();
|
||||
if(jarMods.size())
|
||||
{
|
||||
auto sourceJarPath = m_inst->versionsPath().absoluteFilePath(version->id + "/" + version->id + ".jar");
|
||||
QString localPath = version_id + "/" + version_id + ".jar";
|
||||
auto metacache = ENV.metacache();
|
||||
auto entry = metacache->resolveEntry("versions", localPath);
|
||||
QString fullJarPath = entry->getFullPath();
|
||||
if(!JarUtils::createModdedJar(sourceJarPath, finalJarPath, jarMods))
|
||||
{
|
||||
emitFailed(tr("Failed to create the custom Minecraft jar file."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (version->traits.contains("legacyFML"))
|
||||
{
|
||||
fmllibsStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
assetIndexStart();
|
||||
}
|
||||
}
|
||||
|
||||
void OneSixUpdate::jarlibFailed()
|
||||
{
|
||||
QStringList failed = jarlibDownloadJob->getFailedFiles();
|
||||
QString failed_all = failed.join("\n");
|
||||
emitFailed(
|
||||
tr("Failed to download the following files:\n%1\n\nPlease try again.").arg(failed_all));
|
||||
}
|
||||
|
||||
void OneSixUpdate::fmllibsStart()
|
||||
{
|
||||
// Get the mod list
|
||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||
std::shared_ptr<MinecraftProfile> fullversion = inst->getMinecraftProfile();
|
||||
bool forge_present = false;
|
||||
|
||||
QString version = inst->intendedVersionId();
|
||||
auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
|
||||
if (!fmlLibsMapping.contains(version))
|
||||
{
|
||||
assetIndexStart();
|
||||
return;
|
||||
}
|
||||
|
||||
auto &libList = fmlLibsMapping[version];
|
||||
|
||||
// determine if we need some libs for FML or forge
|
||||
setStatus(tr("Checking for FML libraries..."));
|
||||
forge_present = (fullversion->versionPatch("net.minecraftforge") != nullptr);
|
||||
// we don't...
|
||||
if (!forge_present)
|
||||
{
|
||||
assetIndexStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// now check the lib folder inside the instance for files.
|
||||
for (auto &lib : libList)
|
||||
{
|
||||
QFileInfo libInfo(PathCombine(inst->libDir(), lib.filename));
|
||||
if (libInfo.exists())
|
||||
continue;
|
||||
fmlLibsToProcess.append(lib);
|
||||
}
|
||||
|
||||
// if everything is in place, there's nothing to do here...
|
||||
if (fmlLibsToProcess.isEmpty())
|
||||
{
|
||||
assetIndexStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// download missing libs to our place
|
||||
setStatus(tr("Dowloading FML libraries..."));
|
||||
auto dljob = new NetJob("FML libraries");
|
||||
auto metacache = ENV.metacache();
|
||||
for (auto &lib : fmlLibsToProcess)
|
||||
{
|
||||
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
|
||||
QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename
|
||||
: URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename;
|
||||
dljob->addNetAction(CacheDownload::make(QUrl(urlString), entry));
|
||||
}
|
||||
|
||||
connect(dljob, SIGNAL(succeeded()), SLOT(fmllibsFinished()));
|
||||
connect(dljob, SIGNAL(failed()), SLOT(fmllibsFailed()));
|
||||
connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
|
||||
legacyDownloadJob.reset(dljob);
|
||||
legacyDownloadJob->start();
|
||||
}
|
||||
|
||||
void OneSixUpdate::fmllibsFinished()
|
||||
{
|
||||
legacyDownloadJob.reset();
|
||||
if (!fmlLibsToProcess.isEmpty())
|
||||
{
|
||||
setStatus(tr("Copying FML libraries into the instance..."));
|
||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||
auto metacache = ENV.metacache();
|
||||
int index = 0;
|
||||
for (auto &lib : fmlLibsToProcess)
|
||||
{
|
||||
progress(index, fmlLibsToProcess.size());
|
||||
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
|
||||
auto path = PathCombine(inst->libDir(), lib.filename);
|
||||
if (!ensureFilePathExists(path))
|
||||
{
|
||||
emitFailed(tr("Failed creating FML library folder inside the instance."));
|
||||
return;
|
||||
}
|
||||
if (!QFile::copy(entry->getFullPath(), PathCombine(inst->libDir(), lib.filename)))
|
||||
{
|
||||
emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename));
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
progress(index, fmlLibsToProcess.size());
|
||||
}
|
||||
assetIndexStart();
|
||||
}
|
||||
|
||||
void OneSixUpdate::fmllibsFailed()
|
||||
{
|
||||
emitFailed("Game update failed: it was impossible to fetch the required FML libraries.");
|
||||
return;
|
||||
}
|
||||
|
68
logic/minecraft/OneSixUpdate.h
Normal file
68
logic/minecraft/OneSixUpdate.h
Normal file
@ -0,0 +1,68 @@
|
||||
/* Copyright 2013-2015 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "logic/net/NetJob.h"
|
||||
#include "logic/tasks/Task.h"
|
||||
#include "logic/minecraft/VersionFilterData.h"
|
||||
#include <quazip.h>
|
||||
|
||||
class MinecraftVersion;
|
||||
class OneSixInstance;
|
||||
|
||||
class OneSixUpdate : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OneSixUpdate(OneSixInstance *inst, QObject *parent = 0);
|
||||
virtual void executeTask();
|
||||
|
||||
private
|
||||
slots:
|
||||
void versionUpdateFailed(QString reason);
|
||||
|
||||
void jarlibStart();
|
||||
void jarlibFinished();
|
||||
void jarlibFailed();
|
||||
|
||||
void fmllibsStart();
|
||||
void fmllibsFinished();
|
||||
void fmllibsFailed();
|
||||
|
||||
void assetIndexStart();
|
||||
void assetIndexFinished();
|
||||
void assetIndexFailed();
|
||||
|
||||
void assetsFinished();
|
||||
void assetsFailed();
|
||||
|
||||
private:
|
||||
NetJobPtr jarlibDownloadJob;
|
||||
NetJobPtr legacyDownloadJob;
|
||||
|
||||
/// target version, determined during this task
|
||||
std::shared_ptr<MinecraftVersion> targetVersion;
|
||||
/// the task that is spawned for version updates
|
||||
std::shared_ptr<Task> versionUpdateTask;
|
||||
|
||||
OneSixInstance *m_inst = nullptr;
|
||||
QString jarHashOnEntry;
|
||||
QList<FMLlib> fmlLibsToProcess;
|
||||
};
|
47
logic/minecraft/SkinUtils.cpp
Normal file
47
logic/minecraft/SkinUtils.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
/* Copyright 2013-2015 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 "logic/minecraft/SkinUtils.h"
|
||||
#include "logic/net/HttpMetaCache.h"
|
||||
#include "logic/Env.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace SkinUtils
|
||||
{
|
||||
/*
|
||||
* Given a username, return a pixmap of the cached skin (if it exists), QPixmap() otherwise
|
||||
*/
|
||||
QPixmap getFaceFromCache(QString username, int height, int width)
|
||||
{
|
||||
QFile fskin(ENV.metacache()
|
||||
->resolveEntry("skins", username + ".png")
|
||||
->getFullPath());
|
||||
|
||||
if (fskin.exists())
|
||||
{
|
||||
QPixmap skin(fskin.fileName());
|
||||
if(!skin.isNull())
|
||||
{
|
||||
return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
return QPixmap();
|
||||
}
|
||||
}
|
23
logic/minecraft/SkinUtils.h
Normal file
23
logic/minecraft/SkinUtils.h
Normal file
@ -0,0 +1,23 @@
|
||||
/* Copyright 2013-2015 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 <QPixmap>
|
||||
|
||||
namespace SkinUtils
|
||||
{
|
||||
QPixmap getFaceFromCache(QString username, int height = 64, int width = 64);
|
||||
}
|
@ -35,7 +35,7 @@
|
||||
#include "MinecraftVersionList.h"
|
||||
#include "ProfileUtils.h"
|
||||
|
||||
#include "logic/OneSixInstance.h"
|
||||
#include "logic/minecraft/OneSixInstance.h"
|
||||
#include "logic/MMCJson.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
Reference in New Issue
Block a user