MassBuilderSaveTool/src/UESaveFile/UESaveFile.cpp

230 lines
6.3 KiB
C++

// 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 <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Path.h>
#include "BinaryReader.h"
#include "BinaryWriter.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<UnrealPropertyBase::ptr>{};
loadData();
return valid();
}
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<UnrealPropertyBase::ptr> {
return _properties;
}
#include <string>
#include <Corrade/Containers/StringStl.h>
auto UESaveFile::saveToFile() -> bool {
BinaryWriter writer{_filepath + ".tmp"_s};
if(!writer.open()) {
_lastError = "Couldn't open the file for saving."_s;
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;
return false;
}
if(!writer.writeUnsignedInt(_customFormatVersion) ||
!writer.writeUnsignedInt(_customFormatData.size()))
{
_lastError = "Couldn't write the header."_s;
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 header."_s;
return false;
}
}
if(!writer.writeUEString(_saveType)) {
_lastError = "Couldn't write the header."_s;
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;
return false;
}
if(!writer.flushToFile()) {
_lastError = "Couldn't write the property "_s + *prop->name + " to the file."_s;
return false;
}
}
writer.writeUnsignedInt(0);
writer.closeFile();
if(!Utility::Path::copy(_filepath, _filepath + ".bak"_s)) {
return false;
}
if(!Utility::Path::remove(_filepath)) {
return false;
}
if(!Utility::Path::move(_filepath + ".tmp"_s, _filepath)) {
Utility::Path::move(_filepath + ".bak"_s, _filepath);
return false;
}
_noReloadAfterSave = true;
return true;
}
void UESaveFile::loadData() {
_valid = false;
if(!Utility::Path::exists(_filepath)) {
return;
}
BinaryReader reader{_filepath};
if(!reader.open()) {
_lastError = _filepath + " couldn't be opened."_s;
return;
}
Containers::Array<char> magic;
if(!reader.readArray(magic, 4)) {
_lastError = "Couldn't read magic bytes in "_s + _filepath;
return;
}
Containers::String invalid = _filepath + " isn't a valid UE4 save."_s;
if(std::strncmp(magic.data(), _magicBytes.data(), 4) != 0) {
_lastError = std::move(invalid);
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 = std::move(invalid);
return;
}
if(!reader.readUnsignedInt(_customFormatVersion)) {
_lastError = std::move(invalid);
return;
}
UnsignedInt custom_format_data_size = 0;
if(!reader.readUnsignedInt(custom_format_data_size)) {
_lastError = std::move(invalid);
return;
}
arrayReserve(_customFormatData, 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 = std::move(invalid);
return;
}
arrayAppend(_customFormatData, entry);
}
if(!reader.readUEString(_saveType)) {
_lastError = std::move(invalid);
return;
}
UnrealPropertyBase::ptr prop;
while((prop = _propSerialiser->read(reader)) != nullptr) {
arrayAppend(_properties, std::move(prop));
}
if(_properties.back()->name != "None"_s && _properties.back()->propertyType != "NoneProperty"_s) {
_lastError = "Couldn't find a final NoneProperty."_s;
return;
}
reader.closeFile();
_valid = true;
}