From 9c82adaee57a9da5ef818143b89f30c2439f68be Mon Sep 17 00:00:00 2001
From: Petr Mrázek <peterix@gmail.com>
Date: Tue, 19 Feb 2019 01:00:03 +0100
Subject: GH-2209 Fix sounds in old (pre-1.6) versions

---
 api/logic/CMakeLists.txt                         |   2 +
 api/logic/minecraft/AssetsUtils.cpp              | 134 +++++++++++++++++++----
 api/logic/minecraft/AssetsUtils.h                |   9 +-
 api/logic/minecraft/MinecraftInstance.cpp        |  15 ++-
 api/logic/minecraft/MinecraftInstance.h          |   1 +
 api/logic/minecraft/launch/ReconstructAssets.cpp |  36 ++++++
 api/logic/minecraft/launch/ReconstructAssets.h   |  33 ++++++
 api/logic/minecraft/update/AssetUpdateTask.cpp   |   4 +-
 8 files changed, 209 insertions(+), 25 deletions(-)
 create mode 100644 api/logic/minecraft/launch/ReconstructAssets.cpp
 create mode 100644 api/logic/minecraft/launch/ReconstructAssets.h

(limited to 'api/logic')

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<QString> collectPathsFromDir(QString dirPath)
+{
+    std::error_code ignoredError;
+    QFileInfo dirInfo(dirPath);
+
+    if (!dirInfo.exists())
+    {
+        return {};
+    }
+
+    QSet<QString> 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(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index))
+    {
+        qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!";
+        return false;
+    }
 
-    if (loadAssetsIndex && index.isVirtual)
+    QString targetPath;
+    bool removeLeftovers = false;
+    if(index.isVirtual)
+    {
+        targetPath = virtualRoot.path();
+        removeLeftovers = true;
+        qDebug() << "Reconstructing virtual assets folder at" << targetPath;
+    }
+    else if(index.mapToResources)
     {
-        qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path();
+        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<QString, AssetObject> 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<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
         process->appendStep(step);
     }
 
+    // reconstruct assets if needed
+    {
+        auto step = std::make_shared<ReconstructAssets>(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> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(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 <launch/LaunchStep.h>
+#include <memory>
+
+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;
 }
-- 
cgit