// MassBuilderSaveTool // Copyright (C) 2021-2022 Guillaume Jacquemin // // 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 . #include #include #include "BinaryReader.h" #include "BinaryWriter.h" #include "../Logger/Logger.h" #include "UESaveFile.h" using namespace Containers::Literals; UESaveFile::UESaveFile(Containers::String filepath): _propSerialiser{PropertySerialiser::instance()} { _filepath = std::move(filepath); loadData(); } auto UESaveFile::valid() const -> bool { return _valid; } auto UESaveFile::lastError() const -> Containers::StringView { return _lastError; } auto UESaveFile::reloadData() -> bool { if(_noReloadAfterSave) { _noReloadAfterSave = false; return valid(); } _properties = Containers::Array{}; loadData(); return valid(); } auto UESaveFile::saveType() -> Containers::StringView { return _saveType; } void UESaveFile::appendProperty(UnrealPropertyBase::ptr prop) { auto none_prop = std::move(_properties.back()); _properties.back() = std::move(prop); arrayAppend(_properties, std::move(none_prop)); } auto UESaveFile::props() -> Containers::ArrayView { return _properties; } auto UESaveFile::saveToFile() -> bool { LOG_INFO_FORMAT("Writing to {}.", _filepath); bool temp_file = _filepath.hasSuffix(".tmp"); if(!temp_file && !Utility::Path::copy(_filepath, _filepath + ".bak"_s)) { _lastError = "Couldn't create a backup for " + _filepath; LOG_ERROR(_lastError); return false; } BinaryWriter writer{_filepath + ".tmp"_s}; if(!writer.open()) { _lastError = "Couldn't open the file for saving."_s; LOG_ERROR(_lastError); return false; } if(!writer.writeArray(arrayView(_magicBytes)) || !writer.writeUnsignedInt(_saveVersion) || !writer.writeUnsignedInt(_packageVersion) || !writer.writeUnsignedShort(_engineVersion.major) || !writer.writeUnsignedShort(_engineVersion.minor) || !writer.writeUnsignedShort(_engineVersion.patch) || !writer.writeUnsignedInt(_engineVersion.build) || !writer.writeUEString(_engineVersion.buildId)) { _lastError = "Couldn't write the header."_s; LOG_ERROR(_lastError); return false; } if(!writer.writeUnsignedInt(_customFormatVersion) || !writer.writeUnsignedInt(_customFormatData.size())) { _lastError = "Couldn't write the custom format data."_s; LOG_ERROR(_lastError); return false; } for(UnsignedLong i = 0; i < _customFormatData.size(); i++) { if(!writer.writeStaticArray(Containers::StaticArrayView<16, const char>{_customFormatData[i].id}) || !writer.writeUnsignedInt(_customFormatData[i].value)) { _lastError = "Couldn't write the custom format data."_s; LOG_ERROR(_lastError); return false; } } if(!writer.writeUEString(_saveType)) { _lastError = "Couldn't write the save type."_s; LOG_ERROR(_lastError); return false; } for(auto& prop : _properties) { UnsignedLong bytes_written = 0; if(!_propSerialiser->write(prop, bytes_written, writer)) { _lastError = "Couldn't write the property "_s + *prop->name + " to the array."_s; LOG_ERROR(_lastError); return false; } if(!writer.flushToFile()) { _lastError = "Couldn't write the property "_s + *prop->name + " to the file."_s; LOG_ERROR(_lastError); return false; } } writer.writeUnsignedInt(0); writer.closeFile(); if(!Utility::Path::copy(_filepath + ".tmp"_s, _filepath)) { _lastError = "Couldn't save the file properly."; LOG_ERROR(_lastError); return false; } Utility::Path::remove(_filepath + ".tmp"_s); _noReloadAfterSave = true; return true; } void UESaveFile::loadData() { LOG_INFO_FORMAT("Reading data from {}.", _filepath); _valid = false; if(!Utility::Path::exists(_filepath)) { _lastError = "The file couldn't be found."; LOG_ERROR(_lastError); return; } BinaryReader reader{_filepath}; if(!reader.open()) { _lastError = _filepath + " couldn't be opened."_s; LOG_ERROR(_lastError); return; } Containers::Array magic; if(!reader.readArray(magic, 4)) { _lastError = "Couldn't read magic bytes in "_s + _filepath; LOG_ERROR(_lastError); return; } if(std::strncmp(magic.data(), _magicBytes.data(), 4) != 0) { _lastError = Utility::format("Magic bytes don't match. Expected {{{}, {}, {}, {}}}, got {{{}, {}, {}, {}}} instead", _magicBytes[0], _magicBytes[1], _magicBytes[2], _magicBytes[3], magic[0], magic[1], magic[2], magic[3]); LOG_ERROR(_lastError); return; } if(!reader.readUnsignedInt(_saveVersion) || !reader.readUnsignedInt(_packageVersion) || !reader.readUnsignedShort(_engineVersion.major) || !reader.readUnsignedShort(_engineVersion.minor) || !reader.readUnsignedShort(_engineVersion.patch) || !reader.readUnsignedInt(_engineVersion.build) || !reader.readUEString(_engineVersion.buildId)) { _lastError = "Couldn't read version data."; LOG_ERROR(_lastError); return; } if(!reader.readUnsignedInt(_customFormatVersion)) { _lastError = "Couldn't read the custom format version."; LOG_ERROR(_lastError); return; } UnsignedInt custom_format_data_size = 0; if(!reader.readUnsignedInt(custom_format_data_size)) { _lastError = "Couldn't read the custom format data size."; LOG_ERROR(_lastError); return; } _customFormatData = Containers::Array{custom_format_data_size}; for(UnsignedInt i = 0; i < custom_format_data_size; i++) { CustomFormatDataEntry entry; if(!reader.readStaticArray(entry.id) || !reader.readInt(entry.value)) { _lastError = "Couldn't read the custom format data"; LOG_ERROR(_lastError); return; } _customFormatData[i] = std::move(entry); } if(!reader.readUEString(_saveType)) { _lastError = "Couldn't read the save type."; LOG_ERROR(_lastError); return; } UnrealPropertyBase::ptr prop; while((prop = _propSerialiser->read(reader)) != nullptr) { arrayAppend(_properties, std::move(prop)); } if(_properties.isEmpty()) { _lastError = "No properties were found."_s; LOG_ERROR(_lastError); return; } if(_properties.back()->name != "None"_s && _properties.back()->propertyType != "NoneProperty"_s) { _lastError = "Couldn't find a final NoneProperty."_s; LOG_ERROR(_lastError); return; } reader.closeFile(); _valid = true; }