diff --git a/CMakeLists.txt b/CMakeLists.txt
index b0d91c0..720c516 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,8 +47,16 @@ add_executable(wxMASSManager WIN32
GUI/NameChangeDialog.cpp
GUI/EvtNameChangeDialog.h
GUI/EvtNameChangeDialog.cpp
+ MassBuilderManager/MassBuilderManager.h
+ MassBuilderManager/MassBuilderManager.cpp
MassManager/MassManager.h
MassManager/MassManager.cpp
+ Profile/Profile.h
+ Profile/Profile.cpp
+ ProfileManager/ProfileManager.h
+ ProfileManager/ProfileManager.cpp
+ ScreenshotManager/ScreenshotManager.h
+ ScreenshotManager/ScreenshotManager.cpp
resource.rc)
target_compile_options(wxMASSManager PRIVATE -D_FILE_OFFSET_BITS=64 -D__WXMSW__ -fpermissive)
diff --git a/MassBuilderManager/MassBuilderManager.cpp b/MassBuilderManager/MassBuilderManager.cpp
new file mode 100644
index 0000000..627904e
--- /dev/null
+++ b/MassBuilderManager/MassBuilderManager.cpp
@@ -0,0 +1,87 @@
+// wxMASSManager
+// Copyright (C) 2020 Guillaume Jacquemin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+#include
+
+#include
+#include
+
+#include "MassBuilderManager.h"
+
+using namespace Corrade;
+
+MassBuilderManager::MassBuilderManager() {
+ _ready = findSaveDirectory();
+}
+
+auto MassBuilderManager::ready() -> bool {
+ return _ready;
+}
+
+auto MassBuilderManager::lastError() -> std::string const& {
+ return _lastError;
+}
+
+auto MassBuilderManager::saveDirectory() -> std::string const& {
+ return _saveDirectory;
+}
+
+auto MassBuilderManager::checkGameState() -> GameState {
+ WTS_PROCESS_INFOW* process_infos = nullptr;
+ unsigned long process_count = 0;
+ _gameState = GameState::Unknown;
+
+ if(WTSEnumerateProcessesW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &process_infos, &process_count)) {
+ for(unsigned long i = 0; i < process_count; ++i) {
+ if(std::wcscmp(process_infos[i].pProcessName, L"MASS_Builder-Win64-Shipping.exe") == 0) {
+ _gameState = GameState::Running;
+ break;
+ }
+ else {
+ _gameState = GameState::NotRunning;
+ }
+ }
+ }
+
+ if(process_infos != nullptr) {
+ WTSFreeMemory(process_infos);
+ process_infos = nullptr;
+ }
+
+ return _gameState;
+}
+
+auto MassBuilderManager::gameState() -> GameState {
+ return _gameState;
+}
+
+auto MassBuilderManager::findSaveDirectory() -> bool {
+ wchar_t h[MAX_PATH];
+ if(!SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, h))) {
+ _lastError = "SHGetFolderPathW() failed in MassBuilderManager::findSaveDirectory()";
+ return false;
+ }
+
+ _saveDirectory = Utility::Directory::join(Utility::Directory::fromNativeSeparators(Utility::Unicode::narrow(h)), "MASS_Builder");
+
+ if(!Utility::Directory::exists(_saveDirectory)) {
+ _lastError = _saveDirectory + " wasn't found.";
+ return false;
+ }
+
+ return true;
+}
diff --git a/MassBuilderManager/MassBuilderManager.h b/MassBuilderManager/MassBuilderManager.h
new file mode 100644
index 0000000..c8aff0f
--- /dev/null
+++ b/MassBuilderManager/MassBuilderManager.h
@@ -0,0 +1,52 @@
+#ifndef MASSBUILDERMANAGER_H
+#define MASSBUILDERMANAGER_H
+
+// wxMASSManager
+// Copyright (C) 2020 Guillaume Jacquemin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+
+#include
+
+enum class GameState : std::uint8_t {
+ Unknown, NotRunning, Running
+};
+
+class MassBuilderManager {
+ public:
+ MassBuilderManager();
+
+ auto ready() -> bool;
+ auto lastError() -> std::string const&;
+
+ auto saveDirectory() -> std::string const&;
+
+ auto checkGameState() -> GameState;
+ auto gameState() -> GameState;
+
+ private:
+ auto findSaveDirectory() -> bool;
+
+ bool _ready = false;
+
+ std::string _lastError = "";
+
+ std::string _saveDirectory = "";
+
+ GameState _gameState = GameState::Unknown;
+};
+
+#endif //MASSBUILDERMANAGER_H
diff --git a/Profile/Profile.cpp b/Profile/Profile.cpp
new file mode 100644
index 0000000..4f4e13f
--- /dev/null
+++ b/Profile/Profile.cpp
@@ -0,0 +1,134 @@
+// wxMASSManager
+// Copyright (C) 2020 Guillaume Jacquemin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+
+#include
+#include
+#include
+
+#include "Profile.h"
+
+constexpr char company_name_locator[] = { 'C', 'o', 'm', 'p', 'a', 'n', 'y', 'N', 'a', 'm', 'e', '\0', 0x0C, '\0', '\0', '\0', 'S', 't', 'r', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'y', '\0' };
+
+constexpr char active_slot_locator[] = { 'A', 'c', 't', 'i', 'v', 'e', 'F', 'r', 'a', 'm', 'e', 'S', 'l', 'o', 't', '\0', 0x0C, '\0', '\0', '\0', 'I', 'n', 't', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'y', '\0' };
+
+constexpr char credits_locator[] = { 'C', 'r', 'e', 'd', 'i', 't', '\0', 0x0C, '\0', '\0', '\0', 'I', 'n', 't', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'y', '\0' };
+
+using namespace Corrade;
+
+Profile::Profile(const std::string& path) {
+ auto map = Utility::Directory::mapRead(path);
+
+ if(!map) {
+ _lastError = "Couldn't memory-map " + Utility::Directory::filename(path);
+ return;
+ }
+
+ _profileDirectory = Utility::Directory::path(path);
+ _filename = Utility::Directory::filename(path);
+
+ if(Utility::String::beginsWith(_filename, "Demo")) {
+ _type = ProfileType::Demo;
+ }
+ else {
+ _type = ProfileType::FullGame;
+ }
+
+ _steamId = Utility::String::ltrim(Utility::String::rtrim(_filename, ".sav"), (_type == ProfileType::Demo ? "Demo" : "") + std::string{"Profile"});
+
+ auto it = std::search(map.begin(), map.end(), &company_name_locator[0], &company_name_locator[27]);
+
+ if(it == map.end()) {
+ _lastError = "Couldn't find a company name in " + _filename;
+ return;
+ }
+
+ _companyName = std::string{it + 41};
+
+ _valid = true;
+}
+
+auto Profile::valid() const -> bool {
+ return _valid;
+}
+
+auto Profile::lastError() const -> std::string const& {
+ return _lastError;
+}
+
+auto Profile::filename() const -> std::string const& {
+ return _filename;
+}
+
+auto Profile::type() const -> ProfileType {
+ return _type;
+}
+
+auto Profile::steamId() const -> std::string const& {
+ return _steamId;
+}
+
+auto Profile::companyName() const -> std::string const& {
+ return _companyName;
+}
+
+auto Profile::activeFrameSlot() const -> std::int8_t {
+ return _activeFrameSlot;
+}
+
+auto Profile::getActiveFrameSlot() -> std::int8_t {
+ auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename));
+
+ auto iter = std::search(mmap.begin(), mmap.end(), &active_slot_locator[0], &active_slot_locator[31]);
+
+ if(iter == mmap.end()) {
+ if(std::search(mmap.begin(), mmap.end(), &credits_locator[0], &credits_locator[22]) != mmap.end()) {
+ _activeFrameSlot = 0;
+ }
+ else {
+ _lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file.";
+ _activeFrameSlot = -1;
+ }
+ }
+ else {
+ _activeFrameSlot = *(iter + 41);
+ }
+
+ return _activeFrameSlot;
+}
+
+auto Profile::credits() const -> std::int32_t {
+ return _credits;
+}
+
+auto Profile::getCredits() -> std::int32_t {
+ auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_profileDirectory, _filename));
+
+ auto iter = std::search(mmap.begin(), mmap.end(), &credits_locator[0], &credits_locator[22]);
+
+ if(iter != mmap.end()) {
+ _credits = ((*(iter + 0x20)) << 0) +
+ ((*(iter + 0x21)) << 8) +
+ ((*(iter + 0x22)) << 16) +
+ ((*(iter + 0x23)) << 24);
+ }
+ else{
+ _credits = -1;
+ }
+
+ return _credits;
+}
diff --git a/Profile/Profile.h b/Profile/Profile.h
new file mode 100644
index 0000000..3497907
--- /dev/null
+++ b/Profile/Profile.h
@@ -0,0 +1,53 @@
+#ifndef PROFILE_H
+#define PROFILE_H
+
+#include
+
+#include
+
+enum class ProfileType : std::uint8_t {
+ Demo,
+ FullGame
+};
+
+class Profile {
+ public:
+ explicit Profile(const std::string& path);
+
+ auto valid() const -> bool;
+
+ auto lastError() const -> std::string const&;
+
+ auto filename() const -> std::string const&;
+
+ auto type() const -> ProfileType;
+
+ auto steamId() const -> std::string const&;
+
+ auto companyName() const -> std::string const&;
+
+ auto activeFrameSlot() const -> std::int8_t;
+ auto getActiveFrameSlot() -> std::int8_t;
+
+ auto credits() const -> std::int32_t;
+ auto getCredits() -> std::int32_t;
+
+ private:
+ std::string _profileDirectory;
+ std::string _filename;
+
+ ProfileType _type;
+
+ std::string _steamId;
+
+ bool _valid = false;
+ std::string _lastError = "";
+
+ std::string _companyName;
+
+ std::int8_t _activeFrameSlot = 0;
+
+ std::int32_t _credits;
+};
+
+#endif //PROFILE_H
diff --git a/ProfileManager/ProfileManager.cpp b/ProfileManager/ProfileManager.cpp
new file mode 100644
index 0000000..324401a
--- /dev/null
+++ b/ProfileManager/ProfileManager.cpp
@@ -0,0 +1,100 @@
+// wxMASSManager
+// Copyright (C) 2020 Guillaume Jacquemin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+#include
+
+#include
+#include
+
+#include "ProfileManager.h"
+
+using namespace Corrade;
+
+ProfileManager::ProfileManager(const std::string& base_path) {
+ _profileDirectory = Utility::Directory::join(base_path, "Saved/SaveGames");
+
+ if(Utility::Directory::exists(_profileDirectory) == false) {
+ _lastError = "Couldn't find the profile directory. Make sure you played enough of the game.";
+ return;
+ }
+
+ using Utility::Directory::Flag;
+ std::vector files = Utility::Directory::list(_profileDirectory, Flag::SkipSpecial|Flag::SkipDirectories|Flag::SkipDotAndDotDot);
+
+ auto predicate = [](const std::string& file)->bool{
+ std::regex regex("(Demo)?Profile[0-9]{17}\\.sav", std::regex::nosubs);
+ std::cmatch m;
+ return !std::regex_match(file.c_str(), m, regex);
+ };
+
+ files.erase(std::remove_if(files.begin(), files.end(), predicate), files.end());
+
+ for(const std::string& file : files) {
+ Profile profile{Utility::Directory::join(_profileDirectory, file)};
+
+ if(profile.valid() == false) {
+ Utility::Warning{} << "Profile" << file.c_str() << "is invalid:" << profile.lastError().c_str();
+ continue;
+ }
+
+ _profiles.push_back(std::move(profile));
+ }
+
+ if(_profiles.size() == 0) {
+ _lastError = "No profiles were found.";
+ return;
+ }
+
+ _ready = true;
+}
+
+auto ProfileManager::ready() -> bool {
+ return _ready;
+}
+
+auto ProfileManager::lastError() -> std::string const& {
+ return _lastError;
+}
+
+auto ProfileManager::profileDirectory() -> std::string const& {
+ return _profileDirectory;
+}
+
+auto ProfileManager::profiles() -> std::vector const& {
+ return _profiles;
+}
+
+auto ProfileManager::currentProfile() -> Profile* {
+ return _currentProfile;
+}
+
+auto ProfileManager::setProfile(std::size_t index) -> bool {
+ bool result = false;
+
+ try {
+ _currentProfile = &(_profiles.at(index));
+ result = true;
+ }
+ catch(const std::out_of_range&) {
+ _lastError = "Invalid profile index";
+ _currentProfile = nullptr;
+ }
+
+ return result;
+}
+
+
diff --git a/ProfileManager/ProfileManager.h b/ProfileManager/ProfileManager.h
new file mode 100644
index 0000000..099f170
--- /dev/null
+++ b/ProfileManager/ProfileManager.h
@@ -0,0 +1,52 @@
+#ifndef PROFILEMANAGER_H
+#define PROFILEMANAGER_H
+
+// wxMASSManager
+// Copyright (C) 2020 Guillaume Jacquemin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+#include
+
+#include "../Profile/Profile.h"
+
+using namespace Corrade;
+
+class ProfileManager {
+ public:
+ ProfileManager(const std::string& base_path);
+
+ auto ready() -> bool;
+ auto lastError() -> std::string const&;
+
+ auto profileDirectory() -> std::string const&;
+
+ auto profiles() -> std::vector const&;
+
+ auto currentProfile() -> Profile*;
+ auto setProfile(std::size_t index) -> bool;
+
+ private:
+ bool _ready = false;
+ std::string _lastError = "";
+
+ std::string _profileDirectory;
+
+ std::vector _profiles;
+
+ Profile* _currentProfile = nullptr;
+};
+
+#endif //PROFILEMANAGER_H
diff --git a/ScreenshotManager/ScreenshotManager.cpp b/ScreenshotManager/ScreenshotManager.cpp
new file mode 100644
index 0000000..37a2154
--- /dev/null
+++ b/ScreenshotManager/ScreenshotManager.cpp
@@ -0,0 +1,145 @@
+// wxMASSManager
+// Copyright (C) 2020 Guillaume Jacquemin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+
+#include
+#include
+
+#include
+
+#include "ScreenshotManager.h"
+
+using namespace Corrade;
+
+ScreenshotManager::ScreenshotManager(const std::string& base_path) {
+ _screenshotDirectory = Utility::Directory::join(base_path, "Saved/Screenshots/WindowsNoEditor");
+
+ if(!Utility::Directory::exists(_screenshotDirectory)) {
+ Utility::Directory::mkpath(_screenshotDirectory);
+ }
+
+ using Utility::Directory::Flag;
+ std::vector file_list = Utility::Directory::list(_screenshotDirectory, Flag::SkipSpecial|Flag::SkipDirectories|Flag::SkipDotAndDotDot);
+
+ auto iter = std::remove_if(file_list.begin(), file_list.end(), [](std::string& file){
+ return !Utility::String::endsWith(file, ".png");
+ });
+
+ file_list.erase(iter, file_list.end());
+
+ _screenshots.reserve(file_list.size());
+
+ for(const std::string& file : file_list) {
+ addScreenshot(file);
+ }
+}
+
+auto ScreenshotManager::screenshotDirectory() -> std::string const& {
+ return _screenshotDirectory;
+}
+
+auto ScreenshotManager::screenshots() -> std::vector const& {
+ return _screenshots;
+}
+
+void ScreenshotManager::sortScreenshots(SortType type) {
+ _sortType = type;
+ sortScreenshots();
+}
+
+void ScreenshotManager::sortScreenshots(SortOrder order) {
+ _sortOrder = order;
+ sortScreenshots();
+}
+
+void ScreenshotManager::sortScreenshots() {
+ auto predicate = [this](const Screenshot& item_1, const Screenshot& item_2)->bool{
+ switch(_sortType) {
+ case SortType::Filename:
+ return wxString::FromUTF8(item_1._filename.c_str()).CmpNoCase(wxString::FromUTF8(item_2._filename.c_str())) < 0;
+ case SortType::CreationDate:
+ return item_1._creationDate.IsEarlierThan(item_2._creationDate);
+ }
+
+ return true;
+ };
+
+ switch(_sortOrder) {
+ case SortOrder::Ascending:
+ std::stable_sort(_screenshots.begin(), _screenshots.end(), predicate);
+ break;
+ case SortOrder::Descending:
+ std::stable_sort(_screenshots.rbegin(), _screenshots.rend(), predicate);
+ break;
+ }
+}
+
+auto ScreenshotManager::updateScreenshot(const std::string& filename) -> int {
+ addScreenshot(filename);
+ sortScreenshots();
+ int index = 0;
+ for(const Screenshot& s : _screenshots) {
+ if(s._filename == filename) {
+ return index;
+ }
+ }
+
+ return -1;
+}
+
+void ScreenshotManager::removeScreenshot(int index) {
+ if(static_cast(index + 1) > _screenshots.size()) {
+ return;
+ }
+
+ auto it = _screenshots.begin() + index;
+ _screenshots.erase(it);
+}
+
+void ScreenshotManager::deleteScreenshot(int index) {
+ if(static_cast(index + 1) > _screenshots.size()) {
+ return;
+ }
+
+ Utility::Directory::rm(Utility::Directory::join(_screenshotDirectory, _screenshots[index]._filename));
+}
+
+void ScreenshotManager::addScreenshot(const std::string& filename) {
+ std::string screenshot_path = Utility::Directory::toNativeSeparators(Utility::Directory::join(_screenshotDirectory, filename));
+
+ wxFileName screenshot_meta(screenshot_path);
+ wxDateTime creation_date;
+ screenshot_meta.GetTimes(nullptr, nullptr, &creation_date);
+
+ wxImage thumb{screenshot_path, wxBITMAP_TYPE_PNG};
+
+ wxSize size = thumb.GetSize();
+ if(size.GetWidth() > size.GetHeight()) {
+ size.Set(160, (size.GetHeight() * 160) / size.GetWidth());
+ }
+ else if(size.GetHeight() > size.GetWidth()) {
+ size.Set((size.GetWidth() * 160) / size.GetHeight(), 160);
+ }
+ else {
+ size.Set(160, 160);
+ }
+
+ thumb.Rescale(size.GetWidth(), size.GetHeight(), wxIMAGE_QUALITY_HIGH)
+ .Resize(wxSize{160, 160}, wxPoint{(160 - size.GetWidth()) / 2, (160 - size.GetHeight()) / 2});
+
+ _screenshots.push_back(Screenshot{filename, creation_date, thumb});
+}
diff --git a/ScreenshotManager/ScreenshotManager.h b/ScreenshotManager/ScreenshotManager.h
new file mode 100644
index 0000000..122bdd0
--- /dev/null
+++ b/ScreenshotManager/ScreenshotManager.h
@@ -0,0 +1,67 @@
+#ifndef SCREENSHOTMANAGER_H
+#define SCREENSHOTMANAGER_H
+
+// wxMASSManager
+// Copyright (C) 2020 Guillaume Jacquemin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+#include
+
+#include
+#include
+
+enum class SortType : uint8_t {
+ Filename, CreationDate
+};
+
+enum class SortOrder: uint8_t {
+ Ascending, Descending
+};
+
+struct Screenshot {
+ std::string _filename;
+ wxDateTime _creationDate;
+ wxImage _thumbnail;
+};
+
+class ScreenshotManager {
+ public:
+ ScreenshotManager(const std::string& base_path);
+
+ auto screenshotDirectory() -> std::string const&;
+
+ auto screenshots() -> std::vector const&;
+
+ void sortScreenshots(SortType type);
+ void sortScreenshots(SortOrder order);
+ void sortScreenshots();
+
+ auto updateScreenshot(const std::string& filename) -> int;
+ void removeScreenshot(int index);
+ void deleteScreenshot(int index);
+
+ private:
+ void addScreenshot(const std::string& filename);
+
+ std::string _screenshotDirectory = "";
+
+ std::vector _screenshots;
+
+ SortType _sortType = SortType::Filename;
+ SortOrder _sortOrder = SortOrder::Ascending;
+};
+
+#endif //SCREENSHOTMANAGER_H