Compare commits

..

5 commits

11 changed files with 1205 additions and 510 deletions

View file

@ -114,12 +114,15 @@ add_executable(MassBuilderSaveTool WIN32
SaveTool/SaveTool.cpp
SaveTool/SaveTool_drawAbout.cpp
SaveTool/SaveTool_drawMainMenu.cpp
SaveTool/SaveTool_FileWatcher.cpp
SaveTool/SaveTool_Initialisation.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
SaveTool/SaveTool_UpdateChecker.cpp
ProfileManager/ProfileManager.h
ProfileManager/ProfileManager.cpp
Profile/Profile.h

View file

@ -27,7 +27,7 @@ using namespace Containers::Literals;
using namespace Magnum;
static const std::map<Int, Containers::StringView> accessories {
// Primitives
// region Primitives
{1, "Cube (S)"_s},
{2, "Pentagon (S)"_s},
{3, "Hexagon (S)"_s},
@ -48,6 +48,11 @@ static const std::map<Int, Containers::StringView> accessories {
{18, "Decal Pad 03 (S)"_s},
{19, "Decal Pad 04 (S)"_s},
{20, "Decal Pad 05 (S)"_s},
{21, "Triangle (S)"_s},
{22, "ThinStar (S)"_s},
{23, "Star (S)"_s},
{24, "SixSideStar (S)"_s},
{25, "Asterisk (S)"_s},
{51, "SquBevel (S)"_s},
{52, "TriBevel (S)"_s},
@ -70,9 +75,556 @@ static const std::map<Int, Containers::StringView> accessories {
{69, "CofEmboss (S)"_s},
{70, "JevEmboss (S)"_s},
// Armours
{101, "Flat Hex Pin (S)"_s},
{102, "Cross Circle Pin (S)"_s},
{103, "Flat Circle Pin (S)"_s},
{104, "Hex Circle Pin (S)"_s},
{105, "Circle Button Pin (S)"_s},
{106, "Hexagon Pin (S)"_s},
{107, "Cross Square Pin (S)"_s},
{108, "Flat Square Pin (S)"_s},
{109, "Quad Corner Pin (S)"_s},
{110, "Bi Corner Pin (S)"_s},
{111, "Circle Pin (S)"_s},
{112, "Flat End Pin (S)"_s},
{113, "Flat Cut Pin (S)"_s},
{114, "Radial Pin (S)"_s},
{115, "Diamiter Pin (S)"_s},
// Components
{151, "TriPoint (S)"_s},
{152, "SquPoint (S)"_s},
{153, "PenPoint (S)"_s},
{154, "HexPoint (S)"_s},
{155, "CycPoint (S)"_s},
{156, "Bevel SquCutPoint (S)"_s},
{157, "Bevel HexCutPoint (S)"_s},
{158, "Bevel HexPoint (S)"_s},
{159, "Bevel CycCutPoint (S)"_s},
{160, "Bevel CycPoint (S)"_s},
// Connectors
{201, "Shaped Edge 01 (M)"_s},
{202, "Shaped Edge 02 (M)"_s},
{203, "Shaped Edge 03 (M)"_s},
{204, "Shaped Edge 04 (M)"_s},
{205, "Shaped Edge 05 (M)"_s},
{206, "Shaped Edge 06 (M)"_s},
{207, "Shaped Edge 07 (M)"_s},
{208, "Shaped Edge 08 (M)"_s},
{209, "Shaped Edge 09 (M)"_s},
{210, "Shaped Edge 10 (M)"_s},
{211, "Shaped Edge 11 (M)"_s},
{212, "Shaped Edge 12 (M)"_s},
{213, "Shaped Edge 13 (M)"_s},
{214, "Shaped Edge 14 (M)"_s},
{215, "Shaped Edge 15 (M)"_s},
{216, "Shaped Edge 16 (M)"_s},
{217, "Shaped Edge 17 (M)"_s},
{218, "Shaped Edge 18 (M)"_s},
{219, "Shaped Edge 19 (M)"_s},
{220, "Shaped Edge 20 (M)"_s},
{251, "Fish Tail 01 (M)"_s},
{252, "Fish Tail 02 (M)"_s},
{253, "Fish Tail 03 (M)"_s},
{254, "Fish Tail 04 (M)"_s},
{255, "Fish Tail 05 (M)"_s},
{256, "Based Separator 01 (M)"_s},
{257, "Based Separator 02 (M)"_s},
{258, "Based Separator 03 (M)"_s},
{259, "Based Separator 04 (M)"_s},
{260, "Based Separator 05 (M)"_s},
{261, "Based Separator 06 (M)"_s},
{262, "Based Separator 07 (M)"_s},
{263, "Based Separator 08 (M)"_s},
{264, "Based Separator 09 (M)"_s},
{265, "Based Separator 10 (M)"_s},
{301, "Rectangular Box 01 (M)"_s},
{302, "Rectangular Box 02 (M)"_s},
{303, "Rectangular Box 03 (M)"_s},
{304, "Rectangular Box 04 (M)"_s},
{305, "Rectangular Box 05 (M)"_s},
{306, "CofBox 01 (M)"_s},
{307, "CofBox 02 (M)"_s},
{308, "CofBox 03 (M)"_s},
{309, "CofBox 04 (M)"_s},
{310, "CofBox 05 (M)"_s},
{311, "Triangular Box 01 (M)"_s},
{312, "Triangular Box 02 (M)"_s},
{313, "Triangular Box 03 (M)"_s},
{314, "Triangular Box 04 (M)"_s},
{315, "Triangular Box 05 (M)"_s},
{316, "Diagonal Box A01 (M)"_s},
{317, "Diagonal Box A02 (M)"_s},
{318, "Diagonal Box A03 (M)"_s},
{319, "Diagonal Box A04 (M)"_s},
{320, "Diagonal Box A05 (M)"_s},
{321, "Diagonal Box B01 (M)"_s},
{322, "Diagonal Box B02 (M)"_s},
{323, "Diagonal Box B03 (M)"_s},
{324, "Diagonal Box B04 (M)"_s},
{325, "Diagonal Box B05 (M)"_s},
// endregion
// region Armours
{1001, "Short Layer 01 (M)"_s},
{1002, "Short Layer 02 (M)"_s},
{1003, "Short Layer 03 (M)"_s},
{1004, "Short Layer 04 (M)"_s},
{1005, "Short Layer 05 (M)"_s},
{1006, "Long Layer 01 (M)"_s},
{1007, "Long Layer 02 (M)"_s},
{1008, "Long Layer 03 (M)"_s},
{1009, "Long Layer 04 (M)"_s},
{1010, "Long Layer 05 (M)"_s},
{1011, "Diagonal Long Layer 01 (M)"_s},
{1012, "Diagonal Long Layer 02 (M)"_s},
{1013, "Diagonal Long Layer 03 (M)"_s},
{1014, "Diagonal Long Layer 04 (M)"_s},
{1015, "Diagonal Long Layer 05 (M)"_s},
{1051, "Sloped Layer 01 (M)"_s},
{1052, "Sloped Layer 02 (M)"_s},
{1053, "Sloped Layer 03 (M)"_s},
{1054, "Sloped Layer 04 (M)"_s},
{1055, "Sloped Layer 05 (M)"_s},
{1056, "Sloped Layer 06 (M)"_s},
{1057, "Sloped Layer 07 (M)"_s},
{1058, "Sloped Layer 08 (M)"_s},
{1059, "Sloped Layer 09 (M)"_s},
{1060, "Sloped Layer 10 (M)"_s},
{1061, "Sloped Layer 11 (M)"_s},
{1062, "Sloped Layer 12 (M)"_s},
{1063, "Sloped Layer 13 (M)"_s},
{1064, "Sloped Layer 14 (M)"_s},
{1065, "Sloped Layer 15 (M)"_s},
{1101, "Raised Center 01 (M)"_s},
{1102, "Raised Center 02 (M)"_s},
{1103, "Raised Center 03 (M)"_s},
{1104, "Raised Center 04 (M)"_s},
{1105, "Raised Center 05 (M)"_s},
{1106, "Raised Block 01 (M)"_s},
{1107, "Raised Block 02 (M)"_s},
{1108, "Raised Block 03 (M)"_s},
{1109, "Raised Pointed (M)"_s},
{1110, "Raised Cover (M)"_s},
{1111, "Raised Slant 01 (M)"_s},
{1112, "Raised Slant 02 (M)"_s},
{1113, "Raised Slant 03 (M)"_s},
{1114, "Raised Slant 04 (M)"_s},
{1115, "Raised Slant 05 (M)"_s},
{1151, "Wide Patch 01 (L)"_s},
{1152, "Wide Patch 02 (L)"_s},
{1153, "Wide Patch 03 (L)"_s},
{1154, "Wide Patch 04 (L)"_s},
{1155, "Wide Patch 05 (L)"_s},
{1201, "Pointed Armour 01 (L)"_s},
{1202, "Pointed Armour 02 (L)"_s},
{1203, "Pointed Armour 03 (L)"_s},
{1204, "Pointed Armour 04 (L)"_s},
{1205, "Pointed Armour 05 (L)"_s},
{1206, "Pointed Armour 06 (L)"_s},
{1207, "Pointed Armour 07 (L)"_s},
{1208, "Pointed Armour 08 (L)"_s},
{1209, "Pointed Armour 09 (L)"_s},
{1210, "Pointed Armour 10 (L)"_s},
{1211, "Pointed Armour 11 (L)"_s},
{1212, "Pointed Armour 12 (L)"_s},
{1213, "Pointed Armour 13 (L)"_s},
{1214, "Pointed Armour 14 (L)"_s},
{1215, "Pointed Armour 15 (L)"_s},
{1251, "E Limb Cover 01 (L)"_s},
{1252, "E Limb Cover 02 (L)"_s},
{1253, "E Limb Cover 03 (L)"_s},
{1254, "E Limb Cover 04 (L)"_s},
{1255, "E Limb Cover 05 (L)"_s},
{1256, "E Limb Cover 06 (L)"_s},
{1257, "E Limb Cover 07 (L)"_s},
{1258, "E Limb Cover 08 (L)"_s},
{1259, "E Limb Cover 09 (L)"_s},
{1260, "E Limb Cover 10 (L)"_s},
{1301, "C Limb Cover 01 (L)"_s},
{1302, "C Limb Cover 02 (L)"_s},
{1303, "C Limb Cover 03 (L)"_s},
{1304, "C Limb Cover 04 (L)"_s},
{1305, "C Limb Cover 05 (L)"_s},
{1306, "C Limb Cover 06 (L)"_s},
{1307, "C Limb Cover 07 (L)"_s},
{1308, "C Limb Cover 08 (L)"_s},
{1309, "C Limb Cover 09 (L)"_s},
{1310, "C Limb Cover 10 (L)"_s},
{1311, "C Limb Cover 11 (L)"_s},
{1312, "C Limb Cover 12 (L)"_s},
{1313, "C Limb Cover 13 (L)"_s},
{1314, "C Limb Cover 14 (L)"_s},
{1315, "C Limb Cover 15 (L)"_s},
{1316, "C Limb Cover 16 (L)"_s},
{1317, "C Limb Cover 17 (L)"_s},
{1318, "C Limb Cover 18 (L)"_s},
{1319, "C Limb Cover 19 (L)"_s},
{1320, "C Limb Cover 20 (L)"_s},
{1351, "P Limb Cover 01 (XL)"_s},
{1352, "P Limb Cover 02 (XL)"_s},
{1353, "P Limb Cover 03 (XL)"_s},
{1354, "P Limb Cover 04 (XL)"_s},
{1355, "P Limb Cover 05 (XL)"_s},
{1401, "Flat Cover 01 (XL)"_s},
{1402, "Flat Cover 02 (XL)"_s},
{1403, "Flat Cover 03 (XL)"_s},
{1404, "Flat Cover 04 (XL)"_s},
{1405, "Flat Cover 05 (XL)"_s},
{1406, "Flat Cover 06 (XL)"_s},
{1407, "Flat Cover 07 (XL)"_s},
{1408, "Flat Cover 08 (XL)"_s},
{1409, "Flat Cover 09 (XL)"_s},
{1410, "Flat Cover 10 (XL)"_s},
{1451, "L Side Opening 01 (XL)"_s},
{1452, "L Side Opening 02 (XL)"_s},
{1453, "L Side Opening 03 (XL)"_s},
{1454, "L Side Opening 04 (XL)"_s},
{1455, "L Side Opening 05 (XL)"_s},
{1456, "L Side Opening 06 (XL)"_s},
{1457, "L Side Opening 07 (XL)"_s},
{1458, "L Side Opening 08 (XL)"_s},
{1459, "L Side Opening 09 (XL)"_s},
{1460, "L Side Opening 10 (XL)"_s},
// endregion
// region Components
{2001, "Disc Padding 01 (M)"_s},
{2002, "Disc Padding 02 (M)"_s},
{2003, "Disc Padding 03 (M)"_s},
{2004, "Disc Padding 04 (M)"_s},
{2005, "Disc Padding 05 (M)"_s},
{2006, "Thin Padding 01 (M)"_s},
{2007, "Thin Padding 02 (M)"_s},
{2008, "Thin Padding 03 (M)"_s},
{2009, "Thin Padding 04 (M)"_s},
{2010, "Thin Padding 05 (M)"_s},
{2011, "Thick Padding 01 (M)"_s},
{2012, "Thick Padding 02 (M)"_s},
{2013, "Thick Padding 03 (M)"_s},
{2014, "Thick Padding 04 (M)"_s},
{2015, "Thick Padding 05 (M)"_s},
{2016, "Thick Padding 06 (M)"_s},
{2017, "Thick Padding 07 (M)"_s},
{2018, "Thick Padding 08 (M)"_s},
{2019, "Thick Padding 09 (M)"_s},
{2020, "Thick Padding 10 (M)"_s},
{2021, "CSide Padding 01 (M)"_s},
{2022, "CSide Padding 02 (M)"_s},
{2023, "CSide Padding 03 (M)"_s},
{2024, "CSide Padding 04 (M)"_s},
{2025, "CSide Padding 05 (M)"_s},
{2051, "Container 01 (L)"_s},
{2052, "Container 02 (L)"_s},
{2053, "Container 03 (L)"_s},
{2054, "Container 04 (L)"_s},
{2055, "Container 05 (L)"_s},
{2101, "Plating 01 (L)"_s},
{2102, "Plating 02 (L)"_s},
{2103, "Plating 03 (L)"_s},
{2104, "Plating 04 (L)"_s},
{2105, "Plating 05 (L)"_s},
{2151, "Complex Base 01 (L)"_s},
{2152, "Complex Base 02 (L)"_s},
{2153, "Complex Base 03 (L)"_s},
{2154, "Complex Base 04 (L)"_s},
{2155, "Complex Base 05 (L)"_s},
{2156, "Complex Base 06 (L)"_s},
{2157, "Complex Base 07 (L)"_s},
{2158, "Complex Base 08 (L)"_s},
{2159, "Complex Base 09 (L)"_s},
{2160, "Complex Base 10 (L)"_s},
{2201, "Long Base 01 (XL)"_s},
{2202, "Long Base 02 (XL)"_s},
{2203, "Long Base 03 (XL)"_s},
{2204, "Long Base 04 (XL)"_s},
{2205, "Long Base 05 (XL)"_s},
{2251, "Straight Wing 01 (XL)"_s},
{2252, "Straight Wing 02 (XL)"_s},
{2253, "Straight Wing 03 (XL)"_s},
{2254, "Straight Wing 04 (XL)"_s},
{2255, "Straight Wing 05 (XL)"_s},
{2256, "Straight Wing 06 (XL)"_s},
{2257, "Straight Wing 07 (XL)"_s},
{2258, "Straight Wing 08 (XL)"_s},
{2259, "Straight Wing 09 (XL)"_s},
{2260, "Straight Wing 10 (XL)"_s},
{2301, "Triangular Wing 01 (XL)"_s},
{2302, "Triangular Wing 02 (XL)"_s},
{2303, "Triangular Wing 03 (XL)"_s},
{2304, "Triangular Wing 04 (XL)"_s},
{2305, "Triangular Wing 05 (XL)"_s},
{2306, "Triangular Wing 06 (XL)"_s},
{2307, "Triangular Wing 07 (XL)"_s},
{2308, "Triangular Wing 08 (XL)"_s},
{2309, "Triangular Wing 09 (XL)"_s},
{2310, "Triangular Wing 10 (XL)"_s},
{2311, "Triangular Wing 11 (L)"_s},
{2312, "Triangular Wing 12 (L)"_s},
{2313, "Triangular Wing 13 (L)"_s},
{2314, "Triangular Wing 14 (L)"_s},
{2315, "Triangular Wing 15 (L)"_s},
{2351, "Complex Wing 01 (XL)"_s},
{2352, "Complex Wing 02 (XL)"_s},
{2353, "Complex Wing 03 (XL)"_s},
{2354, "Complex Wing 04 (XL)"_s},
{2355, "Complex Wing 05 (XL)"_s},
{2356, "Complex Wing 06 (L)"_s},
{2357, "Complex Wing 07 (L)"_s},
{2358, "Complex Wing 08 (L)"_s},
{2359, "Complex Wing 09 (L)"_s},
{2360, "Complex Wing 10 (L)"_s},
{2401, "Blade 01 (XL)"_s},
{2402, "Blade 02 (XL)"_s},
{2403, "Blade 03 (XL)"_s},
{2404, "Blade 04 (XL)"_s},
{2405, "Blade 05 (XL)"_s},
{2406, "Blade 06 (XL)"_s},
{2407, "Blade 07 (XL)"_s},
{2408, "Blade 08 (XL)"_s},
{2409, "Blade 09 (XL)"_s},
{2410, "Blade 10 (XL)"_s},
{2426, "Curved Blade 01 (XL)"_s},
{2427, "Curved Blade 02 (XL)"_s},
{2428, "Curved Blade 03 (XL)"_s},
{2429, "Curved Blade 04 (XL)"_s},
{2430, "Curved Blade 05 (XL)"_s},
{2451, "Horn 01 (M)"_s},
{2452, "Horn 02 (M)"_s},
{2453, "Horn 03 (M)"_s},
{2454, "Horn 04 (M)"_s},
{2455, "Horn 05 (M)"_s},
{2456, "Horn 06 (M)"_s},
{2457, "Horn 07 (M)"_s},
{2458, "Horn 08 (M)"_s},
{2459, "Horn 09 (M)"_s},
{2460, "Horn 10 (M)"_s},
{2461, "Horn 11 (M)"_s},
{2462, "Horn 12 (M)"_s},
{2463, "Horn 13 (M)"_s},
{2464, "Horn 14 (M)"_s},
{2465, "Horn 15 (M)"_s},
{2471, "Mask (M)"_s},
{2472, "Droplet (M)"_s},
{2473, "Thigh (M)"_s},
{2474, "LegS (M)"_s},
{2475, "LegTH (M)"_s},
{2476, "Plume 01 (M)"_s},
{2477, "Plume 02 (M)"_s},
{2478, "Plume 03 (M)"_s},
{2479, "Plume 04 (M)"_s},
{2480, "Plume 05 (M)"_s},
{2491, "Tail 01 (XL)"_s},
{2492, "Tail 02 (XL)"_s},
{2493, "Tail 03 (XL)"_s},
{2494, "Tail 04 (XL)"_s},
{2495, "Tail 05 (XL)"_s},
{2501, "Finger 01 (M)"_s},
{2502, "Finger 02 (M)"_s},
{2503, "Finger 03 (M)"_s},
{2504, "Finger 04 (M)"_s},
{2505, "Finger 05 (M)"_s},
{2521, "Fabric 01 (XL)"_s},
{2522, "Fabric 02 (XL)"_s},
{2523, "Fabric 03 (XL)"_s},
{2524, "Fabric 04 (XL)"_s},
{2525, "Fabric 05 (XL)"_s},
{2551, "Energy Barrel 01 (XL)"_s},
{2552, "Energy Barrel 02 (XL)"_s},
{2553, "Energy Barrel 03 (XL)"_s},
{2554, "Energy Barrel 04 (XL)"_s},
{2555, "Energy Barrel 05 (XL)"_s},
{2601, "L Bullet Barrel 01 (XL)"_s},
{2602, "L Bullet Barrel 02 (XL)"_s},
{2603, "L Bullet Barrel 03 (XL)"_s},
{2604, "L Bullet Barrel 04 (XL)"_s},
{2605, "L Bullet Barrel 05 (XL)"_s},
{2606, "S Bullet Barrel 01 (XL)"_s},
{2607, "S Bullet Barrel 02 (XL)"_s},
{2608, "S Bullet Barrel 03 (XL)"_s},
{2609, "S Bullet Barrel 04 (XL)"_s},
{2610, "S Bullet Barrel 05 (XL)"_s},
{2651, "Cylinder Scope 01 (M)"_s},
{2652, "Cylinder Scope 02 (M)"_s},
{2653, "Cylinder Scope 03 (M)"_s},
{2654, "Cylinder Scope 04 (M)"_s},
{2655, "Cylinder Scope 05 (M)"_s},
{2656, "Elec Scope 01 (M)"_s},
{2657, "Elec Scope 02 (M)"_s},
{2658, "Elec Scope 03 (M)"_s},
{2659, "Elec Scope 04 (M)"_s},
{2660, "Elec Scope 05 (M)"_s},
{2661, "Mark Scope 01 (S)"_s},
{2662, "Mark Scope 02 (S)"_s},
{2663, "Mark Scope 03 (S)"_s},
{2664, "Mark Scope 04 (S)"_s},
{2665, "Mark Scope 05 (S)"_s},
{2701, "S Single Weaponry (M)"_s},
{2702, "S Packed Weaponry 01 (M)"_s},
{2703, "S Packed Weaponry 02 (M)"_s},
{2704, "S Packed Weaponry 03 (M)"_s},
{2705, "S Packed Weaponry 04 (M)"_s},
{2706, "L Single Weaponry (XL)"_s},
{2707, "L Packed Weaponry 01 (XL)"_s},
{2708, "L Packed Weaponry 02 (XL)"_s},
{2709, "L Packed Weaponry 03 (XL)"_s},
{2710, "L Packed Weaponry 04 (XL)"_s},
{2711, "Atk Single Weaponry (XL)"_s},
{2712, "Atk Packed Weaponry 01 (XL)"_s},
{2713, "Atk Packed Weaponry 02 (XL)"_s},
{2714, "Atk Packed Weaponry 03 (XL)"_s},
{2715, "Atk Packed Weaponry 04 (XL)"_s},
{2751, "Vent 01 (M)"_s},
{2752, "Vent 02 (M)"_s},
{2753, "Vent 03 (M)"_s},
{2754, "Vent 04 (M)"_s},
{2755, "Vent 05 (M)"_s},
{2756, "Vent 06 (M)"_s},
{2757, "Vent 07 (M)"_s},
{2758, "Vent 08 (M)"_s},
{2759, "Vent 09 (M)"_s},
{2760, "Vent 10 (M)"_s},
{2901, "Complex Construct 01 (L)"_s},
{2902, "Complex Construct 02 (L)"_s},
{2903, "Complex Construct 03 (L)"_s},
{2904, "Complex Construct 04 (L)"_s},
{2905, "Complex Construct 05 (L)"_s},
// endregion
// region Connectors
{3001, "Circular Vent 01 (M)"_s},
{3002, "Circular Vent 02 (M)"_s},
{3003, "Circular Vent 03 (M)"_s},
{3004, "Circular Vent 04 (M)"_s},
{3005, "Circular Vent 05 (M)"_s},
{3006, "Circular Vent 06 (M)"_s},
{3007, "Circular Vent 07 (M)"_s},
{3008, "Circular Vent 08 (M)"_s},
{3009, "Circular Vent 09 (M)"_s},
{3010, "Circular Vent 10 (M)"_s},
{3011, "Circular Vent 11 (M)"_s},
{3012, "Circular Vent 12 (M)"_s},
{3013, "Circular Vent 13 (M)"_s},
{3014, "Circular Vent 14 (M)"_s},
{3015, "Circular Vent 15 (M)"_s},
{3051, "Reactor 01 (L)"_s},
{3052, "Reactor 02 (L)"_s},
{3053, "Reactor 03 (L)"_s},
{3054, "Reactor 04 (L)"_s},
{3055, "Reactor 05 (L)"_s},
{3101, "Connecting Tube 01 (XL)"_s},
{3102, "Connecting Tube 02 (XL)"_s},
{3103, "Connecting Tube 03 (XL)"_s},
{3104, "Connecting Tube 04 (XL)"_s},
{3105, "Connecting Tube 05 (XL)"_s},
{3151, "Latch 01 (M)"_s},
{3152, "Latch 02 (M)"_s},
{3153, "Latch 03 (M)"_s},
{3154, "Latch 04 (M)"_s},
{3155, "Latch 05 (M)"_s},
{3156, "Latch 06 (M)"_s},
{3157, "Latch 07 (M)"_s},
{3158, "Latch 08 (M)"_s},
{3159, "Latch 09 (M)"_s},
{3160, "Latch 10 (M)"_s},
{3161, "Latch 11 (M)"_s},
{3162, "Latch 12 (M)"_s},
{3163, "Latch 13 (M)"_s},
{3164, "Latch 14 (M)"_s},
{3165, "Latch 15 (M)"_s},
{3201, "Short Connector 01 (M)"_s},
{3202, "Short Connector 02 (M)"_s},
{3203, "Short Connector 03 (M)"_s},
{3204, "Short Connector 04 (M)"_s},
{3205, "Short Connector 05 (M)"_s},
{3206, "Antenna 01 (S)"_s},
{3207, "Antenna 02 (S)"_s},
{3208, "Antenna 03 (S)"_s},
{3209, "Antenna 04 (S)"_s},
{3210, "Antenna 05 (S)"_s},
{3226, "Long Connector 01 (XL)"_s},
{3227, "Long Connector 02 (XL)"_s},
{3228, "Long Connector 03 (XL)"_s},
{3229, "Long Connector 04 (XL)"_s},
{3230, "Long Connector 05 (XL)"_s},
{3231, "Long Connector 06 (XL)"_s},
{3232, "Long Connector 07 (XL)"_s},
{3233, "Long Connector 08 (XL)"_s},
{3234, "Long Connector 09 (XL)"_s},
{3235, "Long Connector 10 (XL)"_s},
{3251, "Complex Connector 01 (XL)"_s},
{3252, "Complex Connector 02 (XL)"_s},
{3253, "Complex Connector 03 (XL)"_s},
{3254, "Complex Connector 04 (XL)"_s},
{3255, "Complex Connector 05 (XL)"_s},
{3301, "Tube Line 01 (L)"_s},
{3302, "Tube Line 02 (L)"_s},
{3303, "Tube Line 03 (L)"_s},
{3304, "Tube Line 04 (XL)"_s},
{3305, "Tube Line 05 (XL)"_s},
{3306, "Tube Line 06 (M)"_s},
{3307, "Tube Line 07 (M)"_s},
{3308, "Tube Line 08 (M)"_s},
{3309, "Tube Line 09 (L)"_s},
{3310, "Tube Line 10 (L)"_s},
{3351, "Radar Plate 01 (M)"_s},
{3352, "Radar Plate 02 (M)"_s},
{3353, "Radar Plate 03 (M)"_s},
{3354, "Radar Plate 04 (M)"_s},
{3355, "Radar Plate 05 (M)"_s},
{3356, "Radar Pod 01 (M)"_s},
{3357, "Radar Pod 02 (M)"_s},
{3358, "Radar Pod 03 (M)"_s},
{3359, "Radar Pod 04 (M)"_s},
{3360, "Radar Pod 05 (M)"_s},
{3401, "Tri Pod 01 (M)"_s},
{3402, "Tri Pod 02 (M)"_s},
{3403, "Tri Pod 03 (M)"_s},
{3404, "Tri Pod 04 (M)"_s},
{3405, "Tri Pod 05 (M)"_s},
{3406, "Signal Pod 01 (M)"_s},
{3407, "Signal Pod 02 (M)"_s},
{3408, "Signal Pod 03 (M)"_s},
{3409, "Signal Pod 04 (M)"_s},
{3410, "Signal Pod 05 (M)"_s},
// endregion
};

View file

@ -106,6 +106,10 @@ void Mass::refreshValues() {
}
}
if(_mass->saveType() != "/Game/Core/Save/bpSaveGameUnit.bpSaveGameUnit_C"_s) {
Utility::Error{} << _filename << "is not a valid unit save.";
}
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
if(!unit_data) {
Utility::Error{} << "Couldn't find unit data in" << _filename;

View file

@ -106,6 +106,10 @@ void Profile::refreshValues() {
return;
}
if(_profile.saveType() != "/Game/Core/Save/bpSaveGameProfile.bpSaveGameProfile_C"_s) {
Utility::Error{} << _filename << "is not a valid profile save.";
}
auto name_prop = _profile.at<StringProperty>("CompanyName"_s);
if(!name_prop) {
_lastError = "No company name in "_s + _filename;

View file

@ -16,13 +16,7 @@
#include "SaveTool.h"
#include <cstring>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/String.h>
#include <Corrade/Utility/Unicode.h>
#include <Magnum/GL/DebugOutput.h>
@ -35,15 +29,10 @@
#include <curl/curl.h>
#include <windef.h>
#include <winuser.h>
#include <processthreadsapi.h>
#include <shellapi.h>
#include <shlobj.h>
#include <wtsapi32.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h"
using namespace Containers::Literals;
@ -204,39 +193,6 @@ SaveTool::~SaveTool() {
Utility::Debug{} << "Exiting...";
}
void SaveTool::handleFileAction(efsw::WatchID watch_id,
const std::string&,
const std::string& filename,
efsw::Action action,
std::string old_filename)
{
SDL_Event event;
SDL_zero(event);
event.type = _fileEventId;
if(watch_id == _watchIDs[StagingDir] && Utility::String::endsWith(filename, ".sav")) {
event.user.code = StagedUpdate;
SDL_PushEvent(&event);
return;
}
if(Utility::String::endsWith(filename, "Config.sav")) {
return;
} // TODO: actually do something when config files will finally be handled
if(!Utility::String::endsWith(filename, _currentProfile->account() + ".sav")) {
return;
}
event.user.code = action;
event.user.data1 = Containers::String{Containers::AllocatedInit, filename.c_str()}.release();
if(action == efsw::Actions::Moved) {
event.user.data2 = Containers::String{Containers::AllocatedInit, old_filename.c_str()}.release();
}
SDL_PushEvent(&event);
}
void SaveTool::drawEvent() {
#ifdef SAVETOOL_DEBUG_BUILD
tweak.update();
@ -299,406 +255,6 @@ void SaveTool::anyEvent(SDL_Event& event) {
}
}
void SaveTool::initEvent(SDL_Event& event) {
_initThread.join();
switch(event.user.code) {
case InitSuccess:
_uiState = UiState::ProfileManager;
ImGui::CloseCurrentPopup();
break;
case ProfileManagerFailure:
Utility::Error{} << "Error initialising ProfileManager:" << _profileManager->lastError();
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising ProfileManager", _profileManager->lastError().data(), window());
exit(EXIT_FAILURE);
break;
default:
break;
}
}
void SaveTool::updateCheckEvent(SDL_Event& event) {
_updateThread.join();
if(event.user.code == CurlInitFailed) {
_queue.addToast(Toast::Type::Error, "Couldn't initialise libcurl. Update check aborted."_s);
return;
}
else if(event.user.code == CurlError) {
Containers::String error{static_cast<char*>(event.user.data2), CURL_ERROR_SIZE, nullptr};
_queue.addToast(Toast::Type::Error, error, std::chrono::milliseconds{5000});
_queue.addToast(Toast::Type::Error, static_cast<char*>(event.user.data1), std::chrono::milliseconds{5000});
return;
}
else if(event.user.code == CurlTimeout) {
_queue.addToast(Toast::Type::Error, "The request timed out."_s);
return;
}
else if(event.user.code != 200) {
_queue.addToast(Toast::Type::Error, Utility::format("The request failed with error code {}", event.user.code));
return;
}
struct Version {
explicit Version(Containers::StringView str) {
std::size_t start_point = 0;
if(str[0] == 'v') {
start_point++;
}
auto components = Containers::StringView{str.data() + start_point}.split('.');
major = std::strtol(components[0].data(), nullptr, 10);
minor = std::strtol(components[1].data(), nullptr, 10);
patch = std::strtol(components[2].data(), nullptr, 10);
fullVersion = major * 10000 + minor * 100 + patch;
if(str.hasSuffix("-pre")) {
prerelease = true;
}
}
Int fullVersion;
Int major = 0;
Int minor = 0;
Int patch = 0;
bool prerelease = false;
bool operator==(const Version& other) const {
return fullVersion == other.fullVersion;
}
bool operator>(const Version& other) const {
if((fullVersion > other.fullVersion) ||
(fullVersion == other.fullVersion && prerelease == false && other.prerelease == true))
{
return true;
}
else {
return false;
}
}
operator Containers::String() const {
return Utility::format("{}.{}.{}{}", major, minor, patch, prerelease ? "-pre" : "");
}
};
static const Version current_ver{SAVETOOL_VERSION};
Containers::String response{static_cast<char*>(event.user.data1), strlen(static_cast<char*>(event.user.data1)), nullptr};
auto components = response.split('\n');
Version latest_ver{components.front()};
if(latest_ver > current_ver) {
_queue.addToast(Toast::Type::Warning, "Your version is out of date.\nCheck the settings for more information."_s,
std::chrono::milliseconds{5000});
_updateAvailable = true;
_latestVersion = latest_ver;
_releaseLink = Utility::format("https://williamjcm.ovh/git/williamjcm/MassBuilderSaveTool/releases/tag/v{}", components.front());
_downloadLink = components.back();
}
else if(latest_ver == current_ver || (current_ver > latest_ver && current_ver.prerelease == true)) {
_queue.addToast(Toast::Type::Success, "The application is already up to date."_s);
}
else if(current_ver > latest_ver && current_ver.prerelease == false) {
_queue.addToast(Toast::Type::Warning, "Your version is more recent than the latest one in the repo. How???"_s);
}
}
void SaveTool::fileUpdateEvent(SDL_Event& event) {
if(event.user.code == StagedUpdate) {
_massManager->refreshStagedMasses();
return;
}
Containers::String filename{static_cast<char*>(event.user.data1), std::strlen(static_cast<char*>(event.user.data1)), nullptr};
Containers::String old_filename;
Int index = 0;
Int old_index = 0;
bool is_current_profile = filename == _currentProfile->filename();
bool is_unit = filename.hasPrefix(_currentProfile->isDemo() ? "DemoUnit"_s : "Unit"_s);
if(is_unit) {
index = ((filename[_currentProfile->isDemo() ? 8 : 4] - 0x30) * 10) +
(filename[_currentProfile->isDemo() ? 9 : 5] - 0x30);
}
if(event.user.code == FileMoved) {
old_filename = Containers::String{static_cast<char*>(event.user.data2), std::strlen(static_cast<char*>(event.user.data2)), nullptr};
old_index = ((old_filename[_currentProfile->isDemo() ? 8 : 4] - 0x30) * 10) +
(old_filename[_currentProfile->isDemo() ? 9 : 5] - 0x30);
}
switch(event.user.code) {
case FileAdded:
if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
else {
_currentMass->setDirty();
}
}
break;
case FileDeleted:
if(is_current_profile) {
_currentProfile = nullptr;
_uiState = UiState::ProfileManager;
_profileManager->refreshProfiles();
}
else if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
}
break;
case FileModified:
if(is_current_profile) {
_currentProfile->refreshValues();
}
else if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
else {
if(_modifiedBySaveTool && _currentMass->filename() == filename) {
auto handle = CreateFileW(Utility::Unicode::widen(Containers::StringView{filename}).data(), GENERIC_READ, 0,
nullptr, OPEN_EXISTING, 0, nullptr);
if(handle && handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
_modifiedBySaveTool = false;
}
}
else {
_currentMass->setDirty();
}
}
}
break;
case FileMoved:
if(is_unit) {
if(old_filename.hasSuffix(".sav"_s)) {
_massManager->refreshHangar(index);
_massManager->refreshHangar(old_index);
}
}
break;
default:
_queue.addToast(Toast::Type::Warning, "Unknown file action type"_s);
}
}
void SaveTool::initialiseConfiguration() {
Utility::Debug{} << "Reading configuration file...";
if(_conf.hasValue("cheat_mode"_s)) {
_cheatMode = _conf.value<bool>("cheat_mode"_s);
}
else {
_conf.setValue("cheat_mode"_s, _cheatMode);
}
if(_conf.hasValue("unsafe_mode"_s)) {
_unsafeMode = _conf.value<bool>("unsafe_mode"_s);
}
else {
_conf.setValue("unsafe_mode"_s, _unsafeMode);
}
if(_conf.hasValue("startup_update_check"_s)) {
_checkUpdatesOnStartup = _conf.value<bool>("startup_update_check"_s);
}
else {
_conf.setValue("startup_update_check"_s, _checkUpdatesOnStartup);
}
if(_conf.hasValue("skip_disclaimer"_s)) {
_skipDisclaimer = _conf.value<bool>("skip_disclaimer"_s);
}
else {
_conf.setValue("skip_disclaimer"_s, _skipDisclaimer);
}
if(_conf.hasValue("frame_limit"_s)) {
std::string frame_limit = _conf.value("frame_limit"_s);
if(frame_limit == "vsync"_s) {
_framelimit = Framelimit::Vsync;
}
else if(frame_limit == "half_vsync"_s) {
_framelimit = Framelimit::HalfVsync;
}
else {
_framelimit = Framelimit::FpsCap;
_fpsCap = std::stoul(frame_limit);
}
}
else {
_conf.setValue("frame_limit"_s, "vsync"_s);
}
_conf.save();
}
void SaveTool::initialiseGui() {
Utility::Debug{} << "Initialising ImGui...";
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
auto reg_font = _rs.getRaw("SourceSansPro-Regular.ttf"_s);
ImFontConfig font_config;
font_config.FontDataOwnedByAtlas = false;
std::strcpy(font_config.Name, "Source Sans Pro");
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(reg_font.data()), reg_font.size(), 20.0f, &font_config);
auto icon_font = _rs.getRaw(FONT_ICON_FILE_NAME_FAS);
static const ImWchar icon_range[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
ImFontConfig icon_config;
icon_config.FontDataOwnedByAtlas = false;
icon_config.MergeMode = true;
icon_config.PixelSnapH = true;
icon_config.OversampleH = icon_config.OversampleV = 1;
icon_config.GlyphMinAdvanceX = 18.0f;
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(icon_font.data()), icon_font.size(), 16.0f, &icon_config, icon_range);
auto brand_font = _rs.getRaw(FONT_ICON_FILE_NAME_FAB);
static const ImWchar brand_range[] = { ICON_MIN_FAB, ICON_MAX_FAB, 0 };
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(brand_font.data()), brand_font.size(), 16.0f, &icon_config, brand_range);
auto mono_font = _rs.getRaw("SourceCodePro-Regular.ttf"_s);
ImVector<ImWchar> range;
ImFontGlyphRangesBuilder builder;
builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
builder.AddChar(u'š'); // This allows displaying Vladimír Vondruš' name in Corrade's and Magnum's licences.
builder.BuildRanges(&range);
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(mono_font.data()), mono_font.size(), 18.0f, &font_config, range.Data);
_imgui = ImGuiIntegration::Context(*ImGui::GetCurrentContext(), windowSize());
io.IniFilename = nullptr;
ImGuiStyle& style = ImGui::GetStyle();
style.WindowTitleAlign = {0.5f, 0.5f};
style.FrameRounding = 3.2f;
style.Colors[ImGuiCol_WindowBg] = ImColor(0xff1f1f1f);
}
void SaveTool::initialiseManager() {
SDL_Event event;
SDL_zero(event);
event.type = _initEventId;
_profileManager.emplace(_saveDir, _backupsDir);
if(!_profileManager->ready()) {
event.user.code = ProfileManagerFailure;
SDL_PushEvent(&event);
return;
}
event.user.code = InitSuccess;
SDL_PushEvent(&event);
}
auto SaveTool::initialiseToolDirectories() -> bool {
Utility::Debug{} << "Initialising Save Tool directories...";
_backupsDir = Utility::Path::join(Utility::Path::split(*Utility::Path::executableLocation()).first(), "backups");
_stagingDir = Utility::Path::join(Utility::Path::split(*Utility::Path::executableLocation()).first(), "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::Path::exists(_backupsDir)) {
Utility::Debug{} << "Backups directory not found, creating...";
if(!Utility::Path::make(_backupsDir)) {
Utility::Error{} << (_lastError = "Couldn't create the backups directory.");
return false;
}
}
if(!Utility::Path::exists(_stagingDir)) {
Utility::Debug{} << "Staging directory not found, creating...";
if(!Utility::Path::make(_stagingDir)) {
Utility::Error{} << (_lastError = "Couldn't create the backups directory.");
return false;
}
}
//if(!Utility::Directory::exists(_armouryDir)) {
// Utility::Debug{} << "Armoury directory not found, creating...";
// if(!Utility::Path::make(_armouryDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the armoury directory.");
// return false;
// }
//}
//if(!Utility::Directory::exists(_armoursDir)) {
// Utility::Debug{} << "Armours directory not found, creating...";
// if(!Utility::Path::make(_armoursDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the armours directory.");
// return false;
// }
//}
//if(!Utility::Directory::exists(_weaponsDir)) {
// Utility::Debug{} << "Weapons directory not found, creating...";
// if(!Utility::Path::make(_weaponsDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the weapons directory.");
// return false;
// }
//}
//if(!Utility::Directory::exists(_stylesDir)) {
// Utility::Debug{} << "Styles directory not found, creating...";
// if(!Utility::Path::make(_stylesDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the styles directory.");
// return false;
// }
//}
return true;
}
auto SaveTool::findGameDataDirectory() -> bool {
Utility::Debug{} << "Searching for the game's save directory...";
wchar_t* localappdata_path = nullptr;
Containers::ScopeGuard guard{localappdata_path, CoTaskMemFree};
if(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_NO_APPCONTAINER_REDIRECTION, nullptr, &localappdata_path) != S_OK)
{
Utility::Error{} << (_lastError = "SHGetKnownFolderPath() failed in SaveTool::findGameDataDirectory()"_s);
return false;
}
_gameDataDir = Utility::Path::join(Utility::Path::fromNativeSeparators(Utility::Unicode::narrow(localappdata_path)), "MASS_Builder"_s);
if(!Utility::Path::exists(_gameDataDir)) {
Utility::Error{} << (_lastError = _gameDataDir + " wasn't found. Make sure to play the game at least once."_s);
return false;
}
_configDir = Utility::Path::join(_gameDataDir, "Saved/Config/WindowsNoEditor"_s);
_saveDir = Utility::Path::join(_gameDataDir, "Saved/SaveGames"_s);
_screenshotsDir = Utility::Path::join(_gameDataDir, "Saved/Screenshots/WindowsNoEditor"_s);
return true;
}
void SaveTool::initialiseMassManager() {
_massManager.emplace(_saveDir, _currentProfile->account(), _currentProfile->isDemo(), _stagingDir);
}
void SaveTool::initialiseFileWatcher() {
_fileWatcher.emplace();
_watchIDs[SaveDir] = _fileWatcher->addWatch(_saveDir, this, false);
_watchIDs[StagingDir] = _fileWatcher->addWatch(_stagingDir, this, false);
_fileWatcher->watch();
}
void SaveTool::drawImGui() {
_imgui.newFrame();
@ -895,55 +451,3 @@ void SaveTool::checkGameState() {
_gameState = GameState::Unknown;
}
}
inline auto writeData(char* ptr, std::size_t size, std::size_t nmemb, Containers::String* buf)-> std::size_t {
if(!ptr || !buf) return 0;
(*buf) = Utility::format("{}{}", *buf, Containers::StringView{ptr, size * nmemb});
return size * nmemb;
}
void SaveTool::checkForUpdates() {
SDL_Event event;
SDL_zero(event);
event.type = _updateEventId;
auto curl = curl_easy_init();
if(!curl) {
event.user.code = CurlInitFailed;
}
if(curl) {
Containers::String response_body{Containers::AllocatedInit, ""};
Containers::String error_buffer{ValueInit, CURL_ERROR_SIZE * 2};
curl_easy_setopt(curl, CURLOPT_URL, "https://williamjcm.ovh/mbst/version");
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_body);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer.data());
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000L);
auto code = curl_easy_perform(curl);
if(code == CURLE_OK) {
long status = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
event.user.code = Int(status);
event.user.data1 = response_body.release();
}
else if(code == CURLE_OPERATION_TIMEDOUT) {
event.user.code = CurlTimeout;
}
else {
event.user.code = CurlError;
event.user.data1 = const_cast<char*>(curl_easy_strerror(code));
event.user.data2 = Containers::String{error_buffer}.release();
}
curl_easy_cleanup(curl);
}
SDL_PushEvent(&event);
}

