269 lines
7.3 KiB
C++
269 lines
7.3 KiB
C++
// MassBuilderSaveTool
|
|
// Copyright (C) 2021-2024 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/Optional.h>
|
|
#include <Corrade/Utility/Path.h>
|
|
|
|
#include "../Logger/Logger.h"
|
|
|
|
#include "../BinaryIo/Reader.h"
|
|
#include "../BinaryIo/Writer.h"
|
|
|
|
#include "File.h"
|
|
|
|
using namespace Containers::Literals;
|
|
|
|
namespace Gvas {
|
|
|
|
File::File(Containers::String filepath):
|
|
_propSerialiser{PropertySerialiser::instance()}
|
|
{
|
|
_filepath = Utility::move(filepath);
|
|
|
|
loadData();
|
|
}
|
|
|
|
bool
|
|
File::valid() const {
|
|
return _valid;
|
|
}
|
|
|
|
Containers::StringView
|
|
File::lastError() const {
|
|
return _lastError;
|
|
}
|
|
|
|
bool
|
|
File::reloadData() {
|
|
if(_noReloadAfterSave) {
|
|
_noReloadAfterSave = false;
|
|
return valid();
|
|
}
|
|
|
|
_properties = Containers::Array<Types::UnrealPropertyBase::ptr>{};
|
|
loadData();
|
|
return valid();
|
|
}
|
|
|
|
Containers::StringView
|
|
File::saveType() {
|
|
return _saveType;
|
|
}
|
|
|
|
void
|
|
File::appendProperty(Types::UnrealPropertyBase::ptr prop) {
|
|
auto none_prop = Utility::move(_properties.back());
|
|
_properties.back() = Utility::move(prop);
|
|
arrayAppend(_properties, Utility::move(none_prop));
|
|
}
|
|
|
|
Containers::ArrayView<Types::UnrealPropertyBase::ptr>
|
|
File::props() {
|
|
return _properties;
|
|
}
|
|
|
|
bool
|
|
File::saveToFile() {
|
|
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;
|
|
}
|
|
|
|
BinaryIo::Writer 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.writeUint32(_saveVersion) ||
|
|
!writer.writeUint32(_packageVersion) ||
|
|
!writer.writeUint16(_engineVersion.major) ||
|
|
!writer.writeUint16(_engineVersion.minor) ||
|
|
!writer.writeUint16(_engineVersion.patch) ||
|
|
!writer.writeUint32(_engineVersion.build) ||
|
|
!writer.writeUEString(_engineVersion.buildId))
|
|
{
|
|
_lastError = "Couldn't write the header."_s;
|
|
LOG_ERROR(_lastError);
|
|
return false;
|
|
}
|
|
|
|
if(!writer.writeUint32(_customFormatVersion) ||
|
|
!writer.writeUint32(_customFormatData.size()))
|
|
{
|
|
_lastError = "Couldn't write the custom format data."_s;
|
|
LOG_ERROR(_lastError);
|
|
return false;
|
|
}
|
|
|
|
for(auto& i : _customFormatData) {
|
|
if(!writer.writeStaticArray<16>(staticArrayView(i.id)) || !writer.writeUint32(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) {
|
|
std::size_t 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.writeUint32(0u);
|
|
|
|
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
|
|
File::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;
|
|
}
|
|
|
|
BinaryIo::Reader reader{_filepath};
|
|
|
|
if(!reader.open()) {
|
|
_lastError = _filepath + " couldn't be opened."_s;
|
|
LOG_ERROR(_lastError);
|
|
return;
|
|
}
|
|
|
|
Containers::Array<char> 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.readUint32(_saveVersion) ||
|
|
!reader.readUint32(_packageVersion) ||
|
|
!reader.readUint16(_engineVersion.major) ||
|
|
!reader.readUint16(_engineVersion.minor) ||
|
|
!reader.readUint16(_engineVersion.patch) ||
|
|
!reader.readUint32(_engineVersion.build) ||
|
|
!reader.readUEString(_engineVersion.buildId))
|
|
{
|
|
_lastError = "Couldn't read version data.";
|
|
LOG_ERROR(_lastError);
|
|
return;
|
|
}
|
|
|
|
if(!reader.readUint32(_customFormatVersion)) {
|
|
_lastError = "Couldn't read the custom format version.";
|
|
LOG_ERROR(_lastError);
|
|
return;
|
|
}
|
|
|
|
std::uint32_t custom_format_data_size = 0;
|
|
|
|
if(!reader.readUint32(custom_format_data_size)) {
|
|
_lastError = "Couldn't read the custom format data size.";
|
|
LOG_ERROR(_lastError);
|
|
return;
|
|
}
|
|
|
|
_customFormatData = Containers::Array<CustomFormatDataEntry>{custom_format_data_size};
|
|
|
|
for(auto& entry : _customFormatData) {
|
|
if(!reader.readStaticArray(entry.id) ||
|
|
!reader.readInt32(entry.value))
|
|
{
|
|
_lastError = "Couldn't read the custom format data";
|
|
LOG_ERROR(_lastError);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(!reader.readUEString(_saveType)) {
|
|
_lastError = "Couldn't read the save type.";
|
|
LOG_ERROR(_lastError);
|
|
return;
|
|
}
|
|
|
|
Types::UnrealPropertyBase::ptr prop;
|
|
while((prop = _propSerialiser->read(reader)) != nullptr) {
|
|
arrayAppend(_properties, Utility::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;
|
|
}
|
|
|
|
}
|