2021-09-22 17:37:50 +02:00
|
|
|
// MassBuilderSaveTool
|
|
|
|
// Copyright (C) 2021 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/Directory.h>
|
|
|
|
|
|
|
|
#include "BinaryReader.h"
|
|
|
|
#include "BinaryWriter.h"
|
|
|
|
|
|
|
|
#include "UESaveFile.h"
|
|
|
|
|
|
|
|
UESaveFile::UESaveFile(std::string filepath)
|
|
|
|
{
|
|
|
|
_filepath = std::move(filepath);
|
|
|
|
|
|
|
|
loadData();
|
|
|
|
}
|
|
|
|
|
|
|
|
auto UESaveFile::valid() const -> bool {
|
|
|
|
return _valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto UESaveFile::lastError() const -> const std::string& {
|
|
|
|
return _lastError;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto UESaveFile::reloadData() -> bool {
|
2021-09-22 19:46:41 +02:00
|
|
|
if(_noReloadAfterSave) {
|
2021-09-27 16:16:47 +02:00
|
|
|
_noReloadAfterSave = false;
|
2021-09-22 19:46:41 +02:00
|
|
|
return valid();
|
|
|
|
}
|
|
|
|
|
2021-09-27 16:21:39 +02:00
|
|
|
_properties = Containers::Array<UnrealPropertyBase::ptr>{};
|
2021-09-22 17:37:50 +02:00
|
|
|
loadData();
|
|
|
|
return valid();
|
|
|
|
}
|
|
|
|
|
2021-09-23 15:08:20 +02:00
|
|
|
void UESaveFile::appendProperty(UnrealPropertyBase::ptr prop) {
|
|
|
|
auto none_prop = std::move(_properties.back());
|
|
|
|
_properties.back() = std::move(prop);
|
|
|
|
arrayAppend(_properties, std::move(none_prop));
|
|
|
|
}
|
|
|
|
|
2021-09-22 17:37:50 +02:00
|
|
|
auto UESaveFile::props() -> Containers::ArrayView<UnrealPropertyBase::ptr> {
|
|
|
|
return _properties;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto UESaveFile::saveToFile() -> bool {
|
2021-09-22 19:46:41 +02:00
|
|
|
BinaryWriter writer{_filepath + ".tmp"};
|
2021-09-22 17:37:50 +02:00
|
|
|
|
|
|
|
if(!writer.open()) {
|
2021-09-23 18:25:28 +02:00
|
|
|
_lastError = "Couldn't open the file for saving.";
|
2021-09-22 17:37:50 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-09-22 18:22:48 +02:00
|
|
|
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) ||
|
2021-09-22 17:37:50 +02:00
|
|
|
!writer.writeUEString(_engineVersion.buildId))
|
|
|
|
{
|
2021-09-23 18:25:28 +02:00
|
|
|
_lastError = "Couldn't write the header.";
|
2021-09-22 17:37:50 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!writer.writeUnsignedInt(_customFormatVersion) ||
|
|
|
|
!writer.writeUnsignedInt(_customFormatData.size()))
|
|
|
|
{
|
2021-09-23 18:25:28 +02:00
|
|
|
_lastError = "Couldn't write the header.";
|
2021-09-22 17:37:50 +02:00
|
|
|
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))
|
|
|
|
{
|
2021-09-23 18:25:28 +02:00
|
|
|
_lastError = "Couldn't write the header.";
|
2021-09-22 17:37:50 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!writer.writeUEString(_saveType)) {
|
2021-09-23 18:25:28 +02:00
|
|
|
_lastError = "Couldn't write the header.";
|
2021-09-22 17:37:50 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(auto& prop : _properties) {
|
|
|
|
UnsignedLong bytes_written = 0;
|
|
|
|
if(!_propSerialiser.write(prop, bytes_written, writer)) {
|
2021-09-23 18:25:28 +02:00
|
|
|
_lastError = "Couldn't write the property " + *prop->name + " to the array.";
|
2021-09-22 17:37:50 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!writer.flushToFile()) {
|
2021-09-23 18:25:28 +02:00
|
|
|
_lastError = "Couldn't write the property " + *prop->name + " to the file.";
|
2021-09-22 17:37:50 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
writer.writeUnsignedInt(0);
|
|
|
|
|
2021-09-22 21:50:39 +02:00
|
|
|
writer.closeFile();
|
|
|
|
|
2021-09-22 19:46:41 +02:00
|
|
|
if(!Utility::Directory::copy(_filepath, _filepath + ".bak")) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!Utility::Directory::rm(_filepath)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!Utility::Directory::move(_filepath + ".tmp", _filepath)) {
|
|
|
|
Utility::Directory::move(_filepath + ".bak", _filepath);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_noReloadAfterSave = true;
|
|
|
|
|
2021-09-22 17:37:50 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UESaveFile::loadData() {
|
|
|
|
_valid = false;
|
|
|
|
|
|
|
|
if(!Utility::Directory::exists(_filepath)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
BinaryReader reader{_filepath};
|
|
|
|
|
|
|
|
if(!reader.open()) {
|
|
|
|
_lastError = _filepath + " couldn't be opened.";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Containers::Array<char> magic;
|
|
|
|
if(!reader.readArray(magic, 4)) {
|
|
|
|
_lastError = "Couldn't read magic bytes in " + _filepath;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string invalid = _filepath + " isn't a valid UE4 save.";
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2021-09-23 15:07:29 +02:00
|
|
|
if(_properties.back()->name != "None" && _properties.back()->propertyType != "NoneProperty") {
|
2021-09-27 16:16:47 +02:00
|
|
|
_lastError = "Couldn't find a final NoneProperty.";
|
2021-09-23 15:07:29 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-09-27 16:16:47 +02:00
|
|
|
reader.closeFile();
|
|
|
|
|
2021-09-22 17:37:50 +02:00
|
|
|
_valid = true;
|
|
|
|
}
|