View file

@ -0,0 +1,139 @@
// 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 <https://www.gnu.org/licenses/>.
#include <Corrade/Utility/String.h>
#include <Corrade/Utility/Unicode.h>
#include <fileapi.h>
#include <handleapi.h>
#include "SaveTool.h"
void SaveTool::handleFileAction(efsw::WatchID watch_id,
const std::string&,
const std::string& filename,
efsw::Action action,
std::string old_filename)
{
SDL_Event event;
SDL_zero(event);
event.type = _fileEventId;
if(watch_id == _watchIDs[StagingDir] && Utility::String::endsWith(filename, ".sav")) {
event.user.code = StagedUpdate;
SDL_PushEvent(&event);
return;
}
if(Utility::String::endsWith(filename, "Config.sav")) {
return;
} // TODO: actually do something when config files will finally be handled
if(!Utility::String::endsWith(filename, _currentProfile->account() + ".sav")) {
return;
}
event.user.code = action;
event.user.data1 = Containers::String{Containers::AllocatedInit, filename.c_str()}.release();
if(action == efsw::Actions::Moved) {
event.user.data2 = Containers::String{Containers::AllocatedInit, old_filename.c_str()}.release();
}
SDL_PushEvent(&event);
}
void SaveTool::fileUpdateEvent(SDL_Event& event) {
if(event.user.code == StagedUpdate) {
_massManager->refreshStagedMasses();
return;
}
Containers::String filename{static_cast<char*>(event.user.data1), std::strlen(static_cast<char*>(event.user.data1)), nullptr};
Containers::String old_filename;
Int index = 0;
Int old_index = 0;
bool is_current_profile = filename == _currentProfile->filename();
bool is_unit = filename.hasPrefix(_currentProfile->isDemo() ? "DemoUnit"_s : "Unit"_s);
if(is_unit) {
index = ((filename[_currentProfile->isDemo() ? 8 : 4] - 0x30) * 10) +
(filename[_currentProfile->isDemo() ? 9 : 5] - 0x30);
}
if(event.user.code == FileMoved) {
old_filename = Containers::String{static_cast<char*>(event.user.data2), std::strlen(static_cast<char*>(event.user.data2)), nullptr};
old_index = ((old_filename[_currentProfile->isDemo() ? 8 : 4] - 0x30) * 10) +
(old_filename[_currentProfile->isDemo() ? 9 : 5] - 0x30);
}
switch(event.user.code) {
case FileAdded:
if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
else {
_currentMass->setDirty();
}
}
break;
case FileDeleted:
if(is_current_profile) {
_currentProfile = nullptr;
_uiState = UiState::ProfileManager;
_profileManager->refreshProfiles();
}
else if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
}
break;
case FileModified:
if(is_current_profile) {
_currentProfile->refreshValues();
}
else if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
else {
if(_modifiedBySaveTool && _currentMass->filename() == filename) {
auto handle = CreateFileW(Utility::Unicode::widen(Containers::StringView{filename}).data(), GENERIC_READ, 0,
nullptr, OPEN_EXISTING, 0, nullptr);
if(handle && handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
_modifiedBySaveTool = false;
}
}
else {
_currentMass->setDirty();
}
}
}
break;
case FileMoved:
if(is_unit) {
if(old_filename.hasSuffix(".sav"_s)) {
_massManager->refreshHangar(index);
_massManager->refreshHangar(old_index);
}
}
break;
default:
_queue.addToast(Toast::Type::Warning, "Unknown file action type"_s);
}
}

