From 90a2a9edd95aa2273d1651719000a0f835ac2ee5 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquemin Date: Mon, 25 Mar 2024 12:08:35 +0100 Subject: [PATCH] Add ImportExport, with custom style support. --- src/CMakeLists.txt | 9 ++ src/ImportExport/Export.cpp | 127 ++++++++++++++++++++++++ src/ImportExport/Export.h | 31 ++++++ src/ImportExport/Import.cpp | 186 ++++++++++++++++++++++++++++++++++++ src/ImportExport/Import.h | 31 ++++++ src/ImportExport/Keys.h | 36 +++++++ src/Mass/CustomStyle.h | 9 ++ src/Mass/Mass_Armour.cpp | 4 + src/Mass/Mass_Frame.cpp | 4 + src/Mass/Mass_Styles.cpp | 4 + src/Mass/Mass_Weapons.cpp | 4 + 11 files changed, 445 insertions(+) create mode 100644 src/ImportExport/Export.cpp create mode 100644 src/ImportExport/Export.h create mode 100644 src/ImportExport/Import.cpp create mode 100644 src/ImportExport/Import.h create mode 100644 src/ImportExport/Keys.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 67291af..ef8f1a7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -128,6 +128,14 @@ set(Gvas_SOURCES Gvas/PropertySerialiser.cpp ) +set(ImportExport_SOURCES + ImportExport/Import.h + ImportExport/Import.cpp + ImportExport/Export.h + ImportExport/Export.cpp + ImportExport/Keys.h +) + if(CORRADE_TARGET_WINDOWS) set(SAVETOOL_RC_FILE resource.rc) endif() @@ -199,6 +207,7 @@ add_executable(MassBuilderSaveTool ${Logger_SOURCES} ${BinaryIo_SOURCES} ${Gvas_SOURCES} + ${ImportExport_SOURCES} ${SAVETOOL_RC_FILE} ${Assets} ) diff --git a/src/ImportExport/Export.cpp b/src/ImportExport/Export.cpp new file mode 100644 index 0000000..44d4309 --- /dev/null +++ b/src/ImportExport/Export.cpp @@ -0,0 +1,127 @@ +// 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 . + +#include +#include + +#include "../Logger/Logger.h" +#include "../BinaryIo/Writer.h" +#include "../Configuration/Configuration.h" +#include "../Utilities/Crc32.h" + +#include "Keys.h" + +#include "Export.h" + +namespace mbst { namespace ImportExport { + +static Containers::String last_export_error; + +Containers::StringView +lastExportError() { + return last_export_error; +} + +bool +exportStyle(Containers::StringView mass_name, mbst::GameObjects::CustomStyle& style) { + Containers::String style_type_str; + switch(style.type) { + case GameObjects::CustomStyle::Type::Unknown: + style_type_str = "Style"; + break; + case GameObjects::CustomStyle::Type::Frame: + style_type_str = "FrameStyle"; + break; + case GameObjects::CustomStyle::Type::Armour: + style_type_str = "ArmourStyle"; + break; + case GameObjects::CustomStyle::Type::Weapon: + style_type_str = "WeaponStyle"; + break; + case GameObjects::CustomStyle::Type::Global: + style_type_str = "GlobalStyle"; + break; + } + + auto filename = Utility::format("{}_{}_{}.style.mbst", mass_name, style_type_str, style.name); + for(auto& c : filename) { + if(c == ' ') { + c = '_'; + } + } + + auto path = Utility::Path::join(conf().directories().styles, filename); + BinaryIo::Writer writer{path}; + + if(!writer.open()) { + last_export_error = path + " couldn't be opened."; + return false; + } + + if(!writer.writeString("MBSTSTYLE")) { + LOG_ERROR(last_export_error = "Couldn't write magic bytes into " + filename); + return false; + } + + writer.writeValueToArray(Keys::CustomStyle::Name); + writer.writeUEStringToArray(style.name); + + writer.writeValueToArray(Keys::CustomStyle::Colour); + writer.writeValueToArray(style.colour); + writer.writeValueToArray(Keys::CustomStyle::Metallic); + writer.writeValueToArray(style.metallic); + writer.writeValueToArray(Keys::CustomStyle::Gloss); + writer.writeValueToArray(style.gloss); + writer.writeValueToArray(Keys::CustomStyle::Glow); + writer.writeValueToArray(style.glow); + + writer.writeValueToArray(Keys::CustomStyle::PatternId); + writer.writeValueToArray(style.patternId); + writer.writeValueToArray(Keys::CustomStyle::PatternOpacity); + writer.writeValueToArray(style.opacity); + writer.writeValueToArray(Keys::CustomStyle::PatternOffset); + writer.writeValueToArray(style.offset); + writer.writeValueToArray(Keys::CustomStyle::PatternRotation); + writer.writeValueToArray(style.rotation); + writer.writeValueToArray(Keys::CustomStyle::PatternScale); + writer.writeValueToArray(style.scale); + + if(!writer.writeUint64(writer.array().size())) { + LOG_ERROR(last_export_error = "Couldn't write data size into " + filename); + writer.closeFile(); + Utility::Path::remove(path); + return false; + } + + auto crc = Utilities::crc32(0, writer.array()); + if(!writer.writeUint32(crc)) { + LOG_ERROR(last_export_error = "Couldn't write CRC32 checksum into " + filename); + writer.closeFile(); + Utility::Path::remove(path); + return false; + } + + if(!writer.flushToFile()) { + LOG_ERROR(last_export_error = "Couldn't write data into " + filename); + writer.closeFile(); + Utility::Path::remove(path); + return false; + } + + return true; +} + +}} diff --git a/src/ImportExport/Export.h b/src/ImportExport/Export.h new file mode 100644 index 0000000..46c00f8 --- /dev/null +++ b/src/ImportExport/Export.h @@ -0,0 +1,31 @@ +#pragma once + +// 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 . + +#include + +#include "../Mass/CustomStyle.h" + +using namespace Corrade; + +namespace mbst { namespace ImportExport { + +auto lastExportError() -> Containers::StringView; + +bool exportStyle(Containers::StringView mass_name, GameObjects::CustomStyle& style); + +}} diff --git a/src/ImportExport/Import.cpp b/src/ImportExport/Import.cpp new file mode 100644 index 0000000..d07a863 --- /dev/null +++ b/src/ImportExport/Import.cpp @@ -0,0 +1,186 @@ +// 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 . + +#include + +#include +#include +#include +#include + +#include "../BinaryIo/Reader.h" +#include "../Configuration/Configuration.h" +#include "../Logger/Logger.h" +#include "../Utilities/Crc32.h" + +#include "Keys.h" + +#include "Import.h" + +namespace mbst { namespace ImportExport { + +static Containers::String last_import_error; + +Containers::StringView +lastImportError() { + return last_import_error; +} + +Containers::Optional +importStyle(Containers::StringView filename) { + auto path = Utility::Path::join(conf().directories().styles, filename); + if(!Utility::Path::exists(path)) { + LOG_ERROR(last_import_error = path + " doesn't exist."); + return Containers::NullOpt; + } + + BinaryIo::Reader reader{path}; + if(!reader.open()) { + last_import_error = path + " couldn't be opened."; + return Containers::NullOpt; + } + + Containers::Array magic_bytes; + if(!reader.readArray(magic_bytes, 9)) { + LOG_ERROR(last_import_error = "Couldn't read the magic bytes."); + return Containers::NullOpt; + } + + Containers::StringView magic_bytes_view = magic_bytes; + static const auto expected_magic_bytes = "MBSTSTYLE"_s; + if(magic_bytes_view != expected_magic_bytes) { + LOG_ERROR(last_import_error = "Magic bytes are mismatched."); + return Containers::NullOpt; + } + + std::size_t data_size; + if(!reader.readUint64(data_size)) { + LOG_ERROR(last_import_error = "Couldn't read data size."); + return Containers::NullOpt; + } + + std::uint32_t crc; + if(!reader.readUint32(crc)) { + LOG_ERROR(last_import_error = "Couldn't read CRC-32 checksum."); + return Containers::NullOpt; + } + + auto position = reader.position(); + + { + Containers::Array data; + if(!reader.readArray(data, data_size)) { + LOG_ERROR(last_import_error = "Couldn't read data."); + return Containers::NullOpt; + } + + auto computed_crc = Utilities::crc32(0, data); + if(computed_crc != crc) { + LOG_ERROR(last_import_error = Utility::format("CRC-32 checksum doesn't match. " + "Expected {}, got {} instead.", + crc, computed_crc)); + return Containers::NullOpt; + } + } + + if(!reader.seek(position)) { + LOG_ERROR(last_import_error = Utility::format("Couldn't seek to position {}.", position)); + return Containers::NullOpt; + } + + GameObjects::CustomStyle style{}; + + while(!reader.eof()) { + Keys::CustomStyle key; + if(!reader.readValue(key)) { + if(reader.eof()) { + break; + } + LOG_ERROR(last_import_error = "Couldn't read key."); + return Containers::NullOpt; + } + + switch(key) { + case Keys::Name: + if(!reader.readUEString(style.name)) { + LOG_ERROR(last_import_error = "Couldn't read style name."); + return Containers::NullOpt; + } + break; + case Keys::Colour: + if(!reader.readValue(style.colour)) { + LOG_ERROR(last_import_error = "Couldn't read style colour."); + return Containers::NullOpt; + } + break; + case Keys::Metallic: + if(!reader.readFloat(style.metallic)) { + LOG_ERROR(last_import_error = "Couldn't read style metallic."); + return Containers::NullOpt; + } + break; + case Keys::Gloss: + if(!reader.readFloat(style.gloss)) { + LOG_ERROR(last_import_error = "Couldn't read style gloss."); + return Containers::NullOpt; + } + break; + case Keys::Glow: + if(!reader.readValue(style.glow)) { + LOG_ERROR(last_import_error = "Couldn't read style glow."); + return Containers::NullOpt; + } + break; + case Keys::PatternId: + if(!reader.readInt32(style.patternId)) { + LOG_ERROR(last_import_error = "Couldn't read style pattern ID."); + return Containers::NullOpt; + } + break; + case Keys::PatternOpacity: + if(!reader.readFloat(style.opacity)) { + LOG_ERROR(last_import_error = "Couldn't read style pattern opacity."); + return Containers::NullOpt; + } + break; + case Keys::PatternOffset: + if(!reader.readValue(style.offset)) { + LOG_ERROR(last_import_error = "Couldn't read style pattern offset."); + return Containers::NullOpt; + } + break; + case Keys::PatternRotation: + if(!reader.readFloat(style.rotation)) { + LOG_ERROR(last_import_error = "Couldn't read style pattern rotation."); + return Containers::NullOpt; + } + break; + case Keys::PatternScale: + if(!reader.readFloat(style.scale)) { + LOG_ERROR(last_import_error = "Couldn't read style pattern scale."); + return Containers::NullOpt; + } + break; + default: + LOG_ERROR(last_import_error = Utility::format("Unknown key {}.", key)); + return Containers::NullOpt; + } + } + + return Utility::move(style); +} + +}} diff --git a/src/ImportExport/Import.h b/src/ImportExport/Import.h new file mode 100644 index 0000000..184ae07 --- /dev/null +++ b/src/ImportExport/Import.h @@ -0,0 +1,31 @@ +#pragma once + +// 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 . + +#include + +#include "../Mass/CustomStyle.h" + +using namespace Corrade; + +namespace mbst { namespace ImportExport { + +auto lastImportError() -> Containers::StringView; + +auto importStyle(Containers::StringView filename) -> Containers::Optional; + +}} diff --git a/src/ImportExport/Keys.h b/src/ImportExport/Keys.h new file mode 100644 index 0000000..0ea1228 --- /dev/null +++ b/src/ImportExport/Keys.h @@ -0,0 +1,36 @@ +#pragma once + +// 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 . + +#include + +namespace mbst { namespace ImportExport { namespace Keys { + +enum CustomStyle: std::uint8_t { + Name = 0, // type: string + Colour = 1, // type: Magnum::Color4 + Metallic = 2, // type: float + Gloss = 3, // type: float + Glow = 4, // type: bool + PatternId = 5, // type: std::int32_t + PatternOpacity = 6, // type: float + PatternOffset = 7, // type: Magnum::Vector2 + PatternRotation = 8, // type: float + PatternScale = 9, // type: float +}; + +}}} diff --git a/src/Mass/CustomStyle.h b/src/Mass/CustomStyle.h index b041f8c..b7d6ec0 100644 --- a/src/Mass/CustomStyle.h +++ b/src/Mass/CustomStyle.h @@ -39,6 +39,15 @@ struct CustomStyle { Vector2 offset{0.5f}; float rotation = 0.0f; float scale = 0.5f; + + // This is only used to know which style array the current style is located in when exporting a standalone style. + enum class Type: std::uint8_t { + Unknown, + Frame, + Armour, + Weapon, + Global + } type = Type::Unknown; }; }} diff --git a/src/Mass/Mass_Armour.cpp b/src/Mass/Mass_Armour.cpp index 720026a..8f42da0 100644 --- a/src/Mass/Mass_Armour.cpp +++ b/src/Mass/Mass_Armour.cpp @@ -413,6 +413,10 @@ Mass::getArmourCustomStyles() { } getCustomStyles(_armour.customStyles, armour_styles); + + for(auto& style : _armour.customStyles) { + style.type = CustomStyle::Type::Armour; + } } bool diff --git a/src/Mass/Mass_Frame.cpp b/src/Mass/Mass_Frame.cpp index 58df0d7..f897ed9 100644 --- a/src/Mass/Mass_Frame.cpp +++ b/src/Mass/Mass_Frame.cpp @@ -373,6 +373,10 @@ Mass::getFrameCustomStyles() { } getCustomStyles(_frame.customStyles, frame_styles); + + for(auto& style : _frame.customStyles) { + style.type = CustomStyle::Type::Frame; + } } bool diff --git a/src/Mass/Mass_Styles.cpp b/src/Mass/Mass_Styles.cpp index 9db7c05..daa0c8d 100644 --- a/src/Mass/Mass_Styles.cpp +++ b/src/Mass/Mass_Styles.cpp @@ -57,6 +57,10 @@ Mass::getGlobalStyles() { } getCustomStyles(_globalStyles, global_styles); + + for(auto& style : _globalStyles) { + style.type = CustomStyle::Type::Global; + } } bool diff --git a/src/Mass/Mass_Weapons.cpp b/src/Mass/Mass_Weapons.cpp index 3527de4..ef8f975 100644 --- a/src/Mass/Mass_Weapons.cpp +++ b/src/Mass/Mass_Weapons.cpp @@ -219,6 +219,10 @@ Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayViewat(MASS_WEAPON_ATTACH)->value; auto& damage_type = weapon_prop->at(MASS_WEAPON_DAMAGE_TYPE)->enumValue; #define c(enumerator, strenum) if(damage_type == (strenum)) { weapon.damageType = Weapon::DamageType::enumerator; } else