Compare commits

..

No commits in common. "master" and "v1.6.0" have entirely different histories.

15 changed files with 285 additions and 37 deletions

4
.gitmodules vendored
View file

@ -22,6 +22,10 @@
path = third-party/libzip
url = https://github.com/nih-at/libzip
branch = main
[submodule "efsw"]
path = third-party/efsw
url = https://github.com/SpartanJ/efsw
branch = master
[submodule "libcurl"]
path = third-party/curl
url = https://github.com/curl/curl

View file

@ -27,6 +27,7 @@ include(CMakeDependentOption)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_CORRADE_MAGNUM "Use system-wide versions of Corrade and Magnum." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_SDL2 "Use a system-wide version of SDL2." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_LIBZIP "Use a system-wide version of libzip." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_EFSW "Use a system-wide version of EFSW." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_LIBCURL "Use a system-wide version of libcurl." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
if(NOT SAVETOOL_USE_SYSTEM_LIBS OR NOT (SAVETOOL_USE_SYSTEM_CORRADE_MAGNUM AND SAVETOOL_USE_SYSTEM_SDL2 AND SAVETOOL_USE_SYSTEM_LIBZIP AND SAVETOOL_USE_SYSTEM_EFSW AND SAVETOOL_USE_SYSTEM_LIBCURL))
@ -113,6 +114,13 @@ if(NOT SAVETOOL_USE_SYSTEM_LIBZIP)
add_subdirectory(third-party/libzip EXCLUDE_FROM_ALL)
endif(NOT SAVETOOL_USE_SYSTEM_LIBZIP)
if(NOT SAVETOOL_USE_SYSTEM_EFSW)
set(VERBOSE OFF CACHE BOOL "" FORCE)
set(BUILD_TEST_APP OFF CACHE BOOL "" FORCE)
set(EFSW_INSTALL OFF CACHE BOOL "" FORCE)
add_subdirectory(third-party/efsw EXCLUDE_FROM_ALL)
endif(NOT SAVETOOL_USE_SYSTEM_EFSW)
if(NOT SAVETOOL_USE_SYSTEM_LIBCURL)
set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
set(ENABLE_UNICODE ON CACHE BOOL "" FORCE)

View file

