diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:45:07 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:45:07 +0300 |
| commit | 31b9a8949ed0a288143e23bf739f2eb64fdc63be (patch) | |
| tree | 8a984fa143c38fccad461a77792d6864f3e82cd3 /meshmc/launcher/minecraft/AssetsUtils.cpp | |
| parent | 934382c8a1ce738589dee9ee0f14e1cec812770e (diff) | |
| parent | fad6a1066616b69d7f5fef01178efdf014c59537 (diff) | |
| download | Project-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.cpp | 344 |
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; +} |
