// 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 . */ #include "LocalWorldSaveParseTask.h" #include "FileSystem.h" #include "archive/ArchiveReader.h" #include #include #include 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 , /// QString , /// bool /// ) static std::tuple 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 , /// QString , /// bool /// ) static std::tuple 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(); }