@ -92,6 +92,7 @@ Application::Application(const Arguments& arguments):
}
_updateEventId = _initEventId + 1;
_fileEventId = _initEventId + 2;
LOG_INFO("Initialising the timer subsystem.");
if(SDL_InitSubSystem(SDL_INIT_TIMER) != 0) {
@ -225,6 +226,9 @@ Application::anyEvent(SDL_Event& event) {
else if(event.type == _updateEventId) {
updateCheckEvent(event);
}
else if(event.type == _fileEventId) {
fileUpdateEvent(event);
}
}
void

View file

@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <mutex>
#include <string>
#include <thread>
#include <Corrade/Containers/Pointer.h>
@ -34,6 +35,8 @@
#include <imgui.h>
#include <efsw/efsw.hpp>
#include "../Managers/BackupManager.h"
#include "../Managers/MassManager.h"
#include "../Managers/ProfileManager.h"
@ -51,11 +54,17 @@ using namespace Magnum;
namespace mbst {
class Application: public Platform::Sdl2Application {
class Application: public Platform::Sdl2Application, public efsw::FileWatchListener {
public:
explicit Application(const Arguments& arguments);
virtual ~Application();
~Application() override;
void handleFileAction(efsw::WatchID watch_id,
const std::string& dir,
const std::string& filename,
efsw::Action action,
std::string old_filename) override;
private:
// Events
@ -81,11 +90,21 @@ class Application: public Platform::Sdl2Application {
void updateCheckEvent(SDL_Event& event);
enum FileEventType: std::int32_t {
FileAdded = efsw::Action::Add,
FileDeleted = efsw::Action::Delete,
FileModified = efsw::Action::Modified,
FileMoved = efsw::Action::Moved,
StagedUpdate = 1 << 3
};
void fileUpdateEvent(SDL_Event& event);
// Initialisation methods
void initialiseConfiguration();
void initialiseGui();
void initialiseManager();
void initialiseMassManager();
void initialiseFileWatcher();
// GUI-related methods
void drawImGui();
@ -225,6 +244,7 @@ class Application: public Platform::Sdl2Application {
std::uint32_t _initEventId;
std::uint32_t _updateEventId;
std::uint32_t _fileEventId;
Containers::String _lastError;
@ -246,9 +266,17 @@ class Application: public Platform::Sdl2Application {
GameObjects::Weapon* _currentWeapon = nullptr;
Containers::Pointer<efsw::FileWatcher> _fileWatcher;
enum watchID {
SaveDir = 0,
StagingDir = 1
};
Containers::StaticArray<2, efsw::WatchID> _watchIDs;
Containers::Optional<UpdateChecker> _checker{Containers::NullOpt};
std::mutex _checkerMutex;
bool _modifiedBySaveTool = false;
bool _jointsDirty = false;
bool _stylesDirty = false;
bool _eyeFlareDirty = false;

View file

@ -0,0 +1,158 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cstring>
#include <Corrade/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>
#include "Application.h"
namespace mbst {
void
Application::handleFileAction(efsw::WatchID watch_id, const std::string&, const std::string& filename,
efsw::Action action, std::string old_filename)
{
SDL_Event event;
SDL_zero(event);
event.type = _fileEventId;
event.user.data1 = Containers::String{Containers::AllocatedInit, filename.c_str()}.release();
if(watch_id == _watchIDs[StagingDir] && Utility::String::endsWith(filename, ".sav")) {
event.user.code = StagedUpdate | action;
SDL_PushEvent(&event);
return;
}
if(Utility::String::endsWith(filename, "Config.sav")) {
return;
} // TODO: actually do something when config files will finally be handled
if(!Utility::String::endsWith(filename, Utility::format("Profile{}.sav", _currentProfile->account()).data()) &&
filename.find("Unit") == std::string::npos)
{
return;
}
event.user.code = action;
if(action == efsw::Actions::Moved) {
event.user.data2 = Containers::String{Containers::AllocatedInit, old_filename.c_str()}.release();
}
SDL_PushEvent(&event);
}
void
Application::fileUpdateEvent(SDL_Event& event) {
Containers::String filename{static_cast<char*>(event.user.data1),
std::strlen(static_cast<char*>(event.user.data1)), nullptr};
if((event.user.code & StagedUpdate) == StagedUpdate) {
_stagedMassManager->refreshMass(filename);
return;
}
Containers::String old_filename;
std::int32_t index = 0;
std::int32_t old_index = 0;
bool is_current_profile = filename == _currentProfile->filename();
bool is_unit = filename.hasPrefix(_currentProfile->isDemo() ? "DemoUnit"_s : "Unit"_s);
if(is_unit) {
index = ((filename[_currentProfile->isDemo() ? 8 : 4] - 0x30) * 10) +
(filename[_currentProfile->isDemo() ? 9 : 5] - 0x30);
}
if(event.user.code == FileMoved) {
old_filename = Containers::String{static_cast<char*>(event.user.data2),
std::strlen(static_cast<char*>(event.user.data2)), nullptr};
old_index = ((old_filename[_currentProfile->isDemo() ? 8 : 4] - 0x30) * 10) +
(old_filename[_currentProfile->isDemo() ? 9 : 5] - 0x30);
}
switch(event.user.code) {
case FileAdded:
if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
else {
_currentMass->setDirty();
}
}
break;
case FileDeleted:
if(is_current_profile) {
_currentProfile = nullptr;
_uiState = UiState::ProfileManager;
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))) {
_massManager->refreshHangar(index);
}
}
break;
case FileModified:
if(is_current_profile) {
_currentProfile->refreshValues();
}
else if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
else {
if(_modifiedBySaveTool && _currentMass->filename() == filename) {
auto handle = CreateFileW(Utility::Unicode::widen(Containers::StringView{filename}),
GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if(handle && handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
_modifiedBySaveTool = false;
}
}
else {
_currentMass->setDirty();
}
}
}
break;
case FileMoved:
if(is_unit) {
if(old_filename.hasSuffix(".sav"_s)) {
_massManager->refreshHangar(index);
_massManager->refreshHangar(old_index);
}
}
break;
default:
_queue.addToast(Toast::Type::Warning, "Unknown file action type"_s);
}
}
}

View file

@ -122,6 +122,8 @@ Application::initialiseManager() {
_backupManager.emplace();
_stagedMassManager.emplace();
event.user.code = InitSuccess;
SDL_PushEvent(&event);
}
@ -130,8 +132,15 @@ void
Application::initialiseMassManager() {
LOG_INFO("Initialising the M.A.S.S. manager.");
_massManager.emplace(_currentProfile->account(), _currentProfile->isDemo());
LOG_INFO("Initialising the staged M.A.S.S. manager.");
_stagedMassManager.emplace();
}
void
Application::initialiseFileWatcher() {
LOG_INFO("Initialising the file watcher.");
_fileWatcher.emplace();
_watchIDs[SaveDir] = _fileWatcher->addWatch(conf().directories().gameSaves, this, false);
_watchIDs[StagingDir] = _fileWatcher->addWatch(conf().directories().staging, this, false);
_fileWatcher->watch();
}
}

View file

@ -55,20 +55,9 @@ Application::drawManager() {
if(ImGui::Button(ICON_FA_ARROW_LEFT " Back to profile manager")) {
_currentProfile = nullptr;
_massManager.reset();
_stagedMassManager.reset();
_fileWatcher.reset();
_uiState = UiState::ProfileManager;
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_SYNC_ALT " Refresh external changes")) {
_currentProfile->refreshValues();
if(!_currentProfile->valid()) {
_currentProfile = nullptr;
_profileManager->refreshProfiles();
_queue.addToast(Toast::Type::Error, "The current profile isn't valid anymore."_s);
ImGui::End();
return;
}
}
if(ImGui::BeginChild("##ProfileInfo",
{ImGui::GetContentRegionAvail().x * 0.60f, 0.0f},
@ -104,11 +93,6 @@ Application::drawManager() {
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("M.A.S.S. management");
drawHelpMarker("To move, import, or export builds, drag-and-drop them.");
if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh")) {
for(int i = 0; i < 32 ; i++) {
_massManager->refreshHangar(i);
}
}
ImGui::EndMenuBar();
}
@ -543,13 +527,10 @@ Application::drawMassManager() {
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Staging area");
ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_FOLDER_OPEN " Open staging folder")) {
openUri(Utility::Path::toNativeSeparators(conf().directories().staging));
}
ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh")) {
_stagedMassManager->refresh();
}
for(const auto& mass : _stagedMassManager->stagedMasses()) {
ImGui::TableNextRow();
@ -594,7 +575,6 @@ Application::drawMassManager() {
if(!_massManager->exportMass(index)) {
_queue.addToast(Toast::Type::Error, _massManager->lastError());
}
_stagedMassManager->refresh();
}
ImGui::EndDragDropTarget();

View file

@ -72,13 +72,17 @@ Application::drawMassViewer() {
drawTooltip(_currentMass->filename());
ImGui::TableSetColumnIndex(2);
if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh external changes")) {
if(_currentMass->dirty()) {
ImGui::TextUnformatted("External changes detected");
ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh")) {
_currentMass->refreshValues();
_currentMass->setDirty(false);
_jointsDirty = false;
_stylesDirty = false;
_eyeFlareDirty = false;
}
}
ImGui::TableSetColumnIndex(3);
if(ImGui::SmallButton(ICON_FA_TIMES)) {
@ -186,7 +190,9 @@ Application::drawGlobalStyles() {
_currentMass->getGlobalStyles();
break;
case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeGlobalStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;

View file

@ -14,8 +14,6 @@
// 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 <string>
#include <imgui_internal.h>
#include "../FontAwesome/IconsFontAwesome5.h"
@ -199,7 +197,9 @@ Application::drawArmour() {
ImGui::EndChild();
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeArmourPart(part.slot)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
}
@ -357,8 +357,11 @@ Application::drawBLAttachment() {
ImGui::EndGroup();
}
_modifiedBySaveTool = true;
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeBulletLauncherAttachments()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
}
@ -385,7 +388,9 @@ Application::drawCustomArmourStyles() {
_currentMass->getArmourCustomStyles();
break;
case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeArmourCustomStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;

View file

@ -149,7 +149,9 @@ Application::drawJointSliders() {
}
else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeJointSliders()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
_jointsDirty = false;
@ -204,7 +206,9 @@ Application::drawFrameStyles() {
}
else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeFrameStyles()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
_stylesDirty = false;
@ -236,7 +240,9 @@ Application::drawEyeColourPicker() {
}
else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeEyeFlareColour()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
_eyeFlareDirty = false;
@ -270,7 +276,9 @@ Application::drawCustomFrameStyles() {
_currentMass->getFrameCustomStyles();
break;
case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeFrameCustomStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;

View file

@ -94,38 +94,46 @@ Application::drawWeaponCategory(Containers::StringView name, GameObjects::Weapon
ImGui::PushID(name.cbegin());
if(drawUnsafeWidget(ImGui::SmallButton, ICON_FA_SAVE " Save")) {
_modifiedBySaveTool = true;
switch(category) {
case GameObjects::Weapon::Type::Melee:
if(!_currentMass->writeMeleeWeapons()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::Shield:
if(!_currentMass->writeShields()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::BulletShooter:
if(!_currentMass->writeBulletShooters()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::EnergyShooter:
if(!_currentMass->writeEnergyShooters()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::BulletLauncher:
if(!_currentMass->writeBulletLaunchers()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::EnergyLauncher:
if(!_currentMass->writeEnergyLaunchers()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
default:
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, "Unknown weapon type");
}
}

View file

@ -91,6 +91,7 @@ Application::drawProfileManager() {
{
_currentProfile = _profileManager->getProfile(i);
initialiseMassManager();
initialiseFileWatcher();
_uiState = UiState::MainManager;
}

View file

@ -193,6 +193,23 @@ Application::drawAbout() {
ImGui::TreePop();
}
if(ImGui::TreeNodeEx("Entropia File System Watcher (efsw)", ImGuiTreeNodeFlags_SpanAvailWidth)) {
auto efsw_repo = "https://github.com/SpartanJ/efsw";
drawAlignedText(ICON_FA_GITHUB " %s", efsw_repo);
ImGui::SameLine();
if(ImGui::Button("Copy to clipboard")) {
ImGui::SetClipboardText(efsw_repo);
}
ImGui::SameLine();
if(ImGui::Button("Open in browser")) {
openUri(efsw_repo);
}
ImGui::TextUnformatted("Licence: MIT");
ImGui::TreePop();
}
if(ImGui::TreeNodeEx("libcurl", ImGuiTreeNodeFlags_SpanAvailWidth)) {
ImGui::Text("Version used: %s", LIBCURL_VERSION);
auto curl_website = "https://curl.se/libcurl";

View file

@ -18,7 +18,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(SAVETOOL_PROJECT_VERSION 1.6.1)
set(SAVETOOL_PROJECT_VERSION 1.6.0)
find_package(Corrade REQUIRED Containers Utility)
if(CORRADE_TARGET_WINDOWS)
@ -31,6 +31,10 @@ if(SAVETOOL_USE_SYSTEM_LIBZIP)
find_package(libzip REQUIRED)
endif(SAVETOOL_USE_SYSTEM_LIBZIP)
if(SAVETOOL_USE_SYSTEM_EFSW)
find_package(efsw REQUIRED)
endif(SAVETOOL_USE_SYSTEM_EFSW)
if(SAVETOOL_USE_SYSTEM_LIBCURL)
find_package(CURL REQUIRED HTTPS)
endif(SAVETOOL_USE_SYSTEM_LIBCURL)
@ -141,6 +145,7 @@ add_executable(MassBuilderSaveTool
Application/Application.cpp
Application/Application_drawAbout.cpp
Application/Application_drawMainMenu.cpp
Application/Application_FileWatcher.cpp
Application/Application_Initialisation.cpp
Application/Application_MainManager.cpp
Application/Application_MassViewer.cpp
@ -221,7 +226,7 @@ target_compile_definitions(MassBuilderSaveTool PRIVATE
SAVETOOL_VERSION_STRING="${SAVETOOL_PROJECT_VERSION}"
SAVETOOL_VERSION_MAJOR=1
SAVETOOL_VERSION_MINOR=6
SAVETOOL_VERSION_PATCH=1
SAVETOOL_VERSION_PATCH=0
SAVETOOL_VERSION_PRERELEASE=false
SAVETOOL_CODENAME="Glorious Fuckery"
SAVETOOL_SUPPORTED_GAME_VERSION="0.12.x"
@ -257,6 +262,12 @@ else()
target_link_libraries(MassBuilderSaveTool PRIVATE zip)
endif()
if(SAVETOOL_USE_SYSTEM_EFSW)
target_link_libraries(MassBuilderSaveTool PRIVATE efsw::efsw)
else()
target_link_libraries(MassBuilderSaveTool PRIVATE efsw)
endif()
if(CORRADE_TARGET_WINDOWS)
target_link_libraries(MassBuilderSaveTool PRIVATE
Corrade::Main

1
third-party/efsw vendored Submodule

@ -0,0 +1 @@
Subproject commit 87abe599995d5646f5d83cf2e3a225bd73148b3a