Compare commits

...

77 Commits

Author SHA1 Message Date
Guillaume Jacquemin 7b64d78b5b CMakeLists: bump version number. 2022-12-13 22:53:47 +01:00
Guillaume Jacquemin 59aa006611 Profile: actually fix the fix. 2022-12-13 22:53:36 +01:00
Guillaume Jacquemin 81b35476a0 CMakeLists: bump version number. 2022-12-13 21:08:30 +01:00
Guillaume Jacquemin b909f0ac13 Profile: fix bug in material saving when array doesn't exist. 2022-12-13 21:03:43 +01:00
Guillaume Jacquemin aff84ccc96 SaveTool: minor formatting changes. 2022-12-01 23:39:53 +01:00
Guillaume Jacquemin 8177d61755 SaveTool: fix some string-related issues. 2022-12-01 23:38:54 +01:00
Guillaume Jacquemin c0943bd084 Bump version number. 2022-11-30 10:03:57 +01:00
Guillaume Jacquemin 6fa21128ab SaveTool: improve FPS capping mechanism. 2022-11-30 10:03:45 +01:00
Guillaume Jacquemin a8ab212931 Bump version number. 2022-11-27 08:58:54 +01:00
Guillaume Jacquemin 9a7aaaeaca Update .gitignore. 2022-11-26 11:45:36 +01:00
Guillaume Jacquemin b3bf75918e Update README. 2022-11-26 11:44:57 +01:00
Guillaume Jacquemin 9de6766750 SaveTool: add the libcurl version to the about screen. 2022-11-26 11:31:14 +01:00
Guillaume Jacquemin 90ff680aff Main: fix Clang builds.
For some reason, only GCC has setlocale() wrapped in the std namespace.
2022-11-26 11:30:48 +01:00
Guillaume Jacquemin 07fca7b0d0 SaveTool: make the update checker split on CRLF, not just LF.
Just in case...
2022-11-25 17:34:42 +01:00
Guillaume Jacquemin 4ea694ea6e SaveTool: add advanced mode.
This'll help hide undocumented values.
2022-11-25 17:10:56 +01:00
Guillaume Jacquemin 722cc511d6 Main: don't forget to initialise Logger.
Especially on release builds...
2022-11-25 17:10:26 +01:00
Guillaume Jacquemin 924838ecb4 Logger: cleanup. 2022-11-25 17:10:01 +01:00
Guillaume Jacquemin dfd0d56ab0 Profile,SaveTool: add support for T6 materials. 2022-11-25 16:25:56 +01:00
Guillaume Jacquemin 89a10ff98d Maps/LastMissionId: update for version 0.9. 2022-11-25 15:19:37 +01:00
Guillaume Jacquemin 71fde374a9 Maps/StoryProgress: update for 0.9. 2022-11-25 15:19:21 +01:00
Guillaume Jacquemin c7cc3ba76c Maps/WeaponParts: added the new ranged base parts. 2022-11-25 11:47:12 +01:00
Guillaume Jacquemin 0aa933e766 SaveTool: change the SDL version check. 2022-11-25 11:46:47 +01:00
Guillaume Jacquemin 5ea6f1e3a7 SaveToo: add HiDPI support to ImGui.
I... kinda forgot to do that.
2022-11-25 11:46:14 +01:00
Guillaume Jacquemin 32046d9bf8 Update dependencies and adapt to changes. 2022-11-25 10:33:55 +01:00
Guillaume Jacquemin 3c4dfbda9a SaveTool: make the weapon part selector focus on the current item. 2022-11-25 09:43:32 +01:00
Guillaume Jacquemin d1712bf8cb Mass: add logging indentation to refreshValues(). 2022-11-25 09:43:11 +01:00
Guillaume Jacquemin 567546489f Maps/Accessories: update with 0.9 accessories.
Also update formatting for better readability.
2022-11-25 09:42:44 +01:00
Guillaume Jacquemin c054169124 SaveTool: update formatting of accessory numbers. 2022-11-24 11:13:08 +01:00
Guillaume Jacquemin b58ff5a763 Maps/Accessories: separate the size from the label.
That now means it's possible to filter the accessory list by accessory
size.
2022-11-24 10:46:58 +01:00
Guillaume Jacquemin 28eb40f6b4 PropertySerialiser: remove logging for missing serialisers.
It's spammy when dealing with most structures in the save files.
2022-11-24 10:45:37 +01:00
Guillaume Jacquemin d0a3375d7a Serialisers: use Logger. 2022-11-24 09:12:41 +01:00
Guillaume Jacquemin 393ec4a372 UESaveFile: use Logger. 2022-11-22 11:03:45 +01:00
Guillaume Jacquemin 83e9169dae MassManager: use Logger ***everywhere***. 2022-11-22 10:35:05 +01:00
Guillaume Jacquemin 8625f8835c SaveTool: use Logger. 2022-11-22 10:34:25 +01:00
Guillaume Jacquemin c3a9c8dd31 ToastQueue: use strings for data instead of views.
Why the fuck didn't I catch that sooner ?
2022-11-22 10:23:53 +01:00
Guillaume Jacquemin 51b25ea9c5 MassManager: use Logger _everywhere_. 2022-11-22 10:06:22 +01:00
Guillaume Jacquemin 1421257c4f SaveTool: some more cleanup. 2022-11-21 20:47:09 +01:00
Guillaume Jacquemin df5fa7a39e SaveTool: remove the FPS cap implementation.
I should port the code from my raycaster engine to here.
2022-11-21 20:40:10 +01:00
Guillaume Jacquemin c5b4747685 SaveTool: clean things up a bit. 2022-11-21 20:37:28 +01:00
Guillaume Jacquemin 7ddc8e0748 Main: edit a message. 2022-11-21 19:28:49 +01:00
Guillaume Jacquemin 7392d961c7 SaveTool: update formatting. 2022-11-21 19:28:37 +01:00
Guillaume Jacquemin fa81d2428e SaveTool: increase reliability. 2022-11-21 19:22:03 +01:00
Guillaume Jacquemin 79c97733db ProfileManager: use Logger. 2022-11-21 19:21:49 +01:00
Guillaume Jacquemin 060daebe17 SaveTool: improve reliability. 2022-11-21 19:09:18 +01:00
Guillaume Jacquemin 8cf5351f0b Profile: use Logger. 2022-11-21 19:03:18 +01:00
Guillaume Jacquemin e795e276da Profile(Manager),SaveTool: get rid of that legacy nonsense. 2022-11-21 19:03:00 +01:00
Guillaume Jacquemin bf820f65ec Profile: setResource() now can create an array if it's missing. 2022-11-21 18:59:30 +01:00
Guillaume Jacquemin fb6246cff7 Profile: ensure a profile is invalid if the GVAS underneath is too. 2022-11-21 18:58:57 +01:00
Guillaume Jacquemin 7257b9865c Profile: clean headers up a bit. 2022-11-21 18:58:19 +01:00
Guillaume Jacquemin 45bc2b97d9 Profile: add property names. 2022-11-21 18:58:01 +01:00
Guillaume Jacquemin 9de62db449 MassManager: use Logger. 2022-11-21 18:12:42 +01:00
Guillaume Jacquemin 44656b32d5 Mass: use Logger.
Oh, and some stuff didn't initially get logged, so now I can have better
insight in case of errors.
2022-11-21 18:01:29 +01:00
Guillaume Jacquemin 9ec88fa521 Mass: QoL changes.
Now property names are behind defines, so if a name changes, I don't
have to hunt it down in up to six source files.
2022-11-21 18:00:38 +01:00
Guillaume Jacquemin ee540b601e Mass: do some header cleanup. 2022-11-21 12:31:01 +01:00
Guillaume Jacquemin fe10bbb3f3 Main: use Logger. 2022-11-21 10:17:13 +01:00
Guillaume Jacquemin bb066d3134 CMakeLists: add Logger from the old 1.4 branch. 2022-11-21 10:15:10 +01:00
Guillaume Jacquemin e21e7a1aba Main: add processor detection. 2022-11-21 09:51:21 +01:00
Guillaume Jacquemin 714d8cc6bb Main: force UTF-8 locale. 2022-11-21 09:50:43 +01:00
Guillaume Jacquemin a33cbdfad6 SaveTool: fix includes. 2022-11-21 09:49:01 +01:00
Guillaume Jacquemin b7cd78ca21 Main: reformat includes. 2022-11-21 09:48:33 +01:00
Guillaume Jacquemin 453c5391a4 Main: Improve some dialogs. 2022-11-21 09:47:21 +01:00
Guillaume Jacquemin 05611d59b1 Main: update log. 2022-11-21 09:30:38 +01:00
Guillaume Jacquemin a5a8db289a Main: update formatting. 2022-11-21 09:30:13 +01:00
Guillaume Jacquemin 6130734764 Application.manifest: add HiDPI and force UTF-8 codepage. 2022-11-21 09:29:51 +01:00
Guillaume Jacquemin 16b8807eb7 SaveTool: remove unsafe mode.
It was just not good at all.
2022-11-21 09:20:21 +01:00
Guillaume Jacquemin f1ea2bda25 CMakeLists: officially start work on version 1.4. 2022-11-21 09:20:21 +01:00
Guillaume Jacquemin 42cec59c71 CMakeLists: improve formatting. 2022-11-21 09:20:21 +01:00
Guillaume Jacquemin 5c6a83c03b CMakeLists: improve libcurl builds. 2022-11-21 09:20:21 +01:00
Guillaume Jacquemin e6c597ffbc Disable Interconnect.
Nope, it's just not needed.
2022-11-21 09:20:02 +01:00
Guillaume Jacquemin 6cb52761be Bump version number. 2022-07-06 17:23:34 +02:00
Guillaume Jacquemin 6f2b19dbc3 SaveTool: fix a condition. 2022-07-06 17:22:55 +02:00
Guillaume Jacquemin e61d4bba85 Adapt to Corrade/Magnum changes. 2022-07-06 17:22:40 +02:00
Guillaume Jacquemin 38532d8c35 Update Corrade and Magnum. 2022-07-06 17:08:52 +02:00
Guillaume Jacquemin 8ba8ec3219 CMakeLists: bump version number. 2022-05-03 20:29:18 +02:00
Guillaume Jacquemin 55eb367eb2 UESaveFile: fix a bug that shouldn't have been there in the first place. 2022-05-03 20:25:32 +02:00
Guillaume Jacquemin b6398f3373 CMakeLists: bump version number. 2022-04-17 13:50:09 +02:00
Guillaume Jacquemin b598476809 UESaveFile: fix a bug when working with a temp file. 2022-04-17 12:46:25 +02:00
72 changed files with 2553 additions and 1538 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
build*/
*build*/
.idea/
*.kdev4
*~

View File

