diff options
author | Petr Mrázek <peterix@gmail.com> | 2021-07-25 19:11:59 +0200 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2021-07-25 19:50:44 +0200 |
commit | 20b9f2b42a3b58b6081af271774fbcc34025dccb (patch) | |
tree | 064fa59facb3357139b47bd4e60bfc8edb35ca11 /launcher/minecraft/World.cpp | |
parent | dd133680858351e3e07690e286882327a4f42ba5 (diff) | |
download | PrismLauncher-20b9f2b42a3b58b6081af271774fbcc34025dccb.tar.gz PrismLauncher-20b9f2b42a3b58b6081af271774fbcc34025dccb.tar.bz2 PrismLauncher-20b9f2b42a3b58b6081af271774fbcc34025dccb.zip |
NOISSUE Flatten gui and logic libraries into MultiMC
Diffstat (limited to 'launcher/minecraft/World.cpp')
-rw-r--r-- | launcher/minecraft/World.cpp | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp new file mode 100644 index 00000000..a2b4dac7 --- /dev/null +++ b/launcher/minecraft/World.cpp @@ -0,0 +1,520 @@ +/* Copyright 2015-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 <QDir> +#include <QString> +#include <QDebug> +#include <QSaveFile> +#include "World.h" + +#include "GZip.h" +#include <MMCZip.h> +#include <FileSystem.h> +#include <sstream> +#include <io/stream_reader.h> +#include <tag_string.h> +#include <tag_primitive.h> +#include <quazip.h> +#include <quazipfile.h> +#include <quazipdir.h> + +#include <QCoreApplication> + +#include <nonstd/optional> + +using nonstd::optional; +using nonstd::nullopt; + +GameType::GameType(nonstd::optional<int> original): + original(original) +{ + if(!original) { + return; + } + switch(*original) { + case 0: + type = GameType::Survival; + break; + case 1: + type = GameType::Creative; + break; + case 2: + type = GameType::Adventure; + break; + case 3: + type = GameType::Spectator; + break; + default: + break; + } +} + +QString GameType::toTranslatedString() const +{ + switch (type) + { + case GameType::Survival: + return QCoreApplication::translate("GameType", "Survival"); + case GameType::Creative: + return QCoreApplication::translate("GameType", "Creative"); + case GameType::Adventure: + return QCoreApplication::translate("GameType", "Adventure"); + case GameType::Spectator: + return QCoreApplication::translate("GameType", "Spectator"); + default: + break; + } + if(original) { + return QCoreApplication::translate("GameType", "Unknown (%1)").arg(*original); + } + return QCoreApplication::translate("GameType", "Undefined"); +} + +QString GameType::toLogString() const +{ + switch (type) + { + case GameType::Survival: + return "Survival"; + case GameType::Creative: + return "Creative"; + case GameType::Adventure: + return "Adventure"; + case GameType::Spectator: + return "Spectator"; + default: + break; + } + if(original) { + return QString("Unknown (%1)").arg(*original); + } + return "Undefined"; +} + +std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data) +{ + QByteArray output; + if(!GZip::unzip(data, output)) + { + return nullptr; + } + std::istringstream foo(std::string(output.constData(), output.size())); + try { + auto pair = nbt::io::read_compound(foo); + + if(pair.first != "") + return nullptr; + + if(pair.second == nullptr) + return nullptr; + + return std::move(pair.second); + } + catch (const nbt::io::input_error &e) + { + qWarning() << "Unable to parse level.dat:" << e.what(); + return nullptr; + } +} + +QByteArray serializeLevelDat(nbt::tag_compound * levelInfo) +{ + std::ostringstream s; + nbt::io::write_tag("", *levelInfo, s); + QByteArray val( s.str().data(), (int) s.str().size() ); + return val; +} + +QString getLevelDatFromFS(const QFileInfo &file) +{ + QDir worldDir(file.filePath()); + if(!file.isDir() || !worldDir.exists("level.dat")) + { + return QString(); + } + return worldDir.absoluteFilePath("level.dat"); +} + +QByteArray getLevelDatDataFromFS(const QFileInfo &file) +{ + auto fullFilePath = getLevelDatFromFS(file); + if(fullFilePath.isNull()) + { + return QByteArray(); + } + QFile f(fullFilePath); + if(!f.open(QIODevice::ReadOnly)) + { + return QByteArray(); + } + return f.readAll(); +} + +bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data) +{ + auto fullFilePath = getLevelDatFromFS(file); + if(fullFilePath.isNull()) + { + return false; + } + QSaveFile f(fullFilePath); + if(!f.open(QIODevice::WriteOnly)) + { + return false; + } + QByteArray compressed; + if(!GZip::zip(data, compressed)) + { + return false; + } + if(f.write(compressed) != compressed.size()) + { + f.cancelWriting(); + return false; + } + return f.commit(); +} + +World::World(const QFileInfo &file) +{ + repath(file); +} + +void World::repath(const QFileInfo &file) +{ + m_containerFile = file; + m_folderName = file.fileName(); + if(file.isFile() && file.suffix() == "zip") + { + m_iconFile = QString(); + readFromZip(file); + } + else if(file.isDir()) + { + QFileInfo assumedIconPath(file.absoluteFilePath() + "/icon.png"); + if(assumedIconPath.exists()) { + m_iconFile = assumedIconPath.absoluteFilePath(); + } + readFromFS(file); + } +} + +bool World::resetIcon() +{ + if(m_iconFile.isNull()) { + return false; + } + if(QFile(m_iconFile).remove()) { + m_iconFile = QString(); + return true; + } + return false; +} + +void World::readFromFS(const QFileInfo &file) +{ + auto bytes = getLevelDatDataFromFS(file); + if(bytes.isEmpty()) + { + is_valid = false; + return; + } + loadFromLevelDat(bytes); + levelDatTime = file.lastModified(); +} + +void World::readFromZip(const QFileInfo &file) +{ + QuaZip zip(file.absoluteFilePath()); + is_valid = zip.open(QuaZip::mdUnzip); + if (!is_valid) + { + return; + } + auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); + is_valid = !location.isEmpty(); + if (!is_valid) + { + return; + } + m_containerOffsetPath = location; + QuaZipFile zippedFile(&zip); + // read the install profile + is_valid = zip.setCurrentFile(location + "level.dat"); + if (!is_valid) + { + return; + } + is_valid = zippedFile.open(QIODevice::ReadOnly); + QuaZipFileInfo64 levelDatInfo; + zippedFile.getFileInfo(&levelDatInfo); + auto modTime = levelDatInfo.getNTFSmTime(); + if(!modTime.isValid()) + { + modTime = levelDatInfo.dateTime; + } + levelDatTime = modTime; + if (!is_valid) + { + return; + } + loadFromLevelDat(zippedFile.readAll()); + zippedFile.close(); +} + +bool World::install(const QString &to, const QString &name) +{ + auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to)); + if(!FS::ensureFolderPathExists(finalPath)) + { + return false; + } + bool ok = false; + if(m_containerFile.isFile()) + { + QuaZip zip(m_containerFile.absoluteFilePath()); + if (!zip.open(QuaZip::mdUnzip)) + { + return false; + } + ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath); + } + else if(m_containerFile.isDir()) + { + QString from = m_containerFile.filePath(); + ok = FS::copy(from, finalPath)(); + } + + if(ok && !name.isEmpty() && m_actualName != name) + { + World newWorld(finalPath); + if(newWorld.isValid()) + { + newWorld.rename(name); + } + } + return ok; +} + +bool World::rename(const QString &newName) +{ + if(m_containerFile.isFile()) + { + return false; + } + + auto data = getLevelDatDataFromFS(m_containerFile); + if(data.isEmpty()) + { + return false; + } + + auto worldData = parseLevelDat(data); + if(!worldData) + { + return false; + } + auto &val = worldData->at("Data"); + if(val.get_type() != nbt::tag_type::Compound) + { + return false; + } + auto &dataCompound = val.as<nbt::tag_compound>(); + dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data())); + data = serializeLevelDat(worldData.get()); + + putLevelDatDataToFS(m_containerFile, data); + + m_actualName = newName; + + QDir parentDir(m_containerFile.absoluteFilePath()); + parentDir.cdUp(); + QFile container(m_containerFile.absoluteFilePath()); + auto dirName = FS::DirNameFromString(m_actualName, parentDir.absolutePath()); + container.rename(parentDir.absoluteFilePath(dirName)); + + return true; +} + +namespace { + +optional<QString> read_string (nbt::value& parent, const char * name) +{ + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::String) + { + return nullopt; + } + auto & tag_str = namedValue.as<nbt::tag_string>(); + return QString::fromStdString(tag_str.get()); + } + catch (const std::out_of_range &e) + { + // fallback for old world formats + qWarning() << "String NBT tag" << name << "could not be found."; + return nullopt; + } + catch (const std::bad_cast &e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to string."; + return nullopt; + } +} + +optional<int64_t> read_long (nbt::value& parent, const char * name) +{ + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::Long) + { + return nullopt; + } + auto & tag_str = namedValue.as<nbt::tag_long>(); + return tag_str.get(); + } + catch (const std::out_of_range &e) + { + // fallback for old world formats + qWarning() << "Long NBT tag" << name << "could not be found."; + return nullopt; + } + catch (const std::bad_cast &e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to long."; + return nullopt; + } +} + +optional<int> read_int (nbt::value& parent, const char * name) +{ + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::Int) + { + return nullopt; + } + auto & tag_str = namedValue.as<nbt::tag_int>(); + return tag_str.get(); + } + catch (const std::out_of_range &e) + { + // fallback for old world formats + qWarning() << "Int NBT tag" << name << "could not be found."; + return nullopt; + } + catch (const std::bad_cast &e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to int."; + return nullopt; + } +} + +GameType read_gametype(nbt::value& parent, const char * name) { + return GameType(read_int(parent, name)); +} + +} + +void World::loadFromLevelDat(QByteArray data) +{ + auto levelData = parseLevelDat(data); + if(!levelData) + { + is_valid = false; + return; + } + + nbt::value * valPtr = nullptr; + try { + valPtr = &levelData->at("Data"); + } + catch (const std::out_of_range &e) { + qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what(); + is_valid = false; + return; + } + nbt::value &val = *valPtr; + + is_valid = val.get_type() == nbt::tag_type::Compound; + if(!is_valid) + return; + + auto name = read_string(val, "LevelName"); + m_actualName = name ? *name : m_folderName; + + auto timestamp = read_long(val, "LastPlayed"); + m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime; + + m_gameType = read_gametype(val, "GameType"); + + optional<int64_t> randomSeed; + try { + auto &WorldGen_val = val.at("WorldGenSettings"); + randomSeed = read_long(WorldGen_val, "seed"); + } + catch (const std::out_of_range &) {} + if(!randomSeed) { + randomSeed = read_long(val, "RandomSeed"); + } + m_randomSeed = randomSeed ? *randomSeed : 0; + + qDebug() << "World Name:" << m_actualName; + qDebug() << "Last Played:" << m_lastPlayed.toString(); + if(randomSeed) { + qDebug() << "Seed:" << *randomSeed; + } + qDebug() << "GameType:" << m_gameType.toLogString(); +} + +bool World::replace(World &with) +{ + if (!destroy()) + return false; + bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())(); + if (success) + { + m_folderName = with.m_folderName; + m_containerFile.refresh(); + } + return success; +} + +bool World::destroy() +{ + if(!is_valid) return false; + if (m_containerFile.isDir()) + { + QDir d(m_containerFile.filePath()); + return d.removeRecursively(); + } + else if(m_containerFile.isFile()) + { + QFile file(m_containerFile.absoluteFilePath()); + return file.remove(); + } + return true; +} + +bool World::operator==(const World &other) const +{ + return is_valid == other.is_valid && folderName() == other.folderName(); +} |