mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-08-04 03:47:57 -04:00
202 lines
5.8 KiB
C++
202 lines
5.8 KiB
C++
|
|
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
/*
|
|
* Prism Launcher - Minecraft Launcher
|
|
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
|
*
|
|
* 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, version 3.
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
#include "LocalWorldSaveParseTask.h"
|
|
|
|
#include "FileSystem.h"
|
|
#include "archive/ArchiveReader.h"
|
|
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <tuple>
|
|
|
|
namespace WorldSaveUtils {
|
|
|
|
bool process(WorldSave& pack, ProcessingLevel level)
|
|
{
|
|
switch (pack.type()) {
|
|
case ResourceType::FOLDER:
|
|
return WorldSaveUtils::processFolder(pack, level);
|
|
case ResourceType::ZIPFILE:
|
|
return WorldSaveUtils::processZIP(pack, level);
|
|
default:
|
|
qWarning() << "Invalid type for world save parse task!";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// @brief checks a folder structure to see if it contains a level.dat
|
|
/// @param dir the path to check
|
|
/// @param saves used in recursive call if a "saves" dir was found
|
|
/// @return std::tuple of (
|
|
/// bool <found level.dat>,
|
|
/// QString <name of folder containing level.dat>,
|
|
/// bool <saves folder found>
|
|
/// )
|
|
static std::tuple<bool, QString, bool> contains_level_dat(QDir dir, bool saves = false)
|
|
{
|
|
for (auto const& entry : dir.entryInfoList()) {
|
|
if (!entry.isDir()) {
|
|
continue;
|
|
}
|
|
if (!saves && entry.fileName() == "saves") {
|
|
return contains_level_dat(QDir(entry.filePath()), true);
|
|
}
|
|
QFileInfo level_dat(FS::PathCombine(entry.filePath(), "level.dat"));
|
|
if (level_dat.exists() && level_dat.isFile()) {
|
|
return std::make_tuple(true, entry.fileName(), saves);
|
|
}
|
|
}
|
|
return std::make_tuple(false, "", saves);
|
|
}
|
|
|
|
bool processFolder(WorldSave& save, ProcessingLevel level)
|
|
{
|
|
Q_ASSERT(save.type() == ResourceType::FOLDER);
|
|
|
|
auto [found, save_dir_name, found_saves_dir] = contains_level_dat(QDir(save.fileinfo().filePath()));
|
|
|
|
if (!found) {
|
|
return false;
|
|
}
|
|
|
|
save.setSaveDirName(save_dir_name);
|
|
|
|
if (found_saves_dir) {
|
|
save.setSaveFormat(WorldSaveFormat::MULTI);
|
|
} else {
|
|
save.setSaveFormat(WorldSaveFormat::SINGLE);
|
|
}
|
|
|
|
if (level == ProcessingLevel::BasicInfoOnly) {
|
|
return true; // only need basic info already checked
|
|
}
|
|
|
|
// reserved for more intensive processing
|
|
|
|
return true; // all tests passed
|
|
}
|
|
|
|
/// @brief checks a folder structure to see if it contains a level.dat
|
|
/// @param zip the zip file to check
|
|
/// @return std::tuple of (
|
|
/// bool <found level.dat>,
|
|
/// QString <name of folder containing level.dat>,
|
|
/// bool <saves folder found>
|
|
/// )
|
|
static std::tuple<bool, QString, bool> contains_level_dat(QString fileName)
|
|
{
|
|
MMCZip::ArchiveReader zip(fileName);
|
|
if (!zip.collectFiles()) {
|
|
return std::make_tuple(false, "", false);
|
|
}
|
|
bool saves = false;
|
|
if (zip.exists("/saves")) {
|
|
saves = true;
|
|
}
|
|
|
|
for (auto file : zip.getFiles()) {
|
|
QString relativePath = file;
|
|
if (saves) {
|
|
if (!relativePath.startsWith("saves/", Qt::CaseInsensitive))
|
|
continue;
|
|
relativePath = relativePath.mid(QString("saves/").length());
|
|
}
|
|
if (!relativePath.endsWith("/level.dat", Qt::CaseInsensitive))
|
|
continue;
|
|
|
|
int slashIndex = relativePath.indexOf('/');
|
|
if (slashIndex == -1)
|
|
continue; // malformed: no slash between saves/ and level.dat
|
|
|
|
QString worldName = relativePath.left(slashIndex);
|
|
QString remaining = relativePath.mid(slashIndex + 1);
|
|
|
|
// Check that there's nothing between worldName/ and level.dat
|
|
if (remaining == "level.dat") {
|
|
QString worldDir = (saves ? "saves/" : "") + worldName;
|
|
return std::make_tuple(true, worldDir, saves);
|
|
}
|
|
}
|
|
|
|
return std::make_tuple(false, "", saves);
|
|
}
|
|
|
|
bool processZIP(WorldSave& save, ProcessingLevel level)
|
|
{
|
|
Q_ASSERT(save.type() == ResourceType::ZIPFILE);
|
|
|
|
auto [found, save_dir_name, found_saves_dir] = contains_level_dat(save.fileinfo().filePath());
|
|
|
|
if (!found) {
|
|
return false;
|
|
}
|
|
if (save_dir_name.endsWith("/")) {
|
|
save_dir_name.chop(1);
|
|
}
|
|
|
|
save.setSaveDirName(save_dir_name);
|
|
|
|
if (found_saves_dir) {
|
|
save.setSaveFormat(WorldSaveFormat::MULTI);
|
|
} else {
|
|
save.setSaveFormat(WorldSaveFormat::SINGLE);
|
|
}
|
|
|
|
if (level == ProcessingLevel::BasicInfoOnly) {
|
|
return true; // only need basic info already checked
|
|
}
|
|
|
|
// reserved for more intensive processing
|
|
|
|
return true;
|
|
}
|
|
|
|
bool validate(QFileInfo file)
|
|
{
|
|
WorldSave sp{ file };
|
|
return WorldSaveUtils::process(sp, ProcessingLevel::BasicInfoOnly) && sp.valid();
|
|
}
|
|
|
|
} // namespace WorldSaveUtils
|
|
|
|
LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(false), m_token(token), m_save(save) {}
|
|
|
|
bool LocalWorldSaveParseTask::abort()
|
|
{
|
|
m_aborted = true;
|
|
return true;
|
|
}
|
|
|
|
void LocalWorldSaveParseTask::executeTask()
|
|
{
|
|
if (!WorldSaveUtils::process(m_save)) {
|
|
emitFailed("this is not a world");
|
|
return;
|
|
}
|
|
|
|
if (m_aborted)
|
|
emitAborted();
|
|
else
|
|
emitSucceeded();
|
|
}
|