View file

@ -0,0 +1,255 @@
// 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 <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/Unicode.h>
#include <shlobj.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h"
#include "SaveTool.h"
void SaveTool::initEvent(SDL_Event& event) {
_initThread.join();
switch(event.user.code) {
case InitSuccess:
_uiState = UiState::ProfileManager;
ImGui::CloseCurrentPopup();
break;
case ProfileManagerFailure:
Utility::Error{} << "Error initialising ProfileManager:" << _profileManager->lastError();
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising ProfileManager", _profileManager->lastError().data(), window());
exit(EXIT_FAILURE);
break;
default:
break;
}
}
void SaveTool::initialiseConfiguration() {
Utility::Debug{} << "Reading configuration file...";
if(_conf.hasValue("cheat_mode"_s)) {
_cheatMode = _conf.value<bool>("cheat_mode"_s);
}
else {
_conf.setValue("cheat_mode"_s, _cheatMode);
}
if(_conf.hasValue("unsafe_mode"_s)) {
_unsafeMode = _conf.value<bool>("unsafe_mode"_s);
}
else {
_conf.setValue("unsafe_mode"_s, _unsafeMode);
}
if(_conf.hasValue("startup_update_check"_s)) {
_checkUpdatesOnStartup = _conf.value<bool>("startup_update_check"_s);
}
else {
_conf.setValue("startup_update_check"_s, _checkUpdatesOnStartup);
}
if(_conf.hasValue("skip_disclaimer"_s)) {
_skipDisclaimer = _conf.value<bool>("skip_disclaimer"_s);
}
else {
_conf.setValue("skip_disclaimer"_s, _skipDisclaimer);
}
if(_conf.hasValue("frame_limit"_s)) {
std::string frame_limit = _conf.value("frame_limit"_s);
if(frame_limit == "vsync"_s) {
_framelimit = Framelimit::Vsync;
}
else if(frame_limit == "half_vsync"_s) {
_framelimit = Framelimit::HalfVsync;
}
else {
_framelimit = Framelimit::FpsCap;
_fpsCap = std::stoul(frame_limit);
}
}
else {
_conf.setValue("frame_limit"_s, "vsync"_s);
}
_conf.save();
}
void SaveTool::initialiseGui() {
Utility::Debug{} << "Initialising ImGui...";
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
auto reg_font = _rs.getRaw("SourceSansPro-Regular.ttf"_s);
ImFontConfig font_config;
font_config.FontDataOwnedByAtlas = false;
std::strcpy(font_config.Name, "Source Sans Pro");
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(reg_font.data()), reg_font.size(), 20.0f, &font_config);
auto icon_font = _rs.getRaw(FONT_ICON_FILE_NAME_FAS);
static const ImWchar icon_range[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
ImFontConfig icon_config;
icon_config.FontDataOwnedByAtlas = false;
icon_config.MergeMode = true;
icon_config.PixelSnapH = true;
icon_config.OversampleH = icon_config.OversampleV = 1;
icon_config.GlyphMinAdvanceX = 18.0f;
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(icon_font.data()), icon_font.size(), 16.0f, &icon_config, icon_range);
auto brand_font = _rs.getRaw(FONT_ICON_FILE_NAME_FAB);
static const ImWchar brand_range[] = { ICON_MIN_FAB, ICON_MAX_FAB, 0 };
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(brand_font.data()), brand_font.size(), 16.0f, &icon_config, brand_range);
auto mono_font = _rs.getRaw("SourceCodePro-Regular.ttf"_s);
ImVector<ImWchar> range;
ImFontGlyphRangesBuilder builder;
builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
builder.AddChar(u'š'); // This allows displaying Vladimír Vondruš' name in Corrade's and Magnum's licences.
builder.BuildRanges(&range);
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(mono_font.data()), mono_font.size(), 18.0f, &font_config, range.Data);
_imgui = ImGuiIntegration::Context(*ImGui::GetCurrentContext(), windowSize());
io.IniFilename = nullptr;
ImGuiStyle& style = ImGui::GetStyle();
style.WindowTitleAlign = {0.5f, 0.5f};
style.FrameRounding = 3.2f;
style.Colors[ImGuiCol_WindowBg] = ImColor(0xff1f1f1f);
}
void SaveTool::initialiseManager() {
SDL_Event event;
SDL_zero(event);
event.type = _initEventId;
_profileManager.emplace(_saveDir, _backupsDir);
if(!_profileManager->ready()) {
event.user.code = ProfileManagerFailure;
SDL_PushEvent(&event);
return;
}
event.user.code = InitSuccess;
SDL_PushEvent(&event);
}
auto SaveTool::initialiseToolDirectories() -> bool {
Utility::Debug{} << "Initialising Save Tool directories...";
_backupsDir = Utility::Path::join(Utility::Path::split(*Utility::Path::executableLocation()).first(), "backups");
_stagingDir = Utility::Path::join(Utility::Path::split(*Utility::Path::executableLocation()).first(), "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::Path::exists(_backupsDir)) {
Utility::Debug{} << "Backups directory not found, creating...";
if(!Utility::Path::make(_backupsDir)) {
Utility::Error{} << (_lastError = "Couldn't create the backups directory.");
return false;
}
}
if(!Utility::Path::exists(_stagingDir)) {
Utility::Debug{} << "Staging directory not found, creating...";
if(!Utility::Path::make(_stagingDir)) {
Utility::Error{} << (_lastError = "Couldn't create the backups directory.");
return false;
}
}
//if(!Utility::Directory::exists(_armouryDir)) {
// Utility::Debug{} << "Armoury directory not found, creating...";
// if(!Utility::Path::make(_armouryDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the armoury directory.");
// return false;
// }
//}
//if(!Utility::Directory::exists(_armoursDir)) {
// Utility::Debug{} << "Armours directory not found, creating...";
// if(!Utility::Path::make(_armoursDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the armours directory.");
// return false;
// }
//}
//if(!Utility::Directory::exists(_weaponsDir)) {
// Utility::Debug{} << "Weapons directory not found, creating...";
// if(!Utility::Path::make(_weaponsDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the weapons directory.");
// return false;
// }
//}
//if(!Utility::Directory::exists(_stylesDir)) {
// Utility::Debug{} << "Styles directory not found, creating...";
// if(!Utility::Path::make(_stylesDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the styles directory.");
// return false;
// }
//}
return true;
}
auto SaveTool::findGameDataDirectory() -> bool {
Utility::Debug{} << "Searching for the game's save directory...";
wchar_t* localappdata_path = nullptr;
Containers::ScopeGuard guard{localappdata_path, CoTaskMemFree};
if(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_NO_APPCONTAINER_REDIRECTION, nullptr, &localappdata_path) != S_OK)
{
Utility::Error{} << (_lastError = "SHGetKnownFolderPath() failed in SaveTool::findGameDataDirectory()"_s);
return false;
}
_gameDataDir = Utility::Path::join(Utility::Path::fromNativeSeparators(Utility::Unicode::narrow(localappdata_path)), "MASS_Builder"_s);
if(!Utility::Path::exists(_gameDataDir)) {
Utility::Error{} << (_lastError = _gameDataDir + " wasn't found. Make sure to play the game at least once."_s);
return false;
}
_configDir = Utility::Path::join(_gameDataDir, "Saved/Config/WindowsNoEditor"_s);
_saveDir = Utility::Path::join(_gameDataDir, "Saved/SaveGames"_s);
_screenshotsDir = Utility::Path::join(_gameDataDir, "Saved/Screenshots/WindowsNoEditor"_s);
return true;
}
void SaveTool::initialiseMassManager() {
_massManager.emplace(_saveDir, _currentProfile->account(), _currentProfile->isDemo(), _stagingDir);
}
void SaveTool::initialiseFileWatcher() {
_fileWatcher.emplace();
_watchIDs[SaveDir] = _fileWatcher->addWatch(_saveDir, this, false);
_watchIDs[StagingDir] = _fileWatcher->addWatch(_stagingDir, this, false);
_fileWatcher->watch();
}