@ -21,16 +21,19 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/modules/" ${CMAKE_MODULE_PATH})
SET(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
set(BUILD_STATIC ON CACHE BOOL "" FORCE)
set(BUILD_STATIC_PIC ON CACHE BOOL "" FORCE)
set(BUILD_STATIC_UNIQUE_GLOBALS OFF CACHE BOOL "" FORCE)
set(WITH_INTERCONNECT ON CACHE BOOL "" FORCE)
set(WITH_PLUGINMANAGER ON CACHE BOOL "" FORCE)
set(WITH_TESTSUITE OFF CACHE BOOL "" FORCE)
set(WITH_MAIN ON CACHE BOOL "" FORCE)
set(CORRADE_BUILD_DEPRECATED OFF CACHE BOOL "" FORCE)
set(CORRADE_BUILD_STATIC ON CACHE BOOL "" FORCE)
set(CORRADE_BUILD_STATIC_PIC ON CACHE BOOL "" FORCE)
set(CORRADE_BUILD_STATIC_UNIQUE_GLOBALS OFF CACHE BOOL "" FORCE)
set(CORRADE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_INTERCONNECT OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_PLUGINMANAGER OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_TESTSUITE OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_MAIN ON CACHE BOOL "" FORCE)
set(CORRADE_UTILITY_USE_ANSI_COLORS ON CACHE BOOL "" FORCE)
add_subdirectory(third-party/corrade EXCLUDE_FROM_ALL)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(DIRECTX OFF CACHE BOOL "" FORCE) # We use OpenGL.
set(SDL_ATOMIC OFF CACHE BOOL "" FORCE)
set(SDL_CPUINFO OFF CACHE BOOL "" FORCE)
@ -47,26 +50,34 @@ set(SDL_TIMERS ON CACHE BOOL "" FORCE)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
add_subdirectory(third-party/SDL EXCLUDE_FROM_ALL)
set(TARGET_GL ON CACHE BOOL "" FORCE)
set(TARGET_GLES OFF CACHE BOOL "" FORCE)
set(TARGET_VK OFF CACHE BOOL "" FORCE)
set(WITH_AUDIO OFF CACHE BOOL "" FORCE)
set(WITH_DEBUGTOOLS OFF CACHE BOOL "" FORCE)
set(WITH_GL ON CACHE BOOL "" FORCE)
set(WITH_MESHTOOLS OFF CACHE BOOL "" FORCE)
set(WITH_PRIMITIVES OFF CACHE BOOL "" FORCE)
set(WITH_SCENEGRAPH OFF CACHE BOOL "" FORCE)
set(WITH_SHADERS ON CACHE BOOL "" FORCE)
set(WITH_SHADERTOOLS OFF CACHE BOOL "" FORCE)
set(WITH_TEXT OFF CACHE BOOL "" FORCE)
set(WITH_TEXTURETOOLS OFF CACHE BOOL "" FORCE)
set(WITH_TRADE OFF CACHE BOOL "" FORCE)
set(WITH_VK OFF CACHE BOOL "" FORCE)
set(WITH_SDL2APPLICATION ON CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_STATIC ON CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_STATIC_PIC ON CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_STATIC_UNIQUE_GLOBALS OFF CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_DEPRECATED OFF CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(MAGNUM_TARGET_GL ON CACHE BOOL "" FORCE)
set(MAGNUM_TARGET_GLES OFF CACHE BOOL "" FORCE)
set(MAGNUM_TARGET_VK OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_AUDIO OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_DEBUGTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_GL ON CACHE BOOL "" FORCE)
set(MAGNUM_WITH_MATERIALTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_MESHTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_PRIMITIVES OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SCENEGRAPH OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SCENETOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SHADERS ON CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SHADERTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_TEXT OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_TEXTURETOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_TRADE OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_VK OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SDL2APPLICATION ON CACHE BOOL "" FORCE)
add_subdirectory(third-party/magnum EXCLUDE_FROM_ALL)
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third-party/imgui)
set(WITH_IMGUI ON CACHE BOOL "" FORCE)
set(MAGNUM_WITH_IMGUI ON CACHE BOOL "" FORCE)
add_subdirectory(third-party/magnum-integration EXCLUDE_FROM_ALL)
set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
@ -81,7 +92,6 @@ set(BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(BUILD_REGRESS OFF CACHE BOOL "" FORCE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(BUILD_DOC OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
add_subdirectory(third-party/libzip EXCLUDE_FROM_ALL)
set(VERBOSE OFF CACHE BOOL "" FORCE)
@ -89,12 +99,18 @@ set(BUILD_TEST_APP OFF CACHE BOOL "" FORCE)
set(EFSW_INSTALL OFF CACHE BOOL "" FORCE)
add_subdirectory(third-party/efsw EXCLUDE_FROM_ALL)
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(ENABLE_UNICODE ON CACHE BOOL "" FORCE)
set(ENABLE_DEBUG ON CACHE BOOL "" FORCE)
set(ENABLE_INET_PTON OFF CACHE BOOL "" FORCE)
set(ENABLE_DEBUG OFF CACHE BOOL "" FORCE)
set(ENABLE_THREADED_RESOLVER OFF CACHE BOOL "" FORCE)
set(HTTP_ONLY ON CACHE BOOL "" FORCE)
set(USE_LIBIDN2 OFF CACHE BOOL "" FORCE)
set(USE_WIN32_IDN ON CACHE BOOL "" FORCE)
set(CURL_USE_LIBPSL OFF CACHE BOOL "" FORCE)
set(CURL_STATIC_CRT OFF CACHE BOOL "" FORCE)
set(CURL_USE_SCHANNEL ON CACHE BOOL "" FORCE)
set(CURL_USE_LIBSSH2 OFF CACHE BOOL "" FORCE) # For some reason, even when HTTP_ONLY is set to ON, libcurl will try to link to libssh2.
add_subdirectory(third-party/curl EXCLUDE_FROM_ALL)

View File

@ -1,16 +1,20 @@
# M.A.S.S. Builder Save Tool
A save file manager and editor for M.A.S.S. Builder. Based on [wxMASSManager](https://williamjcm.ovh/git/williamjcm/wxMASSManager), this is a fork using Magnum and ImGui for the UI.
A save file manager and editor for M.A.S.S. Builder. Based on [wxMASSManager](https://git.williamjcm.ovh/williamjcm/wxMASSManager),
this is a fork using Magnum and ImGui for the UI.
## Installing
Get the `MassBuilderSaveTool-<version>.zip` file from the [Releases](https://williamjcm.ovh/git/williamjcm/MassBuilderSaveTool/releases) page, and extract it somewhere. Then, launch `MassBuilderSaveTool.exe`.
Get the `MassBuilderSaveTool-<version>.zip` file from the [the main website](https://williamjcm.ovh/mbst) or on the
[Releases](https://git.williamjcm.ovh/williamjcm/MassBuilderSaveTool/releases) page, and extract it somewhere. Then,
launch `MassBuilderSaveTool-<version>.exe`.
## Building on MSYS2 - IGNORE IF YOU JUST WANT TO USE THE APP!
1. Install the 64-bit (`x86_64`) version of [MSYS2](https://www.msys2.org/) in its default path (`C:\msys64`), and update it fully.
2. Run `pacman -S git mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja`.
3. In a `MINGW64` shell, type `git clone https://github.com/williamjcm/MassBuilderSaveTool`.
1. Install the 64-bit (`x86_64`) version of [MSYS2](https://www.msys2.org/) in its default path (`C:\msys64`), and
update it fully.
2. Run `pacman -S git mingw-w64-ucrt-x86_64-toolchain mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja`.
3. In a `URCT64` shell, type `git clone https://github.com/williamjcm/MassBuilderSaveTool`.
4. Type `cd MassBuilderSaveTool && git submodule init && git submodule update && mkdir build && cd build`.
5. Type `cmake -GNinja -DCMAKE_BUILD_TYPE=Release ..`
6. Type `ninja`

View File

@ -11,4 +11,16 @@
/>
</dependentAssembly>
</dependency>
</assembly>
<asmv3:application>
<asmv3:windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">
UTF-8
</activeCodePage>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
true/pm</dpiAware> <!-- legacy -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
permonitorv2,permonitor
</dpiAwareness> <!-- falls back to pm if pmv2 is not available -->
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

View File

@ -18,9 +18,9 @@ set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(SAVETOOL_PROJECT_VERSION 1.3.2)
set(SAVETOOL_PROJECT_VERSION 1.4.3)
find_package(Corrade REQUIRED Main Containers Utility Interconnect)
find_package(Corrade REQUIRED Main Containers Utility)
find_package(Magnum REQUIRED GL Sdl2Application)
find_package(MagnumIntegration REQUIRED ImGui)
@ -28,85 +28,101 @@ set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON)
corrade_add_resource(Assets assets.conf)
add_library(Logger STATIC EXCLUDE_FROM_ALL
Logger/Logger.h
Logger/Logger.cpp
Logger/EntryType.h
Logger/MagnumLogBuffer.h
Logger/MagnumLogBuffer.cpp
)
target_link_libraries(Logger PRIVATE
Corrade::Utility
Magnum::Magnum
)
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/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/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)
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)
Corrade::Containers
Corrade::Utility
Magnum::Magnum
Logger
)
add_executable(MassBuilderSaveTool WIN32
main.cpp
@ -127,6 +143,7 @@ add_executable(MassBuilderSaveTool WIN32
ProfileManager/ProfileManager.cpp
Profile/Profile.h
Profile/Profile.cpp
Profile/PropertyNames.h
Profile/ResourceIDs.h
MassManager/MassManager.h
MassManager/MassManager.cpp
@ -143,6 +160,7 @@ add_executable(MassBuilderSaveTool WIN32
Mass/Mass_Weapons.cpp
Mass/Mass_Styles.cpp
Mass/Mass_DecalsAccessories.cpp
Mass/PropertyNames.h
Mass/Weapon.h
Mass/Weapon.cpp
Mass/WeaponPart.h
@ -164,14 +182,17 @@ add_executable(MassBuilderSaveTool WIN32
FontAwesome/IconsFontAwesome5.h
FontAwesome/IconsFontAwesome5Brands.h
resource.rc
${Assets})
${Assets}
)
if(CMAKE_BUILD_TYPE STREQUAL Debug)
add_compile_definitions(SAVETOOL_DEBUG_BUILD)
endif()
add_compile_definitions(SAVETOOL_VERSION="${SAVETOOL_PROJECT_VERSION}"
SAVETOOL_CODENAME="Dickish Cyclops"
SUPPORTED_GAME_VERSION="0.8.6")
add_compile_definitions(
SAVETOOL_VERSION="${SAVETOOL_PROJECT_VERSION}"
SAVETOOL_CODENAME="Enigmatic Ellenier"
SUPPORTED_GAME_VERSION="0.9.x"
)
if(CMAKE_BUILD_TYPE STREQUAL Release)
set_target_properties(MassBuilderSaveTool PROPERTIES OUTPUT_NAME MassBuilderSaveTool-${SAVETOOL_PROJECT_VERSION})
@ -186,15 +207,16 @@ endif()
target_link_libraries(MassBuilderSaveTool PRIVATE
Corrade::Containers
Corrade::Utility
Corrade::Interconnect
Corrade::Main
Magnum::Magnum
Magnum::GL
Magnum::Sdl2Application
MagnumIntegration::ImGui
Logger
UESaveFile
efsw
zip
libcurl
imm32
wtsapi32)
wtsapi32
)

23
src/Logger/EntryType.h Normal file
View File

@ -0,0 +1,23 @@
#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 <https://www.gnu.org/licenses/>.
enum class EntryType {
Info,
Warning,
Error,
};

112
src/Logger/Logger.cpp Normal file
View File

@ -0,0 +1,112 @@
// 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 <iostream>
#include <Corrade/Utility/Debug.h>
#include "Logger.h"
using Containers::Array;
using Utility::Debug;
using Utility::Warning;
using Utility::Error;
using namespace Magnum;
Logger&
Logger::instance() {
static Logger logger;
return logger;
}
void
Logger::initialise() {
#ifndef SAVETOOL_DEBUG_BUILD
_logFile.open("SaveToolLog.txt", std::ios::trunc);
_logFile << "In case you encounter a bug:\n" <<
"1. Do not run the Save Tool again, as this log will be cleared.\n" <<
"2. Go to either the official Sekai Project Discord guild, or the community M.A.S.S. Builder one.\n" <<
"3. Mention me (William JCM#2301) to get my attention, with a description of the bug.\n"
" Please include as many details as possible, I don't want to play \"20 questions\", and neither do you.\n" <<
"4. Send me this file _when I ask for it_, preferably in DMs.\n" <<
std::endl;
#endif
}
void
Logger::indent() {
_indentLevel++;
}
void
Logger::unindent() {
if(_indentLevel > 0) {
_indentLevel--;
}
}
void
Logger::log(EntryType type, StringView location, StringView message) {
Debug d{
#ifndef SAVETOOL_DEBUG_BUILD
&_logFile
#else
&std::cout
#endif
};
#ifdef SAVETOOL_DEBUG_BUILD
#define COLOURED_TEXT(colour, text) Debug::color(Debug::Color::colour) << (text) << Debug::resetColor
#else
#define COLOURED_TEXT(colour, text) (text)
#endif
switch(type) {
case EntryType::Info:
d << COLOURED_TEXT(Default, "[ INFO]"_s);
break;
case EntryType::Warning:
d << COLOURED_TEXT(Yellow, "[WARNING]"_s);
break;
case EntryType::Error:
d << COLOURED_TEXT(Red, "[ ERROR]"_s);
break;
}
#undef COLOURED_TEXT
d << "["_s << Debug::nospace << location << Debug::nospace << "]";
for(UnsignedInt i = 0; i < _indentLevel; i++) {
d << Debug::nospace << " "_s << Debug::nospace;
}
d << ((message.back() == '\n') ? message.exceptSuffix(1) : message);
}
void
Logger::lockMutex() {
_logMutex.lock();
}
void
Logger::unlockMutex() {
_logMutex.unlock();
}
Logger&
logger() {
return Logger::instance();
}

90
src/Logger/Logger.h Normal file
View File

@ -0,0 +1,90 @@
#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 <https://www.gnu.org/licenses/>.
#include <ctime>
#include <mutex>
#ifndef SAVETOOL_DEBUG_BUILD
#include <fstream>
#endif
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Utility/Format.h>
#include <Magnum/Types.h>
#include "EntryType.h"
using namespace Corrade;
using Containers::ArrayView;
using Containers::String;
using Containers::StringView;
using namespace Magnum;
using namespace Containers::Literals;
class Logger {
public:
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
Logger(Logger&&) = delete;
Logger& operator=(Logger&&) = delete;
static auto instance() -> Logger&;
void initialise();
void indent();
void unindent();
void log(EntryType type, StringView location, StringView message);
void lockMutex();
void unlockMutex();
private:
Logger() = default;
#ifndef SAVETOOL_DEBUG_BUILD
std::ofstream _logFile;
#endif
UnsignedInt _indentLevel = 0;
std::mutex _logMutex{};
};
auto logger() -> Logger&;
#define LOG(entry_type, message) logger().lockMutex(); \
logger().log(EntryType::entry_type, \
Utility::format("{}:{}", StringView{__builtin_FILE()}.find("src"_s).data() + 4, __builtin_LINE()), \
message); \
logger().unlockMutex()
#define LOG_INFO(message) LOG(Info, message)
#define LOG_WARNING(message) LOG(Warning, message)
#define LOG_ERROR(message) LOG(Error, message)
#define LOG_INFO_FORMAT(message, ...) LOG_INFO(Utility::format(message, __VA_ARGS__))
#define LOG_WARNING_FORMAT(message, ...) LOG_WARNING(Utility::format(message, __VA_ARGS__))
#define LOG_ERROR_FORMAT(message, ...) LOG_ERROR(Utility::format(message, __VA_ARGS__))

View File

@ -0,0 +1,30 @@
// 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 "MagnumLogBuffer.h"
MagnumLogBuffer::MagnumLogBuffer(EntryType type): std::stringbuf(std::ios_base::out), _type{type} {}
MagnumLogBuffer::~MagnumLogBuffer() = default;
int
MagnumLogBuffer::sync() {
logger().lockMutex();
logger().log(_type, "Corrade/Magnum"_s, str().c_str());
logger().unlockMutex();
str({});
return 0;
}

View File

@ -0,0 +1,34 @@
#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 <https://www.gnu.org/licenses/>.
#include <sstream>
#include "Logger.h"
#include "EntryType.h"
class MagnumLogBuffer : public std::stringbuf {
public:
explicit MagnumLogBuffer(EntryType type);
~MagnumLogBuffer();
private:
int sync() override;
EntryType _type;
};

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,8 @@ static const std::map<Int, Containers::StringView> mission_id_map {{
{0x0070, "Mission 13 - The Sandstorm Glutton"_s},
{0x0071, "Mission 14 - An Icy Investigation"_s},
{0x0072, "Mission 15 - Outposts Line of Defense"_s},
{0x0073, "Mission 16 - Hidden in the Pass"_s},
{0x0074, "Mission 17 - Homebase Security"_s},
// Hunting grounds
{0x00C8, "Hunt 1 - Desert Pathway Safety"_s},
@ -50,6 +52,7 @@ static const std::map<Int, Containers::StringView> mission_id_map {{
{0x00CA, "Hunt 3 - Abandoned Valley Raid"_s},
{0x00CB, "Hunt 4 - Depths of the Machineries"_s},
{0x00CC, "Hunt 5 - Crater Crashers"_s},
{0x00CD, "Hunt 6 - Prototype Performance Tests"_s},
// Challenges
{0x012C, "Challenge 1 - Redline Battlefront"_s},

View File

@ -92,16 +92,23 @@ static const Corrade::Containers::Array<StoryProgressPoint> story_progress
{0x0515, "Chapter 2"_s, "Returned to hangar"_s, "After mission 12"_s},
{0x0516, "Chapter 2"_s, "Got hunt 4 and mission 13 briefing"_s, "After mission 12"_s},
{0x0578, "Chapter 3"_s, "Chapter start, talking with Reina"_s, "After mission 13"_s},
{0x0579, "Chapter 3"_s, "Returned to hangar"_s, "After mission 13"_s},
{0x057A, "Chapter 3"_s, "Talked with Reina in development section"_s, "After mission 13"_s},
{0x057B, "Chapter 3"_s, "Got briefing for challenges 1, 2, and 3"_s, "After mission 13"_s},
{0x057C, "Chapter 3"_s, "Talked with Reina about device"_s, "After mission 13"_s},
{0x057D, "Chapter 3"_s, "Got mission 14 briefing"_s, "After mission 13"_s},
{0x05DC, "Chapter 3"_s, "Talking with Reina and Nier"_s, "After mission 14"_s},
{0x05DD, "Chapter 3"_s, "Returned to hangar"_s, "After mission 14"_s},
{0x05DE, "Chapter 3"_s, "Got briefing for mission 15 and hunt 5"_s, "After mission 14"_s},
{0x0640, "Chapter 3"_s, "Talking with Nier and Kazu, and Reina"_s, "After mission 15"_s},
{0x0641, "Chapter 3"_s, "Returned to hangar"_s, "After mission 15"_s},
{0x0578, "Chapter 3"_s, "Chapter start, talking with Reina"_s, "After mission 13"_s},
{0x0579, "Chapter 3"_s, "Returned to hangar"_s, "After mission 13"_s},
{0x057A, "Chapter 3"_s, "Talked with Reina in development section"_s, "After mission 13"_s},
{0x057B, "Chapter 3"_s, "Got briefing for challenges 1, 2, and 3"_s, "After mission 13"_s},
{0x057C, "Chapter 3"_s, "Talked with Reina about device"_s, "After mission 13"_s},
{0x057D, "Chapter 3"_s, "Got mission 14 briefing"_s, "After mission 13"_s},
{0x05DC, "Chapter 3"_s, "Talking with Reina and Nier"_s, "After mission 14"_s},
{0x05DD, "Chapter 3"_s, "Returned to hangar"_s, "After mission 14"_s},
{0x05DE, "Chapter 3"_s, "Got briefing for mission 15 and hunt 5"_s, "After mission 14"_s},
{0x0640, "Chapter 3"_s, "Talking with Nier and Kazu, and Reina"_s, "After mission 15"_s},
{0x0641, "Chapter 3"_s, "Returned to hangar"_s, "After mission 15"_s},
{0x0642, "Chapter 3"_s, "Talked with Reina and Nier in dev section"_s, "After mission 15"_s},
{0x0643, "Chapter 3"_s, "Got briefing for mission 16"_s, "After mission 15"_s},
{0x06A4, "Chapter 3"_s, "Talking with Kunai"_s, "After mission 16"_s},
{0x06A5, "Chapter 3"_s, "Returned to hangar"_s, "After mission 16"_s},
{0x06A6, "Chapter 3"_s, "Got mission 17 briefing"_s, "After mission 16"_s},
{0x0708, "Chapter 3"_s, "Debriefing"_s, "After mission 17"_s},
{0x070A, "Chapter 3"_s, "Got hunt 6 briefing"_s, "After mission 17"_s},
}
};

View File

@ -213,110 +213,142 @@ static const std::map<Int, Containers::StringView> shield_shells {
// region Bullet Shooters
static const std::map<Int, Containers::StringView> bshooter_triggers {
{0, "BL-Combat Trigger (1H)"_s},
{1, "Light Machine Trigger (1H)"_s},
{2, "Tactical Trigger (1H)"_s},
{3, "Compact Trigger (1H)"_s},
{4, "Longhold Trigger (1H)"_s},
{5, "Downhold Trigger (1H)"_s},
{6, "Cellblock Trigger (1H)"_s},
{0, "BL-Combat Trigger (1H)"_s},
{1, "Light Machine Trigger (1H)"_s},
{2, "Tactical Trigger (1H)"_s},
{3, "Compact Trigger (1H)"_s},
{4, "Longhold Trigger (1H)"_s},
{5, "Downhold Trigger (1H)"_s},
{6, "Cellblock Trigger (1H)"_s},
{99, "Base Trigger (1H)"_s},
{100, "BL-Machine Trigger (2H)"_s},
{101, "BL-Short Trigger (2H)"_s},
{102, "Shielded Trigger (2H)"_s},
{103, "Platedframe Trigger (2H)"_s},
{104, "Sidebox Trigger (2H) (Motion)"_s},
{199, "2H Base Trigger (2H)"_s},
};
static const std::map<Int, Containers::StringView> bshooter_barrels {
{0, "BL-Combat Barrel (1 shot)"_s},
{1, "Shock Absorb Barrel (1 shot) (Motion)"_s},
{2, "Muzzlemod Barrel (1 shot)"_s},
{3, "Triangular Barrel (1 shot)"_s},
{4, "Recoilblock Barrel (1 shot) (Motion)"_s},
{0, "BL-Combat Barrel (1 shot)"_s},
{1, "Shock Absorb Barrel (1 shot) (Motion)"_s},
{2, "Muzzlemod Barrel (1 shot)"_s},
{3, "Triangular Barrel (1 shot)"_s},
{4, "Recoilblock Barrel (1 shot) (Motion)"_s},
{97, "Short S Base Barrel (1 shot)"_s},
{98, "Medium S Base Barrel (1 shot)"_s},
{99, "Long S Base Barrel (1 shot)"_s},
{100, "Six-Barrel Gatling (Auto) (Motion)"_s},
{101, "Modded Six-Barrel Gatling (Auto) (Motion)"_s},
{102, "Four-Barrel Gatling (Auto) (Motion)"_s},
{103, "Retro Style Gatling (Auto) (Motion)"_s},
{197, "Short G Base Barrel (Auto)"_s},
{198, "Medium G Base Barrel (Auto)"_s},
{199, "Long G Base Barrel (Auto)"_s},
{200, "Blast Barrel (Spread)"_s},
{201, "Wideblast Barrel (Spread) (Motion)"_s},
{202, "Pelleter Barrel (Spread) (Motion)"_s},
{203, "Lockhold Barrel (Spread) (Motion)"_s},
{297, "Short B Base Barrel (Spread)"_s},
{298, "Medium B Base Barrel (Spread)"_s},
{299, "Long B Base Barrel (Spread)"_s},
{300, "Propulsive Barrel (Detonate)"_s},
{301, "Roundbox Barrel (Detonate)"_s},
{302, "ShieldDet Barrel (Detonate)"_s},
{303, "RecoilDet Barrel (Detonate) (Motion)"_s},
{397, "Short D Base Barrel (Detonate)"_s},
{398, "Medium D Base Barrel (Detonate)"_s},
{399, "Long D Base Barrel (Detonate)"_s},
};
// endregion
//region Energy Shooters
static const std::map<Int, Containers::StringView> eshooter_triggers {
{0, "EN-Rifle Trigger (1H)"_s},
{1, "Underarm Trigger (1H)"_s},
{2, "EN-Inverted Trigger (1H)"_s},
{3, "EN-Submachine Trigger (1H) (Motion)"_s},
{4, "EN-Needler Trigger (1H)"_s},
{5, "Angular Trigger (1H)"_s},
{6, "Exposed Trigger (1H)"_s},
{0, "EN-Rifle Trigger (1H)"_s},
{1, "Underarm Trigger (1H)"_s},
{2, "EN-Inverted Trigger (1H)"_s},
{3, "EN-Submachine Trigger (1H) (Motion)"_s},
{4, "EN-Needler Trigger (1H)"_s},
{5, "Angular Trigger (1H)"_s},
{6, "Exposed Trigger (1H)"_s},
{99, "Base EnTrigger (1H)"_s},
{100, "EN-Combat Trigger (2H)"_s},
{101, "EN-Alternate Trigger (2H)"_s},
{102, "Framed Trigger (2H) (Motion)"_s},
{103, "Stabilised Trigger (2H)"_s},
{104, "EN-Heavy Trigger (2H)"_s},
{199, "2H Base EnTrigger (2H)"_s},
};
static const std::map<Int, Containers::StringView> eshooter_busters {
{0, "EN-Combat Buster (1 shot)"_s},
{1, "Delta Cycler (1 shot) (Motion)"_s},
{2, "EN-Longbarrel Buster (1 shot)"_s},
{3, "Kinetic Buster (1 shot) (Motion)"_s},
{0, "EN-Combat Buster (1 shot)"_s},
{1, "Delta Cycler (1 shot) (Motion)"_s},
{2, "EN-Longbarrel Buster (1 shot)"_s},
{3, "Kinetic Buster (1 shot) (Motion)"_s},
{97, "Short S Base Buster (1 shot)"_s},
{98, "Medium S Base Buster (1 shot)"_s},
{99, "Long S Base Buster (1 shot)"_s},
{100, "EN-Rifle Buster (Auto)"_s},
{101, "EN-Focus Buster (Auto)"_s},
{102, "Machinist Buster (Auto)"_s},
{103, "EN-Precision Buster (Auto) (Motion)"_s},
{197, "Short A Base Buster (Auto)"_s},
{198, "Medium A Base Buster (Auto)"_s},
{199, "Long A Base Buster (Auto)"_s},
{200, "Railcharge Buster (Ray) (Motion)"_s},
{201, "Clawcharge Buster (Ray)"_s},
{202, "Twizelcharge Buster (Ray)"_s},
{203, "Deltacharge Buster (Ray)"_s},
{297, "Short R Base Buster (Ray)"_s},
{298, "Medium R Base Buster (Ray)"_s},
{299, "Long R Base Buster (Ray)"_s},
{300, "Subsonic Buster (Wave)"_s},
{301, "Amplifier Buster (Wave) (Motion)"_s},
{302, "Cyclonwave Buster (Wave)"_s},
{303, "Warhorn Buster (Wave) (Motion)"_s},
{397, "Short W Base Buster (Wave)"_s},
{398, "Medium W Base Buster (Wave)"_s},
{399, "Long W Base Buster (Wave)"_s},
};
// endregion
// region Bullet Launchers
static const std::map<Int, Containers::StringView> blauncher_pods {
{0, "BL-Delta Pack Launcher (Missile x12)"_s},
{1, "BL-Twin Pack Launcher (Missile x12)"_s},
{2, "Detector Launcher (Missile x12)"_s},
{3, "BL-Triplet Pack Launcher (Missile x12)"_s},
{4, "Shielded Launcher (Missile x12)"_s},
{0, "BL-Delta Pack Launcher (Missile x12)"_s},
{1, "BL-Twin Pack Launcher (Missile x12)"_s},
{2, "Detector Launcher (Missile x12)"_s},
{3, "BL-Triplet Pack Launcher (Missile x12)"_s},
{4, "Shielded Launcher (Missile x12)"_s},
{99, "H Base Pod (Missile x12)"_s},
{100, "Warhead Pod (Nuke x2)"_s},
{101, "Warhead Launcher (Nuke x2)"_s},
{102, "Triangular Warhead Pod (Nuke x2)"_s},
{103, "Expanded Warhead Pod (Nuke x2)"_s},
{104, "Shielded Warhead Pod (Nuke x2)"_s},
{199, "N Base Pod (Nuke x2)"_s},
{200, "Widepack Launcher (Salvo x24)"_s},
{201, "Covered Launcher (Salvo x24)"_s},
{202, "Double Delta Launcher (Salvo x24)"_s},
{203, "Hexagonal Launcher (Salvo x24)"_s},
{204, "Shielded Six Launcher (Salvo x24)"_s},
{299, "S Base Pod (Salvo x24)"_s},
{300, "Sentinel Cluster Pod (Cluster x40)"_s},
{301, "Pincer Cluster Pod (Cluster x40)"_s},
{302, "Elliptical Cluster Pod (Cluster x40)"_s},
{303, "Sawed Cluster Pod (Cluster x40)"_s},
{304, "Pentagonal Cluster Pod (Cluster x40)"_s},
{399, "C Base Pod (Cluster x40)"_s},
};
static const std::map<Int, Containers::StringView> blauncher_projectiles {
@ -358,16 +390,18 @@ static const std::map<Int, Containers::StringView> elauncher_generators {
{25, "Carrier Unit"_s},
{26, "Compartment Unit"_s},
{27, "Flatedge Unit"_s},
{99, "Base Generator"},
};
static const std::map<Int, Containers::StringView> elauncher_pods {
{0, "EN-Dual Claw Launcher (Echo) (Motion)"_s},
{1, "EN-Assault Launcher (Echo)"_s},
{2, "EN-Tactical Launcher (Echo)"_s},
{3, "EN-Force Focus Launcher (Echo) (Motion)"_s},
{4, "EN-Needler Launcher (Echo)"_s},
{5, "Spark Launcher (Echo)"_s},
{6, "Pinpoint Launcher (Echo)"_s},
{0, "EN-Dual Claw Launcher (Echo) (Motion)"_s},
{1, "EN-Assault Launcher (Echo)"_s},
{2, "EN-Tactical Launcher (Echo)"_s},
{3, "EN-Force Focus Launcher (Echo) (Motion)"_s},
{4, "EN-Needler Launcher (Echo)"_s},
{5, "Spark Launcher (Echo)"_s},
{6, "Pinpoint Launcher (Echo)"_s},
{99, "E Base EPod (Echo)"_s},
{100, "Raystream Launcher (Beam)"_s},
{101, "Perpetum Launcher (Beam)"_s},
@ -376,6 +410,7 @@ static const std::map<Int, Containers::StringView> elauncher_pods {
{104, "Crosshair Launcher (Beam)"_s},
{105, "Powerlined Launcher (Beam)"_s},
{106, "Attached Launcher (Beam)"_s},
{199, "B Base EPod (Beam)"_s},
{200, "Hilt Launcher (Slash) (Motion)"_s},
{201, "Underangle Launcher (Slash)"_s},
@ -384,6 +419,7 @@ static const std::map<Int, Containers::StringView> elauncher_pods {
{204, "Spike Launcher (Slash)"_s},
{205, "Tri-Pronged Launcher (Slash) (Motion)"_s},
{206, "Heavyblade Launcher (Slash)"_s},
{299, "S Base EPod (Slash)"_s},
{300, "Covering Launcher (Photon)"_s},
{301, "Boxhead Launcher (Photon)"_s},
@ -392,5 +428,6 @@ static const std::map<Int, Containers::StringView> elauncher_pods {
{304, "Shelled Launcher (Photon)"_s},
{305, "Widearm Launcher (Photon)"_s},
{306, "Wingspan Launcher (Photon)"_s},
{399, "P Base EPod (Photon)"_s},
};
// endregion

View File

@ -21,17 +21,14 @@
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Path.h>
#include "PropertyNames.h"
#include "../Logger/Logger.h"
#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"
@ -51,28 +48,28 @@ auto Mass::lastError() -> Containers::StringView {
auto Mass::getNameFromFile(Containers::StringView path) -> Containers::Optional<Containers::String> {
if(!Utility::Path::exists(path)) {
Utility::Error{} << path << "couldn't be found."_s;
LOG_ERROR_FORMAT("{} couldn't be found.", path);
return Containers::NullOpt;
}
UESaveFile mass{path};
if(!mass.valid()) {
Utility::Error{} << "The unit file seems to be corrupt."_s;
LOG_ERROR_FORMAT("{} is invalid: {}", path, mass.lastError());
return Containers::NullOpt;
}
auto unit_data = mass.at<GenericStructProperty>("UnitData"_s);
auto unit_data = mass.at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Couldn't find unit data in the file."_s;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, path);
return Containers::NullOpt;
}
auto name_prop = unit_data->at<StringProperty>("Name_45_A037C5D54E53456407BDF091344529BB"_s);
auto name_prop = unit_data->at<StringProperty>(MASS_NAME);
if(!name_prop) {
Utility::Error{} << "Couldn't find the name in the file."_s;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_NAME, path);
return Containers::NullOpt;
}
@ -80,47 +77,63 @@ auto Mass::getNameFromFile(Containers::StringView path) -> Containers::Optional<
}
void Mass::refreshValues() {
Utility::Debug{} << "=Refreshing values for" << _filename << Utility::Debug::nospace << "=";
LOG_INFO_FORMAT("Refreshing values for {}.", _filename);
Containers::ScopeGuard guard{[]{ Utility::Error{} << "Refresh failed."; }};
logger().lockMutex();
logger().indent();
logger().unlockMutex();
Containers::ScopeGuard indent_guard{[]{
logger().lockMutex();
logger().unindent();
logger().unlockMutex();
}};
LOG_INFO("Checking if file exists.");
if(!Utility::Path::exists(Utility::Path::join(_folder, _filename))) {
Utility::Warning{} << _filename << "does not exist in" << _folder;
LOG_WARNING_FORMAT("{} doesn't exist in {}.", _filename, _folder);
_state = State::Empty;
return;
}
if(!_mass) {
LOG_INFO("Reading the GVAS save.");
_mass.emplace(Utility::Path::join(_folder, _filename));
if(!_mass->valid()) {
Utility::Error{} << _mass->lastError();
LOG_ERROR(_mass->lastError());
_state = State::Invalid;
return;
}
}
else {
LOG_INFO("Reloading the GVAS data.");
if(!_mass->reloadData()) {
Utility::Error{} << _mass->lastError();
LOG_ERROR(_mass->lastError());
_state = State::Invalid;
return;
}
}
LOG_INFO("Checking the save file type.");
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;
LOG_ERROR_FORMAT("{} is not a valid unit save.", _filename);
_state = State::Invalid;
return;
}
auto name_prop = unit_data->at<StringProperty>("Name_45_A037C5D54E53456407BDF091344529BB"_s);
LOG_INFO("Getting the unit data.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
LOG_INFO("Reading the M.A.S.S. name.");
auto name_prop = unit_data->at<StringProperty>(MASS_NAME);
if(!name_prop) {
Utility::Error{} << "Couldn't find a M.A.S.S. name in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_NAME, _filename);
_name = Containers::NullOpt;
_state = State::Invalid;
return;
@ -203,18 +216,16 @@ void Mass::refreshValues() {
return;
}
LOG_INFO("Getting the associated account.");
auto account_prop = _mass->at<StringProperty>("Account"_s);
if(!account_prop) {
Utility::Error{} << "Couldn't find an account ID in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_ACCOUNT, _filename);
_state = State::Invalid;
return;
}
_account = account_prop->value;
guard.release();
guard = Containers::ScopeGuard{[]{Utility::Debug{} << "Refresh successful.";}};
_state = State::Valid;
}
@ -223,6 +234,7 @@ auto Mass::filename() -> Containers::StringView {
}
auto Mass::name() -> Containers::StringView {
CORRADE_INTERNAL_ASSERT(_name);
return *_name;
}
@ -232,6 +244,7 @@ auto Mass::setName(Containers::StringView new_name) -> bool {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return false;
}
@ -239,6 +252,7 @@ auto Mass::setName(Containers::StringView new_name) -> bool {
auto name_prop = unit_data->at<StringProperty>("Name_45_A037C5D54E53456407BDF091344529BB"_s);
if(!name_prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_NAME, _filename);
_state = State::Invalid;
return false;
}
@ -266,17 +280,20 @@ void Mass::setDirty(bool dirty) {
}
void Mass::getTuning() {
getTuningCategory("Engine"_s, _tuning.engineId, "Gears"_s, _tuning.gearIds);
getTuningCategory(MASS_ENGINE, _tuning.engineId,
MASS_GEARS, _tuning.gearIds);
if(_state == State::Invalid) {
return;
}
getTuningCategory("OS"_s, _tuning.osId, "Modules"_s, _tuning.moduleIds);
getTuningCategory(MASS_OS, _tuning.osId,
MASS_MODULES, _tuning.moduleIds);
if(_state == State::Invalid) {
return;
}
getTuningCategory("Architect"_s, _tuning.archId, "Techs"_s, _tuning.techIds);
getTuningCategory(MASS_ARCHITECT, _tuning.archId,
MASS_TECHS, _tuning.techIds);
if(_state == State::Invalid) {
return;
}
@ -313,10 +330,10 @@ auto Mass::account() -> Containers::StringView {
auto Mass::updateAccount(Containers::StringView new_account) -> bool {
_account = new_account;
auto account = _mass->at<StringProperty>("Account"_s);
auto account = _mass->at<StringProperty>(MASS_ACCOUNT);
if(!account) {
_lastError = "Couldn't find the " MASS_ACCOUNT " property."_s;
_state = State::Invalid;
_lastError = "Couldn't find the account property."_s;
return false;
}
@ -333,9 +350,11 @@ auto Mass::updateAccount(Containers::StringView new_account) -> bool {
void Mass::getTuningCategory(Containers::StringView big_node_prop_name, Int& big_node_id,
Containers::StringView small_nodes_prop_name, Containers::ArrayView<Int> small_nodes_ids)
{
LOG_INFO_FORMAT("Getting tuning data ({}, {}).", big_node_prop_name, small_nodes_prop_name);
auto node_id = _mass->at<IntProperty>(big_node_prop_name);
if(!node_id) {
Utility::Error{} << "Couldn't find" << big_node_prop_name << "in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", big_node_prop_name, _filename);
_state = State::Invalid;
return;
}
@ -343,13 +362,14 @@ void Mass::getTuningCategory(Containers::StringView big_node_prop_name, Int& big
auto node_ids = _mass->at<ArrayProperty>(small_nodes_prop_name);
if(!node_ids) {
Utility::Error{} << "Couldn't find" << small_nodes_prop_name << "in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", small_nodes_prop_name, _filename);
_state = State::Invalid;
return;
}
if(node_ids->items.size() != small_nodes_ids.size()) {
Utility::Error{} << "Node ID arrays are not of the same size. Expected" << small_nodes_ids.size() << Utility::Debug::nospace << ", got" << node_ids->items.size() << "instead.";
LOG_ERROR_FORMAT("Node ID arrays are not of the same size. Expected {}, got {} instead.",
small_nodes_ids.size(), node_ids->items.size());
_state = State::Invalid;
return;
}

View File

@ -16,6 +16,8 @@
#include <algorithm>
#include "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/ByteProperty.h"
#include "../UESaveFile/Types/GenericStructProperty.h"
@ -32,22 +34,25 @@ auto Mass::armourParts() -> Containers::ArrayView<ArmourPart> {
}
void Mass::getArmourParts() {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Getting armour parts.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Couldn't find unit data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto armour_array = unit_data->at<ArrayProperty>("Armor_10_12E266C44116DDAF57E99ABB575A4B3C"_s);
auto armour_array = unit_data->at<ArrayProperty>(MASS_ARMOUR_PARTS);
if(!armour_array) {
Utility::Error{} << "Couldn't find the armour parts array in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_ARMOUR_PARTS, _filename);
_state = State::Invalid;
return;
}
if(armour_array->items.size() != _armour.parts.size()) {
Utility::Error{} << "Armour arrays are not of the same size. Expected" << _armour.parts.size() << Utility::Debug::nospace << ", got" << armour_array->items.size() << "instead.";
LOG_ERROR_FORMAT("Armour part arrays are not of the same size. Expected {}, got {} instead.",
_armour.parts.size(), armour_array->items.size());
_state = State::Invalid;
return;
}
@ -56,26 +61,28 @@ void Mass::getArmourParts() {
auto part_prop = armour_array->at<GenericStructProperty>(i);
auto& part = _armour.parts[i];
auto& armour_slot = part_prop->at<ByteProperty>("Slot_3_408BA56F4C9605C7E805CF91B642249C"_s)->enumValue;
auto& armour_slot = part_prop->at<ByteProperty>(MASS_ARMOUR_SLOT)->enumValue;
#define c(enumerator, strenum, name) if(armour_slot == (strenum)) { part.slot = ArmourSlot::enumerator; } else
#include "../Maps/ArmourSlots.hpp"
#undef c
{
LOG_ERROR_FORMAT("Invalid armour slot enumerator {}.", armour_slot);
_state = State::Invalid;
Utility::Warning{} << "Invalid armour slot enum value in getArmourParts()."_s;
return;
}
part.id = part_prop->at<IntProperty>("ID_5_ACD101864D3481DE96EDACACC09BDD25"_s)->value;
part.id = part_prop->at<IntProperty>(MASS_ARMOUR_ID)->value;
auto part_styles = part_prop->at<ArrayProperty>("Styles_47_3E31870441DFD7DB8BEE5C85C26B365B"_s);
auto part_styles = part_prop->at<ArrayProperty>(MASS_ARMOUR_STYLES);
if(!part_styles) {
Utility::Error{} << "Part styles not found for part number" << i << "in" << _filename;
LOG_ERROR_FORMAT("Part styles not found for part number {}.", i);
_state = State::Invalid;
return;
}
if(part_styles->items.size() != part.styles.size()) {
Utility::Error{} << "Part style arrays are not of the same size. Expected" << part.styles.size() << Utility::Debug::nospace << ", got" << part_styles->items.size() << "instead.";
LOG_ERROR_FORMAT("Armour part style arrays are not of the same size. Expected {}, got {} instead.",
part.styles.size(), part_styles->items.size());
_state = State::Invalid;
return;
}
@ -84,9 +91,9 @@ void Mass::getArmourParts() {
part.styles[j] = part_styles->at<IntProperty>(j)->value;
}
auto decals_array = part_prop->at<ArrayProperty>("Decals_42_F358794A4F18497970F56BA9627D3603"_s);
auto decals_array = part_prop->at<ArrayProperty>(MASS_ARMOUR_DECALS);
if(!decals_array) {
Utility::Error{} << "Part decals not found for part number" << i << "in" << _filename;
LOG_ERROR_FORMAT("Part decals not found for part number {}.", i);
_state = State::Invalid;
return;
}
@ -95,9 +102,9 @@ void Mass::getArmourParts() {
getDecals(part.decals, decals_array);
auto accs_array = part_prop->at<ArrayProperty>("Accessories_52_D902DD4241FA0050C2529596255153F3"_s);
auto accs_array = part_prop->at<ArrayProperty>(MASS_ARMOUR_ACCESSORIES);
if(!accs_array) {
Utility::Error{} << "Part accessories not found for part number" << i << "in" << _filename;
LOG_WARNING_FORMAT("Part accessories not found for part number {}.", i);
part.accessories = Containers::Array<Accessory>{};
continue;
}
@ -111,11 +118,25 @@ void Mass::getArmourParts() {
}
auto Mass::writeArmourPart(ArmourSlot slot) -> bool {
LOG_INFO_FORMAT("Writing armour part in slot {}.", static_cast<int>(slot));
auto& part = *std::find_if(_armour.parts.begin(), _armour.parts.end(), [&slot](const ArmourPart& part){ return slot == part.slot; });
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "Couldn't find the unit data in " + _filename + ".";
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto armour_array = unit_data->at<ArrayProperty>("Armor_10_12E266C44116DDAF57E99ABB575A4B3C"_s);
auto armour_array = unit_data->at<ArrayProperty>(MASS_ARMOUR_PARTS);
if(!armour_array) {
_lastError = "Couldn't find the armour part array in " + _filename + ".";
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
Containers::StringView slot_str = nullptr;
switch(slot) {
@ -130,7 +151,7 @@ auto Mass::writeArmourPart(ArmourSlot slot) -> bool {
for(UnsignedInt i = 0; i < armour_array->items.size(); i++) {
part_prop = armour_array->at<GenericStructProperty>(i);
if(slot_str == part_prop->at<ByteProperty>("Slot_3_408BA56F4C9605C7E805CF91B642249C"_s)->enumValue) {
if(slot_str == part_prop->at<ByteProperty>(MASS_ARMOUR_SLOT)->enumValue) {
break;
}
else {
@ -147,21 +168,22 @@ auto Mass::writeArmourPart(ArmourSlot slot) -> bool {
#include "../Maps/ArmourSlots.hpp"
#undef c
}
LOG_ERROR(_lastError);
return false;
}
part_prop->at<IntProperty>("ID_5_ACD101864D3481DE96EDACACC09BDD25"_s)->value = part.id;
part_prop->at<IntProperty>(MASS_ARMOUR_ID)->value = part.id;
auto part_styles = part_prop->at<ArrayProperty>("Styles_47_3E31870441DFD7DB8BEE5C85C26B365B"_s);
auto part_styles = part_prop->at<ArrayProperty>(MASS_ARMOUR_STYLES);
for(UnsignedInt i = 0; i < part.styles.size(); i++) {
part_styles->at<IntProperty>(i)->value = part.styles[i];
}
auto decals_array = part_prop->at<ArrayProperty>("Decals_42_F358794A4F18497970F56BA9627D3603"_s);
auto decals_array = part_prop->at<ArrayProperty>(MASS_ARMOUR_DECALS);
writeDecals(part.decals, decals_array);
if(part.accessories.size() != 0) {
auto accs_array = part_prop->at<ArrayProperty>("Accessories_52_D902DD4241FA0050C2529596255153F3"_s);
auto accs_array = part_prop->at<ArrayProperty>(MASS_ARMOUR_ACCESSORIES);
writeAccessories(part.accessories, accs_array);
}
@ -182,24 +204,27 @@ auto Mass::bulletLauncherAttachments() -> Containers::ArrayView<BulletLauncherAt
}
void Mass::getBulletLauncherAttachments() {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Getting the bullet launcher attachment data.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Couldn't find unit data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto attach_style_prop = unit_data->at<ByteProperty>("WeaponBLAttachmentStyle_65_5943FCE8406F18D2C3F69285EB23A699"_s);
auto attach_array = unit_data->at<ArrayProperty>("WeaponBLAttachment_61_442D08F547510A4CEE1501BBAF297BA0"_s);
auto attach_style_prop = unit_data->at<ByteProperty>(MASS_BL_ATTACHMENT_STYLE);
auto attach_array = unit_data->at<ArrayProperty>(MASS_BL_ATTACHMENTS);
if(!attach_style_prop && !attach_array) {
LOG_WARNING_FORMAT("No bullet launcher attachment data found in {}.", _filename);
_armour.blAttachmentStyle = BulletLauncherAttachmentStyle::NotFound;
return;
}
if(attach_style_prop && !attach_array) {
LOG_WARNING_FORMAT("No bullet launcher attachments found in {}.", _filename);
_armour.blAttachmentStyle = BulletLauncherAttachmentStyle::NotFound;
Utility::Error{} << "Couldn't find bullet launcher attachments in" << _filename;
_state = State::Invalid;
return;
}
@ -211,25 +236,25 @@ void Mass::getBulletLauncherAttachments() {
auto attachment_prop = attach_array->at<GenericStructProperty>(i);
auto& attachment = _armour.blAttachment[i];
Containers::StringView socket = attachment_prop->at<StringProperty>("Socket_9_B9DBF30D4A1F0032A2BE2F8B342B35A9"_s)->value;
Containers::StringView socket = attachment_prop->at<StringProperty>(MASS_BL_ATTACHMENT_SOCKET)->value;
#define c(enumerator, strenum, name) if(socket == (strenum)) { attachment.socket = BulletLauncherSocket::enumerator; } else
#include "../Maps/BulletLauncherSockets.hpp"
#undef c
{
Utility::Error{} << "Invalid BL attachment socket.";
LOG_ERROR_FORMAT("Invalid attachment socket {}.", socket);
_state = State::Invalid;
return;
}
auto rel_loc_prop = attachment_prop->at<VectorStructProperty>("RelativeLocation_10_2F6E75DF4C40622658340E9A22D38B02"_s);
auto rel_loc_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELLOC);
attachment.relativeLocation = Vector3{rel_loc_prop->x, rel_loc_prop->y, rel_loc_prop->z};
auto off_loc_prop = attachment_prop->at<VectorStructProperty>("OffsetLocation_11_F42B3DA3436948FF85752DB33722382F"_s);
auto off_loc_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_OFFLOC);
attachment.offsetLocation = Vector3{off_loc_prop->x, off_loc_prop->y, off_loc_prop->z};
auto rel_rot_prop = attachment_prop->at<VectorStructProperty>("RelativeRotation_12_578140464621245132CFF2A2AD85E735"_s);
auto rel_rot_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELROT);
attachment.relativeRotation = Vector3{rel_rot_prop->x, rel_rot_prop->y, rel_rot_prop->z};
auto off_rot_prop = attachment_prop->at<VectorStructProperty>("OffsetRotation_13_B5980BCD47905D842D1490A1A520B064"_s);
auto off_rot_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_OFFROT);
attachment.offsetRotation = Vector3{off_rot_prop->x, off_rot_prop->y, off_rot_prop->z};
auto rel_scale_prop = attachment_prop->at<VectorStructProperty>("RelativeScale_16_37BC80EF42699F79533F7AA7B3094E38"_s);
auto rel_scale_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELSCALE);
attachment.relativeScale = Vector3{rel_scale_prop->x, rel_scale_prop->y, rel_scale_prop->z};
}
}
@ -240,7 +265,8 @@ void Mass::getBulletLauncherAttachments() {
#include "../Maps/BulletLauncherAttachmentStyles.hpp"
#undef c
{
Utility::Error{} << "Unknown BL attachment style enumerator.";
LOG_ERROR_FORMAT("Invalid attachment style {}.", attach_style);
_state = State::Invalid;
}
}
else {
@ -249,26 +275,31 @@ void Mass::getBulletLauncherAttachments() {
}
auto Mass::writeBulletLauncherAttachments() -> bool {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Writing bullet launcher attachments.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_state = State::Invalid;
_lastError = "No unit data in " + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto attach_style_prop = unit_data->at<ByteProperty>("WeaponBLAttachmentStyle_65_5943FCE8406F18D2C3F69285EB23A699"_s);
auto attach_array = unit_data->at<ArrayProperty>("WeaponBLAttachment_61_442D08F547510A4CEE1501BBAF297BA0"_s);
auto attach_style_prop = unit_data->at<ByteProperty>(MASS_BL_ATTACHMENT_STYLE);
auto attach_array = unit_data->at<ArrayProperty>(MASS_BL_ATTACHMENTS);
if(!attach_style_prop && !attach_array) {
_armour.blAttachmentStyle = BulletLauncherAttachmentStyle::NotFound;
_lastError = "No attachment properties to write to in " + _filename;
LOG_ERROR(_lastError);
_armour.blAttachmentStyle = BulletLauncherAttachmentStyle::NotFound;
return false;
}
if(attach_style_prop && !attach_array) {
_lastError = "Couldn't find the attachments in " + _filename;
LOG_ERROR(_lastError);
_armour.blAttachmentStyle = BulletLauncherAttachmentStyle::NotFound;
_state = State::Invalid;
_lastError = "Couldn't find the attachments in " + _filename;
return false;
}
@ -279,33 +310,34 @@ auto Mass::writeBulletLauncherAttachments() -> bool {
auto attachment_prop = attach_array->at<GenericStructProperty>(i);
auto& attachment = _armour.blAttachment[i];
auto& socket = attachment_prop->at<StringProperty>("Socket_9_B9DBF30D4A1F0032A2BE2F8B342B35A9"_s)->value;
auto& socket = attachment_prop->at<StringProperty>(MASS_BL_ATTACHMENT_SOCKET)->value;
switch(attachment.socket) {
#define c(enumerator, strenum, name) case BulletLauncherSocket::enumerator: socket = strenum; break;
#include "../Maps/BulletLauncherSockets.hpp"
#undef c
default:
_lastError = "Invalid socket type."_s;
LOG_ERROR(_lastError);
return false;
}
auto rel_loc_prop = attachment_prop->at<VectorStructProperty>("RelativeLocation_10_2F6E75DF4C40622658340E9A22D38B02"_s);
auto rel_loc_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELLOC);
rel_loc_prop->x = attachment.relativeLocation.x();
rel_loc_prop->y = attachment.relativeLocation.y();
rel_loc_prop->z = attachment.relativeLocation.z();
auto off_loc_prop = attachment_prop->at<VectorStructProperty>("OffsetLocation_11_F42B3DA3436948FF85752DB33722382F"_s);
auto off_loc_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_OFFLOC);
off_loc_prop->x = attachment.offsetLocation.x();
off_loc_prop->y = attachment.offsetLocation.y();
off_loc_prop->z = attachment.offsetLocation.z();
auto rel_rot_prop = attachment_prop->at<VectorStructProperty>("RelativeRotation_12_578140464621245132CFF2A2AD85E735"_s);
auto rel_rot_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELROT);
rel_rot_prop->x = attachment.relativeRotation.x();
rel_rot_prop->y = attachment.relativeRotation.y();
rel_rot_prop->z = attachment.relativeRotation.z();
auto off_rot_prop = attachment_prop->at<VectorStructProperty>("OffsetRotation_13_B5980BCD47905D842D1490A1A520B064"_s);
auto off_rot_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_OFFROT);
off_rot_prop->x = attachment.offsetRotation.x();
off_rot_prop->y = attachment.offsetRotation.y();
off_rot_prop->z = attachment.offsetRotation.z();
auto rel_scale_prop = attachment_prop->at<VectorStructProperty>("RelativeScale_16_37BC80EF42699F79533F7AA7B3094E38"_s);
auto rel_scale_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELSCALE);
rel_scale_prop->x = attachment.relativeScale.x();
rel_scale_prop->y = attachment.relativeScale.y();
rel_scale_prop->z = attachment.relativeScale.z();
@ -314,7 +346,7 @@ auto Mass::writeBulletLauncherAttachments() -> bool {
if(!attach_style_prop) {
attach_style_prop = new ByteProperty;
attach_style_prop->name.emplace("WeaponBLAttachmentStyle_65_5943FCE8406F18D2C3F69285EB23A699"_s);
attach_style_prop->name.emplace(MASS_BL_ATTACHMENT_STYLE);
attach_style_prop->enumType = "enuBLAttachmentStyle"_s;
ByteProperty::ptr prop{attach_style_prop};
arrayAppend(unit_data->properties, std::move(prop));
@ -329,6 +361,7 @@ auto Mass::writeBulletLauncherAttachments() -> bool {
#undef c
default:
_lastError = "Unknown BL attachment style.";
LOG_ERROR(_lastError);
return false;
}
@ -345,22 +378,25 @@ auto Mass::armourCustomStyles() -> Containers::ArrayView<CustomStyle> {
}
void Mass::getArmourCustomStyles() {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Getting the custom armour styles.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Couldn't find unit data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto armour_styles = unit_data->at<ArrayProperty>("ArmorStyle_42_E2F6AC3647788CB366BD469B3B7E899E"_s);
auto armour_styles = unit_data->at<ArrayProperty>(MASS_CUSTOM_ARMOUR_STYLES);
if(!armour_styles) {
Utility::Error{} << "Couldn't find custom armour styles in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_CUSTOM_ARMOUR_STYLES, _filename);
_state = State::Invalid;
return;
}
if(armour_styles->items.size() != _armour.customStyles.size()) {
Utility::Error{} << "Custom armour style arrays are not of the same size. Expected" << _armour.customStyles.size() << Utility::Debug::nospace << ", got" << armour_styles->items.size() << "instead.";
LOG_ERROR_FORMAT("Custom armour style arrays are not of the same size. Expected {}, got {} instead.",
_armour.customStyles.size(), armour_styles->items.size());
_state = State::Invalid;
return;
}
@ -369,21 +405,26 @@ void Mass::getArmourCustomStyles() {
}
auto Mass::writeArmourCustomStyle(UnsignedLong index) -> bool {
LOG_INFO_FORMAT("Writing custom armour style {}.", index);
if(index > _armour.customStyles.size()) {
_lastError = "Style index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_state = State::Invalid;
_lastError = "Couldn't find unit data in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto armour_styles = unit_data->at<ArrayProperty>("ArmorStyle_42_E2F6AC3647788CB366BD469B3B7E899E"_s);
auto armour_styles = unit_data->at<ArrayProperty>(MASS_CUSTOM_ARMOUR_STYLES);
if(!armour_styles) {
_lastError = "Couldn't find armour custom styles in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}

View File

@ -14,6 +14,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 "PropertyNames.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/BoolProperty.h"
#include "../UESaveFile/Types/ColourStructProperty.h"
@ -34,21 +35,21 @@ void Mass::getDecals(Containers::ArrayView<Decal> decals, ArrayProperty* decal_a
CORRADE_INTERNAL_ASSERT(decal_prop);
auto& decal = decals[i];
decal.id = decal_prop->at<IntProperty>("ID_3_694C0B35404D8A3168AEC89026BC8CF9"_s)->value;
auto colour_prop = decal_prop->at<ColourStructProperty>("Color_8_1B0B9D2B43DA6AAB9FA549B374D3E606"_s);
decal.id = decal_prop->at<IntProperty>(MASS_DECAL_ID)->value;
auto colour_prop = decal_prop->at<ColourStructProperty>(MASS_DECAL_COLOUR);
decal.colour = Color4{colour_prop->r, colour_prop->g, colour_prop->b, colour_prop->a};
auto pos_prop = decal_prop->at<VectorStructProperty>("Position_41_022C8FE84E1AAFE587261E88F2C72250"_s);
auto pos_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_POSITION);
decal.position = Vector3{pos_prop->x, pos_prop->y, pos_prop->z};
auto u_prop = decal_prop->at<VectorStructProperty>("UAxis_37_EBEB715F45491AECACCC07A1AE4646D1"_s);
auto u_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_UAXIS);
decal.uAxis = Vector3{u_prop->x, u_prop->y, u_prop->z};
auto v_prop = decal_prop->at<VectorStructProperty>("VAxis_39_C31EB2664EE202CAECFBBB84100B5E35"_s);
auto v_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_VAXIS);
decal.vAxis = Vector3{v_prop->x, v_prop->y, v_prop->z};
auto offset_prop = decal_prop->at<Vector2DStructProperty>("Offset_29_B02BBBB74FC60F5EDBEBAB8020738020"_s);
auto offset_prop = decal_prop->at<Vector2DStructProperty>(MASS_DECAL_OFFSET);
decal.offset = Vector2{offset_prop->x, offset_prop->y};
decal.scale = decal_prop->at<FloatProperty>("Scale_32_959D1C2747AFD8D62808468235CBBA40"_s)->value;
decal.rotation = decal_prop->at<FloatProperty>("Rotation_27_12D7C314493D203D5C2326A03C5F910F"_s)->value;
decal.flip = decal_prop->at<BoolProperty>("Flip_35_CECCFB184CCD9412BD93FE9A8B656BE1"_s)->value;
decal.wrap = decal_prop->at<BoolProperty>("Wrap_43_A7C68CDF4A92AF2ECDA53F953EE7CA62"_s)->value;
decal.scale = decal_prop->at<FloatProperty>(MASS_DECAL_SCALE)->value;
decal.rotation = decal_prop->at<FloatProperty>(MASS_DECAL_ROTATION)->value;
decal.flip = decal_prop->at<BoolProperty>(MASS_DECAL_FLIP)->value;
decal.wrap = decal_prop->at<BoolProperty>(MASS_DECAL_WRAP)->value;
}
}
@ -58,31 +59,31 @@ void Mass::writeDecals(Containers::ArrayView<Decal> decals, ArrayProperty* decal
CORRADE_INTERNAL_ASSERT(decal_prop);
auto& decal = decals[i];
decal_prop->at<IntProperty>("ID_3_694C0B35404D8A3168AEC89026BC8CF9"_s)->value = decal.id;
auto colour_prop = decal_prop->at<ColourStructProperty>("Color_8_1B0B9D2B43DA6AAB9FA549B374D3E606"_s);
decal_prop->at<IntProperty>(MASS_DECAL_ID)->value = decal.id;
auto colour_prop = decal_prop->at<ColourStructProperty>(MASS_DECAL_COLOUR);
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<VectorStructProperty>("Position_41_022C8FE84E1AAFE587261E88F2C72250"_s);
auto pos_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_POSITION);
pos_prop->x = decal.position.x();
pos_prop->y = decal.position.y();
pos_prop->z = decal.position.z();
auto u_prop = decal_prop->at<VectorStructProperty>("UAxis_37_EBEB715F45491AECACCC07A1AE4646D1"_s);
auto u_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_UAXIS);
u_prop->x = decal.uAxis.x();
u_prop->y = decal.uAxis.y();
u_prop->z = decal.uAxis.z();
auto v_prop = decal_prop->at<VectorStructProperty>("VAxis_39_C31EB2664EE202CAECFBBB84100B5E35"_s);
auto v_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_VAXIS);
v_prop->x = decal.vAxis.x();
v_prop->y = decal.vAxis.y();
v_prop->z = decal.vAxis.z();
auto offset_prop = decal_prop->at<Vector2DStructProperty>("Offset_29_B02BBBB74FC60F5EDBEBAB8020738020"_s);
auto offset_prop = decal_prop->at<Vector2DStructProperty>(MASS_DECAL_OFFSET);
offset_prop->x = decal.offset.x();
offset_prop->y = decal.offset.y();
decal_prop->at<FloatProperty>("Scale_32_959D1C2747AFD8D62808468235CBBA40"_s)->value = decal.scale;
decal_prop->at<FloatProperty>("Rotation_27_12D7C314493D203D5C2326A03C5F910F"_s)->value = decal.rotation;
decal_prop->at<BoolProperty>("Flip_35_CECCFB184CCD9412BD93FE9A8B656BE1"_s)->value = decal.flip;
decal_prop->at<BoolProperty>("Wrap_43_A7C68CDF4A92AF2ECDA53F953EE7CA62"_s)->value = decal.wrap;
decal_prop->at<FloatProperty>(MASS_DECAL_SCALE)->value = decal.scale;
decal_prop->at<FloatProperty>(MASS_DECAL_ROTATION)->value = decal.rotation;
decal_prop->at<BoolProperty>(MASS_DECAL_FLIP)->value = decal.flip;
decal_prop->at<BoolProperty>(MASS_DECAL_WRAP)->value = decal.wrap;
}
}
@ -92,21 +93,21 @@ void Mass::getAccessories(Containers::ArrayView<Accessory> accessories, ArrayPro
CORRADE_INTERNAL_ASSERT(acc_prop);
auto& accessory = accessories[i];
accessory.attachIndex = acc_prop->at<IntProperty>("AttachIndex_2_4AFCF6024E4BA7426C6B9F80B8179D20"_s)->value;
accessory.id = acc_prop->at<IntProperty>("ID_4_5757B32647BAE263266259B8A7DFFFC1"_s)->value;
auto acc_styles = acc_prop->at<ArrayProperty>("Styles_7_91DEB0F24E24D13FC9472882C11D0DFD"_s);
accessory.attachIndex = acc_prop->at<IntProperty>(MASS_ACCESSORY_ATTACH_INDEX)->value;
accessory.id = acc_prop->at<IntProperty>(MASS_ACCESSORY_ID)->value;
auto acc_styles = acc_prop->at<ArrayProperty>(MASS_ACCESSORY_STYLES);
for(UnsignedInt j = 0; j < acc_styles->items.size(); j++) {
accessory.styles[j] = acc_styles->at<IntProperty>(j)->value;
}
auto rel_pos_prop = acc_prop->at<VectorStructProperty>("RelativePosition_14_BE8FB2A94074F34B3EDA6683B227D3A1"_s);
auto rel_pos_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_RELPOS);
accessory.relativePosition = Vector3{rel_pos_prop->x, rel_pos_prop->y, rel_pos_prop->z};
auto rel_pos_offset_prop = acc_prop->at<VectorStructProperty>("RelativePositionOffset_15_98FD0CE74E44BBAFC2D46FB4CA4E0ED6"_s);
auto rel_pos_offset_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_OFFPOS);
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<RotatorStructProperty>("RelativeRotation_20_C78C73274E6E78E7878F8C98ECA342C0"_s);
auto rel_rot_prop = acc_prop->at<RotatorStructProperty>(MASS_ACCESSORY_RELROT);
accessory.relativeRotation = Vector3{rel_rot_prop->x, rel_rot_prop->y, rel_rot_prop->z};
auto rel_rot_offset_prop = acc_prop->at<RotatorStructProperty>("RelativeRotationOffset_21_E07FA0EC46728B7BA763C6861249ABAA"_s);
auto rel_rot_offset_prop = acc_prop->at<RotatorStructProperty>(MASS_ACCESSORY_OFFROT);
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<VectorStructProperty>("LocalScale_24_DC2D93A742A41A46E7E61D988F15ED53"_s);
auto local_scale_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_SCALE);
accessory.localScale = Vector3{local_scale_prop->x, local_scale_prop->y, local_scale_prop->z};
}
}
@ -117,29 +118,29 @@ void Mass::writeAccessories(Containers::ArrayView<Accessory> accessories, ArrayP
CORRADE_INTERNAL_ASSERT(acc_prop);
auto& accessory = accessories[i];
acc_prop->at<IntProperty>("AttachIndex_2_4AFCF6024E4BA7426C6B9F80B8179D20"_s)->value = accessory.attachIndex;
acc_prop->at<IntProperty>("ID_4_5757B32647BAE263266259B8A7DFFFC1"_s)->value = accessory.id;
auto acc_styles = acc_prop->at<ArrayProperty>("Styles_7_91DEB0F24E24D13FC9472882C11D0DFD"_s);
acc_prop->at<IntProperty>(MASS_ACCESSORY_ATTACH_INDEX)->value = accessory.attachIndex;
acc_prop->at<IntProperty>(MASS_ACCESSORY_ID)->value = accessory.id;
auto acc_styles = acc_prop->at<ArrayProperty>(MASS_ACCESSORY_STYLES);
for(UnsignedInt j = 0; j < acc_styles->items.size(); j++) {
acc_styles->at<IntProperty>(j)->value = accessory.styles[j];
}
auto rel_pos_prop = acc_prop->at<VectorStructProperty>("RelativePosition_14_BE8FB2A94074F34B3EDA6683B227D3A1"_s);
auto rel_pos_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_RELPOS);
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<VectorStructProperty>("RelativePositionOffset_15_98FD0CE74E44BBAFC2D46FB4CA4E0ED6"_s);
auto rel_pos_offset_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_OFFPOS);
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<RotatorStructProperty>("RelativeRotation_20_C78C73274E6E78E7878F8C98ECA342C0"_s);
auto rel_rot_prop = acc_prop->at<RotatorStructProperty>(MASS_ACCESSORY_RELROT);
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<RotatorStructProperty>("RelativeRotationOffset_21_E07FA0EC46728B7BA763C6861249ABAA"_s);
auto rel_rot_offset_prop = acc_prop->at<RotatorStructProperty>(MASS_ACCESSORY_OFFROT);
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<VectorStructProperty>("LocalScale_24_DC2D93A742A41A46E7E61D988F15ED53"_s);
auto local_scale_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_SCALE);
local_scale_prop->x = accessory.localScale.x();
local_scale_prop->y = accessory.localScale.y();
local_scale_prop->z = accessory.localScale.z();

View File

@ -14,6 +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 "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/ColourStructProperty.h"
#include "../UESaveFile/Types/FloatProperty.h"
@ -29,132 +31,137 @@ auto Mass::jointSliders() -> Joints& {
}
void Mass::getJointSliders() {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Getting joint sliders.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Can't find unit data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto frame_prop = unit_data->at<GenericStructProperty>("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"_s);
auto frame_prop = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame_prop) {
Utility::Error{} << "Can't find frame data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_FRAME, _filename);
_state = State::Invalid;
return;
}
auto length = frame_prop->at<FloatProperty>("NeckLength_6_ED6AF79849C27CD1A9D523A09E2BFE58"_s);
auto length = frame_prop->at<FloatProperty>(MASS_JOINT_NECK);
_frame.joints.neck = (length ? length->value : 0.0f);
length = frame_prop->at<FloatProperty>("BodyLength_7_C16287754CBA96C93BAE36A5C154996A"_s);
length = frame_prop->at<FloatProperty>(MASS_JOINT_BODY);
_frame.joints.body = (length ? length->value : 0.0f);
length = frame_prop->at<FloatProperty>("ShoulderLength_8_220EDF304F1C1226F0D8D39117FB3883"_s);
length = frame_prop->at<FloatProperty>(MASS_JOINT_SHOULDER);
_frame.joints.shoulders = (length ? length->value : 0.0f);
length = frame_prop->at<FloatProperty>("HipLength_14_02AEEEAC4376087B9C51F0AA7CC92818"_s);
length = frame_prop->at<FloatProperty>(MASS_JOINT_HIP);
_frame.joints.hips = (length ? length->value : 0.0f);
length = frame_prop->at<FloatProperty>("ArmUpperLength_10_249FDA3E4F3B399E7B9E5C9B7C765EAE"_s);
length = frame_prop->at<FloatProperty>(MASS_JOINT_ARM_UPPER);
_frame.joints.upperArms = (length ? length->value : 0.0f);
length = frame_prop->at<FloatProperty>("ArmLowerLength_12_ACD0F02745C28882619376926292FB36"_s);
length = frame_prop->at<FloatProperty>(MASS_JOINT_ARM_LOWER);
_frame.joints.lowerArms = (length ? length->value : 0.0f);
length = frame_prop->at<FloatProperty>("LegUpperLength_16_A7C4C71249A3776F7A543D96819C0C61"_s);
length = frame_prop->at<FloatProperty>(MASS_JOINT_LEG_UPPER);
_frame.joints.upperLegs = (length ? length->value : 0.0f);
length = frame_prop->at<FloatProperty>("LegLowerLength_18_D2DF39964EA0F2A2129D0491B08A032F"_s);
length = frame_prop->at<FloatProperty>(MASS_JOINT_LEG_LOWER);
_frame.joints.lowerLegs = (length ? length->value : 0.0f);
}
auto Mass::writeJointSliders() -> bool {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Writing joint sliders");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_state = State::Invalid;
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto frame_prop = unit_data->at<GenericStructProperty>("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"_s);
auto frame_prop = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame_prop) {
_state = State::Invalid;
_lastError = "No frame data in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
Containers::Array<UnrealPropertyBase::ptr> temp;
auto length = frame_prop->atMove<FloatProperty>("NeckLength_6_ED6AF79849C27CD1A9D523A09E2BFE58"_s);
auto length = frame_prop->atMove<FloatProperty>(MASS_JOINT_NECK);
if(_frame.joints.neck != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace("NeckLength_6_ED6AF79849C27CD1A9D523A09E2BFE58"_s);
length->name.emplace(MASS_JOINT_NECK);
}
length->value = _frame.joints.neck;
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<FloatProperty>("BodyLength_7_C16287754CBA96C93BAE36A5C154996A"_s);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_BODY);
if(_frame.joints.body != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace("BodyLength_7_C16287754CBA96C93BAE36A5C154996A"_s);
length->name.emplace(MASS_JOINT_BODY);
}
length->value = _frame.joints.body;
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<FloatProperty>("ShoulderLength_8_220EDF304F1C1226F0D8D39117FB3883"_s);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_SHOULDER);
if(_frame.joints.shoulders != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace("ShoulderLength_8_220EDF304F1C1226F0D8D39117FB3883"_s);
length->name.emplace(MASS_JOINT_SHOULDER);
}
length->value = _frame.joints.shoulders;
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<FloatProperty>("ArmUpperLength_10_249FDA3E4F3B399E7B9E5C9B7C765EAE"_s);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_ARM_UPPER);
if(_frame.joints.upperArms != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace("ArmUpperLength_10_249FDA3E4F3B399E7B9E5C9B7C765EAE"_s);
length->name.emplace(MASS_JOINT_ARM_UPPER);
}
length->value = _frame.joints.upperArms;
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<FloatProperty>("ArmLowerLength_12_ACD0F02745C28882619376926292FB36"_s);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_ARM_LOWER);
if(_frame.joints.lowerArms != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace("ArmLowerLength_12_ACD0F02745C28882619376926292FB36"_s);
length->name.emplace(MASS_JOINT_ARM_LOWER);
}
length->value = _frame.joints.lowerArms;
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<FloatProperty>("HipLength_14_02AEEEAC4376087B9C51F0AA7CC92818"_s);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_HIP);
if(_frame.joints.hips != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace("HipLength_14_02AEEEAC4376087B9C51F0AA7CC92818"_s);
length->name.emplace(MASS_JOINT_HIP);
}
length->value = _frame.joints.hips;
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<FloatProperty>("LegUpperLength_16_A7C4C71249A3776F7A543D96819C0C61"_s);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_LEG_UPPER);
if(_frame.joints.upperLegs != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace("LegUpperLength_16_A7C4C71249A3776F7A543D96819C0C61"_s);
length->name.emplace(MASS_JOINT_LEG_UPPER);
}
length->value = _frame.joints.upperLegs;
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<FloatProperty>("LegLowerLength_18_D2DF39964EA0F2A2129D0491B08A032F"_s);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_LEG_LOWER);
if(_frame.joints.lowerLegs != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace("LegLowerLength_18_D2DF39964EA0F2A2129D0491B08A032F"_s);
length->name.emplace(MASS_JOINT_LEG_LOWER);
}
length->value = _frame.joints.lowerLegs;
arrayAppend(temp, std::move(length));
@ -179,29 +186,32 @@ auto Mass::frameStyles() -> Containers::ArrayView<Int> {
}
void Mass::getFrameStyles() {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Getting frame styles.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Can't find unit data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto frame_prop = unit_data->at<GenericStructProperty>("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"_s);
auto frame_prop = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame_prop) {
Utility::Error{} << "Can't find frame data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_FRAME, _filename);
_state = State::Invalid;
return;
}
auto frame_styles = frame_prop->at<ArrayProperty>("Styles_32_00A3B3284B37F1E7819458844A20EB48"_s);
auto frame_styles = frame_prop->at<ArrayProperty>(MASS_FRAME_STYLES);
if(!frame_styles) {
Utility::Error{} << "Can't find frame styles in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_FRAME_STYLES, _filename);
_state = State::Invalid;
return;
}
if(frame_styles->items.size() != _frame.styles.size()) {
Utility::Error{} << "Frame style arrays are not of the same size. Expected" << _frame.styles.size() << Utility::Debug::nospace << ", got" << frame_styles->items.size() << "instead.";
LOG_ERROR_FORMAT("Frame style arrays are not of the same size. Expected {}, got {} instead.",
_frame.styles.size(), frame_styles->items.size());
_state = State::Invalid;
return;
}
@ -212,24 +222,29 @@ void Mass::getFrameStyles() {
}
auto Mass::writeFrameStyles() -> bool {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Writing frame styles.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_state = State::Invalid;
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto frame = unit_data->at<GenericStructProperty>("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"_s);
auto frame = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame) {
_state = State::Invalid;
_lastError = "No frame data in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto frame_styles = frame->at<ArrayProperty>("Styles_32_00A3B3284B37F1E7819458844A20EB48"_s);
auto frame_styles = frame->at<ArrayProperty>(MASS_FRAME_STYLES);
if(!frame_styles) {
_state = State::Invalid;
_lastError = "No frame styles in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
@ -250,23 +265,25 @@ auto Mass::eyeFlareColour() -> Color4& {
}
void Mass::getEyeFlareColour() {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Getting the eye flare colour.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Can't find unit data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto frame_prop = unit_data->at<GenericStructProperty>("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"_s);
auto frame_prop = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame_prop) {
Utility::Error{} << "Can't find frame data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_FRAME, _filename);
_state = State::Invalid;
return;
}
auto eye_flare_prop = frame_prop->at<ColourStructProperty>("EyeFlareColor_36_AF79999C40FCA0E88A2F9A84488A38CA"_s);
auto eye_flare_prop = frame_prop->at<ColourStructProperty>(MASS_EYE_FLARE);
if(!eye_flare_prop) {
Utility::Error{} << "Can't find eye flare data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_EYE_FLARE, _filename);
_state = State::Invalid;
return;
}
@ -275,24 +292,29 @@ void Mass::getEyeFlareColour() {
}
auto Mass::writeEyeFlareColour() -> bool {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Writing the eye flare colour.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_state = State::Invalid;
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto frame = unit_data->at<GenericStructProperty>("Frame_3_F92B0F6A44A15088AF7F41B9FF290653"_s);
auto frame = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame) {
_state = State::Invalid;
_lastError = "No frame data in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto eye_flare_prop = frame->at<ColourStructProperty>("EyeFlareColor_36_AF79999C40FCA0E88A2F9A84488A38CA"_s);
auto eye_flare_prop = frame->at<ColourStructProperty>(MASS_EYE_FLARE);
if(!eye_flare_prop) {
_state = State::Invalid;
_lastError = "No eye flare property in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
@ -314,22 +336,25 @@ auto Mass::frameCustomStyles() -> Containers::ArrayView<CustomStyle> {
}
void Mass::getFrameCustomStyles() {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Getting the frame's custom styles.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Can't find unit data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto frame_styles = unit_data->at<ArrayProperty>("FrameStyle_44_04A44C9440363CCEC5443D98BFAF22AA"_s);
auto frame_styles = unit_data->at<ArrayProperty>(MASS_CUSTOM_FRAME_STYLES);
if(!frame_styles) {
Utility::Error{} << "Can't find frame styles in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_CUSTOM_FRAME_STYLES, _filename);
_state = State::Invalid;
return;
}
if(frame_styles->items.size() != _frame.customStyles.size()) {
Utility::Error{} << "Frame custom style arrays are not of the same size. Expected" << _frame.customStyles.size() << Utility::Debug::nospace << ", got" << frame_styles->items.size() << "instead.";
LOG_ERROR_FORMAT("Frame custom style arrays are not of the same size. Expected {}, got {} instead.",
_frame.customStyles.size(), frame_styles->items.size());
_state = State::Invalid;
return;
}
@ -338,22 +363,27 @@ void Mass::getFrameCustomStyles() {
}
auto Mass::writeFrameCustomStyle(UnsignedLong index) -> bool {
LOG_INFO_FORMAT("Writing frame custom style number {}.", index);
if(index > _frame.customStyles.size()) {
_lastError = "Style index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_state = State::Invalid;
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto frame_styles = unit_data->at<ArrayProperty>("FrameStyle_44_04A44C9440363CCEC5443D98BFAF22AA"_s);
auto frame_styles = unit_data->at<ArrayProperty>(MASS_CUSTOM_FRAME_STYLES);
if(!frame_styles) {
_state = State::Invalid;
_lastError = "No frame styles in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}

View File

@ -14,6 +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 "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/ColourStructProperty.h"
#include "../UESaveFile/Types/FloatProperty.h"
@ -30,15 +32,18 @@ auto Mass::globalStyles() -> Containers::ArrayView<CustomStyle> {
}
void Mass::getGlobalStyles() {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
LOG_INFO("Getting global styles.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Can't find unit data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto global_styles = unit_data->at<ArrayProperty>("GlobalStyles_57_6A681C114035241F7BDAAE9B43A8BF1B"_s);
auto global_styles = unit_data->at<ArrayProperty>(MASS_GLOBAL_STYLES);
if(!global_styles) {
LOG_WARNING_FORMAT("Couldn't find global styles in {}.", _filename);
_globalStyles = Containers::Array<CustomStyle>{0};
return;
}
@ -51,22 +56,27 @@ void Mass::getGlobalStyles() {
}
auto Mass::writeGlobalStyle(UnsignedLong index) -> bool {
LOG_INFO_FORMAT("Writing global style number {}.", index);
if(index > _globalStyles.size()) {
_lastError = "Global style index out of range"_s;
LOG_ERROR(_lastError);
return false;
}
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_state = State::Invalid;
_lastError = "No unit data found in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto global_styles = unit_data->at<ArrayProperty>("GlobalStyles_57_6A681C114035241F7BDAAE9B43A8BF1B"_s);
auto global_styles = unit_data->at<ArrayProperty>(MASS_GLOBAL_STYLES);
if(!global_styles) {
_state = State::Invalid;
_lastError = "No global styles found in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
@ -78,52 +88,53 @@ void Mass::getCustomStyles(Containers::ArrayView<CustomStyle> styles, ArrayPrope
auto style_prop = style_array->at<GenericStructProperty>(i);
auto& style = styles[i];
style.name = style_prop->at<StringProperty>("Name_27_1532115A46EF2B2FA283908DF561A86B"_s)->value;
auto colour_prop = style_prop->at<ColourStructProperty>("Color_5_F0D383DF40474C9464AE48A0984A212E"_s);
style.name = style_prop->at<StringProperty>(MASS_STYLE_NAME)->value;
auto colour_prop = style_prop->at<ColourStructProperty>(MASS_STYLE_COLOUR);
style.colour = Color4{colour_prop->r, colour_prop->g, colour_prop->b, colour_prop->a};
style.metallic = style_prop->at<FloatProperty>("Metallic_10_0A4CD1E4482CBF41CA61D0A856DE90B9"_s)->value;
style.gloss = style_prop->at<FloatProperty>("Gloss_11_9769599842CC275A401C4282A236E240"_s)->value;
style.glow = colour_prop->a == 0.0f ? false : true;
style.metallic = style_prop->at<FloatProperty>(MASS_STYLE_METALLIC)->value;
style.gloss = style_prop->at<FloatProperty>(MASS_STYLE_GLOSS)->value;
style.glow = colour_prop->a != 0.0f;
style.patternId = style_prop->at<IntProperty>("PatternID_14_516DB85641DAF8ECFD2920BE2BDF1311"_s)->value;
style.opacity = style_prop->at<FloatProperty>("Opacity_30_53BD060B4DFCA1C92302D6A0F7831131"_s)->value;
style.patternId = style_prop->at<IntProperty>(MASS_STYLE_PATTERN_ID)->value;
style.opacity = style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OPACITY)->value;
style.offset = Vector2{
style_prop->at<FloatProperty>("OffsetX_23_70FC2E814C64BBB82452748D2AF9CD48"_s)->value,
style_prop->at<FloatProperty>("OffsetY_24_5E1F866C4C054D9B2EE337ADC180C17F"_s)->value
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OFFSETX)->value,
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OFFSETY)->value
};
style.rotation = style_prop->at<FloatProperty>("Rotation_25_EC2DFAD84AD0A6BD3FA841ACD52EDD6D"_s)->value;
style.scale = style_prop->at<FloatProperty>("Scale_26_19DF0708409262183E1247B317137671"_s)->value;
style.rotation = style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_ROTATION)->value;
style.scale = style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_SCALE)->value;
}
}
auto Mass::writeCustomStyle(const CustomStyle& style, UnsignedLong index, ArrayProperty* style_array) -> bool {
if(!style_array) {
_lastError = "Mass::setCustomStyle(): style_array is null."_s;
_lastError = "style_array is null."_s;
LOG_ERROR(_lastError);
return false;
}
auto style_prop = style_array->at<GenericStructProperty>(index);
if(!style_prop) {
_lastError = "Style index is out of range in "_s + _filename;
LOG_ERROR(_lastError);
return false;
}
style_prop->at<StringProperty>("Name_27_1532115A46EF2B2FA283908DF561A86B"_s)->value = style.name;
auto colour_prop = style_prop->at<ColourStructProperty>("Color_5_F0D383DF40474C9464AE48A0984A212E"_s);
style_prop->at<StringProperty>(MASS_STYLE_NAME)->value = style.name;
auto colour_prop = style_prop->at<ColourStructProperty>(MASS_STYLE_COLOUR);
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<FloatProperty>("Metallic_10_0A4CD1E4482CBF41CA61D0A856DE90B9"_s)->value = style.metallic;
style_prop->at<FloatProperty>("Gloss_11_9769599842CC275A401C4282A236E240"_s)->value = style.gloss;
style_prop->at<FloatProperty>(MASS_STYLE_METALLIC)->value = style.metallic;
style_prop->at<FloatProperty>(MASS_STYLE_GLOSS)->value = style.gloss;
style_prop->at<IntProperty>("PatternID_14_516DB85641DAF8ECFD2920BE2BDF1311"_s)->value = style.patternId;
style_prop->at<FloatProperty>("Opacity_30_53BD060B4DFCA1C92302D6A0F7831131"_s)->value = style.opacity;
style_prop->at<FloatProperty>("OffsetX_23_70FC2E814C64BBB82452748D2AF9CD48"_s)->value = style.offset.x();
style_prop->at<FloatProperty>("OffsetY_24_5E1F866C4C054D9B2EE337ADC180C17F"_s)->value = style.offset.y();
style_prop->at<FloatProperty>("Rotation_25_EC2DFAD84AD0A6BD3FA841ACD52EDD6D"_s)->value = style.rotation;
style_prop->at<FloatProperty>("Scale_26_19DF0708409262183E1247B317137671"_s)->value = style.scale;
style_prop->at<IntProperty>(MASS_STYLE_PATTERN_ID)->value = style.patternId;
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OPACITY)->value = style.opacity;
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OFFSETX)->value = style.offset.x();
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OFFSETY)->value = style.offset.y();
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_ROTATION)->value = style.rotation;
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_SCALE)->value = style.scale;
if(!_mass->saveToFile()) {
_lastError = _mass->lastError();

View File

@ -14,6 +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 "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/BoolProperty.h"
#include "../UESaveFile/Types/ByteProperty.h"
@ -31,11 +33,13 @@ auto Mass::meleeWeapons() -> Containers::ArrayView<Weapon> {
}
void Mass::getMeleeWeapons() {
getWeaponType("WeaponCC_22_0BBEC58C4A0EA1DB9E037B9339EE26A7"_s, _weapons.melee);
LOG_INFO("Getting melee weapons.");
getWeaponType(MASS_WEAPONS_MELEE, _weapons.melee);
}
auto Mass::writeMeleeWeapons() -> bool {
return writeWeaponType("WeaponCC_22_0BBEC58C4A0EA1DB9E037B9339EE26A7"_s, _weapons.melee);
LOG_INFO("Writing melee weapons.");
return writeWeaponType(MASS_WEAPONS_MELEE, _weapons.melee);
}
auto Mass::shields() -> Containers::ArrayView<Weapon> {
@ -43,11 +47,13 @@ auto Mass::shields() -> Containers::ArrayView<Weapon> {
}
void Mass::getShields() {
getWeaponType("Shield_53_839BFD7945481BAEA3E43A9C5CA8E92E"_s, _weapons.shields);
LOG_INFO("Getting shields.");
getWeaponType(MASS_WEAPONS_SHIELD, _weapons.shields);
}
auto Mass::writeShields() -> bool {
return writeWeaponType("Shield_53_839BFD7945481BAEA3E43A9C5CA8E92E"_s, _weapons.shields);
LOG_INFO("Writing shields.");
return writeWeaponType(MASS_WEAPONS_SHIELD, _weapons.shields);
}
auto Mass::bulletShooters() -> Containers::ArrayView<Weapon> {
@ -55,11 +61,13 @@ auto Mass::bulletShooters() -> Containers::ArrayView<Weapon> {
}
void Mass::getBulletShooters() {
getWeaponType("WeaponBS_35_6EF6E0104FD7A138DF47F88CB57A83ED"_s, _weapons.bulletShooters);
LOG_INFO("Getting bullet shooters.");
getWeaponType(MASS_WEAPONS_BSHOOTER, _weapons.bulletShooters);
}
auto Mass::writeBulletShooters() -> bool {
return writeWeaponType("WeaponBS_35_6EF6E0104FD7A138DF47F88CB57A83ED"_s, _weapons.bulletShooters);
LOG_INFO("Writing bullet shooters.");
return writeWeaponType(MASS_WEAPONS_BSHOOTER, _weapons.bulletShooters);
}
auto Mass::energyShooters() -> Containers::ArrayView<Weapon> {
@ -67,11 +75,13 @@ auto Mass::energyShooters() -> Containers::ArrayView<Weapon> {
}
void Mass::getEnergyShooters() {
getWeaponType("WeaponES_37_1A295D544528623880A0B1AC2C7DEE99"_s, _weapons.energyShooters);
LOG_INFO("Getting energy shooters.");
getWeaponType(MASS_WEAPONS_ESHOOTER, _weapons.energyShooters);
}
auto Mass::writeEnergyShooters() -> bool {
return writeWeaponType("WeaponES_37_1A295D544528623880A0B1AC2C7DEE99"_s, _weapons.energyShooters);
LOG_INFO("Writing energy shooters.");
return writeWeaponType(MASS_WEAPONS_ESHOOTER, _weapons.energyShooters);
}
auto Mass::bulletLaunchers() -> Containers::ArrayView<Weapon> {
@ -79,11 +89,13 @@ auto Mass::bulletLaunchers() -> Containers::ArrayView<Weapon> {
}
void Mass::getBulletLaunchers() {
getWeaponType("WeaponBL_36_5FD7C41E4613A75B44AB0E90B362846E"_s, _weapons.bulletLaunchers);
LOG_INFO("Getting bullet launchers.");
getWeaponType(MASS_WEAPONS_BLAUNCHER, _weapons.bulletLaunchers);
}
auto Mass::writeBulletLaunchers() -> bool {
return writeWeaponType("WeaponBL_36_5FD7C41E4613A75B44AB0E90B362846E"_s, _weapons.bulletLaunchers);
LOG_INFO("Writing bullet launchers.");
return writeWeaponType(MASS_WEAPONS_BLAUNCHER, _weapons.bulletLaunchers);
}
auto Mass::energyLaunchers() -> Containers::ArrayView<Weapon> {
@ -91,30 +103,33 @@ auto Mass::energyLaunchers() -> Containers::ArrayView<Weapon> {
}
void Mass::getEnergyLaunchers() {
getWeaponType("WeaponEL_38_9D23F3884ACA15902C9E6CA6E4995995"_s, _weapons.energyLaunchers);
LOG_INFO("Getting energy launchers.");
getWeaponType(MASS_WEAPONS_ELAUNCHER, _weapons.energyLaunchers);
}
auto Mass::writeEnergyLaunchers() -> bool {
return writeWeaponType("WeaponEL_38_9D23F3884ACA15902C9E6CA6E4995995"_s, _weapons.energyLaunchers);
LOG_INFO("Writing energy launchers.");
return writeWeaponType(MASS_WEAPONS_ELAUNCHER, _weapons.energyLaunchers);
}
void Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weapon> weapon_array) {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
Utility::Error{} << "Can't find unit data in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto prop = unit_data->at<ArrayProperty>(prop_name);
if(!prop) {
Utility::Error{} << "Can't find" << prop_name << "in" << _filename;
LOG_ERROR_FORMAT("Couldn't find {} in {}.", prop_name, _filename);
_state = State::Invalid;
return;
}
if(prop->items.size() != weapon_array.size()) {
Utility::Error{} << "Weapon arrays are not of the same size. Expected" << weapon_array.size() << Utility::Debug::nospace << ", got" << prop->items.size() << "instead.";
LOG_ERROR_FORMAT("Weapon arrays are not of the same size. Expected {}, got {} instead.",
weapon_array.size(), prop->items.size());
_state = State::Invalid;
return;
}
@ -123,38 +138,39 @@ void Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView
auto weapon_prop = prop->at<GenericStructProperty>(i);
auto& weapon = weapon_array[i];
weapon.name = weapon_prop->at<StringProperty>("Name_13_7BF0D31F4E50C50C47231BB36A485D92"_s)->value;
auto& weapon_type = weapon_prop->at<ByteProperty>("Type_2_35ABA8C3406F8D9BBF14A89CD6BCE976"_s)->enumValue;
weapon.name = weapon_prop->at<StringProperty>(MASS_WEAPON_NAME)->value;
auto& weapon_type = weapon_prop->at<ByteProperty>(MASS_WEAPON_TYPE)->enumValue;
#define c(enumerator, strenum, name) if(weapon_type == (strenum)) { weapon.type = WeaponType::enumerator; } else
#include "../Maps/WeaponTypes.hpp"
#undef c
{
LOG_ERROR_FORMAT("Invalid weapon type {} in {}.", weapon_type, _filename);
_state = State::Invalid;
Utility::Warning{} << "Invalid weapon type enum value in getWeaponType()."_s;
return;
}
auto parts_prop = weapon_prop->at<ArrayProperty>("Element_6_8E4617CC4B2C1F1490435599784EC6E0"_s);
auto parts_prop = weapon_prop->at<ArrayProperty>(MASS_WEAPON_ELEMENT);
weapon.parts = Containers::Array<WeaponPart>{ValueInit, parts_prop->items.size()};
for(UnsignedInt j = 0; j < parts_prop->items.size(); j++) {
auto part_prop = parts_prop->at<GenericStructProperty>(j);
auto& part = weapon.parts[j];
part.id = part_prop->at<IntProperty>("ID_2_A74D75434308158E5926178822DD28EE"_s)->value;
part.id = part_prop->at<IntProperty>(MASS_WEAPON_PART_ID)->value;
auto part_styles = part_prop->at<ArrayProperty>("Styles_17_994C97C34A90667BE5B716BFD0B97588"_s);
auto part_styles = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_STYLES);
for(UnsignedInt k = 0; k < part_styles->items.size(); k++) {
part.styles[k] = part_styles->at<IntProperty>(k)->value;
}
auto part_decals = part_prop->at<ArrayProperty>("Decals_13_8B81112B453D7230C0CDE982185E14F1"_s);
auto part_decals = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_DECALS);
if(part_decals->items.size() != part.decals.size()) {
part.decals = Containers::Array<Decal>{part_decals->items.size()};
}
getDecals(part.decals, part_decals);
auto part_accs = part_prop->at<ArrayProperty>("Accessories_21_3878DE8B4ED0EA0DB725E98BCDC20E0C"_s);
auto part_accs = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_ACCESSORIES);
if(!part_accs) {
part.accessories = Containers::Array<Accessory>{0};
continue;
@ -166,62 +182,69 @@ void Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView
getAccessories(part.accessories, part_accs);
}
auto custom_styles = weapon_prop->at<ArrayProperty>("Styles_10_8C3C82444B986AD7A99595AD4985912D"_s);
auto custom_styles = weapon_prop->at<ArrayProperty>(MASS_CUSTOM_WEAPON_STYLES);
if(!custom_styles) {
Utility::Error{} << "Can't find weapon custom styles in" << _filename;
LOG_ERROR_FORMAT("Can't find weapon custom styles in {}", _filename);
_state = State::Invalid;
return;
}
if(custom_styles->items.size() != weapon.customStyles.size()) {
Utility::Error{} << "Weapon custom style arrays are not of the same size. Expected" << weapon.customStyles.size() << Utility::Debug::nospace << ", got" << custom_styles->items.size() << "instead.";
LOG_ERROR_FORMAT("Custom weapon style arrays are not of the same size. Expected {}, got {} instead.",
weapon.customStyles.size(), custom_styles->items.size());
_state = State::Invalid;
return;
}
getCustomStyles(weapon.customStyles, custom_styles);
weapon.attached = weapon_prop->at<BoolProperty>("Attach_15_D00AABBD4AD6A04778D56D81E51927B3"_s)->value;
auto& damage_type = weapon_prop->at<ByteProperty>("DamageType_18_E1FFA53540591A9087EC698117A65C83"_s)->enumValue;
weapon.attached = weapon_prop->at<BoolProperty>(MASS_WEAPON_ATTACH)->value;
auto& damage_type = weapon_prop->at<ByteProperty>(MASS_WEAPON_DAMAGE_TYPE)->enumValue;
#define c(enumerator, strenum) if(damage_type == (strenum)) { weapon.damageType = DamageType::enumerator; } else
#include "../Maps/DamageTypes.hpp"
#undef c
{
LOG_ERROR_FORMAT("Invalid damage type {} in {}.", damage_type, _filename);
_state = State::Invalid;
Utility::Warning{} << "Invalid damage type enum value in getWeaponType()."_s;
return;
}
weapon.dualWield = weapon_prop->at<BoolProperty>("DualWield_20_B2EB2CEA4A6A233DC7575996B6DD1222"_s)->value;
auto& effect_colour_mode = weapon_prop->at<ByteProperty>("ColorEfxMode_24_D254BCF943E852BF9ADB8AAA8FD80014"_s)->enumValue;
weapon.dualWield = weapon_prop->at<BoolProperty>(MASS_WEAPON_DUAL_WIELD)->value;
auto& effect_colour_mode = weapon_prop->at<ByteProperty>(MASS_WEAPON_COLOUR_EFX_MODE)->enumValue;
#define c(enumerator, strenum) if(effect_colour_mode == (strenum)) { weapon.effectColourMode = EffectColourMode::enumerator; } else
#include "../Maps/EffectColourModes.hpp"
#undef c
{
LOG_ERROR_FORMAT("Invalid effect colour mode {} in {}.", effect_colour_mode, _filename);
_state = State::Invalid;
Utility::Warning{} << "Invalid effect colour mode in getWeaponType()."_s;
return;
}
auto effect_colour = weapon_prop->at<ColourStructProperty>("ColorEfx_26_D921B62946C493E487455A831F4520AC"_s);
auto effect_colour = weapon_prop->at<ColourStructProperty>(MASS_WEAPON_COLOUR_EFX);
weapon.effectColour = Color4{effect_colour->r, effect_colour->g, effect_colour->b, effect_colour->a};
}
}
auto Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weapon> weapon_array) -> bool {
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_state = State::Invalid;
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto prop = unit_data->at<ArrayProperty>(prop_name);
if(!prop) {
_state = State::Invalid;
_lastError = prop_name + " not found in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
if(prop->items.size() != weapon_array.size()) {
_lastError = Utility::format("Weapon arrays are not of the same size. Expected {}, got {} instead.",
weapon_array.size(), prop->items.size());
LOG_ERROR(_lastError);
_state = State::Invalid;
_lastError = "Weapon type array size mismatch."_s;
return false;
}
@ -229,19 +252,23 @@ auto Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayVi
auto weapon_prop = prop->at<GenericStructProperty>(i);
auto& weapon = weapon_array[i];
weapon_prop->at<StringProperty>("Name_13_7BF0D31F4E50C50C47231BB36A485D92"_s)->value = weapon.name;
weapon_prop->at<StringProperty>(MASS_WEAPON_NAME)->value = weapon.name;
switch(weapon.type) {
#define c(enumerator, strenum, name) case WeaponType::enumerator: weapon_prop->at<ByteProperty>("Type_2_35ABA8C3406F8D9BBF14A89CD6BCE976"_s)->enumValue = strenum; break;
#define c(enumerator, strenum, name) case WeaponType::enumerator: weapon_prop->at<ByteProperty>(MASS_WEAPON_TYPE)->enumValue = strenum; break;
#include "../Maps/WeaponTypes.hpp"
#undef c
default:
Utility::Warning{} << "Invalid weapon type enum value in writeWeaponType()."_s;
_lastError = Utility::format("Invalid weapon type at index {}.", i);
LOG_ERROR(_lastError);
return false;
}
auto parts_prop = weapon_prop->at<ArrayProperty>("Element_6_8E4617CC4B2C1F1490435599784EC6E0"_s);
auto parts_prop = weapon_prop->at<ArrayProperty>(MASS_WEAPON_ELEMENT);
if(parts_prop->items.size() != weapon.parts.size()) {
_lastError = Utility::format("Weapon part arrays are not of the same size. Expected {}, got {} instead.",
weapon.parts.size(), parts_prop->items.size());
LOG_ERROR(_lastError);
_state = State::Invalid;
_lastError = "Weapon parts array size mismatch."_s;
return false;
}
@ -249,40 +276,45 @@ auto Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayVi
auto part_prop = parts_prop->at<GenericStructProperty>(j);
auto& part = weapon.parts[j];
part_prop->at<IntProperty>("ID_2_A74D75434308158E5926178822DD28EE"_s)->value = part.id;
part_prop->at<IntProperty>(MASS_WEAPON_PART_ID)->value = part.id;
auto part_styles = part_prop->at<ArrayProperty>("Styles_17_994C97C34A90667BE5B716BFD0B97588"_s);
auto part_styles = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_STYLES);
for(UnsignedInt k = 0; k < part_styles->items.size(); k++) {
part_styles->at<IntProperty>(k)->value = part.styles[k];
}
auto part_decals = part_prop->at<ArrayProperty>("Decals_13_8B81112B453D7230C0CDE982185E14F1"_s);
auto part_decals = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_DECALS);
writeDecals(part.decals, part_decals);
auto part_accs = part_prop->at<ArrayProperty>("Accessories_21_3878DE8B4ED0EA0DB725E98BCDC20E0C"_s);
auto part_accs = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_ACCESSORIES);
if(!part_accs) {
continue;
}
if(part_accs->items.size() != part.accessories.size()) {
_lastError = Utility::format("Part accessory arrays are not of the same size. Expected {}, got {} instead.",
part.accessories.size(), part_accs->items.size());
LOG_ERROR(_lastError);
_state = State::Invalid;
_lastError = "Accessories array size mismatch."_s;
return false;
}
writeAccessories(part.accessories, part_accs);
}
auto custom_styles = weapon_prop->at<ArrayProperty>("Styles_10_8C3C82444B986AD7A99595AD4985912D"_s);
auto custom_styles = weapon_prop->at<ArrayProperty>(MASS_CUSTOM_WEAPON_STYLES);
if(!custom_styles) {
_state = State::Invalid;
_lastError = "No custom styles found for weapon."_s;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
if(custom_styles->items.size() != weapon.customStyles.size()) {
_lastError = Utility::format("Custom style arrays are not of the same size. Expected {}, got {} instead.",
weapon.customStyles.size(), custom_styles->items.size());
LOG_ERROR(_lastError);
_state = State::Invalid;
_lastError = "Custom styles array size mismatch."_s;
return false;
}
@ -290,25 +322,29 @@ auto Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayVi
writeCustomStyle(weapon.customStyles[j], j, custom_styles);
}
weapon_prop->at<BoolProperty>("Attach_15_D00AABBD4AD6A04778D56D81E51927B3"_s)->value = weapon.attached;
weapon_prop->at<BoolProperty>(MASS_WEAPON_ATTACH)->value = weapon.attached;
switch(weapon.damageType) {
#define c(enumerator, strenum) case DamageType::enumerator: weapon_prop->at<ByteProperty>("DamageType_18_E1FFA53540591A9087EC698117A65C83"_s)->enumValue = strenum; break;
#define c(enumerator, strenum) case DamageType::enumerator: weapon_prop->at<ByteProperty>(MASS_WEAPON_DAMAGE_TYPE)->enumValue = strenum; break;
#include "../Maps/DamageTypes.hpp"
#undef c
default:
Utility::Warning{} << "Unknown damage type enum value in writeWeaponType()."_s;
_lastError = Utility::format("Invalid damage type at index {}.", i);
LOG_ERROR(_lastError);
return false;
}
weapon_prop->at<BoolProperty>("DualWield_20_B2EB2CEA4A6A233DC7575996B6DD1222"_s)->value = weapon.dualWield;
weapon_prop->at<BoolProperty>(MASS_WEAPON_DUAL_WIELD)->value = weapon.dualWield;
switch(weapon.effectColourMode) {
#define c(enumerator, enumstr) case EffectColourMode::enumerator: \
weapon_prop->at<ByteProperty>("ColorEfxMode_24_D254BCF943E852BF9ADB8AAA8FD80014"_s)->enumValue = enumstr; \
weapon_prop->at<ByteProperty>(MASS_WEAPON_COLOUR_EFX_MODE)->enumValue = enumstr; \
break;
#include "../Maps/EffectColourModes.hpp"
#undef c
default:
Utility::Warning{} << "Unknown effect colour mode in writeWeaponType()."_s;
_lastError = Utility::format("Invalid damage type at index {}.", i);
LOG_ERROR(_lastError);
return false;
}
auto effect_colour = weapon_prop->at<ColourStructProperty>("ColorEfx_26_D921B62946C493E487455A831F4520AC"_s);
auto effect_colour = weapon_prop->at<ColourStructProperty>(MASS_WEAPON_COLOUR_EFX);
effect_colour->r = weapon.effectColour.r();
effect_colour->g = weapon.effectColour.g();
effect_colour->b = weapon.effectColour.b();

104
src/Mass/PropertyNames.h Normal file
View File

@ -0,0 +1,104 @@
#pragma once
#define MASS_UNIT_DATA "UnitData"
#define MASS_NAME "Name_45_A037C5D54E53456407BDF091344529BB"
#define MASS_ACCOUNT "Account"
#define MASS_GLOBAL_STYLES "GlobalStyles_57_6A681C114035241F7BDAAE9B43A8BF1B"
// Frame stuff
#define MASS_FRAME "Frame_3_F92B0F6A44A15088AF7F41B9FF290653"
#define MASS_JOINT_NECK "NeckLength_6_ED6AF79849C27CD1A9D523A09E2BFE58"
#define MASS_JOINT_BODY "BodyLength_7_C16287754CBA96C93BAE36A5C154996A"
#define MASS_JOINT_SHOULDER "ShoulderLength_8_220EDF304F1C1226F0D8D39117FB3883"
#define MASS_JOINT_ARM_UPPER "ArmUpperLength_10_249FDA3E4F3B399E7B9E5C9B7C765EAE"
#define MASS_JOINT_ARM_LOWER "ArmLowerLength_12_ACD0F02745C28882619376926292FB36"
#define MASS_JOINT_HIP "HipLength_14_02AEEEAC4376087B9C51F0AA7CC92818"
#define MASS_JOINT_LEG_UPPER "LegUpperLength_16_A7C4C71249A3776F7A543D96819C0C61"
#define MASS_JOINT_LEG_LOWER "LegLowerLength_18_D2DF39964EA0F2A2129D0491B08A032F"
#define MASS_FRAME_STYLES "Styles_32_00A3B3284B37F1E7819458844A20EB48"
#define MASS_EYE_FLARE "EyeFlareColor_36_AF79999C40FCA0E88A2F9A84488A38CA"
#define MASS_CUSTOM_FRAME_STYLES "FrameStyle_44_04A44C9440363CCEC5443D98BFAF22AA"
// Armour stuff
#define MASS_ARMOUR_PARTS "Armor_10_12E266C44116DDAF57E99ABB575A4B3C"
#define MASS_ARMOUR_SLOT "Slot_3_408BA56F4C9605C7E805CF91B642249C"
#define MASS_ARMOUR_ID "ID_5_ACD101864D3481DE96EDACACC09BDD25"
#define MASS_ARMOUR_STYLES "Styles_47_3E31870441DFD7DB8BEE5C85C26B365B"
#define MASS_ARMOUR_DECALS "Decals_42_F358794A4F18497970F56BA9627D3603"
#define MASS_ARMOUR_ACCESSORIES "Accessories_52_D902DD4241FA0050C2529596255153F3"
#define MASS_CUSTOM_ARMOUR_STYLES "ArmorStyle_42_E2F6AC3647788CB366BD469B3B7E899E"
// Weapon stuff
#define MASS_WEAPONS_MELEE "WeaponCC_22_0BBEC58C4A0EA1DB9E037B9339EE26A7"
#define MASS_WEAPONS_SHIELD "Shield_53_839BFD7945481BAEA3E43A9C5CA8E92E"
#define MASS_WEAPONS_BSHOOTER "WeaponBS_35_6EF6E0104FD7A138DF47F88CB57A83ED"
#define MASS_WEAPONS_ESHOOTER "WeaponES_37_1A295D544528623880A0B1AC2C7DEE99"
#define MASS_WEAPONS_BLAUNCHER "WeaponBL_36_5FD7C41E4613A75B44AB0E90B362846E"
#define MASS_WEAPONS_ELAUNCHER "WeaponEL_38_9D23F3884ACA15902C9E6CA6E4995995"
#define MASS_WEAPON_NAME "Name_13_7BF0D31F4E50C50C47231BB36A485D92"
#define MASS_WEAPON_TYPE "Type_2_35ABA8C3406F8D9BBF14A89CD6BCE976"
#define MASS_WEAPON_ELEMENT "Element_6_8E4617CC4B2C1F1490435599784EC6E0"
#define MASS_CUSTOM_WEAPON_STYLES "Styles_10_8C3C82444B986AD7A99595AD4985912D"
#define MASS_WEAPON_ATTACH "Attach_15_D00AABBD4AD6A04778D56D81E51927B3"
#define MASS_WEAPON_DAMAGE_TYPE "DamageType_18_E1FFA53540591A9087EC698117A65C83"
#define MASS_WEAPON_DUAL_WIELD "DualWield_20_B2EB2CEA4A6A233DC7575996B6DD1222"
#define MASS_WEAPON_COLOUR_EFX_MODE "ColorEfxMode_24_D254BCF943E852BF9ADB8AAA8FD80014"
#define MASS_WEAPON_COLOUR_EFX "ColorEfx_26_D921B62946C493E487455A831F4520AC"
// Weapon part stuff
#define MASS_WEAPON_PART_ID "ID_2_A74D75434308158E5926178822DD28EE"
#define MASS_WEAPON_PART_STYLES "Styles_17_994C97C34A90667BE5B716BFD0B97588"
#define MASS_WEAPON_PART_DECALS "Decals_13_8B81112B453D7230C0CDE982185E14F1"
#define MASS_WEAPON_PART_ACCESSORIES "Accessories_21_3878DE8B4ED0EA0DB725E98BCDC20E0C"
// BL attachment stuff
#define MASS_BL_ATTACHMENT_STYLE "WeaponBLAttachmentStyle_65_5943FCE8406F18D2C3F69285EB23A699"
#define MASS_BL_ATTACHMENTS "WeaponBLAttachment_61_442D08F547510A4CEE1501BBAF297BA0"
#define MASS_BL_ATTACHMENT_SOCKET "Socket_9_B9DBF30D4A1F0032A2BE2F8B342B35A9"
#define MASS_BL_ATTACHMENT_RELLOC "RelativeLocation_10_2F6E75DF4C40622658340E9A22D38B02"
#define MASS_BL_ATTACHMENT_OFFLOC "OffsetLocation_11_F42B3DA3436948FF85752DB33722382F"
#define MASS_BL_ATTACHMENT_RELROT "RelativeRotation_12_578140464621245132CFF2A2AD85E735"
#define MASS_BL_ATTACHMENT_OFFROT "OffsetRotation_13_B5980BCD47905D842D1490A1A520B064"
#define MASS_BL_ATTACHMENT_RELSCALE "RelativeScale_16_37BC80EF42699F79533F7AA7B3094E38"
// Style stuff
#define MASS_STYLE_NAME "Name_27_1532115A46EF2B2FA283908DF561A86B"
#define MASS_STYLE_COLOUR "Color_5_F0D383DF40474C9464AE48A0984A212E"
#define MASS_STYLE_METALLIC "Metallic_10_0A4CD1E4482CBF41CA61D0A856DE90B9"
#define MASS_STYLE_GLOSS "Gloss_11_9769599842CC275A401C4282A236E240"
#define MASS_STYLE_PATTERN_ID "PatternID_14_516DB85641DAF8ECFD2920BE2BDF1311"
#define MASS_STYLE_PATTERN_OPACITY "Opacity_30_53BD060B4DFCA1C92302D6A0F7831131"
#define MASS_STYLE_PATTERN_OFFSETX "OffsetX_23_70FC2E814C64BBB82452748D2AF9CD48"
#define MASS_STYLE_PATTERN_OFFSETY "OffsetY_24_5E1F866C4C054D9B2EE337ADC180C17F"
#define MASS_STYLE_PATTERN_ROTATION "Rotation_25_EC2DFAD84AD0A6BD3FA841ACD52EDD6D"
#define MASS_STYLE_PATTERN_SCALE "Scale_26_19DF0708409262183E1247B317137671"
// Decal stuff
#define MASS_DECAL_ID "ID_3_694C0B35404D8A3168AEC89026BC8CF9"
#define MASS_DECAL_COLOUR "Color_8_1B0B9D2B43DA6AAB9FA549B374D3E606"
#define MASS_DECAL_POSITION "Position_41_022C8FE84E1AAFE587261E88F2C72250"
#define MASS_DECAL_UAXIS "UAxis_37_EBEB715F45491AECACCC07A1AE4646D1"
#define MASS_DECAL_VAXIS "VAxis_39_C31EB2664EE202CAECFBBB84100B5E35"
#define MASS_DECAL_OFFSET "Offset_29_B02BBBB74FC60F5EDBEBAB8020738020"
#define MASS_DECAL_SCALE "Scale_32_959D1C2747AFD8D62808468235CBBA40"
#define MASS_DECAL_ROTATION "Rotation_27_12D7C314493D203D5C2326A03C5F910F"
#define MASS_DECAL_FLIP "Flip_35_CECCFB184CCD9412BD93FE9A8B656BE1"
#define MASS_DECAL_WRAP "Wrap_43_A7C68CDF4A92AF2ECDA53F953EE7CA62"
// Accessory stuff
#define MASS_ACCESSORY_ATTACH_INDEX "AttachIndex_2_4AFCF6024E4BA7426C6B9F80B8179D20"
#define MASS_ACCESSORY_ID "ID_4_5757B32647BAE263266259B8A7DFFFC1"
#define MASS_ACCESSORY_STYLES "Styles_7_91DEB0F24E24D13FC9472882C11D0DFD"
#define MASS_ACCESSORY_RELPOS "RelativePosition_14_BE8FB2A94074F34B3EDA6683B227D3A1"
#define MASS_ACCESSORY_OFFPOS "RelativePositionOffset_15_98FD0CE74E44BBAFC2D46FB4CA4E0ED6"
#define MASS_ACCESSORY_RELROT "RelativeRotation_20_C78C73274E6E78E7878F8C98ECA342C0"
#define MASS_ACCESSORY_OFFROT "RelativeRotationOffset_21_E07FA0EC46728B7BA763C6861249ABAA"
#define MASS_ACCESSORY_SCALE "LocalScale_24_DC2D93A742A41A46E7E61D988F15ED53"
// Tuning stuff
#define MASS_ENGINE "Engine"
#define MASS_GEARS "Gears"
#define MASS_OS "OS"
#define MASS_MODULES "Modules"
#define MASS_ARCHITECT "Architect"
#define MASS_TECHS "Techs"

View File

@ -19,16 +19,20 @@
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include "../Logger/Logger.h"
#include "MassManager.h"
using namespace Containers::Literals;
MassManager::MassManager(Containers::StringView save_path, Containers::StringView account, bool demo, Containers::StringView staging_dir):
MassManager::MassManager(Containers::StringView save_path, Containers::StringView account, bool demo,
Containers::StringView staging_dir):
_saveDirectory{save_path}, _account{account}, _demo{demo}, _stagingAreaDirectory{staging_dir}
{
Containers::String mass_filename = "";
for(UnsignedInt i = 0; i < _hangars.size(); i++) {
mass_filename = Utility::Path::join(_saveDirectory, Utility::format("{}Unit{:.2d}{}.sav", demo ? "Demo"_s : ""_s, i, _account));
mass_filename = Utility::Path::join(_saveDirectory,
Utility::format("{}Unit{:.2d}{}.sav", demo ? "Demo"_s : ""_s, i, _account));
new(&_hangars[i]) Mass{mass_filename};
}
@ -45,6 +49,8 @@ auto MassManager::hangar(Int hangar) -> Mass& {
void MassManager::refreshHangar(Int hangar) {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar index out of range.";
LOG_ERROR(_lastError);
return;
}
@ -56,7 +62,8 @@ void MassManager::refreshHangar(Int hangar) {
auto MassManager::importMass(Containers::StringView staged_fn, Int hangar) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar out of range in MassManager::importMass()"_s;
_lastError = "Hangar index out of range.";
LOG_ERROR(_lastError);
return false;
}
@ -64,6 +71,7 @@ auto MassManager::importMass(Containers::StringView staged_fn, Int hangar) -> bo
if(it == _stagedMasses.end()) {
_lastError = "Couldn't find "_s + staged_fn + " in the staged M.A.S.S.es."_s;
LOG_ERROR(_lastError);
return false;
}
@ -87,6 +95,7 @@ auto MassManager::importMass(Containers::StringView staged_fn, Int hangar) -> bo
if(!Utility::Path::move(source + ".tmp"_s, dest)) {
_lastError = Utility::format("Couldn't move {} to hangar {:.2d}", staged_fn, hangar + 1);
LOG_ERROR(_lastError);
return false;
}
@ -95,12 +104,14 @@ auto MassManager::importMass(Containers::StringView staged_fn, Int hangar) -> bo
auto MassManager::exportMass(Int hangar) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar out of range in MassManager::exportMass()"_s;
_lastError = "Hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
if(_hangars[hangar].state() != Mass::State::Valid) {
_lastError = Utility::format("There is no valid data to export in hangar {:.2d}", hangar + 1);
LOG_ERROR(_lastError);
return false;
}
@ -110,6 +121,7 @@ auto MassManager::exportMass(Int hangar) -> bool {
if(!Utility::Path::copy(source, dest)) {
_lastError = Utility::format("Couldn't export data from hangar {:.2d} to {}", hangar, dest);
LOG_ERROR(_lastError);
return false;
}
@ -118,12 +130,14 @@ auto MassManager::exportMass(Int hangar) -> bool {
auto MassManager::moveMass(Int source, Int destination) -> bool {
if(source < 0 || source >= 32) {
_lastError = "Source hangar out of range."_s;
_lastError = "Source hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
if(destination < 0 || destination >= 32) {
_lastError = "Destination hangar out of range."_s;
_lastError = "Destination hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
@ -153,12 +167,14 @@ auto MassManager::moveMass(Int source, Int destination) -> bool {
auto MassManager::deleteMass(Int hangar) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar out of range."_s;
_lastError = "Hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
if(!Utility::Path::remove(Utility::Path::join(_saveDirectory, _hangars[hangar].filename()))) {
_lastError = Utility::format("Deletion failed: {}", std::strerror(errno));
LOG_ERROR(_lastError);
return false;
}
@ -173,10 +189,11 @@ void MassManager::refreshStagedMasses() {
_stagedMasses.clear();
using Utility::Path::ListFlag;
auto file_list = Utility::Path::list(_stagingAreaDirectory, ListFlag::SkipSpecial|ListFlag::SkipDirectories|ListFlag::SkipDotAndDotDot);
auto file_list = Utility::Path::list(_stagingAreaDirectory,
ListFlag::SkipSpecial|ListFlag::SkipDirectories|ListFlag::SkipDotAndDotDot);
if(!file_list) {
Utility::Error{} << _stagingAreaDirectory << "couldn't be opened";
LOG_ERROR_FORMAT("{} couldn't be opened.", _stagingAreaDirectory);
return;
}
@ -184,24 +201,24 @@ void MassManager::refreshStagedMasses() {
return !file.hasSuffix(".sav"_s);
});
auto list_view = file_list->except(file_list->end() - iter);
auto list_view = file_list->exceptSuffix(file_list->end() - iter);
Utility::Debug{} << "Scanning for staged M.A.S.S.es...";
LOG_INFO("Scanning for staged M.A.S.S.es...");
for(Containers::StringView file : list_view) {
auto name = Mass::getNameFromFile(Utility::Path::join(_stagingAreaDirectory, file));
if(name) {
Utility::Debug{} << "Found staged M.A.S.S.:" << *name;
LOG_INFO_FORMAT("Found staged M.A.S.S.: {}", *name);
_stagedMasses[file] = *name;
}
else {
Utility::Warning{} << "Skipped:" << file;
LOG_WARNING_FORMAT("Skipped {}.", file);
}
}
}
void MassManager::refreshStagedMass(Containers::StringView filename) {
Utility::Debug{} << "Refreshing staged unit with filename" << filename;
LOG_INFO_FORMAT("Refreshing staged unit with filename {}.", filename);
bool file_exists = Utility::Path::exists(Utility::Path::join(_stagingAreaDirectory, filename));
auto it = _stagedMasses.find(filename);
@ -223,11 +240,13 @@ void MassManager::refreshStagedMass(Containers::StringView filename) {
auto MassManager::deleteStagedMass(Containers::StringView filename) -> bool {
if(_stagedMasses.find(filename) == _stagedMasses.cend()) {
_lastError = "The file "_s + filename + " couldn't be found in the list of staged M.A.S.S.es."_s;
LOG_ERROR(_lastError);
return false;
}
if(!Utility::Path::remove(Utility::Path::join(_stagingAreaDirectory, filename))) {
_lastError = filename + " couldn't be deleted: " + std::strerror(errno);
LOG_ERROR(_lastError);
return false;
}

View File

@ -16,12 +16,11 @@
#include <algorithm>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/StaticArray.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/Format.h>
#include "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/ResourceItemValue.h"
#include "../UESaveFile/Types/IntProperty.h"
@ -35,8 +34,11 @@ using namespace Containers::Literals;
Profile::Profile(Containers::StringView path):
_profile(path)
{
LOG_INFO_FORMAT("Reading profile at {}.", path);
if(!_profile.valid()) {
_lastError = _profile.lastError();
_valid = false;
return;
}
@ -49,7 +51,7 @@ Profile::Profile(Containers::StringView path):
_type = ProfileType::FullGame;
}
auto account_prop = _profile.at<StringProperty>("Account"_s);
auto account_prop = _profile.at<StringProperty>(PROFILE_ACCOUNT);
if(!account_prop) {
_lastError = "Couldn't find an account ID in "_s + _filename;
_valid = false;
@ -57,13 +59,6 @@ Profile::Profile(Containers::StringView path):
}
_account = account_prop->value;
if(_account.hasPrefix("PMCSlot"_s)) {
_version = ProfileVersion::Normal;
}
else {
_version = ProfileVersion::Legacy;
}
refreshValues();
}
@ -87,72 +82,77 @@ auto Profile::isDemo() const -> bool {
return _type == ProfileType::Demo;
}
auto Profile::version() const -> ProfileVersion {
return _version;
}
auto Profile::isLegacy() const -> bool {
return _version == ProfileVersion::Legacy;
}
auto Profile::account() const -> Containers::StringView {
return _account;
}
void Profile::refreshValues() {
if(!_profile.reloadData()) {
_lastError = _profile.lastError();
LOG_ERROR(_profile.lastError());
_valid = false;
return;
}
if(_profile.saveType() != "/Game/Core/Save/bpSaveGameProfile.bpSaveGameProfile_C"_s) {
Utility::Error{} << _filename << "is not a valid profile save.";
LOG_ERROR_FORMAT("{} is not a valid profile save.", _filename);
_valid = false;
return;
}
auto name_prop = _profile.at<StringProperty>("CompanyName"_s);
LOG_INFO("Getting the company name.");
auto name_prop = _profile.at<StringProperty>(PROFILE_NAME);
if(!name_prop) {
_lastError = "No company name in "_s + _filename;
LOG_ERROR(_lastError);
_valid = false;
return;
}
_name = name_prop->value;
auto prop = _profile.at<IntProperty>("ActiveFrameSlot"_s);
LOG_INFO("Getting the active frame slot.");
auto prop = _profile.at<IntProperty>(PROFILE_ACTIVE_FRAME_SLOT);
_activeFrameSlot = prop ? prop->value : 0;
prop = _profile.at<IntProperty>("Credit"_s);
LOG_INFO("Getting the credits.");
prop = _profile.at<IntProperty>(PROFILE_CREDITS);
_credits = prop ? prop->value : 0;
prop = _profile.at<IntProperty>("StoryProgress"_s);
LOG_INFO("Getting the story progress.");
prop = _profile.at<IntProperty>(PROFILE_STORY_PROGRESS);
_storyProgress = prop ? prop->value : 0;
prop = _profile.at<IntProperty>("LastMissionID"_s);
LOG_INFO("Getting the last mission ID.");
prop = _profile.at<IntProperty>(PROFILE_LAST_MISSION_ID);
_lastMissionId = prop ? prop->value : 0;
_verseSteel = getResource("ResourceMaterial"_s, VerseSteel);
_undinium = getResource("ResourceMaterial"_s, Undinium);
_necriumAlloy = getResource("ResourceMaterial"_s, NecriumAlloy);
_lunarite = getResource("ResourceMaterial"_s, Lunarite);
_asterite = getResource("ResourceMaterial"_s, Asterite);
LOG_INFO("Getting the materials.");
_verseSteel = getResource(PROFILE_MATERIAL, VerseSteel);
_undinium = getResource(PROFILE_MATERIAL, Undinium);
_necriumAlloy = getResource(PROFILE_MATERIAL, NecriumAlloy);
_lunarite = getResource(PROFILE_MATERIAL, Lunarite);
_asterite = getResource(PROFILE_MATERIAL, Asterite);
_halliteFragma = getResource(PROFILE_MATERIAL, HalliteFragma);
_ednil = getResource("ResourceMaterial"_s, Ednil);
_nuflalt = getResource("ResourceMaterial"_s, Nuflalt);
_aurelene = getResource("ResourceMaterial"_s, Aurelene);
_soldus = getResource("ResourceMaterial"_s, Soldus);
_synthesisedN = getResource("ResourceMaterial"_s, SynthesisedN);
_ednil = getResource(PROFILE_MATERIAL, Ednil);
_nuflalt = getResource(PROFILE_MATERIAL, Nuflalt);
_aurelene = getResource(PROFILE_MATERIAL, Aurelene);
_soldus = getResource(PROFILE_MATERIAL, Soldus);
_synthesisedN = getResource(PROFILE_MATERIAL, SynthesisedN);
_nanoc = getResource(PROFILE_MATERIAL, Nanoc);
_alcarbonite = getResource("ResourceMaterial"_s, Alcarbonite);
_keriphene = getResource("ResourceMaterial"_s, Keriphene);
_nitinolCM = getResource("ResourceMaterial"_s, NitinolCM);
_quarkium = getResource("ResourceMaterial"_s, Quarkium);
_alterene = getResource("ResourceMaterial"_s, Alterene);
_alcarbonite = getResource(PROFILE_MATERIAL, Alcarbonite);
_keriphene = getResource(PROFILE_MATERIAL, Keriphene);
_nitinolCM = getResource(PROFILE_MATERIAL, NitinolCM);
_quarkium = getResource(PROFILE_MATERIAL, Quarkium);
_alterene = getResource(PROFILE_MATERIAL, Alterene);
_cosmium = getResource(PROFILE_MATERIAL, Cosmium);
_mixedComposition = getResource("ResourceQuarkData"_s, MixedComposition);
_voidResidue = getResource("ResourceQuarkData"_s, VoidResidue);
_muscularConstruction = getResource("ResourceQuarkData"_s, MuscularConstruction);
_mineralExoskeletology = getResource("ResourceQuarkData"_s, MineralExoskeletology);
_carbonisedSkin = getResource("ResourceQuarkData"_s, CarbonisedSkin);
_mixedComposition = getResource(PROFILE_QUARK_DATA, MixedComposition);
_voidResidue = getResource(PROFILE_QUARK_DATA, VoidResidue);
_muscularConstruction = getResource(PROFILE_QUARK_DATA, MuscularConstruction);
_mineralExoskeletology = getResource(PROFILE_QUARK_DATA, MineralExoskeletology);
_carbonisedSkin = getResource(PROFILE_QUARK_DATA, CarbonisedSkin);
_isolatedVoidParticle = getResource(PROFILE_QUARK_DATA, IsolatedVoidParticle);
_valid = true;
}
@ -162,9 +162,10 @@ auto Profile::companyName() const -> Containers::StringView {
}
auto Profile::renameCompany(Containers::StringView new_name) -> bool {
auto name_prop = _profile.at<StringProperty>("CompanyName"_s);
auto name_prop = _profile.at<StringProperty>(PROFILE_NAME);
if(!name_prop) {
_lastError = "No company name in "_s + _filename;
LOG_ERROR(_lastError);
_valid = false;
return false;
}
@ -188,11 +189,12 @@ auto Profile::credits() const -> Int {
}
auto Profile::setCredits(Int amount) -> bool {
auto credits_prop = _profile.at<IntProperty>("Credit"_s);
auto credits_prop = _profile.at<IntProperty>(PROFILE_CREDITS);
if(!credits_prop) {
credits_prop = new IntProperty;
credits_prop->name.emplace("Credit"_s);
credits_prop->valueLength = sizeof(Int);
_profile.appendProperty(IntProperty::ptr{credits_prop});
}
@ -216,6 +218,7 @@ auto Profile::setStoryProgress(Int progress) -> bool {
if(!story_progress_prop) {
story_progress_prop = new IntProperty;
story_progress_prop->name.emplace("StoryProgress"_s);
story_progress_prop->valueLength = sizeof(Int);
_profile.appendProperty(IntProperty::ptr{story_progress_prop});
}
@ -238,7 +241,7 @@ auto Profile::verseSteel() const -> Int {
}
auto Profile::setVerseSteel(Int amount) -> bool {
return setResource("ResourceMaterial"_s, VerseSteel, amount);
return setResource(PROFILE_MATERIAL, VerseSteel, amount);
}
auto Profile::undinium() const -> Int {
@ -246,7 +249,7 @@ auto Profile::undinium() const -> Int {
}
auto Profile::setUndinium(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Undinium, amount);
return setResource(PROFILE_MATERIAL, Undinium, amount);
}
auto Profile::necriumAlloy() const -> Int {
@ -254,7 +257,7 @@ auto Profile::necriumAlloy() const -> Int {
}
auto Profile::setNecriumAlloy(Int amount) -> bool {
return setResource("ResourceMaterial"_s, NecriumAlloy, amount);
return setResource(PROFILE_MATERIAL, NecriumAlloy, amount);
}
auto Profile::lunarite() const -> Int {
@ -262,7 +265,7 @@ auto Profile::lunarite() const -> Int {
}
auto Profile::setLunarite(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Lunarite, amount);
return setResource(PROFILE_MATERIAL, Lunarite, amount);
}
auto Profile::asterite() const -> Int {
@ -270,7 +273,17 @@ auto Profile::asterite() const -> Int {
}
auto Profile::setAsterite(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Asterite, amount);
return setResource(PROFILE_MATERIAL, Asterite, amount);
}
Int
Profile::halliteFragma() const {
return _halliteFragma;
}
bool
Profile::setHalliteFragma(Int amount) {
return setResource(PROFILE_MATERIAL, HalliteFragma, amount);
}
auto Profile::ednil() const -> Int {
@ -278,7 +291,7 @@ auto Profile::ednil() const -> Int {
}
auto Profile::setEdnil(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Ednil, amount);
return setResource(PROFILE_MATERIAL, Ednil, amount);
}
auto Profile::nuflalt() const -> Int {
@ -286,7 +299,7 @@ auto Profile::nuflalt() const -> Int {
}
auto Profile::setNuflalt(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Nuflalt, amount);
return setResource(PROFILE_MATERIAL, Nuflalt, amount);
}
auto Profile::aurelene() const -> Int {
@ -294,7 +307,7 @@ auto Profile::aurelene() const -> Int {
}
auto Profile::setAurelene(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Aurelene, amount);
return setResource(PROFILE_MATERIAL, Aurelene, amount);
}
auto Profile::soldus() const -> Int {
@ -302,7 +315,7 @@ auto Profile::soldus() const -> Int {
}
auto Profile::setSoldus(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Soldus, amount);
return setResource(PROFILE_MATERIAL, Soldus, amount);
}
auto Profile::synthesisedN() const -> Int {
@ -310,7 +323,17 @@ auto Profile::synthesisedN() const -> Int {
}
auto Profile::setSynthesisedN(Int amount) -> bool {
return setResource("ResourceMaterial"_s, SynthesisedN, amount);
return setResource(PROFILE_MATERIAL, SynthesisedN, amount);
}
Int
Profile::nanoc() const {
return _nanoc;
}
bool
Profile::setNanoc(Int amount) {
return setResource(PROFILE_MATERIAL, Nanoc, amount);
}
auto Profile::alcarbonite() const -> Int {
@ -318,7 +341,7 @@ auto Profile::alcarbonite() const -> Int {
}
auto Profile::setAlcarbonite(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Alcarbonite, amount);
return setResource(PROFILE_MATERIAL, Alcarbonite, amount);
}
auto Profile::keriphene() const -> Int {
@ -326,7 +349,7 @@ auto Profile::keriphene() const -> Int {
}
auto Profile::setKeriphene(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Keriphene, amount);
return setResource(PROFILE_MATERIAL, Keriphene, amount);
}
auto Profile::nitinolCM() const -> Int {
@ -334,7 +357,7 @@ auto Profile::nitinolCM() const -> Int {
}
auto Profile::setNitinolCM(Int amount) -> bool {
return setResource("ResourceMaterial"_s, NitinolCM, amount);
return setResource(PROFILE_MATERIAL, NitinolCM, amount);
}
auto Profile::quarkium() const -> Int {
@ -342,7 +365,7 @@ auto Profile::quarkium() const -> Int {
}
auto Profile::setQuarkium(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Quarkium, amount);
return setResource(PROFILE_MATERIAL, Quarkium, amount);
}
auto Profile::alterene() const -> Int {
@ -350,7 +373,17 @@ auto Profile::alterene() const -> Int {
}
auto Profile::setAlterene(Int amount) -> bool {
return setResource("ResourceMaterial"_s, Alterene, amount);
return setResource(PROFILE_MATERIAL, Alterene, amount);
}
Int
Profile::cosmium() const {
return _cosmium;
}
bool
Profile::setCosmium(Int amount) {
return setResource(PROFILE_MATERIAL, Cosmium, amount);
}
auto Profile::mixedComposition() const -> Int {
@ -358,7 +391,7 @@ auto Profile::mixedComposition() const -> Int {
}
auto Profile::setMixedComposition(Int amount) -> bool {
return setResource("ResourceQuarkData"_s, MixedComposition, amount);
return setResource(PROFILE_QUARK_DATA, MixedComposition, amount);
}
auto Profile::voidResidue() const -> Int {
@ -366,7 +399,7 @@ auto Profile::voidResidue() const -> Int {
}
auto Profile::setVoidResidue(Int amount) -> bool {
return setResource("ResourceQuarkData"_s, VoidResidue, amount);
return setResource(PROFILE_QUARK_DATA, VoidResidue, amount);
}
auto Profile::muscularConstruction() const -> Int {
@ -374,7 +407,7 @@ auto Profile::muscularConstruction() const -> Int {
}
auto Profile::setMuscularConstruction(Int amount) -> bool {
return setResource("ResourceQuarkData"_s, MuscularConstruction, amount);
return setResource(PROFILE_QUARK_DATA, MuscularConstruction, amount);
}
auto Profile::mineralExoskeletology() const -> Int {
@ -382,7 +415,7 @@ auto Profile::mineralExoskeletology() const -> Int {
}
auto Profile::setMineralExoskeletology(Int amount) -> bool {
return setResource("ResourceQuarkData"_s, MineralExoskeletology, amount);
return setResource(PROFILE_QUARK_DATA, MineralExoskeletology, amount);
}
auto Profile::carbonisedSkin() const -> Int {
@ -390,7 +423,17 @@ auto Profile::carbonisedSkin() const -> Int {
}
auto Profile::setCarbonisedSkin(Int amount) -> bool {
return setResource("ResourceQuarkData"_s, CarbonisedSkin, amount);
return setResource(PROFILE_QUARK_DATA, CarbonisedSkin, amount);
}
Int
Profile::isolatedVoidParticle() const {
return _isolatedVoidParticle;
}
bool
Profile::setIsolatedVoidParticle(Int amount) {
return setResource(PROFILE_QUARK_DATA, IsolatedVoidParticle, amount);
}
auto Profile::getResource(Containers::StringView container, MaterialID id) -> Int {
@ -413,9 +456,10 @@ auto Profile::setResource(Containers::StringView container, MaterialID id, Int a
auto mats_prop = _profile.at<ArrayProperty>(container);
if(!mats_prop) {
_lastError = "Couldn't find "_s + container + " in "_s + _filename;
_valid = false;
return false;
mats_prop = new ArrayProperty;
mats_prop->name.emplace(container);
mats_prop->itemType = "StructProperty";
_profile.appendProperty(ArrayProperty::ptr{mats_prop});
}
auto predicate = [&id](UnrealPropertyBase::ptr& prop){
@ -428,6 +472,9 @@ auto Profile::setResource(Containers::StringView container, MaterialID id, Int a
ResourceItemValue* res_prop;
if(it == mats_prop->items.end()) {
res_prop = new ResourceItemValue;
if(mats_prop->items.isEmpty()) {
res_prop->name.emplace(container);
}
res_prop->id = id;
ResourceItemValue::ptr prop{res_prop};
arrayAppend(mats_prop->items, std::move(prop));

View File

@ -33,11 +33,6 @@ enum class ProfileType : UnsignedByte {
FullGame
};
enum class ProfileVersion : UnsignedByte {
Legacy, // pre-0.8
Normal // 0.8 and later
};
class Profile {
public:
explicit Profile(Containers::StringView path);
@ -51,9 +46,6 @@ class Profile {
auto type() const -> ProfileType;
auto isDemo() const -> bool;
auto version() const -> ProfileVersion;
auto isLegacy() const -> bool;
auto account() const -> Containers::StringView;
void refreshValues();
@ -86,6 +78,9 @@ class Profile {
auto asterite() const -> Int;
auto setAsterite(Int amount) -> bool;
Int halliteFragma() const;
bool setHalliteFragma(Int amount);
auto ednil() const -> Int;
auto setEdnil(Int amount) -> bool;
@ -101,6 +96,9 @@ class Profile {
auto synthesisedN() const -> Int;
auto setSynthesisedN(Int amount) -> bool;
Int nanoc() const;
bool setNanoc(Int amount);
auto alcarbonite() const -> Int;
auto setAlcarbonite(Int amount) -> bool;
@ -116,6 +114,9 @@ class Profile {
auto alterene() const -> Int;
auto setAlterene(Int amount) -> bool;
Int cosmium() const;
bool setCosmium(Int amount);
auto mixedComposition() const -> Int;
auto setMixedComposition(Int amount) -> bool;
@ -131,6 +132,9 @@ class Profile {
auto carbonisedSkin() const -> Int;
auto setCarbonisedSkin(Int amount) -> bool;
Int isolatedVoidParticle() const;
bool setIsolatedVoidParticle(Int amount);
private:
auto getResource(Containers::StringView container, MaterialID id) -> Int;
auto setResource(Containers::StringView container, MaterialID id, Int amount) -> bool;
@ -138,7 +142,6 @@ class Profile {
Containers::String _filename;
ProfileType _type;
ProfileVersion _version;
UESaveFile _profile;
@ -148,29 +151,33 @@ class Profile {
Int _storyProgress = 0;
Int _lastMissionId = 0;
Int _verseSteel = 0;
Int _undinium = 0;
Int _necriumAlloy = 0;
Int _lunarite = 0;
Int _asterite = 0;
Int _verseSteel = 0;
Int _undinium = 0;
Int _necriumAlloy = 0;
Int _lunarite = 0;
Int _asterite = 0;
Int _halliteFragma = 0;
Int _ednil = 0;
Int _nuflalt = 0;
Int _aurelene = 0;
Int _soldus = 0;
Int _synthesisedN = 0;
Int _nanoc = 0;
Int _alcarbonite = 0;
Int _keriphene = 0;
Int _nitinolCM = 0;
Int _quarkium = 0;
Int _alterene = 0;
Int _cosmium = 0;
Int _mixedComposition = 0;
Int _voidResidue = 0;
Int _muscularConstruction = 0;
Int _mineralExoskeletology = 0;
Int _carbonisedSkin = 0;
Int _isolatedVoidParticle = 0;
Containers::String _account;

View File

@ -0,0 +1,10 @@
#pragma once
#define PROFILE_NAME "CompanyName"
#define PROFILE_ACTIVE_FRAME_SLOT "ActiveFrameSlot"
#define PROFILE_CREDITS "Credit"
#define PROFILE_STORY_PROGRESS "StoryProgress"
#define PROFILE_LAST_MISSION_ID "LastMissionID"
#define PROFILE_MATERIAL "ResourceMaterial"
#define PROFILE_QUARK_DATA "ResourceQuarkData"
#define PROFILE_ACCOUNT "Account"

View File

@ -21,27 +21,31 @@
using namespace Magnum;
enum MaterialID : Int {
VerseSteel = 0xC3500,
Undinium = 0xC3501,
NecriumAlloy = 0xC3502,
Lunarite = 0xC3503,
Asterite = 0xC3504,
VerseSteel = 0xC3500,
Undinium = 0xC3501,
NecriumAlloy = 0xC3502,
Lunarite = 0xC3503,
Asterite = 0xC3504,
HalliteFragma = 0xC3505,
Ednil = 0xC350A,
Nuflalt = 0xC350B,
Aurelene = 0xC350C,
Soldus = 0xC350D,
SynthesisedN = 0xC350E,
Nanoc = 0xC350F,
Alcarbonite = 0xC3514,
Keriphene = 0xC3515,
NitinolCM = 0xC3516,
Quarkium = 0xC3517,
Alterene = 0xC3518,
Cosmium = 0xC3519,
MixedComposition = 0xDBBA0,
VoidResidue = 0xDBBA1,
MuscularConstruction = 0xDBBA2,
MineralExoskeletology = 0xDBBA3,
CarbonisedSkin = 0xDBBA4
CarbonisedSkin = 0xDBBA4,
IsolatedVoidParticle = 0xDBBA5,
};

View File

@ -18,8 +18,6 @@
#include <algorithm>
#include <chrono>
#include <iomanip>
#include <regex>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Containers/StaticArray.h>
@ -29,6 +27,8 @@
#include <zip.h>
#include "../Logger/Logger.h"
#include "ProfileManager.h"
using namespace Containers::Literals;
@ -53,6 +53,8 @@ auto ProfileManager::profiles() -> Containers::ArrayView<Profile> {
}
auto ProfileManager::refreshProfiles() -> bool {
LOG_INFO("Refreshing profiles.");
_profiles = Containers::Array<Profile>{};
using Utility::Path::ListFlag;
@ -61,31 +63,30 @@ auto ProfileManager::refreshProfiles() -> bool {
if(!files) {
_lastError = _saveDirectory + " can't be opened.";
LOG_ERROR(_lastError);
return false;
}
auto predicate = [](Containers::StringView file)->bool{
std::regex legacy_regex("(Demo)?Profile[0-9]{17}\\.sav", std::regex::nosubs);
std::regex new_regex("(Demo)?ProfilePMCSlot[0-9]{3}\\.sav", std::regex::nosubs);
std::cmatch m;
return !std::regex_match(file.data(), m, legacy_regex) && !std::regex_match(file.data(), m, new_regex);
return !((file.hasPrefix("DemoProfile") || file.hasPrefix("Profile")) && file.hasSuffix(".sav"));
};
auto files_view = files->except(files->end() - std::remove_if(files->begin(), files->end(), predicate));
auto files_view = files->exceptSuffix(files->end() - std::remove_if(files->begin(), files->end(), predicate));
for(const auto& file : files_view) {
Profile profile{Utility::Path::join(_saveDirectory, file)};
if(!profile.valid()) {
Utility::Warning{} << "Profile"_s << file << "is invalid:"_s << profile.lastError();
LOG_WARNING_FORMAT("Profile {} is invalid: {}", file, profile.lastError());
continue;
}
arrayAppend(_profiles, std::move(profile));
}
if(_profiles.empty()) {
if(_profiles.isEmpty()) {
_lastError = "No valid profiles were found."_s;
LOG_ERROR(_lastError);
return false;
}
@ -101,6 +102,7 @@ auto ProfileManager::deleteProfile(std::size_t index, bool delete_builds) -> boo
_lastError = Utility::format("Couldn't delete {} (filename: {}).",
_profiles[index].companyName(),
_profiles[index].filename());
LOG_ERROR(_lastError);
refreshProfiles();
return false;
}
@ -129,7 +131,7 @@ auto ProfileManager::backupProfile(std::size_t index, bool backup_builds) -> boo
std::tm* time = std::localtime(&timestamp);
auto& profile = _profiles[index];
auto filename = Utility::format("{}_{}{:.2d}{:.2d}_{:.2d}{:.2d}{:.2d}.mbprofbackup",
auto filename = Utility::format("{}_{}{:.2d}{:.2d}_{:.2d}{:.2d}{:.2d}.backup.mbst",
Utility::String::replaceAll(profile.companyName().data(), " ", "_").c_str(),
time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
time->tm_hour, time->tm_min, time->tm_sec);
@ -140,26 +142,28 @@ auto ProfileManager::backupProfile(std::size_t index, bool backup_builds) -> boo
if(zip == nullptr) {
zip_error_init_with_code(&error, error_code);
_lastError = zip_error_strerror(&error);
LOG_ERROR(_lastError);
return false;
}
zip_source_t* profile_source = zip_source_file(zip, Utility::Path::toNativeSeparators(Utility::Path::join(_saveDirectory, profile.filename())).data(), 0, 0);
if(profile_source == nullptr) {
_lastError = zip_strerror(zip);
LOG_ERROR(_lastError);
zip_source_free(profile_source);
return false;
}
if(zip_file_add(zip, profile.filename().data(), profile_source, ZIP_FL_ENC_UTF_8) == -1) {
_lastError = zip_strerror(zip);
LOG_ERROR(_lastError);
zip_source_free(profile_source);
return false;
}
auto comment = Utility::format("{}|{}{}|{}-{:.2d}-{:.2d}-{:.2d}-{:.2d}-{:.2d}",
auto comment = Utility::format("{}|{}|{}-{:.2d}-{:.2d}-{:.2d}-{:.2d}-{:.2d}",
profile.companyName(),
profile.isDemo() ? "demo"_s : "full"_s,
profile.isLegacy() ? ""_s : "_new"_s,
time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
time->tm_hour, time->tm_min, time->tm_sec);
zip_set_archive_comment(zip, comment.data(), comment.size());
@ -189,6 +193,7 @@ auto ProfileManager::backupProfile(std::size_t index, bool backup_builds) -> boo
if(zip_close(zip) == -1) {
_lastError = zip_strerror(zip);
LOG_ERROR(_lastError);
return false;
}
@ -210,14 +215,15 @@ void ProfileManager::refreshBackups() {
if(!files) {
_lastError = _backupsDirectory + " can't be opened.";
LOG_ERROR(_lastError);
return;
}
auto predicate = [](Containers::StringView file)->bool{
return !file.hasSuffix(".mbprofbackup"_s);
return !(file.hasSuffix(".mbprofbackup"_s) || file.hasSuffix(".backup.mbst"));
};
auto files_view = files->except(files->end() - std::remove_if(files->begin(), files->end(), predicate));
auto files_view = files->exceptSuffix(files->end() - std::remove_if(files->begin(), files->end(), predicate));
int error_code = 0;
zip_t* zip = nullptr;
@ -252,21 +258,11 @@ void ProfileManager::refreshBackups() {
backup.company = info[0];
if(info[1] == "full") {
if(info[1].hasPrefix("full")) {
backup.type = ProfileType::FullGame;
backup.version = ProfileVersion::Legacy;
}
else if(info[1] == "demo") {
else if(info[1].hasPrefix("demo")) {
backup.type = ProfileType::Demo;
backup.version = ProfileVersion::Legacy;
}
else if(info[1] == "full_new") {
backup.type = ProfileType::FullGame;
backup.version = ProfileVersion::Normal;
}
else if(info[1] == "demo_new") {
backup.type = ProfileType::Demo;
backup.version = ProfileVersion::Normal;
}
else {
continue;
@ -297,6 +293,7 @@ void ProfileManager::refreshBackups() {
auto ProfileManager::deleteBackup(std::size_t index) -> bool {
if(!Utility::Path::remove(Utility::Path::join(_backupsDirectory, _backups[index].filename))) {
_lastError = "Couldn't delete " + _backups[index].filename;
LOG_ERROR(_lastError);
return false;
}
@ -323,6 +320,7 @@ auto ProfileManager::restoreBackup(std::size_t index) -> bool {
zip_error_t error;
zip_error_init_with_code(&error, error_code);
_lastError = zip_error_strerror(&error);
LOG_ERROR(_lastError);
return false;
}
@ -332,6 +330,7 @@ auto ProfileManager::restoreBackup(std::size_t index) -> bool {
FILE* out = std::fopen(Utility::Path::join(_saveDirectory, file).data(), "wb");
if(out == nullptr) {
_lastError = Utility::format(error_format.data(), file, std::strerror(errno));
LOG_ERROR(_lastError);
return false;
}
@ -340,6 +339,7 @@ auto ProfileManager::restoreBackup(std::size_t index) -> bool {
zip_file_t* zf = zip_fopen(zip, file.data(), ZIP_FL_ENC_GUESS);
if(zf == nullptr) {
_lastError = Utility::format(error_format.data(), file, zip_strerror(zip));
LOG_ERROR(_lastError);
return false;
}
@ -351,12 +351,14 @@ auto ProfileManager::restoreBackup(std::size_t index) -> bool {
while((bytes_read = zip_fread(zf, buf.data(), buf.size())) > 0) {
if(std::fwrite(buf.data(), sizeof(char), bytes_read, out) < static_cast<std::size_t>(bytes_read)) {
_lastError = Utility::format(error_format.data(), file, "not enough bytes written.");
LOG_ERROR(_lastError);
return false;
}
}
if(bytes_read == -1) {
_lastError = Utility::format(error_format.data(), file, "couldn't read bytes from archive.");
LOG_ERROR(_lastError);
return false;
}
}

View File

@ -29,7 +29,6 @@ struct Backup {
Containers::String filename;
Containers::String company;
ProfileType type;
ProfileVersion version;
struct {
int year;
int month;

View File

@ -27,12 +27,15 @@
#include <Magnum/ImGuiIntegration/Integration.h>
#include <Magnum/ImGuiIntegration/Context.hpp>
#include <SDL.h>
#include <curl/curl.h>
#include <shellapi.h>
#include <wtsapi32.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../Logger/Logger.h"
using namespace Containers::Literals;
@ -47,10 +50,11 @@ SaveTool::SaveTool(const Arguments& arguments):
Configuration{}.setTitle("M.A.S.S. Builder Save Tool " SAVETOOL_VERSION " (\"" SAVETOOL_CODENAME "\")")
.setSize({960, 720})}
{
#ifdef SAVETOOL_DEBUG_BUILD
tweak.enable(""_s, "../../"_s);
#endif
#ifdef SAVETOOL_DEBUG_BUILD
tweak.enable("", "../../");
#endif
LOG_INFO("Configuring OpenGL renderer.");
GL::Renderer::enable(GL::Renderer::Feature::Blending);
GL::Renderer::enable(GL::Renderer::Feature::ScissorTest);
GL::Renderer::disable(GL::Renderer::Feature::FaceCulling);
@ -60,25 +64,20 @@ SaveTool::SaveTool(const Arguments& arguments):
GL::Renderer::setBlendEquation(GL::Renderer::BlendEquation::Add,
GL::Renderer::BlendEquation::Add);
Utility::Debug{} << "Renderer initialisation successful.";
Utility::Debug{} << "===Configuring SDL2===";
{
Utility::Debug d{};
d << "Enabling clickthrough...";
if(SDL_VERSION_ATLEAST(2, 0, 5)) {
if(SDL_SetHintWithPriority(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1", SDL_HINT_OVERRIDE) == SDL_TRUE) {
d << "success!"_s;
} else {
d << "error: hint couldn't be set."_s;
}
} else {
d << "error: SDL2 is too old (version < 2.0.5)."_s;
}
LOG_INFO("Configuring SDL2.");
#if SDL_VERSION_ATLEAST(2,0,5)
if(SDL_SetHintWithPriority(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1", SDL_HINT_OVERRIDE) == SDL_TRUE) {
LOG_INFO("Clickthrough is enabled.");
}
else {
LOG_WARNING("Clickthrough is disabled.");
}
#else
LOG_WARNING_FORMAT("Clickthrough is disabled: SDL2 version is too old ({}.{}.{})",
SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
#endif
LOG_INFO("Registering custom events.");
if((_initEventId = SDL_RegisterEvents(3)) == UnsignedInt(-1)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
"SDL_RegisterEvents() failed in SaveTool::SaveTool(). Exiting...", window());
@ -89,26 +88,27 @@ SaveTool::SaveTool(const Arguments& arguments):
_updateEventId = _initEventId + 1;
_fileEventId = _initEventId + 2;
LOG_INFO("Initialising the timer subsystem.");
if(SDL_InitSubSystem(SDL_INIT_TIMER) != 0) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app", SDL_GetError(), window());
LOG_ERROR(SDL_GetError());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app",
SDL_GetError(), window());
exit(EXIT_FAILURE);
return;
}
Utility::Debug{} << "SDL2 configuration successful.";
Utility::Debug{} << "===Initialising the Save Tool===";
initialiseGui();
if(!initialiseToolDirectories()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app", _lastError.data(), window());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app",
_lastError.data(), window());
exit(EXIT_FAILURE);
return;
}
if(!findGameDataDirectory()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app", _lastError.data(), window());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app",
_lastError.data(), window());
exit(EXIT_FAILURE);
return;
}
@ -120,6 +120,7 @@ SaveTool::SaveTool(const Arguments& arguments):
return interval;
}, this);
if(_gameCheckTimerId == 0) {
LOG_ERROR(SDL_GetError());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", SDL_GetError(), window());
exit(EXIT_FAILURE);
return;
@ -127,19 +128,7 @@ SaveTool::SaveTool(const Arguments& arguments):
initialiseConfiguration();
switch(_framelimit) {
case Framelimit::Vsync:
setSwapInterval(1);
break;
case Framelimit::HalfVsync:
setSwapInterval(2);
break;
case Framelimit::FpsCap:
setSwapInterval(0);
setMinimalLoopPeriod(1000/_fpsCap);
break;
}
LOG_INFO("Initialising update checker.");
curl_global_init(CURL_GLOBAL_DEFAULT);
if(_checkUpdatesOnStartup) {
_queue.addToast(Toast::Type::Default, "Checking for updates..."_s);
@ -152,64 +141,61 @@ SaveTool::SaveTool(const Arguments& arguments):
GL::DebugOutput::setEnabled(GL::DebugOutput::Source::Api, GL::DebugOutput::Type::Other, {131185}, false);
}
Utility::Debug{} << "Initialisation successful.";
Utility::Debug{} << "===Running main loop===";
if(_skipDisclaimer) {
_uiState = UiState::Initialising;
_initThread = std::thread{[this]{ initialiseManager(); }};
}
_timeline.start();
}
SaveTool::~SaveTool() {
Utility::Debug{} << "===Perfoming cleanup===";
LOG_INFO("Cleaning up.");
Utility::Debug{} << "Shutting libcurl down...";
LOG_INFO("Shutting libcurl down.");
curl_global_cleanup();
SDL_RemoveTimer(_gameCheckTimerId);
Utility::Debug{} << "Saving the configuration...";
LOG_INFO("Saving the configuration.");
_conf.setValue("cheat_mode"_s, _cheatMode);
_conf.setValue("unsafe_mode"_s, _unsafeMode);
_conf.setValue("advanced_mode"_s, _advancedMode);
_conf.setValue("startup_update_check"_s, _checkUpdatesOnStartup);
_conf.setValue("skip_disclaimer"_s, _skipDisclaimer);
switch(_framelimit) {
case Framelimit::Vsync:
_conf.setValue("frame_limit"_s, "vsync"_s);
break;
case Framelimit::HalfVsync:
_conf.setValue("frame_limit"_s, "half_vsync"_s);
break;
case Framelimit::FpsCap:
_conf.setValue<UnsignedInt>("frame_limit"_s, _fpsCap);
break;
}
_conf.setValue("swap_interval"_s, _swapInterval);
_conf.setValue("fps_cap"_s, _fpsCap);
_conf.save();
Utility::Debug{} << "Exiting...";
LOG_INFO("Exiting.");
}
void SaveTool::drawEvent() {
#ifdef SAVETOOL_DEBUG_BUILD
#ifdef SAVETOOL_DEBUG_BUILD
tweak.update();
#endif
#endif
GL::defaultFramebuffer.clear(GL::FramebufferClear::Color);
drawImGui();
swapBuffers();
if(_swapInterval == 0 && _fpsCap < 301.0f) {
while(_timeline.currentFrameDuration() < (1.0f / _fpsCap));
}
redraw();
_timeline.nextFrame();
}
void SaveTool::viewportEvent(ViewportEvent& event) {
GL::defaultFramebuffer.setViewport({{}, event.framebufferSize()});
_imgui.relayout(event.windowSize());
const Vector2 size = Vector2{windowSize()}/dpiScaling();
_imgui.relayout(size, windowSize(), framebufferSize());
}
void SaveTool::keyPressEvent(KeyEvent& event) {
@ -297,7 +283,7 @@ void SaveTool::drawGui() {
drawAbout();
}
#ifdef SAVETOOL_DEBUG_BUILD
#ifdef SAVETOOL_DEBUG_BUILD
if(_demoWindow) {
ImGui::ShowDemoWindow(&_demoWindow);
}
@ -309,7 +295,7 @@ void SaveTool::drawGui() {
if(_metricsWindow) {
ImGui::ShowMetricsWindow(&_metricsWindow);
}
#endif
#endif
_queue.draw(windowSize());
}
@ -328,11 +314,11 @@ void SaveTool::drawDisclaimer() {
ImGui::TextUnformatted("Before you start using the app, there are a few things you should know:");
ImGui::PushTextWrapPos(windowSize().x() * 0.67f);
ImGui::PushTextWrapPos(float(windowSize().x()) * 0.67f);
ImGui::Bullet();
ImGui::SameLine();
ImGui::TextUnformatted("For this application to work properly, it is recommended to disable Steam Cloud syncing for the game. To disable it, right-click the game in your Steam library, click \"Properties\", go to the \"General\" tab, and uncheck \"Keep game saves in the Steam Cloud for M.A.S.S. Builder\".");
ImGui::TextUnformatted(R"(For this application to work properly, it is recommended to disable Steam Cloud syncing for the game. To disable it, right-click the game in your Steam library, click "Properties", go to the "General" tab, and uncheck "Keep game saves in the Steam Cloud for M.A.S.S. Builder".)");
ImGui::Bullet();
ImGui::SameLine();

View File

@ -23,11 +23,15 @@
#include <Corrade/Containers/String.h>
#include <Corrade/Utility/Configuration.h>
#include <Corrade/Utility/Resource.h>
#ifdef SAVETOOL_DEBUG_BUILD
#include <Corrade/Utility/Tweakable.h>
#endif
#include <Magnum/Timeline.h>
#include <Magnum/Platform/Sdl2Application.h>
#include <Magnum/ImGuiIntegration/Context.h>
#include <SDL.h>
#include <SDL_timer.h>
#include <imgui.h>
#include <imgui_internal.h>
@ -39,8 +43,6 @@
#include "../ToastQueue/ToastQueue.h"
#ifdef SAVETOOL_DEBUG_BUILD
#include <Corrade/Utility/Tweakable.h>
#define tw CORRADE_TWEAKABLE
#endif
@ -166,23 +168,16 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
template<typename Functor, typename... Args>
auto drawUnsafeWidget(Functor func, Args... args) -> bool {
GameState game_state = _gameState; // Copying the value to reduce the risk of a data race.
if(!_unsafeMode && game_state != GameState::NotRunning) {
ImGui::BeginDisabled();
}
ImGui::BeginDisabled(game_state != GameState::NotRunning);
bool result = func(std::forward<Args>(args)...);
if(!_unsafeMode && game_state != GameState::NotRunning) {
ImGui::EndDisabled();
}
ImGui::EndDisabled();
return result;
} // Obviously, should only be used with ImGui widgets that return a bool.
// Also, func should be a lambda if there are any default arguments, like ImGui::Button(), etc...
template<typename... Args>
void drawUnsafeText(Containers::StringView text, Args... args) { // Alternative to the above, for ImGui::Text*() variants.
if(!_unsafeMode && _gameState != GameState::NotRunning) {
void drawUnsafeText(const char* text, Args... args) { // Alternative to the above, for ImGui::Text*() variants.
if(_gameState != GameState::NotRunning) {
ImGui::TextDisabled(text, std::forward<Args>(args)...);
}
else {
@ -217,11 +212,11 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
} _uiState{UiState::Disclaimer};
bool _aboutPopup{false};
#ifdef SAVETOOL_DEBUG_BUILD
#ifdef SAVETOOL_DEBUG_BUILD
bool _demoWindow{false};
bool _styleEditor{false};
bool _metricsWindow{false};
#endif
#endif
ToastQueue _queue;
@ -267,16 +262,11 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
};
Containers::StaticArray<2, efsw::WatchID> _watchIDs;
enum class Framelimit: UnsignedByte {
Vsync,
HalfVsync,
FpsCap
} _framelimit{Framelimit::Vsync};
UnsignedInt _fpsCap{60};
int _swapInterval = 1;
float _fpsCap = 60.0f;
bool _skipDisclaimer{false};
bool _checkUpdatesOnStartup{true};
bool _unsafeMode{false};
bool _updateAvailable{false};
Containers::String _latestVersion;
@ -301,4 +291,7 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
bool _eLaunchersDirty{false};
bool _cheatMode{false};
bool _advancedMode{false};
Timeline _timeline;
};

View File

@ -14,9 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/String.h>
#include <Corrade/Utility/Unicode.h>
#include <SDL_events.h>
#include <SDL_messagebox.h>
#include <fileapi.h>
#include <handleapi.h>
@ -44,7 +48,7 @@ void SaveTool::handleFileAction(efsw::WatchID watch_id,
return;
} // TODO: actually do something when config files will finally be handled
if(!Utility::String::endsWith(filename, _currentProfile->account() + ".sav")) {
if(!Utility::String::endsWith(filename, Utility::format("Profile{}.sav", _currentProfile->account()).data())) {
return;
}
@ -57,7 +61,8 @@ void SaveTool::handleFileAction(efsw::WatchID watch_id,
}
void SaveTool::fileUpdateEvent(SDL_Event& event) {
Containers::String filename{static_cast<char*>(event.user.data1), std::strlen(static_cast<char*>(event.user.data1)), nullptr};
Containers::String filename{static_cast<char*>(event.user.data1),
std::strlen(static_cast<char*>(event.user.data1)), nullptr};
if((event.user.code & StagedUpdate) == StagedUpdate) {
_massManager->refreshStagedMass(filename);
@ -96,7 +101,11 @@ void SaveTool::fileUpdateEvent(SDL_Event& event) {
if(is_current_profile) {
_currentProfile = nullptr;
_uiState = UiState::ProfileManager;
_profileManager->refreshProfiles();
if(!_profileManager->refreshProfiles()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
_profileManager->lastError().data(), window());
exit(EXIT_FAILURE);
}
}
else if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {

View File

@ -19,10 +19,14 @@
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/Unicode.h>
#include <SDL_events.h>
#include <SDL_messagebox.h>
#include <shlobj.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h"
#include "../Logger/Logger.h"
#include "SaveTool.h"
@ -35,8 +39,8 @@ void SaveTool::initEvent(SDL_Event& event) {
ImGui::CloseCurrentPopup();
break;
case ProfileManagerFailure:
Utility::Error{} << "Error initialising ProfileManager:" << _profileManager->lastError();
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising ProfileManager", _profileManager->lastError().data(), window());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error ",
_profileManager->lastError().data(), window());
exit(EXIT_FAILURE);
break;
default:
@ -45,7 +49,7 @@ void SaveTool::initEvent(SDL_Event& event) {
}
void SaveTool::initialiseConfiguration() {
Utility::Debug{} << "Reading configuration file...";
LOG_INFO("Reading configuration file.");
if(_conf.hasValue("cheat_mode"_s)) {
_cheatMode = _conf.value<bool>("cheat_mode"_s);
@ -54,11 +58,11 @@ void SaveTool::initialiseConfiguration() {
_conf.setValue("cheat_mode"_s, _cheatMode);
}
if(_conf.hasValue("unsafe_mode"_s)) {
_unsafeMode = _conf.value<bool>("unsafe_mode"_s);
if(_conf.hasValue("advanced_mode"_s)) {
_advancedMode = _conf.value<bool>("advanced_mode"_s);
}
else {
_conf.setValue("unsafe_mode"_s, _unsafeMode);
_conf.setValue("advanced_mode"_s, _advancedMode);
}
if(_conf.hasValue("startup_update_check"_s)) {
@ -75,38 +79,51 @@ void SaveTool::initialiseConfiguration() {
_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);
}
if(_conf.hasValue("swap_interval"_s)) {
_swapInterval = _conf.value<int>("swap_interval"_s);
}
else {
_conf.setValue("frame_limit"_s, "vsync"_s);
_conf.setValue("swap_interval"_s, 1);
}
if(_conf.hasValue("fps_cap"_s)) {
_fpsCap = _conf.value<float>("fps_cap");
}
else {
_conf.setValue("fps_cap", 60.0f);
}
if(_conf.hasValue("frame_limit"_s)) {
std::string frame_limit = _conf.value("frame_limit"_s);
if(frame_limit == "half_vsync"_s) {
_swapInterval = 2;
}
_conf.removeValue("frame_limit"_s);
}
setSwapInterval(_swapInterval);
if(_swapInterval == 0) {
setMinimalLoopPeriod(0);
}
_conf.save();
}
void SaveTool::initialiseGui() {
Utility::Debug{} << "Initialising ImGui...";
LOG_INFO("Initialising Dear ImGui.");
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
const auto size = Vector2{windowSize()}/dpiScaling();
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);
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(reg_font.data()), int(reg_font.size()),
20.0f * Float(framebufferSize().x()) / size.x(), &font_config);
auto icon_font = _rs.getRaw(FONT_ICON_FILE_NAME_FAS);
static const ImWchar icon_range[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
@ -116,11 +133,13 @@ void SaveTool::initialiseGui() {
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);
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(icon_font.data()), int(icon_font.size()),
16.0f * Float(framebufferSize().x()) / size.x(), &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);
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(brand_font.data()), int(brand_font.size()),
16.0f * Float(framebufferSize().x()) / size.x(), &icon_config, brand_range);
auto mono_font = _rs.getRaw("SourceCodePro-Regular.ttf"_s);
ImVector<ImWchar> range;
@ -128,7 +147,8 @@ void SaveTool::initialiseGui() {
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);
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(mono_font.data()), int(mono_font.size()),
18.0f * Float(framebufferSize().x()) / size.x(), &font_config, range.Data);
_imgui = ImGuiIntegration::Context(*ImGui::GetCurrentContext(), windowSize());
@ -142,6 +162,8 @@ void SaveTool::initialiseGui() {
}
void SaveTool::initialiseManager() {
LOG_INFO("Initialising the profile manager.");
SDL_Event event;
SDL_zero(event);
event.type = _initEventId;
@ -158,7 +180,7 @@ void SaveTool::initialiseManager() {
}
auto SaveTool::initialiseToolDirectories() -> bool {
Utility::Debug{} << "Initialising Save Tool directories...";
LOG_INFO("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");
@ -168,17 +190,17 @@ auto SaveTool::initialiseToolDirectories() -> bool {
//_stylesDir = Utility::Directory::join(_armouryDir, "styles");
if(!Utility::Path::exists(_backupsDir)) {
Utility::Debug{} << "Backups directory not found, creating...";
LOG_WARNING("Backups directory not found, creating...");
if(!Utility::Path::make(_backupsDir)) {
Utility::Error{} << (_lastError = "Couldn't create the backups directory.");
LOG_ERROR(_lastError = "Couldn't create the backups directory.");
return false;
}
}
if(!Utility::Path::exists(_stagingDir)) {
Utility::Debug{} << "Staging directory not found, creating...";
LOG_WARNING("Staging directory not found, creating...");
if(!Utility::Path::make(_stagingDir)) {
Utility::Error{} << (_lastError = "Couldn't create the backups directory.");
LOG_ERROR(_lastError = "Couldn't create the staging directory.");
return false;
}
}
@ -219,20 +241,21 @@ auto SaveTool::initialiseToolDirectories() -> bool {
}
auto SaveTool::findGameDataDirectory() -> bool {
Utility::Debug{} << "Searching for the game's save directory...";
LOG_INFO("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);
_lastError = Utility::format("SHGetKnownFolderPath() failed with error code {}.", GetLastError());
LOG_ERROR(_lastError);
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);
LOG_ERROR(_lastError = _gameDataDir + " wasn't found. Make sure to play the game at least once."_s);
return false;
}
@ -244,10 +267,12 @@ auto SaveTool::findGameDataDirectory() -> bool {
}
void SaveTool::initialiseMassManager() {
LOG_INFO("Initialising the M.A.S.S. manager.");
_massManager.emplace(_saveDir, _currentProfile->account(), _currentProfile->isDemo(), _stagingDir);
}
void SaveTool::initialiseFileWatcher() {
LOG_INFO("Initialising the file watcher.");
_fileWatcher.emplace();
_watchIDs[SaveDir] = _fileWatcher->addWatch(_saveDir, this, false);
_watchIDs[StagingDir] = _fileWatcher->addWatch(_stagingDir, this, false);

View File

@ -14,20 +14,19 @@
// 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 "SaveTool.h"
#include <algorithm>
#include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/String.h>
#include <SDL_messagebox.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../Maps/LastMissionId.h"
#include "../Maps/StoryProgress.h"
#include "SaveTool.h"
void SaveTool::drawManager() {
ImGui::SetNextWindowPos({0.0f, ImGui::GetItemRectSize().y}, ImGuiCond_Always);
ImGui::SetNextWindowSize({Float(windowSize().x()), Float(windowSize().y()) - ImGui::GetItemRectSize().y},
@ -52,7 +51,7 @@ void SaveTool::drawManager() {
}
if(ImGui::BeginChild("##ProfileInfo",
{ImGui::GetContentRegionAvailWidth() * 0.60f, 0.0f},
{ImGui::GetContentRegionAvail().x * 0.60f, 0.0f},
true, ImGuiWindowFlags_MenuBar))
{
if(ImGui::BeginMenuBar()) {
@ -146,8 +145,8 @@ auto SaveTool::drawRenamePopup(Containers::ArrayView<char> name_view) -> bool {
callback, nullptr);
ImGui::SameLine();
GameState game_state = _gameState;
if((!_unsafeMode && game_state != GameState::NotRunning) ||
!(len >= 6 && len <= 32) ||
if(game_state != GameState::NotRunning ||
!(len >= 6 && len <= 32) ||
!(name_view[0] != ' ' && name_view[len - 1] != ' '))
{
ImGui::BeginDisabled();
@ -158,8 +157,8 @@ auto SaveTool::drawRenamePopup(Containers::ArrayView<char> name_view) -> bool {
ImGui::CloseCurrentPopup();
}
if((!_unsafeMode && game_state != GameState::NotRunning) ||
!(len >= 6 && len <= 32) ||
if(game_state != GameState::NotRunning ||
!(len >= 6 && len <= 32) ||
!(name_view[0] != ' ' && name_view[len - 1] != ' '))
{
ImGui::EndDisabled();
@ -204,7 +203,7 @@ void SaveTool::drawGeneralInfo() {
ImGui::Text("Last mission: 0x%x", _currentProfile->lastMissionId());
}
drawTooltip("This is the last mission selected in the mission selection screen, not the last mission played.",
windowSize().x() * 0.35f);
float(windowSize().x()) * 0.35f);
const Float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
ImGui::Dummy({ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y - footer_height_to_reserve});
@ -249,7 +248,7 @@ void SaveTool::drawGeneralInfo() {
}
drawTooltip("Story progress directly affects unlocked levels.");
if(ImGui::BeginPopup("StoryProgressMenu")) {
if(!_unsafeMode && _gameState != GameState::NotRunning) {
if(_gameState != GameState::NotRunning) {
ImGui::CloseCurrentPopup();
}
for(const auto& sp : story_progress) {
@ -310,7 +309,9 @@ void SaveTool::drawResearchInventory() {
drawMaterialRow("Asterite", 5,
[this]{ return _currentProfile->asterite(); },
[this](Int amount){ return _currentProfile->setAsterite(amount); });
drawUnavailableMaterialRow("Hallite fragma", 6);
drawMaterialRow("Hallite fragma", 6,
[this]{ return _currentProfile->halliteFragma(); },
[this](Int amount){ return _currentProfile->setHalliteFragma(amount); });
drawUnavailableMaterialRow("Unnoctinium", 7);
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
@ -332,7 +333,9 @@ void SaveTool::drawResearchInventory() {
drawMaterialRow("Synthesized N", 5,
[this]{ return _currentProfile->synthesisedN(); },
[this](Int amount){ return _currentProfile->setSynthesisedN(amount); });
drawUnavailableMaterialRow("Nanoc", 6);
drawMaterialRow("Nanoc", 6,
[this]{ return _currentProfile->nanoc(); },
[this](Int amount){ return _currentProfile->setNanoc(amount); });
drawUnavailableMaterialRow("Abyssillite", 7);
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
@ -354,7 +357,9 @@ void SaveTool::drawResearchInventory() {
drawMaterialRow("Alterene", 5,
[this]{ return _currentProfile->alterene(); },
[this](Int amount){ return _currentProfile->setAlterene(amount); });
drawUnavailableMaterialRow("Cosmium", 6);
drawMaterialRow("Cosmium", 6,
[this]{ return _currentProfile->cosmium(); },
[this](Int amount){ return _currentProfile->setCosmium(amount); });
drawUnavailableMaterialRow("Purified quarkium", 7);
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
@ -376,7 +381,9 @@ void SaveTool::drawResearchInventory() {
drawMaterialRow("Carbonized skin", 5,
[this]{ return _currentProfile->carbonisedSkin(); },
[this](Int amount){ return _currentProfile->setCarbonisedSkin(amount); });
drawUnavailableMaterialRow("Isolated void particle", 6);
drawMaterialRow("Isolated void particle", 6,
[this]{ return _currentProfile->isolatedVoidParticle(); },
[this](Int amount){ return _currentProfile->setIsolatedVoidParticle(amount); });
drawUnavailableMaterialRow("Weaponised physiology", 7);
ImGui::EndTable();
@ -473,7 +480,7 @@ void SaveTool::drawMassManager() {
ImGui::EndDragDropSource();
}
if((_unsafeMode || _gameState == GameState::NotRunning) && ImGui::BeginDragDropTarget()) {
if(_gameState == GameState::NotRunning && ImGui::BeginDragDropTarget()) {
if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("StagedMass")) {
if(payload->DataSize != sizeof(Containers::String)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error",
@ -580,7 +587,7 @@ void SaveTool::drawMassManager() {
ImGui::TableSetColumnIndex(0);
Containers::String staged_formatted = Utility::format("{} ({})", pair.second, pair.first);
ImGui::Selectable(staged_formatted.data());
if((ImGui::CalcTextSize(staged_formatted.data()).x + ImGui::GetStyle().FramePadding.x) > ImGui::GetContentRegionAvailWidth()) {
if((ImGui::CalcTextSize(staged_formatted.data()).x + ImGui::GetStyle().FramePadding.x) > ImGui::GetContentRegionAvail().x) {
drawTooltip(staged_formatted.data());
}
if(ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) {
@ -644,7 +651,7 @@ auto SaveTool::drawDeleteMassPopup(int mass_index) -> ImGuiID {
return 0;
}
ImGui::PushTextWrapPos(windowSize().x() * 0.40f);
ImGui::PushTextWrapPos(float(windowSize().x()) * 0.40f);
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);
@ -688,7 +695,7 @@ auto SaveTool::drawDeleteStagedMassPopup(Containers::StringView filename) -> ImG
return ImGui::GetID("Confirmation##DeleteStagedMassConfirmation");
}
ImGui::PushTextWrapPos(windowSize().x() * 0.40f);
ImGui::PushTextWrapPos(float(windowSize().x()) * 0.40f);
ImGui::Text("Are you sure you want to delete the staged M.A.S.S. named %s ? This operation is irreversible.",
_massManager->stagedMasses().at(filename).data());
ImGui::PopTextWrapPos();

View File

@ -15,15 +15,15 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Format.h>
#include <Magnum/ImGuiIntegration/Integration.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../Maps/Accessories.h"
#define STYLENAMES_DEFINITION
#include "../Maps/StyleNames.h"
#include "../FontAwesome/IconsFontAwesome5.h"
#include "SaveTool.h"
void SaveTool::drawMassViewer() {
@ -90,7 +90,7 @@ void SaveTool::drawMassViewer() {
_selectedWeaponPart = 0;
_selectedWeaponDecal = 0;
_selectedWeaponAccessory = 0;
};
}
ImGui::EndTable();
}
@ -166,7 +166,7 @@ void SaveTool::drawGlobalStyles() {
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);
ImGui::PushID(int(i));
DCSResult result;
result = drawCustomStyle(_currentMass->globalStyles()[i]);
switch(result) {
@ -393,9 +393,12 @@ auto SaveTool::drawCustomStyle(CustomStyle& style) -> DCSResult {
void SaveTool::drawDecalEditor(Decal& decal) {
ImGui::Text("ID: %i", decal.id);
if(ImGui::BeginTable("##DecalTable", 2, ImGuiTableFlags_BordersInnerV)) {
if(ImGui::BeginTable("##DecalTable", _advancedMode ? 2 : 1, ImGuiTableFlags_BordersInnerV)) {
ImGui::TableSetupColumn("##Normal", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##Advanced", ImGuiTableColumnFlags_WidthStretch);
if(_advancedMode) {
ImGui::TableSetupColumn("##Advanced", ImGuiTableColumnFlags_WidthStretch);
}
ImGui::TableNextRow();
@ -439,52 +442,53 @@ void SaveTool::drawDecalEditor(Decal& decal) {
ImGui::Checkbox("##Wrap", &decal.wrap);
ImGui::EndGroup();
if(_advancedMode) {
ImGui::TableNextColumn();
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::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::BeginGroup();
drawAlignedText("Position:");
drawAlignedText("U axis:");
drawAlignedText("V axis:");
ImGui::EndGroup();
ImGui::SameLine();
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::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("##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::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();
}
@ -495,7 +499,7 @@ void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<C
ImGui::TextUnformatted("Accessory: <none>");
}
else if(accessories.find(accessory.id) != accessories.cend()) {
ImGui::Text("Accessory #%i - %s", accessory.id, accessories.at(accessory.id).data());
ImGui::Text("Accessory #%.4i - %s", accessory.id, accessories.at(accessory.id).name.data());
}
else {
ImGui::Text("Accessory #%i", accessory.id);
@ -505,12 +509,10 @@ void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<C
ImGui::SameLine();
static Int tab = 0;
static Containers::Optional<AccessorySize> size = Containers::NullOpt;
if(ImGui::SmallButton("Change")) {
ImGui::OpenPopup("##AccessoryPopup");
if(accessory.id < 1000) {
tab = 0;
}
else if(accessory.id >= 3000) {
if(accessory.id >= 3000) {
tab = 3;
}
else if(accessory.id >= 2000) {
@ -519,9 +521,18 @@ void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<C
else if(accessory.id >= 1000) {
tab = 1;
}
else {
tab = 0;
}
}
if(ImGui::BeginPopup("##AccessoryPopup")) {
Float selectable_width = 90.0f;
static const char* size_labels[] = {
"S",
"M",
"L",
"XL"
};
static const 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})) {
@ -549,10 +560,56 @@ void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<C
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, {0.5f, 0.0f});
if(ImGui::Selectable("All", !size, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
size = Containers::NullOpt;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("S", size && *size == AccessorySize::S, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(!size) {
size.emplace();
}
*size = AccessorySize::S;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("M", size && *size == AccessorySize::M, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(!size) {
size.emplace();
}
*size = AccessorySize::M;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("L", size && *size == AccessorySize::L, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(!size) {
size.emplace();
}
*size = AccessorySize::L;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("XL", size && *size == AccessorySize::XL, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(!size) {
size.emplace();
}
*size = AccessorySize::XL;
}
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)) {
if(acc.first >= tab * 1000 && acc.first < ((tab + 1) * 1000) && (!size || *size == acc.second.size)) {
if(ImGui::Selectable(Utility::format("#{:.4d} - {} ({})", acc.first, acc.second.name, size_labels[acc.second.size]).data(),
acc.first == accessory.id))
{
accessory.id = acc.first;
accessory.attachIndex = 0;
}
@ -578,9 +635,13 @@ void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<C
ImGui::BeginGroup();
drawAlignedText("Styles:");
drawAlignedText("Base position:");
if(_advancedMode) {
drawAlignedText("Base position:");
}
drawAlignedText("Position offset:");
drawAlignedText("Base rotation:");
if(_advancedMode) {
drawAlignedText("Base rotation:");
}
drawAlignedText("Rotation offset:");
drawAlignedText("Scale:");
ImGui::EndGroup();
@ -611,15 +672,17 @@ void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<C
}
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();
if(_advancedMode) {
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");
@ -633,15 +696,17 @@ void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<C
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();
if(_advancedMode) {
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");

View File

@ -43,7 +43,7 @@ void SaveTool::drawArmour() {
};
for(UnsignedInt i = 0; i < _currentMass->armourParts().size(); i++) {
ImGui::PushID(i);
ImGui::PushID(int(i));
auto& part = _currentMass->armourParts()[i];
@ -61,7 +61,7 @@ void SaveTool::drawArmour() {
if(ImGui::CollapsingHeader(header)) {
ImGui::BeginGroup();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth() * 0.491f);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * 0.491f);
if(ImGui::BeginListBox("##ChangePart")) {
if(std::strncmp("Neck", slot_labels[UnsignedInt(part.slot)].data(), 4) != 0) {
for(auto& set : armour_sets) {
@ -103,7 +103,7 @@ void SaveTool::drawArmour() {
ImGui::PushID(j);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth() - 2.0f);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 2.0f);
if(ImGui::BeginCombo("##Style", getStyleName(part.styles[j], _currentMass->armourCustomStyles()).data())) {
for(const auto& style : style_names) {
if(ImGui::Selectable(getStyleName(style.first, _currentMass->armourCustomStyles()).data(), part.styles[j] == style.first)) {
@ -126,7 +126,7 @@ void SaveTool::drawArmour() {
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);
ImGui::RadioButton(std::to_string(j + 1).c_str(), &_selectedArmourDecals[i], int(j));
}
drawDecalEditor(part.decals[_selectedArmourDecals[i]]);
@ -141,7 +141,7 @@ void SaveTool::drawArmour() {
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);
ImGui::RadioButton(std::string{char(65 + j)}.c_str(), &_selectedArmourAccessories[i], int(j));
}
drawAccessoryEditor(part.accessories[_selectedArmourAccessories[i]], _currentMass->armourCustomStyles());
@ -301,7 +301,7 @@ void SaveTool::drawCustomArmourStyles() {
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);
ImGui::PushID(int(i));
DCSResult result;
result = drawCustomStyle(_currentMass->armourCustomStyles()[i]);
switch(result) {
@ -310,7 +310,7 @@ void SaveTool::drawCustomArmourStyles() {
break;
case DCS_Save:
_modifiedBySaveTool = true;
if(_currentMass->writeArmourCustomStyle(i)) {
if(!_currentMass->writeArmourCustomStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}

View File

@ -32,7 +32,7 @@ void SaveTool::drawFrameInfo() {
ImGui::BeginGroup();
if(ImGui::BeginChild("##JointSliders", {(ImGui::GetContentRegionAvailWidth() / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 300.0f}, true, ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginChild("##JointSliders", {(ImGui::GetContentRegionAvail().x / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 300.0f}, true, ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("Joint sliders");
@ -43,7 +43,7 @@ void SaveTool::drawFrameInfo() {
}
ImGui::EndChild();
if(ImGui::BeginChild("##FrameStyles", {(ImGui::GetContentRegionAvailWidth() / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 0.0f}, true, ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginChild("##FrameStyles", {(ImGui::GetContentRegionAvail().x / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 0.0f}, true, ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("Frame styles");
@ -291,7 +291,7 @@ void SaveTool::drawCustomFrameStyles() {
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);
ImGui::PushID(int(i));
DCSResult result;
result = drawCustomStyle(_currentMass->frameCustomStyles()[i]);
switch(result) {

View File

@ -33,7 +33,7 @@ void SaveTool::drawWeapons() {
if(!ImGui::BeginTable("##WeaponsList", 1,
ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersOuter|ImGuiTableFlags_BordersInnerH,
{ImGui::GetContentRegionAvailWidth() * 0.2f, -footer_height_to_reserve}))
{ImGui::GetContentRegionAvail().x * 0.2f, -footer_height_to_reserve}))
{
ImGui::EndGroup();
return;
@ -269,7 +269,7 @@ void SaveTool::drawWeaponCategory(Containers::StringView name, Containers::Array
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(i);
ImGui::PushID(int(i));
if(ImGui::Selectable(weapon.name.data(), _currentWeapon == &weapon)) {
_currentWeapon = &weapon;
@ -309,7 +309,7 @@ void SaveTool::drawWeaponCategory(Containers::StringView name, Containers::Array
ImGui::PopID();
if(weapon.attached == true) {
if(weapon.attached) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, 0x1F008CFFu);
}
}
@ -454,7 +454,9 @@ void SaveTool::drawWeaponEditor(Weapon& weapon) {
return nullptr;
}();
CORRADE_INTERNAL_ASSERT(map);
if(!map) {
return;
}
if(map->find(part.id) != map->cend()) {
ImGui::TextUnformatted(map->at(part.id).data());
@ -477,6 +479,9 @@ void SaveTool::drawWeaponEditor(Weapon& weapon) {
if(ImGui::Selectable(mapped_part.second.data(), mapped_part.first == part.id)) {
part.id = mapped_part.first;
}
if(mapped_part.first == part.id) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndListBox();
@ -531,7 +536,7 @@ void SaveTool::drawWeaponEditor(Weapon& weapon) {
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);
ImGui::RadioButton(std::to_string(i + 1).c_str(), &_selectedWeaponDecal, int(i));
}
drawDecalEditor(part.decals[_selectedWeaponDecal]);
@ -546,7 +551,7 @@ void SaveTool::drawWeaponEditor(Weapon& weapon) {
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);
ImGui::RadioButton(std::string{char(65 + i)}.c_str(), &_selectedWeaponAccessory, int(i));
}
drawAccessoryEditor(part.accessories[_selectedWeaponAccessory], weapon.customStyles);

View File

@ -14,12 +14,14 @@
// 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 "SaveTool.h"
#include <Magnum/ImGuiIntegration/Integration.h>
#include <SDL_messagebox.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "SaveTool.h"
extern const ImVec2 center_pivot;
void SaveTool::drawProfileManager() {
@ -50,7 +52,8 @@ void SaveTool::drawProfileManager() {
ImGui::TableSetColumnIndex(1);
if(ImGui::SmallButton("Refresh")) {
if(!_profileManager->refreshProfiles()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error in ProfileManager", _profileManager->lastError().data(), window());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
_profileManager->lastError().data(), window());
exit(EXIT_FAILURE);
}
}
@ -82,7 +85,7 @@ void SaveTool::drawProfileManager() {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::PushID(i);
ImGui::PushID(int(i));
if(ImGui::Selectable(profile.companyName().data(), false,
ImGuiSelectableFlags_SpanAllColumns|ImGuiSelectableFlags_AllowItemOverlap))
{
@ -93,9 +96,7 @@ void SaveTool::drawProfileManager() {
}
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s%s",
profile.isDemo() ? "Demo" : "Full",
profile.isLegacy() ? " (legacy)" : "");
ImGui::TextUnformatted(profile.isDemo() ? "Demo" : "Full");
ImGui::TableSetColumnIndex(2);
if(ImGui::SmallButton(ICON_FA_FILE_ARCHIVE)) {
@ -137,7 +138,7 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID {
if(ImGui::BeginPopupModal("Restore backup", nullptr,
ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::PushTextWrapPos(windowSize().x() * 0.40f);
ImGui::PushTextWrapPos(float(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()[backup_index].company.data(),
_profileManager->backups()[backup_index].timestamp.year,
@ -159,7 +160,11 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID {
if(!_profileManager->restoreBackup(backup_index)) {
_queue.addToast(Toast::Type::Error, _profileManager->lastError());
}
_profileManager->refreshProfiles();
if(!_profileManager->refreshProfiles()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
_profileManager->lastError().data(), window());
exit(EXIT_FAILURE);
}
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
@ -176,7 +181,7 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID {
if(ImGui::BeginPopupModal("Delete backup", nullptr,
ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::PushTextWrapPos(windowSize().x() * 0.40f);
ImGui::PushTextWrapPos(float(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()[backup_index].company.data(),
_profileManager->backups()[backup_index].timestamp.year,
@ -230,7 +235,7 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID {
ImGui::EndTable();
}
if(_profileManager->backups().empty()) {
if(_profileManager->backups().isEmpty()) {
ImGui::TextDisabled("No backups were found.");
}
else if(ImGui::BeginTable("##Backups", 4,
@ -276,12 +281,10 @@ auto SaveTool::drawBackupListPopup() -> ImGuiID {
backup.timestamp.second);
ImGui::TableSetColumnIndex(2);
ImGui::Text("%s%s",
backup.type == ProfileType::Demo ? "Demo" : "Full",
backup.version == ProfileVersion::Legacy ? " (legacy)" : "");
ImGui::TextUnformatted(backup.type == ProfileType::Demo ? "Demo" : "Full");
ImGui::TableSetColumnIndex(3);
ImGui::PushID(i);
ImGui::PushID(int(i));
if(ImGui::SmallButton(ICON_FA_UNDO)) {
backup_index = i;
ImGui::OpenPopup(restore_backup_popup_id);
@ -338,12 +341,18 @@ auto SaveTool::drawBackupProfilePopup(std::size_t profile_index) -> ImGuiID {
ImGui::TableSetColumnIndex(1);
if(ImGui::Button("Yes")) {
_profileManager->backupProfile(profile_index, true);
if(!_profileManager->backupProfile(profile_index, true)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
_profileManager->lastError().data(), window());
}
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if(ImGui::Button("No", ImGui::GetItemRectSize())) {
_profileManager->backupProfile(profile_index, false);
if(!_profileManager->backupProfile(profile_index, false)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
_profileManager->lastError().data(), window());
}
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
@ -371,7 +380,7 @@ auto SaveTool::drawDeleteProfilePopup(std::size_t profile_index) -> ImGuiID {
delete_builds = false;
}
ImGui::PushTextWrapPos(windowSize().x() * 0.40f);
ImGui::PushTextWrapPos(float(windowSize().x()) * 0.40f);
ImGui::Text("Are you sure you want to delete the %s profile named %s ? This operation is irreversible.",
_profileManager->profiles()[profile_index].isDemo() ? "demo" : "full game",
_profileManager->profiles()[profile_index].companyName().data());

View File

@ -16,8 +16,12 @@
#include <Corrade/Utility/Format.h>
#include <SDL_events.h>
#include <curl/curl.h>
#include "../Logger/Logger.h"
#include "SaveTool.h"
void SaveTool::updateCheckEvent(SDL_Event& event) {
@ -25,20 +29,26 @@ void SaveTool::updateCheckEvent(SDL_Event& event) {
if(event.user.code == CurlInitFailed) {
_queue.addToast(Toast::Type::Error, "Couldn't initialise libcurl. Update check aborted."_s);
LOG_ERROR("Couldn't initialise libcurl. Update check aborted.");
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});
_queue.addToast(Toast::Type::Error, static_cast<char*>(event.user.data1),
std::chrono::milliseconds{5000});
LOG_ERROR_FORMAT("{}: {}", static_cast<char*>(event.user.data1), static_cast<char*>(event.user.data2));
return;
}
else if(event.user.code == CurlTimeout) {
_queue.addToast(Toast::Type::Error, "The request timed out."_s);
LOG_ERROR("The request timed out.");
return;
}
else if(event.user.code != 200) {
_queue.addToast(Toast::Type::Error, Utility::format("The request failed with error code {}", event.user.code));
_queue.addToast(Toast::Type::Error,
Utility::format("The request failed with error code {}.", event.user.code));
LOG_ERROR_FORMAT("The request failed with error code {}.", event.user.code);
return;
}
@ -69,11 +79,11 @@ void SaveTool::updateCheckEvent(SDL_Event& event) {
bool prerelease = false;
bool operator==(const Version& other) const {
return fullVersion == other.fullVersion;
return fullVersion == other.fullVersion && prerelease == other.prerelease;
}
bool operator>(const Version& other) const {
if((fullVersion > other.fullVersion) ||
(fullVersion == other.fullVersion && prerelease == false && other.prerelease == true))
(fullVersion == other.fullVersion && !prerelease && other.prerelease))
{
return true;
}
@ -81,31 +91,35 @@ void SaveTool::updateCheckEvent(SDL_Event& event) {
return false;
}
}
operator Containers::String() const {
explicit 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');
auto str = static_cast<char*>(event.user.data1);
Containers::String response{str, strlen(str), nullptr};
auto components = response.splitOnAnyWithoutEmptyParts("\r\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,
_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());
_latestVersion = Containers::String{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)) {
else if(latest_ver == current_ver || (current_ver > latest_ver && current_ver.prerelease)) {
_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);
else if(current_ver > latest_ver && !current_ver.prerelease) {
_queue.addToast(Toast::Type::Warning,
"Your version is more recent than the latest one in the repo. How???"_s);
}
}
@ -135,7 +149,7 @@ void SaveTool::checkForUpdates() {
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_TCP_KEEPALIVE, 0L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000L);
auto code = curl_easy_perform(curl);

View File

@ -17,7 +17,6 @@
#include "SaveTool.h"
#include <Corrade/version.h>
#include <Corrade/Containers/StringView.h>
#include <Magnum/version.h>
#include <Magnum/versionIntegration.h>
@ -25,6 +24,8 @@
#include <zipconf.h>
#include <curl/curlver.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h"
@ -32,7 +33,7 @@ extern const ImVec2 center_pivot;
void SaveTool::drawAbout() {
ImGui::SetNextWindowPos(ImVec2{Vector2{windowSize() / 2.0f}}, ImGuiCond_Always, center_pivot);
ImGui::SetNextWindowSize({windowSize().x() * 0.8f, windowSize().y() * 0.75f}, ImGuiCond_Always);
ImGui::SetNextWindowSize({float(windowSize().x()) * 0.8f, float(windowSize().y()) * 0.75f}, ImGuiCond_Always);
ImGui::OpenPopup("About##AboutPopup");
if(!ImGui::BeginPopupModal("About##AboutPopup", &_aboutPopup,
@ -84,7 +85,7 @@ void SaveTool::drawAbout() {
if(ImGui::CollapsingHeader("Licence")) {
ImGui::TextWrapped("This application is made available under the terms of the GNU General Public License, version 3, the full text of which is available below:");
if(ImGui::BeginChild("##GPL", {0.0f, windowSize().y() * 0.3f}, true)) {
if(ImGui::BeginChild("##GPL", {0.0f, float(windowSize().y()) * 0.3f}, true)) {
static auto licence = _rs.getRaw("COPYING");
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
ImGui::TextEx(licence.data(), licence.data() + licence.size(), ImGuiTextFlags_None);
@ -114,7 +115,7 @@ void SaveTool::drawAbout() {
ImGui::TextUnformatted("Licence: MIT");
static auto corrade_licence = _rs.getRaw("COPYING.Corrade");
if(ImGui::BeginChild("##CorradeLicence", {0.0f, windowSize().y() * 0.3f}, true)) {
if(ImGui::BeginChild("##CorradeLicence", {0.0f, float(windowSize().y()) * 0.3f}, true)) {
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
ImGui::TextEx(corrade_licence.data(), corrade_licence.data() + corrade_licence.size(), ImGuiTextFlags_None);
ImGui::PopFont();
@ -142,7 +143,7 @@ void SaveTool::drawAbout() {
ImGui::TextUnformatted("Licence: MIT");
static auto magnum_licence = _rs.getRaw("COPYING.Magnum");
if(ImGui::BeginChild("##MagnumLicence", {0.0f, windowSize().y() * 0.3f}, true)) {
if(ImGui::BeginChild("##MagnumLicence", {0.0f, float(windowSize().y()) * 0.3f}, true)) {
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
ImGui::TextEx(magnum_licence.data(), magnum_licence.data() + magnum_licence.size(), ImGuiTextFlags_None);
ImGui::PopFont();
@ -168,7 +169,7 @@ void SaveTool::drawAbout() {
ImGui::TextUnformatted("Licence: MIT");
static auto imgui_licence = _rs.getRaw("LICENSE.ImGui");
if(ImGui::BeginChild("##ImGuiLicence", {0.0f, windowSize().y() * 0.3f}, true)) {
if(ImGui::BeginChild("##ImGuiLicence", {0.0f, float(windowSize().y()) * 0.3f}, true)) {
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
ImGui::TextEx(imgui_licence.data(), imgui_licence.data() + imgui_licence.size(), ImGuiTextFlags_None);
ImGui::PopFont();
@ -194,7 +195,7 @@ void SaveTool::drawAbout() {
ImGui::TextUnformatted("Licence: zlib");
static auto sdl_licence = _rs.getRaw("LICENSE.SDL");
if(ImGui::BeginChild("##SDLLicence", {0.0f, windowSize().y() * 0.3f}, true)) {
if(ImGui::BeginChild("##SDLLicence", {0.0f, float(windowSize().y()) * 0.3f}, true)) {
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
ImGui::TextEx(sdl_licence.data(), sdl_licence.data() + sdl_licence.size(), ImGuiTextFlags_None);
ImGui::PopFont();
@ -220,7 +221,7 @@ void SaveTool::drawAbout() {
ImGui::TextUnformatted("Licence: 3-clause BSD");
static auto libzip_licence = _rs.getRaw("LICENSE.libzip");
if(ImGui::BeginChild("##libzipLicence", {0.0f, windowSize().y() * 0.3f}, true)) {
if(ImGui::BeginChild("##libzipLicence", {0.0f, float(windowSize().y()) * 0.3f}, true)) {
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
ImGui::TextEx(libzip_licence.data(), libzip_licence.data() + libzip_licence.size(), ImGuiTextFlags_None);
ImGui::PopFont();
@ -245,7 +246,7 @@ void SaveTool::drawAbout() {
ImGui::TextUnformatted("Licence: MIT");
static auto efsw_licence = _rs.getRaw("LICENSE.efsw");
if(ImGui::BeginChild("##efswLicence", {0.0f, windowSize().y() * 0.3f}, true)) {
if(ImGui::BeginChild("##efswLicence", {0.0f, float(windowSize().y()) * 0.3f}, true)) {
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
ImGui::TextEx(efsw_licence.data(), efsw_licence.data() + efsw_licence.size(), ImGuiTextFlags_None);
ImGui::PopFont();
@ -256,6 +257,7 @@ void SaveTool::drawAbout() {
}
if(ImGui::TreeNodeEx("libcurl", ImGuiTreeNodeFlags_SpanAvailWidth)) {
ImGui::Text("Version used: %s", LIBCURL_VERSION);
auto curl_website = "https://curl.se/libcurl";
drawAlignedText(ICON_FA_GLOBE " %s", curl_website);
ImGui::SameLine();
@ -270,7 +272,7 @@ void SaveTool::drawAbout() {
ImGui::TextUnformatted("Licence: MIT/X derivative");
static auto curl_licence = _rs.getRaw("LICENSE.curl");
if(ImGui::BeginChild("##libcurlLicence", {0.0f, windowSize().y() * 0.3f}, true)) {
if(ImGui::BeginChild("##libcurlLicence", {0.0f, float(windowSize().y()) * 0.3f}, true)) {
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
ImGui::TextEx(curl_licence.data(), curl_licence.data() + curl_licence.size(), ImGuiTextFlags_None);
ImGui::PopFont();

View File

@ -14,13 +14,13 @@
// 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 "SaveTool.h"
#include <Corrade/Utility/Path.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h"
#include "SaveTool.h"
void SaveTool::drawMainMenu() {
if(ImGui::BeginMainMenuBar()) {
if(ImGui::BeginMenu("Save Tool##SaveToolMenu")) {
@ -55,58 +55,59 @@ void SaveTool::drawMainMenu() {
ImGui::Separator();
if(ImGui::BeginMenu(ICON_FA_COG " Settings")) {
drawAlignedText("Frame limiter:");
ImGui::BeginGroup();
drawAlignedText("Vertical sync:");
if(_swapInterval == 0) {
drawAlignedText("FPS cap:");
}
ImGui::EndGroup();
ImGui::SameLine();
static UnsignedByte selection = static_cast<UnsignedByte>(_framelimit);
static const char* framelimit_labels[3] = {
"V-sync",
"Half V-sync",
"FPS cap, no V-sync"
ImGui::BeginGroup();
static const char* framelimit_labels[] = {
"Off",
"Every VBLANK",
"Every second VBLANK",
"Every third VBLANK",
};
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
if(ImGui::BeginCombo("##FrameLimit", framelimit_labels[selection])) {
if(ImGui::Selectable(framelimit_labels[0], _framelimit == Framelimit::Vsync)) {
selection = 0;
_framelimit = Framelimit::Vsync;
setSwapInterval(1);
}
if(ImGui::Selectable(framelimit_labels[1], _framelimit == Framelimit::HalfVsync)) {
selection = 1;
_framelimit = Framelimit::HalfVsync;
setSwapInterval(2);
}
if(ImGui::Selectable(framelimit_labels[2], _framelimit == Framelimit::FpsCap)) {
selection = 2;
_framelimit = Framelimit::FpsCap;
setSwapInterval(0);
setMinimalLoopPeriod(1000 / _fpsCap);
ImGui::PushItemWidth(300.0f);
if(ImGui::BeginCombo("##FrameLimit", framelimit_labels[_swapInterval])) {
for(int i = 0; i <= 3; i++) {
if(ImGui::Selectable(framelimit_labels[i], _swapInterval == i)) {
_swapInterval = i;
setSwapInterval(i);
if(i == 0) {
setMinimalLoopPeriod(0);
}
}
}
ImGui::EndCombo();
}
if(_framelimit == Framelimit::FpsCap) {
static constexpr UnsignedInt min_fps = 15;
static constexpr UnsignedInt max_fps = 150;
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
if(ImGui::SliderScalar("##FpsSlider", ImGuiDataType_U32, &_fpsCap, &min_fps, &max_fps, "%u FPS", ImGuiSliderFlags_AlwaysClamp)) {
setMinimalLoopPeriod(1000 / _fpsCap);
}
if(_swapInterval == 0) {
ImGui::SliderFloat("##FpsCapSlider", &_fpsCap, 15.0f, 301.0f,
_fpsCap != 301.0f ? "%.0f" : "Uncapped", ImGuiSliderFlags_AlwaysClamp);
}
ImGui::PopItemWidth();
ImGui::EndGroup();
ImGui::Checkbox("Cheat mode", &_cheatMode);
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
drawHelpMarker("This gives access to save edition features that can be considered cheats.",
Float(windowSize().x()) * 0.4f);
ImGui::Checkbox("Unsafe mode", &_unsafeMode);
ImGui::Checkbox("Advanced mode", &_advancedMode);
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
drawHelpMarker("This allows changing the state of save files in the game's save folder even when the game is running.",
drawHelpMarker("This gives access to editing values that have unknown purposes or are undocumented.",
Float(windowSize().x()) * 0.4f);
ImGui::Checkbox("Check for updates on startup", &_checkUpdatesOnStartup);
@ -155,11 +156,11 @@ void SaveTool::drawMainMenu() {
if(ImGui::BeginMenu(ICON_FA_DISCORD " Discord communities")) {
if(ImGui::MenuItem("Official server")) {
openUri("https://discord.gg/quS7E46");
openUri("https://discord.gg/sekai-project");
}
if(ImGui::MenuItem("Community server")) {
openUri("https://discord.gg/YSSRTRB");
openUri("https://discord.gg/massbuildercommunity");
}
ImGui::EndMenu();
@ -168,7 +169,7 @@ void SaveTool::drawMainMenu() {
ImGui::EndMenu();
}
#ifdef SAVETOOL_DEBUG_BUILD
#ifdef SAVETOOL_DEBUG_BUILD
if(ImGui::BeginMenu("Debug tools")) {
ImGui::MenuItem("ImGui demo window", nullptr, &_demoWindow);
ImGui::MenuItem("ImGui style editor", nullptr, &_styleEditor);
@ -176,7 +177,7 @@ void SaveTool::drawMainMenu() {
ImGui::EndMenu();
}
#endif
#endif
if(ImGui::BeginMenu("Help")) {
if(ImGui::BeginMenu(ICON_FA_KEYBOARD " Keyboard shortcuts")) {

View File

@ -62,7 +62,7 @@ class Toast {
private:
Type _type{Type::Default};
Containers::StringView _message;
Containers::String _message;
std::chrono::milliseconds _timeout;
std::chrono::steady_clock::time_point _creationTime;
Animation::Track<UnsignedInt, Phase> _phaseTrack;

View File

@ -19,14 +19,15 @@
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/String.h>
#include "../Logger/Logger.h"
#include "BinaryReader.h"
BinaryReader::BinaryReader(Containers::StringView filename) {
_file = std::fopen(filename.data(), "rb");
if(!_file) {
Utility::Error{} << "Couldn't open" << filename << "for reading:\n"
<< std::strerror(errno);
LOG_ERROR_FORMAT("Couldn't open {} for reading: {}", filename, std::strerror(errno));
}
}

View File

@ -16,6 +16,8 @@
#include <cstring>
#include "../Logger/Logger.h"
#include "BinaryWriter.h"
using namespace Containers::Literals;
@ -23,8 +25,7 @@ using namespace Containers::Literals;
BinaryWriter::BinaryWriter(Containers::StringView filename) {
_file = std::fopen(filename.data(), "wb");
if(!_file) {
Utility::Error{} << "Couldn't open"_s << filename << "for reading:\n"_s
<< std::strerror(errno);
LOG_ERROR_FORMAT("Couldn't open {} for reading: {}", filename, std::strerror(errno));
}
}
@ -116,7 +117,7 @@ auto BinaryWriter::writeArray(Containers::ArrayView<const char> array) -> bool {
auto BinaryWriter::writeUEString(Containers::StringView str) -> bool {
if(str.size() > UINT32_MAX) {
Utility::Error{} << "BinaryWriter::writeUEString(): string is too big."_s;
LOG_ERROR_FORMAT("String is too big. Expected size() < UINT32_MAX, got {} instead.", str.size());
return false;
}

View File

@ -40,6 +40,8 @@
#include "BinaryReader.h"
#include "BinaryWriter.h"
#include "../Logger/Logger.h"
#include "PropertySerialiser.h"
PropertySerialiser::PropertySerialiser() {
@ -164,7 +166,7 @@ auto PropertySerialiser::deserialise(Containers::String name, Containers::String
prop = serialiser->deserialise(name, type, value_length, reader, *this);
if(!prop) {
!Utility::Error{} << "No prop in" << __func__;
LOG_ERROR("No property.");
return nullptr;
}

View File

@ -19,6 +19,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../PropertySerialiser.h"
#include "../../Logger/Logger.h"
#include "ArrayPropertySerialiser.h"
@ -28,16 +29,19 @@ auto ArrayPropertySerialiser::deserialiseProperty(Containers::StringView name, C
{
Containers::String item_type;
if(!reader.readUEString(item_type)) {
LOG_ERROR_FORMAT("Couldn't read the item type of array property {}.", name);
return nullptr;
}
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in array property {}.", name);
return nullptr;
}
UnsignedInt item_count;
if(!reader.readUnsignedInt(item_count)) {
LOG_ERROR_FORMAT("Couldn't read array property {}'s item count.", name);
return nullptr;
}
@ -53,6 +57,7 @@ auto ArrayPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, U
{
auto array_prop = dynamic_cast<ArrayProperty*>(prop.get());
if(!array_prop) {
LOG_ERROR("The property is not a valid array property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "BoolPropertySerialiser.h"
@ -30,15 +31,18 @@ auto BoolPropertySerialiser::deserialise(Containers::StringView name, Containers
PropertySerialiser& serialiser) -> UnrealPropertyBase::ptr
{
if(value_length != 0) {
LOG_ERROR_FORMAT("Invalid value length for bool property {}. Expected 0, got {} instead.", name, value_length);
return nullptr;
}
Short value;
if(!reader.readShort(value)) {
LOG_ERROR_FORMAT("Couldn't read bool property {}'s value.", name);
return nullptr;
}
if(value > 1 || value < 0) {
LOG_ERROR_FORMAT("Bool property {}'s value is invalid. Expected 1 or 0, got {} instead.", name, value);
return nullptr;
}
@ -52,8 +56,8 @@ auto BoolPropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLo
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto bool_prop = dynamic_cast<BoolProperty*>(prop.get());
if(!bool_prop) {
LOG_ERROR("The property is not a valid bool property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "BytePropertySerialiser.h"
@ -33,16 +34,19 @@ auto BytePropertySerialiser::deserialise(Containers::StringView name, Containers
if(value_length != UnsignedLong(-1)) {
if(!reader.readUEString(prop->enumType)) {
LOG_ERROR_FORMAT("Couldn't read byte property {}'s enum type.", name);
return nullptr;
}
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in byte property {}.", name);
return nullptr;
}
}
if(!reader.readUEString(prop->enumValue)) {
LOG_ERROR("Couldn't read byte property's enum value.");
return nullptr;
}
@ -64,8 +68,8 @@ auto BytePropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLo
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto byte_prop = dynamic_cast<ByteProperty*>(prop.get());
if(!byte_prop) {
LOG_ERROR("The property is not a valid byte property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "ColourPropertySerialiser.h"
@ -28,6 +29,7 @@ auto ColourPropertySerialiser::deserialiseProperty(Containers::StringView name,
if(!reader.readFloat(prop->r) || !reader.readFloat(prop->g) ||
!reader.readFloat(prop->b) || !reader.readFloat(prop->a))
{
LOG_ERROR_FORMAT("Couldn't read colour property {}'s value.", name);
return nullptr;
}
@ -38,8 +40,8 @@ auto ColourPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop,
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto colour_prop = dynamic_cast<ColourStructProperty*>(prop.get());
if(!colour_prop) {
LOG_ERROR("The property is not a valid colour property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "DateTimePropertySerialiser.h"
@ -26,6 +27,7 @@ auto DateTimePropertySerialiser::deserialiseProperty(Containers::StringView name
auto prop = Containers::pointer<DateTimeStructProperty>();
if(!reader.readUnsignedLong(prop->timestamp)) {
LOG_ERROR_FORMAT("Couldn't read date/time property {}'s value.", name);
return nullptr;
}
@ -36,8 +38,8 @@ auto DateTimePropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto dt_prop = dynamic_cast<DateTimeStructProperty*>(prop.get());
if(!dt_prop) {
LOG_ERROR("The property is not a valid date/time property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "EnumPropertySerialiser.h"
@ -32,15 +33,18 @@ auto EnumPropertySerialiser::deserialise(Containers::StringView name, Containers
auto prop = Containers::pointer<EnumProperty>();
if(!reader.readUEString(prop->enumType)) {
LOG_ERROR_FORMAT("Couldn't read enum property {}'s enum type.", name);
return nullptr;
}
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in enum property {}.", name);
return nullptr;
}
if(!reader.readUEString(prop->value)) {
LOG_ERROR_FORMAT("Couldn't read enum property {}'s enum value.", name);
return nullptr;
}
@ -51,8 +55,8 @@ auto EnumPropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLo
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto enum_prop = dynamic_cast<EnumProperty*>(prop.get());
if(!enum_prop) {
LOG_ERROR("The property is not a valid enum property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "FloatPropertySerialiser.h"
@ -33,10 +34,12 @@ auto FloatPropertySerialiser::deserialise(Containers::StringView name, Container
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in float property {}.", name);
return nullptr;
}
if(!reader.readFloat(prop->value)) {
LOG_ERROR_FORMAT("Couldn't read float property {}'s value.", name);
return nullptr;
}
@ -47,8 +50,8 @@ auto FloatPropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedL
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto float_prop = dynamic_cast<FloatProperty*>(prop.get());
if(!float_prop) {
LOG_ERROR("The property is not a valid float property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "GuidPropertySerialiser.h"
@ -28,7 +29,7 @@ auto GuidPropertySerialiser::deserialiseProperty(Containers::StringView name, Co
auto prop = Containers::pointer<GuidStructProperty>();
if(!reader.readStaticArray(prop->guid)) {
Utility::Error{} << "Couldn't read GUID in"_s << __func__;
LOG_ERROR_FORMAT("Couldn't read GUID property {}'s value.", name);
return nullptr;
}
@ -39,8 +40,8 @@ auto GuidPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, Un
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto guid_prop = dynamic_cast<GuidStructProperty*>(prop.get());
if(!guid_prop) {
LOG_ERROR("The property is not a valid byte property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "IntPropertySerialiser.h"
@ -27,6 +28,7 @@ auto IntPropertySerialiser::deserialiseProperty(Containers::StringView name, Con
if(value_length == UnsignedLong(-1)) {
if(!reader.readInt(prop->value)) {
LOG_ERROR("Couldn't read int property's value.");
return nullptr;
}
@ -36,10 +38,12 @@ auto IntPropertySerialiser::deserialiseProperty(Containers::StringView name, Con
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in int property {}.", name);
return nullptr;
}
if(!reader.readInt(prop->value)) {
LOG_ERROR_FORMAT("Couldn't read int property {}'s value.", name);
return nullptr;
}
@ -52,8 +56,8 @@ auto IntPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, Uns
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto int_prop = dynamic_cast<IntProperty*>(prop.get());
if(!int_prop) {
LOG_ERROR("The property is not a valid int property.");
return false;
}

View File

@ -17,8 +17,8 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../PropertySerialiser.h"
#include "../Types/NoneProperty.h"
#include "../../Logger/Logger.h"
#include "MapPropertySerialiser.h"
@ -31,25 +31,30 @@ auto MapPropertySerialiser::deserialiseProperty(Containers::StringView name, Con
auto prop = Containers::pointer<MapProperty>();
if(!reader.readUEString(prop->keyType)) {
LOG_ERROR_FORMAT("Couldn't read map property {}'s key type.", name);
return nullptr;
}
if(!reader.readUEString(prop->valueType)) {
LOG_ERROR_FORMAT("Couldn't read map property {}'s value type.", name);
return nullptr;
}
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in map property {}.", name);
return nullptr;
}
UnsignedInt null;
if(!reader.readUnsignedInt(null) || null != 0u) {
LOG_ERROR_FORMAT("Couldn't read a null int in map property {}.", name);
return nullptr;
}
UnsignedInt count;
if(!reader.readUnsignedInt(count)) {
LOG_ERROR_FORMAT("Couldn't read map property {}'s item count.", name);
return nullptr;
}
@ -64,10 +69,12 @@ auto MapPropertySerialiser::deserialiseProperty(Containers::StringView name, Con
if(prop->keyType == "IntProperty"_s || prop->keyType == "StrProperty"_s) {
pair.key = serialiser.readItem(reader, prop->keyType, -1, name);
if(pair.key == nullptr) {
LOG_ERROR_FORMAT("Couldn't read a valid key in map property {}.", name);
return nullptr;
}
}
else { // Add other branches depending on key type, should more maps appear in the future.
LOG_ERROR_FORMAT("Key type {} not implemented.", prop->keyType);
return nullptr;
}
@ -105,6 +112,7 @@ auto MapPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, Uns
{
auto map_prop = dynamic_cast<MapProperty*>(prop.get());
if(!map_prop) {
LOG_ERROR("The property is not a valid map property.");
return false;
}
@ -120,17 +128,20 @@ auto MapPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, Uns
UnsignedLong dummy_bytes_written = 0;
for(auto& pair : map_prop->map) {
if(!serialiser.writeItem(pair.key, map_prop->keyType, dummy_bytes_written, writer)) {
LOG_ERROR("Couldn't write a key.");
return false;
}
for(auto& value : pair.values) {
if(map_prop->valueType == "StructProperty"_s) {
if(!serialiser.write(value, dummy_bytes_written, writer)) {
LOG_ERROR("Couldn't write a value.");
return false;
}
}
else {
if(!serialiser.writeItem(value, map_prop->valueType, dummy_bytes_written, writer)) {
LOG_ERROR("Couldn't write a value.");
return false;
}
}

View File

@ -17,9 +17,9 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../PropertySerialiser.h"
#include "../Types/IntProperty.h"
#include "../Types/NoneProperty.h"
#include "../../Logger/Logger.h"
#include "ResourcePropertySerialiser.h"
@ -31,49 +31,46 @@ auto ResourcePropertySerialiser::deserialiseProperty(Containers::StringView name
{
auto prop = Containers::pointer<ResourceItemValue>();
Containers::String str;
if(!reader.readUEString(str) || str != "ID_4_AAE08F17428E229EC7A2209F51081A21"_s) {
auto id_prop = serialiser.read(reader);
if(!id_prop) {
LOG_ERROR("Couldn't read the ID property."_s);
return nullptr;
}
if(!reader.readUEString(str) || str != "IntProperty"_s) {
if((*id_prop->name) != "ID_4_AAE08F17428E229EC7A2209F51081A21"_s ||
id_prop->propertyType != "IntProperty"_s ||
dynamic_cast<IntProperty*>(id_prop.get()) == nullptr)
{
LOG_ERROR("The ID property is invalid."_s);
return nullptr;
}
if(!reader.readUnsignedLong(value_length) || value_length != 4ull) {
prop->id = dynamic_cast<IntProperty*>(id_prop.get())->value;
auto value_prop = serialiser.read(reader);
if(!value_prop) {
LOG_ERROR("Couldn't read the value property."_s);
return nullptr;
}
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
if((*value_prop->name) != "Quantity_3_560F09B5485C365D3041888910019CE3"_s ||
value_prop->propertyType != "IntProperty"_s ||
dynamic_cast<IntProperty*>(value_prop.get()) == nullptr)
{
LOG_ERROR("The value property is invalid."_s);
return nullptr;
}
if(!reader.readInt(prop->id)) {
return nullptr;
}
prop->quantity = dynamic_cast<IntProperty*>(value_prop.get())->value;
if(!reader.readUEString(str) || str != "Quantity_3_560F09B5485C365D3041888910019CE3"_s) {
return nullptr;
}
auto none_prop = serialiser.read(reader);
if(!reader.readUEString(str) || str != "IntProperty"_s) {
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"_s) {
if(!none_prop ||
(*none_prop->name) != "None"_s ||
none_prop->propertyType != "NoneProperty"_s ||
!dynamic_cast<NoneProperty*>(none_prop.get()))
{
LOG_ERROR("Couldn't find a terminating NoneProperty."_s);
return nullptr;
}
@ -85,6 +82,7 @@ auto ResourcePropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop
{
auto res_prop = dynamic_cast<ResourceItemValue*>(prop.get());
if(!res_prop) {
LOG_ERROR("The property is not a valid ResourceItemValue property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "RotatorPropertySerialiser.h"
@ -26,6 +27,7 @@ auto RotatorPropertySerialiser::deserialiseProperty(Containers::StringView name,
auto prop = Containers::pointer<RotatorStructProperty>();
if(!reader.readFloat(prop->x) || !reader.readFloat(prop->y) || !reader.readFloat(prop->z)) {
LOG_ERROR_FORMAT("Couldn't read rotator property {}'s value.", name);
return nullptr;
}
@ -36,8 +38,8 @@ auto RotatorPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop,
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto rotator = dynamic_cast<RotatorStructProperty*>(prop.get());
if(!rotator) {
LOG_ERROR("The property is not a valid rotator property.");
return false;
}

View File

@ -17,6 +17,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../PropertySerialiser.h"
#include "../../Logger/Logger.h"
#include "SetPropertySerialiser.h"
@ -26,21 +27,25 @@ auto SetPropertySerialiser::deserialiseProperty(Containers::StringView name, Con
{
Containers::String item_type;
if(!reader.readUEString(item_type)) {
LOG_ERROR_FORMAT("Couldn't read set property {}'s item type.", name);
return nullptr;
}
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in set property {}.", name);
return nullptr;
}
UnsignedInt four_bytes;
if(!reader.readUnsignedInt(four_bytes) || four_bytes != 0u) {
LOG_ERROR_FORMAT("Couldn't read four null bytes in set property {}.", name);
return nullptr;
}
UnsignedInt item_count;
if(!reader.readUnsignedInt(item_count)) {
LOG_ERROR_FORMAT("Couldn't read set property {}'s item count.", name);
return nullptr;
}
@ -56,6 +61,7 @@ auto SetPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop, Uns
{
auto set_prop = dynamic_cast<SetProperty*>(prop.get());
if(!set_prop) {
LOG_ERROR("The property is not a valid set property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "StringPropertySerialiser.h"
@ -36,11 +37,13 @@ auto StringPropertySerialiser::deserialise(Containers::StringView name, Containe
if(value_length != UnsignedLong(-1)) {
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in string property {}.", name);
return nullptr;
}
}
if(!reader.readUEString(prop->value)) {
LOG_ERROR_FORMAT("Couldn't read string property {}'s value.", name);
return nullptr;
}
@ -53,8 +56,8 @@ auto StringPropertySerialiser::serialise(UnrealPropertyBase::ptr& prop, Unsigned
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto str_prop = dynamic_cast<StringProperty*>(prop.get());
if(!str_prop) {
LOG_ERROR("The property is not a valid string property.");
return false;
}

View File

@ -19,9 +19,9 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../PropertySerialiser.h"
#include "../Types/GenericStructProperty.h"
#include "../Types/NoneProperty.h"
#include "../../Logger/Logger.h"
#include "StructSerialiser.h"
@ -36,16 +36,19 @@ auto StructSerialiser::deserialise(Containers::StringView name, Containers::Stri
{
Containers::String item_type;
if(!reader.readUEString(item_type)) {
LOG_ERROR_FORMAT("Couldn't read struct property {}'s item type.", name);
return nullptr;
}
Containers::StaticArray<16, char> guid{ValueInit};
if(!reader.readStaticArray(guid)) {
LOG_ERROR_FORMAT("Couldn't read struct property {}'s GUID.", name);
return nullptr;
}
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in struct property {}.", name);
return nullptr;
}
@ -67,10 +70,11 @@ auto StructSerialiser::deserialise(Containers::StringView name, Containers::Stri
}
if(!prop) {
LOG_ERROR("Invalid property");
return nullptr;
}
static_cast<StructProperty*>(prop.get())->structGuid = guid;
dynamic_cast<StructProperty*>(prop.get())->structGuid = guid;
arrayAppend(array, std::move(prop));
}
@ -84,20 +88,23 @@ auto StructSerialiser::deserialise(Containers::StringView name, Containers::Stri
{
Containers::String item_type;
if(!reader.readUEString(item_type)) {
LOG_ERROR_FORMAT("Couldn't read struct property {}'s item type.", name);
return nullptr;
}
if(item_type == "None") {
return Containers::pointer<NoneProperty>();
return NoneProperty::ptr{};
}
Containers::StaticArray<16, char> guid{ValueInit};
if(!reader.readStaticArray(guid)) {
LOG_ERROR_FORMAT("Couldn't read struct property {}'s GUID.", name);
return nullptr;
}
char terminator;
if(!reader.readChar(terminator) || terminator != '\0') {
LOG_ERROR_FORMAT("Couldn't read a null byte in byte property {}.", name);
return nullptr;
}
@ -123,6 +130,7 @@ auto StructSerialiser::serialise(Containers::ArrayView<UnrealPropertyBase::ptr>
auto struct_prop = dynamic_cast<StructProperty*>(props.front().get());
if(!struct_prop) {
LOG_ERROR("The property is not a valid struct property.");
return false;
}
@ -135,13 +143,14 @@ auto StructSerialiser::serialise(Containers::ArrayView<UnrealPropertyBase::ptr>
UnsignedLong bytes_written_here = 0;
for(auto& prop : props) {
struct_prop = dynamic_cast<StructProperty*>(prop.get());
if(!struct_prop) {
LOG_ERROR("The property is not a valid struct property.");
return false;
}
if(!serialiser.writeItem(prop, struct_prop->structType, bytes_written_here, writer)) {
if(!writeStructValue(struct_prop, bytes_written_here, writer, serialiser)) {
LOG_ERROR("Couldn't write the struct value.");
return false;
}
}
@ -158,8 +167,8 @@ auto StructSerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& by
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto struct_prop = dynamic_cast<StructProperty*>(prop.get());
if(!struct_prop) {
LOG_ERROR("The property is not a valid struct property.");
return false;
}
@ -171,6 +180,7 @@ auto StructSerialiser::serialise(UnrealPropertyBase::ptr& prop, UnsignedLong& by
UnsignedLong dummy_bytes_written = 0;
UnsignedLong vl_start = writer.arrayPosition();
if(!writeStructValue(struct_prop, dummy_bytes_written, writer, serialiser)) {
LOG_ERROR("Couldn't write the struct value.");
return false;
}
bytes_written += writer.arrayPosition() - vl_start;
@ -206,13 +216,14 @@ auto StructSerialiser::writeStructValue(StructProperty* prop, UnsignedLong& byte
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto struct_prop = dynamic_cast<GenericStructProperty*>(prop);
if(!struct_prop) {
LOG_ERROR("The property is not a valid struct property.");
return false;
}
for(auto& item : struct_prop->properties) {
if(!serialiser.write(item, bytes_written, writer)) {
LOG_ERROR("Couldn't write the struct's data.");
return false;
}
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "Vector2DPropertySerialiser.h"
@ -26,6 +27,7 @@ auto Vector2DPropertySerialiser::deserialiseProperty(Containers::StringView name
auto prop = Containers::pointer<Vector2DStructProperty>();
if(!reader.readFloat(prop->x) || !reader.readFloat(prop->y)) {
LOG_ERROR_FORMAT("Couldn't read 2D vector property {}'s value.", name);
return nullptr;
}
@ -36,8 +38,8 @@ auto Vector2DPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto vector = dynamic_cast<Vector2DStructProperty*>(prop.get());
if(!vector) {
LOG_ERROR("The property is not a valid 2D vector property.");
return false;
}

View File

@ -16,6 +16,7 @@
#include "../BinaryReader.h"
#include "../BinaryWriter.h"
#include "../../Logger/Logger.h"
#include "VectorPropertySerialiser.h"
@ -26,6 +27,7 @@ auto VectorPropertySerialiser::deserialiseProperty(Containers::StringView name,
auto prop = Containers::pointer<VectorStructProperty>();
if(!reader.readFloat(prop->x) || !reader.readFloat(prop->y) || !reader.readFloat(prop->z)) {
LOG_ERROR_FORMAT("Couldn't read vector property {}'s value.", name);
return nullptr;
}
@ -36,8 +38,8 @@ auto VectorPropertySerialiser::serialiseProperty(UnrealPropertyBase::ptr& prop,
BinaryWriter& writer, PropertySerialiser& serialiser) -> bool
{
auto vector = dynamic_cast<VectorStructProperty*>(prop.get());
if(!vector) {
LOG_ERROR("The property is not a valid vector property.");
return false;
}

View File

@ -15,11 +15,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include "BinaryReader.h"
#include "BinaryWriter.h"
#include "../Logger/Logger.h"
#include "UESaveFile.h"
@ -67,10 +67,13 @@ auto UESaveFile::props() -> Containers::ArrayView<UnrealPropertyBase::ptr> {
}
auto UESaveFile::saveToFile() -> bool {
LOG_INFO_FORMAT("Writing to {}.", _filepath);
bool temp_file = _filepath.hasSuffix(".tmp");
if(!temp_file && !Utility::Path::copy(_filepath, _filepath + ".bak"_s)) {
_lastError = "Couldn't create a backup for " + _filepath;
LOG_ERROR(_lastError);
return false;
}
@ -78,6 +81,7 @@ auto UESaveFile::saveToFile() -> bool {
if(!writer.open()) {
_lastError = "Couldn't open the file for saving."_s;
LOG_ERROR(_lastError);
return false;
}
@ -91,13 +95,15 @@ auto UESaveFile::saveToFile() -> bool {
!writer.writeUEString(_engineVersion.buildId))
{
_lastError = "Couldn't write the header."_s;
LOG_ERROR(_lastError);
return false;
}
if(!writer.writeUnsignedInt(_customFormatVersion) ||
!writer.writeUnsignedInt(_customFormatData.size()))
{
_lastError = "Couldn't write the header."_s;
_lastError = "Couldn't write the custom format data."_s;
LOG_ERROR(_lastError);
return false;
}
@ -105,13 +111,15 @@ auto UESaveFile::saveToFile() -> bool {
if(!writer.writeStaticArray(Containers::StaticArrayView<16, const char>{_customFormatData[i].id}) ||
!writer.writeUnsignedInt(_customFormatData[i].value))
{
_lastError = "Couldn't write the header."_s;
_lastError = "Couldn't write the custom format data."_s;
LOG_ERROR(_lastError);
return false;
}
}
if(!writer.writeUEString(_saveType)) {
_lastError = "Couldn't write the header."_s;
_lastError = "Couldn't write the save type."_s;
LOG_ERROR(_lastError);
return false;
}
@ -119,11 +127,13 @@ auto UESaveFile::saveToFile() -> bool {
UnsignedLong bytes_written = 0;
if(!_propSerialiser->write(prop, bytes_written, writer)) {
_lastError = "Couldn't write the property "_s + *prop->name + " to the array."_s;
LOG_ERROR(_lastError);
return false;
}
if(!writer.flushToFile()) {
_lastError = "Couldn't write the property "_s + *prop->name + " to the file."_s;
LOG_ERROR(_lastError);
return false;
}
}
@ -132,13 +142,13 @@ auto UESaveFile::saveToFile() -> bool {
writer.closeFile();
if(!temp_file && !Utility::Path::copy(_filepath + ".tmp"_s, _filepath)) {
if(!Utility::Path::copy(_filepath + ".tmp"_s, _filepath)) {
_lastError = "Couldn't save the file properly.";
LOG_ERROR(_lastError);
return false;
}
else {
Utility::Path::remove(_filepath + ".tmp"_s);
}
Utility::Path::remove(_filepath + ".tmp"_s);
_noReloadAfterSave = true;
@ -146,9 +156,13 @@ auto UESaveFile::saveToFile() -> bool {
}
void UESaveFile::loadData() {
LOG_INFO_FORMAT("Reading data from {}.", _filepath);
_valid = false;
if(!Utility::Path::exists(_filepath)) {
_lastError = "The file couldn't be found.";
LOG_ERROR(_lastError);
return;
}
@ -156,19 +170,22 @@ void UESaveFile::loadData() {
if(!reader.open()) {
_lastError = _filepath + " couldn't be opened."_s;
LOG_ERROR(_lastError);
return;
}
Containers::Array<char> magic;
if(!reader.readArray(magic, 4)) {
_lastError = "Couldn't read magic bytes in "_s + _filepath;
LOG_ERROR(_lastError);
return;
}
Containers::String invalid = _filepath + " isn't a valid UE4 save."_s;
if(std::strncmp(magic.data(), _magicBytes.data(), 4) != 0) {
_lastError = std::move(invalid);
_lastError = Utility::format("Magic bytes don't match. Expected {{{}, {}, {}, {}}}, got {{{}, {}, {}, {}}} instead",
_magicBytes[0], _magicBytes[1], _magicBytes[2], _magicBytes[3],
magic[0], magic[1], magic[2], magic[3]);
LOG_ERROR(_lastError);
return;
}
@ -180,19 +197,22 @@ void UESaveFile::loadData() {
!reader.readUnsignedInt(_engineVersion.build) ||
!reader.readUEString(_engineVersion.buildId))
{
_lastError = std::move(invalid);
_lastError = "Couldn't read version data.";
LOG_ERROR(_lastError);
return;
}
if(!reader.readUnsignedInt(_customFormatVersion)) {
_lastError = std::move(invalid);
_lastError = "Couldn't read the custom format version.";
LOG_ERROR(_lastError);
return;
}
UnsignedInt custom_format_data_size = 0;
if(!reader.readUnsignedInt(custom_format_data_size)) {
_lastError = std::move(invalid);
_lastError = "Couldn't read the custom format data size.";
LOG_ERROR(_lastError);
return;
}
@ -204,15 +224,17 @@ void UESaveFile::loadData() {
if(!reader.readStaticArray(entry.id) ||
!reader.readInt(entry.value))
{
_lastError = std::move(invalid);
_lastError = "Couldn't read the custom format data";
LOG_ERROR(_lastError);
return;
}
arrayAppend(_customFormatData, entry);
_customFormatData[i] = std::move(entry);
}
if(!reader.readUEString(_saveType)) {
_lastError = std::move(invalid);
_lastError = "Couldn't read the save type.";
LOG_ERROR(_lastError);
return;
}
@ -221,8 +243,15 @@ void UESaveFile::loadData() {
arrayAppend(_properties, std::move(prop));
}
if(_properties.isEmpty()) {
_lastError = "No properties were found."_s;
LOG_ERROR(_lastError);
return;
}
if(_properties.back()->name != "None"_s && _properties.back()->propertyType != "NoneProperty"_s) {
_lastError = "Couldn't find a final NoneProperty."_s;
LOG_ERROR(_lastError);
return;
}

View File

@ -14,45 +14,72 @@
// 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 "SaveTool/SaveTool.h"
#include <SDL_messagebox.h>
#ifndef SAVETOOL_DEBUG_BUILD
#include <fstream>
#endif
#include <cpuid.h>
#include <errhandlingapi.h>
#include <synchapi.h>
#include <winerror.h>
#include "Logger/MagnumLogBuffer.h"
#include "SaveTool/SaveTool.h"
int main(int argc, char** argv) {
#ifndef SAVETOOL_DEBUG_BUILD
std::ofstream output{"SaveToolLog.txt", std::ios::trunc|std::ios::out};
MagnumLogBuffer debug_intercept_buffer{EntryType::Info};
MagnumLogBuffer warning_intercept_buffer{EntryType::Warning};
MagnumLogBuffer error_intercept_buffer{EntryType::Error};
std::ostream debug_intercept_stream{&debug_intercept_buffer};
std::ostream warning_intercept_stream{&warning_intercept_buffer};
std::ostream error_intercept_stream{&error_intercept_buffer};
Debug debug_intercept{&debug_intercept_stream};
Warning warning_intercept{&warning_intercept_stream};
Error error_intercept{&error_intercept_stream};
Utility::Debug d{&output};
Utility::Warning w{&output};
Utility::Error e{&output};
#else
Utility::Warning w{Utility::Debug::defaultOutput()};
Utility::Error e{Utility::Debug::defaultOutput()};
#endif
logger().initialise();
Utility::Debug{} << "===M.A.S.S. Builder Save Tool version " SAVETOOL_VERSION "===";
LOG_INFO("Initialising M.A.S.S. Builder Save Tool version " SAVETOOL_VERSION ".");
auto str = setlocale(LC_ALL, ".utf-8");
if(str) {
Containers::StringView locale{str};
LOG_INFO_FORMAT("Current locale: {}", locale);
if(!locale.hasSuffix(".utf8")) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
"Your system doesn't support UTF-8.", nullptr);
return EXIT_FAILURE;
}
}
auto mutex_handle = CreateMutexW(nullptr, 0, L"MassBuilderSaveTool");
if(!mutex_handle) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app",
"There was an error initialising the mutex.",nullptr);
LOG_ERROR_FORMAT("Error initialising the instance checker. GetLastError() returned {}.", GetLastError());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
"There was an error initialising the single-instance checker.",nullptr);
return EXIT_FAILURE;
}
if(GetLastError() == ERROR_ALREADY_EXISTS) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app",
LOG_ERROR("Another instance of the application was detected.");
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
"There can be only one running instance of the application.",nullptr);
return EXIT_FAILURE;
}
Utility::Debug{} << "===Initialising OpenGL renderer===";
std::uint32_t brand[12];
if (!__get_cpuid_max(0x80000004, nullptr)) {
LOG_WARNING("CPUID features not supported. Can't get the current processor's model.");
}
else {
__get_cpuid(0x80000002, brand+0x0, brand+0x1, brand+0x2, brand+0x3);
__get_cpuid(0x80000003, brand+0x4, brand+0x5, brand+0x6, brand+0x7);
__get_cpuid(0x80000004, brand+0x8, brand+0x9, brand+0xa, brand+0xb);
LOG_INFO_FORMAT("Processor: {}", Containers::arrayCast<const char>(brand).data());
}
SaveTool app({argc, argv});
Int result = app.exec();

2
third-party/SDL vendored

@ -1 +1 @@
Subproject commit 857cc7c0c93f003dbfc5fed7a87de907fffb2a1b
Subproject commit 0bfeed061b10ea7dd37c88d9bae1824bad760f3a

2
third-party/corrade vendored

@ -1 +1 @@
Subproject commit dc4f2eac6814b37b5257d295c2838bcde95272aa
Subproject commit 41b2e250b7e1e09c2ed057f079617e3d77393c47

2
third-party/curl vendored

@ -1 +1 @@
Subproject commit 64db5c575d9c5536bd273a890f50777ad1ca7c13
Subproject commit 280cbeee2789749fa9b2267ce7a07813645897de

2
third-party/efsw vendored

@ -1 +1 @@
Subproject commit f5f42f4b9a1c34512b779b2c5544ae42fdf97afa
Subproject commit f3914e475cc8cec461ac05060470c6510ee81246

2
third-party/imgui vendored

@ -1 +1 @@
Subproject commit ddddabdccfdafffd8664fb4e29230dc4f848137e
Subproject commit a8df192df022ed6ac447e7b7ada718c4c4824b41

2
third-party/libzip vendored

@ -1 +1 @@
Subproject commit e7c81b67ab91d5dc54c2238d9f7d9abab1a0a8c3
Subproject commit bdc03ab23b703fcc516436d6ebcbfb6ac4484033

2
third-party/magnum vendored

@ -1 +1 @@
Subproject commit 3fc9028b5451aa95973f104d1ef2a1c0df589e64
Subproject commit a49602987499379e4a2d155472961d99ddfc75ba

@ -1 +1 @@
Subproject commit 323c23f4e8e7cda9a7848c03401a3ba0a1de0bd4
Subproject commit 883bd0cb6e00172b6fca34ff8cc4bc70bc302637