summaryrefslogtreecommitdiff
path: root/meshmc/launcher/minecraft/AssetsUtils.cpp
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:45:07 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:45:07 +0300
commit31b9a8949ed0a288143e23bf739f2eb64fdc63be (patch)
tree8a984fa143c38fccad461a77792d6864f3e82cd3 /meshmc/launcher/minecraft/AssetsUtils.cpp
parent934382c8a1ce738589dee9ee0f14e1cec812770e (diff)
parentfad6a1066616b69d7f5fef01178efdf014c59537 (diff)
downloadProject-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.tar.gz
Project-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.zip
Add 'meshmc/' from commit 'fad6a1066616b69d7f5fef01178efdf014c59537'
git-subtree-dir: meshmc git-subtree-mainline: 934382c8a1ce738589dee9ee0f14e1cec812770e git-subtree-split: fad6a1066616b69d7f5fef01178efdf014c59537
Diffstat (limited to 'meshmc/launcher/minecraft/AssetsUtils.cpp')
-rw-r--r--meshmc/launcher/minecraft/AssetsUtils.cpp344
1 files changed, 344 insertions, 0 deletions
diff --git a/meshmc/launcher/minecraft/AssetsUtils.cpp b/meshmc/launcher/minecraft/AssetsUtils.cpp
new file mode 100644
index 0000000000..9781a18648
--- /dev/null
+++ b/meshmc/launcher/minecraft/AssetsUtils.cpp
@@ -0,0 +1,344 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 <QDirIterator>
+#include <QCryptographicHash>
+#include <QJsonParseError>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QVariant>
+#include <QDebug>
+
+#include "AssetsUtils.h"
+#include "FileSystem.h"
+#include "net/Download.h"
+#include "net/ChecksumValidator.h"
+#include "BuildConfig.h"
+
+#include "Application.h"
+
+namespace
+{
+ QSet<QString> collectPathsFromDir(QString dirPath)
+ {
+ 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
+
+namespace AssetsUtils
+{
+
+ /*
+ * Returns true on success, with index populated
+ * index is undefined otherwise
+ */
+ bool loadAssetsIndexJson(const QString& assetsId, const 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;
+ }
+ index.id = assetsId;
+
+ // 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 mapToResources = root.value("map_to_resources");
+ if (!mapToResources.isUndefined()) {
+ index.mapToResources = mapToResources.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;
+ }
+
+ // FIXME: ugly code duplication
+ QDir getAssetsDir(const QString& assetsId, const 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 determine assets path!";
+ return virtualRoot;
+ }
+
+ 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;
+ if (!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) {
+ 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(targetPath, map);
+ QFile target(target_path);
+
+ QString tlk = asset_object.hash.left(2);
+
+ QString original_path =
+ FS::PathCombine(objectDir.path(), tlk, asset_object.hash);
+ 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.path();
+ FS::ensureFolderPathExists(target_dir.path());
+
+ bool couldCopy = original.copy(target_path);
+ 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 true;
+ }
+
+} // namespace AssetsUtils
+
+NetAction::Ptr AssetObject::getDownloadAction()
+{
+ QFileInfo objectFile(getLocalPath());
+ if ((!objectFile.isFile()) || (objectFile.size() != size)) {
+ auto objectDL =
+ Net::Download::makeFile(getUrl(), objectFile.filePath());
+ if (hash.size()) {
+ auto rawHash = QByteArray::fromHex(hash.toLatin1());
+ objectDL->addValidator(
+ new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
+ }
+ objectDL->m_total_progress = size;
+ return objectDL;
+ }
+ return nullptr;
+}
+
+QString AssetObject::getLocalPath()
+{
+ return "assets/objects/" + getRelPath();
+}
+
+QUrl AssetObject::getUrl()
+{
+ return BuildConfig.RESOURCE_BASE + getRelPath();
+}
+
+QString AssetObject::getRelPath()
+{
+ return hash.left(2) + "/" + hash;
+}
+
+NetJob::Ptr AssetsIndex::getDownloadJob()
+{
+ auto job = new NetJob(QObject::tr("Assets for %1").arg(id),
+ APPLICATION->network());
+ for (auto& object : objects.values()) {
+ auto dl = object.getDownloadAction();
+ if (dl) {
+ job->addNetAction(dl);
+ }
+ }
+ if (job->size())
+ return job;
+ return nullptr;
+}