diff --git a/CMakeLists.txt b/CMakeLists.txt index 5281f8d..e54cc44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # MassBuilderSaveTool -# Copyright (C) 2021 Guillaume Jacquemin +# Copyright (C) 2021-2022 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad2ac6a..6e8d616 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ # MassBuilderSaveTool -# Copyright (C) 2021 Guillaume Jacquemin +# Copyright (C) 2021-2022 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 @@ -18,7 +18,7 @@ set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(SAVETOOL_PROJECT_VERSION 1.2.0) +set(SAVETOOL_PROJECT_VERSION 1.3.0-pre) find_package(Corrade REQUIRED Main Containers Utility Interconnect) find_package(Magnum REQUIRED GL Sdl2Application) @@ -28,6 +28,86 @@ set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON) corrade_add_resource(Assets assets.conf) +add_library(UESaveFile STATIC EXCLUDE_FROM_ALL + UESaveFile/Serialisers/AbstractUnrealCollectionPropertySerialiser.h + UESaveFile/Serialisers/AbstractUnrealPropertySerialiser.h + UESaveFile/Serialisers/AbstractUnrealStructSerialiser.h + UESaveFile/Serialisers/ArrayPropertySerialiser.h + UESaveFile/Serialisers/ArrayPropertySerialiser.cpp + UESaveFile/Serialisers/BoolPropertySerialiser.h + UESaveFile/Serialisers/BoolPropertySerialiser.cpp + UESaveFile/Serialisers/BytePropertySerialiser.h + UESaveFile/Serialisers/BytePropertySerialiser.cpp + UESaveFile/Serialisers/ColourPropertySerialiser.h + UESaveFile/Serialisers/ColourPropertySerialiser.cpp + UESaveFile/Serialisers/DateTimePropertySerialiser.h + UESaveFile/Serialisers/DateTimePropertySerialiser.cpp + UESaveFile/Serialisers/EnumPropertySerialiser.h + UESaveFile/Serialisers/EnumPropertySerialiser.cpp + UESaveFile/Serialisers/FloatPropertySerialiser.h + UESaveFile/Serialisers/FloatPropertySerialiser.cpp + UESaveFile/Serialisers/GuidPropertySerialiser.h + UESaveFile/Serialisers/GuidPropertySerialiser.cpp + UESaveFile/Serialisers/IntPropertySerialiser.h + UESaveFile/Serialisers/IntPropertySerialiser.cpp + UESaveFile/Serialisers/MapPropertySerialiser.h + UESaveFile/Serialisers/MapPropertySerialiser.cpp + UESaveFile/Serialisers/ResourcePropertySerialiser.h + UESaveFile/Serialisers/ResourcePropertySerialiser.cpp + UESaveFile/Serialisers/RotatorPropertySerialiser.h + UESaveFile/Serialisers/RotatorPropertySerialiser.cpp + UESaveFile/Serialisers/StringPropertySerialiser.h + UESaveFile/Serialisers/StringPropertySerialiser.cpp + UESaveFile/Serialisers/SetPropertySerialiser.h + UESaveFile/Serialisers/SetPropertySerialiser.cpp + UESaveFile/Serialisers/StructSerialiser.h + UESaveFile/Serialisers/StructSerialiser.cpp + UESaveFile/Serialisers/TextPropertySerialiser.h + UESaveFile/Serialisers/TextPropertySerialiser.cpp + UESaveFile/Serialisers/UnrealPropertySerialiser.h + UESaveFile/Serialisers/VectorPropertySerialiser.h + UESaveFile/Serialisers/VectorPropertySerialiser.cpp + UESaveFile/Serialisers/Vector2DPropertySerialiser.h + UESaveFile/Serialisers/Vector2DPropertySerialiser.cpp + + UESaveFile/Types/ArrayProperty.h + UESaveFile/Types/BoolProperty.h + UESaveFile/Types/ByteProperty.h + UESaveFile/Types/ColourStructProperty.h + UESaveFile/Types/DateTimeStructProperty.h + UESaveFile/Types/EnumProperty.h + UESaveFile/Types/FloatProperty.h + UESaveFile/Types/GenericStructProperty.h + UESaveFile/Types/GuidStructProperty.h + UESaveFile/Types/IntProperty.h + UESaveFile/Types/MapProperty.h + UESaveFile/Types/NoneProperty.h + UESaveFile/Types/RotatorStructProperty.h + UESaveFile/Types/SetProperty.h + UESaveFile/Types/StringProperty.h + UESaveFile/Types/StructProperty.h + UESaveFile/Types/ResourceItemValue.h + UESaveFile/Types/TextProperty.h + UESaveFile/Types/UnrealProperty.h + UESaveFile/Types/UnrealPropertyBase.h + UESaveFile/Types/VectorStructProperty.h + + UESaveFile/Debug.h + UESaveFile/Debug.cpp + UESaveFile/UESaveFile.h + UESaveFile/UESaveFile.cpp + UESaveFile/BinaryReader.h + UESaveFile/BinaryReader.cpp + UESaveFile/BinaryWriter.h + UESaveFile/BinaryWriter.cpp + UESaveFile/PropertySerialiser.h + UESaveFile/PropertySerialiser.cpp) + +target_link_libraries(UESaveFile PRIVATE + Corrade::Containers + Corrade::Utility + Magnum::Magnum) + add_executable(MassBuilderSaveTool WIN32 main.cpp SaveTool/SaveTool.h @@ -35,19 +115,40 @@ add_executable(MassBuilderSaveTool WIN32 SaveTool/SaveTool_drawAbout.cpp SaveTool/SaveTool_drawMainMenu.cpp SaveTool/SaveTool_MainManager.cpp + SaveTool/SaveTool_MassViewer.cpp + SaveTool/SaveTool_MassViewer_Frame.cpp + SaveTool/SaveTool_MassViewer_Armour.cpp + SaveTool/SaveTool_MassViewer_Weapons.cpp SaveTool/SaveTool_ProfileManager.cpp ProfileManager/ProfileManager.h ProfileManager/ProfileManager.cpp Profile/Profile.h Profile/Profile.cpp + Profile/ResourceIDs.h MassManager/MassManager.h MassManager/MassManager.cpp + Mass/Accessory.h + Mass/ArmourPart.h + Mass/CustomStyle.h + Mass/Decal.h + Mass/Joints.h Mass/Mass.h Mass/Mass.cpp + Mass/Weapon.h + Mass/Weapon.cpp + Mass/WeaponPart.h + Maps/Accessories.h + Maps/ArmourSets.h + Maps/ArmourSlots.hpp + Maps/DamageTypes.hpp + Maps/EffectColourModes.hpp Maps/LastMissionId.h Maps/StoryProgress.h + Maps/StyleNames.h + Maps/WeaponTypes.hpp ToastQueue/ToastQueue.h ToastQueue/ToastQueue.cpp + Utilities/Crc32.h FontAwesome/IconsFontAwesome5.h FontAwesome/IconsFontAwesome5Brands.h resource.rc @@ -57,7 +158,7 @@ if(CMAKE_BUILD_TYPE STREQUAL Debug) add_compile_definitions(SAVETOOL_DEBUG_BUILD) endif() add_compile_definitions(SAVETOOL_VERSION="${SAVETOOL_PROJECT_VERSION}" - SAVETOOL_CODENAME="Cute Quindolia" + SAVETOOL_CODENAME="Dickish Cyclops" SUPPORTED_GAME_VERSION="0.7.6") if(CMAKE_BUILD_TYPE STREQUAL Release) @@ -66,6 +167,10 @@ endif() target_link_options(MassBuilderSaveTool PRIVATE -static -static-libgcc -static-libstdc++) +if(CMAKE_BUILD_TYPE STREQUAL Release) + target_link_options(MassBuilderSaveTool PRIVATE -Wl,-S) +endif() + target_link_libraries(MassBuilderSaveTool PRIVATE Corrade::Containers Corrade::Utility @@ -75,6 +180,7 @@ target_link_libraries(MassBuilderSaveTool PRIVATE Magnum::GL Magnum::Sdl2Application MagnumIntegration::ImGui + UESaveFile efsw zip cpr::cpr diff --git a/src/Maps/Accessories.h b/src/Maps/Accessories.h new file mode 100644 index 0000000..3f41ccc --- /dev/null +++ b/src/Maps/Accessories.h @@ -0,0 +1,74 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Magnum; + +static const std::map accessories { + // Primitives + {1, "Cube (S)"}, + {2, "Pentagon (S)"}, + {3, "Hexagon (S)"}, + {4, "Cylinder (S)"}, + {5, "Sphere (S)"}, + {6, "TriPyramid (S)"}, + {7, "SquPyramid (S)"}, + {8, "PenPyramid (S)"}, + {9, "HexPyramid (S)"}, + {10, "Cone (S)"}, + {11, "SquStick (S)"}, + {12, "PenStick (S)"}, + {13, "HexStick (S)"}, + {14, "CycStick (S)"}, + {15, "Capsule (S)"}, + {16, "Decal Pad 01 (S)"}, + {17, "Decal Pad 02 (S)"}, + {18, "Decal Pad 03 (S)"}, + {19, "Decal Pad 04 (S)"}, + {20, "Decal Pad 05 (S)"}, + + {51, "SquBevel (S)"}, + {52, "TriBevel (S)"}, + {53, "PenBevel (S)"}, + {54, "HexBevel (S)"}, + {55, "CycBevel (S)"}, + {56, "RecBevel (S)"}, + {57, "DaiBevel (S)"}, + {58, "MonBevel (S)"}, + {59, "CofBevel (S)"}, + {60, "JevBevel (S)"}, + {61, "SquEmboss (S)"}, + {62, "TriEmboss (S)"}, + {63, "PenEmboss (S)"}, + {64, "HexEmboss (S)"}, + {65, "CycEmboss (S)"}, + {66, "RecEmboss (S)"}, + {67, "DaiEmboss (S)"}, + {68, "MonEmboss (S)"}, + {69, "CofEmboss (S)"}, + {70, "JevEmboss (S)"}, + + // Armours + + // Components + + // Connectors +}; diff --git a/src/Maps/ArmourSets.h b/src/Maps/ArmourSets.h new file mode 100644 index 0000000..24d46bd --- /dev/null +++ b/src/Maps/ArmourSets.h @@ -0,0 +1,51 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Magnum; + +struct ArmourSet { + const char* name; + bool neck_compatible; +}; + +static const std::map armour_sets { + {-1, {"", true}}, + {0, {"Vanguard", true}}, + {1, {"Assault Mk.I", true}}, + {2, {"Assault Mk.II", false}}, + {3, {"Assault Mk.III", false}}, + {7, {"Titan 001", true}}, + {8, {"Titan 002", false}}, + {9, {"Titan 003", false}}, + {13, {"Blitz X", true}}, + {14, {"Blitz EX", false}}, + {15, {"Blitz EXS", false}}, + {16, {"Kaiser S-R0", true}}, + {17, {"Kaiser S-R1", false}}, + {18, {"Kaiser S-R2", false}}, + {19, {"Hammerfall MG-A", true}}, + {20, {"Hammerfall MG-S", false}}, + {21, {"Hammerfall MG-X", false}}, + {22, {"Panzer S-UC", true}}, + {23, {"Panzer L-UC", false}}, + {24, {"Panzer H-UC", false}}, +}; diff --git a/src/Maps/ArmourSlots.hpp b/src/Maps/ArmourSlots.hpp new file mode 100644 index 0000000..3004a65 --- /dev/null +++ b/src/Maps/ArmourSlots.hpp @@ -0,0 +1,56 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 . + +#ifdef c +c(Face, "enuArmorSlots::NewEnumerator0", "Face") +c(UpperHead, "enuArmorSlots::NewEnumerator1", "Upper head") +c(LowerHead, "enuArmorSlots::NewEnumerator2", "Lower head") +c(Neck, "enuArmorSlots::NewEnumerator3", "Neck") +c(UpperBody, "enuArmorSlots::NewEnumerator4", "Upper body") +c(MiddleBody, "enuArmorSlots::NewEnumerator5", "Middle body") +c(LowerBody, "enuArmorSlots::NewEnumerator6", "Lower body") +c(FrontWaist, "enuArmorSlots::NewEnumerator7", "Front waist") +c(LeftFrontSkirt, "enuArmorSlots::NewEnumerator8", "Left front skirt") +c(RightFrontSkirt, "enuArmorSlots::NewEnumerator9", "Right front skirt") +c(LeftSideSkirt, "enuArmorSlots::NewEnumerator10", "Left side skirt") +c(RightSideSkirt, "enuArmorSlots::NewEnumerator11", "Right side skirt") +c(LeftBackSkirt, "enuArmorSlots::NewEnumerator12", "Left back skirt") +c(RightBackSkirt, "enuArmorSlots::NewEnumerator13", "Right back skirt") +c(BackWaist, "enuArmorSlots::NewEnumerator14", "Back waist") +c(LeftShoulder, "enuArmorSlots::NewEnumerator15", "Left shoulder") +c(RightShoulder, "enuArmorSlots::NewEnumerator16", "Right shoulder") +c(LeftUpperArm, "enuArmorSlots::NewEnumerator17", "Left upper arm") +c(RightUpperArm, "enuArmorSlots::NewEnumerator18", "Right upper arm") +c(LeftElbow, "enuArmorSlots::NewEnumerator19", "Left elbow") +c(RightElbow, "enuArmorSlots::NewEnumerator20", "Right elbow") +c(LeftLowerArm, "enuArmorSlots::NewEnumerator21", "Left lower arm") +c(RightLowerArm, "enuArmorSlots::NewEnumerator22", "Right lower arm") +c(Backpack, "enuArmorSlots::NewEnumerator23", "Backpack") +c(LeftHand, "enuArmorSlots::NewEnumerator24", "Left hand") +c(RightHand, "enuArmorSlots::NewEnumerator25", "Right hand") +c(LeftUpperLeg, "enuArmorSlots::NewEnumerator26", "Left upper leg") +c(RightUpperLeg, "enuArmorSlots::NewEnumerator27", "Right upper leg") +c(LeftKnee, "enuArmorSlots::NewEnumerator28", "Left knee") +c(RightKnee, "enuArmorSlots::NewEnumerator29", "Right knee") +c(LeftLowerLeg, "enuArmorSlots::NewEnumerator30", "Left lower leg") +c(RightLowerLeg, "enuArmorSlots::NewEnumerator31", "Right lower leg") +c(LeftAnkle, "enuArmorSlots::NewEnumerator32", "Left ankle") +c(RightAnkle, "enuArmorSlots::NewEnumerator33", "Right ankle") +c(LeftHeel, "enuArmorSlots::NewEnumerator34", "Left heel") +c(RightHeel, "enuArmorSlots::NewEnumerator35", "Right heel") +c(LeftFoot, "enuArmorSlots::NewEnumerator36", "Left foot") +c(RightFoot, "enuArmorSlots::NewEnumerator37", "Right foot") +#endif diff --git a/src/Maps/DamageTypes.hpp b/src/Maps/DamageTypes.hpp new file mode 100644 index 0000000..5665f65 --- /dev/null +++ b/src/Maps/DamageTypes.hpp @@ -0,0 +1,24 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 . + +#ifdef c +c(Physical, "enuDamageProperty::NewEnumerator0") +c(Piercing, "enuDamageProperty::NewEnumerator1") +c(Heat, "enuDamageProperty::NewEnumerator2") +c(Freeze, "enuDamageProperty::NewEnumerator3") +c(Shock, "enuDamageProperty::NewEnumerator4") +c(Plasma, "enuDamageProperty::NewEnumerator5") +#endif diff --git a/src/Maps/EffectColourModes.hpp b/src/Maps/EffectColourModes.hpp new file mode 100644 index 0000000..7df0077 --- /dev/null +++ b/src/Maps/EffectColourModes.hpp @@ -0,0 +1,20 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 . + +#ifdef c +c(Default, "enuWeaponEffectColorMode::NewEnumerator0") +c(Custom, "enuWeaponEffectColorMode::NewEnumerator1") +#endif diff --git a/src/Maps/LastMissionId.h b/src/Maps/LastMissionId.h index 34dbdc0..165fbb1 100644 --- a/src/Maps/LastMissionId.h +++ b/src/Maps/LastMissionId.h @@ -1,7 +1,7 @@ #pragma once // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -33,6 +33,7 @@ static const std::map mission_id_map {{ {0x006E, "Mission 11 - Tempestuous Sector"}, {0x006F, "Mission 12 - Clashes of Metal"}, {0x0070, "Mission 13 - The Sandstorm Glutton"}, + {0x0071, "Mission 14 - An Icy Investigation"}, // Hunting grounds {0x00C8, "Hunt 1 - Desert Pathway Safety"}, diff --git a/src/Maps/StoryProgress.h b/src/Maps/StoryProgress.h index 4643b92..6bb1742 100644 --- a/src/Maps/StoryProgress.h +++ b/src/Maps/StoryProgress.h @@ -1,7 +1,7 @@ #pragma once // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -22,7 +22,7 @@ struct StoryProgressPoint { Int id; const char* chapter; const char* point; - const char* after = ""; + const char* after = nullptr; }; static const Corrade::Containers::Array story_progress @@ -88,5 +88,7 @@ static const Corrade::Containers::Array story_progress {0x0579, "Chapter 2", "Returned to hangar", "After mission 13"}, {0x057A, "Chapter 2", "Talked with Reina in development section", "After mission 13"}, {0x057B, "Chapter 2", "Got briefing for challenges 1, 2, and 3", "After mission 13"}, + {0x057C, "Chapter 2", "Talked with Reina about device", "After mission 13"}, + {0x057D, "Chapter 2", "Got mission 14 briefing", "After mission 13"}, } }; diff --git a/src/Maps/StyleNames.h b/src/Maps/StyleNames.h new file mode 100644 index 0000000..048509a --- /dev/null +++ b/src/Maps/StyleNames.h @@ -0,0 +1,192 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Magnum; + +extern const std::map style_names +#ifdef STYLENAMES_DEFINITION +{ + {0, "Custom Style 1"}, + {1, "Custom Style 2"}, + {2, "Custom Style 3"}, + {3, "Custom Style 4"}, + {4, "Custom Style 5"}, + {5, "Custom Style 6"}, + {6, "Custom Style 7"}, + {7, "Custom Style 8"}, + {8, "Custom Style 9"}, + {9, "Custom Style 10"}, + {10, "Custom Style 11"}, + {11, "Custom Style 12"}, + {12, "Custom Style 13"}, + {13, "Custom Style 14"}, + {14, "Custom Style 15"}, + {15, "Custom Style 16"}, + + {50, "Global Style 1"}, + {51, "Global Style 2"}, + {52, "Global Style 3"}, + {53, "Global Style 4"}, + {54, "Global Style 5"}, + {55, "Global Style 6"}, + {56, "Global Style 7"}, + {57, "Global Style 8"}, + {58, "Global Style 9"}, + {59, "Global Style 10"}, + {60, "Global Style 11"}, + {61, "Global Style 12"}, + {62, "Global Style 13"}, + {63, "Global Style 14"}, + {64, "Global Style 15"}, + {65, "Global Style 16"}, + + {100, "Iron"}, + {101, "Silver"}, + {102, "Gold"}, + {103, "Bronze"}, + {104, "Copper"}, + {105, "Nickel"}, + {106, "Cobalt"}, + {107, "Aluminium"}, + {108, "Titanium"}, + {109, "Platinum"}, + {110, "Gun Metal"}, + {111, "White"}, + {112, "White Metal"}, + {113, "White Gloss"}, + {114, "Grey"}, + {115, "Grey Metal"}, + {116, "Grey Gloss"}, + {117, "Dark Grey"}, + {118, "Dark Grey Metal"}, + {119, "Dark Grey Gloss"}, + {120, "Black"}, + {121, "Black Metal"}, + {122, "Black Gloss"}, + {123, "Red"}, + {124, "Red Metal"}, + {125, "Red Gloss"}, + {126, "Dark Red"}, + {127, "Dark Red Metal"}, + {128, "Dark Red Gloss"}, + {129, "Orange"}, + {130, "Orange Metal"}, + {131, "Orange Gloss"}, + {132, "Dark Orange"}, + {133, "Dark Orange Metal"}, + {134, "Dark Orange Gloss"}, + {135, "Yellow"}, + {136, "Yellow Metal"}, + {137, "Yellow Gloss"}, + {138, "Brown"}, + {139, "Brown Metal"}, + {140, "Brown Gloss"}, + {141, "Dark Brown"}, + {142, "Dark Brown Metal"}, + {143, "Dark Brown Gloss"}, + {144, "Leafgreen"}, + {145, "Leafgreen Metal"}, + {146, "Leafgreen Gloss"}, + {147, "Military Green"}, + {148, "Military Green Metal"}, + {149, "Military Green Gloss"}, + {150, "Green"}, + {151, "Green Metal"}, + {152, "Green Gloss"}, + {153, "Dark Green"}, + {154, "Dark Green Metal"}, + {155, "Dark Green Gloss"}, + {156, "Teal"}, + {157, "Teal Metal"}, + {158, "Teal Gloss"}, + {159, "Cyan"}, + {160, "Cyan Metal"}, + {161, "Cyan Gloss"}, + {162, "Blue"}, + {163, "Blue Metal"}, + {164, "Blue Gloss"}, + {165, "Blue Sky"}, + {166, "Blue Sky Metal"}, + {167, "Blue Sky Gloss"}, + {168, "Dark Blue"}, + {169, "Dark Blue Metal"}, + {170, "Dark Blue Gloss"}, + {171, "Purple"}, + {172, "Purple Metal"}, + {173, "Purple Gloss"}, + {174, "Dark Purple"}, + {175, "Dark Purple Metal"}, + {176, "Dark Purple Gloss"}, + {177, "Pink"}, + {178, "Pink Metal"}, + {179, "Pink Gloss"}, + {180, "Rosy Brown"}, + {181, "Rosy Brown Metal"}, + {182, "Rosy Brown Gloss"}, + {183, "Ivory"}, + {184, "Ivory Metal"}, + {185, "Ivory Gloss"}, + {186, "Slate Brown"}, + {187, "Slate Brown Metal"}, + {188, "Slate Brown Gloss"}, + {189, "Slate Green"}, + {190, "Slate Green Metal"}, + {191, "Slate Green Gloss"}, + {192, "Slate Blue"}, + {193, "Slate Blue Metal"}, + {194, "Slate Blue Gloss"}, + {195, "Slate Purple"}, + {196, "Slate Purple Metal"}, + {197, "Slate Purple Gloss"}, + {198, "White Glow"}, + {199, "White Radiance"}, + {200, "Red Glow"}, + {201, "Red Radiance"}, + {202, "Orange Glow"}, + {203, "Orange Radiance"}, + {204, "Yellow Glow"}, + {205, "Yellow Radiance"}, + {206, "Leafgreen Glow"}, + {207, "Leafgreen Radiance"}, + {208, "Green Glow"}, + {209, "Green Radiance"}, + {210, "Teal Glow"}, + {211, "Teal Radiance"}, + {212, "Cyan Glow"}, + {213, "Cyan Radiance"}, + {214, "Blue Glow"}, + {215, "Blue Radiance"}, + {216, "Purple Glow"}, + {217, "Purple Radiance"}, + {218, "Pink Glow"}, + {219, "Pink Radiance"}, + {220, "Grey Camo"}, + {221, "Dark Grey Camo"}, + {222, "Green Camo"}, + {223, "Dark Green Camo"}, + {224, "Brown Camo"}, + {225, "Dark Brown Camo"}, + {226, "Blue Camo"}, + {227, "Dark Blue Camo"}, +} +#endif +; diff --git a/src/Maps/WeaponTypes.hpp b/src/Maps/WeaponTypes.hpp new file mode 100644 index 0000000..baa31d6 --- /dev/null +++ b/src/Maps/WeaponTypes.hpp @@ -0,0 +1,24 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 . + +#ifdef c +c(Melee, "enuWeaponTypes::NewEnumerator0", "Melee weapon") +c(BulletShooter, "enuWeaponTypes::NewEnumerator1", "Bullet shooter") +c(EnergyShooter, "enuWeaponTypes::NewEnumerator2", "Energy shooter") +c(BulletLauncher, "enuWeaponTypes::NewEnumerator3", "Bullet launcher") +c(EnergyLauncher, "enuWeaponTypes::NewEnumerator4", "Energy launcher") +c(Shield, "enuWeaponTypes::NewEnumerator5", "Shield") +#endif diff --git a/src/Mass/Accessory.h b/src/Mass/Accessory.h new file mode 100644 index 0000000..cca271d --- /dev/null +++ b/src/Mass/Accessory.h @@ -0,0 +1,36 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Corrade; +using namespace Magnum; + +struct Accessory { + Int attachIndex = -1; + Int id = -1; + Containers::StaticArray<2, Int> styles{ValueInit}; + Vector3 relativePosition{0.0f}; + Vector3 relativePositionOffset{0.0f}; + Vector3 relativeRotation{0.0f}; + Vector3 relativeRotationOffset{0.0f}; + Vector3 localScale{1.0f}; +}; diff --git a/src/Mass/ArmourPart.h b/src/Mass/ArmourPart.h new file mode 100644 index 0000000..7246b89 --- /dev/null +++ b/src/Mass/ArmourPart.h @@ -0,0 +1,79 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "Decal.h" +#include "Accessory.h" + +using namespace Corrade; +using namespace Magnum; + +enum class ArmourSlot { + Face = 0, + UpperHead = 1, + LowerHead = 2, + Neck = 3, + UpperBody = 4, + MiddleBody = 5, + LowerBody = 6, + FrontWaist = 7, + LeftFrontSkirt = 8, + RightFrontSkirt = 9, + LeftSideSkirt = 10, + RightSideSkirt = 11, + LeftBackSkirt = 12, + RightBackSkirt = 13, + BackWaist = 14, + LeftShoulder = 15, + RightShoulder = 16, + LeftUpperArm = 17, + RightUpperArm = 18, + LeftElbow = 19, + RightElbow = 20, + LeftLowerArm = 21, + RightLowerArm = 22, + Backpack = 23, + LeftHand = 24, + RightHand = 25, + LeftUpperLeg = 26, + RightUpperLeg = 27, + LeftKnee = 28, + RightKnee = 29, + LeftLowerLeg = 30, + RightLowerLeg = 31, + LeftAnkle = 32, + RightAnkle = 33, + LeftHeel = 34, + RightHeel = 35, + LeftFoot = 36, + RightFoot = 37, +}; + +struct ArmourPart { + ArmourSlot slot = ArmourSlot::Face; + Int id = 0; + Containers::StaticArray<4, Int> styles{ValueInit}; + Containers::Array decals; + Containers::Array accessories; +}; diff --git a/src/Mass/CustomStyle.h b/src/Mass/CustomStyle.h new file mode 100644 index 0000000..cee5cf9 --- /dev/null +++ b/src/Mass/CustomStyle.h @@ -0,0 +1,39 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Magnum; + +struct CustomStyle { + std::string name; + Color4 colour{0.0f}; + Float metallic = 0.5f; + Float gloss = 0.5f; + bool glow = false; + + Int patternId = 0; + Float opacity = 0.5f; + Vector2 offset{0.5f}; + Float rotation = 0.0f; + Float scale = 0.5f; +}; diff --git a/src/Mass/Decal.h b/src/Mass/Decal.h new file mode 100644 index 0000000..487f813 --- /dev/null +++ b/src/Mass/Decal.h @@ -0,0 +1,38 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Magnum; + +struct Decal { + Int id = -1; + Color4 colour{0.0f}; + Vector3 position{0.0f}; + Vector3 uAxis{0.0f}; + Vector3 vAxis{0.0f}; + Vector2 offset{0.5f}; + Float scale = 0.5f; + Float rotation = 0.0f; + bool flip = false; + bool wrap = false; +}; diff --git a/src/Mass/Joints.h b/src/Mass/Joints.h new file mode 100644 index 0000000..8cc58c2 --- /dev/null +++ b/src/Mass/Joints.h @@ -0,0 +1,32 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Magnum; + +struct Joints { + Float neck = 0.0f; + Float body = 0.0f; + Float shoulders = 0.0f; + Float hips = 0.0f; + Float upperArms = 0.0f; + Float lowerArms = 0.0f; + Float upperLegs = 0.0f; + Float lowerLegs = 0.0f; +}; diff --git a/src/Mass/Mass.cpp b/src/Mass/Mass.cpp index 7f6f67f..38bdf2c 100644 --- a/src/Mass/Mass.cpp +++ b/src/Mass/Mass.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -14,138 +14,1335 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include - #include #include #include +#include "../UESaveFile/Types/ArrayProperty.h" +#include "../UESaveFile/Types/BoolProperty.h" +#include "../UESaveFile/Types/ByteProperty.h" +#include "../UESaveFile/Types/ColourStructProperty.h" +#include "../UESaveFile/Types/FloatProperty.h" +#include "../UESaveFile/Types/GenericStructProperty.h" +#include "../UESaveFile/Types/IntProperty.h" +#include "../UESaveFile/Types/RotatorStructProperty.h" +#include "../UESaveFile/Types/StringProperty.h" +#include "../UESaveFile/Types/VectorStructProperty.h" +#include "../UESaveFile/Types/Vector2DStructProperty.h" + #include "Mass.h" -using namespace Corrade; +Mass::Mass(const std::string& path) { + _folder = Utility::Directory::path(path); + _filename = Utility::Directory::filename(path); -constexpr char mass_name_locator[] = "Name_45_A037C5D54E53456407BDF091344529BB\0\x0c\0\0\0StrProperty"; -constexpr char steamid_locator[] = "Account\0\x0c\0\0\0StrProperty"; - -std::string Mass::_lastError; - -Mass::Mass(const std::string& filename) { - _filename = filename; - - if(!Utility::Directory::exists(_filename)) { - _lastError = "The file " + _filename + " couldn't be found."; - return; - } - - auto mmap = Utility::Directory::mapRead(_filename); - - auto iter = std::search(mmap.begin(), mmap.end(), &mass_name_locator[0], &mass_name_locator[56]); - - if(iter != mmap.end()) { - _name = std::string{iter + 70}; - _state = MassState::Valid; - } - else { - _lastError = "The name couldn't be found in " + filename; - _state = MassState::Invalid; - } + refreshValues(); } auto Mass::lastError() -> std::string const& { return _lastError; } -auto Mass::getNameFromFile(const std::string& filename) -> std::string { - if(!Utility::Directory::exists(filename)) { - _lastError = "The file " + filename + " couldn't be found."; - return ""; +auto Mass::getNameFromFile(const std::string& path) -> Containers::Optional { + if(!Utility::Directory::exists(path)) { + Utility::Error{} << path.c_str() << "couldn't be found."; + return Containers::NullOpt; } - std::string name = ""; + UESaveFile mass{path}; - auto mmap = Utility::Directory::mapRead(filename); + if(!mass.valid()) { + Utility::Error{} << "The unit file seems to be corrupt."; + return Containers::NullOpt; + } - auto iter = std::search(mmap.begin(), mmap.end(), &mass_name_locator[0], &mass_name_locator[56]); + auto unit_data = mass.at("UnitData"); - if(iter != mmap.end()) { - name = std::string{iter + 70}; + if(!unit_data) { + Utility::Error{} << "Couldn't find unit data in the file."; + return Containers::NullOpt; + } + + auto name_prop = unit_data->at("Name_45_A037C5D54E53456407BDF091344529BB"); + + if(!name_prop) { + Utility::Error{} << "Couldn't find the name in the file."; + return Containers::NullOpt; + } + + return name_prop->value; +} + +void Mass::refreshValues() { + if(!Utility::Directory::exists(Utility::Directory::join(_folder, _filename))) { + _state = State::Empty; + return; + } + + if(!_mass) { + _mass.emplace(Utility::Directory::join(_folder, _filename)); + if(!_mass->valid()) { + _state = State::Invalid; + return; + } } else { - _lastError = "The name couldn't be found in " + filename; + if(!_mass->reloadData()) { + _state = State::Invalid; + return; + } } - return name; + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + return; + } + + auto name_prop = unit_data->at("Name_45_A037C5D54E53456407BDF091344529BB"); + + if(!name_prop) { + _name = Containers::NullOpt; + _state = State::Invalid; + return; + } + + _name = name_prop->value; + + getJointSliders(); + if(_state == State::Invalid) { + return; + } + + getFrameStyles(); + if(_state == State::Invalid) { + return; + } + + getEyeFlareColour(); + if(_state == State::Invalid) { + return; + } + + getFrameCustomStyles(); + if(_state == State::Invalid) { + return; + } + + getArmourParts(); + if(_state == State::Invalid) { + return; + } + + getArmourCustomStyles(); + if(_state == State::Invalid) { + return; + } + + getMeleeWeapons(); + if(_state == State::Invalid) { + return; + } + + getShields(); + if(_state == State::Invalid) { + return; + } + + getBulletShooters(); + if(_state == State::Invalid) { + return; + } + + getEnergyShooters(); + if(_state == State::Invalid) { + return; + } + + getBulletLaunchers(); + if(_state == State::Invalid) { + return; + } + + getEnergyLaunchers(); + if(_state == State::Invalid) { + return; + } + + getGlobalStyles(); + if(_state == State::Invalid) { + return; + } + + getTuning(); + if(_state == State::Invalid) { + return; + } + + auto account_prop = _mass->at("Account"); + if(!account_prop) { + _state = State::Invalid; + return; + } + + _account = account_prop->value; + + _state = State::Valid; } auto Mass::filename() -> std::string const&{ return _filename; } -auto Mass::name() -> std::string const&{ +auto Mass::name() -> Containers::Optional const& { return _name; } -auto Mass::getName() -> std::string const& { - if(!Utility::Directory::exists(_filename)) { - _lastError = "The file " + _filename + " couldn't be found."; - _state = MassState::Empty; - return _name = ""; - } +auto Mass::setName(std::string new_name) -> bool { + _name = new_name; - auto mmap = Utility::Directory::mapRead(_filename); + auto unit_data = _mass->at("UnitData"); - auto iter = std::search(mmap.begin(), mmap.end(), &mass_name_locator[0], &mass_name_locator[56]); - - if(iter != mmap.end()) { - _state = MassState::Valid; - return _name = std::string{iter + 70}; - } - else { - _lastError = "The name couldn't be found in " + _filename; - _state = MassState::Invalid; - return _name = ""; - } -} - -auto Mass::state() -> MassState { - return _state; -} - -auto Mass::updateSteamId(const std::string& steam_id) -> bool { - if(!Utility::Directory::exists(_filename)) { - _lastError = "The file " + _filename + " couldn't be found."; - _state = MassState::Empty; + if(!unit_data) { + _state = State::Invalid; return false; } - Utility::Directory::copy(_filename, _filename + ".tmp"); + auto name_prop = unit_data->at("Name_45_A037C5D54E53456407BDF091344529BB"); - { - auto mmap = Utility::Directory::map(_filename + ".tmp"); - - auto iter = std::search(mmap.begin(), mmap.end(), &steamid_locator[0], &steamid_locator[23]); - - if(iter == mmap.end()) { - _lastError = "The M.A.S.S. file at " + _filename + " seems to be corrupt."; - Utility::Directory::rm(_filename + ".tmp"); - return false; - } - - iter += 37; - - if(std::strncmp(iter, steam_id.c_str(), steam_id.length()) != 0) { - for(int i = 0; i < 17; ++i) { - *(iter + i) = steam_id[i]; - } - } + if(!name_prop) { + _state = State::Invalid; + return false; } - if(Utility::Directory::exists(_filename)) { - Utility::Directory::rm(_filename); + name_prop->value = std::move(new_name); + + return _mass->saveToFile(); +} + +auto Mass::state() -> State { + return _state; +} + +auto Mass::dirty() const -> bool { + return _dirty; +} + +void Mass::setDirty(bool dirty) { + _dirty = dirty; +} + +auto Mass::jointSliders() -> Joints& { + return _frame.joints; +} + +void Mass::getJointSliders() { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + return; } - Utility::Directory::move(_filename + ".tmp", _filename); + auto frame_prop = unit_data->at("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"); + if(!frame_prop) { + _state = State::Invalid; + return; + } + + auto length = frame_prop->at("NeckLength_6_ED6AF79849C27CD1A9D523A09E2BFE58"); + _frame.joints.neck = (length ? length->value : 0.0f); + length = frame_prop->at("BodyLength_7_C16287754CBA96C93BAE36A5C154996A"); + _frame.joints.body = (length ? length->value : 0.0f); + length = frame_prop->at("ShoulderLength_8_220EDF304F1C1226F0D8D39117FB3883"); + _frame.joints.shoulders = (length ? length->value : 0.0f); + length = frame_prop->at("HipLength_14_02AEEEAC4376087B9C51F0AA7CC92818"); + _frame.joints.hips = (length ? length->value : 0.0f); + length = frame_prop->at("ArmUpperLength_10_249FDA3E4F3B399E7B9E5C9B7C765EAE"); + _frame.joints.upperArms = (length ? length->value : 0.0f); + length = frame_prop->at("ArmLowerLength_12_ACD0F02745C28882619376926292FB36"); + _frame.joints.lowerArms = (length ? length->value : 0.0f); + length = frame_prop->at("LegUpperLength_16_A7C4C71249A3776F7A543D96819C0C61"); + _frame.joints.upperLegs = (length ? length->value : 0.0f); + length = frame_prop->at("LegLowerLength_18_D2DF39964EA0F2A2129D0491B08A032F"); + _frame.joints.lowerLegs = (length ? length->value : 0.0f); +} + +auto Mass::writeJointSliders() -> bool { + auto unit_data = _mass->at("UnitData"); + + if(!unit_data) { + _state = State::Invalid; + _lastError = "No unit data in " + _filename; + return false; + } + + auto frame_prop = unit_data->at("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"); + + if(!frame_prop) { + _state = State::Invalid; + _lastError = "No frame data in " + _filename; + return false; + } + + Containers::Array temp; + + auto length = frame_prop->atMove("NeckLength_6_ED6AF79849C27CD1A9D523A09E2BFE58"); + if(_frame.joints.neck != 0.0f) { + if(!length) { + length.emplace(); + length->name.emplace("NeckLength_6_ED6AF79849C27CD1A9D523A09E2BFE58"); + } + length->value = _frame.joints.neck; + arrayAppend(temp, std::move(length)); + } + + length = frame_prop->atMove("BodyLength_7_C16287754CBA96C93BAE36A5C154996A"); + if(_frame.joints.body != 0.0f) { + if(!length) { + length.emplace(); + length->name.emplace("BodyLength_7_C16287754CBA96C93BAE36A5C154996A"); + } + length->value = _frame.joints.body; + arrayAppend(temp, std::move(length)); + } + + length = frame_prop->atMove("ShoulderLength_8_220EDF304F1C1226F0D8D39117FB3883"); + if(_frame.joints.shoulders != 0.0f) { + if(!length) { + length.emplace(); + length->name.emplace("ShoulderLength_8_220EDF304F1C1226F0D8D39117FB3883"); + } + length->value = _frame.joints.shoulders; + arrayAppend(temp, std::move(length)); + } + + length = frame_prop->atMove("ArmUpperLength_10_249FDA3E4F3B399E7B9E5C9B7C765EAE"); + if(_frame.joints.upperArms != 0.0f) { + if(!length) { + length.emplace(); + length->name.emplace("ArmUpperLength_10_249FDA3E4F3B399E7B9E5C9B7C765EAE"); + } + length->value = _frame.joints.upperArms; + arrayAppend(temp, std::move(length)); + } + + length = frame_prop->atMove("ArmLowerLength_12_ACD0F02745C28882619376926292FB36"); + if(_frame.joints.lowerArms != 0.0f) { + if(!length) { + length.emplace(); + length->name.emplace("ArmLowerLength_12_ACD0F02745C28882619376926292FB36"); + } + length->value = _frame.joints.lowerArms; + arrayAppend(temp, std::move(length)); + } + + length = frame_prop->atMove("HipLength_14_02AEEEAC4376087B9C51F0AA7CC92818"); + if(_frame.joints.hips != 0.0f) { + if(!length) { + length.emplace(); + length->name.emplace("HipLength_14_02AEEEAC4376087B9C51F0AA7CC92818"); + } + length->value = _frame.joints.hips; + arrayAppend(temp, std::move(length)); + } + + length = frame_prop->atMove("LegUpperLength_16_A7C4C71249A3776F7A543D96819C0C61"); + if(_frame.joints.upperLegs != 0.0f) { + if(!length) { + length.emplace(); + length->name.emplace("LegUpperLength_16_A7C4C71249A3776F7A543D96819C0C61"); + } + length->value = _frame.joints.upperLegs; + arrayAppend(temp, std::move(length)); + } + + length = frame_prop->atMove("LegLowerLength_18_D2DF39964EA0F2A2129D0491B08A032F"); + if(_frame.joints.lowerLegs != 0.0f) { + if(!length) { + length.emplace(); + length->name.emplace("LegLowerLength_18_D2DF39964EA0F2A2129D0491B08A032F"); + } + length->value = _frame.joints.lowerLegs; + arrayAppend(temp, std::move(length)); + } + + arrayAppend(temp, std::move(frame_prop->properties[frame_prop->properties.size() - 3])); + arrayAppend(temp, std::move(frame_prop->properties[frame_prop->properties.size() - 2])); + arrayAppend(temp, std::move(frame_prop->properties[frame_prop->properties.size() - 1])); + + frame_prop->properties = std::move(temp); + + if(!_mass->saveToFile()) { + _lastError = _mass->lastError(); + return false; + } return true; } + +auto Mass::frameStyles() -> Containers::ArrayView { + return _frame.styles; +} + +void Mass::getFrameStyles() { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + return; + } + + auto frame_prop = unit_data->at("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"); + if(!frame_prop) { + _state = State::Invalid; + return; + } + + auto frame_styles = frame_prop->at("Styles_32_00A3B3284B37F1E7819458844A20EB48"); + if(!frame_styles) { + _state = State::Invalid; + return; + } + + if(frame_styles->items.size() != _frame.styles.size()) { + _state = State::Invalid; + return; + } + + for(UnsignedInt i = 0; i < frame_styles->items.size(); i++) { + _frame.styles[i] = frame_styles->at(i)->value; + } +} + +auto Mass::writeFrameStyles() -> bool { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + _lastError = "No unit data in " + _filename; + return false; + } + + auto frame = unit_data->at("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"); + if(!frame) { + _state = State::Invalid; + _lastError = "No frame data in " + _filename; + return false; + } + + auto frame_styles = frame->at("Styles_32_00A3B3284B37F1E7819458844A20EB48"); + if(!frame_styles) { + _state = State::Invalid; + _lastError = "No frame styles in " + _filename; + return false; + } + + for(UnsignedInt i = 0; i < frame_styles->items.size(); i++) { + frame_styles->at(i)->value = _frame.styles[i]; + } + + if(!_mass->saveToFile()) { + _lastError = _mass->lastError(); + return false; + } + + return true; +} + +auto Mass::eyeFlareColour() -> Color4& { + return _frame.eyeFlare; +} + +void Mass::getEyeFlareColour() { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + return; + } + + auto frame_prop = unit_data->at("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"); + if(!frame_prop) { + _state = State::Invalid; + return; + } + + auto eye_flare_prop = frame_prop->at("EyeFlareColor_36_AF79999C40FCA0E88A2F9A84488A38CA"); + if(!eye_flare_prop) { + _state = State::Invalid; + return; + } + + _frame.eyeFlare = Color4{eye_flare_prop->r, eye_flare_prop->g, eye_flare_prop->b, eye_flare_prop->a}; +} + +auto Mass::writeEyeFlareColour() -> bool { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + _lastError = "No unit data in " + _filename; + return false; + } + + auto frame = unit_data->at("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"); + if(!frame) { + _state = State::Invalid; + _lastError = "No frame data in " + _filename; + return false; + } + + auto eye_flare_prop = frame->at("EyeFlareColor_36_AF79999C40FCA0E88A2F9A84488A38CA"); + if(!eye_flare_prop) { + _state = State::Invalid; + _lastError = "No eye flare property in " + _filename; + return false; + } + + eye_flare_prop->r = _frame.eyeFlare.r(); + eye_flare_prop->g = _frame.eyeFlare.g(); + eye_flare_prop->b = _frame.eyeFlare.b(); + eye_flare_prop->a = _frame.eyeFlare.a(); + + if(!_mass->saveToFile()) { + _lastError = _mass->lastError(); + return false; + } + + return true; +} + +auto Mass::frameCustomStyles() -> Containers::ArrayView { + return _frame.customStyles; +} + +void Mass::getFrameCustomStyles() { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + return; + } + + auto frame_styles = unit_data->at("FrameStyle_44_04A44C9440363CCEC5443D98BFAF22AA"); + if(!frame_styles) { + _state = State::Invalid; + return; + } + + if(frame_styles->items.size() != _frame.customStyles.size()) { + _state = State::Invalid; + return; + } + + getCustomStyles(_frame.customStyles, frame_styles); +} + +auto Mass::writeFrameCustomStyle(UnsignedLong index) -> bool { + if(index > _frame.customStyles.size()) { + _lastError = "Style index out of range."; + return false; + } + + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + _lastError = "No unit data in " + _filename; + return false; + } + + auto frame_styles = unit_data->at("FrameStyle_44_04A44C9440363CCEC5443D98BFAF22AA"); + if(!frame_styles) { + _state = State::Invalid; + _lastError = "No frame styles in " + _filename; + return false; + } + + return setCustomStyle(_frame.customStyles[index], index, frame_styles); +} + +auto Mass::armourParts() -> Containers::ArrayView { + return _armour.parts; +} + +void Mass::getArmourParts() { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + return; + } + + auto armour_array = unit_data->at("Armor_10_12E266C44116DDAF57E99ABB575A4B3C"); + if(!armour_array) { + _state = State::Invalid; + return; + } + + if(armour_array->items.size() != _armour.parts.size()) { + _state = State::Invalid; + return; + } + + for(UnsignedInt i = 0; i < armour_array->items.size(); i++) { + auto part_prop = armour_array->at(i); + auto& part = _armour.parts[i]; + + auto& armour_slot = part_prop->at("Slot_3_408BA56F4C9605C7E805CF91B642249C")->enumValue; + #define c(enumerator, strenum, name) if(armour_slot == (strenum)) { part.slot = ArmourSlot::enumerator; } else + #include "../Maps/ArmourSlots.hpp" + #undef c + { + _state = State::Invalid; + Utility::Warning{} << "Invalid armour slot enum value in getArmourParts()."; + } + + part.id = part_prop->at("ID_5_ACD101864D3481DE96EDACACC09BDD25")->value; + + auto part_styles = part_prop->at("Styles_47_3E31870441DFD7DB8BEE5C85C26B365B"); + if(!part_styles) { + _state = State::Invalid; + return; + } + + if(part_styles->items.size() != part.styles.size()) { + _state = State::Invalid; + return; + } + + for(UnsignedInt j = 0; j < part_styles->items.size(); j++) { + part.styles[j] = part_styles->at(j)->value; + } + + auto decals_array = part_prop->at("Decals_42_F358794A4F18497970F56BA9627D3603"); + if(!decals_array) { + _state = State::Invalid; + return; + } + + part.decals = Containers::Array{decals_array->items.size()}; + + getDecals(part.decals, decals_array); + + auto accs_array = part_prop->at("Accessories_52_D902DD4241FA0050C2529596255153F3"); + if(!accs_array) { + part.accessories = Containers::Array{}; + continue; + } + + if(part.accessories.size() != accs_array->items.size()) { + part.accessories = Containers::Array{accs_array->items.size()}; + } + + getAccessories(part.accessories, accs_array); + } +} + +auto Mass::writeArmourPart(ArmourSlot slot) -> bool { + auto& part = *std::find_if(_armour.parts.begin(), _armour.parts.end(), [&slot](const ArmourPart& part){ return slot == part.slot; }); + + auto unit_data = _mass->at("UnitData"); + + auto armour_array = unit_data->at("Armor_10_12E266C44116DDAF57E99ABB575A4B3C"); + + const char* slot_str = nullptr; + switch(slot) { + #define c(enumerator, strenum, name) case ArmourSlot::enumerator: \ + slot_str = strenum; \ + break; + #include "../Maps/ArmourSlots.hpp" + #undef c + } + + GenericStructProperty* part_prop = nullptr; + + for(UnsignedInt i = 0; i < armour_array->items.size(); i++) { + part_prop = armour_array->at(i); + if(slot_str != part_prop->at("Slot_3_408BA56F4C9605C7E805CF91B642249C")->value) { + part_prop = nullptr; + } + } + + if(!part_prop) { + _lastError = "Couldn't find the armour part for slot "; + switch(slot) { + #define c(enumerator, strenum, name) case ArmourSlot::enumerator: \ + _lastError += "ArmourSlot::" #enumerator "."; \ + break; + #include "../Maps/ArmourSlots.hpp" + #undef c + } + return false; + } + + part_prop->at("ID_5_ACD101864D3481DE96EDACACC09BDD25")->value = part.id; + + auto part_styles = part_prop->at("Styles_47_3E31870441DFD7DB8BEE5C85C26B365B"); + for(UnsignedInt i = 0; i < part.styles.size(); i++) { + part_styles->at(i)->value = part.styles[i]; + } + + auto decals_array = part_prop->at("Decals_42_F358794A4F18497970F56BA9627D3603"); + writeDecals(part.decals, decals_array); + + if(part.accessories.size() != 0) { + auto accs_array = part_prop->at("Accessories_52_D902DD4241FA0050C2529596255153F3"); + writeAccessories(part.accessories, accs_array); + } + + if(!_mass->saveToFile()) { + _lastError = _mass->lastError(); + return false; + } + + return true; +} + +auto Mass::armourCustomStyles() -> Containers::ArrayView { + return _armour.customStyles; +} + +void Mass::getArmourCustomStyles() { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + return; + } + + auto armour_styles = unit_data->at("ArmorStyle_42_E2F6AC3647788CB366BD469B3B7E899E"); + if(!armour_styles) { + _state = State::Invalid; + return; + } + + if(armour_styles->items.size() != _armour.customStyles.size()) { + _state = State::Invalid; + return; + } + + getCustomStyles(_armour.customStyles, armour_styles); +} + +auto Mass::writeArmourCustomStyle(UnsignedLong index) -> bool { + if(index > _armour.customStyles.size()) { + _lastError = "Style index out of range."; + return false; + } + + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + _lastError = "Couldn't find unit data in " + _filename; + return false; + } + + auto armour_styles = unit_data->at("ArmorStyle_42_E2F6AC3647788CB366BD469B3B7E899E"); + if(!armour_styles) { + _lastError = "Couldn't find armour custom styles in " + _filename; + _state = State::Invalid; + return false; + } + + return setCustomStyle(_armour.customStyles[index], index, armour_styles); +} + +auto Mass::meleeWeapons() -> Containers::ArrayView { + return _weapons.melee; +} + +void Mass::getMeleeWeapons() { + getWeaponType("WeaponCC_22_0BBEC58C4A0EA1DB9E037B9339EE26A7", _weapons.melee); +} + +auto Mass::writeMeleeWeapons() -> bool { + return writeWeaponType("WeaponCC_22_0BBEC58C4A0EA1DB9E037B9339EE26A7", _weapons.melee); +} + +auto Mass::shields() -> Containers::ArrayView { + return _weapons.shields; +} + +void Mass::getShields() { + getWeaponType("Shield_53_839BFD7945481BAEA3E43A9C5CA8E92E", _weapons.shields); +} + +auto Mass::writeShields() -> bool { + return writeWeaponType("Shield_53_839BFD7945481BAEA3E43A9C5CA8E92E", _weapons.shields); +} + +auto Mass::bulletShooters() -> Containers::ArrayView { + return _weapons.bulletShooters; +} + +void Mass::getBulletShooters() { + getWeaponType("WeaponBS_35_6EF6E0104FD7A138DF47F88CB57A83ED", _weapons.bulletShooters); +} + +auto Mass::writeBulletShooters() -> bool { + return writeWeaponType("WeaponBS_35_6EF6E0104FD7A138DF47F88CB57A83ED", _weapons.bulletShooters); +} + +auto Mass::energyShooters() -> Containers::ArrayView { + return _weapons.energyShooters; +} + +void Mass::getEnergyShooters() { + getWeaponType("WeaponES_37_1A295D544528623880A0B1AC2C7DEE99", _weapons.energyShooters); +} + +auto Mass::writeEnergyShooters() -> bool { + return writeWeaponType("WeaponES_37_1A295D544528623880A0B1AC2C7DEE99", _weapons.energyShooters); +} + +auto Mass::bulletLaunchers() -> Containers::ArrayView { + return _weapons.bulletLaunchers; +} + +void Mass::getBulletLaunchers() { + getWeaponType("WeaponBL_36_5FD7C41E4613A75B44AB0E90B362846E", _weapons.bulletLaunchers); +} + +auto Mass::writeBulletLaunchers() -> bool { + return writeWeaponType("WeaponBL_36_5FD7C41E4613A75B44AB0E90B362846E", _weapons.bulletLaunchers); +} + +auto Mass::energyLaunchers() -> Containers::ArrayView { + return _weapons.energyLaunchers; +} + +void Mass::getEnergyLaunchers() { + getWeaponType("WeaponEL_38_9D23F3884ACA15902C9E6CA6E4995995", _weapons.energyLaunchers); +} + +auto Mass::writeEnergyLaunchers() -> bool { + return writeWeaponType("WeaponEL_38_9D23F3884ACA15902C9E6CA6E4995995", _weapons.energyLaunchers); +} + +auto Mass::globalStyles() -> Containers::ArrayView { + return _globalStyles; +} + +void Mass::getGlobalStyles() { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + return; + } + + auto global_styles = unit_data->at("GlobalStyles_57_6A681C114035241F7BDAAE9B43A8BF1B"); + if(!global_styles) { + _globalStyles = Containers::Array{0}; + return; + } + + if(global_styles->items.size() != _globalStyles.size()) { + _globalStyles = Containers::Array{global_styles->items.size()}; + } + + getCustomStyles(_globalStyles, global_styles); +} + +auto Mass::writeGlobalStyle(UnsignedLong index) -> bool { + if(index > _globalStyles.size()) { + _lastError = "Global style index out of range"; + return false; + } + + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + _lastError = "No unit data found in " + _filename; + return false; + } + + auto global_styles = unit_data->at("GlobalStyles_57_6A681C114035241F7BDAAE9B43A8BF1B"); + if(!global_styles) { + _state = State::Invalid; + _lastError = "No global styles found in " + _filename; + return false; + } + + return setCustomStyle(_globalStyles[index], index, global_styles); +} + +void Mass::getTuning() { + getTuningCategory("Engine", _tuning.engineId, "Gears", _tuning.gearIds); + if(_state == State::Invalid) { + return; + } + + getTuningCategory("OS", _tuning.osId, "Modules", _tuning.moduleIds); + if(_state == State::Invalid) { + return; + } + + getTuningCategory("Architect", _tuning.archId, "Techs", _tuning.techIds); + if(_state == State::Invalid) { + return; + } +} + +auto Mass::engine() -> Int& { + return _tuning.engineId; +} + +auto Mass::gears() -> Containers::ArrayView { + return _tuning.gearIds; +} + +auto Mass::os() -> Int& { + return _tuning.osId; +} + +auto Mass::modules() -> Containers::ArrayView { + return _tuning.moduleIds; +} + +auto Mass::architecture() -> Int& { + return _tuning.archId; +} + +auto Mass::techs() -> Containers::ArrayView { + return _tuning.techIds; +} + +auto Mass::account() -> const std::string& { + return _account; +} + +auto Mass::updateAccount(const std::string& new_account) -> bool { + _account = new_account; + + auto account = _mass->at("Account"); + if(!account) { + _state = State::Invalid; + _lastError = "Couldn't find the account property."; + return false; + } + + account->value = new_account; + + if(!_mass->saveToFile()) { + _lastError = _mass->lastError(); + return false; + } + + return true; +} + +void Mass::getCustomStyles(Containers::ArrayView styles, ArrayProperty* style_array) { + for(UnsignedInt i = 0; i < style_array->items.size(); i++) { + auto style_prop = style_array->at(i); + auto& style = styles[i]; + + style.name = style_prop->at("Name_27_1532115A46EF2B2FA283908DF561A86B")->value; + auto colour_prop = style_prop->at("Color_5_F0D383DF40474C9464AE48A0984A212E"); + style.colour = Color4{colour_prop->r, colour_prop->g, colour_prop->b, colour_prop->a}; + style.metallic = style_prop->at("Metallic_10_0A4CD1E4482CBF41CA61D0A856DE90B9")->value; + style.gloss = style_prop->at("Gloss_11_9769599842CC275A401C4282A236E240")->value; + style.glow = colour_prop->a == 0.0f ? false : true; + + style.patternId = style_prop->at("PatternID_14_516DB85641DAF8ECFD2920BE2BDF1311")->value; + style.opacity = style_prop->at("Opacity_30_53BD060B4DFCA1C92302D6A0F7831131")->value; + style.offset = Vector2{ + style_prop->at("OffsetX_23_70FC2E814C64BBB82452748D2AF9CD48")->value, + style_prop->at("OffsetY_24_5E1F866C4C054D9B2EE337ADC180C17F")->value + }; + style.rotation = style_prop->at("Rotation_25_EC2DFAD84AD0A6BD3FA841ACD52EDD6D")->value; + style.scale = style_prop->at("Scale_26_19DF0708409262183E1247B317137671")->value; + } +} + +auto Mass::setCustomStyle(const CustomStyle& style, UnsignedLong index, ArrayProperty* style_array) -> bool { + if(!style_array) { + _lastError = "Mass::setCustomStyle(): style_array is null."; + return false; + } + + auto style_prop = style_array->at(index); + + if(!style_prop) { + _lastError = "Style index is out of range in " + _filename; + return false; + } + + style_prop->at("Name_27_1532115A46EF2B2FA283908DF561A86B")->value = style.name; + auto colour_prop = style_prop->at("Color_5_F0D383DF40474C9464AE48A0984A212E"); + colour_prop->r = style.colour.r(); + colour_prop->g = style.colour.g(); + colour_prop->b = style.colour.b(); + colour_prop->a = style.glow ? 1.0f : 0.0f; + style_prop->at("Metallic_10_0A4CD1E4482CBF41CA61D0A856DE90B9")->value = style.metallic; + style_prop->at("Gloss_11_9769599842CC275A401C4282A236E240")->value = style.gloss; + + style_prop->at("PatternID_14_516DB85641DAF8ECFD2920BE2BDF1311")->value = style.patternId; + style_prop->at("Opacity_30_53BD060B4DFCA1C92302D6A0F7831131")->value = style.opacity; + style_prop->at("OffsetX_23_70FC2E814C64BBB82452748D2AF9CD48")->value = style.offset.x(); + style_prop->at("OffsetY_24_5E1F866C4C054D9B2EE337ADC180C17F")->value = style.offset.y(); + style_prop->at("Rotation_25_EC2DFAD84AD0A6BD3FA841ACD52EDD6D")->value = style.rotation; + style_prop->at("Scale_26_19DF0708409262183E1247B317137671")->value = style.scale; + + if(!_mass->saveToFile()) { + _lastError = _mass->lastError(); + return false; + } + + return true; +} + +void Mass::getDecals(Containers::ArrayView decals, ArrayProperty* decal_array) { + for(UnsignedInt i = 0; i < decal_array->items.size(); i++) { + auto decal_prop = decal_array->at(i); + auto& decal = decals[i]; + + decal.id = decal_prop->at("ID_3_694C0B35404D8A3168AEC89026BC8CF9")->value; + auto colour_prop = decal_prop->at("Color_8_1B0B9D2B43DA6AAB9FA549B374D3E606"); + decal.colour = Color4{colour_prop->r, colour_prop->g, colour_prop->b, colour_prop->a}; + auto pos_prop = decal_prop->at("Position_41_022C8FE84E1AAFE587261E88F2C72250"); + decal.position = Vector3{pos_prop->x, pos_prop->y, pos_prop->z}; + auto u_prop = decal_prop->at("UAxis_37_EBEB715F45491AECACCC07A1AE4646D1"); + decal.uAxis = Vector3{u_prop->x, u_prop->y, u_prop->z}; + auto v_prop = decal_prop->at("VAxis_39_C31EB2664EE202CAECFBBB84100B5E35"); + decal.vAxis = Vector3{v_prop->x, v_prop->y, v_prop->z}; + auto offset_prop = decal_prop->at("Offset_29_B02BBBB74FC60F5EDBEBAB8020738020"); + decal.offset = Vector2{offset_prop->x, offset_prop->y}; + decal.scale = decal_prop->at("Scale_32_959D1C2747AFD8D62808468235CBBA40")->value; + decal.rotation = decal_prop->at("Rotation_27_12D7C314493D203D5C2326A03C5F910F")->value; + decal.flip = decal_prop->at("Flip_35_CECCFB184CCD9412BD93FE9A8B656BE1")->value; + decal.wrap = decal_prop->at("Wrap_43_A7C68CDF4A92AF2ECDA53F953EE7CA62")->value; + } +} + +void Mass::writeDecals(Containers::ArrayView decals, ArrayProperty* decal_array) { + for(UnsignedInt i = 0; i < decal_array->items.size(); i++) { + auto decal_prop = decal_array->at(i); + auto& decal = decals[i]; + + decal_prop->at("ID_3_694C0B35404D8A3168AEC89026BC8CF9")->value = decal.id; + auto colour_prop = decal_prop->at("Color_8_1B0B9D2B43DA6AAB9FA549B374D3E606"); + colour_prop->r = decal.colour.r(); + colour_prop->g = decal.colour.g(); + colour_prop->b = decal.colour.b(); + colour_prop->a = decal.colour.a(); + auto pos_prop = decal_prop->at("Position_41_022C8FE84E1AAFE587261E88F2C72250"); + pos_prop->x = decal.position.x(); + pos_prop->y = decal.position.y(); + pos_prop->z = decal.position.z(); + auto u_prop = decal_prop->at("UAxis_37_EBEB715F45491AECACCC07A1AE4646D1"); + u_prop->x = decal.uAxis.x(); + u_prop->y = decal.uAxis.y(); + u_prop->z = decal.uAxis.z(); + auto v_prop = decal_prop->at("VAxis_39_C31EB2664EE202CAECFBBB84100B5E35"); + v_prop->x = decal.vAxis.x(); + v_prop->y = decal.vAxis.y(); + v_prop->z = decal.vAxis.z(); + auto offset_prop = decal_prop->at("Offset_29_B02BBBB74FC60F5EDBEBAB8020738020"); + offset_prop->x = decal.offset.x(); + offset_prop->y = decal.offset.y(); + decal_prop->at("Scale_32_959D1C2747AFD8D62808468235CBBA40")->value = decal.scale; + decal_prop->at("Rotation_27_12D7C314493D203D5C2326A03C5F910F")->value = decal.rotation; + decal_prop->at("Flip_35_CECCFB184CCD9412BD93FE9A8B656BE1")->value = decal.flip; + decal_prop->at("Wrap_43_A7C68CDF4A92AF2ECDA53F953EE7CA62")->value = decal.wrap; + } +} + +void Mass::getAccessories(Containers::ArrayView accessories, ArrayProperty* accessory_array) { + for(UnsignedInt i = 0; i < accessory_array->items.size(); i++) { + auto acc_prop = accessory_array->at(i); + auto& accessory = accessories[i]; + + accessory.attachIndex = acc_prop->at("AttachIndex_2_4AFCF6024E4BA7426C6B9F80B8179D20")->value; + accessory.id = acc_prop->at("ID_4_5757B32647BAE263266259B8A7DFFFC1")->value; + auto acc_styles = acc_prop->at("Styles_7_91DEB0F24E24D13FC9472882C11D0DFD"); + for(UnsignedInt j = 0; j < acc_styles->items.size(); j++) { + accessory.styles[j] = acc_styles->at(j)->value; + } + auto rel_pos_prop = acc_prop->at("RelativePosition_14_BE8FB2A94074F34B3EDA6683B227D3A1"); + accessory.relativePosition = Vector3{rel_pos_prop->x, rel_pos_prop->y, rel_pos_prop->z}; + auto rel_pos_offset_prop = acc_prop->at("RelativePositionOffset_15_98FD0CE74E44BBAFC2D46FB4CA4E0ED6"); + accessory.relativePositionOffset = Vector3{rel_pos_offset_prop->x, rel_pos_offset_prop->y, rel_pos_offset_prop->z}; + auto rel_rot_prop = acc_prop->at("RelativeRotation_20_C78C73274E6E78E7878F8C98ECA342C0"); + accessory.relativeRotation = Vector3{rel_rot_prop->x, rel_rot_prop->y, rel_rot_prop->z}; + auto rel_rot_offset_prop = acc_prop->at("RelativeRotationOffset_21_E07FA0EC46728B7BA763C6861249ABAA"); + accessory.relativeRotationOffset = Vector3{rel_rot_offset_prop->x, rel_rot_offset_prop->y, rel_rot_offset_prop->z}; + auto local_scale_prop = acc_prop->at("LocalScale_24_DC2D93A742A41A46E7E61D988F15ED53"); + accessory.localScale = Vector3{local_scale_prop->x, local_scale_prop->y, local_scale_prop->z}; + } +} + +void Mass::writeAccessories(Containers::ArrayView accessories, ArrayProperty* accs_array) { + for(UnsignedInt i = 0; i < accs_array->items.size(); i++) { + auto acc_prop = accs_array->at(i); + auto& accessory = accessories[i]; + + acc_prop->at("AttachIndex_2_4AFCF6024E4BA7426C6B9F80B8179D20")->value = accessory.attachIndex; + acc_prop->at("ID_4_5757B32647BAE263266259B8A7DFFFC1")->value = accessory.id; + auto acc_styles = acc_prop->at("Styles_7_91DEB0F24E24D13FC9472882C11D0DFD"); + for(UnsignedInt j = 0; j < acc_styles->items.size(); j++) { + acc_styles->at(j)->value = accessory.styles[j]; + } + auto rel_pos_prop = acc_prop->at("RelativePosition_14_BE8FB2A94074F34B3EDA6683B227D3A1"); + rel_pos_prop->x = accessory.relativePosition.x(); + rel_pos_prop->y = accessory.relativePosition.y(); + rel_pos_prop->z = accessory.relativePosition.z(); + auto rel_pos_offset_prop = acc_prop->at("RelativePositionOffset_15_98FD0CE74E44BBAFC2D46FB4CA4E0ED6"); + rel_pos_offset_prop->x = accessory.relativePositionOffset.x(); + rel_pos_offset_prop->y = accessory.relativePositionOffset.y(); + rel_pos_offset_prop->z = accessory.relativePositionOffset.z(); + auto rel_rot_prop = acc_prop->at("RelativeRotation_20_C78C73274E6E78E7878F8C98ECA342C0"); + rel_rot_prop->x = accessory.relativeRotation.x(); + rel_rot_prop->y = accessory.relativeRotation.y(); + rel_rot_prop->z = accessory.relativeRotation.z(); + auto rel_rot_offset_prop = acc_prop->at("RelativeRotationOffset_21_E07FA0EC46728B7BA763C6861249ABAA"); + rel_rot_offset_prop->x = accessory.relativeRotationOffset.x(); + rel_rot_offset_prop->y = accessory.relativeRotationOffset.y(); + rel_rot_offset_prop->z = accessory.relativeRotationOffset.z(); + auto local_scale_prop = acc_prop->at("LocalScale_24_DC2D93A742A41A46E7E61D988F15ED53"); + local_scale_prop->x = accessory.localScale.x(); + local_scale_prop->y = accessory.localScale.y(); + local_scale_prop->z = accessory.localScale.z(); + } +} + +void Mass::getWeaponType(const char* prop_name, Containers::ArrayView weapon_array) { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + return; + } + + auto prop = unit_data->at(prop_name); + if(!prop) { + _state = State::Invalid; + return; + } + + if(prop->items.size() != weapon_array.size()) { + _state = State::Invalid; + return; + } + + for(UnsignedInt i = 0; i < weapon_array.size(); i++) { + auto weapon_prop = prop->at(i); + auto& weapon = weapon_array[i]; + + weapon.name = weapon_prop->at("Name_13_7BF0D31F4E50C50C47231BB36A485D92")->value; + auto& weapon_type = weapon_prop->at("Type_2_35ABA8C3406F8D9BBF14A89CD6BCE976")->enumValue; + #define c(enumerator, strenum, name) if(weapon_type == (strenum)) { weapon.type = WeaponType::enumerator; } else + #include "../Maps/WeaponTypes.hpp" + #undef c + { + _state = State::Invalid; + Utility::Warning{} << "Invalid weapon type enum value in getWeaponType()."; + } + + auto parts_prop = weapon_prop->at("Element_6_8E4617CC4B2C1F1490435599784EC6E0"); + weapon.parts = Containers::Array{ValueInit, parts_prop->items.size()}; + + for(UnsignedInt j = 0; j < parts_prop->items.size(); j++) { + auto part_prop = parts_prop->at(j); + auto& part = weapon.parts[j]; + + part.id = part_prop->at("ID_2_A74D75434308158E5926178822DD28EE")->value; + + auto part_styles = part_prop->at("Styles_17_994C97C34A90667BE5B716BFD0B97588"); + for(UnsignedInt k = 0; k < part_styles->items.size(); k++) { + part.styles[k] = part_styles->at(k)->value; + } + + auto part_decals = part_prop->at("Decals_13_8B81112B453D7230C0CDE982185E14F1"); + if(part_decals->items.size() != part.decals.size()) { + part.decals = Containers::Array{part_decals->items.size()}; + } + + getDecals(part.decals, part_decals); + + auto part_accs = part_prop->at("Accessories_21_3878DE8B4ED0EA0DB725E98BCDC20E0C"); + if(!part_accs) { + part.accessories = Containers::Array{0}; + continue; + } + + if(part_accs->items.size() != part.accessories.size()) { + part.accessories = Containers::Array{part_accs->items.size()}; + } + getAccessories(part.accessories, part_accs); + } + + auto custom_styles = weapon_prop->at("Styles_10_8C3C82444B986AD7A99595AD4985912D"); + if(!custom_styles) { + _state = State::Invalid; + return; + } + + if(custom_styles->items.size() != weapon.customStyles.size()) { + _state = State::Invalid; + return; + } + + getCustomStyles(weapon.customStyles, custom_styles); + + weapon.attached = weapon_prop->at("Attach_15_D00AABBD4AD6A04778D56D81E51927B3")->value; + auto& damage_type = weapon_prop->at("DamageType_18_E1FFA53540591A9087EC698117A65C83")->enumValue; + #define c(enumerator, strenum) if(damage_type == (strenum)) { weapon.damageType = DamageType::enumerator; } else + #include "../Maps/DamageTypes.hpp" + #undef c + { + _state = State::Invalid; + Utility::Warning{} << "Invalid damage type enum value in getWeaponType()."; + } + weapon.dualWield = weapon_prop->at("DualWield_20_B2EB2CEA4A6A233DC7575996B6DD1222")->value; + auto& effect_colour_mode = weapon_prop->at("ColorEfxMode_24_D254BCF943E852BF9ADB8AAA8FD80014")->enumValue; + #define c(enumerator, strenum) if(effect_colour_mode == (strenum)) { weapon.effectColourMode = EffectColourMode::enumerator; } else + #include "../Maps/EffectColourModes.hpp" + #undef c + { + _state = State::Invalid; + Utility::Warning{} << "Invalid effect colour mode in getWeaponType()."; + } + auto effect_colour = weapon_prop->at("ColorEfx_26_D921B62946C493E487455A831F4520AC"); + weapon.effectColour = Color4{effect_colour->r, effect_colour->g, effect_colour->b, effect_colour->a}; + } +} + +auto Mass::writeWeaponType(const char* prop_name, Containers::ArrayView weapon_array) -> bool { + auto unit_data = _mass->at("UnitData"); + if(!unit_data) { + _state = State::Invalid; + _lastError = "No unit data in " + _filename; + return false; + } + + auto prop = unit_data->at(prop_name); + if(!prop) { + _state = State::Invalid; + _lastError = std::string{prop_name} + " not found in " + _filename; + return false; + } + + if(prop->items.size() != weapon_array.size()) { + _state = State::Invalid; + _lastError = "Weapon type array size mismatch."; + return false; + } + + for(UnsignedInt i = 0; i < weapon_array.size(); i++) { + auto weapon_prop = prop->at(i); + auto& weapon = weapon_array[i]; + + weapon_prop->at("Name_13_7BF0D31F4E50C50C47231BB36A485D92")->value = weapon.name; + switch(weapon.type) { + #define c(enumerator, strenum, name) case WeaponType::enumerator: weapon_prop->at("Type_2_35ABA8C3406F8D9BBF14A89CD6BCE976")->enumValue = strenum; break; + #include "../Maps/WeaponTypes.hpp" + #undef c + default: + Utility::Warning{} << "Invalid weapon type enum value in writeWeaponType()."; + } + + auto parts_prop = weapon_prop->at("Element_6_8E4617CC4B2C1F1490435599784EC6E0"); + if(parts_prop->items.size() != weapon.parts.size()) { + _state = State::Invalid; + _lastError = "Weapon parts array size mismatch."; + return false; + } + + for(UnsignedInt j = 0; j < parts_prop->items.size(); j++) { + auto part_prop = parts_prop->at(j); + auto& part = weapon.parts[j]; + + part_prop->at("ID_2_A74D75434308158E5926178822DD28EE")->value = part.id; + + auto part_styles = part_prop->at("Styles_17_994C97C34A90667BE5B716BFD0B97588"); + for(UnsignedInt k = 0; k < part_styles->items.size(); k++) { + part_styles->at(k)->value = part.styles[k]; + } + + auto part_decals = part_prop->at("Decals_13_8B81112B453D7230C0CDE982185E14F1"); + writeDecals(part.decals, part_decals); + + auto part_accs = part_prop->at("Accessories_21_3878DE8B4ED0EA0DB725E98BCDC20E0C"); + if(!part_accs) { + continue; + } + + if(part_accs->items.size() != part.accessories.size()) { + _state = State::Invalid; + _lastError = "Accessories array size mismatch."; + return false; + } + + writeAccessories(part.accessories, part_accs); + } + + auto custom_styles = weapon_prop->at("Styles_10_8C3C82444B986AD7A99595AD4985912D"); + if(!custom_styles) { + _state = State::Invalid; + _lastError = "No custom styles found for weapon."; + return false; + } + + if(custom_styles->items.size() != weapon.customStyles.size()) { + _state = State::Invalid; + _lastError = "Custom styles array size mismatch."; + return false; + } + + for(UnsignedInt j = 0; j < weapon.customStyles.size(); j++) { + setCustomStyle(weapon.customStyles[j], j, custom_styles); + } + + weapon_prop->at("Attach_15_D00AABBD4AD6A04778D56D81E51927B3")->value = weapon.attached; + switch(weapon.damageType) { + #define c(enumerator, strenum) case DamageType::enumerator: weapon_prop->at("DamageType_18_E1FFA53540591A9087EC698117A65C83")->enumValue = strenum; break; + #include "../Maps/DamageTypes.hpp" + #undef c + default: + Utility::Warning{} << "Unknown damage type enum value in writeWeaponType()."; + } + weapon_prop->at("DualWield_20_B2EB2CEA4A6A233DC7575996B6DD1222")->value = weapon.dualWield; + switch(weapon.effectColourMode) { + #define c(enumerator, enumstr) case EffectColourMode::enumerator: \ + weapon_prop->at("ColorEfxMode_24_D254BCF943E852BF9ADB8AAA8FD80014")->enumValue = enumstr; \ + break; + #include "../Maps/EffectColourModes.hpp" + #undef c + default: + Utility::Warning{} << "Unknown effect colour mode in writeWeaponType()."; + } + auto effect_colour = weapon_prop->at("ColorEfx_26_D921B62946C493E487455A831F4520AC"); + effect_colour->r = weapon.effectColour.r(); + effect_colour->g = weapon.effectColour.g(); + effect_colour->b = weapon.effectColour.b(); + effect_colour->a = weapon.effectColour.a(); + } + + if(!_mass->saveToFile()) { + _lastError = _mass->lastError(); + return false; + } + + return true; +} + +void Mass::getTuningCategory(const char* big_node_prop_name, Int& big_node_id, + const char* small_nodes_prop_name, Containers::ArrayView small_nodes_ids) +{ + auto node_id = _mass->at(big_node_prop_name); + if(!node_id) { + _state = State::Invalid; + return; + } + big_node_id = node_id->value; + + auto node_ids = _mass->at(small_nodes_prop_name); + if(!node_ids) { + _state = State::Invalid; + return; + } + + if(node_ids->items.size() != small_nodes_ids.size()) { + _state = State::Invalid; + return; + } + + for(UnsignedInt i = 0; i < small_nodes_ids.size(); i++) { + auto small_node_id = node_ids->at(i); + CORRADE_INTERNAL_ASSERT(small_node_id); + small_nodes_ids[i] = small_node_id->value; + } +} diff --git a/src/Mass/Mass.h b/src/Mass/Mass.h index 1c2d0ad..3edfc3c 100644 --- a/src/Mass/Mass.h +++ b/src/Mass/Mass.h @@ -1,7 +1,7 @@ #pragma once // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -18,17 +18,37 @@ #include -#include +#include +#include +#include +#include +#include +#include +#include + +#include "Joints.h" +#include "CustomStyle.h" +#include "Decal.h" +#include "Accessory.h" +#include "ArmourPart.h" +#include "WeaponPart.h" +#include "Weapon.h" + +#include "../UESaveFile/UESaveFile.h" + +using namespace Corrade; using namespace Magnum; -enum class MassState : UnsignedByte { - Empty, Invalid, Valid -}; +struct ArrayProperty; class Mass { public: - explicit Mass(const std::string& filename); + enum class State : UnsignedByte { + Empty, Invalid, Valid + }; + + explicit Mass(const std::string& path); Mass(const Mass&) = delete; Mass& operator=(const Mass&) = delete; @@ -36,23 +56,153 @@ class Mass { Mass(Mass&&) = default; Mass& operator=(Mass&&) = default; - static auto lastError() -> std::string const&; + auto lastError() -> std::string const&; - static auto getNameFromFile(const std::string& filename) -> std::string; + static auto getNameFromFile(const std::string& path) -> Containers::Optional; + + void refreshValues(); auto filename() -> std::string const&; - auto name() -> std::string const&; - auto getName() -> std::string const&; + auto name() -> Containers::Optional const&; + auto setName(std::string new_name) -> bool; - auto state() -> MassState; + auto state() -> State; - auto updateSteamId(const std::string& steam_id) -> bool; + auto dirty() const -> bool; + void setDirty(bool dirty = true); + + auto jointSliders() -> Joints&; + void getJointSliders(); + auto writeJointSliders() -> bool; + + auto frameStyles() -> Containers::ArrayView; + void getFrameStyles(); + auto writeFrameStyles() -> bool; + + auto eyeFlareColour() -> Color4&; + void getEyeFlareColour(); + auto writeEyeFlareColour() -> bool; + + auto frameCustomStyles() -> Containers::ArrayView; + void getFrameCustomStyles(); + auto writeFrameCustomStyle(UnsignedLong index) -> bool; + + auto armourParts() -> Containers::ArrayView; + void getArmourParts(); + auto writeArmourPart(ArmourSlot slot) -> bool; + + auto armourCustomStyles() -> Containers::ArrayView; + void getArmourCustomStyles(); + auto writeArmourCustomStyle(UnsignedLong index) -> bool; + + auto meleeWeapons() -> Containers::ArrayView; + void getMeleeWeapons(); + auto writeMeleeWeapons() -> bool; + + auto shields() -> Containers::ArrayView; + void getShields(); + auto writeShields() -> bool; + + auto bulletShooters() -> Containers::ArrayView; + void getBulletShooters(); + auto writeBulletShooters() -> bool; + + auto energyShooters() -> Containers::ArrayView; + void getEnergyShooters(); + auto writeEnergyShooters() -> bool; + + auto bulletLaunchers() -> Containers::ArrayView; + void getBulletLaunchers(); + auto writeBulletLaunchers() -> bool; + + auto energyLaunchers() -> Containers::ArrayView; + void getEnergyLaunchers(); + auto writeEnergyLaunchers() -> bool; + + auto globalStyles() -> Containers::ArrayView; + void getGlobalStyles(); + auto writeGlobalStyle(UnsignedLong index) -> bool; + + void getTuning(); + + auto engine() -> Int&; + auto gears() -> Containers::ArrayView; + + auto os() -> Int&; + auto modules() -> Containers::ArrayView; + + auto architecture() -> Int&; + auto techs() -> Containers::ArrayView; + + auto account() -> std::string const&; + auto updateAccount(const std::string& new_account) -> bool; private: - static std::string _lastError; + void getCustomStyles(Containers::ArrayView styles, ArrayProperty* style_array); + auto setCustomStyle(const CustomStyle& style, UnsignedLong index, ArrayProperty* style_array) -> bool; - std::string _filename = ""; - std::string _name = ""; - MassState _state = MassState::Empty; + void getDecals(Containers::ArrayView decals, ArrayProperty* decal_array); + void writeDecals(Containers::ArrayView decals, ArrayProperty* decal_array); + + void getAccessories(Containers::ArrayView accessories, ArrayProperty* accessory_array); + void writeAccessories(Containers::ArrayView accessories, ArrayProperty* accs_array); + + void getWeaponType(const char* prop_name, Containers::ArrayView weapon_array); + auto writeWeaponType(const char* prop_name, Containers::ArrayView weapon_array) -> bool; + + void getTuningCategory(const char* big_node_prop_name, Int& big_node_id, + const char* small_nodes_prop_name, Containers::ArrayView small_nodes_ids); + + Containers::Optional _mass; + + std::string _lastError; + + std::string _folder; + std::string _filename; + State _state = State::Empty; + + bool _dirty = false; + + Containers::Optional _name = Containers::NullOpt; + + struct { + Joints joints{}; + + Containers::StaticArray<4, Int> styles{ValueInit}; + + Color4 eyeFlare{0.0f}; + + Containers::StaticArray<16, CustomStyle> customStyles; + } _frame; + + struct { + Containers::StaticArray<38, ArmourPart> parts; + + Containers::StaticArray<16, CustomStyle> customStyles; + } _armour; + + struct { + Containers::StaticArray<8, Weapon> melee; + Containers::StaticArray<1, Weapon> shields; + Containers::StaticArray<4, Weapon> bulletShooters; + Containers::StaticArray<4, Weapon> energyShooters; + Containers::StaticArray<4, Weapon> bulletLaunchers; + Containers::StaticArray<4, Weapon> energyLaunchers; + } _weapons; + + Containers::Array _globalStyles; + + struct { + Int engineId; + Containers::StaticArray<7, Int> gearIds; + + Int osId; + Containers::StaticArray<7, Int> moduleIds; + + Int archId; + Containers::StaticArray<7, Int> techIds; + } _tuning; + + std::string _account; }; diff --git a/src/Mass/Weapon.cpp b/src/Mass/Weapon.cpp new file mode 100644 index 0000000..5864282 --- /dev/null +++ b/src/Mass/Weapon.cpp @@ -0,0 +1,49 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "Weapon.h" + +Weapon::Weapon(const Weapon& other) { + name = other.name; + type = other.type; + parts = Containers::Array{other.parts.size()}; + for(UnsignedInt i = 0; i < parts.size(); i++) { + parts[i] = other.parts[i]; + } + customStyles = other.customStyles; + attached = other.attached; + damageType = other.damageType; + dualWield = other.dualWield; + effectColourMode = other.effectColourMode; + effectColour = other.effectColour; +} + +Weapon& Weapon::operator=(const Weapon& other) { + name = other.name; + type = other.type; + parts = Containers::Array{other.parts.size()}; + for(UnsignedInt i = 0; i < parts.size(); i++) { + parts[i] = other.parts[i]; + } + customStyles = other.customStyles; + attached = other.attached; + damageType = other.damageType; + dualWield = other.dualWield; + effectColourMode = other.effectColourMode; + effectColour = other.effectColour; + + return *this; +} diff --git a/src/Mass/Weapon.h b/src/Mass/Weapon.h new file mode 100644 index 0000000..660de20 --- /dev/null +++ b/src/Mass/Weapon.h @@ -0,0 +1,74 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "WeaponPart.h" +#include "CustomStyle.h" + +using namespace Corrade; +using namespace Magnum; + +enum class WeaponType { + Melee = 0, + Shield = 5, + BulletShooter = 1, + EnergyShooter = 2, + BulletLauncher = 3, + EnergyLauncher = 4, +}; + +enum class DamageType { + Physical = 0, + Piercing = 1, + Plasma = 5, + Heat = 2, + Freeze = 3, + Shock = 4, +}; + +enum class EffectColourMode { + Default = 0, + Custom = 1, +}; + +struct Weapon { + Weapon() = default; + + Weapon(const Weapon& other); + Weapon& operator=(const Weapon& other); + + Weapon(Weapon&& other) = default; + Weapon& operator=(Weapon&& other) = default; + + std::string name; + WeaponType type = WeaponType::Melee; + Containers::Array parts; + Containers::StaticArray<16, CustomStyle> customStyles{ValueInit}; + bool attached = false; + DamageType damageType = DamageType::Physical; + bool dualWield = false; + EffectColourMode effectColourMode = EffectColourMode::Default; + Color4 effectColour{0.0f}; +}; diff --git a/src/Mass/WeaponPart.h b/src/Mass/WeaponPart.h new file mode 100644 index 0000000..227b5af --- /dev/null +++ b/src/Mass/WeaponPart.h @@ -0,0 +1,66 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "Decal.h" +#include "Accessory.h" + +using namespace Corrade; +using namespace Magnum; + +struct WeaponPart { + WeaponPart() = default; + + WeaponPart(const WeaponPart& other) { + id = other.id; + styles = other.styles; + decals = Containers::Array{other.decals.size()}; + for(UnsignedInt i = 0; i < decals.size(); i++) { + decals[i] = other.decals[i]; + } + accessories = Containers::Array{other.accessories.size()}; + for(UnsignedInt i = 0; i < accessories.size(); i++) { + accessories[i] = other.accessories[i]; + } + } + WeaponPart& operator=(const WeaponPart& other) { + id = other.id; + styles = other.styles; + decals = Containers::Array{other.decals.size()}; + for(UnsignedInt i = 0; i < decals.size(); i++) { + decals[i] = other.decals[i]; + } + accessories = Containers::Array{other.accessories.size()}; + for(UnsignedInt i = 0; i < accessories.size(); i++) { + accessories[i] = other.accessories[i]; + } + return *this; + } + + WeaponPart(WeaponPart&& other) = default; + WeaponPart& operator=(WeaponPart&& other) = default; + + Int id = 0; + Containers::StaticArray<4, Int> styles{ValueInit}; + Containers::Array decals{}; + Containers::Array accessories{}; +}; diff --git a/src/MassManager/MassManager.cpp b/src/MassManager/MassManager.cpp index e0c4910..22b18bf 100644 --- a/src/MassManager/MassManager.cpp +++ b/src/MassManager/MassManager.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -49,23 +49,11 @@ auto MassManager::lastError() -> std::string const& { return _lastError; } -auto MassManager::massName(int hangar) -> std::string const& { - if(hangar < 0 || hangar >= 32) { - return empty_string; - } - - return _hangars[hangar].name(); +auto MassManager::hangar(Int hangar) -> Mass& { + return _hangars[hangar]; } -auto MassManager::massState(int hangar) -> MassState { - if(hangar < 0 || hangar >= 32) { - return MassState::Empty; - } - - return _hangars[hangar].state(); -} - -void MassManager::refreshHangar(int hangar) { +void MassManager::refreshHangar(Int hangar) { if(hangar < 0 || hangar >= 32) { return; } @@ -75,7 +63,7 @@ void MassManager::refreshHangar(int hangar) { _hangars[hangar] = Mass{mass_filename}; } -auto MassManager::importMass(const std::string& staged_fn, int hangar) -> bool { +auto MassManager::importMass(const std::string& staged_fn, Int hangar) -> bool { if(hangar < 0 || hangar >= 32) { _lastError = "Hangar out of range in MassManager::importMass()"; return false; @@ -91,18 +79,22 @@ auto MassManager::importMass(const std::string& staged_fn, int hangar) -> bool { std::string source = Utility::Directory::join(_stagingAreaDirectory, staged_fn); Utility::Directory::copy(source, source + ".tmp"); - if(!Mass{source + ".tmp"}.updateSteamId(_steamId)) { - _lastError = "The M.A.S.S. file at " + source + " seems to be corrupt."; - Utility::Directory::rm(source + ".tmp"); - return false; + Mass mass{source + ".tmp"}; + if(!mass.updateAccount(_steamId)) { + _lastError = mass.lastError(); + Utility::Directory::rm(source + ".tmp"); + return false; + } } - if(Utility::Directory::exists(_hangars[hangar].filename())) { - Utility::Directory::rm(_hangars[hangar].filename()); + std::string dest = Utility::Directory::join(_saveDirectory, _hangars[hangar].filename()); + + if(Utility::Directory::exists(dest)) { + Utility::Directory::rm(dest); } - if(!Utility::Directory::move(source + ".tmp", _hangars[hangar].filename())) { + if(!Utility::Directory::move(source + ".tmp", dest)) { _lastError = Utility::formatString("Couldn't move {} to hangar {:.2d}", staged_fn, hangar + 1); return false; } @@ -110,20 +102,20 @@ auto MassManager::importMass(const std::string& staged_fn, int hangar) -> bool { return true; } -auto MassManager::exportMass(int hangar) -> bool { +auto MassManager::exportMass(Int hangar) -> bool { if(hangar < 0 || hangar >= 32) { _lastError = "Hangar out of range in MassManager::exportMass()"; return false; } - if(_hangars[hangar].state() != MassState::Valid) { + if(_hangars[hangar].state() != Mass::State::Valid) { _lastError = Utility::formatString("There is no valid data to export in hangar {:.2d}", hangar + 1); return false; } std::string source = Utility::Directory::join(_saveDirectory, _hangars[hangar].filename()); std::string dest = Utility::Directory::join(_stagingAreaDirectory, - Utility::formatString("{}_{}.sav", _hangars[hangar].name(), _steamId)); + Utility::formatString("{}_{}.sav", *_hangars[hangar].name(), _steamId)); if(!Utility::Directory::copy(source, dest)) { _lastError = Utility::formatString("Couldn't export data from hangar {:.2d} to {}", hangar, dest); @@ -133,7 +125,7 @@ auto MassManager::exportMass(int hangar) -> bool { return true; } -auto MassManager::moveMass(int source, int destination) -> bool { +auto MassManager::moveMass(Int source, Int destination) -> bool { if(source < 0 || source >= 32) { _lastError = "Source hangar out of range."; return false; @@ -144,38 +136,38 @@ auto MassManager::moveMass(int source, int destination) -> bool { return false; } - std::string source_file = _hangars[source].filename(); - std::string dest_file = _hangars[destination].filename(); - MassState dest_state = _hangars[destination].state(); + std::string source_file = Utility::Directory::join(_saveDirectory, _hangars[source].filename()); + std::string dest_file = Utility::Directory::join(_saveDirectory, _hangars[destination].filename()); + Mass::State dest_state = _hangars[destination].state(); switch(dest_state) { - case MassState::Empty: + case Mass::State::Empty: break; - case MassState::Invalid: + case Mass::State::Invalid: Utility::Directory::rm(dest_file); break; - case MassState::Valid: + case Mass::State::Valid: Utility::Directory::move(dest_file, dest_file + ".tmp"); break; } Utility::Directory::move(source_file, dest_file); - if(dest_state == MassState::Valid) { + if(dest_state == Mass::State::Valid) { Utility::Directory::move(dest_file + ".tmp", source_file); } return true; } -auto MassManager::deleteMass(int hangar) -> bool { +auto MassManager::deleteMass(Int hangar) -> bool { if(hangar < 0 || hangar >= 32) { - _lastError = "Hangar out of bounds"; + _lastError = "Hangar out of range."; return false; } - if(!Utility::Directory::rm(_hangars[hangar].filename())) { - _lastError = "Deletion failed. Maybe the file was already deleted, or it's locked by another application."; + if(!Utility::Directory::rm(Utility::Directory::join(_saveDirectory, _hangars[hangar].filename()))) { + _lastError = Utility::formatString("Deletion failed: {}", std::strerror(errno)); return false; } @@ -199,7 +191,7 @@ void MassManager::refreshStagedMasses() { file_list.erase(iter, file_list.end()); for(const std::string& file : file_list) { - std::string name = Mass::getNameFromFile(Utility::Directory::join(_stagingAreaDirectory, file)); + std::string name = *Mass::getNameFromFile(Utility::Directory::join(_stagingAreaDirectory, file)); if(!name.empty()) { _stagedMasses[file] = name; @@ -214,7 +206,7 @@ auto MassManager::deleteStagedMass(const std::string& filename) -> bool { } if(!Utility::Directory::rm(Utility::Directory::join(_stagingAreaDirectory, filename))) { - _lastError = "The file " + filename + " couldn't be deleted for unknown reasons."; + _lastError = Utility::formatString("{} couldn't be deleted: {}", filename, std::strerror(errno)); return false; } diff --git a/src/MassManager/MassManager.h b/src/MassManager/MassManager.h index 2b0d0f7..fbfee37 100644 --- a/src/MassManager/MassManager.h +++ b/src/MassManager/MassManager.h @@ -1,7 +1,7 @@ #pragma once // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -31,8 +31,7 @@ class MassManager { auto lastError() -> std::string const&; - auto massName(int hangar) -> std::string const&; - auto massState(int hangar) -> MassState; + auto hangar(int hangar) -> Mass&; void refreshHangar(int hangar); diff --git a/src/Profile/Locators.h b/src/Profile/Locators.h deleted file mode 100644 index 428040e..0000000 --- a/src/Profile/Locators.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -// 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 . - -constexpr char company_name_locator[] = "CompanyName\0\f\0\0\0StrProperty"; -constexpr char active_slot_locator[] = "ActiveFrameSlot\0\f\0\0\0IntProperty"; -constexpr char credits_locator[] = "Credit\0\f\0\0\0IntProperty"; -constexpr char story_progress_locator[] = "StoryProgress\0\f\0\0\0IntProperty"; -constexpr char last_mission_id_locator[] = "LastMissionID\0\f\0\0\0IntProperty"; - -constexpr char verse_steel_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x00\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char undinium_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x01\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char necrium_alloy_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x02\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char lunarite_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x03\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char asterite_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x04\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; - -constexpr char ednil_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x0a\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char nuflalt_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x0b\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char aurelene_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\f\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char soldus_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x0d\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char synthesized_n_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x0e\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; - -constexpr char alcarbonite_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x14\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char keriphene_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x15\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char nitinol_cm_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x16\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char quarkium_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x17\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char alterene_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x18\x35\f\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; - -constexpr char mixed_composition_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa0\xbb\x0d\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char void_residue_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa1\xbb\x0d\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char muscular_construction_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa2\xbb\x0d\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char mineral_exoskeletology_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa3\xbb\x0d\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; -constexpr char carbonized_skin_locator[] = - "ID_4_AAE08F17428E229EC7A2209F51081A21\0\f\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa4\xbb\x0d\0,\0\0\0" - "Quantity_3_560F09B5485C365D3041888910019CE3\0\f\0\0\0IntProperty"; - -constexpr char engine_inventory_locator[] = "InventoryEngine\0\x0e\0\0\0ArrayProperty"; -constexpr char gear_inventory_locator[] = "InventoryGear\0\x0e\0\0\0ArrayProperty"; -constexpr char os_inventory_locator[] = "InventoryOS\0\x0e\0\0\0ArrayProperty"; -constexpr char module_inventory_locator[] = "InventoryModule\0\x0e\0\0\0ArrayProperty"; -constexpr char arch_inventory_locator[] = "InventoryArchitect\0\x0e\0\0\0ArrayProperty"; -constexpr char tech_inventory_locator[] = "InventoryTech\0\x0e\0\0\0ArrayProperty"; diff --git a/src/Profile/Profile.cpp b/src/Profile/Profile.cpp index 46a1819..6913153 100644 --- a/src/Profile/Profile.cpp +++ b/src/Profile/Profile.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -17,25 +17,23 @@ #include #include +#include #include #include #include -#include "Profile.h" +#include "../UESaveFile/Types/ArrayProperty.h" +#include "../UESaveFile/Types/ResourceItemValue.h" +#include "../UESaveFile/Types/IntProperty.h" +#include "../UESaveFile/Types/StringProperty.h" -#include "Locators.h" +#include "Profile.h" using namespace Corrade; -Profile::Profile(const std::string& path) { - auto map = Utility::Directory::mapRead(path); - - if(!map) { - _lastError = "Couldn't memory-map " + Utility::Directory::filename(path); - return; - } - - _profileDirectory = Utility::Directory::path(path); +Profile::Profile(const std::string& path): + _profile(path) +{ _filename = Utility::Directory::filename(path); if(Utility::String::beginsWith(_filename, "Demo")) { @@ -45,18 +43,22 @@ Profile::Profile(const std::string& path) { _type = ProfileType::FullGame; } - _steamId = Utility::String::ltrim(Utility::String::rtrim(_filename, ".sav"), (_type == ProfileType::Demo ? "Demo" : "") + std::string{"Profile"}); - - auto it = std::search(map.begin(), map.end(), &company_name_locator[0], &company_name_locator[27]); - - if(it == map.end()) { - _lastError = "Couldn't find a company name in " + _filename; + auto account_prop = _profile.at("Account"); + if(!account_prop) { + _lastError = "Couldn't find an account ID in " + _filename; + _valid = false; return; } + _account = account_prop->value; - _companyName = std::string{it + 41}; + if(Utility::String::beginsWith(_account, "PMCSlot")) { + _version = ProfileVersion::Normal; + } + else { + _version = ProfileVersion::Legacy; + } - _valid = true; + refreshValues(); } auto Profile::valid() const -> bool { @@ -75,929 +77,353 @@ auto Profile::type() const -> ProfileType { return _type; } -auto Profile::steamId() const -> std::string const& { - return _steamId; +auto Profile::version() const -> ProfileVersion { + return _version; +} + +auto Profile::account() const -> std::string const& { + return _account; } void Profile::refreshValues() { - getCompanyName(); - getActiveFrameSlot(); - getCredits(); - getStoryProgress(); - getLastMissionId(); + if(!_profile.reloadData()) { + _lastError = _profile.lastError(); + _valid = false; + return; + } - getVerseSteel(); - getUndinium(); - getNecriumAlloy(); - getLunarite(); - getAsterite(); + auto name_prop = _profile.at("CompanyName"); + if(!name_prop) { + _lastError = "No company name in " + _filename; + _valid = false; + return; + } + _name = name_prop->value; - getEdnil(); - getNuflalt(); - getAurelene(); - getSoldus(); - getSynthesizedN(); + auto prop = _profile.at("ActiveFrameSlot"); + _activeFrameSlot = prop ? prop->value : 0; - getAlcarbonite(); - getKeriphene(); - getNitinolCM(); - getQuarkium(); - getAlterene(); + prop = _profile.at("Credit"); + _credits = prop ? prop->value : 0; - getMixedComposition(); - getVoidResidue(); - getMuscularConstruction(); - getMineralExoskeletology(); - getCarbonizedSkin(); + prop = _profile.at("StoryProgress"); + _storyProgress = prop ? prop->value : 0; + + prop = _profile.at("LastMissionID"); + _lastMissionId = prop ? prop->value : 0; + + _verseSteel = getResource("ResourceMaterial", VerseSteel); + _undinium = getResource("ResourceMaterial", Undinium); + _necriumAlloy = getResource("ResourceMaterial", NecriumAlloy); + _lunarite = getResource("ResourceMaterial", Lunarite); + _asterite = getResource("ResourceMaterial", Asterite); + + _ednil = getResource("ResourceMaterial", Ednil); + _nuflalt = getResource("ResourceMaterial", Nuflalt); + _aurelene = getResource("ResourceMaterial", Aurelene); + _soldus = getResource("ResourceMaterial", Soldus); + _synthesisedN = getResource("ResourceMaterial", SynthesisedN); + + _alcarbonite = getResource("ResourceMaterial", Alcarbonite); + _keriphene = getResource("ResourceMaterial", Keriphene); + _nitinolCM = getResource("ResourceMaterial", NitinolCM); + _quarkium = getResource("ResourceMaterial", Quarkium); + _alterene = getResource("ResourceMaterial", Alterene); + + _mixedComposition = getResource("ResourceQuarkData", MixedComposition); + _voidResidue = getResource("ResourceQuarkData", VoidResidue); + _muscularConstruction = getResource("ResourceQuarkData", MuscularConstruction); + _mineralExoskeletology = getResource("ResourceQuarkData", MineralExoskeletology); + _carbonisedSkin = getResource("ResourceQuarkData", CarbonisedSkin); + + _valid = true; } auto Profile::companyName() const -> std::string const& { - return _companyName; -} - -auto Profile::getCompanyName() -> std::string const& { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto it = std::search(mmap.begin(), mmap.end(), &company_name_locator[0], &company_name_locator[27]); - - if(it == mmap.end()) { - _lastError = "Couldn't find a company name in " + _filename; - _companyName = ""; - } - else { - _companyName = std::string{it + 41}; - } - - return _companyName; + return _name; } auto Profile::renameCompany(const std::string& new_name) -> bool { - char length_difference = static_cast(_companyName.length() - new_name.length()); - - std::string profile_data = Utility::Directory::readString(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(profile_data.begin(), profile_data.end(), &company_name_locator[0], &company_name_locator[27]); - - if(iter != profile_data.end()) { - - *(iter + 0x1C) = *(iter + 0x1C) - length_difference; - *(iter + 0x25) = *(iter + 0x25) - length_difference; - - while(*(iter + 0x29) != '\0') { - profile_data.erase(iter + 0x29); - } - - profile_data.insert(iter + 0x29, new_name.cbegin(), new_name.cend()); - - if(!Utility::Directory::writeString(Utility::Directory::join(_profileDirectory, _filename), profile_data)) { - _lastError = "The file" + _filename + " couldn't be written to."; - return false; - } - - _companyName = new_name; - - return true; - } - else { - _lastError = "Couldn't find the company name in " + _filename; - + auto name_prop = _profile.at("CompanyName"); + if(!name_prop) { + _lastError = "No company name in " + _filename; + _valid = false; return false; } + + name_prop->value = new_name; + + if(!_profile.saveToFile()) { + _lastError = _profile.lastError(); + return false; + } + + return true; } auto Profile::activeFrameSlot() const -> Int { return _activeFrameSlot; } -auto Profile::getActiveFrameSlot() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &active_slot_locator[0], &active_slot_locator[31]); - - if(iter == mmap.end()) { - if(std::search(mmap.begin(), mmap.end(), &credits_locator[0], &credits_locator[22]) != mmap.end()) { - _activeFrameSlot = 0; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _activeFrameSlot = -1; - } - } - else { - _activeFrameSlot = *(iter + 41); - } - - return _activeFrameSlot; -} - auto Profile::credits() const -> Int { return _credits; } -auto Profile::getCredits() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &credits_locator[0], &credits_locator[22]); - - if(iter != mmap.end()) { - _credits = *reinterpret_cast(iter + 0x20); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _credits = -1; - } - - return _credits; -} - auto Profile::setCredits(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); + auto credits_prop = _profile.at("Credit"); - auto iter = std::search(mmap.begin(), mmap.end(), &credits_locator[0], &credits_locator[22]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x20) = amount; - _credits = amount; - return true; + if(!credits_prop) { + credits_prop = new IntProperty; + credits_prop->name.emplace("Credit"); + _profile.appendProperty(IntProperty::ptr{credits_prop}); } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; + + credits_prop->value = amount; + + if(!_profile.saveToFile()) { + _lastError = _profile.lastError(); return false; } + + return true; } auto Profile::storyProgress() const -> Int { return _storyProgress; } -auto Profile::getStoryProgress() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &story_progress_locator[0], &story_progress_locator[29]); - - if(iter != mmap.end()) { - _storyProgress = *reinterpret_cast(iter + 0x27); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _storyProgress = -1; - } - - return _storyProgress; -} - auto Profile::setStoryProgress(Int progress) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); + auto story_progress_prop = _profile.at("StoryProgress"); - auto iter = std::search(mmap.begin(), mmap.end(), &story_progress_locator[0], &story_progress_locator[29]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x27) = progress; - _storyProgress = progress; - return true; + if(!story_progress_prop) { + story_progress_prop = new IntProperty; + story_progress_prop->name.emplace("StoryProgress"); + _profile.appendProperty(IntProperty::ptr{story_progress_prop}); } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; + + story_progress_prop->value = progress; + + if(!_profile.saveToFile()) { + _lastError = _profile.lastError(); return false; } + + return true; } auto Profile::lastMissionId() const -> Int { return _lastMissionId; } -auto Profile::getLastMissionId() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &last_mission_id_locator[0], &last_mission_id_locator[29]); - - if(iter != mmap.end()) { - _lastMissionId = *reinterpret_cast(iter + 0x27); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _lastMissionId = -1; - } - - return _lastMissionId; -} - auto Profile::verseSteel() const -> Int { return _verseSteel; } -auto Profile::getVerseSteel() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &verse_steel_locator[0], &verse_steel_locator[129]); - - if(iter != mmap.end()) { - _verseSteel = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _verseSteel = -1; - } - - return _verseSteel; -} - auto Profile::setVerseSteel(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &verse_steel_locator[0], &verse_steel_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _verseSteel = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", VerseSteel, amount); } auto Profile::undinium() const -> Int { return _undinium; } -auto Profile::getUndinium() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &undinium_locator[0], &undinium_locator[129]); - - if(iter != mmap.end()) { - _undinium = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _undinium = -1; - } - - return _undinium; -} - auto Profile::setUndinium(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &undinium_locator[0], &undinium_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _undinium = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Undinium, amount); } auto Profile::necriumAlloy() const -> Int { return _necriumAlloy; } -auto Profile::getNecriumAlloy() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &necrium_alloy_locator[0], &necrium_alloy_locator[129]); - - if(iter != mmap.end()) { - _necriumAlloy = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _necriumAlloy = -1; - } - - return _necriumAlloy; -} - auto Profile::setNecriumAlloy(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &necrium_alloy_locator[0], &necrium_alloy_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _necriumAlloy = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", NecriumAlloy, amount); } auto Profile::lunarite() const -> Int { return _lunarite; } -auto Profile::getLunarite() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &lunarite_locator[0], &lunarite_locator[129]); - - if(iter != mmap.end()) { - _lunarite = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _lunarite = -1; - } - - return _lunarite; -} - auto Profile::setLunarite(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &lunarite_locator[0], &lunarite_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _lunarite = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Lunarite, amount); } auto Profile::asterite() const -> Int { return _asterite; } -auto Profile::getAsterite() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &asterite_locator[0], &asterite_locator[129]); - - if(iter != mmap.end()) { - _asterite = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _asterite = -1; - } - - return _asterite; -} - auto Profile::setAsterite(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &asterite_locator[0], &asterite_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _asterite = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Asterite, amount); } auto Profile::ednil() const -> Int { return _ednil; } -auto Profile::getEdnil() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &ednil_locator[0], &ednil_locator[129]); - - if(iter != mmap.end()) { - _ednil = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _ednil = -1; - } - - return _ednil; -} - auto Profile::setEdnil(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &ednil_locator[0], &ednil_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _ednil = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Ednil, amount); } auto Profile::nuflalt() const -> Int { return _nuflalt; } -auto Profile::getNuflalt() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &nuflalt_locator[0], &nuflalt_locator[129]); - - if(iter != mmap.end()) { - _nuflalt = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _nuflalt = -1; - } - - return _nuflalt; -} - auto Profile::setNuflalt(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &nuflalt_locator[0], &nuflalt_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _nuflalt = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Nuflalt, amount); } auto Profile::aurelene() const -> Int { return _aurelene; } -auto Profile::getAurelene() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &aurelene_locator[0], &aurelene_locator[129]); - - if(iter != mmap.end()) { - _aurelene = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _aurelene = -1; - } - - return _aurelene; -} - auto Profile::setAurelene(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &aurelene_locator[0], &aurelene_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _aurelene = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Aurelene, amount); } auto Profile::soldus() const -> Int { return _soldus; } -auto Profile::getSoldus() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &soldus_locator[0], &soldus_locator[129]); - - if(iter != mmap.end()) { - _soldus = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _soldus = -1; - } - - return _soldus; -} - auto Profile::setSoldus(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &soldus_locator[0], &soldus_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _soldus = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Soldus, amount); } -auto Profile::synthesizedN() const -> Int { - return _synthesizedN; +auto Profile::synthesisedN() const -> Int { + return _synthesisedN; } -auto Profile::getSynthesizedN() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &synthesized_n_locator[0], &synthesized_n_locator[129]); - - if(iter != mmap.end()) { - _synthesizedN = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _synthesizedN = -1; - } - - return _synthesizedN; -} - -auto Profile::setSynthesizedN(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &synthesized_n_locator[0], &synthesized_n_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _synthesizedN = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } +auto Profile::setSynthesisedN(Int amount) -> bool { + return setResource("ResourceMaterial", SynthesisedN, amount); } auto Profile::alcarbonite() const -> Int { return _alcarbonite; } -auto Profile::getAlcarbonite() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &alcarbonite_locator[0], &alcarbonite_locator[129]); - - if(iter != mmap.end()) { - _alcarbonite = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _alcarbonite = -1; - } - - return _alcarbonite; -} - auto Profile::setAlcarbonite(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &alcarbonite_locator[0], &alcarbonite_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _alcarbonite = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Alcarbonite, amount); } auto Profile::keriphene() const -> Int { return _keriphene; } -auto Profile::getKeriphene() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &keriphene_locator[0], &keriphene_locator[129]); - - if(iter != mmap.end()) { - _keriphene = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _keriphene= -1; - } - - return _keriphene; -} - auto Profile::setKeriphene(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &keriphene_locator[0], &keriphene_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _keriphene = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Keriphene, amount); } auto Profile::nitinolCM() const -> Int { return _nitinolCM; } -auto Profile::getNitinolCM() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &nitinol_cm_locator[0], &nitinol_cm_locator[129]); - - if(iter != mmap.end()) { - _nitinolCM = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _nitinolCM = -1; - } - - return _nitinolCM; -} - auto Profile::setNitinolCM(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &nitinol_cm_locator[0], &nitinol_cm_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _nitinolCM = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", NitinolCM, amount); } auto Profile::quarkium() const -> Int { return _quarkium; } -auto Profile::getQuarkium() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &quarkium_locator[0], &quarkium_locator[129]); - - if(iter != mmap.end()) { - _quarkium = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _quarkium = -1; - } - - return _quarkium; -} - auto Profile::setQuarkium(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &quarkium_locator[0], &quarkium_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _quarkium = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Quarkium, amount); } auto Profile::alterene() const -> Int { return _alterene; } -auto Profile::getAlterene() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &alterene_locator[0], &alterene_locator[129]); - - if(iter != mmap.end()) { - _alterene = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _alterene = -1; - } - - return _alterene; -} - auto Profile::setAlterene(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &alterene_locator[0], &alterene_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _alterene = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceMaterial", Alterene, amount); } auto Profile::mixedComposition() const -> Int { return _mixedComposition; } -auto Profile::getMixedComposition() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &mixed_composition_locator[0], &mixed_composition_locator[129]); - - if(iter != mmap.end()) { - _mixedComposition = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _mixedComposition = -1; - } - - return _mixedComposition; -} - auto Profile::setMixedComposition(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &mixed_composition_locator[0], &mixed_composition_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _mixedComposition = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceQuarkData", MixedComposition, amount); } auto Profile::voidResidue() const -> Int { return _voidResidue; } -auto Profile::getVoidResidue() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &void_residue_locator[0], &void_residue_locator[129]); - - if(iter != mmap.end()) { - _voidResidue = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _voidResidue = -1; - } - - return _voidResidue; -} - auto Profile::setVoidResidue(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &void_residue_locator[0], &void_residue_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _voidResidue = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceQuarkData", VoidResidue, amount); } auto Profile::muscularConstruction() const -> Int { return _muscularConstruction; } -auto Profile::getMuscularConstruction() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &muscular_construction_locator[0], &muscular_construction_locator[129]); - - if(iter != mmap.end()) { - _muscularConstruction = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _muscularConstruction = -1; - } - - return _muscularConstruction; -} - auto Profile::setMuscularConstruction(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &muscular_construction_locator[0], &muscular_construction_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _muscularConstruction = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - return false; - } + return setResource("ResourceQuarkData", MuscularConstruction, amount); } auto Profile::mineralExoskeletology() const -> Int { return _mineralExoskeletology; } -auto Profile::getMineralExoskeletology() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &mineral_exoskeletology_locator[0], &mineral_exoskeletology_locator[129]); - - if(iter != mmap.end()) { - _mineralExoskeletology = *reinterpret_cast(iter + 0x8C); - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _mineralExoskeletology = -1; - } - - return _mineralExoskeletology; -} - auto Profile::setMineralExoskeletology(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); + return setResource("ResourceQuarkData", MineralExoskeletology, amount); +} - auto iter = std::search(mmap.begin(), mmap.end(), &mineral_exoskeletology_locator[0], &mineral_exoskeletology_locator[129]); +auto Profile::carbonisedSkin() const -> Int { + return _carbonisedSkin; +} - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _mineralExoskeletology = amount; - return true; +auto Profile::setCarbonisedSkin(Int amount) -> bool { + return setResource("ResourceQuarkData", CarbonisedSkin, amount); +} + +auto Profile::getResource(const char* container, MaterialID id) -> Int { + auto mats_prop = _profile.at(container); + + if(!mats_prop) { + return 0; } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; + + auto predicate = [&id](UnrealPropertyBase::ptr& prop){ + auto res_prop = static_cast(prop.get()); + return res_prop->id == id; + }; + + auto it = std::find_if(mats_prop->items.begin(), mats_prop->items.end(), predicate); + return it != mats_prop->items.end() ? static_cast(it->get())->quantity : 0; +} + +auto Profile::setResource(const char* container, MaterialID id, Int amount) -> bool { + auto mats_prop = _profile.at(container); + + if(!mats_prop) { + _lastError = "Couldn't find " + std::string{container} + " in " + _filename; + _valid = false; return false; } -} -auto Profile::carbonizedSkin() const -> Int { - return _carbonizedSkin; -} + auto predicate = [&id](UnrealPropertyBase::ptr& prop){ + auto res_prop = static_cast(prop.get()); + return res_prop->id == id; + }; -auto Profile::getCarbonizedSkin() -> Int { - auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename)); + auto it = std::find_if(mats_prop->items.begin(), mats_prop->items.end(), predicate); - auto iter = std::search(mmap.begin(), mmap.end(), &carbonized_skin_locator[0], &carbonized_skin_locator[129]); - - if(iter != mmap.end()) { - _carbonizedSkin = *reinterpret_cast(iter + 0x8C); + ResourceItemValue* res_prop; + if(it == mats_prop->items.end()) { + res_prop = new ResourceItemValue; + res_prop->id = id; + ResourceItemValue::ptr prop{res_prop}; + arrayAppend(mats_prop->items, std::move(prop)); } else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; - _carbonizedSkin = -1; + res_prop = static_cast(it->get()); } - return _carbonizedSkin; -} + res_prop->quantity = amount; -auto Profile::setCarbonizedSkin(Int amount) -> bool { - auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename)); - - auto iter = std::search(mmap.begin(), mmap.end(), &carbonized_skin_locator[0], &carbonized_skin_locator[129]); - - if(iter != mmap.end()) { - *reinterpret_cast(iter + 0x8C) = amount; - _carbonizedSkin = amount; - return true; - } - else { - _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file."; + if(!_profile.saveToFile()) { + _lastError = _profile.lastError(); return false; } + + return true; } diff --git a/src/Profile/Profile.h b/src/Profile/Profile.h index 63cf5bc..b65ea6a 100644 --- a/src/Profile/Profile.h +++ b/src/Profile/Profile.h @@ -1,7 +1,7 @@ #pragma once // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -20,6 +20,10 @@ #include +#include "../UESaveFile/UESaveFile.h" + +#include "ResourceIDs.h" + using namespace Magnum; enum class ProfileType : UnsignedByte { @@ -27,6 +31,11 @@ enum class ProfileType : UnsignedByte { FullGame }; +enum class ProfileVersion : UnsignedByte { + Legacy, // pre-0.8 + Normal // 0.8 and later +}; + class Profile { public: explicit Profile(const std::string& path); @@ -39,148 +48,128 @@ class Profile { auto type() const -> ProfileType; - auto steamId() const -> std::string const&; + auto version() const -> ProfileVersion; + + auto account() const -> std::string const&; void refreshValues(); auto companyName() const -> std::string const&; - auto getCompanyName() -> std::string const&; auto renameCompany(const std::string& new_name) -> bool; auto activeFrameSlot() const -> Int; - auto getActiveFrameSlot() -> Int; auto credits() const -> Int; - auto getCredits() -> Int; auto setCredits(Int credits) -> bool; auto storyProgress() const -> Int; - auto getStoryProgress() -> Int; auto setStoryProgress(Int progress) -> bool; auto lastMissionId() const -> Int; - auto getLastMissionId() -> Int; auto verseSteel() const -> Int; - auto getVerseSteel() -> Int; auto setVerseSteel(Int amount) -> bool; auto undinium() const -> Int; - auto getUndinium() -> Int; auto setUndinium(Int amount) -> bool; auto necriumAlloy() const -> Int; - auto getNecriumAlloy() -> Int; auto setNecriumAlloy(Int amount) -> bool; auto lunarite() const -> Int; - auto getLunarite() -> Int; auto setLunarite(Int amount) -> bool; auto asterite() const -> Int; - auto getAsterite() -> Int; auto setAsterite(Int amount) -> bool; auto ednil() const -> Int; - auto getEdnil() -> Int; auto setEdnil(Int amount) -> bool; auto nuflalt() const -> Int; - auto getNuflalt() -> Int; auto setNuflalt(Int amount) -> bool; auto aurelene() const -> Int; - auto getAurelene() -> Int; auto setAurelene(Int amount) -> bool; auto soldus() const -> Int; - auto getSoldus() -> Int; auto setSoldus(Int amount) -> bool; - auto synthesizedN() const -> Int; - auto getSynthesizedN() -> Int; - auto setSynthesizedN(Int amount) -> bool; + auto synthesisedN() const -> Int; + auto setSynthesisedN(Int amount) -> bool; auto alcarbonite() const -> Int; - auto getAlcarbonite() -> Int; auto setAlcarbonite(Int amount) -> bool; auto keriphene() const -> Int; - auto getKeriphene() -> Int; auto setKeriphene(Int amount) -> bool; auto nitinolCM() const -> Int; - auto getNitinolCM() -> Int; auto setNitinolCM(Int amount) -> bool; auto quarkium() const -> Int; - auto getQuarkium() -> Int; auto setQuarkium(Int amount) -> bool; auto alterene() const -> Int; - auto getAlterene() -> Int; auto setAlterene(Int amount) -> bool; auto mixedComposition() const -> Int; - auto getMixedComposition() -> Int; auto setMixedComposition(Int amount) -> bool; auto voidResidue() const -> Int; - auto getVoidResidue() -> Int; auto setVoidResidue(Int amount) -> bool; auto muscularConstruction() const -> Int; - auto getMuscularConstruction() -> Int; auto setMuscularConstruction(Int amount) -> bool; auto mineralExoskeletology() const -> Int; - auto getMineralExoskeletology() -> Int; auto setMineralExoskeletology(Int amount) -> bool; - auto carbonizedSkin() const -> Int; - auto getCarbonizedSkin() -> Int; - auto setCarbonizedSkin(Int amount) -> bool; + auto carbonisedSkin() const -> Int; + auto setCarbonisedSkin(Int amount) -> bool; private: - std::string _profileDirectory; + auto getResource(const char* container, MaterialID id) -> Int; + auto setResource(const char* container, MaterialID id, Int amount) -> bool; + std::string _filename; ProfileType _type; + ProfileVersion _version; - std::string _steamId; + UESaveFile _profile; + + std::string _name; + Int _activeFrameSlot = 0; + Int _credits = 0; + Int _storyProgress = 0; + Int _lastMissionId = 0; + + Int _verseSteel = 0; + Int _undinium = 0; + Int _necriumAlloy = 0; + Int _lunarite = 0; + Int _asterite = 0; + + Int _ednil = 0; + Int _nuflalt = 0; + Int _aurelene = 0; + Int _soldus = 0; + Int _synthesisedN = 0; + + Int _alcarbonite = 0; + Int _keriphene = 0; + Int _nitinolCM = 0; + Int _quarkium = 0; + Int _alterene = 0; + + Int _mixedComposition = 0; + Int _voidResidue = 0; + Int _muscularConstruction = 0; + Int _mineralExoskeletology = 0; + Int _carbonisedSkin = 0; + + std::string _account; bool _valid = false; std::string _lastError; - - std::string _companyName; - - Int _activeFrameSlot = 0; - - Int _credits; - - Int _storyProgress; - - Int _lastMissionId; - - Int _verseSteel; - Int _undinium; - Int _necriumAlloy; - Int _lunarite; - Int _asterite; - Int _ednil; - Int _nuflalt; - Int _aurelene; - Int _soldus; - Int _synthesizedN; - Int _alcarbonite; - Int _keriphene; - Int _nitinolCM; - Int _quarkium; - Int _alterene; - - Int _mixedComposition; - Int _voidResidue; - Int _muscularConstruction; - Int _mineralExoskeletology; - Int _carbonizedSkin; }; diff --git a/src/Profile/ResourceIDs.h b/src/Profile/ResourceIDs.h new file mode 100644 index 0000000..d122e23 --- /dev/null +++ b/src/Profile/ResourceIDs.h @@ -0,0 +1,47 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Magnum; + +enum MaterialID : Int { + VerseSteel = 0xC3500, + Undinium = 0xC3501, + NecriumAlloy = 0xC3502, + Lunarite = 0xC3503, + Asterite = 0xC3504, + + Ednil = 0xC350A, + Nuflalt = 0xC350B, + Aurelene = 0xC350C, + Soldus = 0xC350D, + SynthesisedN = 0xC350E, + + Alcarbonite = 0xC3514, + Keriphene = 0xC3515, + NitinolCM = 0xC3516, + Quarkium = 0xC3517, + Alterene = 0xC3518, + + MixedComposition = 0xDBBA0, + VoidResidue = 0xDBBA1, + MuscularConstruction = 0xDBBA2, + MineralExoskeletology = 0xDBBA3, + CarbonisedSkin = 0xDBBA4 +}; diff --git a/src/ProfileManager/ProfileManager.cpp b/src/ProfileManager/ProfileManager.cpp index b7124e7..38d69cf 100644 --- a/src/ProfileManager/ProfileManager.cpp +++ b/src/ProfileManager/ProfileManager.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -31,8 +31,6 @@ #include "ProfileManager.h" -using namespace Corrade; - ProfileManager::ProfileManager(const std::string& save_dir, const std::string& backup_dir): _saveDirectory{save_dir}, _backupsDirectory{backup_dir} @@ -48,12 +46,12 @@ auto ProfileManager::lastError() -> std::string const& { return _lastError; } -auto ProfileManager::profiles() -> std::vector const& { +auto ProfileManager::profiles() -> Containers::ArrayView { return _profiles; } auto ProfileManager::refreshProfiles() -> bool { - _profiles.clear(); + _profiles = Containers::Array{}; using Utility::Directory::Flag; std::vector files = Utility::Directory::list(_saveDirectory, Flag::SkipSpecial|Flag::SkipDirectories|Flag::SkipDotAndDotDot); @@ -74,11 +72,11 @@ auto ProfileManager::refreshProfiles() -> bool { continue; } - _profiles.push_back(std::move(profile)); + arrayAppend(_profiles, std::move(profile)); } if(_profiles.empty()) { - _lastError = "No profiles were found."; + _lastError = "No valid profiles were found."; return false; } @@ -86,14 +84,14 @@ auto ProfileManager::refreshProfiles() -> bool { } auto ProfileManager::getProfile(std::size_t index) -> Profile* { - return &(_profiles.at(index)); + return index <= _profiles.size() ? &(_profiles[index]) : nullptr; } auto ProfileManager::deleteProfile(std::size_t index, bool delete_builds) -> bool { - if(!Utility::Directory::rm(Utility::Directory::join(_saveDirectory, _profiles.at(index).filename()))) { + if(!Utility::Directory::rm(Utility::Directory::join(_saveDirectory, _profiles[index].filename()))) { _lastError = Utility::formatString("Couldn't delete {} (filename: {}).", - _profiles.at(index).companyName(), - _profiles.at(index).filename()); + _profiles[index].companyName(), + _profiles[index].filename()); refreshProfiles(); return false; } @@ -101,13 +99,18 @@ auto ProfileManager::deleteProfile(std::size_t index, bool delete_builds) -> boo if(delete_builds) { for(UnsignedByte i = 0; i < 32; ++i) { std::string filename = Utility::formatString("{}Unit{:.2d}{}.sav", - _profiles.at(index).type() == ProfileType::Demo ? "Demo": "", - i, _profiles.at(index).steamId()); + _profiles[index].type() == ProfileType::Demo ? "Demo": "", + i, _profiles[index].account()); Utility::Directory::rm(Utility::Directory::join(_saveDirectory, filename)); } } - _profiles.erase(_profiles.cbegin() + index); + std::string file = _profiles[index].filename(); + auto it = std::remove_if(_profiles.begin(), _profiles.end(), [&file](Profile& profile){return profile.filename() == file;}); + + if(it != _profiles.end()) { + arrayRemoveSuffix(_profiles, 1); + } return true; } @@ -117,7 +120,7 @@ auto ProfileManager::backupProfile(std::size_t index, bool backup_builds) -> boo std::tm* time = std::localtime(×tamp); std::string filename = Utility::formatString("{}_{}{:.2d}{:.2d}_{:.2d}{:.2d}{:.2d}.mbprofbackup", - Utility::String::replaceAll(_profiles.at(index).companyName(), " ", "_"), + Utility::String::replaceAll(_profiles[index].companyName(), " ", "_"), time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); @@ -130,21 +133,21 @@ auto ProfileManager::backupProfile(std::size_t index, bool backup_builds) -> boo return false; } - zip_source_t* profile_source = zip_source_file(zip, Utility::Directory::toNativeSeparators(Utility::Directory::join(_saveDirectory, _profiles.at(index).filename())).c_str(), 0, 0); + zip_source_t* profile_source = zip_source_file(zip, Utility::Directory::toNativeSeparators(Utility::Directory::join(_saveDirectory, _profiles[index].filename())).c_str(), 0, 0); if(profile_source == nullptr) { _lastError = zip_strerror(zip); zip_source_free(profile_source); return false; } - if(zip_file_add(zip, _profiles.at(index).filename().c_str(), profile_source, ZIP_FL_ENC_UTF_8) == -1) { + if(zip_file_add(zip, _profiles[index].filename().c_str(), profile_source, ZIP_FL_ENC_UTF_8) == -1) { _lastError = zip_strerror(zip); zip_source_free(profile_source); return false; } - std::string comment = Utility::String::join({_profiles.at(index).companyName(), - _profiles.at(index).type() == ProfileType::Demo ? "demo" : "full", + std::string comment = Utility::String::join({_profiles[index].companyName(), + _profiles[index].type() == ProfileType::Demo ? "demo" : "full", Utility::formatString("{}-{:.2d}-{:.2d}-{:.2d}-{:.2d}-{:.2d}", time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec) @@ -154,8 +157,8 @@ auto ProfileManager::backupProfile(std::size_t index, bool backup_builds) -> boo if(backup_builds) { for(UnsignedByte i = 0; i < 32; ++i) { std::string build_filename = Utility::formatString("{}Unit{:.2d}{}.sav", - _profiles.at(index).type() == ProfileType::Demo ? "Demo": "", - i, _profiles.at(index).steamId()); + _profiles[index].type() == ProfileType::Demo ? "Demo": "", + i, _profiles[index].account()); if(!Utility::Directory::exists(Utility::Directory::join(_saveDirectory, build_filename))) { continue; @@ -184,12 +187,12 @@ auto ProfileManager::backupProfile(std::size_t index, bool backup_builds) -> boo return true; } -auto ProfileManager::backups() -> std::vector const& { +auto ProfileManager::backups() -> Containers::ArrayView { return _backups; } void ProfileManager::refreshBackups() { - _backups.clear(); + _backups = Containers::Array{}; using Utility::Directory::Flag; std::vector files = Utility::Directory::list(_backupsDirectory, Flag::SkipSpecial|Flag::SkipDirectories|Flag::SkipDotAndDotDot); @@ -255,29 +258,34 @@ void ProfileManager::refreshBackups() { continue; } - backup.includedFiles.reserve(num_entries); + arrayReserve(backup.includedFiles, num_entries); for(Long i = 0; i < num_entries; i++) { - backup.includedFiles.emplace_back(zip_get_name(zip, i, ZIP_FL_UNCHANGED)); + arrayAppend(backup.includedFiles, InPlaceInit, zip_get_name(zip, i, ZIP_FL_UNCHANGED)); } - _backups.push_back(std::move(backup)); + arrayAppend(_backups, std::move(backup)); } } auto ProfileManager::deleteBackup(std::size_t index) -> bool { - if(!Utility::Directory::rm(Utility::Directory::join(_backupsDirectory, _backups.at(index).filename))) { - _lastError = "Couldn't delete " + _backups.at(index).filename; + if(!Utility::Directory::rm(Utility::Directory::join(_backupsDirectory, _backups[index].filename))) { + _lastError = "Couldn't delete " + _backups[index].filename; return false; } - _backups.erase(_backups.begin() + index); + std::string file = _backups[index].filename; + auto it = std::remove_if(_backups.begin(), _backups.end(), [&file](Backup& backup){return backup.filename == file;}); + + if(it != _backups.end()) { + arrayRemoveSuffix(_backups, 1); + } return true; } auto ProfileManager::restoreBackup(std::size_t index) -> bool { - const Backup& backup = _backups.at(index); + const Backup& backup = _backups[index]; static const char* error_format = "Extraction of file {} failed: {}"; diff --git a/src/ProfileManager/ProfileManager.h b/src/ProfileManager/ProfileManager.h index d1d47b6..db9887a 100644 --- a/src/ProfileManager/ProfileManager.h +++ b/src/ProfileManager/ProfileManager.h @@ -1,7 +1,7 @@ #pragma once // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -17,10 +17,13 @@ // along with this program. If not, see . #include -#include + +#include #include "../Profile/Profile.h" +using namespace Corrade; + struct Backup { std::string filename; std::string company; @@ -33,7 +36,7 @@ struct Backup { int minute; int second; } timestamp; - std::vector includedFiles; + Containers::Array includedFiles; }; class ProfileManager { @@ -43,14 +46,14 @@ class ProfileManager { auto ready() const -> bool; auto lastError() -> std::string const&; - auto profiles() -> std::vector const&; + auto profiles() -> Containers::ArrayView; auto refreshProfiles() -> bool; auto getProfile(std::size_t index) -> Profile*; auto deleteProfile(std::size_t index, bool delete_builds) -> bool; auto backupProfile(std::size_t index, bool backup_builds) -> bool; - auto backups() -> std::vector const&; + auto backups() -> Containers::ArrayView; void refreshBackups(); auto deleteBackup(std::size_t index) -> bool; @@ -63,6 +66,6 @@ class ProfileManager { const std::string& _saveDirectory; const std::string& _backupsDirectory; - std::vector _profiles; - std::vector _backups; + Containers::Array _profiles; + Containers::Array _backups; }; diff --git a/src/SaveTool/SaveTool.cpp b/src/SaveTool/SaveTool.cpp index f00fa1f..3b2a23f 100644 --- a/src/SaveTool/SaveTool.cpp +++ b/src/SaveTool/SaveTool.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -49,9 +49,6 @@ extern const ImVec2 center_pivot = {0.5f, 0.5f}; #ifdef SAVETOOL_DEBUG_BUILD -#include - -#define tw CORRADE_TWEAKABLE Utility::Tweakable tweak; #endif @@ -65,7 +62,7 @@ SaveTool::SaveTool(const Arguments& arguments): #endif if(SDL_VERSION_ATLEAST(2, 0, 5)) { - if(SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1") == SDL_TRUE) { + if(SDL_SetHintWithPriority(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1", SDL_HINT_OVERRIDE) == SDL_TRUE) { Utility::Debug{} << "Clickthrough is available."; } else { @@ -98,6 +95,10 @@ SaveTool::SaveTool(const Arguments& arguments): _backupsDir = Utility::Directory::join(Utility::Directory::path(Utility::Directory::executableLocation()), "backups"); _stagingDir = Utility::Directory::join(Utility::Directory::path(Utility::Directory::executableLocation()), "staging"); + _armouryDir = Utility::Directory::join(Utility::Directory::path(Utility::Directory::executableLocation()), "armoury"); + _armoursDir = Utility::Directory::join(_armouryDir, "armours"); + _weaponsDir = Utility::Directory::join(_armouryDir, "weapons"); + _stylesDir = Utility::Directory::join(_armouryDir, "styles"); if(!Utility::Directory::exists(_backupsDir)) { Utility::Directory::mkpath(_backupsDir); @@ -107,6 +108,22 @@ SaveTool::SaveTool(const Arguments& arguments): Utility::Directory::mkpath(_stagingDir); } + if(!Utility::Directory::exists(_armouryDir)) { + Utility::Directory::mkpath(_armouryDir); + } + + if(!Utility::Directory::exists(_armoursDir)) { + Utility::Directory::mkpath(_armoursDir); + } + + if(!Utility::Directory::exists(_weaponsDir)) { + Utility::Directory::mkpath(_weaponsDir); + } + + if(!Utility::Directory::exists(_stylesDir)) { + Utility::Directory::mkpath(_stylesDir); + } + if(!findGameDataDirectory()) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app", _lastError.c_str(), window()); exit(EXIT_FAILURE); @@ -206,22 +223,31 @@ void SaveTool::handleFileAction(efsw::WatchID watch_id, return; } + static bool is_moved_after_save = false; + switch(action) { case efsw::Actions::Add: - if(Utility::String::endsWith(filename, _currentProfile->steamId() + ".sav")) { + if(Utility::String::endsWith(filename, _currentProfile->account() + ".sav")) { if(Utility::String::beginsWith(filename, Utility::formatString("{}Unit", _currentProfile->type() == ProfileType::Demo ? "Demo" : ""))) { int index = ((filename[_currentProfile->type() == ProfileType::Demo ? 8 : 4] - 0x30) * 10) + (filename[_currentProfile->type() == ProfileType::Demo ? 9 : 5] - 0x30); - _massManager->refreshHangar(index); + if(!_currentMass || _currentMass != &(_massManager->hangar(index))) { + _massManager->refreshHangar(index); + } + else { + _currentMass->setDirty(); + } } } break; case efsw::Actions::Delete: - if(Utility::String::endsWith(filename, _currentProfile->steamId() + ".sav")) { + if(Utility::String::endsWith(filename, _currentProfile->account() + ".sav")) { if(Utility::String::beginsWith(filename, Utility::formatString("{}Unit", _currentProfile->type() == ProfileType::Demo ? "Demo" : ""))) { int index = ((filename[_currentProfile->type() == ProfileType::Demo ? 8 : 4] - 0x30) * 10) + (filename[_currentProfile->type() == ProfileType::Demo ? 9 : 5] - 0x30); - _massManager->refreshHangar(index); + if(!_currentMass || _currentMass != &(_massManager->hangar(index))) { + _massManager->refreshHangar(index); + } } } break; @@ -229,20 +255,37 @@ void SaveTool::handleFileAction(efsw::WatchID watch_id, if(filename == _currentProfile->filename()) { _currentProfile->refreshValues(); } - else if(Utility::String::endsWith(filename, _currentProfile->steamId() + ".sav")) { + else if(Utility::String::endsWith(filename, _currentProfile->account() + ".sav")) { if(Utility::String::beginsWith(filename, Utility::formatString("{}Unit", _currentProfile->type() == ProfileType::Demo ? "Demo" : ""))) { int index = ((filename[_currentProfile->type() == ProfileType::Demo ? 8 : 4] - 0x30) * 10) + (filename[_currentProfile->type() == ProfileType::Demo ? 9 : 5] - 0x30); - _massManager->refreshHangar(index); + + if(!_currentMass || _currentMass != &(_massManager->hangar(index))) { + _massManager->refreshHangar(index); + } + else { + if(!is_moved_after_save) { + is_moved_after_save = false; + _currentMass->setDirty(); + } + } } } break; case efsw::Actions::Moved: - if(Utility::String::endsWith(filename, _currentProfile->steamId() + ".sav")) { - if(Utility::String::beginsWith(filename, Utility::formatString("{}Unit", _currentProfile->type() == ProfileType::Demo ? "Demo" : ""))) { + if(Utility::String::endsWith(filename, _currentProfile->account() + ".sav")) { + if(Utility::String::endsWith(old_filename, ".tmp")) { + is_moved_after_save = true; + return; + } + + if(Utility::String::beginsWith(filename, Utility::formatString("{}Unit", _currentProfile->type() == ProfileType::Demo ? "Demo" : "")) && + Utility::String::endsWith(old_filename, ".sav")) + { int index = ((filename[_currentProfile->type() == ProfileType::Demo ? 8 : 4] - 0x30) * 10) + (filename[_currentProfile->type() == ProfileType::Demo ? 9 : 5] - 0x30); _massManager->refreshHangar(index); + int old_index = ((old_filename[_currentProfile->type() == ProfileType::Demo ? 8 : 4] - 0x30) * 10) + (old_filename[_currentProfile->type() == ProfileType::Demo ? 9 : 5] - 0x30); _massManager->refreshHangar(old_index); @@ -376,24 +419,36 @@ void SaveTool::updateCheckEvent(SDL_Event& event) { return ( major * 10000 + minor * 100 + patch) > (other.major * 10000 + other.minor * 100 + other.patch); } + operator std::string() const { + return Utility::formatString("{}.{}.{}", major, minor, patch); + } }; static const Version current_ver{SAVETOOL_VERSION}; - Version latest_ver{response[0]["tag_name"]}; - if(latest_ver == current_ver) { - _queue.addToast(Toast::Type::Success, "The application is already up to date."); - } - else if(latest_ver > current_ver) { - _queue.addToast(Toast::Type::Warning, "Your version is out of date.\nCheck the settings for more information.", - std::chrono::milliseconds{5000}); - _updateAvailable = true; - _latestVersion = Utility::formatString("{}.{}.{}", latest_ver.major, latest_ver.minor, latest_ver.patch); - _releaseLink = response[0]["html_url"]; - _downloadLink = response[0]["assets"][0]["browser_download_url"]; - } - else if(current_ver > latest_ver) { - _queue.addToast(Toast::Type::Warning, "Your version is more recent than the latest one in the repo. How???"); + for(auto& release : response) { + if(release["prerelease"] == true) { + continue; + } + + Version latest_ver{release["tag_name"]}; + + if(latest_ver > current_ver || (latest_ver == current_ver && Utility::String::endsWith(SAVETOOL_VERSION, "-pre"))) { + _queue.addToast(Toast::Type::Warning, "Your version is out of date.\nCheck the settings for more information.", + std::chrono::milliseconds{5000}); + _updateAvailable = true; + _latestVersion = latest_ver; + _releaseLink = release["html_url"]; + _downloadLink = release["assets"][0]["browser_download_url"]; + } + else if(latest_ver == current_ver || (current_ver > latest_ver && Utility::String::endsWith(SAVETOOL_VERSION, "-pre"))) { + _queue.addToast(Toast::Type::Success, "The application is already up to date."); + } + else if(current_ver > latest_ver) { + _queue.addToast(Toast::Type::Warning, "Your version is more recent than the latest one in the repo. How???"); + } + + break; } } @@ -526,10 +581,8 @@ auto SaveTool::findGameDataDirectory() -> bool { } void SaveTool::initialiseMassManager() { - _currentProfile->refreshValues(); - _massManager.emplace(_saveDir, - _currentProfile->steamId(), + _currentProfile->account(), _currentProfile->type() == ProfileType::Demo, _stagingDir); @@ -576,6 +629,9 @@ void SaveTool::drawGui() { case UiState::MainManager: drawManager(); break; + case UiState::MassViewer: + drawMassViewer(); + break; } if(_aboutPopup) { @@ -712,7 +768,7 @@ void SaveTool::drawTooltip(const char* text, Float wrap_pos) { } void SaveTool::openUri(const std::string& uri) { - ShellExecuteW(nullptr, nullptr, Utility::Unicode::widen(uri).c_str(), nullptr, nullptr, SW_SHOW); + ShellExecuteW(nullptr, nullptr, Utility::Unicode::widen(uri).c_str(), nullptr, nullptr, SW_SHOWDEFAULT); } void SaveTool::checkGameState() { @@ -738,8 +794,7 @@ void SaveTool::checkGameState() { } void SaveTool::checkForUpdates() { - cpr::Response r = cpr::Get(cpr::Url{"https://williamjcm.ovh/git/api/v1/repos/williamjcm/MassBuilderSaveTool/releases"}, - cpr::Parameters{{"limit", "1"}}, cpr::Timeout{10000}); + cpr::Response r = cpr::Get(cpr::Url{"https://williamjcm.ovh/git/api/v1/repos/williamjcm/MassBuilderSaveTool/releases"}, cpr::Timeout{10000}); SDL_Event event; SDL_zero(event); diff --git a/src/SaveTool/SaveTool.h b/src/SaveTool/SaveTool.h index f50f6cf..b871df8 100644 --- a/src/SaveTool/SaveTool.h +++ b/src/SaveTool/SaveTool.h @@ -1,7 +1,7 @@ #pragma once // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include @@ -36,6 +36,12 @@ #include "../MassManager/MassManager.h" #include "../ToastQueue/ToastQueue.h" +#ifdef SAVETOOL_DEBUG_BUILD +#include + +#define tw CORRADE_TWEAKABLE +#endif + using namespace Corrade; using namespace Magnum; @@ -88,18 +94,48 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener void drawMainMenu(); void drawDisclaimer(); void drawInitialisation(); + void drawProfileManager(); auto drawBackupListPopup() -> ImGuiID; auto drawBackupProfilePopup(std::size_t profile_index) -> ImGuiID; auto drawDeleteProfilePopup(std::size_t profile_index) -> ImGuiID; + void drawManager(); auto drawIntEditPopup(int* value_to_edit, int max) -> bool; auto drawRenamePopup(Containers::ArrayView name_view) -> bool; void drawGeneralInfo(); void drawResearchInventory(); + template + void drawMaterialRow(const char* name, Int tier, Getter getter, Setter setter); + void drawUnavailableMaterialRow(const char* name, Int tier); void drawMassManager(); auto drawDeleteMassPopup(int mass_index) -> ImGuiID; auto drawDeleteStagedMassPopup(const std::string& filename) -> ImGuiID; + + void drawMassViewer(); + void drawFrameInfo(); + void drawJointSliders(); + void drawFrameStyles(); + void drawEyeColourPicker(); + void drawCustomFrameStyles(); + void drawArmour(); + void drawCustomArmourStyles(); + void drawWeapons(); + void drawWeaponCategory(const char* name, Containers::ArrayView weapons_view, bool& dirty, const char* payload_type, const char* payload_tooltip); + void drawWeaponEditor(Weapon& weapon); + void drawGlobalStyles(); + void drawTuning(); + void drawDecalEditor(Decal& decal); + void drawAccessoryEditor(Accessory& accessory, Containers::ArrayView style_view); + auto getStyleName(Int id, Containers::ArrayView view) -> const char*; + + enum DCSResult { + DCS_Fail, + DCS_ResetStyle, + DCS_Save + }; + auto drawCustomStyle(CustomStyle& style) -> DCSResult; + void drawAbout(); void drawGameState(); @@ -136,6 +172,12 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener } } + template + void drawAlignedText(const char* text, Args... args) { + ImGui::AlignTextToFramePadding(); + ImGui::Text(text, std::forward(args)...); + } + void openUri(const std::string& uri); void checkGameState(); @@ -152,7 +194,8 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener Disclaimer, Initialising, ProfileManager, - MainManager + MainManager, + MassViewer } _uiState{UiState::Disclaimer}; bool _aboutPopup{false}; @@ -179,6 +222,10 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener std::string _backupsDir; std::string _stagingDir; + std::string _armouryDir; + std::string _armoursDir; + std::string _weaponsDir; + std::string _stylesDir; enum class GameState : UnsignedByte { Unknown, NotRunning, Running @@ -190,6 +237,9 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener Profile* _currentProfile{nullptr}; Containers::Pointer _massManager; + Mass* _currentMass{nullptr}; + + Weapon* _currentWeapon = nullptr; Containers::Pointer _fileWatcher; enum watchID { @@ -214,5 +264,20 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener std::string _releaseLink; std::string _downloadLink; + bool _jointsDirty{false}; + bool _stylesDirty{false}; + bool _eyeFlareDirty{false}; + Containers::StaticArray<38, Int> _selectedArmourDecals{ValueInit}; + Containers::StaticArray<38, Int> _selectedArmourAccessories{ValueInit}; + Int _selectedWeaponPart{0}; + Int _selectedWeaponDecal{0}; + Int _selectedWeaponAccessory{0}; + bool _meleeDirty{false}; + bool _shieldsDirty{false}; + bool _bShootersDirty{false}; + bool _eShootersDirty{false}; + bool _bLaunchersDirty{false}; + bool _eLaunchersDirty{false}; + bool _cheatMode{false}; }; diff --git a/src/SaveTool/SaveTool_MainManager.cpp b/src/SaveTool/SaveTool_MainManager.cpp index 747aea1..d949984 100644 --- a/src/SaveTool/SaveTool_MainManager.cpp +++ b/src/SaveTool/SaveTool_MainManager.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -42,10 +42,9 @@ void SaveTool::drawManager() { return; } - ImGui::AlignTextToFramePadding(); - ImGui::Text("Current profile: %s (%s)", - _currentProfile->companyName().c_str(), - _currentProfile->type() == ProfileType::Demo ? "demo" : "full game"); + drawAlignedText("Current profile: %s (%s)", + _currentProfile->companyName().c_str(), + _currentProfile->type() == ProfileType::Demo ? "demo" : "full game"); ImGui::SameLine(); if(ImGui::Button(ICON_FA_ARROW_LEFT " Back to profile manager")) { _currentProfile = nullptr; @@ -107,8 +106,8 @@ auto SaveTool::drawIntEditPopup(int* value_to_edit, int max) -> bool { drawHelpMarker("You can either drag the widget left or right to change the value,\n" "or click on it while holding Ctrl to edit the value directly."); ImGui::SameLine(); - drawUnsafeWidget([](auto... args){ return ImGui::DragInt("", args...); }, - value_to_edit, 1.0f, 0, max, "%d", ImGuiSliderFlags_AlwaysClamp); + drawUnsafeWidget([](auto... args){ return ImGui::SliderInt("", args...); }, + value_to_edit, 0, max, "%d", ImGuiSliderFlags_AlwaysClamp); ImGui::SameLine(); if(drawUnsafeWidget([]{ return ImGui::Button("Apply"); })) { apply = true; @@ -188,7 +187,7 @@ void SaveTool::drawGeneralInfo() { { ImGui::TextUnformatted("Story progress:"); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.x / 4.0f); - if(std::strcmp(it->after, "") == 0) { + if(!it->after) { ImGui::TextWrapped("%s - %s", it->chapter, it->point); } else { @@ -226,8 +225,7 @@ void SaveTool::drawGeneralInfo() { } if(drawRenamePopup(name_buf)) { if(!_currentProfile->renameCompany(name_buf.data())) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", - _currentProfile->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _currentProfile->lastError()); } } @@ -244,8 +242,7 @@ void SaveTool::drawGeneralInfo() { } if(drawIntEditPopup(&credits, 20000000)) { if(!_currentProfile->setCredits(credits)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", - _currentProfile->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _currentProfile->lastError()); } } @@ -261,11 +258,10 @@ void SaveTool::drawGeneralInfo() { } for(const auto& sp : story_progress) { if(ImGui::BeginMenu(sp.chapter)) { - if(std::strcmp(sp.after, "") == 0) { + if(!sp.after) { if(ImGui::MenuItem(sp.point)) { if(!_currentProfile->setStoryProgress(sp.id)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", - _currentProfile->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _currentProfile->lastError()); } } } @@ -273,8 +269,7 @@ void SaveTool::drawGeneralInfo() { if(ImGui::BeginMenu(sp.after)) { if(ImGui::MenuItem(sp.point)) { if(!_currentProfile->setStoryProgress(sp.id)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", - _currentProfile->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _currentProfile->lastError()); } } ImGui::EndMenu(); @@ -292,45 +287,6 @@ void SaveTool::drawResearchInventory() { return; } - #define unavRow(name, tier) \ - ImGui::TableNextRow(); \ - ImGui::TableSetColumnIndex(0); \ - ImGui::TextUnformatted("T" #tier); \ - ImGui::TableSetColumnIndex(1); \ - ImGui::TextUnformatted(name); \ - ImGui::TableSetColumnIndex(2); \ - ImGui::TextDisabled("Unavailable as of game version " SUPPORTED_GAME_VERSION); - - #define matRow(name, tier, var, getter, setter) \ - ImGui::TableNextRow(); \ - ImGui::TableSetColumnIndex(0); \ - ImGui::TextUnformatted("T" #tier); \ - ImGui::TableSetColumnIndex(1); \ - ImGui::TextUnformatted(name); \ - ImGui::TableSetColumnIndex(2); \ - if(_currentProfile->getter() != -1) { \ - ImGui::Text("%i", _currentProfile->getter()); \ - if(_cheatMode) { \ - ImGui::TableSetColumnIndex(3); \ - ImGui::PushID(#setter); \ - static Int var = _currentProfile->getter(); \ - if(drawUnsafeWidget([]{ return ImGui::SmallButton(ICON_FA_EDIT); })) { \ - (var) = _currentProfile->getter(); \ - ImGui::OpenPopup("int_edit"); \ - } \ - if(drawIntEditPopup(&(var), 9999)) { \ - if(!_currentProfile->set##setter((var))) { \ - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", \ - _currentProfile->lastError().c_str(), window()); \ - } \ - } \ - ImGui::PopID(); \ - } \ - } \ - else { \ - ImGui::TextDisabled("Not found in the save file"); \ - } - if(ImGui::BeginTable("##ResearchInventoryTable", 4, ImGuiTableFlags_BordersOuter|ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersInnerH)) { @@ -343,55 +299,136 @@ void SaveTool::drawResearchInventory() { ImGui::TableSetColumnIndex(1); ImGui::Text("Engine materials"); - matRow("Verse steel", 1, verse_steel, verseSteel, VerseSteel) - matRow("Undinium", 2, undinium, undinium, Undinium) - matRow("Necrium alloy", 3, necrium_alloy, necriumAlloy, NecriumAlloy) - matRow("Lunarite", 4, lunarite, lunarite, Lunarite) - matRow("Asterite", 5, asterite, asterite, Asterite) - unavRow("Hallite fragma", 6) - unavRow("Unnoctinium", 7) + drawMaterialRow("Verse steel", 1, + [this]{ return _currentProfile->verseSteel(); }, + [this](Int amount){ return _currentProfile->setVerseSteel(amount); }); + drawMaterialRow("Undinium", 2, + [this]{ return _currentProfile->undinium(); }, + [this](Int amount){ return _currentProfile->setUndinium(amount); }); + drawMaterialRow("Necrium alloy", 3, + [this]{ return _currentProfile->necriumAlloy(); }, + [this](Int amount){ return _currentProfile->setNecriumAlloy(amount); }); + drawMaterialRow("Lunarite", 4, + [this]{ return _currentProfile->lunarite(); }, + [this](Int amount){ return _currentProfile->setLunarite(amount); }); + drawMaterialRow("Asterite", 5, + [this]{ return _currentProfile->asterite(); }, + [this](Int amount){ return _currentProfile->setAsterite(amount); }); + drawUnavailableMaterialRow("Hallite fragma", 6); + drawUnavailableMaterialRow("Unnoctinium", 7); ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableSetColumnIndex(1); ImGui::Text("OS materials"); - matRow("Ednil", 1, ednil, ednil, Ednil) - matRow("Nuflalt", 2, nuflalt, nuflalt, Nuflalt) - matRow("Aurelene", 3, aurelene, aurelene, Aurelene) - matRow("Soldus", 4, soldus, soldus, Soldus) - matRow("Synthesized N", 5, synthesized_n, synthesizedN, SynthesizedN) - unavRow("Nanoc", 6) - unavRow("Abyssillite", 7) + drawMaterialRow("Ednil", 1, + [this]{ return _currentProfile->ednil(); }, + [this](Int amount){ return _currentProfile->setEdnil(amount); }); + drawMaterialRow("Nuflalt", 2, + [this]{ return _currentProfile->nuflalt(); }, + [this](Int amount){ return _currentProfile->setNuflalt(amount); }); + drawMaterialRow("Aurelene", 3, + [this]{ return _currentProfile->aurelene(); }, + [this](Int amount){ return _currentProfile->setAurelene(amount); }); + drawMaterialRow("Soldus", 4, + [this]{ return _currentProfile->soldus(); }, + [this](Int amount){ return _currentProfile->setSoldus(amount); }); + drawMaterialRow("Synthesized N", 5, + [this]{ return _currentProfile->synthesisedN(); }, + [this](Int amount){ return _currentProfile->setSynthesisedN(amount); }); + drawUnavailableMaterialRow("Nanoc", 6); + drawUnavailableMaterialRow("Abyssillite", 7); ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableSetColumnIndex(1); ImGui::Text("Architect materials"); - matRow("Alcarbonite", 1, alcarbonite, alcarbonite, Alcarbonite) - matRow("Keriphene", 2, keriphene, keriphene, Keriphene) - matRow("Nitinol-CM", 3, nitinol_cm, nitinolCM, NitinolCM) - matRow("Quarkium", 4, quarkium, quarkium, Quarkium) - matRow("Alterene", 5, alterene, alterene, Alterene) - unavRow("Cosmium", 6) - unavRow("Purified quarkium", 7) + drawMaterialRow("Alcarbonite", 1, + [this]{ return _currentProfile->alcarbonite(); }, + [this](Int amount){ return _currentProfile->setAlcarbonite(amount); }); + drawMaterialRow("Keripehene", 2, + [this]{ return _currentProfile->keriphene(); }, + [this](Int amount){ return _currentProfile->setKeriphene(amount); }); + drawMaterialRow("Nitinol-CM", 3, + [this]{ return _currentProfile->nitinolCM(); }, + [this](Int amount){ return _currentProfile->setNitinolCM(amount); }); + drawMaterialRow("Quarkium", 4, + [this]{ return _currentProfile->quarkium(); }, + [this](Int amount){ return _currentProfile->setQuarkium(amount); }); + drawMaterialRow("Alterene", 5, + [this]{ return _currentProfile->alterene(); }, + [this](Int amount){ return _currentProfile->setAlterene(amount); }); + drawUnavailableMaterialRow("Cosmium", 6); + drawUnavailableMaterialRow("Purified quarkium", 7); ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableSetColumnIndex(1); ImGui::Text("Quark data"); - matRow("Mixed composition", 1, mixed_composition, mixedComposition, MixedComposition) - matRow("Void residue", 2, void_residue, voidResidue, VoidResidue) - matRow("Muscular construction", 3, muscular_construction, muscularConstruction, MuscularConstruction) - matRow("Mineral exoskeletology", 4, mineral_exoskeletology, mineralExoskeletology, MineralExoskeletology) - matRow("Carbonized skin", 5, carbonized_skin, carbonizedSkin, CarbonizedSkin) - unavRow("Isolated void particle", 6) - unavRow("Weaponised physiology", 7) + drawMaterialRow("Mixed composition", 1, + [this]{ return _currentProfile->mixedComposition(); }, + [this](Int amount){ return _currentProfile->setMixedComposition(amount); }); + drawMaterialRow("Void residue", 2, + [this]{ return _currentProfile->voidResidue(); }, + [this](Int amount){ return _currentProfile->setVoidResidue(amount); }); + drawMaterialRow("Muscular construction", 3, + [this]{ return _currentProfile->muscularConstruction(); }, + [this](Int amount){ return _currentProfile->setMuscularConstruction(amount); }); + drawMaterialRow("Mineral exoskeletology", 4, + [this]{ return _currentProfile->mineralExoskeletology(); }, + [this](Int amount){ return _currentProfile->setMineralExoskeletology(amount); }); + drawMaterialRow("Carbonized skin", 5, + [this]{ return _currentProfile->carbonisedSkin(); }, + [this](Int amount){ return _currentProfile->setCarbonisedSkin(amount); }); + drawUnavailableMaterialRow("Isolated void particle", 6); + drawUnavailableMaterialRow("Weaponised physiology", 7); ImGui::EndTable(); } +} - #undef unavRow - #undef matRow +template +void SaveTool::drawMaterialRow(const char* name, Int tier, Getter getter, Setter setter) { + static_assert(std::is_same::value, "getter doesn't return an Int, and/or doesn't take zero arguments."); + static_assert(std::is_same::value, "setter doesn't return a bool, and/or doesn't take a single Int as an argument."); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("T%i", tier); + ImGui::TableSetColumnIndex(1); + ImGui::TextUnformatted(name); + ImGui::TableSetColumnIndex(2); + if(getter() != -1) { + ImGui::Text("%i", getter()); + if(_cheatMode) { + ImGui::TableSetColumnIndex(3); + ImGui::PushID(name); + static Int var = 0; + if(drawUnsafeWidget(ImGui::SmallButton, ICON_FA_EDIT)) { + (var) = getter(); + ImGui::OpenPopup("int_edit"); + } + if(drawIntEditPopup(&(var), 9999)) { + if(!setter(var)) { + _queue.addToast(Toast::Type::Error, _currentProfile->lastError()); + } + } + ImGui::PopID(); + } + } + else { + ImGui::TextDisabled("Not found in the save file"); + } +} + +void SaveTool::drawUnavailableMaterialRow(const char* name, Int tier) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("T%i", tier); + ImGui::TableSetColumnIndex(1); + ImGui::TextUnformatted(name); + ImGui::TableSetColumnIndex(2); + ImGui::TextDisabled("Unavailable as of game version " SUPPORTED_GAME_VERSION); } void SaveTool::drawMassManager() { @@ -409,7 +446,7 @@ void SaveTool::drawMassManager() { ImGui::TableSetupColumn("##Hangar", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("##MASSName", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("##Active", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("##DeleteButton", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("##Buttons", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupScrollFreeze(0, 1); @@ -429,17 +466,17 @@ void SaveTool::drawMassManager() { ImGui::TableSetColumnIndex(0); ImGui::Selectable(Utility::formatString("{:.2d}", i + 1).c_str(), false, ImGuiSelectableFlags_SpanAllColumns|ImGuiSelectableFlags_AllowItemOverlap); - if(_massManager->massState(i) == MassState::Valid && + if(_massManager->hangar(i).state() == Mass::State::Valid && ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) { drag_drop_index = i; ImGui::SetDragDropPayload("Mass", &drag_drop_index, sizeof(int)); - ImGui::Text("%s - Hangar %.2d", _massManager->massName(i).c_str(), i + 1); + ImGui::Text("%s - Hangar %.2d", (*_massManager->hangar(i).name()).c_str(), i + 1); ImGui::EndDragDropSource(); } - if((!_unsafeMode && _gameState == GameState::NotRunning) && ImGui::BeginDragDropTarget()) { + if((_unsafeMode || _gameState == GameState::NotRunning) && ImGui::BeginDragDropTarget()) { if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("StagedMass")) { if(payload->DataSize != sizeof(std::string)) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", @@ -451,11 +488,10 @@ void SaveTool::drawMassManager() { std::string file = *(static_cast(payload->Data)); if(!_massManager->importMass(file, i)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error importing M.A.S.S.", - _massManager->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _massManager->lastError()); } } - else if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("Mass")) { + else if((payload = ImGui::AcceptDragDropPayload("Mass"))) { if(payload->DataSize != sizeof(int)) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", "payload->DataSize != sizeof(int) in SaveTool::drawMassManager()", @@ -466,9 +502,7 @@ void SaveTool::drawMassManager() { int index = *(static_cast(payload->Data)); if(!_massManager->moveMass(index, i)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", - _massManager->lastError().c_str(), - window()); + _queue.addToast(Toast::Type::Error, _massManager->lastError()); } } @@ -476,15 +510,15 @@ void SaveTool::drawMassManager() { } ImGui::TableSetColumnIndex(1); - switch(_massManager->massState(i)) { - case MassState::Empty: + switch(_massManager->hangar(i).state()) { + case Mass::State::Empty: ImGui::TextDisabled(""); break; - case MassState::Invalid: + case Mass::State::Invalid: ImGui::TextDisabled(""); break; - case MassState::Valid: - ImGui::TextUnformatted(_massManager->massName(i).c_str()); + case Mass::State::Valid: + ImGui::TextUnformatted((*_massManager->hangar(i).name()).c_str()); break; } @@ -494,9 +528,21 @@ void SaveTool::drawMassManager() { drawTooltip("This is the currently active frame slot."); } - if(_massManager->massState(i) != MassState::Empty) { + if(_massManager->hangar(i).state() != Mass::State::Empty) { ImGui::TableSetColumnIndex(3); ImGui::PushID(i); + if(_massManager->hangar(i).state() == Mass::State::Valid) { + if(ImGui::SmallButton(ICON_FA_SEARCH)) { + _currentMass = &_massManager->hangar(i); + _uiState = UiState::MassViewer; + } + ImGui::SameLine(0.0f, 2.0f); + } + else{ + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.0f); + ImGui::SmallButton(ICON_FA_SEARCH); + ImGui::PopStyleVar(); + } if(drawUnsafeWidget(ImGui::SmallButton, ICON_FA_TRASH_ALT)) { mass_to_delete = i; ImGui::OpenPopup(mass_deletion_popup_ID); @@ -569,9 +615,7 @@ void SaveTool::drawMassManager() { int index = *(static_cast(payload->Data)); if(!_massManager->exportMass(index)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", - _massManager->lastError().c_str(), - window()); + _queue.addToast(Toast::Type::Error, _massManager->lastError()); } } @@ -588,7 +632,7 @@ auto SaveTool::drawDeleteMassPopup(int mass_index) -> ImGuiID { return ImGui::GetID("Confirmation##DeleteMassConfirmation"); } - if(_massManager->massState(mass_index) == MassState::Empty) { + if(_massManager->hangar(mass_index).state() == Mass::State::Empty) { ImGui::CloseCurrentPopup(); ImGui::EndPopup(); return 0; @@ -601,13 +645,13 @@ auto SaveTool::drawDeleteMassPopup(int mass_index) -> ImGuiID { } ImGui::PushTextWrapPos(windowSize().x() * 0.40f); - if(_massManager->massState(mass_index) == MassState::Invalid) { + if(_massManager->hangar(mass_index).state() == Mass::State::Invalid) { ImGui::Text("Are you sure you want to delete the invalid M.A.S.S. data in hangar %.2i ? This operation is irreversible.", mass_index + 1); } else { ImGui::Text("Are you sure you want to delete the M.A.S.S. named %s in hangar %.2i ? This operation is irreversible.", - _massManager->massName(mass_index).c_str(), mass_index + 1); + (*_massManager->hangar(mass_index).name()).c_str(), mass_index + 1); } ImGui::PopTextWrapPos(); @@ -620,8 +664,7 @@ auto SaveTool::drawDeleteMassPopup(int mass_index) -> ImGuiID { ImGui::TableSetColumnIndex(1); if(ImGui::Button("Yes")) { if(!_massManager->deleteMass(mass_index)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error when deleting M.A.S.S.", - _massManager->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _massManager->lastError()); } ImGui::CloseCurrentPopup(); } @@ -659,8 +702,7 @@ auto SaveTool::drawDeleteStagedMassPopup(const std::string& filename) -> ImGuiID ImGui::TableSetColumnIndex(1); if(ImGui::Button("Yes")) { if(!_massManager->deleteStagedMass(filename)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error when deleting M.A.S.S.", - _massManager->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _massManager->lastError()); } ImGui::CloseCurrentPopup(); } diff --git a/src/SaveTool/SaveTool_MassViewer.cpp b/src/SaveTool/SaveTool_MassViewer.cpp new file mode 100644 index 0000000..578b2e9 --- /dev/null +++ b/src/SaveTool/SaveTool_MassViewer.cpp @@ -0,0 +1,607 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../Maps/Accessories.h" + +#define STYLENAMES_DEFINITION +#include "../Maps/StyleNames.h" + +#include "../FontAwesome/IconsFontAwesome5.h" + +#include "SaveTool.h" + +void SaveTool::drawMassViewer() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + _currentMass = nullptr; + _currentWeapon = nullptr; + _uiState = UiState::MainManager; + _queue.addToast(Toast::Type::Error, "The selected M.A.S.S. isn't valid anymore."); + return; + } + + ImGui::SetNextWindowPos({0.0f, ImGui::GetItemRectSize().y}, ImGuiCond_Always); + ImGui::SetNextWindowSize({Float(windowSize().x()), Float(windowSize().y()) - ImGui::GetItemRectSize().y}, + ImGuiCond_Always); + if(!ImGui::Begin("##MassViewer", nullptr, + ImGuiWindowFlags_NoDecoration|ImGuiWindowFlags_NoMove| + ImGuiWindowFlags_NoBackground|ImGuiWindowFlags_NoBringToFrontOnFocus)) + { + ImGui::End(); + return; + } + + if(ImGui::BeginChild("##MassInfo", + {0.0f, 0.0f}, + true, ImGuiWindowFlags_MenuBar)) + { + if(ImGui::BeginMenuBar()) { + if(ImGui::BeginTable("##MassViewerMenuTable", 4)) { + ImGui::TableSetupColumn("##MassName"); + ImGui::TableSetupColumn("##Spacer", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##Updates"); + ImGui::TableSetupColumn("##Close", ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::Text("M.A.S.S.: %s", (*_currentMass->name()).c_str()); + drawTooltip(_currentMass->filename().c_str()); + + ImGui::TableSetColumnIndex(2); + if(_currentMass->dirty()) { + ImGui::TextUnformatted("External changes detected"); + ImGui::SameLine(); + if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh")) { + _currentMass->refreshValues(); + _currentMass->setDirty(false); + _jointsDirty = false; + _stylesDirty = false; + _eyeFlareDirty = false; + } + } + + ImGui::TableSetColumnIndex(3); + if(ImGui::SmallButton(ICON_FA_TIMES)) { + _currentWeapon = nullptr; + _currentMass = nullptr; + _uiState = UiState::MainManager; + _jointsDirty = false; + _stylesDirty = false; + _eyeFlareDirty = false; + _selectedArmourDecals = Containers::StaticArray<38, Int>{ValueInit}; + _selectedArmourAccessories = Containers::StaticArray<38, Int>{ValueInit}; + _selectedWeaponPart = 0; + _selectedWeaponDecal = 0; + _selectedWeaponAccessory = 0; + }; + + ImGui::EndTable(); + } + + ImGui::EndMenuBar(); + } + + ImGui::TextColored(ImColor(255, 255, 0), ICON_FA_EXCLAMATION_TRIANGLE); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextWrapped("WARNING: Colours in this app may look different from in-game colours, due to unavoidable differences in the rendering pipeline."); + ImGui::TextColored(ImColor(255, 255, 0), ICON_FA_EXCLAMATION_TRIANGLE); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextWrapped("Real-time updates are disabled on this screen."); + + if(_currentMass) { + if(ImGui::BeginTabBar("##MassTabBar")) { + if(ImGui::BeginTabItem("Frame")) { + drawFrameInfo(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Custom frame styles")) { + drawCustomFrameStyles(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Armour parts")) { + drawArmour(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Custom armour styles")) { + drawCustomArmourStyles(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Weapons (WIP)")) { + drawWeapons(); + ImGui::EndTabItem(); + } + + if(_currentMass->globalStyles().size() != 0 && ImGui::BeginTabItem("Global styles")) { + drawGlobalStyles(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Tuning (WIP)")) { + drawTuning(); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + } + } + ImGui::EndChild(); + + ImGui::End(); +} + +void SaveTool::drawGlobalStyles() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(!ImGui::BeginChild("##GlobalStyles")) { + ImGui::EndChild(); + return; + } + + ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game."); + + for(UnsignedInt i = 0; i < _currentMass->globalStyles().size(); i++) { + ImGui::PushID(i); + DCSResult result; + result = drawCustomStyle(_currentMass->globalStyles()[i]); + switch(result) { + case DCS_ResetStyle: + _currentMass->getGlobalStyles(); + break; + case DCS_Save: + if(!_currentMass->writeGlobalStyle(i)) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + break; + default: + break; + } + ImGui::PopID(); + } + + ImGui::EndChild(); +} + +void SaveTool::drawTuning() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(!ImGui::BeginTable("##TuningTable", 3)) { + return; + } + + ImGui::TableSetupColumn("##EngineColumn"); + ImGui::TableSetupColumn("##OSColumn"); + ImGui::TableSetupColumn("##ArchitectureColumn"); + + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + + if(ImGui::BeginTable("##EngineTable", 1, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("##Engine"); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Engine"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->engine()); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Gears"); + + for(UnsignedInt i = 0; i < _currentMass->gears().size(); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->gears()[i]); + } + + ImGui::EndTable(); + } + + ImGui::TableSetColumnIndex(1); + + if(ImGui::BeginTable("##OSTable", 1, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("##OS"); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("OS"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->os()); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Modules"); + + for(UnsignedInt i = 0; i < _currentMass->modules().size(); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->modules()[i]); + } + + ImGui::EndTable(); + } + + ImGui::TableSetColumnIndex(2); + + if(ImGui::BeginTable("##ArchTable", 1, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("##Arch"); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Architecture"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->architecture()); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Techs"); + + for(UnsignedInt i = 0; i < _currentMass->techs().size(); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->techs()[i]); + } + + ImGui::EndTable(); + } + + ImGui::EndTable(); +} + +auto SaveTool::drawCustomStyle(CustomStyle& style) -> DCSResult { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return DCS_Fail; + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {8.0f, 0.0f}); + + Containers::ScopeGuard guard{[]{ ImGui::PopStyleVar(); }}; + + DCSResult return_value = DCS_Fail; + + if(!ImGui::BeginChild("##CustomStyle", {0.0f, 244.0f}, true, ImGuiWindowFlags_MenuBar)) { + ImGui::EndChild(); + return DCS_Fail; + } + + if(ImGui::BeginMenuBar()) { + ImGui::TextUnformatted(style.name.c_str()); + + static Containers::StaticArray<33, char> name_buf{ValueInit}; + if(ImGui::SmallButton(ICON_FA_EDIT " Rename")) { + for(auto& c : name_buf) { + c = '\0'; + } + std::strncpy(name_buf.data(), style.name.c_str(), 32); + ImGui::OpenPopup("name_edit"); + } + if(drawRenamePopup(name_buf)) { + style.name = name_buf.data(); + } + + ImGui::EndMenuBar(); + } + + if(ImGui::BeginTable("##StyleTable", 2, ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("##Colour", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##Pattern", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + + ImGui::BeginGroup(); + drawAlignedText("Colour:"); + drawAlignedText("Metallic:"); + drawAlignedText("Gloss:"); + drawAlignedText("Glow:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::ColorEdit3("##Picker", &style.colour.r()); + ImGui::SameLine(); + drawHelpMarker("Right-click for more option, click the coloured square for the full picker."); + + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SliderFloat("##SliderMetallic", &style.metallic, 0.0f, 1.0f); + + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SliderFloat("##SliderGloss", &style.gloss,0.0f, 1.0f); + + ImGui::Checkbox("##Glow", &style.glow); + ImGui::EndGroup(); + + ImGui::TableNextColumn(); + + ImGui::BeginGroup(); + drawAlignedText("Pattern:"); + drawAlignedText("Opacity:"); + drawAlignedText("X offset:"); + drawAlignedText("Y offset:"); + drawAlignedText("Rotation:"); + drawAlignedText("Scale:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + drawAlignedText("%i", style.patternId); + ImGui::PushItemWidth(-FLT_MIN); + ImGui::SliderFloat("##SliderOpacity", &style.opacity, 0.0f, 1.0f); + ImGui::SliderFloat("##SliderOffsetX", &style.offset.x(), 0.0f, 1.0f); + ImGui::SliderFloat("##SliderOffsetY", &style.offset.y(), 0.0f, 1.0f); + ImGui::SliderFloat("##SliderRotation", &style.rotation, 0.0f, 1.0f); + ImGui::SliderFloat("##SliderScale", &style.scale, 0.0f, 1.0f); + ImGui::PopItemWidth(); + ImGui::EndGroup(); + + ImGui::EndTable(); + } + + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + return_value = DCS_Save; + } + ImGui::SameLine(); + if(ImGui::Button(ICON_FA_UNDO " Reset")) { + return_value = DCS_ResetStyle; + } + + ImGui::EndChild(); + + return return_value; +} + +void SaveTool::drawDecalEditor(Decal& decal) { + ImGui::Text("ID: %i", decal.id); + + if(ImGui::BeginTable("##DecalTable", 2, ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("##Normal", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##Advanced", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + + ImGui::BeginGroup(); + drawAlignedText("Colour:"); + drawAlignedText("Offset:"); + drawAlignedText("Rotation:"); + drawAlignedText("Scale:"); + drawAlignedText("Flip X:"); + drawAlignedText("Surface wrap:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::ColorEdit3("##Picker", &decal.colour.r()); + ImGui::SameLine(); + drawHelpMarker("Right-click for more option, click the coloured square for the full picker."); + + ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth()); + ImGui::SliderFloat("##OffsetX", &decal.offset.x(), 0.0f, 1.0f, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##OffsetY", &decal.offset.y(), 0.0f, 1.0f, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(); + drawHelpMarker("0.0 = -100 in-game\n1.0 = 100 in-game"); + + ImGui::SliderFloat("##Rotation", &decal.rotation, 0.0f, 1.0f); + ImGui::SameLine(); + drawHelpMarker("0.0 = 0 in-game\n1.0 = 360 in-game"); + + ImGui::SliderFloat("##Scale", &decal.scale, 0.0f, 1.0f); + ImGui::SameLine(); + drawHelpMarker("0.0 = 1 in-game\n1.0 = 100 in-game"); + + ImGui::Checkbox("##Flip", &decal.flip); + + ImGui::Checkbox("##Wrap", &decal.wrap); + ImGui::EndGroup(); + + + ImGui::TableNextColumn(); + + ImGui::TextColored(ImColor(255, 255, 0), ICON_FA_EXCLAMATION_TRIANGLE); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextUnformatted("Advanced settings. Touch these at your own risk."); + + ImGui::BeginGroup(); + drawAlignedText("Position:"); + drawAlignedText("U axis:"); + drawAlignedText("V axis:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##PosX", &decal.position.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##PosY", &decal.position.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##PosZ", &decal.position.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##UX", &decal.uAxis.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##UY", &decal.uAxis.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##UZ", &decal.uAxis.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##VX", &decal.vAxis.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##VY", &decal.vAxis.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##VZ", &decal.vAxis.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f"); + ImGui::PopItemWidth(); + ImGui::EndGroup(); + + ImGui::EndTable(); + } +} + +void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView style_view) { + if(accessory.id < 1) { + ImGui::TextUnformatted("Accessory: "); + } + else if(accessories.find(accessory.id) != accessories.cend()) { + ImGui::Text("Accessory #%i - %s", accessory.id, accessories.at(accessory.id)); + } + else { + ImGui::Text("Accessory #%i", accessory.id); + drawTooltip("WARNING: accessory mapping is a WIP."); + } + +#ifdef SAVETOOL_DEBUG_BUILD + ImGui::SameLine(0.0f, ImGui::GetStyle().FramePadding.x * 5.0f); + ImGui::Text("Attach index: %i", accessory.attachIndex); +#endif + + ImGui::BeginGroup(); + drawAlignedText("Styles:"); + drawAlignedText("Base position:"); + drawAlignedText("Position offset:"); + drawAlignedText("Base rotation:"); + drawAlignedText("Rotation offset:"); + drawAlignedText("Scale:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth()); + if(ImGui::BeginCombo("##Style1", getStyleName(accessory.styles[0], style_view))) { + for(const auto& style : style_names) { + if(ImGui::Selectable(getStyleName(style.first, style_view), accessory.styles[0] == style.first)) { + accessory.styles[0] = style.first; + } + } + + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + if(ImGui::BeginCombo("##Style2", getStyleName(accessory.styles[1], style_view))) { + for(const auto& style : style_names) { + if(ImGui::Selectable(getStyleName(style.first, style_view), accessory.styles[1] == style.first)) { + accessory.styles[1] = style.first; + } + } + + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##PosX", &accessory.relativePosition.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##PosY", &accessory.relativePosition.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##PosZ", &accessory.relativePosition.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::SliderFloat("##PosOffsetX", &accessory.relativePositionOffset.x(), -500.0f, +500.0f, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##PosOffsetY", &accessory.relativePositionOffset.y(), -500.0f, +500.0f, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##PosOffsetZ", &accessory.relativePositionOffset.z(), -500.0f, +500.0f, "Z: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(); + drawHelpMarker("+/-500.0 = +/-250 in-game"); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##RotX", &accessory.relativeRotation.x(), 1.0f, -FLT_MAX, +FLT_MAX, "Roll: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##RotY", &accessory.relativeRotation.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Yaw: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##RotZ", &accessory.relativeRotation.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Pitch: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::SliderFloat("##RotOffsetX", &accessory.relativeRotationOffset.x(), -180.0f, +180.0f, "Roll: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##RotOffsetY", &accessory.relativeRotationOffset.y(), -180.0f, +180.0f, "Yaw: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##RotOffsetZ", &accessory.relativeRotationOffset.z(), -180.0f, +180.0f, "Pitch: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::SliderFloat("##ScaleX", &accessory.localScale.x(), -3.0f, +3.0f, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##ScaleY", &accessory.localScale.y(), -3.0f, +3.0f, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##ScaleZ", &accessory.localScale.z(), -3.0f, +3.0f, "Z: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(); + drawHelpMarker("+/-3.0 = +/-150 in-game"); + ImGui::EndGroup(); +} + +auto SaveTool::getStyleName(Int id, Containers::ArrayView view) -> const char* { + if(id >= 0 && id <= 15) { + return view[id].name.c_str(); + } + else if(id >= 50 && id <= 65) { + return _currentMass->globalStyles()[id - 50].name.c_str(); + } + else { + return style_names.at(id); + } +} diff --git a/src/SaveTool/SaveTool_MassViewer_Armour.cpp b/src/SaveTool/SaveTool_MassViewer_Armour.cpp new file mode 100644 index 0000000..a3120ad --- /dev/null +++ b/src/SaveTool/SaveTool_MassViewer_Armour.cpp @@ -0,0 +1,198 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../FontAwesome/IconsFontAwesome5.h" + +#include "../Maps/ArmourSets.h" +#include "../Maps/StyleNames.h" + +#include "SaveTool.h" + +void SaveTool::drawArmour() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(ImGui::Button(ICON_FA_UNDO_ALT " Reset all")) { + _currentMass->getArmourParts(); + } + + if(!ImGui::BeginChild("##ArmourParts")) { + ImGui::EndChild(); + return; + } + + static const char* slot_labels[] = { +#define c(enumerator, strenum, name) name, +#include "../Maps/ArmourSlots.hpp" +#undef c + }; + + for(UnsignedInt i = 0; i < _currentMass->armourParts().size(); i++) { + ImGui::PushID(i); + + auto& part = _currentMass->armourParts()[i]; + + static char header[129] = {'\0'}; + + std::memset(header, '\0', 129); + + if(armour_sets.find(part.id) != armour_sets.cend()) { + std::snprintf(header, 128, "%s: %s###%u", slot_labels[UnsignedInt(part.slot)], armour_sets.at(part.id).name, UnsignedInt(part.slot)); + } + else { + std::snprintf(header, 128, "%s: %i###%u", slot_labels[UnsignedInt(part.slot)], part.id, UnsignedInt(part.slot)); + } + + if(ImGui::CollapsingHeader(header)) { + ImGui::BeginGroup(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth() * 0.491f); + if(ImGui::BeginListBox("##ChangePart")) { + if(std::strncmp("Neck", slot_labels[UnsignedInt(part.slot)], 4) != 0) { + for(auto& set : armour_sets) { + if(ImGui::Selectable(set.second.name, set.first == part.id, ImGuiSelectableFlags_SpanAvailWidth)) { + part.id = set.first; + } + } + } + else { + for(auto& set : armour_sets) { + if(!set.second.neck_compatible) { + continue; + } + + if(ImGui::Selectable(set.second.name, set.first == part.id, ImGuiSelectableFlags_SpanAvailWidth)) { + part.id = set.first; + } + } + } + ImGui::EndListBox(); + } + + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + + ImGui::TextUnformatted("Styles:"); + + for(Int j = 0; j < 4; j++) { + drawAlignedText("Slot %d:", j + 1); + + ImGui::SameLine(); + + ImGui::PushID(j); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth() - 2.0f); + if(ImGui::BeginCombo("##Style", getStyleName(part.styles[j], _currentMass->armourCustomStyles()))) { + for(const auto& style : style_names) { + if(ImGui::Selectable(getStyleName(style.first, _currentMass->armourCustomStyles()), part.styles[j] == style.first)) { + part.styles[j] = style.first; + } + } + + ImGui::EndCombo(); + } + + ImGui::PopID(); + } + + ImGui::EndGroup(); + + ImGui::Separator(); + + ImGui::PushID("Decal"); + + drawAlignedText("Showing/editing decal"); + for(UnsignedInt j = 0; j < part.decals.size(); j++) { + ImGui::SameLine(); + ImGui::RadioButton(std::to_string(j + 1).c_str(), &_selectedArmourDecals[i], j); + } + + drawDecalEditor(part.decals[_selectedArmourDecals[i]]); + + ImGui::PopID(); + + if(!part.accessories.size()) { + ImGui::Separator(); + + ImGui::PushID("Accessory"); + + drawAlignedText("Showing/editing accessory"); + for(UnsignedInt j = 0; j < part.accessories.size(); j++) { + ImGui::SameLine(); + ImGui::RadioButton(std::string{char(65 + j)}.c_str(), &_selectedArmourAccessories[i], j); + } + + drawAccessoryEditor(part.accessories[_selectedArmourAccessories[i]], _currentMass->armourCustomStyles()); + + ImGui::PopID(); + } + + ImGui::Separator(); + + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + if(!_currentMass->writeArmourPart(part.slot)) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + } + } + + ImGui::PopID(); + } + + ImGui::EndChild(); +} + +void SaveTool::drawCustomArmourStyles() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(!ImGui::BeginChild("##ArmourStyles")) { + ImGui::EndChild(); + return; + } + + ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game."); + + for(UnsignedInt i = 0; i < _currentMass->armourCustomStyles().size(); i++) { + ImGui::PushID(i); + DCSResult result; + result = drawCustomStyle(_currentMass->armourCustomStyles()[i]); + switch(result) { + case DCS_ResetStyle: + _currentMass->getArmourCustomStyles(); + break; + case DCS_Save: + if(_currentMass->writeArmourCustomStyle(i)) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + break; + default: + break; + } + ImGui::PopID(); + } + + ImGui::EndChild(); +} diff --git a/src/SaveTool/SaveTool_MassViewer_Frame.cpp b/src/SaveTool/SaveTool_MassViewer_Frame.cpp new file mode 100644 index 0000000..8aae76f --- /dev/null +++ b/src/SaveTool/SaveTool_MassViewer_Frame.cpp @@ -0,0 +1,307 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../FontAwesome/IconsFontAwesome5.h" + +#include "../Maps/StyleNames.h" + +#include "SaveTool.h" + +void SaveTool::drawFrameInfo() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(!ImGui::BeginChild("##FrameInfo")) { + ImGui::EndChild(); + return; + } + + ImGui::BeginGroup(); + + if(ImGui::BeginChild("##JointSliders", {(ImGui::GetContentRegionAvailWidth() / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 300.0f}, true, ImGuiWindowFlags_MenuBar)) { + if(ImGui::BeginMenuBar()) { + ImGui::TextUnformatted("Joint sliders"); + + ImGui::EndMenuBar(); + } + + drawJointSliders(); + } + ImGui::EndChild(); + + if(ImGui::BeginChild("##FrameStyles", {(ImGui::GetContentRegionAvailWidth() / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 0.0f}, true, ImGuiWindowFlags_MenuBar)) { + if(ImGui::BeginMenuBar()) { + ImGui::TextUnformatted("Frame styles"); + + ImGui::EndMenuBar(); + } + + drawFrameStyles(); + } + ImGui::EndChild(); + + ImGui::EndGroup(); + + ImGui::SameLine(); + + if(ImGui::BeginChild("##EyeFlare", {0.0f, 0.0f}, true, ImGuiWindowFlags_MenuBar)) { + if(ImGui::BeginMenuBar()) { + ImGui::TextUnformatted("Eye flare colour"); + drawHelpMarker("Right-click the picker for more options.", 250.0f); + + ImGui::EndMenuBar(); + } + + drawEyeColourPicker(); + } + ImGui::EndChild(); + + ImGui::EndChild(); +} + +void SaveTool::drawJointSliders() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + ImGui::TextWrapped("In-game values are multiplied by 100.\nFor example, 0.500 here is equal to 50 in-game."); + + if(ImGui::BeginTable("##JointSliderTable", 2, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("##SliderLabel", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("##Sliders", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + drawAlignedText("Neck"); + ImGui::TableSetColumnIndex(1); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##NeckSlider", &_currentMass->jointSliders().neck, 0.0f, 1.0f)) { + _jointsDirty = true; + } + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + drawAlignedText("Body"); + ImGui::TableSetColumnIndex(1); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##BodySlider", &_currentMass->jointSliders().body, 0.0f, 1.0f)) { + _jointsDirty = true; + } + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + drawAlignedText("Shoulders"); + ImGui::TableSetColumnIndex(1); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##ShouldersSlider", &_currentMass->jointSliders().shoulders, 0.0f, 1.0f)) { + _jointsDirty = true; + } + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + drawAlignedText("Hips"); + ImGui::TableSetColumnIndex(1); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##HipsSlider", &_currentMass->jointSliders().hips, 0.0f, 1.0f)) { + _jointsDirty = true; + } + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + drawAlignedText("Arms"); + ImGui::TableSetColumnIndex(1); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{2.0f, 1.0f}); + if(ImGui::BeginTable("##UpperLowerArmsLayoutTable", 2)) { + ImGui::TableSetupColumn("##UpperArms", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##LowerArms", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##UpperArmsSlider", &_currentMass->jointSliders().upperArms, 0.0f, 1.0f, "Upper: %.3f")) { + _jointsDirty = true; + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##LowerArmsSlider", &_currentMass->jointSliders().lowerArms, 0.0f, 1.0f, "Lower: %.3f")) { + _jointsDirty = true; + } + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + drawAlignedText("Legs"); + ImGui::TableSetColumnIndex(1); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{2.0f, 1.0f}); + if(ImGui::BeginTable("##UpperLowerLegsLayoutTable", 2)) { + ImGui::TableSetupColumn("##UpperLegs", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##LowerLegs", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##UpperLegsSlider", &_currentMass->jointSliders().upperLegs, 0.0f, 1.0f, "Upper: %.3f")) { + _jointsDirty = true; + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##LowerLegsSlider", &_currentMass->jointSliders().lowerLegs, 0.0f, 1.0f, "Lower: %.3f")) { + _jointsDirty = true; + } + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + + ImGui::EndTable(); + } + + if(!_jointsDirty) { + ImGui::BeginDisabled(); + ImGui::Button(ICON_FA_SAVE " Save"); + ImGui::SameLine(); + ImGui::Button(ICON_FA_UNDO " Reset"); + ImGui::EndDisabled(); + } + else { + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + if(!_currentMass->writeJointSliders()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + _jointsDirty = false; + } + ImGui::SameLine(); + if(ImGui::Button(ICON_FA_UNDO " Reset")) { + _currentMass->getJointSliders(); + _jointsDirty = false; + } + } +} + +void SaveTool::drawFrameStyles() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + for(Int i = 0; i < 4; i++) { + drawAlignedText("Slot %d:", i + 1); + + ImGui::SameLine(); + + ImGui::PushID(i); + + if(ImGui::BeginCombo("##Style", getStyleName(_currentMass->frameStyles()[i], _currentMass->frameCustomStyles()))) { + for(const auto& style : style_names) { + if(ImGui::Selectable(getStyleName(style.first, _currentMass->frameCustomStyles()), _currentMass->frameStyles()[i] == style.first)) { + _currentMass->frameStyles()[i] = style.first; + _stylesDirty = true; + } + } + + ImGui::EndCombo(); + } + + ImGui::PopID(); + } + + if(!_stylesDirty) { + ImGui::BeginDisabled(); + ImGui::Button(ICON_FA_SAVE " Save"); + ImGui::SameLine(); + ImGui::Button(ICON_FA_UNDO " Reset"); + ImGui::EndDisabled(); + } + else { + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + if(!_currentMass->writeFrameStyles()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + _stylesDirty = false; + } + ImGui::SameLine(); + if(ImGui::Button(ICON_FA_UNDO " Reset")) { + _currentMass->getFrameStyles(); + _stylesDirty = false; + } + } +} + +void SaveTool::drawEyeColourPicker() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(ImGui::ColorPicker3("##EyeFlarePicker", &_currentMass->eyeFlareColour().x())) { + _eyeFlareDirty = true; + } + + if(!_eyeFlareDirty) { + ImGui::BeginDisabled(); + ImGui::Button(ICON_FA_SAVE " Save"); + ImGui::SameLine(); + ImGui::Button(ICON_FA_UNDO " Reset"); + ImGui::EndDisabled(); + } + else { + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + if(!_currentMass->writeEyeFlareColour()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + _eyeFlareDirty = false; + } + ImGui::SameLine(); + if(ImGui::Button(ICON_FA_UNDO " Reset")) { + _currentMass->getEyeFlareColour(); + _eyeFlareDirty = false; + } + } +} + +void SaveTool::drawCustomFrameStyles() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(!ImGui::BeginChild("##FrameStyles")) { + ImGui::EndChild(); + return; + } + + ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game."); + + for(UnsignedInt i = 0; i < _currentMass->frameCustomStyles().size(); i++) { + ImGui::PushID(i); + DCSResult result; + result = drawCustomStyle(_currentMass->frameCustomStyles()[i]); + switch(result) { + case DCS_ResetStyle: + _currentMass->getFrameCustomStyles(); + break; + case DCS_Save: + if(!_currentMass->writeFrameCustomStyle(i)) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + break; + default: + break; + } + ImGui::PopID(); + } + + ImGui::EndChild(); +} diff --git a/src/SaveTool/SaveTool_MassViewer_Weapons.cpp b/src/SaveTool/SaveTool_MassViewer_Weapons.cpp new file mode 100644 index 0000000..afac126 --- /dev/null +++ b/src/SaveTool/SaveTool_MassViewer_Weapons.cpp @@ -0,0 +1,475 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../FontAwesome/IconsFontAwesome5.h" + +#include "../Maps/StyleNames.h" + +#include "SaveTool.h" + +void SaveTool::drawWeapons() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + _currentWeapon = nullptr; + return; + } + + const Float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + + ImGui::BeginGroup(); + + if(!ImGui::BeginTable("##WeaponsList", 1, + ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersOuter|ImGuiTableFlags_BordersInnerH, + {ImGui::GetContentRegionAvailWidth() * 0.2f, -footer_height_to_reserve})) + { + ImGui::EndGroup(); + return; + } + + ImGui::TableSetupColumn("Weapon"); + + drawWeaponCategory("Melee weapons", _currentMass->meleeWeapons(), _meleeDirty, "MeleeWeapon", "Melee weapon"); + drawWeaponCategory("Shield", _currentMass->shields(), _shieldsDirty, "Shield", "Shield"); + drawWeaponCategory("Bullet shooters", _currentMass->bulletShooters(), _bShootersDirty, "BShooter", "Bullet shooter"); + drawWeaponCategory("Energy shooters", _currentMass->energyShooters(), _eShootersDirty, "EShooter", "Energy shooter"); + drawWeaponCategory("Bullet launchers", _currentMass->bulletLaunchers(), _bLaunchersDirty, "BLauncher", "Bullet launcher"); + drawWeaponCategory("Energy launchers", _currentMass->energyLaunchers(), _eLaunchersDirty, "ELauncher", "Energy launcher"); + + ImGui::EndTable(); + + bool dirty = _meleeDirty || _shieldsDirty || _bShootersDirty || _eShootersDirty || _bLaunchersDirty || _eLaunchersDirty; + + if(!dirty) { + ImGui::BeginDisabled(); + } + + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + if(_meleeDirty) { + if(!_currentMass->writeMeleeWeapons()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + else { + _meleeDirty = false; + } + } + + if(_shieldsDirty) { + if(!_currentMass->writeShields()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + else { + _shieldsDirty = false; + } + } + + if(_bShootersDirty) { + if(!_currentMass->writeBulletShooters()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + else { + _bShootersDirty = false; + } + } + + if(_eShootersDirty) { + if(_currentMass->writeEnergyShooters()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + else { + _eShootersDirty = false; + } + } + + if(_bLaunchersDirty) { + if(_currentMass->writeBulletLaunchers()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + else { + _bLaunchersDirty = false; + } + } + + if(_eLaunchersDirty) { + if(_currentMass->writeEnergyLaunchers()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + else { + _eLaunchersDirty = false; + } + } + } + + ImGui::SameLine(); + + if(ImGui::Button(ICON_FA_UNDO_ALT " Reset")) { + if(_meleeDirty) { + _currentMass->getMeleeWeapons(); + _meleeDirty = false; + } + if(_shieldsDirty) { + _currentMass->getShields(); + _shieldsDirty = false; + } + if(_bShootersDirty) { + _currentMass->getBulletShooters(); + _bShootersDirty = false; + } + if(_eShootersDirty) { + _currentMass->getEnergyShooters(); + _eShootersDirty = false; + } + if(_bLaunchersDirty) { + _currentMass->getBulletLaunchers(); + _bLaunchersDirty = false; + } + if(_eLaunchersDirty) { + _currentMass->getEnergyLaunchers(); + _eLaunchersDirty = false; + } + } + + if(!dirty) { + ImGui::EndDisabled(); + } + + ImGui::EndGroup(); + + ImGui::SameLine(); + + if(!_currentWeapon) { + ImGui::TextUnformatted("No weapon selected."); + return; + } + + ImGui::BeginGroup(); + + if(!ImGui::BeginChild("##WeaponChild", {0.0f, -footer_height_to_reserve})) { + ImGui::EndChild(); + return; + } + + drawWeaponEditor(*_currentWeapon); + + ImGui::EndChild(); + + ImGui::Separator(); + + if(drawUnsafeWidget([](){ return ImGui::Button(ICON_FA_SAVE " Save changes to weapon category"); })) { + switch(_currentWeapon->type) { + case WeaponType::Melee: + if(!_currentMass->writeMeleeWeapons()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + break; + case WeaponType::Shield: + if(!_currentMass->writeShields()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + break; + case WeaponType::BulletShooter: + if(!_currentMass->writeBulletShooters()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + break; + case WeaponType::EnergyShooter: + if(!_currentMass->writeEnergyShooters()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + break; + case WeaponType::BulletLauncher: + if(!_currentMass->writeBulletLaunchers()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + break; + case WeaponType::EnergyLauncher: + if(!_currentMass->writeEnergyLaunchers()) { + _queue.addToast(Toast::Type::Error, _currentMass->lastError()); + } + break; + default: + _queue.addToast(Toast::Type::Error, "Unknown weapon type"); + + } + } + + ImGui::SameLine(); + + if(ImGui::Button(ICON_FA_UNDO_ALT " Reset weapon category")) { + switch(_currentWeapon->type) { + case WeaponType::Melee: + _currentMass->getMeleeWeapons(); + break; + case WeaponType::Shield: + _currentMass->getShields(); + break; + case WeaponType::BulletShooter: + _currentMass->getBulletShooters(); + break; + case WeaponType::EnergyShooter: + _currentMass->getEnergyShooters(); + break; + case WeaponType::BulletLauncher: + _currentMass->getBulletLaunchers(); + break; + case WeaponType::EnergyLauncher: + _currentMass->getEnergyLaunchers(); + break; + default: + _queue.addToast(Toast::Type::Error, "Unknown weapon type"); + } + } + + ImGui::EndGroup(); +} + +void SaveTool::drawWeaponCategory(const char* name, Containers::ArrayView weapons_view, bool& dirty, + const char* payload_type, const char* payload_tooltip) +{ + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(name); + + ImGui::PushID(payload_type); + + for(UnsignedInt i = 0; i < weapons_view.size(); i++) { + auto& weapon = weapons_view[i]; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::PushID(i); + + if(ImGui::Selectable(weapon.name.c_str(), _currentWeapon == &weapon)) { + _currentWeapon = &weapon; + } + if(ImGui::BeginDragDropSource()) { + ImGui::SetDragDropPayload(payload_type, &i, sizeof(UnsignedInt)); + if(ImGui::GetIO().KeyCtrl) { + ImGui::Text("%s %i - %s (copy)", payload_tooltip, i + 1, weapon.name.c_str()); + } + else { + ImGui::Text("%s %i - %s", payload_tooltip, i + 1, weapon.name.c_str()); + } + ImGui::EndDragDropSource(); + } + if(ImGui::BeginDragDropTarget()) { + if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(payload_type)) { + int index = *static_cast(payload->Data); + + if(!ImGui::GetIO().KeyCtrl) { + if(_currentWeapon == &weapons_view[index]) { + _currentWeapon = &weapons_view[i]; + } + else if (_currentWeapon == &weapons_view[i]) { + _currentWeapon = &weapons_view[index]; + } + + std::swap(weapons_view[index], weapons_view[i]); + } + else { + weapons_view[i] = weapons_view[index]; + } + dirty = true; + } + + ImGui::EndDragDropTarget(); + } + + ImGui::PopID(); + + if(weapon.attached == true) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, 0x1F008CFFu); + } + } + + ImGui::PopID(); +} + +void SaveTool::drawWeaponEditor(Weapon& weapon) { + if(!_currentMass || _currentMass->state() != Mass::State::Valid || !_currentWeapon) { + return; + } + + static const char* labels[] { +#define c(enumerator, strenum, name) name, +#include "../Maps/WeaponTypes.hpp" +#undef c + }; + + drawAlignedText("%s: %s", labels[UnsignedInt(weapon.type)], weapon.name.c_str()); + + ImGui::SameLine(); + + static Containers::StaticArray<33, char> name_buf{ValueInit}; + if(ImGui::Button(ICON_FA_EDIT " Rename")) { + for(auto& c : name_buf) { + c = '\0'; + } + std::strncpy(name_buf.data(), weapon.name.c_str(), 32); + ImGui::OpenPopup("name_edit"); + } + if(drawRenamePopup(name_buf)) { + weapon.name = name_buf.data(); + } + + ImGui::BeginGroup(); + drawAlignedText("Equipped:"); + + if(weapon.type != WeaponType::Shield) { + drawAlignedText("Damage type:"); + } + + if(weapon.type == WeaponType::Melee) { + drawAlignedText("Dual-wield:"); + + drawAlignedText("Custom effect mode:"); + + drawAlignedText("Custom effect colour:"); + } + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::Checkbox("##EquippedCheckbox", &weapon.attached); + + if(weapon.type != WeaponType::Shield) { + if(weapon.type == WeaponType::Melee && + ImGui::RadioButton("Physical##NoElement", weapon.damageType == DamageType::Physical)) + { + weapon.damageType = DamageType::Physical; + } + else if((weapon.type == WeaponType::BulletShooter || weapon.type == WeaponType::BulletLauncher) && + ImGui::RadioButton("Piercing##NoElement", weapon.damageType == DamageType::Piercing)) + { + weapon.damageType = DamageType::Piercing; + } + else if((weapon.type == WeaponType::EnergyShooter || weapon.type == WeaponType::EnergyLauncher) && + ImGui::RadioButton("Plasma##NoElement", weapon.damageType == DamageType::Plasma)) + { + weapon.damageType = DamageType::Plasma; + } + ImGui::SameLine(); + if(ImGui::RadioButton("Heat##Heat", weapon.damageType == DamageType::Heat)) { + weapon.damageType = DamageType::Heat; + } + ImGui::SameLine(); + if(ImGui::RadioButton("Freeze##Freeze", weapon.damageType == DamageType::Freeze)) { + weapon.damageType = DamageType::Freeze; + } + ImGui::SameLine(); + if(ImGui::RadioButton("Shock##Shock", weapon.damageType == DamageType::Freeze)) { + weapon.damageType = DamageType::Freeze; + } + } + + if(weapon.type == WeaponType::Melee) { + ImGui::Checkbox("##DualWield", &weapon.dualWield); + + if(ImGui::RadioButton("Default##Default", weapon.effectColourMode == EffectColourMode::Default)) { + weapon.effectColourMode = EffectColourMode::Default; + } + ImGui::SameLine(); + if(ImGui::RadioButton("Custom##Custom", weapon.effectColourMode == EffectColourMode::Custom)) { + weapon.effectColourMode = EffectColourMode::Custom; + } + + bool custom_effect = (weapon.effectColourMode == EffectColourMode::Custom); + if(!custom_effect) { + ImGui::BeginDisabled(); + } + + ImGui::ColorEdit3("##CustomEffectColourPicker", &weapon.effectColour.x(), ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_Float); + + if(!custom_effect) { + ImGui::EndDisabled(); + } + } + ImGui::EndGroup(); + + ImGui::Separator(); + + if(ImGui::CollapsingHeader("Weapon parts")) { + drawAlignedText("Viewing/editing part:"); + for(Int i = 0; UnsignedLong(i) < weapon.parts.size(); i++) { + if(UnsignedLong(_selectedWeaponPart) >= weapon.parts.size()) { + _selectedWeaponPart = 0; + } + ImGui::SameLine(); + ImGui::RadioButton(std::to_string(i + 1).c_str(), &_selectedWeaponPart, i); + } + + auto& part = weapon.parts[_selectedWeaponPart]; + + ImGui::Text("ID: %i", part.id); + + if(ImGui::BeginChild("##PartDetails", {0.0f, 0.0f}, true)) { + ImGui::TextUnformatted("Styles:"); + + for(Int i = 0; i < 4; i++) { + drawAlignedText("Slot %d:", i + 1); + + ImGui::SameLine(); + + ImGui::PushID(i); + + if(ImGui::BeginCombo("##Style", getStyleName(part.styles[i], weapon.customStyles))) { + for(const auto& style: style_names) { + if(ImGui::Selectable(getStyleName(style.first, weapon.customStyles), + part.styles[i] == style.first)) { + part.styles[i] = style.first; + } + } + + ImGui::EndCombo(); + } + + ImGui::PopID(); + } + + ImGui::Separator(); + + ImGui::PushID("Decal"); + + drawAlignedText("Showing/editing decal"); + for(UnsignedLong i = 0; i < part.decals.size(); i++) { + ImGui::SameLine(); + ImGui::RadioButton(std::to_string(i + 1).c_str(), &_selectedWeaponDecal, i); + } + + drawDecalEditor(part.decals[_selectedWeaponDecal]); + + ImGui::PopID(); + + if(part.accessories.size() != 0) { + ImGui::Separator(); + + ImGui::PushID("Accessory"); + + drawAlignedText("Showing/editing accessory"); + for(UnsignedLong i = 0; i < part.accessories.size(); i++) { + ImGui::SameLine(); + ImGui::RadioButton(std::string{char(65 + i)}.c_str(), &_selectedWeaponAccessory, i); + } + + drawAccessoryEditor(part.accessories[_selectedWeaponAccessory], weapon.customStyles); + + ImGui::PopID(); + } + } + + ImGui::EndChild(); + } +} diff --git a/src/SaveTool/SaveTool_ProfileManager.cpp b/src/SaveTool/SaveTool_ProfileManager.cpp index 490d9ee..d501d1b 100644 --- a/src/SaveTool/SaveTool_ProfileManager.cpp +++ b/src/SaveTool/SaveTool_ProfileManager.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -81,7 +81,7 @@ void SaveTool::drawProfileManager() { ImGui::TableSetColumnIndex(0); ImGui::PushID(i); - if(ImGui::Selectable(_profileManager->profiles().at(i).companyName().c_str(), false, + if(ImGui::Selectable(_profileManager->profiles()[i].companyName().c_str(), false, ImGuiSelectableFlags_SpanAllColumns|ImGuiSelectableFlags_AllowItemOverlap)) { _currentProfile = _profileManager->getProfile(i); @@ -90,7 +90,7 @@ void SaveTool::drawProfileManager() { } ImGui::TableSetColumnIndex(1); - ImGui::TextUnformatted(_profileManager->profiles().at(i).type() == ProfileType::Demo ? "Demo" : "Full"); + ImGui::TextUnformatted(_profileManager->profiles()[i].type() == ProfileType::Demo ? "Demo (legacy)" : "Full (legacy)"); ImGui::TableSetColumnIndex(2); if(ImGui::SmallButton(ICON_FA_FILE_ARCHIVE)) { @@ -132,13 +132,13 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID { { ImGui::PushTextWrapPos(windowSize().x() * 0.40f); ImGui::Text("Are you sure you want to restore the %s backup from %.4i-%.2i-%.2i %.2i:%.2i:%.2i ? Any existing data will be overwritten.", - _profileManager->backups().at(backup_index).company.c_str(), - _profileManager->backups().at(backup_index).timestamp.year, - _profileManager->backups().at(backup_index).timestamp.month, - _profileManager->backups().at(backup_index).timestamp.day, - _profileManager->backups().at(backup_index).timestamp.hour, - _profileManager->backups().at(backup_index).timestamp.minute, - _profileManager->backups().at(backup_index).timestamp.second); + _profileManager->backups()[backup_index].company.c_str(), + _profileManager->backups()[backup_index].timestamp.year, + _profileManager->backups()[backup_index].timestamp.month, + _profileManager->backups()[backup_index].timestamp.day, + _profileManager->backups()[backup_index].timestamp.hour, + _profileManager->backups()[backup_index].timestamp.minute, + _profileManager->backups()[backup_index].timestamp.second); ImGui::PopTextWrapPos(); if(ImGui::BeginTable("##RestoreBackupLayout", 2)) { @@ -150,8 +150,7 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID { ImGui::TableSetColumnIndex(1); if(ImGui::Button("Yes")) { if(!_profileManager->restoreBackup(backup_index)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error when restoring backup", - _profileManager->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _profileManager->lastError()); } _profileManager->refreshProfiles(); ImGui::CloseCurrentPopup(); @@ -172,13 +171,13 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID { { ImGui::PushTextWrapPos(windowSize().x() * 0.40f); ImGui::Text("Are you sure you want to delete the %s backup from %.4i-%.2i-%.2i %.2i:%.2i:%.2i ? This operation is irreversible.", - _profileManager->backups().at(backup_index).company.c_str(), - _profileManager->backups().at(backup_index).timestamp.year, - _profileManager->backups().at(backup_index).timestamp.month, - _profileManager->backups().at(backup_index).timestamp.day, - _profileManager->backups().at(backup_index).timestamp.hour, - _profileManager->backups().at(backup_index).timestamp.minute, - _profileManager->backups().at(backup_index).timestamp.second); + _profileManager->backups()[backup_index].company.c_str(), + _profileManager->backups()[backup_index].timestamp.year, + _profileManager->backups()[backup_index].timestamp.month, + _profileManager->backups()[backup_index].timestamp.day, + _profileManager->backups()[backup_index].timestamp.hour, + _profileManager->backups()[backup_index].timestamp.minute, + _profileManager->backups()[backup_index].timestamp.second); ImGui::PopTextWrapPos(); if(ImGui::BeginTable("##DeleteBackupLayout", 2)) { @@ -190,8 +189,7 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID { ImGui::TableSetColumnIndex(1); if(ImGui::Button("Yes")) { if(!_profileManager->deleteBackup(backup_index)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error when deleting backup", - _profileManager->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _profileManager->lastError()); } ImGui::CloseCurrentPopup(); } @@ -251,10 +249,10 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted(_profileManager->backups().at(i).company.c_str()); + ImGui::TextUnformatted(_profileManager->backups()[i].company.c_str()); if(ImGui::IsItemHovered()) { ImGui::BeginTooltip(); - for(const auto& file : _profileManager->backups().at(i).includedFiles) { + for(const auto& file : _profileManager->backups()[i].includedFiles) { ImGui::TextUnformatted(file.c_str()); } ImGui::EndTooltip(); @@ -262,15 +260,15 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID { ImGui::TableSetColumnIndex(1); ImGui::Text("%.4i-%.2i-%.2i %.2i:%.2i:%.2i", - _profileManager->backups().at(i).timestamp.year, - _profileManager->backups().at(i).timestamp.month, - _profileManager->backups().at(i).timestamp.day, - _profileManager->backups().at(i).timestamp.hour, - _profileManager->backups().at(i).timestamp.minute, - _profileManager->backups().at(i).timestamp.second); + _profileManager->backups()[i].timestamp.year, + _profileManager->backups()[i].timestamp.month, + _profileManager->backups()[i].timestamp.day, + _profileManager->backups()[i].timestamp.hour, + _profileManager->backups()[i].timestamp.minute, + _profileManager->backups()[i].timestamp.second); ImGui::TableSetColumnIndex(2); - ImGui::TextUnformatted(_profileManager->backups().at(i).type == ProfileType::Demo ? "Demo" : "Full"); + ImGui::TextUnformatted(_profileManager->backups()[i].type == ProfileType::Demo ? "Demo" : "Full"); ImGui::TableSetColumnIndex(3); ImGui::PushID(i); @@ -363,8 +361,8 @@ auto SaveTool::drawDeleteProfilePopup(std::size_t profile_index) -> ImGuiID { ImGui::PushTextWrapPos(windowSize().x() * 0.40f); ImGui::Text("Are you sure you want to delete the %s %s profile ? This operation is irreversible.", - _profileManager->profiles().at(profile_index).companyName().c_str(), - _profileManager->profiles().at(profile_index).type() == ProfileType::Demo ? "demo" : "full game"); + _profileManager->profiles()[profile_index].companyName().c_str(), + _profileManager->profiles()[profile_index].type() == ProfileType::Demo ? "demo" : "full game"); ImGui::PopTextWrapPos(); if(ImGui::BeginTable("##DeleteProfileLayout", 2)) { @@ -378,8 +376,7 @@ auto SaveTool::drawDeleteProfilePopup(std::size_t profile_index) -> ImGuiID { ImGui::TableSetColumnIndex(1); if(ImGui::Button("Yes")) { if(!_profileManager->deleteProfile(profile_index, delete_builds)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error when deleting profile", - _profileManager->lastError().c_str(), window()); + _queue.addToast(Toast::Type::Error, _profileManager->lastError()); } ImGui::CloseCurrentPopup(); } diff --git a/src/SaveTool/SaveTool_drawAbout.cpp b/src/SaveTool/SaveTool_drawAbout.cpp index e84b44d..c5fa7ea 100644 --- a/src/SaveTool/SaveTool_drawAbout.cpp +++ b/src/SaveTool/SaveTool_drawAbout.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -57,9 +57,8 @@ void SaveTool::drawAbout() { ImGui::TextWrapped("This application, made for the M.A.S.S. Builder community by Guillaume Jacquemin (aka William JCM), " "is a rewrite of the wxWidgets-powered M.A.S.S. Builder Save Tool (formerly known as wxMASSManager)."); - ImGui::AlignTextToFramePadding(); const char* repo = "https://williamjcm.ovh/git/williamjcm/MassBuilderSaveTool"; - ImGui::Text(ICON_FA_GIT_ALT " %s", repo); + drawAlignedText(ICON_FA_GIT_ALT " %s", repo); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(repo); @@ -90,9 +89,8 @@ void SaveTool::drawAbout() { if(ImGui::TreeNodeEx("Corrade", ImGuiTreeNodeFlags_SpanAvailWidth)) { ImGui::Text("Version used: %s", CORRADE_VERSION_STRING); - ImGui::AlignTextToFramePadding(); const char* corrade_website = "https://magnum.graphics/corrade"; - ImGui::Text(ICON_FA_GLOBE " %s", corrade_website); + drawAlignedText(ICON_FA_GLOBE " %s", corrade_website); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(corrade_website); @@ -119,9 +117,8 @@ void SaveTool::drawAbout() { ImGui::TextUnformatted("Versions used:"); ImGui::BulletText("Magnum: %s", MAGNUM_VERSION_STRING); ImGui::BulletText("Integration: %s", MAGNUMINTEGRATION_VERSION_STRING); - ImGui::AlignTextToFramePadding(); const char* magnum_website = "https://magnum.graphics"; - ImGui::Text(ICON_FA_GLOBE " %s", magnum_website); + drawAlignedText(ICON_FA_GLOBE " %s", magnum_website); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(magnum_website); @@ -146,9 +143,8 @@ void SaveTool::drawAbout() { if(ImGui::TreeNodeEx("Dear ImGui", ImGuiTreeNodeFlags_SpanAvailWidth)) { ImGui::Text("Version used: %s", IMGUI_VERSION); - ImGui::AlignTextToFramePadding(); const char* imgui_repo = "https://github.com/ocornut/imgui"; - ImGui::Text(ICON_FA_GITHUB " %s", imgui_repo); + drawAlignedText(ICON_FA_GITHUB " %s", imgui_repo); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(imgui_repo); @@ -173,9 +169,8 @@ void SaveTool::drawAbout() { if(ImGui::TreeNodeEx("Simple DirectMedia Layer (SDL) 2", ImGuiTreeNodeFlags_SpanAvailWidth)) { ImGui::Text("Version used: %i.%i.%i", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); - ImGui::AlignTextToFramePadding(); const char* sdl_website = "https://www.libsdl.org/"; - ImGui::Text(ICON_FA_GLOBE " %s", sdl_website); + drawAlignedText(ICON_FA_GLOBE " %s", sdl_website); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(sdl_website); @@ -200,9 +195,8 @@ void SaveTool::drawAbout() { if(ImGui::TreeNodeEx("libzip", ImGuiTreeNodeFlags_SpanAvailWidth)) { ImGui::Text("Version used: %s", LIBZIP_VERSION); - ImGui::AlignTextToFramePadding(); const char* libzip_website = "https://libzip.org/"; - ImGui::Text(ICON_FA_GLOBE " %s", libzip_website); + drawAlignedText(ICON_FA_GLOBE " %s", libzip_website); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(libzip_website); @@ -226,9 +220,8 @@ void SaveTool::drawAbout() { } if(ImGui::TreeNodeEx("Entropia File System Watcher (efsw)", ImGuiTreeNodeFlags_SpanAvailWidth)) { - ImGui::AlignTextToFramePadding(); const char* efsw_repo = "https://github.com/SpartanJ/efsw"; - ImGui::Text(ICON_FA_GITHUB " %s", efsw_repo); + drawAlignedText(ICON_FA_GITHUB " %s", efsw_repo); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(efsw_repo); @@ -252,9 +245,8 @@ void SaveTool::drawAbout() { } if(ImGui::TreeNodeEx("C++ Requests (cpr)", ImGuiTreeNodeFlags_SpanAvailWidth)) { - ImGui::AlignTextToFramePadding(); const char* cpr_website = "https://whoshuu.github.io/cpr/"; - ImGui::Text(ICON_FA_GLOBE " %s", cpr_website); + drawAlignedText(ICON_FA_GLOBE " %s", cpr_website); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(cpr_website); @@ -278,9 +270,8 @@ void SaveTool::drawAbout() { } if(ImGui::TreeNodeEx("JSON for Modern C++ (aka json.hpp)", ImGuiTreeNodeFlags_SpanAvailWidth)) { - ImGui::AlignTextToFramePadding(); const char* json_website = "https://json.nlohmann.me/"; - ImGui::Text(ICON_FA_GLOBE " %s", json_website); + drawAlignedText(ICON_FA_GLOBE " %s", json_website); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(json_website); @@ -305,9 +296,8 @@ void SaveTool::drawAbout() { if(ImGui::TreeNodeEx("Font Awesome", ImGuiTreeNodeFlags_SpanAvailWidth)) { ImGui::TextUnformatted("Version used: 5.15.3"); - ImGui::AlignTextToFramePadding(); const char* fa_website = "https://fontawesome.com/"; - ImGui::Text(ICON_FA_GLOBE " %s", fa_website); + drawAlignedText(ICON_FA_GLOBE " %s", fa_website); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(fa_website); @@ -323,9 +313,8 @@ void SaveTool::drawAbout() { } if(ImGui::TreeNodeEx("IconFontCppHeaders", ImGuiTreeNodeFlags_SpanAvailWidth)) { - ImGui::AlignTextToFramePadding(); const char* icon_repo = "https://github.com/juliettef/IconFontCppHeaders"; - ImGui::Text(ICON_FA_GITHUB " %s", icon_repo); + drawAlignedText(ICON_FA_GITHUB " %s", icon_repo); ImGui::SameLine(); if(ImGui::Button("Copy to clipboard")) { ImGui::SetClipboardText(icon_repo); diff --git a/src/SaveTool/SaveTool_drawMainMenu.cpp b/src/SaveTool/SaveTool_drawMainMenu.cpp index 6fd1128..92abb24 100644 --- a/src/SaveTool/SaveTool_drawMainMenu.cpp +++ b/src/SaveTool/SaveTool_drawMainMenu.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 @@ -55,8 +55,7 @@ void SaveTool::drawMainMenu() { ImGui::Separator(); if(ImGui::BeginMenu(ICON_FA_COG " Settings")) { - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted("Frame limiter:"); + drawAlignedText("Frame limiter:"); ImGui::SameLine(); static UnsignedByte selection = static_cast(_framelimit); @@ -118,8 +117,7 @@ void SaveTool::drawMainMenu() { } if(_updateAvailable) { - ImGui::AlignTextToFramePadding(); - ImGui::Text("Version %s is available.", _latestVersion.c_str()); + drawAlignedText("Version %s is available.", _latestVersion.c_str()); if(ImGui::Button(ICON_FA_FILE_SIGNATURE " Release notes")) { openUri(_releaseLink); } @@ -181,6 +179,20 @@ void SaveTool::drawMainMenu() { #endif if(ImGui::BeginMenu("Help")) { + if(ImGui::BeginMenu(ICON_FA_BOOK " ImGui user guide")) { + ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); + ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + ImGui::BulletText("While inputing text:\n"); + ImGui::Indent(); + ImGui::BulletText("CTRL+Left/Right to word jump."); + ImGui::BulletText("CTRL+A or double-click to select all."); + ImGui::BulletText("CTRL+X/C/V to use clipboard cut/copy/paste."); + ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); + ImGui::BulletText("ESCAPE to revert."); + ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract."); + ImGui::Unindent(); + ImGui::EndMenu(); + } ImGui::MenuItem(ICON_FA_INFO_CIRCLE " About", nullptr, &_aboutPopup); ImGui::EndMenu(); diff --git a/src/ToastQueue/ToastQueue.cpp b/src/ToastQueue/ToastQueue.cpp index 1f367c6..ec2113d 100644 --- a/src/ToastQueue/ToastQueue.cpp +++ b/src/ToastQueue/ToastQueue.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 diff --git a/src/ToastQueue/ToastQueue.h b/src/ToastQueue/ToastQueue.h index 3c2d4ce..d309833 100644 --- a/src/ToastQueue/ToastQueue.h +++ b/src/ToastQueue/ToastQueue.h @@ -1,7 +1,7 @@ #pragma once // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 diff --git a/src/UESaveFile/BinaryReader.cpp b/src/UESaveFile/BinaryReader.cpp new file mode 100644 index 0000000..a34486e --- /dev/null +++ b/src/UESaveFile/BinaryReader.cpp @@ -0,0 +1,126 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "BinaryReader.h" + +BinaryReader::BinaryReader(const std::string& filename) { + _file = std::fopen(filename.c_str(), "rb"); + + if(!_file) { + Utility::Error{} << "Couldn't open" << filename.c_str() << "for reading:\n" + << std::strerror(errno); + } +} + +BinaryReader::~BinaryReader() { + closeFile(); +} + +auto BinaryReader::open() -> bool { + return _file; +} + +auto BinaryReader::eof() -> bool { + return std::feof(_file) != 0; +} + +auto BinaryReader::position() -> Long { + return _ftelli64(_file); +} + +auto BinaryReader::seek(Long position) -> bool { + return _fseeki64(_file, position, SEEK_SET) == 0; +} + +void BinaryReader::closeFile() { + std::fclose(_file); + _file = nullptr; +} + +auto BinaryReader::readChar(char& value) -> bool { + return std::fread(&value, sizeof(char), 1, _file) == 1; +} + +auto BinaryReader::readByte(Byte& value) -> bool { + return std::fread(&value, sizeof(Byte), 1, _file) == 1; +} + +auto BinaryReader::readUnsignedByte(UnsignedByte& value) -> bool { + return std::fread(&value, sizeof(UnsignedByte), 1, _file) == 1; +} + +auto BinaryReader::readShort(Short& value) -> bool { + return std::fread(&value, sizeof(Short), 1, _file) == 1; +} + +auto BinaryReader::readUnsignedShort(UnsignedShort& value) -> bool { + return std::fread(&value, sizeof(UnsignedShort), 1, _file) == 1; +} + +auto BinaryReader::readInt(Int& value) -> bool { + return std::fread(&value, sizeof(Int), 1, _file) == 1; +} + +auto BinaryReader::readUnsignedInt(UnsignedInt& value) -> bool { + return std::fread(&value, sizeof(UnsignedInt), 1, _file) == 1; +} + +auto BinaryReader::readLong(Long& value) -> bool { + return std::fread(&value, sizeof(Long), 1, _file) == 1; +} + +auto BinaryReader::readUnsignedLong(UnsignedLong& value) -> bool { + return std::fread(&value, sizeof(UnsignedLong), 1, _file) == 1; +} + +auto BinaryReader::readFloat(Float& value) -> bool { + return std::fread(&value, sizeof(Float), 1, _file) == 1; +} + +auto BinaryReader::readDouble(Double& value) -> bool { + return std::fread(&value, sizeof(Double), 1, _file) == 1; +} + +auto BinaryReader::readArray(Containers::Array& array, std::size_t count) -> bool { + if(array.size() < count) { + array = Containers::Array{ValueInit, count}; + } + + return std::fread(array.data(), sizeof(char), count, _file) == count; +} + +auto BinaryReader::readUEString(std::string& str) -> bool { + UnsignedInt length = 0; + if(!readUnsignedInt(length) || length == 0) { + return false; + } + + str = std::string{}; + str.resize(length - 1); + + return std::fread(&str[0], sizeof(char), length, _file) == length; +} + +auto BinaryReader::peekChar() -> Int { + Int c; + c = std::fgetc(_file); + std::ungetc(c, _file); + return c; +} diff --git a/src/UESaveFile/BinaryReader.h b/src/UESaveFile/BinaryReader.h new file mode 100644 index 0000000..5a11280 --- /dev/null +++ b/src/UESaveFile/BinaryReader.h @@ -0,0 +1,72 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Corrade; +using namespace Magnum; + +class BinaryReader { + public: + explicit BinaryReader(const std::string& filename); + ~BinaryReader(); + + auto open() -> bool; + auto eof() -> bool; + auto position() -> Long; + + auto seek(Long position) -> bool; + + void closeFile(); + + auto readChar(char& value) -> bool; + auto readByte(Byte& value) -> bool; + auto readUnsignedByte(UnsignedByte& value) -> bool; + auto readShort(Short& value) -> bool; + auto readUnsignedShort(UnsignedShort& value) -> bool; + auto readInt(Int& value) -> bool; + auto readUnsignedInt(UnsignedInt& value) -> bool; + auto readLong(Long& value) -> bool; + auto readUnsignedLong(UnsignedLong& value) -> bool; + auto readFloat(Float& value) -> bool; + auto readDouble(Double& value) -> bool; + auto readArray(Containers::Array& array, std::size_t count) -> bool; + + template + auto readValue(T& value) -> bool { + return fread(&value, sizeof(T), 1, _file) == sizeof(T); + } + + template + auto readStaticArray(Containers::StaticArray& array) -> bool { + return std::fread(array.data(), sizeof(char), S, _file) == S; + } + + auto readUEString(std::string& str) -> bool; + + auto peekChar() -> Int; + + private: + std::FILE* _file = nullptr; +}; diff --git a/src/UESaveFile/BinaryWriter.cpp b/src/UESaveFile/BinaryWriter.cpp new file mode 100644 index 0000000..64e6b24 --- /dev/null +++ b/src/UESaveFile/BinaryWriter.cpp @@ -0,0 +1,137 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "BinaryWriter.h" + +BinaryWriter::BinaryWriter(const std::string& filename) { + _file = std::fopen(filename.c_str(), "wb"); + if(!_file) { + Utility::Error{} << "Couldn't open" << filename.c_str() << "for reading:\n" + << std::strerror(errno); + } +} + +BinaryWriter::~BinaryWriter() { + closeFile(); +} + +auto BinaryWriter::open() -> bool { + return _file; +} + +void BinaryWriter::closeFile() { + std::fflush(_file); + std::fclose(_file); + _file = nullptr; +} + +auto BinaryWriter::position() -> Long { + return _ftelli64(_file); +} + +auto BinaryWriter::array() const -> Containers::ArrayView { + return _data; +} + +auto BinaryWriter::arrayPosition() const -> UnsignedLong { + return _index; +} + +auto BinaryWriter::flushToFile() -> bool { + bool ret = writeArray(_data); + std::fflush(_file); + _data = Containers::Array{}; + _index = 0; + return ret; +} + +auto BinaryWriter::writeChar(char value) -> bool { + return std::fwrite(&value, sizeof(char), 1, _file) == 1; +} + +auto BinaryWriter::writeByte(Byte value) -> bool { + return std::fwrite(&value, sizeof(Byte), 1, _file) == 1; +} + +auto BinaryWriter::writeUnsignedByte(UnsignedByte value) -> bool { + return std::fwrite(&value, sizeof(UnsignedByte), 1, _file) == 1; +} + +auto BinaryWriter::writeShort(Short value) -> bool { + return std::fwrite(&value, sizeof(Short), 1, _file) == 1; +} + +auto BinaryWriter::writeUnsignedShort(UnsignedShort value) -> bool { + return std::fwrite(&value, sizeof(UnsignedShort), 1, _file) == 1; +} + +auto BinaryWriter::writeInt(Int value) -> bool { + return std::fwrite(&value, sizeof(Int), 1, _file) == 1; +} + +auto BinaryWriter::writeUnsignedInt(UnsignedInt value) -> bool { + return std::fwrite(&value, sizeof(UnsignedInt), 1, _file) == 1; +} + +auto BinaryWriter::writeLong(Long value) -> bool { + return std::fwrite(&value, sizeof(Long), 1, _file) == 1; +} + +auto BinaryWriter::writeUnsignedLong(UnsignedLong value) -> bool { + return std::fwrite(&value, sizeof(UnsignedLong), 1, _file) == 1; +} + +auto BinaryWriter::writeFloat(Float value) -> bool { + return std::fwrite(&value, sizeof(Float), 1, _file) == 1; +} + +auto BinaryWriter::writeDouble(Double value) -> bool { + return std::fwrite(&value, sizeof(Double), 1, _file) == 1; +} + +auto BinaryWriter::writeArray(Containers::ArrayView array) -> bool { + if(array.size() == 0) { + return false; + } + + return std::fwrite(array.data(), sizeof(char), array.size(), _file) == array.size(); +} + +auto BinaryWriter::writeUEString(const std::string& str) -> bool { + if(str.length() > UINT32_MAX) { + Utility::Error{} << "BinaryWriter::writeUEString(): string is too big."; + return false; + } + + writeUnsignedInt(static_cast(str.length()) + 1); + + if(str.length() > 0) { + std::size_t count = std::fwrite(&str[0], sizeof(char), str.length(), _file); + if(count != str.length()) { + return false; + } + } + return writeChar('\0'); +} + +auto BinaryWriter::writeUEStringToArray(const std::string& value) -> UnsignedLong { + Containers::ArrayView view{value.c_str(), value.length()}; + return writeValueToArray(UnsignedInt(value.length()) + 1u) + writeDataToArray(view) + writeValueToArray('\0'); +} diff --git a/src/UESaveFile/BinaryWriter.h b/src/UESaveFile/BinaryWriter.h new file mode 100644 index 0000000..ac73f1a --- /dev/null +++ b/src/UESaveFile/BinaryWriter.h @@ -0,0 +1,110 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Corrade; +using namespace Magnum; + +class BinaryWriter { + public: + explicit BinaryWriter(const std::string& filename); + ~BinaryWriter(); + + BinaryWriter(const BinaryWriter& other) = delete; + BinaryWriter& operator=(const BinaryWriter& other) = delete; + + BinaryWriter(BinaryWriter&& other) = default; + BinaryWriter& operator=(BinaryWriter&& other) = default; + + auto open() -> bool; + + void closeFile(); + + auto position() -> Long; + + auto array() const -> Containers::ArrayView; + auto arrayPosition() const -> UnsignedLong; + auto flushToFile() -> bool; + + auto writeByte(Byte value) -> bool; + auto writeChar(char value) -> bool; + auto writeUnsignedByte(UnsignedByte value) -> bool; + auto writeShort(Short value) -> bool; + auto writeUnsignedShort(UnsignedShort value) -> bool; + auto writeInt(Int value) -> bool; + auto writeUnsignedInt(UnsignedInt value) -> bool; + auto writeLong(Long value) -> bool; + auto writeUnsignedLong(UnsignedLong value) -> bool; + auto writeFloat(Float value) -> bool; + auto writeDouble(Double value) -> bool; + auto writeArray(Containers::ArrayView array) -> bool; + template + auto writeString(const char(&str)[size]) -> bool { + return writeArray({str, size - 1}); + } + + template + auto writeStaticArray(Containers::StaticArrayView array) -> bool { + return std::fwrite(array.data(), sizeof(char), S, _file) == S; + } + + auto writeUEString(const std::string& str) -> bool; + + template::value, T, T&>> + auto writeValueToArray(U value) -> UnsignedLong { + Containers::ArrayView view{&value, 1}; + return writeDataToArray(view); + } + + auto writeUEStringToArray(const std::string& value) -> UnsignedLong; + + template + void writeValueToArrayAt(T& value, UnsignedLong position) { + Containers::ArrayView view{&value, 1}; + writeDataToArrayAt(view, position); + } + + template + auto writeDataToArray(Containers::ArrayView view) -> UnsignedLong { + arrayAppend(_data, Containers::arrayCast(view)); + _index += sizeof(T) * view.size(); + return sizeof(T) * view.size(); + } + + template + void writeDataToArrayAt(Containers::ArrayView view, UnsignedLong position) { + auto casted_view = Containers::arrayCast(view); + for(UnsignedLong i = 0; i < casted_view.size(); i++) { + _data[position + i] = casted_view[i]; + } + } + + private: + FILE* _file = nullptr; + + Containers::Array _data; + UnsignedLong _index = 0; +}; diff --git a/src/UESaveFile/Debug.cpp b/src/UESaveFile/Debug.cpp new file mode 100644 index 0000000..c11568e --- /dev/null +++ b/src/UESaveFile/Debug.cpp @@ -0,0 +1,76 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "Types/UnrealPropertyBase.h" +#include "Types/ArrayProperty.h" +#include "Types/SetProperty.h" +#include "Types/StructProperty.h" +#include "Types/GenericStructProperty.h" + +#include "Debug.h" + +Utility::Debug& operator<<(Utility::Debug& debug, const ArrayProperty* prop) { + return debug << (*prop->name).c_str() << Utility::Debug::nospace << ":" << + prop->propertyType.c_str() << "of" << prop->items.size() << prop->itemType.c_str(); +} + +Utility::Debug& operator<<(Utility::Debug& debug, const SetProperty* prop) { + return debug << (*prop->name).c_str() << Utility::Debug::nospace << ":" << + prop->propertyType.c_str() << "of" << prop->items.size() << prop->itemType.c_str(); +} + +Utility::Debug& operator<<(Utility::Debug& debug, const GenericStructProperty* prop) { + debug << (*prop->name).c_str() << Utility::Debug::nospace << ":" << + prop->structType.c_str() << "(" << Utility::Debug::nospace << prop->propertyType.c_str() << Utility::Debug::nospace << + ") Contents:"; + for(const auto& item : prop->properties) { + debug << "\n " << Utility::Debug::nospace << item.get(); + } + return debug; +} + +Utility::Debug& operator<<(Utility::Debug& debug, const StructProperty* prop) { + auto cast = dynamic_cast(prop); + if(cast) { + return debug << cast; + } + + return debug << (*prop->name).c_str() << Utility::Debug::nospace << ":" << + prop->structType.c_str() << "(" << Utility::Debug::nospace << prop->propertyType.c_str() << Utility::Debug::nospace << ")"; +} + +Utility::Debug& operator<<(Utility::Debug& debug, const UnrealPropertyBase* prop) { + if(prop->propertyType == "ArrayProperty") { + auto array_prop = dynamic_cast(prop); + if(array_prop) { + return debug << array_prop; + } + } + else if(prop->propertyType == "SetProperty") { + auto set_prop = dynamic_cast(prop); + if(set_prop) { + return debug << set_prop; + } + } + else if(prop->propertyType == "StructProperty") { + auto struct_prop = dynamic_cast(prop); + if(struct_prop) { + return debug << struct_prop; + } + } + + return debug << (*prop->name).c_str() << Utility::Debug::nospace << ":" << prop->propertyType.c_str(); +} diff --git a/src/UESaveFile/Debug.h b/src/UESaveFile/Debug.h new file mode 100644 index 0000000..4a85ca1 --- /dev/null +++ b/src/UESaveFile/Debug.h @@ -0,0 +1,33 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +struct ArrayProperty; +struct SetProperty; +struct GenericStructProperty; +struct StructProperty; +struct UnrealPropertyBase; + +using namespace Corrade; + +Utility::Debug& operator<<(Utility::Debug& debug, const ArrayProperty* prop); +Utility::Debug& operator<<(Utility::Debug& debug, const SetProperty* prop); +Utility::Debug& operator<<(Utility::Debug& debug, const GenericStructProperty* prop); +Utility::Debug& operator<<(Utility::Debug& debug, const StructProperty* prop); +Utility::Debug& operator<<(Utility::Debug& debug, const UnrealPropertyBase* prop); diff --git a/src/UESaveFile/PropertySerialiser.cpp b/src/UESaveFile/PropertySerialiser.cpp new file mode 100644 index 0000000..830e78a --- /dev/null +++ b/src/UESaveFile/PropertySerialiser.cpp @@ -0,0 +1,254 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "Serialisers/ArrayPropertySerialiser.h" +#include "Serialisers/BoolPropertySerialiser.h" +#include "Serialisers/BytePropertySerialiser.h" +#include "Serialisers/ColourPropertySerialiser.h" +#include "Serialisers/DateTimePropertySerialiser.h" +#include "Serialisers/EnumPropertySerialiser.h" +#include "Serialisers/FloatPropertySerialiser.h" +#include "Serialisers/GuidPropertySerialiser.h" +#include "Serialisers/IntPropertySerialiser.h" +#include "Serialisers/MapPropertySerialiser.h" +#include "Serialisers/ResourcePropertySerialiser.h" +#include "Serialisers/RotatorPropertySerialiser.h" +#include "Serialisers/StringPropertySerialiser.h" +#include "Serialisers/SetPropertySerialiser.h" +#include "Serialisers/StructSerialiser.h" +#include "Serialisers/TextPropertySerialiser.h" +#include "Serialisers/VectorPropertySerialiser.h" +#include "Serialisers/Vector2DPropertySerialiser.h" + +#include "Types/NoneProperty.h" + +#include "BinaryReader.h" +#include "BinaryWriter.h" + +#include "PropertySerialiser.h" + + +PropertySerialiser::PropertySerialiser() { + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + arrayAppend(_serialisers, Containers::pointer()); + + arrayAppend(_collectionSerialisers, Containers::pointer()); +} + +auto PropertySerialiser::read(BinaryReader& reader) -> UnrealPropertyBase::ptr { + if(reader.peekChar() < 0 || reader.eof()) { + return nullptr; + } + + std::string name; + if(!reader.readUEString(name)) { + return nullptr; + } + + if(name == "None") { + return Containers::pointer(); + } + + std::string type; + if(!reader.readUEString(type)) { + return nullptr; + } + + UnsignedLong value_length; + if(!reader.readUnsignedLong(value_length)) { + return nullptr; + } + + return deserialise(std::move(name), std::move(type), value_length, reader); +} + +auto PropertySerialiser::readItem(BinaryReader& reader, std::string type, UnsignedLong value_length, std::string name) -> UnrealPropertyBase::ptr { + if(reader.peekChar() < 0 || reader.eof()) { + return nullptr; + } + + return deserialise(std::move(name), std::move(type), value_length, reader); +} + +auto PropertySerialiser::readSet(BinaryReader& reader, const std::string& item_type, UnsignedInt count) -> Containers::Array { + if(reader.peekChar() < 0 || reader.eof()) { + return nullptr; + } + + auto serialiser = getCollectionSerialiser(item_type); + + Containers::Array array; + + if(serialiser) { + std::string name; + if(!reader.readUEString(name)) { + return nullptr; + } + + std::string type; + if(!reader.readUEString(type)) { + return nullptr; + } + + UnsignedLong value_length; + if(!reader.readUnsignedLong(value_length)) { + return nullptr; + } + + array = serialiser->deserialise(name, type, value_length, count, reader, *this); + + for(auto& item : array) { + if(item->name == Containers::NullOpt) { + item->name.emplace(name); + } + } + } + else { + for(UnsignedInt i = 0; i < count; i++) { + auto item = readItem(reader, item_type, UnsignedLong(-1), ""); + arrayAppend(array, std::move(item)); + } + } + + return array; +} + +auto PropertySerialiser::deserialise(std::string name, std::string type, UnsignedLong value_length, + BinaryReader& reader) -> UnrealPropertyBase::ptr +{ + UnrealPropertyBase::ptr prop; + auto serialiser = getSerialiser(type); + + if(serialiser == nullptr) { + return nullptr; + } + + prop = serialiser->deserialise(name, type, value_length, reader, *this); + + if(!prop) { + !Utility::Error{} << "No prop in" << __func__; + return nullptr; + } + + prop->name = std::move(name); + prop->propertyType = std::move(type); + + return prop; +} + +auto PropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, const std::string& item_type, UnsignedLong& bytes_written, + BinaryWriter& writer) -> bool +{ + auto serialiser = getSerialiser(item_type); + if(!serialiser) { + return false; + } + return serialiser->serialise(prop, bytes_written, writer, *this); +} + +auto PropertySerialiser::write(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer) -> bool { + if(prop->name == "None" && prop->propertyType == "NoneProperty" && dynamic_cast(prop.get())) { + bytes_written += writer.writeUEStringToArray(*prop->name); + return true; + } + + bytes_written += writer.writeUEStringToArray(*prop->name); + bytes_written += writer.writeUEStringToArray(prop->propertyType); + + UnsignedLong value_length = 0; + UnsignedLong vl_position = writer.arrayPosition(); + + bytes_written += writer.writeValueToArray(value_length); + + bool ret = serialise(prop, prop->propertyType, value_length, writer); + + writer.writeValueToArrayAt(value_length, vl_position); + + bytes_written += value_length; + + return ret; +} + +auto PropertySerialiser::writeItem(UnrealPropertyBase::ptr& prop, const std::string& item_type, + UnsignedLong& bytes_written, BinaryWriter& writer) -> bool +{ + if(prop->name == "None" && prop->propertyType == "NoneProperty" && dynamic_cast(prop.get())) { + bytes_written += writer.writeUEStringToArray(*prop->name); + return true; + } + + return serialise(prop, item_type, bytes_written, writer); +} + +auto PropertySerialiser::writeSet(Containers::ArrayView props, const std::string& item_type, + UnsignedLong& bytes_written, BinaryWriter& writer) -> bool +{ + auto serialiser = getCollectionSerialiser(item_type); + if(serialiser) { + return serialiser->serialise(props, item_type, bytes_written, writer, *this); + } + else { + for(auto& prop : props) { + if(!writeItem(prop, item_type, bytes_written, writer)) { + return false; + } + } + + return true; + } +} + +auto PropertySerialiser::getSerialiser(const std::string& item_type) -> AbstractUnrealPropertySerialiser* { + for(auto& item : _serialisers) { + for(const std::string& serialiser_type : item->types()) { + if(item_type == serialiser_type) { + return item.get(); + } + } + } + + return nullptr; +} + +auto PropertySerialiser::getCollectionSerialiser(const std::string& item_type) -> AbstractUnrealCollectionPropertySerialiser* { + for(auto& item : _collectionSerialisers) { + for(const std::string& serialiser_type : item->types()) { + if(item_type == serialiser_type) { + return item.get(); + } + } + } + + return nullptr; +} diff --git a/src/UESaveFile/PropertySerialiser.h b/src/UESaveFile/PropertySerialiser.h new file mode 100644 index 0000000..546cd72 --- /dev/null +++ b/src/UESaveFile/PropertySerialiser.h @@ -0,0 +1,51 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "Serialisers/AbstractUnrealPropertySerialiser.h" +#include "Serialisers/AbstractUnrealCollectionPropertySerialiser.h" + +#include "Types/UnrealPropertyBase.h" + +using namespace Corrade; + +class BinaryReader; +class BinaryWriter; + +class PropertySerialiser { + public: + PropertySerialiser(); + + auto read(BinaryReader& reader) -> UnrealPropertyBase::ptr; + auto readItem(BinaryReader& reader, std::string type, UnsignedLong value_length, std::string name) -> UnrealPropertyBase::ptr; + auto readSet(BinaryReader& reader, const std::string& item_type, UnsignedInt count) -> Containers::Array; + auto deserialise(std::string name, std::string type, UnsignedLong value_length, BinaryReader& reader) -> UnrealPropertyBase::ptr; + + auto serialise(UnrealPropertyBase::ptr& prop, const std::string& item_type, UnsignedLong& bytes_written, BinaryWriter& writer) -> bool; + auto write(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer) -> bool; + auto writeItem(UnrealPropertyBase::ptr& prop, const std::string& item_type, UnsignedLong& bytes_written, BinaryWriter& writer) -> bool; + auto writeSet(Containers::ArrayView props, const std::string& item_type, UnsignedLong& bytes_written, BinaryWriter& writer) -> bool; + + private: + auto getSerialiser(const std::string& item_type) -> AbstractUnrealPropertySerialiser*; + auto getCollectionSerialiser(const std::string& item_type) -> AbstractUnrealCollectionPropertySerialiser*; + + Containers::Array _serialisers; + Containers::Array _collectionSerialisers; +}; diff --git a/src/UESaveFile/Serialisers/AbstractUnrealCollectionPropertySerialiser.h b/src/UESaveFile/Serialisers/AbstractUnrealCollectionPropertySerialiser.h new file mode 100644 index 0000000..c672495 --- /dev/null +++ b/src/UESaveFile/Serialisers/AbstractUnrealCollectionPropertySerialiser.h @@ -0,0 +1,47 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../Types/UnrealPropertyBase.h" + +using namespace Corrade; +using namespace Magnum; + +class BinaryReader; +class BinaryWriter; +class PropertySerialiser; + +class AbstractUnrealCollectionPropertySerialiser { + public: + using ptr = Containers::Pointer; + + virtual ~AbstractUnrealCollectionPropertySerialiser() = default; + + virtual auto types() -> Containers::ArrayView = 0; + + virtual auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, UnsignedInt count, BinaryReader& reader, PropertySerialiser& serialiser) -> Containers::Array = 0; + + virtual auto serialise(Containers::ArrayView props, const std::string& item_type, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool = 0; +}; diff --git a/src/UESaveFile/Serialisers/AbstractUnrealPropertySerialiser.h b/src/UESaveFile/Serialisers/AbstractUnrealPropertySerialiser.h new file mode 100644 index 0000000..21c42eb --- /dev/null +++ b/src/UESaveFile/Serialisers/AbstractUnrealPropertySerialiser.h @@ -0,0 +1,46 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../Types/UnrealPropertyBase.h" + +using namespace Corrade; +using namespace Magnum; + +class BinaryReader; +class BinaryWriter; +class PropertySerialiser; + +class AbstractUnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + virtual ~AbstractUnrealPropertySerialiser() = default; + + virtual auto types() -> Containers::ArrayView = 0; + + virtual auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr = 0; + + virtual auto serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool = 0; +}; diff --git a/src/UESaveFile/Serialisers/AbstractUnrealStructSerialiser.h b/src/UESaveFile/Serialisers/AbstractUnrealStructSerialiser.h new file mode 100644 index 0000000..0d8d003 --- /dev/null +++ b/src/UESaveFile/Serialisers/AbstractUnrealStructSerialiser.h @@ -0,0 +1,46 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../Types/UnrealPropertyBase.h" + +using namespace Corrade; +using namespace Magnum; + +class BinaryReader; +class BinaryWriter; + +class AbstractUnrealStructSerialiser { + public: + using ptr = Containers::Pointer; + + virtual ~AbstractUnrealStructSerialiser() = default; + + virtual auto supportsType(const std::string& type) -> bool = 0; + + virtual auto deserialise(BinaryReader& reader) -> UnrealPropertyBase::ptr = 0; + + virtual auto serialise(UnrealPropertyBase::ptr& structProp, BinaryWriter& writer, UnsignedLong& bytes_written) -> bool = 0; +}; diff --git a/src/UESaveFile/Serialisers/ArrayPropertySerialiser.cpp b/src/UESaveFile/Serialisers/ArrayPropertySerialiser.cpp new file mode 100644 index 0000000..7bb6196 --- /dev/null +++ b/src/UESaveFile/Serialisers/ArrayPropertySerialiser.cpp @@ -0,0 +1,66 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" +#include "../PropertySerialiser.h" + +#include "ArrayPropertySerialiser.h" + +auto ArrayPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + std::string item_type; + if(!reader.readUEString(item_type)) { + return nullptr; + } + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + UnsignedInt item_count; + if(!reader.readUnsignedInt(item_count)) { + return nullptr; + } + + auto prop = Containers::pointer(); + prop->itemType = std::move(item_type); + prop->items = serialiser.readSet(reader, prop->itemType, item_count); + + return prop; +} + +auto ArrayPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto array_prop = dynamic_cast(prop.get()); + if(!array_prop) { + return false; + } + + writer.writeUEStringToArray(array_prop->itemType); + writer.writeValueToArray('\0'); + bytes_written += writer.writeValueToArray(UnsignedInt(array_prop->items.size())); + + UnsignedLong start_pos = writer.arrayPosition(); + UnsignedLong dummy_bytes_written = 0; + bool ret = serialiser.writeSet(array_prop->items, array_prop->itemType, dummy_bytes_written, writer); + bytes_written += writer.arrayPosition() - start_pos; + + return ret; +} diff --git a/src/UESaveFile/Serialisers/ArrayPropertySerialiser.h b/src/UESaveFile/Serialisers/ArrayPropertySerialiser.h new file mode 100644 index 0000000..b5c2530 --- /dev/null +++ b/src/UESaveFile/Serialisers/ArrayPropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/ArrayProperty.h" + +class ArrayPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/BoolPropertySerialiser.cpp b/src/UESaveFile/Serialisers/BoolPropertySerialiser.cpp new file mode 100644 index 0000000..ee1c4fa --- /dev/null +++ b/src/UESaveFile/Serialisers/BoolPropertySerialiser.cpp @@ -0,0 +1,61 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "BoolPropertySerialiser.h" + +auto BoolPropertySerialiser::types() -> Containers::ArrayView { + static const Containers::Array types{InPlaceInit, {"BoolProperty"}}; + return types; +} + +auto BoolPropertySerialiser::deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + if(value_length != 0) { + return nullptr; + } + + Short value; + if(!reader.readShort(value)) { + return nullptr; + } + + if(value > 1 || value < 0) { + return nullptr; + } + + auto prop = Containers::pointer(); + prop->value = value; + + return prop; +} + +auto BoolPropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto bool_prop = dynamic_cast(prop.get()); + + if(!bool_prop) { + return false; + } + + writer.writeValueToArray(Short(bool_prop->value)); + + return true; +} diff --git a/src/UESaveFile/Serialisers/BoolPropertySerialiser.h b/src/UESaveFile/Serialisers/BoolPropertySerialiser.h new file mode 100644 index 0000000..373fca0 --- /dev/null +++ b/src/UESaveFile/Serialisers/BoolPropertySerialiser.h @@ -0,0 +1,32 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "AbstractUnrealPropertySerialiser.h" + +#include "../Types/BoolProperty.h" + +class BoolPropertySerialiser : public AbstractUnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + auto types() -> Containers::ArrayView override; + + auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + + auto serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/BytePropertySerialiser.cpp b/src/UESaveFile/Serialisers/BytePropertySerialiser.cpp new file mode 100644 index 0000000..96ad39f --- /dev/null +++ b/src/UESaveFile/Serialisers/BytePropertySerialiser.cpp @@ -0,0 +1,82 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "BytePropertySerialiser.h" + +auto BytePropertySerialiser::types() -> Containers::ArrayView { + static const Containers::Array types{InPlaceInit, {"ByteProperty"}}; + return types; +} + +auto BytePropertySerialiser::deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(value_length != UnsignedLong(-1)) { + if(!reader.readUEString(prop->enumType)) { + return nullptr; + } + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + } + + if(!reader.readUEString(prop->enumValue)) { + return nullptr; + } + + prop->valueLength = value_length; + + //UnsignedInt count = 0; + //if(!reader.readUnsignedInt(count)) { + // return nullptr; + //} + + //if(!reader.readArray(prop->value, count)) { + // return nullptr; + //} + + return prop; +} + +auto BytePropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto byte_prop = dynamic_cast(prop.get()); + + if(!byte_prop) { + return false; + } + + //writer.writeValueToArray('\0'); + //bytes_written += writer.writeValueToArray(byte_prop->value.size()); + //bytes_written += writer.writeDataToArray(byte_prop->value); + + if(byte_prop->valueLength != UnsignedLong(-1)) { + writer.writeUEStringToArray(byte_prop->enumType); + writer.writeValueToArray('\0'); + } + + bytes_written += writer.writeUEStringToArray(byte_prop->enumValue); + + return true; +} diff --git a/src/UESaveFile/Serialisers/BytePropertySerialiser.h b/src/UESaveFile/Serialisers/BytePropertySerialiser.h new file mode 100644 index 0000000..cdf5fbe --- /dev/null +++ b/src/UESaveFile/Serialisers/BytePropertySerialiser.h @@ -0,0 +1,32 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "AbstractUnrealPropertySerialiser.h" + +#include "../Types/ByteProperty.h" + +class BytePropertySerialiser : public AbstractUnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + auto types() -> Containers::ArrayView override; + + auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + + auto serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/ColourPropertySerialiser.cpp b/src/UESaveFile/Serialisers/ColourPropertySerialiser.cpp new file mode 100644 index 0000000..a263311 --- /dev/null +++ b/src/UESaveFile/Serialisers/ColourPropertySerialiser.cpp @@ -0,0 +1,49 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "ColourPropertySerialiser.h" + +auto ColourPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(!reader.readFloat(prop->r) || !reader.readFloat(prop->g) || + !reader.readFloat(prop->b) || !reader.readFloat(prop->a)) + { + return nullptr; + } + + return prop; +} + +auto ColourPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto colour_prop = dynamic_cast(prop.get()); + + if(!colour_prop) { + return false; + } + + bytes_written += writer.writeValueToArray(colour_prop->r) + writer.writeValueToArray(colour_prop->g) + + writer.writeValueToArray(colour_prop->b) + writer.writeValueToArray(colour_prop->a); + + return true; +} diff --git a/src/UESaveFile/Serialisers/ColourPropertySerialiser.h b/src/UESaveFile/Serialisers/ColourPropertySerialiser.h new file mode 100644 index 0000000..5a6cbab --- /dev/null +++ b/src/UESaveFile/Serialisers/ColourPropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/ColourStructProperty.h" + +class ColourPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/DateTimePropertySerialiser.cpp b/src/UESaveFile/Serialisers/DateTimePropertySerialiser.cpp new file mode 100644 index 0000000..b06db6e --- /dev/null +++ b/src/UESaveFile/Serialisers/DateTimePropertySerialiser.cpp @@ -0,0 +1,46 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "DateTimePropertySerialiser.h" + +auto DateTimePropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(!reader.readUnsignedLong(prop->timestamp)) { + return nullptr; + } + + return prop; +} + +auto DateTimePropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto dt_prop = dynamic_cast(prop.get()); + + if(!dt_prop) { + return false; + } + + bytes_written += writer.writeValueToArray(dt_prop->timestamp); + + return true; +} diff --git a/src/UESaveFile/Serialisers/DateTimePropertySerialiser.h b/src/UESaveFile/Serialisers/DateTimePropertySerialiser.h new file mode 100644 index 0000000..abaf5e2 --- /dev/null +++ b/src/UESaveFile/Serialisers/DateTimePropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/DateTimeStructProperty.h" + +class DateTimePropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/EnumPropertySerialiser.cpp b/src/UESaveFile/Serialisers/EnumPropertySerialiser.cpp new file mode 100644 index 0000000..7ee43d8 --- /dev/null +++ b/src/UESaveFile/Serialisers/EnumPropertySerialiser.cpp @@ -0,0 +1,62 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "EnumPropertySerialiser.h" + +auto EnumPropertySerialiser::types() -> Containers::ArrayView { + static const Containers::Array types{InPlaceInit, {"EnumProperty"}}; + return types; +} + +auto EnumPropertySerialiser::deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(!reader.readUEString(prop->enumType)) { + return nullptr; + } + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + if(!reader.readUEString(prop->value)) { + return nullptr; + } + + return prop; +} + +auto EnumPropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto enum_prop = dynamic_cast(prop.get()); + + if(!enum_prop) { + return false; + } + + writer.writeUEStringToArray(enum_prop->enumType); + writer.writeValueToArray('\0'); + bytes_written += writer.writeUEStringToArray(enum_prop->value); + + return true; +} diff --git a/src/UESaveFile/Serialisers/EnumPropertySerialiser.h b/src/UESaveFile/Serialisers/EnumPropertySerialiser.h new file mode 100644 index 0000000..d41c628 --- /dev/null +++ b/src/UESaveFile/Serialisers/EnumPropertySerialiser.h @@ -0,0 +1,32 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "AbstractUnrealPropertySerialiser.h" + +#include "../Types/EnumProperty.h" + +class EnumPropertySerialiser : public AbstractUnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + auto types() -> Containers::ArrayView override; + + auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + + auto serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/FloatPropertySerialiser.cpp b/src/UESaveFile/Serialisers/FloatPropertySerialiser.cpp new file mode 100644 index 0000000..06ce073 --- /dev/null +++ b/src/UESaveFile/Serialisers/FloatPropertySerialiser.cpp @@ -0,0 +1,57 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "FloatPropertySerialiser.h" + +auto FloatPropertySerialiser::types() -> Containers::ArrayView { + static const Containers::Array types{InPlaceInit, {"FloatProperty"}}; + return types; +} + +auto FloatPropertySerialiser::deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + if(!reader.readFloat(prop->value)) { + return nullptr; + } + + return prop; +} + +auto FloatPropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto float_prop = dynamic_cast(prop.get()); + + if(!float_prop) { + return false; + } + + writer.writeValueToArray('\0'); + bytes_written += writer.writeValueToArray(float_prop->value); + + return true; +} diff --git a/src/UESaveFile/Serialisers/FloatPropertySerialiser.h b/src/UESaveFile/Serialisers/FloatPropertySerialiser.h new file mode 100644 index 0000000..34a96b0 --- /dev/null +++ b/src/UESaveFile/Serialisers/FloatPropertySerialiser.h @@ -0,0 +1,32 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "AbstractUnrealPropertySerialiser.h" + +#include "../Types/FloatProperty.h" + +class FloatPropertySerialiser : public AbstractUnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + auto types() -> Containers::ArrayView override; + + auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + + auto serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/GuidPropertySerialiser.cpp b/src/UESaveFile/Serialisers/GuidPropertySerialiser.cpp new file mode 100644 index 0000000..8576c34 --- /dev/null +++ b/src/UESaveFile/Serialisers/GuidPropertySerialiser.cpp @@ -0,0 +1,47 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "GuidPropertySerialiser.h" + +auto GuidPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(!reader.readStaticArray(prop->guid)) { + Utility::Error{} << "Couldn't read guid in" << __func__; + return nullptr; + } + + return prop; +} + +auto GuidPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto guid_prop = dynamic_cast(prop.get()); + + if(!guid_prop) { + return false; + } + + bytes_written += writer.writeDataToArray({guid_prop->guid.data(), guid_prop->guid.size()}); + + return true; +} diff --git a/src/UESaveFile/Serialisers/GuidPropertySerialiser.h b/src/UESaveFile/Serialisers/GuidPropertySerialiser.h new file mode 100644 index 0000000..c8ade5e --- /dev/null +++ b/src/UESaveFile/Serialisers/GuidPropertySerialiser.h @@ -0,0 +1,31 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/GuidStructProperty.h" + +class GuidPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/IntPropertySerialiser.cpp b/src/UESaveFile/Serialisers/IntPropertySerialiser.cpp new file mode 100644 index 0000000..c5b3e4e --- /dev/null +++ b/src/UESaveFile/Serialisers/IntPropertySerialiser.cpp @@ -0,0 +1,66 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "IntPropertySerialiser.h" + +auto IntPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(value_length == UnsignedLong(-1)) { + if(!reader.readInt(prop->value)) { + return nullptr; + } + + prop->valueLength = UnsignedLong(-1); + return prop; + } + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + if(!reader.readInt(prop->value)) { + return nullptr; + } + + prop->name.emplace(name); + + return prop; +} + +auto IntPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto int_prop = dynamic_cast(prop.get()); + + if(!int_prop) { + return false; + } + + if(prop->valueLength != UnsignedLong(-1)) { + writer.writeValueToArray('\0'); + } + + bytes_written += writer.writeValueToArray(int_prop->value); + + return true; +} diff --git a/src/UESaveFile/Serialisers/IntPropertySerialiser.h b/src/UESaveFile/Serialisers/IntPropertySerialiser.h new file mode 100644 index 0000000..1301f05 --- /dev/null +++ b/src/UESaveFile/Serialisers/IntPropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/IntProperty.h" + +class IntPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/MapPropertySerialiser.cpp b/src/UESaveFile/Serialisers/MapPropertySerialiser.cpp new file mode 100644 index 0000000..b1a6046 --- /dev/null +++ b/src/UESaveFile/Serialisers/MapPropertySerialiser.cpp @@ -0,0 +1,140 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" +#include "../PropertySerialiser.h" + +#include "../Types/NoneProperty.h" + +#include "MapPropertySerialiser.h" + +auto MapPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(!reader.readUEString(prop->keyType)) { + return nullptr; + } + + if(!reader.readUEString(prop->valueType)) { + return nullptr; + } + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + UnsignedInt null; + if(!reader.readUnsignedInt(null) || null != 0u) { + return nullptr; + } + + UnsignedInt count; + if(!reader.readUnsignedInt(count)) { + return nullptr; + } + + // Begin dirty code because the MapProperty format doesn't seem to match any of the GVAS reading stuff I've found, + // so I'm just gonna write stuff that matches the only MapProperty I can find in MB's save files. + + arrayReserve(prop->map, count); + + for(UnsignedInt i = 0; i < count; i++) { + MapProperty::KeyValuePair pair; + + if(prop->keyType == "IntProperty" || prop->keyType == "StrProperty") { + pair.key = serialiser.readItem(reader, prop->keyType, -1, name); + if(pair.key == nullptr) { + return nullptr; + } + } + else { // Add other branches depending on key type, should more maps appear in the future. + return nullptr; + } + + UnrealPropertyBase::ptr value_item; + if(prop->valueType == "StructProperty") { + while((value_item = serialiser.read(reader)) != nullptr) { + arrayAppend(pair.values, std::move(value_item)); + + if(pair.values.back()->name == "None" && + pair.values.back()->propertyType == "NoneProperty" && + dynamic_cast(pair.values.back().get()) != nullptr) + { + break; + } + } + } + else if(prop->valueType == "ByteProperty") { + if((value_item = serialiser.readItem(reader, prop->valueType, -1, name)) == nullptr) { + return nullptr; + } + + arrayAppend(pair.values, std::move(value_item)); + } + + arrayAppend(prop->map, std::move(pair)); + } + + // End dirty code + + return prop; +} + +auto MapPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto map_prop = dynamic_cast(prop.get()); + if(!map_prop) { + return false; + } + + writer.writeUEStringToArray(map_prop->keyType); + writer.writeUEStringToArray(map_prop->valueType); + writer.writeValueToArray('\0'); + + UnsignedLong value_start = writer.arrayPosition(); + writer.writeValueToArray(0u); + + writer.writeValueToArray(UnsignedInt(map_prop->map.size())); + + UnsignedLong dummy_bytes_written = 0; + for(auto& pair : map_prop->map) { + if(!serialiser.writeItem(pair.key, map_prop->keyType, dummy_bytes_written, writer)) { + return false; + } + + for(auto& value : pair.values) { + if(map_prop->valueType == "StructProperty") { + if(!serialiser.write(value, dummy_bytes_written, writer)) { + return false; + } + } + else { + if(!serialiser.writeItem(value, map_prop->valueType, dummy_bytes_written, writer)) { + return false; + } + } + } + } + + bytes_written += (writer.arrayPosition() - value_start); + + return true; +} diff --git a/src/UESaveFile/Serialisers/MapPropertySerialiser.h b/src/UESaveFile/Serialisers/MapPropertySerialiser.h new file mode 100644 index 0000000..cbe1402 --- /dev/null +++ b/src/UESaveFile/Serialisers/MapPropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/MapProperty.h" + +class MapPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/ResourcePropertySerialiser.cpp b/src/UESaveFile/Serialisers/ResourcePropertySerialiser.cpp new file mode 100644 index 0000000..44a83d0 --- /dev/null +++ b/src/UESaveFile/Serialisers/ResourcePropertySerialiser.cpp @@ -0,0 +1,103 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" +#include "../PropertySerialiser.h" + +#include "../Types/IntProperty.h" +#include "../Types/NoneProperty.h" + +#include "ResourcePropertySerialiser.h" + +auto ResourcePropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + std::string str; + if(!reader.readUEString(str) || str != "ID_4_AAE08F17428E229EC7A2209F51081A21") { + return nullptr; + } + + if(!reader.readUEString(str) || str != "IntProperty") { + return nullptr; + } + + if(!reader.readUnsignedLong(value_length) || value_length != 4ull) { + return nullptr; + } + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + if(!reader.readInt(prop->id)) { + return nullptr; + } + + if(!reader.readUEString(str) || str != "Quantity_3_560F09B5485C365D3041888910019CE3") { + return nullptr; + } + + if(!reader.readUEString(str) || str != "IntProperty") { + return nullptr; + } + + if(!reader.readUnsignedLong(value_length) || value_length != 4ull) { + return nullptr; + } + + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + if(!reader.readInt(prop->quantity)) { + return nullptr; + } + + if(!reader.readUEString(str) || str != "None") { + return nullptr; + } + + return prop; +} + +auto ResourcePropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto res_prop = dynamic_cast(prop.get()); + if(!res_prop) { + return false; + } + + bytes_written += writer.writeUEStringToArray("ID_4_AAE08F17428E229EC7A2209F51081A21") + + writer.writeUEStringToArray("IntProperty") + + writer.writeValueToArray(4ull) + + writer.writeValueToArray('\0') + + writer.writeValueToArray(res_prop->id); + + bytes_written += writer.writeUEStringToArray("Quantity_3_560F09B5485C365D3041888910019CE3") + + writer.writeUEStringToArray("IntProperty") + + writer.writeValueToArray(4ull) + + writer.writeValueToArray('\0') + + writer.writeValueToArray(res_prop->quantity); + + bytes_written += writer.writeUEStringToArray("None"); + + return true; +} diff --git a/src/UESaveFile/Serialisers/ResourcePropertySerialiser.h b/src/UESaveFile/Serialisers/ResourcePropertySerialiser.h new file mode 100644 index 0000000..e32439b --- /dev/null +++ b/src/UESaveFile/Serialisers/ResourcePropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/ResourceItemValue.h" + +class ResourcePropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/RotatorPropertySerialiser.cpp b/src/UESaveFile/Serialisers/RotatorPropertySerialiser.cpp new file mode 100644 index 0000000..b69633e --- /dev/null +++ b/src/UESaveFile/Serialisers/RotatorPropertySerialiser.cpp @@ -0,0 +1,47 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "RotatorPropertySerialiser.h" + +auto RotatorPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(!reader.readFloat(prop->x) || !reader.readFloat(prop->y) || !reader.readFloat(prop->z)) { + return nullptr; + } + + return prop; +} + +auto RotatorPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto rotator = dynamic_cast(prop.get()); + + if(!rotator) { + return false; + } + + bytes_written += writer.writeValueToArray(rotator->x) + writer.writeValueToArray(rotator->y) + + writer.writeValueToArray(rotator->z); + + return true; +} diff --git a/src/UESaveFile/Serialisers/RotatorPropertySerialiser.h b/src/UESaveFile/Serialisers/RotatorPropertySerialiser.h new file mode 100644 index 0000000..1f05573 --- /dev/null +++ b/src/UESaveFile/Serialisers/RotatorPropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/RotatorStructProperty.h" + +class RotatorPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/SetPropertySerialiser.cpp b/src/UESaveFile/Serialisers/SetPropertySerialiser.cpp new file mode 100644 index 0000000..69a20af --- /dev/null +++ b/src/UESaveFile/Serialisers/SetPropertySerialiser.cpp @@ -0,0 +1,73 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" +#include "../PropertySerialiser.h" + +#include "SetPropertySerialiser.h" + +auto SetPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + std::string item_type; + if(!reader.readUEString(item_type)) { + return nullptr; + } + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + UnsignedInt four_bytes; + if(!reader.readUnsignedInt(four_bytes) || four_bytes != 0u) { + return nullptr; + } + + UnsignedInt item_count; + if(!reader.readUnsignedInt(item_count)) { + return nullptr; + } + + auto prop = Containers::pointer(); + prop->itemType = std::move(item_type); + prop->items = serialiser.readSet(reader, prop->itemType, item_count); + + return prop; +} + +auto SetPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto set_prop = dynamic_cast(prop.get()); + if(!set_prop) { + return false; + } + + writer.writeUEStringToArray(set_prop->itemType); + writer.writeValueToArray('\0'); + + bytes_written += writer.writeValueToArray(0u); + bytes_written += writer.writeValueToArray(UnsignedInt(set_prop->items.size())); + + UnsignedLong start_pos = writer.arrayPosition(); + UnsignedLong dummy_bytes_written = 0; + serialiser.writeSet(set_prop->items, set_prop->itemType, dummy_bytes_written, writer); + bytes_written += writer.arrayPosition() - start_pos; + + return true; +} diff --git a/src/UESaveFile/Serialisers/SetPropertySerialiser.h b/src/UESaveFile/Serialisers/SetPropertySerialiser.h new file mode 100644 index 0000000..44f1a6a --- /dev/null +++ b/src/UESaveFile/Serialisers/SetPropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/SetProperty.h" + +class SetPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/StringPropertySerialiser.cpp b/src/UESaveFile/Serialisers/StringPropertySerialiser.cpp new file mode 100644 index 0000000..047b25e --- /dev/null +++ b/src/UESaveFile/Serialisers/StringPropertySerialiser.cpp @@ -0,0 +1,64 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "StringPropertySerialiser.h" + +auto StringPropertySerialiser::types() -> Containers::ArrayView { + static const Containers::Array types{InPlaceInit, {"NameProperty", "StrProperty", "SoftObjectProperty", "ObjectProperty"}}; + return types; +} + +auto StringPropertySerialiser::deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(type); + + if(value_length != UnsignedLong(-1)) { + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + } + + if(!reader.readUEString(prop->value)) { + return nullptr; + } + + prop->valueLength = value_length; + + return prop; +} + +auto StringPropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto str_prop = dynamic_cast(prop.get()); + + if(!str_prop) { + return false; + } + + if(str_prop->valueLength != UnsignedLong(-1)) { + writer.writeValueToArray('\0'); + } + + bytes_written += writer.writeUEStringToArray(str_prop->value); + + return true; +} diff --git a/src/UESaveFile/Serialisers/StringPropertySerialiser.h b/src/UESaveFile/Serialisers/StringPropertySerialiser.h new file mode 100644 index 0000000..a4a1924 --- /dev/null +++ b/src/UESaveFile/Serialisers/StringPropertySerialiser.h @@ -0,0 +1,32 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "AbstractUnrealPropertySerialiser.h" + +#include "../Types/StringProperty.h" + +class StringPropertySerialiser : public AbstractUnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + auto types() -> Containers::ArrayView override; + + auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + + auto serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/StructSerialiser.cpp b/src/UESaveFile/Serialisers/StructSerialiser.cpp new file mode 100644 index 0000000..cecfb7a --- /dev/null +++ b/src/UESaveFile/Serialisers/StructSerialiser.cpp @@ -0,0 +1,218 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" +#include "../PropertySerialiser.h" + +#include "../Types/GenericStructProperty.h" +#include "../Types/NoneProperty.h" + +#include "StructSerialiser.h" + +auto StructSerialiser::types() -> Containers::ArrayView { + static const Containers::Array types{InPlaceInit, {"StructProperty"}}; + return types; +} + +auto StructSerialiser::deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, + UnsignedInt count, BinaryReader& reader, PropertySerialiser& serialiser) -> Containers::Array +{ + std::string item_type; + if(!reader.readUEString(item_type)) { + return nullptr; + } + + Containers::StaticArray<16, char> guid{ValueInit}; + if(!reader.readStaticArray(guid)) { + return nullptr; + } + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + Containers::Array array; + + if(count == 0) { + auto prop = Containers::pointer(); + prop->structType = std::move(item_type); + prop->structGuid = std::move(guid); + } + else { + for(UnsignedInt i = 0; i < count; i++) { + auto prop = Containers::pointer(); + + prop = serialiser.readItem(reader, item_type, UnsignedLong(-1), name); + + if(!prop) { + prop = readStructValue(name, item_type, value_length, reader, serialiser); + } + + if(!prop) { + return nullptr; + } + + static_cast(prop.get())->structGuid = guid; + + arrayAppend(array, std::move(prop)); + } + } + + return array; +} + +auto StructSerialiser::deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + std::string item_type; + if(!reader.readUEString(item_type)) { + return nullptr; + } + + if(item_type == "None") { + return Containers::pointer(); + } + + Containers::StaticArray<16, char> guid{ValueInit}; + if(!reader.readStaticArray(guid)) { + return nullptr; + } + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + UnrealPropertyBase::ptr prop = serialiser.readItem(reader, item_type, value_length, name); + + if(!prop) { + prop = readStructValue(name, item_type, value_length, reader, serialiser); + if(prop) { + dynamic_cast(prop.get())->structGuid = std::move(guid); + } + } + + return prop; +} + +auto StructSerialiser::serialise(Containers::ArrayView props, const std::string& item_type, + UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + bytes_written += writer.writeUEStringToArray(*(props.front()->name)); + bytes_written += writer.writeUEStringToArray(item_type); + UnsignedLong vl_pos = writer.arrayPosition(); + bytes_written += writer.writeValueToArray(0ull); + + auto struct_prop = dynamic_cast(props.front().get()); + if(!struct_prop) { + return false; + } + + bytes_written += writer.writeUEStringToArray(struct_prop->structType); + bytes_written += writer.writeDataToArray(arrayView(struct_prop->structGuid)); + bytes_written += writer.writeValueToArray('\0'); + + UnsignedLong vl_start = writer.arrayPosition(); + + UnsignedLong bytes_written_here = 0; + for(auto& prop : props) { + struct_prop = dynamic_cast(prop.get()); + + if(!struct_prop) { + return false; + } + + if(!serialiser.writeItem(prop, struct_prop->structType, bytes_written_here, writer)) { + if(!writeStructValue(struct_prop, bytes_written_here, writer, serialiser)) { + return false; + } + } + } + + UnsignedLong vl_stop = writer.arrayPosition() - vl_start; + writer.writeValueToArrayAt(vl_stop, vl_pos); + bytes_written += vl_stop; + + return true; +} + +auto StructSerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto struct_prop = dynamic_cast(prop.get()); + + if(!struct_prop) { + return false; + } + + writer.writeUEStringToArray(struct_prop->structType); + writer.writeDataToArray(arrayView(struct_prop->structGuid)); + writer.writeValueToArray('\0'); + + if(!serialiser.writeItem(prop, struct_prop->structType, bytes_written, writer)) { + UnsignedLong dummy_bytes_written = 0; + UnsignedLong vl_start = writer.arrayPosition(); + if(!writeStructValue(struct_prop, dummy_bytes_written, writer, serialiser)) { + return false; + } + bytes_written += writer.arrayPosition() - vl_start; + } + + return true; +} + +auto StructSerialiser::readStructValue(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> StructProperty::ptr +{ + auto st_prop = Containers::pointer(); + st_prop->structType = type; + + UnrealPropertyBase::ptr prop; + while((prop = serialiser.read(reader)) != nullptr) { + arrayAppend(st_prop->properties, std::move(prop)); + + if(st_prop->properties.back()->name == "None" && + st_prop->properties.back()->propertyType == "NoneProperty" && + dynamic_cast(st_prop->properties.back().get()) != nullptr) + { + break; + } + } + + st_prop->name.emplace(name); + + return st_prop; +} + +auto StructSerialiser::writeStructValue(StructProperty* prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto struct_prop = dynamic_cast(prop); + + if(!struct_prop) { + return false; + } + + for(auto& item : struct_prop->properties) { + if(!serialiser.write(item, bytes_written, writer)) { + return false; + } + } + + return true; +} diff --git a/src/UESaveFile/Serialisers/StructSerialiser.h b/src/UESaveFile/Serialisers/StructSerialiser.h new file mode 100644 index 0000000..671290e --- /dev/null +++ b/src/UESaveFile/Serialisers/StructSerialiser.h @@ -0,0 +1,45 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "AbstractUnrealCollectionPropertySerialiser.h" +#include "AbstractUnrealPropertySerialiser.h" +#include "AbstractUnrealStructSerialiser.h" + +#include "../Types/StructProperty.h" + +class StructSerialiser : public AbstractUnrealPropertySerialiser, public AbstractUnrealCollectionPropertySerialiser { + public: + using ptr = Containers::Pointer; + + auto types() -> Containers::ArrayView override; + + auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, UnsignedInt count, + BinaryReader& reader, PropertySerialiser& serialiser) -> Containers::Array override; + auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + + auto serialise(Containers::ArrayView props, const std::string& item_type, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; + auto serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; + + private: + auto readStructValue(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> StructProperty::ptr; + auto writeStructValue(StructProperty* prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool; +}; diff --git a/src/UESaveFile/Serialisers/TextPropertySerialiser.cpp b/src/UESaveFile/Serialisers/TextPropertySerialiser.cpp new file mode 100644 index 0000000..8e379f4 --- /dev/null +++ b/src/UESaveFile/Serialisers/TextPropertySerialiser.cpp @@ -0,0 +1,84 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "TextPropertySerialiser.h" + +auto TextPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + Long start_position = reader.position(); + + char terminator; + if(!reader.readChar(terminator) || terminator != '\0') { + return nullptr; + } + + if(reader.peekChar() > 0) { + if(!reader.readArray(prop->flags, 8)) { + return nullptr; + } + } + else { + if(!reader.readArray(prop->flags, 4)) { + return nullptr; + } + } + + if(!reader.readChar(prop->id)) { + return nullptr; + } + + auto interval = reader.position() - start_position; + + do { + std::string str; + + if(!reader.readUEString(str)) { + return nullptr; + } + + arrayAppend(prop->data, std::move(str)); + + interval = reader.position() - start_position; + } while(UnsignedLong(interval) < value_length); + + prop->value = prop->data.back(); + + return prop; +} + +auto TextPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto text_prop = dynamic_cast(prop.get()); + + if(!text_prop) { + return false; + } + + writer.writeValueToArray('\0'); + bytes_written += writer.writeDataToArray(text_prop->flags); + for(const auto& str : text_prop->data) { + bytes_written += writer.writeUEStringToArray(str); + } + + return true; +} diff --git a/src/UESaveFile/Serialisers/TextPropertySerialiser.h b/src/UESaveFile/Serialisers/TextPropertySerialiser.h new file mode 100644 index 0000000..b408e83 --- /dev/null +++ b/src/UESaveFile/Serialisers/TextPropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/TextProperty.h" + +class TextPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/UnrealPropertySerialiser.h b/src/UESaveFile/Serialisers/UnrealPropertySerialiser.h new file mode 100644 index 0000000..1c963c9 --- /dev/null +++ b/src/UESaveFile/Serialisers/UnrealPropertySerialiser.h @@ -0,0 +1,58 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "AbstractUnrealPropertySerialiser.h" +#include "../Types/StructProperty.h" + +template +class UnrealPropertySerialiser : public AbstractUnrealPropertySerialiser { + static_assert(std::is_base_of::value, "T must be derived from UnrealPropertyBase."); + + public: + using ptr = Containers::Pointer>; + + auto types() -> Containers::ArrayView override { + static const Containers::Array types = []{ + Containers::Array array; + Containers::Pointer p(new T); + if(std::is_base_of::value) { + array = Containers::Array{InPlaceInit, {dynamic_cast(p.get())->structType}}; + } + else { + array = Containers::Array{InPlaceInit, {p->propertyType}}; + } + return array; + }(); + return types; + } + + auto deserialise(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override { + return deserialiseProperty(name, type, value_length, reader, serialiser); + } + + auto serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override { + return serialiseProperty(prop, bytes_written, writer, serialiser); + } + + private: + virtual auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> typename UnrealPropertyBase::ptr = 0; + + virtual auto serialiseProperty(typename UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool = 0; +}; diff --git a/src/UESaveFile/Serialisers/Vector2DPropertySerialiser.cpp b/src/UESaveFile/Serialisers/Vector2DPropertySerialiser.cpp new file mode 100644 index 0000000..402f4d4 --- /dev/null +++ b/src/UESaveFile/Serialisers/Vector2DPropertySerialiser.cpp @@ -0,0 +1,46 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "Vector2DPropertySerialiser.h" + +auto Vector2DPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(!reader.readFloat(prop->x) || !reader.readFloat(prop->y)) { + return nullptr; + } + + return prop; +} + +auto Vector2DPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto vector = dynamic_cast(prop.get()); + + if(!vector) { + return false; + } + + bytes_written += writer.writeValueToArray(vector->x) + writer.writeValueToArray(vector->y); + + return true; +} diff --git a/src/UESaveFile/Serialisers/Vector2DPropertySerialiser.h b/src/UESaveFile/Serialisers/Vector2DPropertySerialiser.h new file mode 100644 index 0000000..0351243 --- /dev/null +++ b/src/UESaveFile/Serialisers/Vector2DPropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/Vector2DStructProperty.h" + +class Vector2DPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Serialisers/VectorPropertySerialiser.cpp b/src/UESaveFile/Serialisers/VectorPropertySerialiser.cpp new file mode 100644 index 0000000..ee11136 --- /dev/null +++ b/src/UESaveFile/Serialisers/VectorPropertySerialiser.cpp @@ -0,0 +1,47 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "../BinaryReader.h" +#include "../BinaryWriter.h" + +#include "VectorPropertySerialiser.h" + +auto VectorPropertySerialiser::deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, + BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr +{ + auto prop = Containers::pointer(); + + if(!reader.readFloat(prop->x) || !reader.readFloat(prop->y) || !reader.readFloat(prop->z)) { + return nullptr; + } + + return prop; +} + +auto VectorPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, + BinaryWriter& writer, PropertySerialiser& serialiser) -> bool +{ + auto vector = dynamic_cast(prop.get()); + + if(!vector) { + return false; + } + + bytes_written += writer.writeValueToArray(vector->x) + writer.writeValueToArray(vector->y) + + writer.writeValueToArray(vector->z); + + return true; +} diff --git a/src/UESaveFile/Serialisers/VectorPropertySerialiser.h b/src/UESaveFile/Serialisers/VectorPropertySerialiser.h new file mode 100644 index 0000000..a3c9e75 --- /dev/null +++ b/src/UESaveFile/Serialisers/VectorPropertySerialiser.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertySerialiser.h" + +#include "../Types/VectorStructProperty.h" + +class VectorPropertySerialiser : public UnrealPropertySerialiser { + public: + using ptr = Containers::Pointer; + + private: + auto deserialiseProperty(const std::string& name, const std::string& type, UnsignedLong value_length, BinaryReader& reader, PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr override; + auto serialiseProperty(UnrealPropertyBase::ptr& prop, UnsignedLong& bytes_written, BinaryWriter& writer, PropertySerialiser& serialiser) -> bool override; +}; diff --git a/src/UESaveFile/Types/ArrayProperty.h b/src/UESaveFile/Types/ArrayProperty.h new file mode 100644 index 0000000..5cda507 --- /dev/null +++ b/src/UESaveFile/Types/ArrayProperty.h @@ -0,0 +1,42 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertyBase.h" + +struct ArrayProperty : public UnrealPropertyBase { + using ptr = Containers::Pointer; + + ArrayProperty() { + propertyType = "ArrayProperty"; + } + + template + std::enable_if_t::value, T*> + at(std::size_t index) { + if(index >= items.size()) { + return nullptr; + } + + return static_cast(items[index].get()); + } + + std::string itemType; + Containers::Array items; +}; diff --git a/src/UESaveFile/Types/BoolProperty.h b/src/UESaveFile/Types/BoolProperty.h new file mode 100644 index 0000000..ad80c62 --- /dev/null +++ b/src/UESaveFile/Types/BoolProperty.h @@ -0,0 +1,27 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealProperty.h" + +struct BoolProperty : public UnrealProperty { + using ptr = Containers::Pointer; + + BoolProperty() { + propertyType = "BoolProperty"; + } +}; diff --git a/src/UESaveFile/Types/ByteProperty.h b/src/UESaveFile/Types/ByteProperty.h new file mode 100644 index 0000000..26d5398 --- /dev/null +++ b/src/UESaveFile/Types/ByteProperty.h @@ -0,0 +1,33 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealProperty.h" + +struct ByteProperty : public UnrealProperty> { + using ptr = Containers::Pointer; + + ByteProperty() { + propertyType = "ByteProperty"; + } + + // For some reason, M.A.S.S. Builder stores EnumProperties as ByteProperties. Ugh... + std::string enumType; + std::string enumValue; +}; diff --git a/src/UESaveFile/Types/ColourStructProperty.h b/src/UESaveFile/Types/ColourStructProperty.h new file mode 100644 index 0000000..56c898f --- /dev/null +++ b/src/UESaveFile/Types/ColourStructProperty.h @@ -0,0 +1,27 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "StructProperty.h" + +struct ColourStructProperty : public StructProperty { + using ptr = Containers::Pointer; + ColourStructProperty() { + structType = "LinearColor"; + } + Float r = 0.0f, g = 0.0f, b = 0.0f, a = 0.0f; +}; diff --git a/src/UESaveFile/Types/DateTimeStructProperty.h b/src/UESaveFile/Types/DateTimeStructProperty.h new file mode 100644 index 0000000..4e3ecea --- /dev/null +++ b/src/UESaveFile/Types/DateTimeStructProperty.h @@ -0,0 +1,29 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "StructProperty.h" + +struct DateTimeStructProperty : public StructProperty { + using ptr = Containers::Pointer; + + DateTimeStructProperty() { + structType = "DateTime"; + } + + UnsignedLong timestamp = 0; +}; diff --git a/src/UESaveFile/Types/EnumProperty.h b/src/UESaveFile/Types/EnumProperty.h new file mode 100644 index 0000000..9494856 --- /dev/null +++ b/src/UESaveFile/Types/EnumProperty.h @@ -0,0 +1,29 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealProperty.h" + +struct EnumProperty : public UnrealProperty { + using ptr = Containers::Pointer; + + EnumProperty() { + propertyType = "EnumProperty"; + } + + std::string enumType; +}; diff --git a/src/UESaveFile/Types/FloatProperty.h b/src/UESaveFile/Types/FloatProperty.h new file mode 100644 index 0000000..bdc44b0 --- /dev/null +++ b/src/UESaveFile/Types/FloatProperty.h @@ -0,0 +1,27 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealProperty.h" + +struct FloatProperty : public UnrealProperty { + using ptr = Containers::Pointer; + + FloatProperty() { + propertyType = "FloatProperty"; + } +}; diff --git a/src/UESaveFile/Types/GenericStructProperty.h b/src/UESaveFile/Types/GenericStructProperty.h new file mode 100644 index 0000000..a14c7d2 --- /dev/null +++ b/src/UESaveFile/Types/GenericStructProperty.h @@ -0,0 +1,51 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "StructProperty.h" + +struct GenericStructProperty : public StructProperty { + using ptr = Containers::Pointer; + + template + std::enable_if_t::value, T*> + at(const std::string& name) { + for(auto& item : properties) { + if(item->name == name) { + return static_cast(item.get()); + } + } + return nullptr; + } + + template + std::enable_if_t::value, typename T::ptr> + atMove(const std::string& name) { + for(auto& item : properties) { + if(item && item->name == name) { + return Containers::Pointer{static_cast(item.release())}; + } + } + return nullptr; + } + + Containers::Array properties; +}; diff --git a/src/UESaveFile/Types/GuidStructProperty.h b/src/UESaveFile/Types/GuidStructProperty.h new file mode 100644 index 0000000..cf5d1ca --- /dev/null +++ b/src/UESaveFile/Types/GuidStructProperty.h @@ -0,0 +1,31 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "StructProperty.h" + +struct GuidStructProperty : public StructProperty { + using ptr = Containers::Pointer; + + GuidStructProperty() { + structType = "Guid"; + } + + Containers::StaticArray<16, char> guid{ValueInit}; +}; diff --git a/src/UESaveFile/Types/IntProperty.h b/src/UESaveFile/Types/IntProperty.h new file mode 100644 index 0000000..55774d1 --- /dev/null +++ b/src/UESaveFile/Types/IntProperty.h @@ -0,0 +1,27 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealProperty.h" + +struct IntProperty : public UnrealProperty { + using ptr = Containers::Pointer; + + IntProperty() { + propertyType = "IntProperty"; + } +}; diff --git a/src/UESaveFile/Types/MapProperty.h b/src/UESaveFile/Types/MapProperty.h new file mode 100644 index 0000000..d52994f --- /dev/null +++ b/src/UESaveFile/Types/MapProperty.h @@ -0,0 +1,37 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertyBase.h" + +struct MapProperty : public UnrealPropertyBase { + using ptr = Containers::Pointer; + + MapProperty() { + propertyType = "MapProperty"; + } + + std::string keyType; + std::string valueType; + + struct KeyValuePair { + UnrealPropertyBase::ptr key; + Containers::Array values; + }; + + Containers::Array map; +}; diff --git a/src/UESaveFile/Types/NoneProperty.h b/src/UESaveFile/Types/NoneProperty.h new file mode 100644 index 0000000..91e8ab9 --- /dev/null +++ b/src/UESaveFile/Types/NoneProperty.h @@ -0,0 +1,28 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertyBase.h" + +struct NoneProperty : UnrealPropertyBase { + using ptr = Containers::Pointer; + + NoneProperty() { + name.emplace("None"); + propertyType = "NoneProperty"; + } +}; diff --git a/src/UESaveFile/Types/ResourceItemValue.h b/src/UESaveFile/Types/ResourceItemValue.h new file mode 100644 index 0000000..4692296 --- /dev/null +++ b/src/UESaveFile/Types/ResourceItemValue.h @@ -0,0 +1,30 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "StructProperty.h" + +struct ResourceItemValue : public StructProperty { + using ptr = Containers::Pointer; + + ResourceItemValue() { + structType = "sttResourceItemValue"; + structGuid = Containers::StaticArray<16, char>{'\xB7', '\xA7', '\x77', '\xAB', '\xD3', '\x1B', '\xA6', '\x43', '\xAF', '\x42', '\xE5', '\x9E', '\xBF', '\xFD', '\x37', '\x55'}; + } + + Int id = 0, quantity = 0; +}; diff --git a/src/UESaveFile/Types/RotatorStructProperty.h b/src/UESaveFile/Types/RotatorStructProperty.h new file mode 100644 index 0000000..e1bdf21 --- /dev/null +++ b/src/UESaveFile/Types/RotatorStructProperty.h @@ -0,0 +1,29 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "StructProperty.h" + +struct RotatorStructProperty : public StructProperty { + using ptr = Containers::Pointer; + + RotatorStructProperty() { + structType = "Rotator"; + } + + Float x = 0.0f, y = 0.0f, z = 0.0f; +}; diff --git a/src/UESaveFile/Types/SetProperty.h b/src/UESaveFile/Types/SetProperty.h new file mode 100644 index 0000000..6c490ed --- /dev/null +++ b/src/UESaveFile/Types/SetProperty.h @@ -0,0 +1,40 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertyBase.h" + +struct SetProperty : public UnrealPropertyBase { + using ptr = Containers::Pointer; + + SetProperty() { + propertyType = "SetProperty"; + } + + auto at(std::size_t index) -> UnrealPropertyBase* { + if(index >= items.size()) { + return nullptr; + } + + return items[index].get(); + } + + std::string itemType; + Containers::Array items; +}; diff --git a/src/UESaveFile/Types/StringProperty.h b/src/UESaveFile/Types/StringProperty.h new file mode 100644 index 0000000..2d871b7 --- /dev/null +++ b/src/UESaveFile/Types/StringProperty.h @@ -0,0 +1,27 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealProperty.h" + +struct StringProperty : public UnrealProperty { + using ptr = Containers::Pointer; + + explicit StringProperty(const std::string& type = "StrProperty") { + propertyType = type; + } +}; diff --git a/src/UESaveFile/Types/StructProperty.h b/src/UESaveFile/Types/StructProperty.h new file mode 100644 index 0000000..c38a048 --- /dev/null +++ b/src/UESaveFile/Types/StructProperty.h @@ -0,0 +1,32 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertyBase.h" + +struct StructProperty : public UnrealPropertyBase { + using ptr = Containers::Pointer; + + StructProperty() { + std::string propertyType = "StructProperty"; + } + + Containers::StaticArray<16, char> structGuid{ValueInit}; + std::string structType; +}; diff --git a/src/UESaveFile/Types/TextProperty.h b/src/UESaveFile/Types/TextProperty.h new file mode 100644 index 0000000..bffaaad --- /dev/null +++ b/src/UESaveFile/Types/TextProperty.h @@ -0,0 +1,31 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealProperty.h" + +struct TextProperty : public UnrealProperty { + using ptr = Containers::Pointer; + + TextProperty() { + propertyType = "TextProperty"; + } + + Containers::Array flags; + char id = 0; + Containers::Array data; +}; diff --git a/src/UESaveFile/Types/UnrealProperty.h b/src/UESaveFile/Types/UnrealProperty.h new file mode 100644 index 0000000..2192028 --- /dev/null +++ b/src/UESaveFile/Types/UnrealProperty.h @@ -0,0 +1,26 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "UnrealPropertyBase.h" + +template +struct UnrealProperty : public UnrealPropertyBase { + using ptr = Containers::Pointer>; + + T value; +}; diff --git a/src/UESaveFile/Types/UnrealPropertyBase.h b/src/UESaveFile/Types/UnrealPropertyBase.h new file mode 100644 index 0000000..d538428 --- /dev/null +++ b/src/UESaveFile/Types/UnrealPropertyBase.h @@ -0,0 +1,37 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Corrade; +using namespace Magnum; + +struct UnrealPropertyBase { + using ptr = Containers::Pointer; + + virtual ~UnrealPropertyBase() = default; + + Containers::Optional name = Containers::NullOpt; + std::string propertyType; + UnsignedLong valueLength = 0; +}; diff --git a/src/UESaveFile/Types/Vector2DStructProperty.h b/src/UESaveFile/Types/Vector2DStructProperty.h new file mode 100644 index 0000000..9372881 --- /dev/null +++ b/src/UESaveFile/Types/Vector2DStructProperty.h @@ -0,0 +1,29 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "StructProperty.h" + +struct Vector2DStructProperty : public StructProperty { + using ptr = Containers::Pointer; + + Vector2DStructProperty() { + structType = "Vector2D"; + } + + Float x = 0.0f, y = 0.0f; +}; diff --git a/src/UESaveFile/Types/VectorStructProperty.h b/src/UESaveFile/Types/VectorStructProperty.h new file mode 100644 index 0000000..367435b --- /dev/null +++ b/src/UESaveFile/Types/VectorStructProperty.h @@ -0,0 +1,29 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "StructProperty.h" + +struct VectorStructProperty : public StructProperty { + using ptr = Containers::Pointer; + + VectorStructProperty() { + structType = "Vector"; + } + + Float x = 0.0f, y = 0.0f, z = 0.0f; +}; diff --git a/src/UESaveFile/UESaveFile.cpp b/src/UESaveFile/UESaveFile.cpp new file mode 100644 index 0000000..5835769 --- /dev/null +++ b/src/UESaveFile/UESaveFile.cpp @@ -0,0 +1,223 @@ +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "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 { + if(_noReloadAfterSave) { + _noReloadAfterSave = false; + return valid(); + } + + _properties = Containers::Array{}; + loadData(); + return valid(); +} + +void UESaveFile::appendProperty(UnrealPropertyBase::ptr prop) { + auto none_prop = std::move(_properties.back()); + _properties.back() = std::move(prop); + arrayAppend(_properties, std::move(none_prop)); +} + +auto UESaveFile::props() -> Containers::ArrayView { + return _properties; +} + +auto UESaveFile::saveToFile() -> bool { + BinaryWriter writer{_filepath + ".tmp"}; + + if(!writer.open()) { + _lastError = "Couldn't open the file for saving."; + return false; + } + + 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) || + !writer.writeUEString(_engineVersion.buildId)) + { + _lastError = "Couldn't write the header."; + return false; + } + + if(!writer.writeUnsignedInt(_customFormatVersion) || + !writer.writeUnsignedInt(_customFormatData.size())) + { + _lastError = "Couldn't write the header."; + 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)) + { + _lastError = "Couldn't write the header."; + return false; + } + } + + if(!writer.writeUEString(_saveType)) { + _lastError = "Couldn't write the header."; + return false; + } + + for(auto& prop : _properties) { + UnsignedLong bytes_written = 0; + if(!_propSerialiser.write(prop, bytes_written, writer)) { + _lastError = "Couldn't write the property " + *prop->name + " to the array."; + return false; + } + + if(!writer.flushToFile()) { + _lastError = "Couldn't write the property " + *prop->name + " to the file."; + return false; + } + } + + writer.writeUnsignedInt(0); + + writer.closeFile(); + + 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; + + 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 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)); + } + + if(_properties.back()->name != "None" && _properties.back()->propertyType != "NoneProperty") { + _lastError = "Couldn't find a final NoneProperty."; + return; + } + + reader.closeFile(); + + _valid = true; +} diff --git a/src/UESaveFile/UESaveFile.h b/src/UESaveFile/UESaveFile.h new file mode 100644 index 0000000..856e12b --- /dev/null +++ b/src/UESaveFile/UESaveFile.h @@ -0,0 +1,94 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 "Types/UnrealPropertyBase.h" + +#include "PropertySerialiser.h" + +using namespace Corrade; +using namespace Magnum; + +class UESaveFile { + public: + explicit UESaveFile(std::string filepath); + + auto valid() const -> bool; + auto lastError() const -> std::string const&; + + auto reloadData() -> bool; + + template + std::enable_if_t::value, T*> + at(const std::string& name) { + for(auto& prop : _properties) { + if(prop->name == name) { + return static_cast(prop.get()); + } + } + return nullptr; + } + + void appendProperty(UnrealPropertyBase::ptr prop); + + auto props() -> Containers::ArrayView; + + auto saveToFile() -> bool; + + private: + void loadData(); + + bool _valid{false}; + std::string _lastError; + + std::string _filepath; + + bool _noReloadAfterSave = false; + + Containers::StaticArray<4, char> _magicBytes{'G', 'V', 'A', 'S'}; + + UnsignedInt _saveVersion = 0; + UnsignedInt _packageVersion = 0; + struct { + UnsignedShort major = 0; + UnsignedShort minor = 0; + UnsignedShort patch = 0; + UnsignedInt build = 0; + std::string buildId; + } _engineVersion; + + UnsignedInt _customFormatVersion = 0; + struct CustomFormatDataEntry { + Containers::StaticArray<16, char> id; + Int value = 0; + }; + Containers::Array _customFormatData; + + std::string _saveType; + + Containers::Array _properties; + + PropertySerialiser _propSerialiser; +}; diff --git a/src/Utilities/Crc32.h b/src/Utilities/Crc32.h new file mode 100644 index 0000000..e6babd6 --- /dev/null +++ b/src/Utilities/Crc32.h @@ -0,0 +1,62 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021-2022 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 + +using namespace Corrade; +using namespace Magnum; + +struct Crc32 { + static const Containers::StaticArray<256, UnsignedInt> table; + + static auto update(UnsignedInt initial, Containers::ArrayView data) -> UnsignedInt { + UnsignedInt c = initial ^ 0xFFFFFFFF; + auto u = Containers::arrayCast(data); + + for(std::size_t i = 0; i < data.size(); ++i) { + c = table[(c ^ u[i]) & 0xFF] ^ (c >> 8); + } + + return c ^ 0xFFFFFFFF; + } +}; + +const Containers::StaticArray<256, UnsignedInt> Crc32::table = []{ + UnsignedInt polynomial = 0xEDB88320u; + Containers::StaticArray<256, UnsignedInt> temp{ValueInit}; + + for(UnsignedInt i = 0; i < 256; i++) { + UnsignedInt c = i; + + for(std::size_t j = 0; j < 8; j++) { + if(c & 1) { + c = polynomial ^ (c >> 1); + } + else { + c >>= 1; + } + } + + temp[i] = c; + } + + return temp; +}(); diff --git a/src/main.cpp b/src/main.cpp index ca38b71..6545425 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 diff --git a/src/resource.rc b/src/resource.rc index 7491807..df99b1e 100644 --- a/src/resource.rc +++ b/src/resource.rc @@ -1,5 +1,5 @@ // MassBuilderSaveTool -// Copyright (C) 2021 Guillaume Jacquemin +// Copyright (C) 2021-2022 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 diff --git a/third-party/SDL b/third-party/SDL index d956636..857cc7c 160000 --- a/third-party/SDL +++ b/third-party/SDL @@ -1 +1 @@ -Subproject commit d956636c85aafc055d37153d52370e9b1c2c5929 +Subproject commit 857cc7c0c93f003dbfc5fed7a87de907fffb2a1b diff --git a/third-party/corrade b/third-party/corrade index 76d5492..34a53fa 160000 --- a/third-party/corrade +++ b/third-party/corrade @@ -1 +1 @@ -Subproject commit 76d54922ba22e6a1abfffb5c9dc1012dfbae149e +Subproject commit 34a53fab11928765f5fb2704b5653769a20b63a5 diff --git a/third-party/cpr b/third-party/cpr index 814bd09..eccea39 160000 --- a/third-party/cpr +++ b/third-party/cpr @@ -1 +1 @@ -Subproject commit 814bd0926379cfc786dd1d9df9ce266a4bb6cf59 +Subproject commit eccea39e4b112a088ae665eda5854cc366f86128 diff --git a/third-party/efsw b/third-party/efsw index 6fb0c9c..f5f42f4 160000 --- a/third-party/efsw +++ b/third-party/efsw @@ -1 +1 @@ -Subproject commit 6fb0c9ccd284445330723914249b7be46798ee76 +Subproject commit f5f42f4b9a1c34512b779b2c5544ae42fdf97afa diff --git a/third-party/imgui b/third-party/imgui index 94b680e..ddddabd 160000 --- a/third-party/imgui +++ b/third-party/imgui @@ -1 +1 @@ -Subproject commit 94b680e83052b9ff2e877360309020b72db057f2 +Subproject commit ddddabdccfdafffd8664fb4e29230dc4f848137e diff --git a/third-party/json b/third-party/json index 350ff4f..626e7d6 160000 --- a/third-party/json +++ b/third-party/json @@ -1 +1 @@ -Subproject commit 350ff4f7ced7c4117eae2fb93df02823c8021fcb +Subproject commit 626e7d61e44dee32887126c8f437dd077dec09cf diff --git a/third-party/libzip b/third-party/libzip index 821cbfe..e7c81b6 160000 --- a/third-party/libzip +++ b/third-party/libzip @@ -1 +1 @@ -Subproject commit 821cbfe1b260193563b04d3db1bb7eb4a4ed0d8a +Subproject commit e7c81b67ab91d5dc54c2238d9f7d9abab1a0a8c3 diff --git a/third-party/magnum b/third-party/magnum index dd3ce93..b79bc18 160000 --- a/third-party/magnum +++ b/third-party/magnum @@ -1 +1 @@ -Subproject commit dd3ce93888ada70074d63d72c5998d950fa2eba6 +Subproject commit b79bc1821902e9fd91700f0edc517b090809e19c diff --git a/third-party/magnum-integration b/third-party/magnum-integration index 73018a6..36c1b15 160000 --- a/third-party/magnum-integration +++ b/third-party/magnum-integration @@ -1 +1 @@ -Subproject commit 73018a6d5daf04ef69449e82fa757c3acf797b1b +Subproject commit 36c1b1515873eff5047b27751c12a1580c141dd8