View file

@ -502,10 +502,79 @@ void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<C
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::SameLine();
static Int tab = 0;
if(ImGui::SmallButton("Change")) {
ImGui::OpenPopup("##AccessoryPopup");
if(accessory.id < 1000) {
tab = 0;
}
else if(accessory.id >= 3000) {
tab = 3;
}
else if(accessory.id >= 2000) {
tab = 2;
}
else if(accessory.id >= 1000) {
tab = 1;
}
}
if(ImGui::BeginPopup("##AccessoryPopup")) {
Float selectable_width = 90.0f;
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, {0.5f, 0.0f});
if(ImGui::Selectable("Primitives", tab == 0, ImGuiSelectableFlags_DontClosePopups, {selectable_width, 0.0f})) {
tab = 0;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("Armours", tab == 1, ImGuiSelectableFlags_DontClosePopups, {selectable_width, 0.0f})) {
tab = 1;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("Components", tab == 2, ImGuiSelectableFlags_DontClosePopups, {selectable_width, 0.0f})) {
tab = 2;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("Connectors", tab == 3, ImGuiSelectableFlags_DontClosePopups, {selectable_width, 0.0f})) {
tab = 3;
}
ImGui::PopStyleVar();
ImGui::Separator();
if(ImGui::BeginListBox("##AccessoryListbox", {-1.0f, 0.0f})) {
for(const auto& acc : accessories) {
if(acc.first >= tab * 1000 && acc.first < ((tab + 1) * 1000)) {
if(ImGui::Selectable(acc.second.data(), acc.first == accessory.id)) {
accessory.id = acc.first;
accessory.attachIndex = 0;
}
if(acc.first == accessory.id) {
ImGui::SetItemDefaultFocus();
}
}
}
ImGui::EndListBox();
}
ImGui::EndPopup();
}
if(accessory.id > 0) {
ImGui::SameLine();
if(ImGui::SmallButton("Unequip")) {
accessory.id = 0;
accessory.attachIndex = -1;
}
}
ImGui::BeginGroup();
drawAlignedText("Styles:");

