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