Compare commits

...

5 commits

Author SHA1 Message Date
90a2a9edd9
Add ImportExport, with custom style support. 2024-03-25 12:08:35 +01:00
04e99d4953
BinaryIo: fix Reader::readValue().
std::fread() returns how many things it read, not the amount of
bytes read. Worst thing is, I got this right everywhere else.
2024-03-25 12:02:12 +01:00
c9ac1ad4c8
Utility/Crc32: fix naming. 2024-03-17 15:57:31 +01:00
d242431d78
Mass: how did that space get here ? 2024-03-14 15:09:08 +01:00
20f116d832
Mass: improve formatting slightly. 2024-03-11 22:16:51 +01:00
14 changed files with 450 additions and 5 deletions

View file

@ -61,7 +61,7 @@ class Reader {
template<typename T> template<typename T>
bool readValue(T& value) { bool readValue(T& value) {
return fread(&value, sizeof(T), 1, _file) == sizeof(T); return fread(&value, sizeof(T), 1, _file) == 1;
} }
template<std::size_t S> template<std::size_t S>

View file

@ -128,6 +128,14 @@ set(Gvas_SOURCES
Gvas/PropertySerialiser.cpp 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) if(CORRADE_TARGET_WINDOWS)
set(SAVETOOL_RC_FILE resource.rc) set(SAVETOOL_RC_FILE resource.rc)
endif() endif()
@ -199,6 +207,7 @@ add_executable(MassBuilderSaveTool
${Logger_SOURCES} ${Logger_SOURCES}
${BinaryIo_SOURCES} ${BinaryIo_SOURCES}
${Gvas_SOURCES} ${Gvas_SOURCES}
${ImportExport_SOURCES}
${SAVETOOL_RC_FILE} ${SAVETOOL_RC_FILE}
${Assets} ${Assets}
) )

127
src/ImportExport/Export.cpp Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#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>(Keys::CustomStyle::Name);
writer.writeUEStringToArray(style.name);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::Colour);
writer.writeValueToArray<Color4>(style.colour);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::Metallic);
writer.writeValueToArray<float>(style.metallic);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::Gloss);
writer.writeValueToArray<float>(style.gloss);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::Glow);
writer.writeValueToArray<bool>(style.glow);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternId);
writer.writeValueToArray<std::int32_t>(style.patternId);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternOpacity);
writer.writeValueToArray<float>(style.opacity);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternOffset);
writer.writeValueToArray<Vector2>(style.offset);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternRotation);
writer.writeValueToArray<float>(style.rotation);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternScale);
writer.writeValueToArray<float>(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;
}
}}

31
src/ImportExport/Export.h Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/StringView.h>
#include "../Mass/CustomStyle.h"
using namespace Corrade;
namespace mbst { namespace ImportExport {
auto lastExportError() -> Containers::StringView;
bool exportStyle(Containers::StringView mass_name, GameObjects::CustomStyle& style);
}}

186
src/ImportExport/Import.cpp Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
#include <cstring>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Utility/Path.h>
#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<GameObjects::CustomStyle>
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<char> 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<char> 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<Color4>(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);
}
}}

31
src/ImportExport/Import.h Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/StringView.h>
#include "../Mass/CustomStyle.h"
using namespace Corrade;
namespace mbst { namespace ImportExport {
auto lastImportError() -> Containers::StringView;
auto importStyle(Containers::StringView filename) -> Containers::Optional<GameObjects::CustomStyle>;
}}

36
src/ImportExport/Keys.h Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
#include <cstdint>
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
};
}}}

View file

@ -39,6 +39,15 @@ struct CustomStyle {
Vector2 offset{0.5f}; Vector2 offset{0.5f};
float rotation = 0.0f; float rotation = 0.0f;
float scale = 0.5f; 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;
}; };
}} }}

View file

@ -54,7 +54,7 @@ class Mass {
Mass(Mass&&) = default; Mass(Mass&&) = default;
Mass& operator=(Mass&&) = default; Mass& operator=(Mass&&) = default;
auto lastError() -> Containers::StringView ; auto lastError() -> Containers::StringView;
static auto getNameFromFile(Containers::StringView path) -> Containers::Optional<Containers::String>; static auto getNameFromFile(Containers::StringView path) -> Containers::Optional<Containers::String>;

View file

@ -413,6 +413,10 @@ Mass::getArmourCustomStyles() {
} }
getCustomStyles(_armour.customStyles, armour_styles); getCustomStyles(_armour.customStyles, armour_styles);
for(auto& style : _armour.customStyles) {
style.type = CustomStyle::Type::Armour;
}
} }
bool bool

View file

@ -373,6 +373,10 @@ Mass::getFrameCustomStyles() {
} }
getCustomStyles(_frame.customStyles, frame_styles); getCustomStyles(_frame.customStyles, frame_styles);
for(auto& style : _frame.customStyles) {
style.type = CustomStyle::Type::Frame;
}
} }
bool bool

View file

@ -57,6 +57,10 @@ Mass::getGlobalStyles() {
} }
getCustomStyles(_globalStyles, global_styles); getCustomStyles(_globalStyles, global_styles);
for(auto& style : _globalStyles) {
style.type = CustomStyle::Type::Global;
}
} }
bool bool

View file

@ -219,6 +219,10 @@ Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weap
getCustomStyles(weapon.customStyles, custom_styles); getCustomStyles(weapon.customStyles, custom_styles);
for(auto& style : weapon.customStyles) {
style.type = CustomStyle::Type::Weapon;
}
weapon.attached = weapon_prop->at<Gvas::Types::BoolProperty>(MASS_WEAPON_ATTACH)->value; weapon.attached = weapon_prop->at<Gvas::Types::BoolProperty>(MASS_WEAPON_ATTACH)->value;
auto& damage_type = weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_DAMAGE_TYPE)->enumValue; auto& damage_type = weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_DAMAGE_TYPE)->enumValue;
#define c(enumerator, strenum) if(damage_type == (strenum)) { weapon.damageType = Weapon::DamageType::enumerator; } else #define c(enumerator, strenum) if(damage_type == (strenum)) { weapon.damageType = Weapon::DamageType::enumerator; } else
@ -356,7 +360,7 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
} }
weapon_prop->at<Gvas::Types::BoolProperty>(MASS_WEAPON_DUAL_WIELD)->value = weapon.dualWield; weapon_prop->at<Gvas::Types::BoolProperty>(MASS_WEAPON_DUAL_WIELD)->value = weapon.dualWield;
switch(weapon.effectColourMode) { switch(weapon.effectColourMode) {
#define c(enumerator, enumstr) case Weapon::EffectColourMode::enumerator: \ #define c(enumerator, enumstr) case Weapon::EffectColourMode::enumerator: \
weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_COLOUR_EFX_MODE)->enumValue = enumstr; \ weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_COLOUR_EFX_MODE)->enumValue = enumstr; \
break; break;
#include "../Maps/EffectColourModes.hpp" #include "../Maps/EffectColourModes.hpp"

View file

@ -16,10 +16,10 @@
#include "Crc32.h" #include "Crc32.h"
namespace mbst { namespace Crc32 { namespace mbst { namespace Utilities {
std::uint32_t std::uint32_t
update(std::uint32_t initial, Containers::ArrayView<const void> data) { crc32(std::uint32_t initial, Containers::ArrayView<const void> data) {
static const auto table = []{ static const auto table = []{
std::uint32_t polynomial = 0xEDB88320u; std::uint32_t polynomial = 0xEDB88320u;
Containers::StaticArray<256, std::uint32_t> temp{ValueInit}; Containers::StaticArray<256, std::uint32_t> temp{ValueInit};