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