diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt index 57a197be..39850163 100644 --- a/api/logic/CMakeLists.txt +++ b/api/logic/CMakeLists.txt @@ -235,6 +235,8 @@ set(MINECRAFT_SOURCES minecraft/launch/LauncherPartLaunch.h minecraft/launch/PrintInstanceInfo.cpp minecraft/launch/PrintInstanceInfo.h + minecraft/launch/ReconstructAssets.cpp + minecraft/launch/ReconstructAssets.h minecraft/legacy/LegacyModList.h minecraft/legacy/LegacyModList.cpp minecraft/legacy/LegacyInstance.h diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp index c44b4ae6..013e7015 100644 --- a/api/logic/minecraft/AssetsUtils.cpp +++ b/api/logic/minecraft/AssetsUtils.cpp @@ -29,6 +29,34 @@ #include "net/ChecksumValidator.h" #include "net/URLConstants.h" +namespace { +QSet collectPathsFromDir(QString dirPath) +{ + std::error_code ignoredError; + QFileInfo dirInfo(dirPath); + + if (!dirInfo.exists()) + { + return {}; + } + + QSet out; + + QDirIterator iter(dirPath, QDirIterator::Subdirectories); + while (iter.hasNext()) + { + QString value = iter.next(); + QFileInfo info(value); + if(info.isFile()) + { + out.insert(value); + qDebug() << value; + } + } + return out; +} +} + namespace AssetsUtils { @@ -37,7 +65,7 @@ namespace AssetsUtils * Returns true on success, with index populated * index is undefined otherwise */ -bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index) +bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsIndex& index) { /* { @@ -61,7 +89,7 @@ bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index) qCritical() << "Failed to read assets index file" << path; return false; } - index->id = assetsId; + index.id = assetsId; // Read the file and close it. QByteArray jsonData = file.readAll(); @@ -90,7 +118,13 @@ bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index) QJsonValue isVirtual = root.value("virtual"); if (!isVirtual.isUndefined()) { - index->isVirtual = isVirtual.toBool(false); + index.isVirtual = isVirtual.toBool(false); + } + + QJsonValue mapToResources = root.value("map_to_resources"); + if (!mapToResources.isUndefined()) + { + index.mapToResources = mapToResources.toBool(false); } QJsonValue objects = root.value("objects"); @@ -122,13 +156,14 @@ bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index) } } - index->objects.insert(iter.key(), object); + index.objects.insert(iter.key(), object); } return true; } -QDir reconstructAssets(QString assetsId) +// FIXME: ugly code duplication +QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder) { QDir assetsDir = QDir("assets/"); QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); @@ -141,24 +176,77 @@ QDir reconstructAssets(QString assetsId) if (!indexFile.exists()) { - qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets"; + qCritical() << "No assets index file" << indexPath << "; can't determine assets path!"; return virtualRoot; } - qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() - << objectDir.path() << virtualDir.path() << virtualRoot.path(); + AssetsIndex index; + if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) + { + qCritical() << "Failed to load asset index file" << indexPath << "; can't determine assets path!"; + return virtualRoot; + } + + QString targetPath; + if(index.isVirtual) + { + return virtualRoot; + } + else if(index.mapToResources) + { + return QDir(resourcesFolder); + } + return virtualRoot; +} + +// FIXME: ugly code duplication +bool reconstructAssets(QString assetsId, QString resourcesFolder) +{ + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); + QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); + QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); + + QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); + QFile indexFile(indexPath); + QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); + + if (!indexFile.exists()) + { + qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets!"; + return false; + } + + qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path(); AssetsIndex index; - bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index); - - if (loadAssetsIndex && index.isVirtual) + if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) { - qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path(); + qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!"; + return false; + } + QString targetPath; + bool removeLeftovers = false; + if(index.isVirtual) + { + targetPath = virtualRoot.path(); + removeLeftovers = true; + qDebug() << "Reconstructing virtual assets folder at" << targetPath; + } + else if(index.mapToResources) + { + targetPath = resourcesFolder; + qDebug() << "Reconstructing resources folder at" << targetPath; + } + + if (!targetPath.isNull()) + { + auto presentFiles = collectPathsFromDir(targetPath); for (QString map : index.objects.keys()) { AssetObject asset_object = index.objects.value(map); - QString target_path = FS::PathCombine(virtualRoot.path(), map); + QString target_path = FS::PathCombine(targetPath, map); QFile target(target_path); QString tlk = asset_object.hash.left(2); @@ -167,24 +255,32 @@ QDir reconstructAssets(QString assetsId) QFile original(original_path); if (!original.exists()) continue; + + presentFiles.remove(target_path); + if (!target.exists()) { QFileInfo info(target_path); QDir target_dir = info.dir(); - // qDebug() << target_dir; - if (!target_dir.exists()) - QDir("").mkpath(target_dir.path()); + + qDebug() << target_dir.path(); + FS::ensureFolderPathExists(target_dir.path()); bool couldCopy = original.copy(target_path); - qDebug() << " Copying" << original_path << "to" << target_path - << QString::number(couldCopy); // << original.errorString(); + qDebug() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy); } } // TODO: Write last used time to virtualRoot/.lastused + if(removeLeftovers) + { + for(auto & file: presentFiles) + { + qDebug() << "Would remove" << file; + } + } } - - return virtualRoot; + return true; } } diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h index 3755210c..356b8c8a 100644 --- a/api/logic/minecraft/AssetsUtils.h +++ b/api/logic/minecraft/AssetsUtils.h @@ -38,11 +38,16 @@ struct AssetsIndex QString id; QMap objects; bool isVirtual = false; + bool mapToResources = false; }; +/// FIXME: this is absolutely horrendous. REDO!!!! namespace AssetsUtils { -bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index); +bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index); + +QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder); + /// Reconstruct a virtual assets folder for the given assets ID and return the folder -QDir reconstructAssets(QString assetsId); +bool reconstructAssets(QString assetsId, QString resourcesFolder); } diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp index bf4eb1bd..449a2ed5 100644 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ b/api/logic/minecraft/MinecraftInstance.cpp @@ -20,6 +20,7 @@ #include "minecraft/launch/DirectJavaLaunch.h" #include "minecraft/launch/ModMinecraftJar.h" #include "minecraft/launch/ClaimAccount.h" +#include "minecraft/launch/ReconstructAssets.h" #include "java/launch/CheckJava.h" #include "java/JavaUtils.h" #include "meta/Index.h" @@ -216,6 +217,11 @@ QString MinecraftInstance::worldDir() const return FS::PathCombine(gameRoot(), "saves"); } +QString MinecraftInstance::resourcesDir() const +{ + return FS::PathCombine(gameRoot(), "resources"); +} + QDir MinecraftInstance::librariesPath() const { return QDir::current().absoluteFilePath("libraries"); @@ -408,8 +414,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) cons token_mapping["game_directory"] = absRootDir; QString absAssetsDir = QDir("assets/").absolutePath(); auto assets = profile->getMinecraftAssets(); - // FIXME: this is wrong and should be run as an async task - token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath(); + token_mapping["game_assets"] = AssetsUtils::getAssetsDir(assets->id, resourcesDir()).absolutePath(); // 1.7.3+ assets tokens token_mapping["assets_root"] = absAssetsDir; @@ -842,6 +847,12 @@ std::shared_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr s process->appendStep(step); } + // reconstruct assets if needed + { + auto step = std::make_shared(pptr); + process->appendStep(step); + } + { // actually launch the game auto method = launchMethod(); diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h index 091d1bf7..d9fffe57 100644 --- a/api/logic/minecraft/MinecraftInstance.h +++ b/api/logic/minecraft/MinecraftInstance.h @@ -45,6 +45,7 @@ public: QString modsCacheLocation() const; QString libDir() const; QString worldDir() const; + QString resourcesDir() const; QDir jarmodsPath() const; QDir librariesPath() const; QDir versionsPath() const; diff --git a/api/logic/minecraft/launch/ReconstructAssets.cpp b/api/logic/minecraft/launch/ReconstructAssets.cpp new file mode 100644 index 00000000..af9af127 --- /dev/null +++ b/api/logic/minecraft/launch/ReconstructAssets.cpp @@ -0,0 +1,36 @@ +/* Copyright 2013-2019 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 "ReconstructAssets.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" +#include "minecraft/AssetsUtils.h" +#include "launch/LaunchTask.h" + +void ReconstructAssets::executeTask() +{ + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + auto components = minecraftInstance->getComponentList(); + auto profile = components->getProfile(); + auto assets = profile->getMinecraftAssets(); + + if(!AssetsUtils::reconstructAssets(assets->id, minecraftInstance->resourcesDir())) + { + emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error); + } + + emitSucceeded(); +} diff --git a/api/logic/minecraft/launch/ReconstructAssets.h b/api/logic/minecraft/launch/ReconstructAssets.h new file mode 100644 index 00000000..228fa083 --- /dev/null +++ b/api/logic/minecraft/launch/ReconstructAssets.h @@ -0,0 +1,33 @@ +/* Copyright 2013-2019 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 +#include + +class ReconstructAssets: public LaunchStep +{ + Q_OBJECT +public: + explicit ReconstructAssets(LaunchTask *parent) : LaunchStep(parent){}; + virtual ~ReconstructAssets(){}; + + void executeTask() override; + bool canAbort() const override + { + return false; + } +}; diff --git a/api/logic/minecraft/update/AssetUpdateTask.cpp b/api/logic/minecraft/update/AssetUpdateTask.cpp index 1661822d..051b1279 100644 --- a/api/logic/minecraft/update/AssetUpdateTask.cpp +++ b/api/logic/minecraft/update/AssetUpdateTask.cpp @@ -60,7 +60,7 @@ void AssetUpdateTask::assetIndexFinished() QString asset_fname = "assets/indexes/" + assets->id + ".json"; // FIXME: this looks like a job for a generic validator based on json schema? - if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index)) + if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, index)) { auto metacache = ENV.metacache(); auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); @@ -101,7 +101,7 @@ bool AssetUpdateTask::abort() } else { - qWarning() << "Prematurely aborted FMLLibrariesTask"; + qWarning() << "Prematurely aborted AssetUpdateTask"; } return true; }