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