View file

@ -0,0 +1,162 @@
// 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 <https://www.gnu.org/licenses/>.
#include <Corrade/Utility/Format.h>
#include <curl/curl.h>
#include "SaveTool.h"
void SaveTool::updateCheckEvent(SDL_Event& event) {
_updateThread.join();
if(event.user.code == CurlInitFailed) {
_queue.addToast(Toast::Type::Error, "Couldn't initialise libcurl. Update check aborted."_s);
return;
}
else if(event.user.code == CurlError) {
Containers::String error{static_cast<char*>(event.user.data2), CURL_ERROR_SIZE, nullptr};
_queue.addToast(Toast::Type::Error, error, std::chrono::milliseconds{5000});
_queue.addToast(Toast::Type::Error, static_cast<char*>(event.user.data1), std::chrono::milliseconds{5000});
return;
}
else if(event.user.code == CurlTimeout) {
_queue.addToast(Toast::Type::Error, "The request timed out."_s);
return;
}
else if(event.user.code != 200) {
_queue.addToast(Toast::Type::Error, Utility::format("The request failed with error code {}", event.user.code));
return;
}
struct Version {
explicit Version(Containers::StringView str) {
std::size_t start_point = 0;
if(str[0] == 'v') {
start_point++;
}
auto components = Containers::StringView{str.data() + start_point}.split('.');
major = std::strtol(components[0].data(), nullptr, 10);
minor = std::strtol(components[1].data(), nullptr, 10);
patch = std::strtol(components[2].data(), nullptr, 10);
fullVersion = major * 10000 + minor * 100 + patch;
if(str.hasSuffix("-pre")) {
prerelease = true;
}
}
Int fullVersion;
Int major = 0;
Int minor = 0;
Int patch = 0;
bool prerelease = false;
bool operator==(const Version& other) const {
return fullVersion == other.fullVersion;
}
bool operator>(const Version& other) const {
if((fullVersion > other.fullVersion) ||
(fullVersion == other.fullVersion && prerelease == false && other.prerelease == true))
{
return true;
}
else {
return false;
}
}
operator Containers::String() const {
return Utility::format("{}.{}.{}{}", major, minor, patch, prerelease ? "-pre" : "");
}
};
static const Version current_ver{SAVETOOL_VERSION};
Containers::String response{static_cast<char*>(event.user.data1), strlen(static_cast<char*>(event.user.data1)), nullptr};
auto components = response.split('\n');
Version latest_ver{components.front()};
if(latest_ver > current_ver) {
_queue.addToast(Toast::Type::Warning, "Your version is out of date.\nCheck the settings for more information."_s,
std::chrono::milliseconds{5000});
_updateAvailable = true;
_latestVersion = latest_ver;
_releaseLink = Utility::format("https://williamjcm.ovh/git/williamjcm/MassBuilderSaveTool/releases/tag/v{}", components.front());
_downloadLink = components.back();
}
else if(latest_ver == current_ver || (current_ver > latest_ver && current_ver.prerelease == true)) {
_queue.addToast(Toast::Type::Success, "The application is already up to date."_s);
}
else if(current_ver > latest_ver && current_ver.prerelease == false) {
_queue.addToast(Toast::Type::Warning, "Your version is more recent than the latest one in the repo. How???"_s);
}
}
inline auto writeData(char* ptr, std::size_t size, std::size_t nmemb, Containers::String* buf)-> std::size_t {
if(!ptr || !buf) return 0;
(*buf) = Utility::format("{}{}", *buf, Containers::StringView{ptr, size * nmemb});
return size * nmemb;
}
void SaveTool::checkForUpdates() {
SDL_Event event;
SDL_zero(event);
event.type = _updateEventId;
auto curl = curl_easy_init();
if(!curl) {
event.user.code = CurlInitFailed;
}
if(curl) {
Containers::String response_body{Containers::AllocatedInit, ""};
Containers::String error_buffer{ValueInit, CURL_ERROR_SIZE * 2};
curl_easy_setopt(curl, CURLOPT_URL, "https://williamjcm.ovh/mbst/version");
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_body);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer.data());
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000L);
auto code = curl_easy_perform(curl);
if(code == CURLE_OK) {
long status = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
event.user.code = Int(status);
event.user.data1 = response_body.release();
}
else if(code == CURLE_OPERATION_TIMEDOUT) {
event.user.code = CurlTimeout;
}
else {
event.user.code = CurlError;
event.user.data1 = const_cast<char*>(curl_easy_strerror(code));
event.user.data2 = Containers::String{error_buffer}.release();
}
curl_easy_cleanup(curl);
}
SDL_PushEvent(&event);
}

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include "BinaryReader.h"
@ -52,6 +52,10 @@ auto UESaveFile::reloadData() -> bool {
return valid();
}
auto UESaveFile::saveType() -> Containers::StringView {
return _saveType;
}
void UESaveFile::appendProperty(UnrealPropertyBase::ptr prop) {
auto none_prop = std::move(_properties.back());
_properties.back() = std::move(prop);
@ -62,9 +66,6 @@ auto UESaveFile::props() -> Containers::ArrayView<UnrealPropertyBase::ptr> {
return _properties;
}
#include <string>
#include <Corrade/Containers/StringStl.h>
auto UESaveFile::saveToFile() -> bool {
BinaryWriter writer{_filepath + ".tmp"_s};
@ -192,7 +193,7 @@ void UESaveFile::loadData() {
return;
}
arrayReserve(_customFormatData, custom_format_data_size);
_customFormatData = Containers::Array<CustomFormatDataEntry>{custom_format_data_size};
for(UnsignedInt i = 0; i < custom_format_data_size; i++) {
CustomFormatDataEntry entry;

View file

@ -16,7 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Reference.h>
#include <Corrade/Containers/StaticArray.h>
@ -41,6 +41,8 @@ class UESaveFile {
auto reloadData() -> bool;
auto saveType() -> Containers::StringView;
template<typename T>
std::enable_if_t<std::is_base_of<UnrealPropertyBase, T>::value, T*>
at(Containers::StringView name) {