Compare commits

...

64 Commits

Author SHA1 Message Date
Guillaume Jacquemin 4883b617fd Change project structure and update copyright years. 2021-05-20 22:46:59 +02:00
Guillaume Jacquemin e98a5e9d26 Add the FindCorrade module to modules directory. 2021-05-14 19:51:07 +02:00
Guillaume Jacquemin 768bcd1e5f Update the CMakeLists. 2021-05-14 19:50:31 +02:00
Guillaume Jacquemin 896c48290d Update Corrade to latest version. 2021-05-14 19:39:34 +02:00
Guillaume Jacquemin 34cba84362 MainFrame: reduce proportion of the general info. 2021-05-13 22:49:01 +02:00
Guillaume Jacquemin 3cb897ccda EvtMainFrame: Add some extra safety, just in case. 2021-05-12 20:39:48 +02:00
Guillaume Jacquemin 0ce8046728 Bump version number. 2021-05-12 20:27:03 +02:00
Guillaume Jacquemin 13b2614af0 Add a way to edit credits. 2021-05-12 20:21:35 +02:00
Guillaume Jacquemin 171fee333f EvtMainFrame: fix a function signature. 2021-05-12 15:11:05 +02:00
Guillaume Jacquemin 1d7c08b3ca Add a viewer/editor for the research inventory. 2021-05-12 13:53:51 +02:00
Guillaume Jacquemin 32c193a53b MainFrame: change size constraints. 2021-05-11 19:29:25 +02:00
Guillaume Jacquemin ae2a613879 Profile: move locators to their own header. 2021-05-11 19:28:26 +02:00
Guillaume Jacquemin a310669839 Bump version number. 2021-05-11 14:54:42 +02:00
Guillaume Jacquemin f532803d56 Add a way to change the story progression state. 2021-05-11 14:54:33 +02:00
Guillaume Jacquemin 9ebd418ed2 MainFrame: fix the rename event. 2021-05-11 14:53:41 +02:00
Guillaume Jacquemin 3c8e82da96 EvtMainFrame: make the PMC rename button safer. 2021-05-11 14:52:52 +02:00
Guillaume Jacquemin 640f378357 EvtMainFrame: remove an extra line. 2021-05-11 14:51:54 +02:00
Guillaume Jacquemin 3c64f59a8c Expose the company renaming feature. 2021-05-11 14:51:33 +02:00
Guillaume Jacquemin 0ccc232afa Remove the screenshot management feature.
I really wasn't satisfied with it, to be honest.
2021-05-10 21:20:19 +02:00
Guillaume Jacquemin 5256b11a46 Add support for the very first progress state. 2021-05-10 20:33:44 +02:00
Guillaume Jacquemin bb283b0603 Bump version up. 2021-05-10 19:58:02 +02:00
Guillaume Jacquemin 7eceaf329e Fix values for StoryProgress and LastMissionId.
They aren't bad per se, they're just off.
2021-05-10 19:54:46 +02:00
Guillaume Jacquemin aa5d61ac04 Bump version up. 2021-05-10 19:01:51 +02:00
Guillaume Jacquemin a58ad3d330 Change the initial warning message. 2021-05-10 18:39:28 +02:00
Guillaume Jacquemin 76de8c0bee Finish mapping StoryProgress. 2021-05-10 18:11:25 +02:00
Guillaume Jacquemin 7cd1f9aa4e Update the about text. 2021-04-20 15:56:08 +02:00
Guillaume Jacquemin 5c4a3e8d16 Start mapping story progress. 2021-04-20 15:55:41 +02:00
Guillaume Jacquemin d56b2cc4c2 Use a scope guard for more safety. 2021-04-20 15:55:06 +02:00
Guillaume Jacquemin 97eb69922f Move the last mission ID map to its own header. 2021-04-20 15:54:07 +02:00
Guillaume Jacquemin f3b14fbffe MainFrame: improve readability and add challenges.
Also, hunting grounds are now known as "hunts".
2021-03-11 14:19:43 +01:00
Guillaume Jacquemin 9925fc7f03 MainFrame: fix layout. 2021-03-11 14:18:43 +01:00
Guillaume Jacquemin 036475e4c6 Update version information. 2021-03-05 15:03:01 +01:00
Guillaume Jacquemin 5b23303a48 MainFrame: add logic to unsafe mode checkbox. 2021-03-05 14:56:28 +01:00
Guillaume Jacquemin 0b1ecc52f5 MainFrame: update a button's {dis,en}abled logic. 2021-03-05 14:24:27 +01:00
Guillaume Jacquemin 20c35c8d8e MainFrame: upgrade the default profile logic. 2021-03-05 14:20:19 +01:00
Guillaume Jacquemin 9c74f672dd MainFrame: re-enable the warning on launch.
Also add an extra line about the pre-release stuff.
2021-03-05 14:20:09 +01:00
Guillaume Jacquemin 0d386a70d8 MainFrame: add unsafe checkbox.
It doesn't do anything yet, though.
2021-03-05 14:18:06 +01:00
Guillaume Jacquemin e799f1be04 CMakeLists: fix a logic issue. 2020-10-05 12:53:04 +02:00
Guillaume Jacquemin 8a5d9c4231 Prepare for pre-release 1. 2020-10-05 11:05:52 +02:00
Guillaume Jacquemin cebae7d072 Additional safety in fileUpdateEvent. 2020-10-05 10:36:23 +02:00
Guillaume Jacquemin 4c0f1b8ce5 Added an experimental way to rename the company. 2020-10-05 10:36:08 +02:00
Guillaume Jacquemin 932052ab0a Profile: forgot to mark two functions as const. 2020-10-05 08:41:23 +02:00
Guillaume Jacquemin 0b846be139 Prepare the 2.0.0 pre-release version. 2020-10-05 08:36:27 +02:00
Guillaume Jacquemin 3d04ac4a12 Added the last two available levels to the map. 2020-10-05 08:32:59 +02:00
Guillaume Jacquemin d38606b574 Add two more values. 2020-10-05 00:55:08 +02:00
Guillaume Jacquemin 016b549d7a Profile: simplify the credits formula.
IntProperty is a 32-bit signed integer according to research.
2020-10-04 20:11:32 +02:00
Guillaume Jacquemin b2c5c6a69f Update the profile stats when the file is modified. 2020-10-02 12:56:22 +02:00
Guillaume Jacquemin eb08de1a30 MainFrame: rename a symbol. 2020-10-02 12:55:52 +02:00
Guillaume Jacquemin 685bc59540 MassManager: return empty_string by ref instead of a temp object. 2020-10-02 12:55:31 +02:00
Guillaume Jacquemin 4e1a74e03e MainFrame: make code a bit more readable. 2020-10-02 12:54:38 +02:00
Guillaume Jacquemin a24422adcc MainFrame: increase sleep time in fileUpdateEvent().
This should make file access a tiny bit safer.
2020-10-02 12:53:55 +02:00
Guillaume Jacquemin e77b56383a Made the strings more readable. 2020-09-30 22:49:42 +02:00
Guillaume Jacquemin a06ea2caa4 Finish re{writing} the MASS manager. 2020-09-30 16:27:26 +02:00
Guillaume Jacquemin d7fa2abafb Profile: reimplement the backup feature. 2020-09-30 16:26:34 +02:00
Guillaume Jacquemin fdf72544fb Fixed an assert.
It didn't appear in release, but it could have bitten me in the ass at
some point.
2020-09-30 16:26:15 +02:00
Guillaume Jacquemin 854a7bbcca Add the ability to not use the Corrade submodule.
Also add a version check, just in case.
2020-09-21 13:48:22 +02:00
Guillaume Jacquemin 34d938548a Update Corrade to 2020.06. 2020-09-08 23:26:37 +02:00
Guillaume Jacquemin 9ddea11b6e Some early work on classes. 2020-09-01 16:14:43 +02:00
Guillaume Jacquemin 401d7d747e Fix a regression introduced in 1.1.2. Again. 2020-08-17 13:10:45 +02:00
Guillaume Jacquemin cd92cb02fa Update the disclaimer. 2020-07-30 11:10:11 +02:00
Guillaume Jacquemin 44e5790b7c Bump the version number to 1.2.1. 2020-07-22 16:04:15 +02:00
Guillaume Jacquemin d3f1027df2 Fix a regression introduced in 1.1.2. 2020-07-22 16:03:11 +02:00
Guillaume Jacquemin 496ade0481 Bump the version number to 1.2.0. 2020-07-22 09:19:44 +02:00
Guillaume Jacquemin 3f8af910e3 Add demo unit import. 2020-07-22 08:55:12 +02:00
35 changed files with 5821 additions and 2591 deletions

View File

@ -1,5 +1,5 @@
# wxMASSManager
# Copyright (C) 2020 Guillaume Jacquemin
# Copyright (C) 2020-2021 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
@ -18,68 +18,23 @@ cmake_minimum_required(VERSION 3.5)
project(wxMASSManager LANGUAGES CXX)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/modules/" ${CMAKE_MODULE_PATH})
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(BUILD_STATIC ON CACHE BOOL "" FORCE)
set(WITH_INTERCONNECT OFF CACHE BOOL "" FORCE)
set(WITH_PLUGINMANAGER OFF CACHE BOOL "" FORCE)
set(WITH_TESTSUITE OFF CACHE BOOL "" FORCE)
add_subdirectory(corrade)
option(USE_CORRADE_SUBMODULE "Use Corrade from the Git submodule. If set to OFF, make sure you have Corrade 2020.06 or later installed to a path CMake can search in." ON)
find_package(Corrade REQUIRED Containers Utility)
if(USE_CORRADE_SUBMODULE)
set(BUILD_STATIC ON CACHE BOOL "" FORCE)
set(BUILD_STATIC_PIC ON CACHE BOOL "" FORCE)
set(BUILD_STATIC_UNIQUE_GLOBALS OFF CACHE BOOL "" FORCE)
include_directories(SYSTEM "C:/msys64/mingw64/lib/wx/include/msw-unicode-static-3.0")
include_directories(SYSTEM "C:/msys64/mingw64/include/wx-3.0")
set(WITH_INTERCONNECT OFF CACHE BOOL "" FORCE)
set(WITH_PLUGINMANAGER OFF CACHE BOOL "" FORCE)
set(WITH_TESTSUITE OFF CACHE BOOL "" FORCE)
add_subdirectory(corrade EXCLUDE_FROM_ALL)
endif()
set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON)
add_executable(wxMASSManager WIN32
main.cpp
GUI/MainFrame.fbp
GUI/MainFrame.h
GUI/MainFrame.cpp
GUI/EvtMainFrame.h
GUI/EvtMainFrame.cpp
GUI/NameChangeDialog.fbp
GUI/NameChangeDialog.h
GUI/NameChangeDialog.cpp
GUI/EvtNameChangeDialog.h
GUI/EvtNameChangeDialog.cpp
MassManager/MassManager.h
MassManager/MassManager.cpp
resource.rc)
target_compile_options(wxMASSManager PRIVATE -D_FILE_OFFSET_BITS=64 -D__WXMSW__ -fpermissive)
target_link_options(wxMASSManager PRIVATE -static -static-libgcc -static-libstdc++ -pipe -Wl,--subsystem,windows -mwindows)
target_link_libraries(wxMASSManager PRIVATE
Corrade::Containers
Corrade::Utility
wx_mswu_adv-3.0
wx_mswu_core-3.0
wx_baseu-3.0
wxregexu-3.0
wxexpat-3.0
wxtiff-3.0
wxjpeg-3.0
wxpng-3.0
wxzlib-3.0
rpcrt4
oleaut32
ole32
uuid
lzma
jbig
winspool
winmm
shell32
comctl32
comdlg32
advapi32
wsock32
gdi32
oleacc
wtsapi32)
add_subdirectory(src)

View File

@ -1,610 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
#include <wx/filedlg.h>
#include <wx/msgdlg.h>
#include <wx/numdlg.h>
#include <wx/regex.h>
#include <wx/scrolwin.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Directory.h>
#include "EvtNameChangeDialog.h"
#include "EvtMainFrame.h"
using namespace Corrade;
EvtMainFrame::EvtMainFrame(wxWindow* parent): MainFrame(parent) {
SetIcon(wxIcon("MAINICON"));
if(!_manager.ready()) {
errorMessage("There was an issue initialising the manager:\n\n" + _manager.lastError());
return;
}
initialiseListView();
isGameRunning();
_installedListView->Connect(wxEVT_LIST_ITEM_SELECTED, wxListEventHandler(EvtMainFrame::installedSelectionEvent), nullptr, this);
_installedListView->Connect(wxEVT_LIST_ITEM_DESELECTED, wxListEventHandler(EvtMainFrame::installedSelectionEvent), nullptr, this);
_installedListView->Connect(wxEVT_LIST_BEGIN_DRAG, wxListEventHandler(EvtMainFrame::listColumnDragEvent), nullptr, this);
_installedListView->Connect(wxEVT_LIST_COL_BEGIN_DRAG, wxListEventHandler(EvtMainFrame::listColumnDragEvent), nullptr, this);
warningMessage(wxString::FromUTF8("Before you start using this app, a few things you should know:\n\n"
"For this application to work properly, Steam Cloud syncing needs to be disabled for the game.\nTo disable it, right-click the game in your Steam library, click \"Properties\", go to the \"Updates\" tab, and uncheck \"Enable Steam Cloud synchronization for M.A.S.S. Builder\".\n\n"
"DISCLAIMER: The developer of this application cannot be held responsible for data loss or corruption. PLEASE USE AT YOUR OWN RISK!\n\n"
"Last but not least, this application is released under the terms of the GNU General Public Licence version 3. Please see the COPYING file for more details."));
_watcher.Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(EvtMainFrame::fileUpdateEvent), nullptr, this);
_watcher.AddTree(wxFileName(Utility::Directory::toNativeSeparators(_manager.saveDirectory()), wxPATH_WIN),
wxFSW_EVENT_CREATE|wxFSW_EVENT_DELETE|wxFSW_EVENT_MODIFY|wxFSW_EVENT_RENAME, wxString::Format("*%s.sav", _manager.steamId()));
_watcher.AddTree(wxFileName(Utility::Directory::toNativeSeparators(_manager.stagingAreaDirectory()), wxPATH_WIN),
wxFSW_EVENT_CREATE|wxFSW_EVENT_DELETE|wxFSW_EVENT_MODIFY|wxFSW_EVENT_RENAME, "*.sav");
std::vector<std::string> v = _manager.initialiseStagingArea();
for(const std::string& s : v) {
_stagingList->Append(s);
}
_gameCheckTimer.Start(3000);
if(!_manager.findScreenshotDirectory()) {
warningMessage("Screenshot manager not ready:\n\n" + _manager.lastError());
_screenshotsPanel->Disable();
return;
}
_watcher.AddTree(wxFileName(Utility::Directory::toNativeSeparators(_manager.screenshotDirectory()), wxPATH_WIN),
wxFSW_EVENT_CREATE|wxFSW_EVENT_DELETE, "*.png"); // Not monitoring wxFSW_EVENT_{MODIFY,RENAME}, because they're a massive pain to handle. Ugh.
_screenshotsList->SetImageList(&_screenshotThumbs, wxIMAGE_LIST_NORMAL);
updateScreenshotList();
}
EvtMainFrame::~EvtMainFrame() {
_installedListView->Disconnect(wxEVT_LIST_ITEM_SELECTED, wxListEventHandler(EvtMainFrame::installedSelectionEvent), nullptr, this);
_installedListView->Disconnect(wxEVT_LIST_ITEM_DESELECTED, wxListEventHandler(EvtMainFrame::installedSelectionEvent), nullptr, this);
_installedListView->Disconnect(wxEVT_LIST_BEGIN_DRAG, wxListEventHandler(EvtMainFrame::listColumnDragEvent), nullptr, this);
_installedListView->Disconnect(wxEVT_LIST_COL_BEGIN_DRAG, wxListEventHandler(EvtMainFrame::listColumnDragEvent), nullptr, this);
_watcher.Disconnect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(EvtMainFrame::fileUpdateEvent), nullptr, this);
}
bool EvtMainFrame::ready() {
return _manager.ready();
}
void EvtMainFrame::importMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Importing failed:\n\n";
long selected_hangar = _installedListView->GetFirstSelected();
HangarState hangar_state = _manager.hangarState(selected_hangar);
int staged_selection = _stagingList->GetSelection();
int confirmation;
if(hangar_state == HangarState::Filled) {
confirmation = wxMessageBox(wxString::Format("Hangar %.2d is already occupied by the M.A.S.S. named \"%s\". Are you sure you want to import the M.A.S.S. named \"%s\" to this hangar ?",
selected_hangar + 1, *(_manager.massName(selected_hangar)), _manager.stagedMassName(staged_selection)),
"Question", wxYES_NO|wxCENTRE|wxICON_QUESTION, this);
}
else {
confirmation = wxMessageBox(wxString::Format("Are you sure you want to import the M.A.S.S. named \"%s\" to hangar %.2d ?", _manager.stagedMassName(staged_selection), selected_hangar + 1),
"Question", wxYES_NO|wxCENTRE|wxICON_QUESTION, this);
}
if(confirmation == wxNO) {
return;
}
switch(_manager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, importing is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_manager.importMass(staged_selection, selected_hangar)) {
errorMessage(error_prefix + _manager.lastError());
}
break;
case GameState::Running:
errorMessage(error_prefix + "Importing a M.A.S.S. is disabled while the game is running.");
break;
}
}
void EvtMainFrame::exportMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Export failed:\n\n";
long slot = _installedListView->GetFirstSelected();
if(!_manager.exportMass(slot)) {
errorMessage(error_prefix + _manager.lastError());
}
}
void EvtMainFrame::moveMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Move failed:\n\n";
long source_slot = _installedListView->GetFirstSelected();
long choice = wxGetNumberFromUser(wxString::Format("Which hangar do you want to move the M.A.S.S. named \"%s\" to ?\nNotes:\n"
"- If the destination hangar is the same as the source, nothing will happen.\n"
"- If the destination already contains a M.A.S.S., the two will be swapped.\n"
"- If the destination contains invalid data, it will be cleared first.",
*(_manager.massName(source_slot))),
"Slot", "Choose a slot", source_slot + 1, 1, 32, this);
if(choice == -1 || choice == source_slot) {
return;
}
switch(_manager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, moving a M.A.S.S. is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_manager.moveMass(source_slot, choice - 1)) {
errorMessage(error_prefix + _manager.lastError());
}
break;
case GameState::Running:
errorMessage(error_prefix + "Moving a M.A.S.S. is disabled while the game is running.");
break;
}
}
void EvtMainFrame::deleteMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Deletion failed:\n\n";
if(wxMessageBox(wxString::Format("Are you sure you want to delete the data in hangar %.2d ? This operation cannot be undone.", _installedListView->GetFirstSelected() + 1),
"Are you sure ?", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
return;
}
switch(_manager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, deleting a M.A.S.S. is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_manager.deleteMass(_installedListView->GetFirstSelected())) {
errorMessage(error_prefix + _manager.lastError());
}
break;
case GameState::Running:
errorMessage(error_prefix + "Deleting a M.A.S.S. is disabled while the game is running.");
break;
}
}
void EvtMainFrame::renameMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Rename failed:\n\n";
EvtNameChangeDialog dialog{this};
dialog.setName(*(_manager.massName(_installedListView->GetFirstSelected())));
int result = dialog.ShowModal();
if(result == wxID_OK) {
switch(_manager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, renaming a M.A.S.S. is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_manager.renameMass(_installedListView->GetFirstSelected(), dialog.getName())) {
errorMessage(error_prefix + _manager.lastError());
}
break;
case GameState::Running:
errorMessage(error_prefix + "Renaming a M.A.S.S. is disabled while the game is running.");
break;
}
}
}
void EvtMainFrame::backupEvent(wxCommandEvent&) {
const static std::string error_prefix = "Backup failed:\n\n";
wxString current_timestamp = wxDateTime::Now().Format("%Y-%m-%d_%H-%M-%S");
wxFileDialog save_dialog{this, "Choose output location", _manager.saveDirectory(),
wxString::Format("backup_%s_%s.zip", _manager.steamId(), current_timestamp), "Zip archive (*zip)|*zip",
wxFD_SAVE|wxFD_OVERWRITE_PROMPT};
if(save_dialog.ShowModal() == wxID_CANCEL) {
return;
}
if(!_manager.backupSaves(save_dialog.GetPath().ToStdString())) {
errorMessage(error_prefix + _manager.lastError());
}
}
void EvtMainFrame::openSaveDirEvent(wxCommandEvent&) {
wxExecute("explorer.exe " + Utility::Directory::toNativeSeparators(_manager.saveDirectory()));
}
void EvtMainFrame::stagingSelectionEvent(wxCommandEvent&) {
updateCommandsState();
}
void EvtMainFrame::deleteStagedEvent(wxCommandEvent&) {
if(wxMessageBox("Are you sure you want to delete the selected M.A.S.S. ? This operation cannot be undone.",
"Are you sure ?", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
return;
}
int selection = _stagingList->GetSelection();
if(selection != wxNOT_FOUND) {
_manager.deleteStagedMass(selection);
}
}
void EvtMainFrame::openStagingDirEvent(wxCommandEvent&) {
wxExecute("explorer.exe " + Utility::Directory::toNativeSeparators(_manager.stagingAreaDirectory()));
}
void EvtMainFrame::installedSelectionEvent(wxListEvent&) {
updateCommandsState();
}
void EvtMainFrame::listColumnDragEvent(wxListEvent& event) {
event.Veto();
}
void EvtMainFrame::screenshotListSelectionEvent(wxListEvent&) {
updateCommandsState();
}
void EvtMainFrame::screenshotFilenameSortingEvent(wxCommandEvent&) {
_manager.sortScreenshots(SortType::Filename);
updateScreenshotList();
}
void EvtMainFrame::screenshotCreationDateSortingEvent(wxCommandEvent&) {
_manager.sortScreenshots(SortType::CreationDate);
updateScreenshotList();
}
void EvtMainFrame::screenshotAscendingSortingEvent(wxCommandEvent&) {
_manager.sortScreenshots(SortOrder::Ascending);
updateScreenshotList();
}
void EvtMainFrame::screenshotDescendingSortingEvent(wxCommandEvent&) {
_manager.sortScreenshots(SortOrder::Descending);
updateScreenshotList();
}
void EvtMainFrame::viewScreenshotEvent(wxCommandEvent&) {
viewScreenshot();
}
void EvtMainFrame::viewScreenshotEvent(wxListEvent&) {
viewScreenshot();
}
void EvtMainFrame::deleteScreenshotEvent(wxCommandEvent&) {
if(wxMessageBox("Are you sure you want to delete the selected screenshot ? This operation cannot be undone.",
"Are you sure ?", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
return;
}
long selection = _screenshotsList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
if(selection != -1) {
_manager.deleteScreenshot(selection);
}
}
void EvtMainFrame::openScreenshotDirEvent(wxCommandEvent&) {
wxExecute("explorer.exe " + Utility::Directory::toNativeSeparators(_manager.screenshotDirectory()));
}
void EvtMainFrame::fileUpdateEvent(wxFileSystemWatcherEvent& event) {
int event_type = event.GetChangeType();
wxString event_file = event.GetPath().GetFullName();
if(event_type == wxFSW_EVENT_MODIFY && _lastWatcherEventType == wxFSW_EVENT_RENAME) {
_lastWatcherEventType = event_type;
return;
}
wxMilliSleep(50);
if(event.GetPath().GetPath(wxPATH_GET_VOLUME, wxPATH_WIN) == Utility::Directory::toNativeSeparators(_manager.saveDirectory())) {
unitFileEventHandler(event_type, event_file, event);
}
else if(event.GetPath().GetPath(wxPATH_GET_VOLUME, wxPATH_WIN) == Utility::Directory::toNativeSeparators(_manager.stagingAreaDirectory())) {
stagingFileEventHandler(event_type, event_file, event);
}
else if(event.GetPath().GetPath(wxPATH_GET_VOLUME, wxPATH_WIN) == Utility::Directory::toNativeSeparators(_manager.screenshotDirectory())) {
screenshotFileEventHandler(event_type, event_file);
}
_lastWatcherEventType = event_type;
updateCommandsState();
}
void EvtMainFrame::gameCheckTimerEvent(wxTimerEvent&) {
isGameRunning();
}
void EvtMainFrame::unitFileEventHandler(int event_type, const wxString& event_file, const wxFileSystemWatcherEvent& event) {
wxRegEx regex;
switch (event_type) {
case wxFSW_EVENT_CREATE:
case wxFSW_EVENT_DELETE:
regex.Compile(wxString::Format("Unit([0-3][0-9])%s\\.sav", _manager.steamId()), wxRE_ADVANCED);
if(regex.Matches(event_file)) {
long slot;
if(regex.GetMatch(event_file, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
}
}
break;
case wxFSW_EVENT_MODIFY:
if(_lastWatcherEventType == wxFSW_EVENT_RENAME) {
break;
}
if(event_file == _manager.profileSaveName()) {
getActiveSlot();
}
else {
regex.Compile(wxString::Format("Unit([0-3][0-9])%s\\.sav", _manager.steamId()), wxRE_ADVANCED);
if(regex.Matches(event_file)) {
long slot;
if(regex.GetMatch(event_file, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
}
}
}
break;
case wxFSW_EVENT_RENAME:
wxString new_name = event.GetNewPath().GetFullName();
long slot;
if(regex.Compile(wxString::Format("Unit([0-3][0-9])%s\\.sav\\.tmp", _manager.steamId()), wxRE_ADVANCED), regex.Matches(new_name)) {
if(regex.GetMatch(new_name, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
}
}
else if(regex.Compile(wxString::Format("Unit([0-3][0-9])%s\\.sav", _manager.steamId()), wxRE_ADVANCED), regex.Matches(new_name)) {
if(regex.GetMatch(new_name, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
if(regex.Matches(event_file)) {
if(regex.GetMatch(event_file, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
}
}
}
}
break;
}
}
void EvtMainFrame::stagingFileEventHandler(int event_type, const wxString& event_file, const wxFileSystemWatcherEvent& event) {
int index;
switch(event_type) {
case wxFSW_EVENT_CREATE:
index = _manager.updateStagedMass(event_file.ToUTF8().data());
if(index != -1) {
_stagingList->Insert(wxString::Format("%s (%s)", _manager.stagedMassName(index), event_file), index);
}
break;
case wxFSW_EVENT_DELETE:
index = _manager.removeStagedMass(event_file.ToUTF8().data());
if(index != -1) {
_stagingList->Delete(index);
}
break;
case wxFSW_EVENT_MODIFY:
index = _manager.updateStagedMass(event_file.ToUTF8().data());
if(index != -1) {
_stagingList->SetString(index, wxString::Format("%s (%s)", _manager.stagedMassName(index), event_file));
}
break;
case wxFSW_EVENT_RENAME:
index = _manager.removeStagedMass(event_file.ToUTF8().data());
if(index != -1) {
_stagingList->Delete(index);
}
index = _manager.updateStagedMass(event.GetNewPath().GetFullName().ToUTF8().data());
if(index != -1) {
_stagingList->Insert(wxString::Format("%s (%s)", _manager.stagedMassName(index), event.GetNewPath().GetFullName()), index);
}
break;
}
}
void EvtMainFrame::screenshotFileEventHandler(int event_type, const wxString& event_file) {
int index = -1;
switch(event_type) {
case wxFSW_EVENT_CREATE:
_manager.updateScreenshot(event_file.ToUTF8().data());
updateScreenshotList();
break;
case wxFSW_EVENT_DELETE:
index = _screenshotsList->FindItem(-1, event_file, true);
if(index != -1) {
_manager.removeScreenshot(index);
_screenshotsList->DeleteItem(index);
}
break;
}
}
void EvtMainFrame::initialiseListView() {
for(long i = 0; i < 32; i++) {
_installedListView->InsertItem(i, wxString::Format("%.2i", i + 1));
}
_installedListView->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
_installedListView->SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER);
refreshListView();
getActiveSlot();
}
void EvtMainFrame::isGameRunning() {
GameState state = _manager.checkGameState();
switch(state) {
case GameState::Unknown:
_gameStatus->SetLabel("unknown");
_gameStatus->SetForegroundColour(wxColour("orange"));
break;
case GameState::NotRunning:
_gameStatus->SetLabel("not running");
_gameStatus->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_CAPTIONTEXT));
break;
case GameState::Running:
_gameStatus->SetLabel("running");
_gameStatus->SetForegroundColour(wxColour("red"));
break;
}
updateCommandsState();
}
void EvtMainFrame::refreshListView() {
for(int i = 0; i < 32; i++) {
refreshHangar(i);
}
updateCommandsState();
}
void EvtMainFrame::getActiveSlot() {
char slot = _manager.activeSlot();
if(slot != -1) {
wxFont tmp_font = _installedListView->GetItemFont(slot);
tmp_font.SetWeight(wxFONTWEIGHT_NORMAL);
_installedListView->SetItemFont(slot, tmp_font);
}
slot = _manager.getActiveSlot();
if(slot != -1) {
_installedListView->SetItemFont(slot, _installedListView->GetItemFont(slot).Bold());
}
}
void EvtMainFrame::updateCommandsState() {
long selection = _installedListView->GetFirstSelected();
int staged_selection = _stagingList->GetSelection();
GameState game_state = _manager.gameState();
HangarState hangar_state = _manager.hangarState(selection);
_importButton->Enable(selection != -1 && staged_selection != -1 && game_state != GameState::Running);
_exportButton->Enable(selection != -1);
_moveButton->Enable(selection != -1 && game_state != GameState::Running && hangar_state != HangarState::Empty && hangar_state != HangarState::Invalid);
_deleteButton->Enable(selection != -1 && game_state != GameState::Running && hangar_state != HangarState::Empty);
_renameButton->Enable(selection != -1 && game_state != GameState::Running && hangar_state != HangarState::Empty);
_deleteStagedButton->Enable(staged_selection != -1);
long screenshot_selection = _screenshotsList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
_viewScreenshotButton->Enable(screenshot_selection != -1);
_deleteScreenshotButton->Enable(screenshot_selection != -1);
}
void EvtMainFrame::refreshHangar(int slot) {
if(slot < 0 && slot >= 32) {
return;
}
_manager.refreshHangar(slot);
switch(_manager.hangarState(slot)) {
case HangarState::Empty:
_installedListView->SetItem(slot, 1, "<Empty>");
break;
case HangarState::Invalid:
_installedListView->SetItem(slot, 1, "<Invalid>");
break;
case HangarState::Filled:
_installedListView->SetItem(slot, 1, *(_manager.massName(slot)));
break;
}
}
void EvtMainFrame::updateScreenshotList() {
_screenshotsList->DeleteAllItems();
_screenshotThumbs.RemoveAll();
int index = 0;
for(const Screenshot& s : _manager.screenshots()) {
_screenshotsList->InsertItem(index,
wxString::Format("%s\n%s", wxString::FromUTF8(s._filename.c_str()), s._creationDate.Format("%d/%m/%Y %H:%M:%S")),
_screenshotThumbs.Add(s._thumbnail));
++index;
}
}
void EvtMainFrame::viewScreenshot() {
long selection = _screenshotsList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
if(selection == -1) {
return;
}
wxBitmap image(Utility::Directory::toNativeSeparators(Utility::Directory::join(_manager.screenshotDirectory(), _manager.screenshots().at(selection)._filename)), wxBITMAP_TYPE_PNG);
wxDialog view_dialog;
view_dialog.Create(this, wxID_ANY, "Screenshot viewer", wxDefaultPosition, wxSize{1024, 576}, wxCAPTION|wxCLOSE_BOX|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER|wxSYSTEM_MENU);
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
wxScrolledWindow* scroller = new wxScrolledWindow(&view_dialog, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL|wxHSCROLL);
scroller->SetScrollRate(5, 5);
wxBoxSizer* scroller_sizer = new wxBoxSizer(wxVERTICAL);
wxStaticBitmap* screenshot = new wxStaticBitmap(scroller, wxID_ANY, image);
scroller_sizer->Add(screenshot, 1, wxEXPAND, 5);
scroller->SetSizer(scroller_sizer);
scroller->Layout();
scroller_sizer->FitInside(scroller);
sizer->Add(scroller, 1, wxEXPAND, 5);
view_dialog.SetSizer(sizer);
view_dialog.Layout();
sizer->FitInside(&view_dialog);
view_dialog.Centre();
view_dialog.ShowModal();
}
void EvtMainFrame::infoMessage(const wxString& message) {
wxMessageBox(message, "Information", wxOK|wxCENTRE|wxICON_INFORMATION, this);
}
void EvtMainFrame::warningMessage(const wxString& message) {
wxMessageBox(message, "Warning", wxOK|wxCENTRE|wxICON_WARNING, this);
}
void EvtMainFrame::errorMessage(const wxString& message) {
wxMessageBox(message, "Error", wxOK|wxCENTRE|wxICON_ERROR, this);
}

View File

@ -1,259 +0,0 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "MainFrame.h"
///////////////////////////////////////////////////////////////////////////
MainFrame::MainFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxSize( -1,600 ), wxDefaultSize );
wxBoxSizer* bSizerMain;
bSizerMain = new wxBoxSizer( wxVERTICAL );
_mainPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizerMainPanel;
bSizerMainPanel = new wxBoxSizer( wxVERTICAL );
_managerNotebook = new wxNotebook( _mainPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
_massPanel = new wxPanel( _managerNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizerMassPanel;
bSizerMassPanel = new wxBoxSizer( wxHORIZONTAL );
wxStaticBoxSizer* sbSizerInstalled;
sbSizerInstalled = new wxStaticBoxSizer( new wxStaticBox( _massPanel, wxID_ANY, wxT("Installed M.A.S.S.es") ), wxVERTICAL );
_installedListView = new wxListView(_massPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxLC_SINGLE_SEL|wxLC_HRULES);
_installedListView->AppendColumn("Hangar", wxLIST_FORMAT_LEFT);
_installedListView->AppendColumn("M.A.S.S. name", wxLIST_FORMAT_LEFT);
sbSizerInstalled->Add( _installedListView, 1, wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
wxStaticBoxSizer* sbSizerButtons;
sbSizerButtons = new wxStaticBoxSizer( new wxStaticBox( sbSizerInstalled->GetStaticBox(), wxID_ANY, wxT("Hangar actions") ), wxHORIZONTAL );
_moveButton = new wxButton( sbSizerButtons->GetStaticBox(), wxID_ANY, wxT("Move"), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerButtons->Add( _moveButton, 1, wxALL|wxEXPAND, 5 );
_deleteButton = new wxButton( sbSizerButtons->GetStaticBox(), wxID_ANY, wxT("Delete"), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerButtons->Add( _deleteButton, 1, wxALL|wxEXPAND, 5 );
_renameButton = new wxButton( sbSizerButtons->GetStaticBox(), wxID_ANY, wxT("Rename"), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerButtons->Add( _renameButton, 1, wxALL|wxEXPAND, 5 );
sbSizerInstalled->Add( sbSizerButtons, 0, wxEXPAND, 5 );
wxBoxSizer* bSizerSecondRow;
bSizerSecondRow = new wxBoxSizer( wxHORIZONTAL );
_zipButton = new wxButton( sbSizerInstalled->GetStaticBox(), wxID_ANY, wxT("Backup save files"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerSecondRow->Add( _zipButton, 2, wxALL|wxEXPAND, 5 );
_openSaveDirButton = new wxButton( sbSizerInstalled->GetStaticBox(), wxID_ANY, wxT("Open save directory"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerSecondRow->Add( _openSaveDirButton, 2, wxEXPAND|wxALL, 5 );
sbSizerInstalled->Add( bSizerSecondRow, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 );
bSizerMassPanel->Add( sbSizerInstalled, 1, wxEXPAND|wxALL, 5 );
wxBoxSizer* bSizerImportExport;
bSizerImportExport = new wxBoxSizer( wxVERTICAL );
_importButton = new wxButton( _massPanel, wxID_ANY, wxT("Import"), wxDefaultPosition, wxDefaultSize, 0 );
_importButton->SetBitmap( wxArtProvider::GetBitmap( wxART_GO_BACK, wxART_BUTTON ) );
bSizerImportExport->Add( _importButton, 1, wxALL|wxEXPAND, 5 );
_exportButton = new wxButton( _massPanel, wxID_ANY, wxT("Export"), wxDefaultPosition, wxDefaultSize, 0 );
_exportButton->SetBitmap( wxArtProvider::GetBitmap( wxART_GO_FORWARD, wxART_BUTTON ) );
_exportButton->SetBitmapPosition( wxRIGHT );
bSizerImportExport->Add( _exportButton, 0, wxALL|wxEXPAND, 5 );
bSizerMassPanel->Add( bSizerImportExport, 0, wxALIGN_CENTER_VERTICAL, 5 );
wxStaticBoxSizer* sbSizerStagingArea;
sbSizerStagingArea = new wxStaticBoxSizer( new wxStaticBox( _massPanel, wxID_ANY, wxT("Staging area") ), wxVERTICAL );
_stagingList = new wxListBox( sbSizerStagingArea->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_NEEDED_SB|wxLB_SINGLE );
sbSizerStagingArea->Add( _stagingList, 1, wxALL|wxEXPAND, 5 );
_deleteStagedButton = new wxButton( sbSizerStagingArea->GetStaticBox(), wxID_ANY, wxT("Delete staged M.A.S.S."), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerStagingArea->Add( _deleteStagedButton, 0, wxALL|wxEXPAND, 5 );
_stagingAreaButton = new wxButton( sbSizerStagingArea->GetStaticBox(), wxID_ANY, wxT("Open staging area directory"), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerStagingArea->Add( _stagingAreaButton, 0, wxALL|wxEXPAND, 5 );
bSizerMassPanel->Add( sbSizerStagingArea, 1, wxEXPAND|wxALL, 5 );
_massPanel->SetSizer( bSizerMassPanel );
_massPanel->Layout();
bSizerMassPanel->Fit( _massPanel );
_managerNotebook->AddPage( _massPanel, wxT("M.A.S.S.es"), false );
_screenshotsPanel = new wxPanel( _managerNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizerScreenshotsPanel;
bSizerScreenshotsPanel = new wxBoxSizer( wxHORIZONTAL );
_screenshotsList = new wxListCtrl( _screenshotsPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_ALIGN_TOP|wxLC_AUTOARRANGE|wxLC_ICON|wxLC_SINGLE_SEL );
bSizerScreenshotsPanel->Add( _screenshotsList, 1, wxALL|wxEXPAND, 5 );
wxBoxSizer* bSizerScreenshotCommands;
bSizerScreenshotCommands = new wxBoxSizer( wxVERTICAL );
wxStaticBoxSizer* sbSizerSorting;
sbSizerSorting = new wxStaticBoxSizer( new wxStaticBox( _screenshotsPanel, wxID_ANY, wxT("Sorting") ), wxVERTICAL );
wxBoxSizer* bSizerSortType;
bSizerSortType = new wxBoxSizer( wxHORIZONTAL );
_nameRadio = new wxRadioButton( sbSizerSorting->GetStaticBox(), wxID_ANY, wxT("Filename"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP );
_nameRadio->SetValue( true );
bSizerSortType->Add( _nameRadio, 1, wxALL, 5 );
_creationDateRadio = new wxRadioButton( sbSizerSorting->GetStaticBox(), wxID_ANY, wxT("Creation date"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerSortType->Add( _creationDateRadio, 1, wxALL, 5 );
sbSizerSorting->Add( bSizerSortType, 1, wxEXPAND, 5 );
wxBoxSizer* bSizerSortOrder;
bSizerSortOrder = new wxBoxSizer( wxHORIZONTAL );
_ascendingRadio = new wxRadioButton( sbSizerSorting->GetStaticBox(), wxID_ANY, wxT("Ascending"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP );
_ascendingRadio->SetValue( true );
bSizerSortOrder->Add( _ascendingRadio, 1, wxALL, 5 );
_descendingRadio = new wxRadioButton( sbSizerSorting->GetStaticBox(), wxID_ANY, wxT("Descending"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerSortOrder->Add( _descendingRadio, 1, wxALL, 5 );
sbSizerSorting->Add( bSizerSortOrder, 1, wxEXPAND, 5 );
bSizerScreenshotCommands->Add( sbSizerSorting, 0, wxEXPAND|wxALL, 5 );
_viewScreenshotButton = new wxButton( _screenshotsPanel, wxID_ANY, wxT("View"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerScreenshotCommands->Add( _viewScreenshotButton, 0, wxALL|wxEXPAND, 5 );
_deleteScreenshotButton = new wxButton( _screenshotsPanel, wxID_ANY, wxT("Delete"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerScreenshotCommands->Add( _deleteScreenshotButton, 0, wxALL|wxEXPAND, 5 );
_screenshotDirButton = new wxButton( _screenshotsPanel, wxID_ANY, wxT("Open directory"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerScreenshotCommands->Add( _screenshotDirButton, 0, wxALL|wxEXPAND, 5 );
bSizerScreenshotsPanel->Add( bSizerScreenshotCommands, 0, wxEXPAND, 5 );
_screenshotsPanel->SetSizer( bSizerScreenshotsPanel );
_screenshotsPanel->Layout();
bSizerScreenshotsPanel->Fit( _screenshotsPanel );
_managerNotebook->AddPage( _screenshotsPanel, wxT("Photo mode shots"), false );
bSizerMainPanel->Add( _managerNotebook, 1, wxEXPAND, 5 );
_riskLabel = new wxStaticText( _mainPanel, wxID_ANY, wxT("USE THIS TOOL AT YOUR OWN RISK!"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
_riskLabel->Wrap( -1 );
_riskLabel->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
_riskLabel->SetForegroundColour( wxColour( 255, 0, 0 ) );
bSizerMainPanel->Add( _riskLabel, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT, 5 );
wxBoxSizer* bSizerGameStatus;
bSizerGameStatus = new wxBoxSizer( wxHORIZONTAL );
_gameStatusLabel = new wxStaticText( _mainPanel, wxID_ANY, wxT("Game status:"), wxDefaultPosition, wxDefaultSize, 0 );
_gameStatusLabel->Wrap( -1 );
bSizerGameStatus->Add( _gameStatusLabel, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxEXPAND|wxTOP|wxBOTTOM|wxLEFT, 5 );
_gameStatus = new wxStaticText( _mainPanel, wxID_ANY, wxT("not running"), wxDefaultPosition, wxDefaultSize, 0 );
_gameStatus->Wrap( -1 );
_gameStatus->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
_gameStatus->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_CAPTIONTEXT ) );
bSizerGameStatus->Add( _gameStatus, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
bSizerMainPanel->Add( bSizerGameStatus, 0, wxALIGN_CENTER_HORIZONTAL, 5 );
_aboutText = new wxStaticText( _mainPanel, wxID_ANY, wxT("This version of the application was tested on M.A.S.S. Builder early access version 0.4.5.\nIt may or may not work with other versions of the game.\nMade for the M.A.S.S. Builder community by Guillaume Jacquemin."), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
_aboutText->Wrap( -1 );
bSizerMainPanel->Add( _aboutText, 0, wxEXPAND|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 );
_githubLink = new wxHyperlinkCtrl( _mainPanel, wxID_ANY, wxT("https://github.com/williamjcm/wxMASSManager"), wxT("https://github.com/williamjcm/wxMASSManager"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE );
bSizerMainPanel->Add( _githubLink, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
_mainPanel->SetSizer( bSizerMainPanel );
_mainPanel->Layout();
bSizerMainPanel->Fit( _mainPanel );
bSizerMain->Add( _mainPanel, 1, wxEXPAND, 5 );
this->SetSizer( bSizerMain );
this->Layout();
bSizerMain->Fit( this );
_gameCheckTimer.SetOwner( this, wxID_ANY );
this->Centre( wxBOTH );
// Connect Events
_moveButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::moveMassEvent ), NULL, this );
_deleteButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteMassEvent ), NULL, this );
_renameButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::renameMassEvent ), NULL, this );
_zipButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::backupSavesEvent ), NULL, this );
_openSaveDirButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openSaveDirEvent ), NULL, this );
_importButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::importMassEvent ), NULL, this );
_exportButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::exportMassEvent ), NULL, this );
_stagingList->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainFrame::stagingSelectionEvent ), NULL, this );
_deleteStagedButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteStagedEvent ), NULL, this );
_stagingAreaButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openStagingDirEvent ), NULL, this );
_screenshotsList->Connect( wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler( MainFrame::viewScreenshotEvent ), NULL, this );
_screenshotsList->Connect( wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler( MainFrame::screenshotListSelectionEvent ), NULL, this );
_screenshotsList->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( MainFrame::screenshotListSelectionEvent ), NULL, this );
_nameRadio->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( MainFrame::screenshotFilenameSortingEvent ), NULL, this );
_creationDateRadio->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( MainFrame::screenshotCreationDateSortingEvent ), NULL, this );
_ascendingRadio->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( MainFrame::screenshotAscendingSortingEvent ), NULL, this );
_descendingRadio->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( MainFrame::screenshotDescendingSortingEvent ), NULL, this );
_viewScreenshotButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::viewScreenshotEvent ), NULL, this );
_deleteScreenshotButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteScreenshotEvent ), NULL, this );
_screenshotDirButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openScreenshotDirEvent ), NULL, this );
this->Connect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( MainFrame::gameCheckTimerEvent ) );
}
MainFrame::~MainFrame()
{
// Disconnect Events
_moveButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::moveMassEvent ), NULL, this );
_deleteButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteMassEvent ), NULL, this );
_renameButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::renameMassEvent ), NULL, this );
_zipButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::backupSavesEvent ), NULL, this );
_openSaveDirButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openSaveDirEvent ), NULL, this );
_importButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::importMassEvent ), NULL, this );
_exportButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::exportMassEvent ), NULL, this );
_stagingList->Disconnect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainFrame::stagingSelectionEvent ), NULL, this );
_deleteStagedButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteStagedEvent ), NULL, this );
_stagingAreaButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openStagingDirEvent ), NULL, this );
_screenshotsList->Disconnect( wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler( MainFrame::viewScreenshotEvent ), NULL, this );
_screenshotsList->Disconnect( wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler( MainFrame::screenshotListSelectionEvent ), NULL, this );
_screenshotsList->Disconnect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( MainFrame::screenshotListSelectionEvent ), NULL, this );
_nameRadio->Disconnect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( MainFrame::screenshotFilenameSortingEvent ), NULL, this );
_creationDateRadio->Disconnect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( MainFrame::screenshotCreationDateSortingEvent ), NULL, this );
_ascendingRadio->Disconnect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( MainFrame::screenshotAscendingSortingEvent ), NULL, this );
_descendingRadio->Disconnect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( MainFrame::screenshotDescendingSortingEvent ), NULL, this );
_viewScreenshotButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::viewScreenshotEvent ), NULL, this );
_deleteScreenshotButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteScreenshotEvent ), NULL, this );
_screenshotDirButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openScreenshotDirEvent ), NULL, this );
this->Disconnect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( MainFrame::gameCheckTimerEvent ) );
}

View File

@ -1,714 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
#include <cstring>
#include <algorithm>
#include <wx/filename.h>
#include <wx/regex.h>
#include <wx/wfstream.h>
#include <wx/zipstrm.h>
#include <shlobj.h>
#include <wtsapi32.h>
#include <Corrade/Containers/Array.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/FormatStl.h>
#include <Corrade/Utility/String.h>
#include <Corrade/Utility/Unicode.h>
#include "MassManager.h"
constexpr unsigned char mass_name_locator[] = { 'N', 'a', 'm', 'e', '_', '4', '5', '_', 'A', '0', '3', '7', 'C', '5', 'D', '5', '4', 'E', '5', '3', '4', '5', '6', '4', '0', '7', 'B', 'D', 'F', '0', '9', '1', '3', '4', '4', '5', '2', '9', 'B', 'B', '\0', 0x0C, '\0', '\0', '\0', 'S', 't', 'r', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'y', '\0' };
constexpr unsigned char steamid_locator[] = { 'A', 'c', 'c', 'o', 'u', 'n', 't', '\0', 0x0C, '\0', '\0', '\0', 'S', 't', 'r', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'y', '\0' };
constexpr unsigned 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 unsigned 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' };
MassManager::MassManager() {
_ready = findSaveDirectory() && findSteamId();
if(!_ready) {
return;
}
std::string executable_location = Utility::Directory::path(Utility::Directory::executableLocation());
_stagingAreaDirectory = Utility::Directory::join(executable_location, "staging");
_profileSaveName = Utility::formatString("Profile{}.sav", _steamId);
for(int i = 0; i < 32; ++i) {
_hangars[i]._filename = Utility::formatString("Unit{:.2d}{}.sav", i, _steamId);
refreshHangar(i);
}
}
auto MassManager::ready() -> bool {
return _ready;
}
auto MassManager::lastError() -> std::string const& {
return _lastError;
}
auto MassManager::saveDirectory() -> std::string const& {
return _saveDirectory;
}
auto MassManager::stagingAreaDirectory() -> std::string const& {
return _stagingAreaDirectory;
}
auto MassManager::screenshotDirectory() -> std::string const& {
return _screenshotDirectory;
}
auto MassManager::steamId() -> std::string const& {
return _steamId;
}
auto MassManager::profileSaveName() -> std::string const& {
return _profileSaveName;
}
auto MassManager::checkGameState() -> GameState {
WTS_PROCESS_INFOW* process_infos = nullptr;
unsigned long process_count = 0;
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;
}
}
}
else {
_gameState = GameState::Unknown;
}
if(process_infos != nullptr) {
WTSFreeMemory(process_infos);
process_infos = nullptr;
}
return _gameState;
}
auto MassManager::gameState() -> GameState {
return _gameState;
}
auto MassManager::getActiveSlot() -> char{
auto mmap = Utility::Directory::mapRead(Utility::Directory::join(_saveDirectory, _profileSaveName));
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()) {
_activeSlot = 0;
}
else {
_lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file.";
_activeSlot = -1;
}
}
else {
_activeSlot = *(iter + 41);
}
return _activeSlot;
}
auto MassManager::activeSlot() -> char {
return _activeSlot;
}
auto MassManager::importMass(const std::string& source, int hangar) -> bool {
if(hangar < 0 && hangar >= 32) {
_lastError = "Hangar out of range in MassManager::importMass()";
return false;
}
Utility::Directory::copy(source, source + ".tmp");
{
auto mmap = Utility::Directory::map(source + ".tmp");
auto iter = std::search(mmap.begin(), mmap.end(), &steamid_locator[0], &steamid_locator[23]);
if(iter == mmap.end()) {
_lastError = "The M.A.S.S. file at " + source + " seems to be corrupt.";
Utility::Directory::rm(source + ".tmp");
return false;
}
iter += 37;
if(std::strncmp(iter, _steamId.c_str(), _steamId.length()) != 0) {
for(int i = 0; i < 17; ++i) {
*(iter + i) = _steamId[i];
}
}
}
const std::string dest = Utility::Directory::join(_saveDirectory, _hangars[hangar]._filename);
if(Utility::Directory::exists(dest)) {
Utility::Directory::rm(dest);
}
Utility::Directory::move(source + ".tmp", dest);
return true;
}
auto MassManager::importMass(int staged_index, int hangar) -> bool {
if(hangar < 0 && hangar >= 32) {
_lastError = "Hangar out of range in MassManager::importMass()";
return false;
}
int i = 0;
for(const auto& mass_info : _stagedMasses) {
if(i != staged_index) {
++i;
continue;
}
std::string source = Utility::Directory::join(_stagingAreaDirectory, mass_info.first);
Utility::Directory::copy(source, source + ".tmp");
{
auto mmap = Utility::Directory::map(source + ".tmp");
auto iter = std::search(mmap.begin(), mmap.end(), &steamid_locator[0], &steamid_locator[23]);
if(iter == mmap.end()) {
_lastError = "The M.A.S.S. file at " + source + " seems to be corrupt.";
Utility::Directory::rm(source + ".tmp");
return false;
}
iter += 37;
if(std::strncmp(iter, _steamId.c_str(), _steamId.length()) != 0) {
for(int i = 0; i < 17; ++i) {
*(iter + i) = _steamId[i];
}
}
}
const std::string dest = Utility::Directory::join(_saveDirectory, _hangars[hangar]._filename);
if(Utility::Directory::exists(dest)) {
Utility::Directory::rm(dest);
}
Utility::Directory::move(source + ".tmp", dest);
return true;
}
_lastError = "";
return false;
}
auto MassManager::exportMass(int hangar) -> bool {
if(hangar < 0 && hangar >= 32) {
_lastError = "Hangar out of range in MassManager::exportMass()";
return false;
}
if(_hangars[hangar]._state == HangarState::Empty ||
_hangars[hangar]._state == HangarState::Invalid) {
_lastError = Utility::formatString("There is no valid data to export in hangar {:.2d}", hangar);
}
auto name = _hangars[hangar]._massName;
if(!name) {
_lastError = "There was an unexpected error in MassManager::exportMass()";
return false;
}
std::string source = Utility::Directory::join(_saveDirectory, _hangars[hangar]._filename);
std::string dest = Utility::Directory::join(_stagingAreaDirectory, Utility::formatString("{}_{}.sav", _steamId, *(_hangars[hangar]._massName)));
if(!Utility::Directory::copy(source, dest)) {
_lastError = Utility::formatString("Couldn't export data from hangar {:.2d} to {}", hangar, dest);
return false;
}
return true;
}
auto MassManager::moveMass(int source, int destination) -> bool {
if(source < 0 && source >= 32) {
_lastError = "Source hangar out of range in MassManager::moveMass()";
return false;
}
if(destination < 0 && destination >= 32) {
_lastError = "Destination hangar out of range in MassManager::moveMass()";
return false;
}
std::string source_file = Utility::Directory::join(_saveDirectory, _hangars[source]._filename);
std::string dest_file = Utility::Directory::join(_saveDirectory, _hangars[destination]._filename);
HangarState dest_state = _hangars[destination]._state;
switch(dest_state) {
case HangarState::Empty:
break;
case HangarState::Invalid:
Utility::Directory::rm(dest_file);
break;
case HangarState::Filled:
Utility::Directory::move(dest_file, dest_file + ".tmp");
break;
}
Utility::Directory::move(source_file, dest_file);
if(dest_state == HangarState::Filled) {
Utility::Directory::move(dest_file + ".tmp", source_file);
}
return true;
}
auto MassManager::deleteMass(int hangar) -> bool {
if(hangar < 0 && hangar >= 32) {
_lastError = "Hangar number out of range in MassManager::deleteMass()";
return false;
}
std::string file = Utility::Directory::join(_saveDirectory, _hangars[hangar]._filename);
if(Utility::Directory::exists(file)) {
if(!Utility::Directory::rm(file)) {
_lastError = "The M.A.S.S. file couldn't be deleted.";
return false;
}
}
return true;
}
auto MassManager::renameMass(int hangar, const std::string& new_name) -> bool{
if(hangar < 0 && hangar >= 32) {
_lastError = "Hangar number out of range in MassManager::renameMass()";
return false;
}
if(new_name.length() > 32) {
_lastError = "The new name is longer than 32 characters in MassManager::renameMass()";
return false;
}
char length_difference = static_cast<char>(massName(hangar)->length() - new_name.length());
std::string mass_data = Utility::Directory::readString(Utility::Directory::join(_saveDirectory, _hangars[hangar]._filename));
auto iter = std::search(mass_data.begin(), mass_data.end(), &mass_name_locator[0], &mass_name_locator[56]);
if(iter != mass_data.end()) {
*(iter - 45) = *(iter - 45) - length_difference;
*(iter + 57) = *(iter + 57) - length_difference;
*(iter + 66) = *(iter + 66) - length_difference;
while(*(iter + 70) != '\0') {
mass_data.erase(iter + 70);
}
mass_data.insert(iter + 70, new_name.cbegin(), new_name.cend());
if(!Utility::Directory::writeString(Utility::Directory::join(_saveDirectory, _hangars[hangar]._filename), mass_data)) {
_lastError = "The file" + _hangars[hangar]._filename + " couldn't be written to.";
return false;
}
return true;
}
else {
_lastError = "Couldn't find the M.A.S.S. name in " + _hangars[hangar]._filename;
return false;
}
}
auto MassManager::backupSaves(const std::string& filename) -> bool {
if(filename.empty() || (filename.length() < 5 && !Utility::String::endsWith(filename, ".zip"))) {
_lastError = "Invalid filename " + filename + " in MassManager::backupSaves()";
return false;
}
if(Utility::Directory::exists(filename)) {
if(!Utility::Directory::rm(filename)) {
_lastError = "Couldn't overwrite " + filename + " in MassManager::backupSaves()";
}
}
wxFFileOutputStream out{filename};
wxZipOutputStream zip{out};
{
zip.PutNextEntry(_profileSaveName);
wxFFileInputStream profile_stream{Utility::Directory::toNativeSeparators(Utility::Directory::join(_saveDirectory, _profileSaveName)), "rb"};
zip.Write(profile_stream);
}
for(int i = 0; i < 32; ++i) {
std::string unit_file = Utility::Directory::join(_saveDirectory, _hangars[i]._filename);
if(Utility::Directory::exists(unit_file)) {
zip.PutNextEntry(_hangars[i]._filename);
wxFFileInputStream unit_stream{Utility::Directory::toNativeSeparators(unit_file)};
zip.Write(unit_stream);
}
}
return true;
}
void MassManager::refreshHangar(int hangar) {
if(hangar < 0 && hangar >= 32) {
return;
}
std::string unit_file = Utility::Directory::join(_saveDirectory, _hangars[hangar]._filename);
if(!Utility::Directory::exists(unit_file)) {
_hangars[hangar]._state = HangarState::Empty;
_hangars[hangar]._massName = Containers::NullOpt;
}
else {
Containers::Optional<std::string> name = getMassName(unit_file);
_hangars[hangar]._state = name ? HangarState::Filled : HangarState::Invalid;
_hangars[hangar]._massName = name;
}
}
auto MassManager::hangarState(int hangar) -> HangarState {
if(hangar < 0 && hangar >= 32) {
return HangarState::Empty;
}
return _hangars[hangar]._state;
}
auto MassManager::massName(int hangar) -> Containers::Optional<std::string> {
if(hangar < 0 && hangar >= 32) {
return Containers::NullOpt;
}
return _hangars[hangar]._massName;
}
auto MassManager::getMassName(const std::string& filename) -> Containers::Optional<std::string> {
Containers::Optional<std::string> name = Containers::NullOpt;
auto mmap = Utility::Directory::mapRead(filename);
auto iter = std::search(mmap.begin(), mmap.end(), &mass_name_locator[0], &mass_name_locator[56]);
if(iter != mmap.end()) {
name = std::string{iter + 70};
}
else {
_lastError = "Couldn't find the M.A.S.S. name in " + filename;
}
return name;
}
auto MassManager::initialiseStagingArea() -> std::vector<std::string> {
if(!Utility::Directory::exists(_stagingAreaDirectory)) {
Utility::Directory::mkpath(_stagingAreaDirectory);
}
using Utility::Directory::Flag;
std::vector<std::string> file_list = Utility::Directory::list(_stagingAreaDirectory, 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, ".sav");
});
file_list.erase(iter, file_list.end());
std::vector<std::string> mass_names;
mass_names.reserve(file_list.size());
for(const std::string& file : file_list) {
auto name = getMassName(Utility::Directory::join(_stagingAreaDirectory, file));
if(name) {
mass_names.push_back(Utility::formatString("{} ({})", *name, file));
_stagedMasses[file] = *name;
}
}
mass_names.shrink_to_fit();
return std::move(mass_names);
}
auto MassManager::updateStagedMass(const std::string& filename) -> int {
std::string file = Utility::Directory::join(_stagingAreaDirectory, filename);
if(!Utility::Directory::exists(file)) {
return -1;
}
auto name = getMassName(file);
if(!name) {
return -1;
}
_stagedMasses[filename] = *name;
int index = 0;
for(const auto& mass: _stagedMasses) {
if(mass.first != filename) {
++index;
continue;
}
return index;
}
return -1;
}
auto MassManager::removeStagedMass(const std::string& filename) -> int {
int index = 0;
for(auto it = _stagedMasses.begin(); it != _stagedMasses.end(); ++it, ++index) {
if(it->first == filename) {
_stagedMasses.erase(it);
return index;
}
}
return -1;
}
void MassManager::deleteStagedMass(int index) {
int i = 0;
for(auto it = _stagedMasses.begin(); it != _stagedMasses.end(); ++it, ++i) {
if(i == index) {
Utility::Directory::rm(Utility::Directory::join(_stagingAreaDirectory, it->first));
break;
}
}
}
auto MassManager::stagedMassName(int index) -> std::string {
int i = 0;
for(const auto& mass_info : _stagedMasses) {
if(i != index) {
++i;
continue;
}
return mass_info.second;
}
return "";
}
auto MassManager::stagedMassName(const std::string& filename) -> std::string {
auto iter = _stagedMasses.find(filename);
if(iter == _stagedMasses.end()) {
return "";
}
return iter->second;
}
void MassManager::loadScreenshots() {
using Utility::Directory::Flag;
std::vector<std::string> 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 MassManager::screenshots() -> std::vector<Screenshot> const& {
return _screenshots;
}
void MassManager::sortScreenshots(SortType type) {
_sortType = type;
sortScreenshots();
}
void MassManager::sortScreenshots(SortOrder order) {
_sortOrder = order;
sortScreenshots();
}
void MassManager::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 MassManager::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 MassManager::removeScreenshot(int index) {
if(static_cast<size_t>(index + 1) > _screenshots.size()) {
return;
}
auto it = _screenshots.begin() + index;
_screenshots.erase(it);
}
void MassManager::deleteScreenshot(int index) {
if(static_cast<size_t>(index + 1) > _screenshots.size()) {
return;
}
Utility::Directory::rm(Utility::Directory::join(_screenshotDirectory, _screenshots[index]._filename));
}
auto MassManager::findSaveDirectory() -> bool {
wchar_t h[MAX_PATH];
if(!SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, h))) {
_lastError = "SHGetFolderPathW() failed in MassManager::findSaveDirectory()";
return false;
}
_saveDirectory = Utility::Directory::join(Utility::Directory::fromNativeSeparators(Utility::Unicode::narrow(h)), "MASS_Builder/Saved/SaveGames");
if(!Utility::Directory::exists(_saveDirectory)) {
_lastError = _saveDirectory + " wasn't found.";
return false;
}
return true;
}
auto MassManager::findSteamId() -> bool {
std::vector<std::string> listing = Utility::Directory::list(_saveDirectory);
wxRegEx regex;
if(!regex.Compile("Profile([0-9]{17}).sav", wxRE_ADVANCED)) {
_lastError = "Couldn't compile the regex in MassManager::findSteamId()";
return false;
}
for(const std::string& s : listing) {
if(regex.Matches(s)) {
_steamId = regex.GetMatch(s, 1);
return true;
}
}
_lastError = "Couldn't find the profile save.";
return false;
}
auto MassManager::findScreenshotDirectory() -> bool {
wchar_t h[MAX_PATH];
if(!SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, h))) {
_lastError = "SHGetFolderPathW() failed in MassManager::findScreenshotDirectory()";
return false;
}
_screenshotDirectory = Utility::Directory::join(Utility::Directory::fromNativeSeparators(Utility::Unicode::narrow(h)), "MASS_Builder/Saved/Screenshots/WindowsNoEditor");
if(!Utility::Directory::exists(_screenshotDirectory)) {
_lastError = _screenshotDirectory + " wasn't found.";
return false;
}
return true;
}
void MassManager::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});
}

View File

@ -1,142 +0,0 @@
#ifndef MASSMANAGER_H
#define MASSMANAGER_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 <https://www.gnu.org/licenses/>.
#include <map>
#include <string>
#include <vector>
#include <Corrade/Containers/StaticArray.h>
#include <Corrade/Containers/Optional.h>
#include <wx/datetime.h>
#include <wx/image.h>
using namespace Corrade;
enum class GameState : uint8_t {
Unknown, NotRunning, Running
};
enum class HangarState : uint8_t {
Empty, Invalid, Filled
};
enum class SortType : uint8_t {
Filename, CreationDate
};
enum class SortOrder: uint8_t {
Ascending, Descending
};
struct Screenshot {
std::string _filename;
wxDateTime _creationDate;
wxImage _thumbnail;
};
class MassManager {
public:
MassManager();
auto ready() -> bool;
auto lastError() -> std::string const&;
auto saveDirectory() -> std::string const&;
auto stagingAreaDirectory() -> std::string const&;
auto screenshotDirectory() -> std::string const&;
auto steamId() -> std::string const&;
auto profileSaveName() -> std::string const&;
auto checkGameState() -> GameState;
auto gameState() -> GameState;
auto getActiveSlot() -> char;
auto activeSlot() -> char;
auto importMass(const std::string& source, int hangar) -> bool;
auto importMass(int staged_index, int hangar) -> bool;
auto exportMass(int hangar) -> bool;
auto moveMass(int source, int destination) -> bool;
auto deleteMass(int hangar) -> bool;
auto renameMass(int hangar, const std::string& new_name) -> bool;
auto backupSaves(const std::string& filename) -> bool;
void refreshHangar(int hangar);
auto hangarState(int hangar) -> HangarState;
auto massName(int hangar) -> Containers::Optional<std::string>;
auto getMassName(const std::string& filename) -> Containers::Optional<std::string>;
auto initialiseStagingArea() -> std::vector<std::string>;
auto updateStagedMass(const std::string& filename) -> int;
auto removeStagedMass(const std::string& filename) -> int;
void deleteStagedMass(int index);
auto stagedMassName(int index) -> std::string;
auto stagedMassName(const std::string& filename) -> std::string;
auto findScreenshotDirectory() -> bool;
void loadScreenshots();
auto screenshots() -> std::vector<Screenshot> 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:
auto findSaveDirectory() -> bool;
auto findSteamId() -> bool;
void addScreenshot(const std::string& filename);
bool _ready = false;
std::string _lastError = "";
std::string _stagingAreaDirectory = "";
std::string _saveDirectory = "";
std::string _screenshotDirectory = "";
std::string _steamId = "";
std::string _profileSaveName = "";
GameState _gameState = GameState::Unknown;
char _activeSlot = -1;
struct Hangar {
HangarState _state = HangarState::Empty;
Containers::Optional<std::string> _massName = Containers::NullOpt;
std::string _filename = "";
};
Containers::StaticArray<32, Hangar> _hangars{Containers::ValueInit};
std::map<std::string, std::string> _stagedMasses;
std::vector<Screenshot> _screenshots;
SortType _sortType = SortType::Filename;
SortOrder _sortOrder = SortOrder::Ascending;
};
#endif //MASSMANAGER_H

@ -1 +1 @@
Subproject commit 4aeb397c65a608b09d3aa4e19c8ba1153c82bdc4
Subproject commit cc8824e8d2c00d739adba2263217dc7ae1c30dc5

623
modules/FindCorrade.cmake Normal file
View File

@ -0,0 +1,623 @@
#.rst:
# Find Corrade
# ------------
#
# Finds the Corrade library. Basic usage::
#
# find_package(Corrade REQUIRED)
#
# This module tries to find the base Corrade library and then defines the
# following:
#
# Corrade_FOUND - Whether the base library was found
# CORRADE_LIB_SUFFIX_MODULE - Path to CorradeLibSuffix.cmake module
#
# This command will try to find only the base library, not the optional
# components, which are:
#
# Containers - Containers library
# PluginManager - PluginManager library
# TestSuite - TestSuite library
# Utility - Utility library
# rc - corrade-rc executable
#
# Example usage with specifying additional components is::
#
# find_package(Corrade REQUIRED Utility TestSuite)
#
# For each component is then defined:
#
# Corrade_*_FOUND - Whether the component was found
# Corrade::* - Component imported target
#
# The package is found if either debug or release version of each library is
# found. If both debug and release libraries are found, proper version is
# chosen based on actual build configuration of the project (i.e. Debug build
# is linked to debug libraries, Release build to release libraries).
#
# Corrade conditionally defines ``CORRADE_IS_DEBUG_BUILD`` preprocessor
# variable in case build configuration is ``Debug`` (not Corrade itself, but
# build configuration of the project using it). Useful e.g. for selecting
# proper plugin directory.
#
# Corrade defines the following custom target properties:
#
# CORRADE_CXX_STANDARD - C++ standard to require when compiling given
# target. Does nothing if :variable:`CMAKE_CXX_FLAGS` already contains
# particular standard setting flag or if given target contains
# :prop_tgt:`CMAKE_CXX_STANDARD` property. Allowed value is 11, 14 or 17.
# INTERFACE_CORRADE_CXX_STANDARD - C++ standard to require when using given
# target. Does nothing if :variable:`CMAKE_CXX_FLAGS` already contains
# particular standard setting flag or if given target contains
# :prop_tgt:`CMAKE_CXX_STANDARD` property. Allowed value is 11, 14 or 17.
# CORRADE_USE_PEDANTIC_FLAGS - Enable additional compiler/linker flags.
# Boolean.
#
# These properties are inherited from directory properties, meaning that if you
# set them on directories, they get implicitly set on all targets in given
# directory (with a possibility to do target-specific overrides). All Corrade
# libraries have the :prop_tgt:`INTERFACE_CORRADE_CXX_STANDARD` property set to
# 11, meaning that you will always have at least C++11 enabled once you link to
# any Corrade library.
#
# Features of found Corrade library are exposed in these variables:
#
# CORRADE_MSVC2019_COMPATIBILITY - Defined if compiled with compatibility
# mode for MSVC 2019
# CORRADE_MSVC2017_COMPATIBILITY - Defined if compiled with compatibility
# mode for MSVC 2017
# CORRADE_MSVC2015_COMPATIBILITY - Defined if compiled with compatibility
# mode for MSVC 2015
# CORRADE_BUILD_DEPRECATED - Defined if compiled with deprecated APIs
# included
# CORRADE_BUILD_STATIC - Defined if compiled as static libraries.
# Default are shared libraries.
# CORRADE_BUILD_STATIC_UNIQUE_GLOBALS - Defined if static libraries keep their
# globals unique even across different shared libraries. Enabled by default
# for static builds.
# CORRADE_BUILD_MULTITHREADED - Defined if compiled in a way that makes it
# possible to safely use certain Corrade features simultaneously in multiple
# threads
# CORRADE_TARGET_UNIX - Defined if compiled for some Unix flavor
# (Linux, BSD, macOS)
# CORRADE_TARGET_APPLE - Defined if compiled for Apple platforms
# CORRADE_TARGET_IOS - Defined if compiled for iOS (device or
# simulator)
# CORRADE_TARGET_IOS_SIMULATOR - Defined if compiled for iOS Simulator
# CORRADE_TARGET_WINDOWS - Defined if compiled for Windows
# CORRADE_TARGET_WINDOWS_RT - Defined if compiled for Windows RT
# CORRADE_TARGET_EMSCRIPTEN - Defined if compiled for Emscripten
# CORRADE_TARGET_ANDROID - Defined if compiled for Android
# CORRADE_TARGET_GCC - Defined if compiling with GCC or GCC-
# compatible Clang
# CORRADE_TARGET_CLANG - Defined if compiling with Clang or any of its
# variants
# CORRADE_TARGET_APPLE_CLANG - Defined if compiling with Apple's Clang
# CORRADE_TARGET_CLANG_CL - Defined if compiling with Clang-CL (Clang
# with a MSVC frontend)
# CORRADE_TARGET_MSVC - Defined if compiling with MSVC or Clang with
# a MSVC frontend
# CORRADE_TARGET_MINGW - Defined if compiling under MinGW
# CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT - Defined if PluginManager
# doesn't support dynamic plugin loading due to platform limitations
# CORRADE_TESTSUITE_TARGET_XCTEST - Defined if TestSuite is targetting Xcode
# XCTest
# CORRADE_UTILITY_USE_ANSI_COLORS - Defined if ANSI escape sequences are used
# for colored output with Utility::Debug on Windows
#
# Additionally these variables are defined for internal usage:
#
# CORRADE_INCLUDE_DIR - Root include dir
# CORRADE_*_LIBRARY_DEBUG - Debug version of given library, if found
# CORRADE_*_LIBRARY_RELEASE - Release version of given library, if found
# CORRADE_*_EXECUTABLE - Location of given executable, if found
# CORRADE_USE_MODULE - Path to UseCorrade.cmake module (included
# automatically)
# CORRADE_TESTSUITE_XCTEST_RUNNER - Path to XCTestRunner.mm.in file
# CORRADE_TESTSUITE_ADB_RUNNER - Path to AdbRunner.sh file
# CORRADE_PEDANTIC_COMPILER_OPTIONS - List of pedantic compiler options used
# for targets with :prop_tgt:`CORRADE_USE_PEDANTIC_FLAGS` enabled
# CORRADE_PEDANTIC_COMPILER_DEFINITIONS - List of pedantic compiler
# definitions used for targets with :prop_tgt:`CORRADE_USE_PEDANTIC_FLAGS`
# enabled
# CORRADE_CXX{11,14,17,20}_STANDARD_FLAG - Compiler flag to use for targeting
# C++11, 14, 17 or 20 in cases where it's not possible to use
# :prop_tgt:`CORRADE_CXX_STANDARD`. Not defined if a standard switch is
# already present in :variable:`CMAKE_CXX_FLAGS`.
#
# Corrade provides these macros and functions:
#
# .. command:: corrade_add_test
#
# Add unit test using Corrade's TestSuite::
#
# corrade_add_test(<test name>
# <sources>...
# [LIBRARIES <libraries>...]
# [FILES <files>...]
# [ARGUMENTS <arguments>...])
#
# Test name is also executable name. You can use ``LIBRARIES`` to specify
# libraries to link with instead of using :command:`target_link_libraries()`.
# The ``Corrade::TestSuite`` target is linked automatically to each test. Note
# that the :command:`enable_testing()` function must be called explicitly.
# Arguments passed after ``ARGUMENTS`` will be appended to the test
# command line. ``ARGUMENTS`` are supported everywhere except when
# ``CORRADE_TESTSUITE_TARGET_XCTEST`` is enabled.
#
# You can list files needed by the test in the ``FILES`` section. If given
# filename is relative, it is treated relatively to `CMAKE_CURRENT_SOURCE_DIR`.
# The files are added to the :prop_test:`REQUIRED_FILES` target property. On
# Emscripten they are bundled to the executable and available in the virtual
# filesystem root. On Android they are copied along the executable to the
# target. In case of Emscripten and Android, if the file is absolute or
# contains ``..``, only the leaf name is used. Alternatively you can have a
# filename formatted as ``<input>@<output>``, in which case the ``<input>`` is
# treated as local filesystem location and ``<output>`` as remote/virtual
# filesystem location. The remote location can't be absolute or contain ``..``
# / ``@`` characters.
#
# Unless :variable:`CORRADE_TESTSUITE_TARGET_XCTEST` is set, test cases on iOS
# targets are created as bundles with bundle identifier set to CMake project
# name by default. Use the cache variable :variable:`CORRADE_TESTSUITE_BUNDLE_IDENTIFIER_PREFIX`
# to change it to something else.
#
# .. command:: corrade_add_resource
#
# Compile data resources into application binary::
#
# corrade_add_resource(<name> <resources.conf>)
#
# Depends on ``Corrade::rc``, which is part of Corrade utilities. This command
# generates resource data using given configuration file in current build
# directory. Argument name is name under which the resources can be explicitly
# loaded. Variable ``<name>`` contains compiled resource filename, which is
# then used for compiling library / executable. On CMake >= 3.1 the
# `resources.conf` file can contain UTF-8-encoded filenames. Example usage::
#
# corrade_add_resource(app_resources resources.conf)
# add_executable(app source1 source2 ... ${app_resources})
#
# .. command:: corrade_add_plugin
#
# Add dynamic plugin::
#
# corrade_add_plugin(<plugin name>
# "<debug binary install dir>;<debug library install dir>"
# "<release binary install dir>;<release library install dir>"
# <metadata file>
# <sources>...)
#
# The macro adds a preprocessor directive ``CORRADE_DYNAMIC_PLUGIN`` when
# compiling ``<sources>``. Additional libraries can be linked in via
# :command:`target_link_libraries(plugin_name ...) <target_link_libraries>`.
# On DLL platforms, the plugin DLLs and metadata files are put into
# ``<debug binary install dir>`` / ``<release binary install dir>`` and the
# ``*.lib`` files into ``<debug library install dir>`` /
# ``<release library install dir>``. On non-DLL platforms everything is put
# into ``<debug library install dir>`` / ``<release library install dir>``.
#
# If the plugin interface disables plugin metadata files, the
# ``<metadata file>`` can be set to ``""``, in which case no metadata file is
# copied anywhere. Otherwise the metadata file is copied and renamed to
# ``<plugin name>``, retaining its original extension.
#
# corrade_add_plugin(<plugin name>
# <debug install dir>
# <release install dir>
# <metadata file>
# <sources>...)
#
# Unline the above version this puts everything into ``<debug install dir>`` on
# both DLL and non-DLL platforms. If ``<debug install dir>`` is set to
# :variable:`CMAKE_CURRENT_BINARY_DIR` (e.g. for testing purposes), the files
# are copied directly, without the need to perform install step. Note that the
# files are actually put into configuration-based subdirectory, i.e.
# ``${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}``. See documentation of
# :variable:`CMAKE_CFG_INTDIR` variable for more information.
#
# .. command:: corrade_add_static_plugin
#
# Add static plugin::
#
# corrade_add_static_plugin(<plugin name>
# "<binary install dir>;<library install dir>"
# <metadata file>
# <sources>...)
#
# The macro adds a preprocessor directive ``CORRADE_STATIC_PLUGIN`` when
# compiling ``<sources>``. Additional libraries can be linked in via
# :command:`target_link_libraries(plugin_name ...) <target_link_libraries>`.
# The ``<binary install dir>`` is ignored and included just for compatibility
# with the :command:`corrade_add_plugin` command, everything is installed into
# ``<library install dir>``. Note that plugins built in debug configuration
# (e.g. with :variable:`CMAKE_BUILD_TYPE` set to ``Debug``) have ``"-d"``
# suffix to make it possible to have both debug and release plugins installed
# alongside each other.
#
# If the plugin interface disables plugin metadata files, the
# ``<metadata file>`` can be set to ``""``, in which case no metadata file is
# used. Otherwise the metadata file is bundled and renamed to
# ``<plugin name>``, retaining its original extension.
#
# corrade_add_static_plugin(<plugin name>
# <install dir>
# <metadata file>
# <sources>...)
#
# Equivalent to the above with ``<library install dir>`` set to ``<install dir>``.
# If ``<install dir>`` is set to :variable:`CMAKE_CURRENT_BINARY_DIR` (e.g. for
# testing purposes), no installation rules are added.
#
# .. command:: corrade_find_dlls_for_libs
#
# Find corresponding DLLs for library files::
#
# corrade_find_dlls_for_libs(<output variable> <libs>...)
#
# Available only on Windows, for all ``*.lib`` files tries to find
# corresponding DLL file. Useful for bundling dependencies for e.g. WinRT
# packages.
#
#
# This file is part of Corrade.
#
# Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
# 2017, 2018, 2019, 2020, 2021
# Vladimír Vondruš <mosra@centrum.cz>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
# Root include dir
find_path(CORRADE_INCLUDE_DIR
NAMES Corrade/Corrade.h)
mark_as_advanced(CORRADE_INCLUDE_DIR)
# Configuration file
find_file(_CORRADE_CONFIGURE_FILE configure.h
HINTS ${CORRADE_INCLUDE_DIR}/Corrade/)
mark_as_advanced(_CORRADE_CONFIGURE_FILE)
# We need to open configure.h file from CORRADE_INCLUDE_DIR before we check for
# the components. Bail out with proper error message if it wasn't found. The
# complete check with all components is further below.
if(NOT CORRADE_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Corrade
REQUIRED_VARS CORRADE_INCLUDE_DIR _CORRADE_CONFIGURE_FILE)
endif()
# Read flags from configuration
file(READ ${_CORRADE_CONFIGURE_FILE} _corradeConfigure)
string(REGEX REPLACE ";" "\\\\;" _corradeConfigure "${_corradeConfigure}")
string(REGEX REPLACE "\n" ";" _corradeConfigure "${_corradeConfigure}")
set(_corradeFlags
MSVC2015_COMPATIBILITY
MSVC2017_COMPATIBILITY
MSVC2019_COMPATIBILITY
BUILD_DEPRECATED
BUILD_STATIC
BUILD_STATIC_UNIQUE_GLOBALS
BUILD_MULTITHREADED
TARGET_UNIX
TARGET_APPLE
TARGET_IOS
TARGET_IOS_SIMULATOR
TARGET_WINDOWS
TARGET_WINDOWS_RT
TARGET_EMSCRIPTEN
TARGET_ANDROID
# TARGET_X86 etc and TARGET_LIBCXX are not exposed to CMake as the meaning
# is unclear on platforms with multi-arch binaries or when mixing different
# STL implementations. TARGET_GCC etc are figured out via UseCorrade.cmake,
# as the compiler can be different when compiling the lib & when using it.
PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT
TESTSUITE_TARGET_XCTEST
UTILITY_USE_ANSI_COLORS)
foreach(_corradeFlag ${_corradeFlags})
list(FIND _corradeConfigure "#define CORRADE_${_corradeFlag}" _corrade_${_corradeFlag})
if(NOT _corrade_${_corradeFlag} EQUAL -1)
set(CORRADE_${_corradeFlag} 1)
endif()
endforeach()
# CMake module dir
find_path(_CORRADE_MODULE_DIR
NAMES UseCorrade.cmake CorradeLibSuffix.cmake
PATH_SUFFIXES share/cmake/Corrade)
mark_as_advanced(_CORRADE_MODULE_DIR)
set(CORRADE_USE_MODULE ${_CORRADE_MODULE_DIR}/UseCorrade.cmake)
set(CORRADE_LIB_SUFFIX_MODULE ${_CORRADE_MODULE_DIR}/CorradeLibSuffix.cmake)
# Component distinction (listing them explicitly to avoid mistakes with finding
# unknown components)
set(_CORRADE_LIBRARY_COMPONENTS
Containers Interconnect Main PluginManager TestSuite Utility)
set(_CORRADE_HEADER_ONLY_COMPONENTS Containers)
if(NOT CORRADE_TARGET_WINDOWS)
# CorradeMain is a real library only on windows, a dummy target elsewhere
list(APPEND _CORRADE_HEADER_ONLY_COMPONENTS Main)
endif()
set(_CORRADE_EXECUTABLE_COMPONENTS rc)
# Currently everything is enabled implicitly. Keep in sync with Corrade's root
# CMakeLists.txt.
set(_CORRADE_IMPLICITLY_ENABLED_COMPONENTS
Containers Interconnect Main PluginManager TestSuite Utility rc)
# Inter-component dependencies
set(_CORRADE_Containers_DEPENDENCIES Utility)
set(_CORRADE_Interconnect_DEPENDENCIES Containers Utility)
set(_CORRADE_PluginManager_DEPENDENCIES Containers Utility rc)
set(_CORRADE_TestSuite_DEPENDENCIES Containers Utility Main) # see below
set(_CORRADE_Utility_DEPENDENCIES Containers rc)
# Ensure that all inter-component dependencies are specified as well
foreach(_component ${Corrade_FIND_COMPONENTS})
# Mark the dependencies as required if the component is also required
if(Corrade_FIND_REQUIRED_${_component})
foreach(_dependency ${_CORRADE_${_component}_DEPENDENCIES})
set(Corrade_FIND_REQUIRED_${_dependency} TRUE)
endforeach()
endif()
list(APPEND _CORRADE_ADDITIONAL_COMPONENTS ${_CORRADE_${_component}_DEPENDENCIES})
endforeach()
# Main is linked only in corrade_add_test(), not to everything that depends on
# TestSuite, so remove it from the list again once we filled the above
# variables
set(_CORRADE_TestSuite_DEPENDENCIES Containers Utility)
# Join the lists, remove duplicate components
set(_CORRADE_ORIGINAL_FIND_COMPONENTS ${Corrade_FIND_COMPONENTS})
if(_CORRADE_ADDITIONAL_COMPONENTS)
list(INSERT Corrade_FIND_COMPONENTS 0 ${_CORRADE_ADDITIONAL_COMPONENTS})
endif()
if(Corrade_FIND_COMPONENTS)
list(REMOVE_DUPLICATES Corrade_FIND_COMPONENTS)
endif()
# Find all components
foreach(_component ${Corrade_FIND_COMPONENTS})
string(TOUPPER ${_component} _COMPONENT)
# Create imported target in case the library is found. If the project is
# added as subproject to CMake, the target already exists and all the
# required setup is already done from the build tree.
if(TARGET Corrade::${_component})
set(Corrade_${_component}_FOUND TRUE)
else()
# Library (and not header-only) components
if(_component IN_LIST _CORRADE_LIBRARY_COMPONENTS AND NOT _component IN_LIST _CORRADE_HEADER_ONLY_COMPONENTS)
add_library(Corrade::${_component} UNKNOWN IMPORTED)
# Try to find both debug and release version
find_library(CORRADE_${_COMPONENT}_LIBRARY_DEBUG Corrade${_component}-d)
find_library(CORRADE_${_COMPONENT}_LIBRARY_RELEASE Corrade${_component})
mark_as_advanced(CORRADE_${_COMPONENT}_LIBRARY_DEBUG
CORRADE_${_COMPONENT}_LIBRARY_RELEASE)
if(CORRADE_${_COMPONENT}_LIBRARY_RELEASE)
set_property(TARGET Corrade::${_component} APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET Corrade::${_component} PROPERTY
IMPORTED_LOCATION_RELEASE ${CORRADE_${_COMPONENT}_LIBRARY_RELEASE})
endif()
if(CORRADE_${_COMPONENT}_LIBRARY_DEBUG)
set_property(TARGET Corrade::${_component} APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET Corrade::${_component} PROPERTY
IMPORTED_LOCATION_DEBUG ${CORRADE_${_COMPONENT}_LIBRARY_DEBUG})
endif()
endif()
# Header-only library components
if(_component IN_LIST _CORRADE_HEADER_ONLY_COMPONENTS)
add_library(Corrade::${_component} INTERFACE IMPORTED)
endif()
# Default include path names to look for for library / header-only
# components
if(_component IN_LIST _CORRADE_LIBRARY_COMPONENTS)
set(_CORRADE_${_COMPONENT}_INCLUDE_PATH_SUFFIX Corrade/${_component})
set(_CORRADE_${_COMPONENT}_INCLUDE_PATH_NAMES ${_component}.h)
endif()
# Executable components
if(_component IN_LIST _CORRADE_EXECUTABLE_COMPONENTS)
add_executable(Corrade::${_component} IMPORTED)
find_program(CORRADE_${_COMPONENT}_EXECUTABLE corrade-${_component})
mark_as_advanced(CORRADE_${_COMPONENT}_EXECUTABLE)
if(CORRADE_${_COMPONENT}_EXECUTABLE)
set_property(TARGET Corrade::${_component} PROPERTY
IMPORTED_LOCATION ${CORRADE_${_COMPONENT}_EXECUTABLE})
endif()
endif()
# No special setup for Containers library
# Interconnect library
if(_component STREQUAL Interconnect)
# Disable /OPT:ICF on MSVC, which merges functions with identical
# contents and thus breaks signal comparison
if(CORRADE_TARGET_WINDOWS AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
if(CMAKE_VERSION VERSION_LESS 3.13)
set_property(TARGET Corrade::${_component} PROPERTY
INTERFACE_LINK_LIBRARIES "-OPT:NOICF,REF")
else()
set_property(TARGET Corrade::${_component} PROPERTY
INTERFACE_LINK_OPTIONS "/OPT:NOICF,REF")
endif()
endif()
# Main library
elseif(_component STREQUAL Main)
set(_CORRADE_${_COMPONENT}_INCLUDE_PATH_SUFFIX Corrade)
set(_CORRADE_${_COMPONENT}_INCLUDE_PATH_NAMES Corrade.h)
if(CORRADE_TARGET_WINDOWS)
if(NOT MINGW)
# Abusing INTERFACE_LINK_LIBRARIES because
# INTERFACE_LINK_OPTIONS is only since 3.13. They treat
# things with `-` in front as linker flags and fortunately
# I can use `-ENTRY` instead of `/ENTRY`.
# https://gitlab.kitware.com/cmake/cmake/issues/16543
set_property(TARGET Corrade::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES "-ENTRY:$<$<NOT:$<BOOL:$<TARGET_PROPERTY:WIN32_EXECUTABLE>>>:wmainCRTStartup>$<$<BOOL:$<TARGET_PROPERTY:WIN32_EXECUTABLE>>:wWinMainCRTStartup>")
else()
set_property(TARGET Corrade::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES "-municode")
endif()
endif()
# PluginManager library
elseif(_component STREQUAL PluginManager)
# -ldl is handled by Utility now
# TestSuite library has some additional files
elseif(_component STREQUAL TestSuite)
# XCTest runner file
if(CORRADE_TESTSUITE_TARGET_XCTEST)
find_file(CORRADE_TESTSUITE_XCTEST_RUNNER XCTestRunner.mm.in
PATH_SUFFIXES share/corrade/TestSuite)
set(CORRADE_TESTSUITE_XCTEST_RUNNER_NEEDED CORRADE_TESTSUITE_XCTEST_RUNNER)
# ADB runner file
elseif(CORRADE_TARGET_ANDROID)
find_file(CORRADE_TESTSUITE_ADB_RUNNER AdbRunner.sh
PATH_SUFFIXES share/corrade/TestSuite)
set(CORRADE_TESTSUITE_ADB_RUNNER_NEEDED CORRADE_TESTSUITE_ADB_RUNNER)
# Emscripten runner file
elseif(CORRADE_TARGET_EMSCRIPTEN)
find_file(CORRADE_TESTSUITE_EMSCRIPTEN_RUNNER EmscriptenRunner.html.in
PATH_SUFFIXES share/corrade/TestSuite)
set(CORRADE_TESTSUITE_EMSCRIPTEN_RUNNER_NEEDED CORRADE_TESTSUITE_EMSCRIPTEN_RUNNER)
endif()
# Utility library (contains all setup that is used by others)
elseif(_component STREQUAL Utility)
# Top-level include directory
set_property(TARGET Corrade::${_component} APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES ${CORRADE_INCLUDE_DIR})
# Require (at least) C++11 for users
set_property(TARGET Corrade::${_component} PROPERTY
INTERFACE_CORRADE_CXX_STANDARD 11)
set_property(TARGET Corrade::${_component} APPEND PROPERTY
COMPATIBLE_INTERFACE_NUMBER_MAX CORRADE_CXX_STANDARD)
# Directory::libraryLocation() needs this
if(CORRADE_TARGET_UNIX)
set_property(TARGET Corrade::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES ${CMAKE_DL_LIBS})
endif()
# AndroidLogStreamBuffer class needs to be linked to log library
if(CORRADE_TARGET_ANDROID)
set_property(TARGET Corrade::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES "log")
endif()
endif()
# Find library includes
if(_component IN_LIST _CORRADE_LIBRARY_COMPONENTS)
find_path(_CORRADE_${_COMPONENT}_INCLUDE_DIR
NAMES ${_CORRADE_${_COMPONENT}_INCLUDE_PATH_NAMES}
HINTS ${CORRADE_INCLUDE_DIR}/${_CORRADE_${_COMPONENT}_INCLUDE_PATH_SUFFIX})
mark_as_advanced(_CORRADE_${_COMPONENT}_INCLUDE_DIR)
endif()
# Add inter-library dependencies
if(_component IN_LIST _CORRADE_LIBRARY_COMPONENTS OR _component IN_LIST _CORRADE_HEADER_ONLY_COMPONENTS)
foreach(_dependency ${_CORRADE_${_component}_DEPENDENCIES})
if(_dependency IN_LIST _CORRADE_LIBRARY_COMPONENTS OR _dependency IN_LIST _CORRADE_HEADER_ONLY_COMPONENTS)
set_property(TARGET Corrade::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES Corrade::${_dependency})
endif()
endforeach()
endif()
# Decide if the component was found
if((_component IN_LIST _CORRADE_LIBRARY_COMPONENTS AND _CORRADE_${_COMPONENT}_INCLUDE_DIR AND (_component IN_LIST _CORRADE_HEADER_ONLY_COMPONENTS OR CORRADE_${_COMPONENT}_LIBRARY_RELEASE OR CORRADE_${_COMPONENT}_LIBRARY_DEBUG)) OR (_component IN_LIST _CORRADE_EXECUTABLE_COMPONENTS AND CORRADE_${_COMPONENT}_EXECUTABLE))
set(Corrade_${_component}_FOUND TRUE)
else()
set(Corrade_${_component}_FOUND FALSE)
endif()
endif()
endforeach()
# For CMake 3.16+ with REASON_FAILURE_MESSAGE, provide additional potentially
# useful info about the failed components.
if(NOT CMAKE_VERSION VERSION_LESS 3.16)
set(_CORRADE_REASON_FAILURE_MESSAGE )
# Go only through the originally specified find_package() components, not
# the dependencies added by us afterwards
foreach(_component ${_CORRADE_ORIGINAL_FIND_COMPONENTS})
if(Corrade_${_component}_FOUND)
continue()
endif()
# If it's not known at all, tell the user -- it might be a new library
# and an old Find module, or something platform-specific.
if(NOT _component IN_LIST _CORRADE_LIBRARY_COMPONENTS AND NOT _component IN_LIST _CORRADE_EXECUTABLE_COMPONENTS)
list(APPEND _CORRADE_REASON_FAILURE_MESSAGE "${_component} is not a known component on this platform.")
# Otherwise, if it's not among implicitly built components, hint that
# the user may need to enable it.
# TODO: currently, the _FOUND variable doesn't reflect if dependencies
# were found. When it will, this needs to be updated to avoid
# misleading messages.
elseif(NOT _component IN_LIST _CORRADE_IMPLICITLY_ENABLED_COMPONENTS)
string(TOUPPER ${_component} _COMPONENT)
list(APPEND _CORRADE_REASON_FAILURE_MESSAGE "${_component} is not built by default. Make sure you enabled WITH_${_COMPONENT} when building Corrade.")
# Otherwise we have no idea. Better be silent than to print something
# misleading.
else()
endif()
endforeach()
string(REPLACE ";" " " _CORRADE_REASON_FAILURE_MESSAGE "${_CORRADE_REASON_FAILURE_MESSAGE}")
set(_CORRADE_REASON_FAILURE_MESSAGE REASON_FAILURE_MESSAGE "${_CORRADE_REASON_FAILURE_MESSAGE}")
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Corrade REQUIRED_VARS
CORRADE_INCLUDE_DIR
_CORRADE_MODULE_DIR
_CORRADE_CONFIGURE_FILE
${CORRADE_TESTSUITE_XCTEST_RUNNER_NEEDED}
${CORRADE_TESTSUITE_ADB_RUNNER_NEEDED}
${CORRADE_TESTSUITE_EMSCRIPTEN_RUNNER_NEEDED}
HANDLE_COMPONENTS
${_CORRADE_REASON_FAILURE_MESSAGE})
# Finalize the finding process
include(${CORRADE_USE_MODULE})
set(CORRADE_INCLUDE_INSTALL_DIR include/Corrade)
if(CORRADE_BUILD_DEPRECATED AND CORRADE_INCLUDE_INSTALL_PREFIX AND NOT CORRADE_INCLUDE_INSTALL_PREFIX STREQUAL ".")
message(DEPRECATION "CORRADE_INCLUDE_INSTALL_PREFIX is obsolete as its primary use was for old Android NDK versions. Please switch to the NDK r19+ layout instead of using this variable and recreate your build directory to get rid of this warning.")
set(CORRADE_INCLUDE_INSTALL_DIR ${CORRADE_INCLUDE_INSTALL_PREFIX}/${CORRADE_INCLUDE_INSTALL_DIR})
endif()

87
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,87 @@
# wxMASSManager
# Copyright (C) 2020-2021 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/>.
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Corrade REQUIRED Containers Utility)
include_directories(SYSTEM "C:/msys64/mingw64/lib/wx/include/msw-unicode-static-3.0")
include_directories(SYSTEM "C:/msys64/mingw64/include/wx-3.0")
set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON)
add_executable(wxMASSManager WIN32
main.cpp
GUI/MainFrame.fbp
GUI/MainFrame.h
GUI/MainFrame.cpp
GUI/EvtMainFrame.h
GUI/EvtMainFrame.cpp
GUI/NameChangeDialog.fbp
GUI/NameChangeDialog.h
GUI/NameChangeDialog.cpp
GUI/EvtNameChangeDialog.h
GUI/EvtNameChangeDialog.cpp
Maps/LastMissionId.h
Maps/StoryProgress.h
Mass/Mass.h
Mass/Mass.cpp
MassBuilderManager/MassBuilderManager.h
MassBuilderManager/MassBuilderManager.cpp
MassManager/MassManager.h
MassManager/MassManager.cpp
Profile/Locators.h
Profile/Profile.h
Profile/Profile.cpp
ProfileManager/ProfileManager.h
ProfileManager/ProfileManager.cpp
resource.rc)
target_compile_options(wxMASSManager PRIVATE -D_FILE_OFFSET_BITS=64 -D__WXMSW__)
target_link_options(wxMASSManager PRIVATE -static -static-libgcc -static-libstdc++ -pipe -Wl,--subsystem,windows -mwindows)
target_link_libraries(wxMASSManager PRIVATE
Corrade::Containers
Corrade::Utility
wx_mswu_propgrid-3.0
wx_mswu_adv-3.0
wx_mswu_core-3.0
wx_baseu-3.0
wxregexu-3.0
wxexpat-3.0
wxtiff-3.0
wxjpeg-3.0
wxpng-3.0
wxzlib-3.0
rpcrt4
oleaut32
ole32
uuid
lzma
jbig
winspool
winmm
shell32
comctl32
comdlg32
advapi32
wsock32
gdi32
oleacc
wtsapi32)

865
src/GUI/EvtMainFrame.cpp Normal file
View File

@ -0,0 +1,865 @@
// wxMASSManager
// Copyright (C) 2020-2021 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/version.h>
#if !(CORRADE_VERSION_YEAR * 100 + CORRADE_VERSION_MONTH >= 202006)
#error This application requires Corrade 2020.06 or later to build.
#endif
#include <wx/busyinfo.h>
#include <wx/filedlg.h>
#include <wx/menu.h>
#include <wx/msgdlg.h>
#include <wx/numdlg.h>
#include <wx/regex.h>
#include <wx/scrolwin.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/FormatStl.h>
#include "EvtNameChangeDialog.h"
#include "EvtMainFrame.h"
#include "../Maps/LastMissionId.h"
#include "../Maps/StoryProgress.h"
EvtMainFrame::EvtMainFrame(wxWindow* parent):
MainFrame(parent),
_profileManager{_mbManager.saveDirectory()}
{
SetIcon(wxIcon("MAINICON"));
warningMessage(wxString::FromUTF8("Before you start using this app, a few things you should know:\n\n"
"For this application to work properly, it is recommended to disable Steam Cloud syncing for the game.\nTo disable it, right-click the game in your Steam library, click \"Properties\", go to the \"General\" tab, and uncheck \"Keep game saves in the Steam Cloud for M.A.S.S. Builder\".\n\n"
"DISCLAIMER: The developer of this application (Guillaume Jacquemin) isn't associated with Vermillion Digital, and both parties cannot be held responsible for data loss or corruption this app might cause. PLEASE USE AT YOUR OWN RISK!\n\n"
"Last but not least, this application is released under the terms of the GNU General Public Licence version 3. Please see the COPYING file for more details."));
if(!_mbManager.ready()) {
errorMessage("There was an error initialising the manager:\n\n" + _mbManager.lastError());
return;
}
if(!_profileManager.ready()) {
errorMessage("There was an error initialising the manager:\n\n" + _profileManager.lastError());
return;
}
std::size_t default_profile = 0;
int counter = 0;
for(const Profile& p : _profileManager.profiles()) {
if(p.valid()) {
_profileChoice->Append(wxString::Format("%s%s", p.companyName(), p.type() == ProfileType::Demo ? " (Demo)" : ""));
if(p.type() == ProfileType::FullGame && default_profile == 0) {
default_profile = counter;
}
counter++;
}
}
_profileManager.setProfile(default_profile);
_profileChoice->SetSelection(default_profile);
_massManager.emplace(_profileManager.profileDirectory(),
_profileManager.currentProfile()->steamId(),
_profileManager.currentProfile()->type() == ProfileType::Demo);
updateProfileStats();
initialiseListView();
isGameRunning();
_installedListView->Connect(wxEVT_LIST_ITEM_SELECTED, wxListEventHandler(EvtMainFrame::installedSelectionEvent), nullptr, this);
_installedListView->Connect(wxEVT_LIST_ITEM_DESELECTED, wxListEventHandler(EvtMainFrame::installedSelectionEvent), nullptr, this);
_installedListView->Connect(wxEVT_LIST_BEGIN_DRAG, wxListEventHandler(EvtMainFrame::listColumnDragEvent), nullptr, this);
_installedListView->Connect(wxEVT_LIST_COL_BEGIN_DRAG, wxListEventHandler(EvtMainFrame::listColumnDragEvent), nullptr, this);
_watcher.Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(EvtMainFrame::fileUpdateEvent), nullptr, this);
_watcher.AddTree(wxFileName{Utility::Directory::toNativeSeparators(_massManager->saveDirectory()), wxPATH_WIN},
wxFSW_EVENT_CREATE|wxFSW_EVENT_DELETE|wxFSW_EVENT_MODIFY|wxFSW_EVENT_RENAME,
wxString::Format("%s*%s.sav", _profileManager.currentProfile()->type() == ProfileType::Demo ? "Demo" : "", _profileManager.currentProfile()->steamId()));
auto staged_masses = _massManager->stagedMasses();
for(const auto& s : staged_masses) {
_stagingList->Append(wxString::Format("%s (%s)", s.second, s.first));
}
_watcher.AddTree(wxFileName(Utility::Directory::toNativeSeparators(_massManager->stagingAreaDirectory()), wxPATH_WIN),
wxFSW_EVENT_CREATE|wxFSW_EVENT_DELETE|wxFSW_EVENT_MODIFY|wxFSW_EVENT_RENAME, "*.sav");
_gameCheckTimer.Start(2000);
initStoryProgressMenu();
}
EvtMainFrame::~EvtMainFrame() {
_watcher.RemoveAll();
_watcher.Disconnect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(EvtMainFrame::fileUpdateEvent), nullptr, this);
_installedListView->Disconnect(wxEVT_LIST_ITEM_SELECTED, wxListEventHandler(EvtMainFrame::installedSelectionEvent), nullptr, this);
_installedListView->Disconnect(wxEVT_LIST_ITEM_DESELECTED, wxListEventHandler(EvtMainFrame::installedSelectionEvent), nullptr, this);
_installedListView->Disconnect(wxEVT_LIST_BEGIN_DRAG, wxListEventHandler(EvtMainFrame::listColumnDragEvent), nullptr, this);
_installedListView->Disconnect(wxEVT_LIST_COL_BEGIN_DRAG, wxListEventHandler(EvtMainFrame::listColumnDragEvent), nullptr, this);
}
bool EvtMainFrame::ready() {
return _mbManager.ready() && _profileManager.ready();
}
void EvtMainFrame::profileSelectionEvent(wxCommandEvent&) {
_watcher.Remove(wxFileName{Utility::Directory::toNativeSeparators(Utility::Directory::path(_massManager->saveDirectory())) + "\\", wxPATH_WIN});
// Yeah, I really need that `+ "\\"`, because wxWidgets is *that* stupid.
int selection = _profileChoice->GetSelection();
if(_profileManager.setProfile(selection)) {
_massManager.emplace(_profileManager.profileDirectory(),
_profileManager.currentProfile()->steamId(),
_profileManager.currentProfile()->type() == ProfileType::Demo);
_watcher.AddTree(wxFileName{Utility::Directory::toNativeSeparators(_massManager->saveDirectory()), wxPATH_WIN},
wxFSW_EVENT_CREATE|wxFSW_EVENT_DELETE|wxFSW_EVENT_MODIFY|wxFSW_EVENT_RENAME,
wxString::Format("%s*%s.sav", _profileManager.currentProfile()->type() == ProfileType::Demo ? "Demo" : "", _profileManager.currentProfile()->steamId()));
updateProfileStats();
refreshListView();
}
}
void EvtMainFrame::backupSelectedProfileEvent(wxCommandEvent&) {
const static std::string error_prefix = "Backup failed:\n\n";
wxString current_timestamp = wxDateTime::Now().Format("%Y-%m-%d_%H-%M-%S");
wxFileDialog save_dialog{this, "Choose output location", _massManager->saveDirectory(),
wxString::Format("backup_%s%s_%s_%s.zip",
_profileManager.currentProfile()->type() == ProfileType::Demo ? "demo_" : "",
_profileManager.currentProfile()->companyName(),
_profileManager.currentProfile()->steamId(),
current_timestamp),
"Zip archive (*.zip)|*.zip",
wxFD_SAVE|wxFD_OVERWRITE_PROMPT};
if(save_dialog.ShowModal() == wxID_CANCEL) {
return;
}
if(!_profileManager.currentProfile()->backup(save_dialog.GetPath().ToStdString())) {
errorMessage(error_prefix + _massManager->lastError());
}
}
void EvtMainFrame::companyRenameEvent(wxCommandEvent&) {
const static std::string error_prefix = "Rename failed:\n\n";
EvtNameChangeDialog dialog{this};
dialog.setName(_profileManager.currentProfile()->companyName());
int result = dialog.ShowModal();
if(result == wxID_OK) {
if(_unsafeMode == false) {
switch(_mbManager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, renaming the company is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_profileManager.currentProfile()->renameCompany(dialog.getName())) {
errorMessage(error_prefix + _profileManager.currentProfile()->lastError());
}
else {
_profileChoice->SetString(_profileChoice->GetCurrentSelection(),
wxString::Format("%s%s",
_profileManager.currentProfile()->companyName(),
_profileManager.currentProfile()->type() == ProfileType::Demo ? " (Demo)" : ""));
}
break;
case GameState::Running:
errorMessage(error_prefix + "Renaming the company is disabled while the game is running.");
break;
}
}
else {
if(!_profileManager.currentProfile()->renameCompany(dialog.getName())) {
errorMessage(error_prefix + _profileManager.currentProfile()->lastError());
}
else {
_profileChoice->SetString(_profileChoice->GetCurrentSelection(),
wxString::Format("%s%s",
_profileManager.currentProfile()->companyName(),
_profileManager.currentProfile()->type() == ProfileType::Demo ? " (Demo)" : ""));
}
}
}
}
void EvtMainFrame::creditsEditEvent(wxCommandEvent&) {
const static std::string error_prefix = "Credits change failed:\n\n";
if(_unsafeMode == true || _mbManager.gameState() == GameState::NotRunning) {
long number = wxGetNumberFromUser("Please enter a number of credits between 0 and 2 000 000 000 included:", "Credits:", "Input credits",
_profileManager.currentProfile()->credits(), 0, 2000000000, this);
if(number == -1 || number == _profileManager.currentProfile()->credits()) {
return;
}
if(!_profileManager.currentProfile()->setCredits(number)) {
errorMessage(error_prefix + _profileManager.currentProfile()->lastError());
}
updateProfileStats();
}
else if(_mbManager.gameState() == GameState::Unknown) {
errorMessage(error_prefix + "For security reasons, changing credits is disabled if the game's status is unknown.");
}
else if(_mbManager.gameState() == GameState::Running) {
errorMessage(error_prefix + "For security reasons, changing credits is disabled if the game is running.");
}
}
void EvtMainFrame::storyProgressSelectionEvent(wxCommandEvent& event) {
const static std::string error_prefix = "StoryProgress change failed:\n\n";
std::int32_t story_progress = event.GetId() ^ (-10000);
if(_unsafeMode == false) {
switch(_mbManager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, changing the story progression is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_profileManager.currentProfile()->setStoryProgress(story_progress)) {
errorMessage(error_prefix + _profileManager.currentProfile()->lastError());
}
break;
case GameState::Running:
errorMessage(error_prefix + "Changing the story progression is disabled while the game is running.");
break;
}
}
else if(!_profileManager.currentProfile()->setStoryProgress(story_progress)) {
errorMessage(error_prefix + _profileManager.currentProfile()->lastError());
}
updateProfileStats();
}
void EvtMainFrame::openStoryProgressMenuEvent(wxCommandEvent&) {
PopupMenu(_storyProgressSelectionMenu.get());
}
void EvtMainFrame::inventoryChangeEvent(wxPropertyGridEvent& event) {
const static std::string error_prefix = "Inventory change failed:\n\n";
std::int32_t value = event.GetPropertyValue().GetInteger();
Profile* profile = _profileManager.currentProfile();
if(value > 1000000 || value < 0) {
event.SetValidationFailureMessage(error_prefix + "The value must not be higher than 1 million or lower than 0");
event.Veto();
return;
}
if(_unsafeMode == true || _mbManager.gameState() == GameState::NotRunning) {
wxPGProperty* prop = event.GetProperty();
bool success = false;
if(prop == _verseSteel) { success = profile->setVerseSteel(value); }
else if(prop == _undinium) { success = profile->setUndinium(value); }
else if(prop == _necriumAlloy) { success = profile->setNecriumAlloy(value); }
else if(prop == _lunarite) { success = profile->setLunarite(value); }
else if(prop == _asterite) { success = profile->setAsterite(value); }
else if(prop == _ednil) { success = profile->setEdnil(value); }
else if(prop == _nuflalt) { success = profile->setNuflalt(value); }
else if(prop == _aurelene) { success = profile->setAurelene(value); }
else if(prop == _soldus) { success = profile->setSoldus(value); }
else if(prop == _synthesizedN) { success = profile->setSynthesizedN(value); }
else if(prop == _alcarbonite) { success = profile->setAlcarbonite(value); }
else if(prop == _keriphene) { success = profile->setKeriphene(value); }
else if(prop == _nitinolCM) { success = profile->setNitinolCM(value); }
else if(prop == _quarkium) { success = profile->setQuarkium(value); }
else if(prop == _alterene) { success = profile->setAlterene(value); }
else if(prop == _mixedComposition) { success = profile->setMixedComposition(value); }
else if(prop == _voidResidue) { success = profile->setVoidResidue(value); }
else if(prop == _muscularConstruction) { success = profile->setMuscularConstruction(value); }
else if(prop == _mineralExoskeletology) { success = profile->setMineralExoskeletology(value); }
else if(prop == _carbonizedSkin) { success = profile->setCarbonizedSkin(value); }
if(!success) {
event.SetValidationFailureMessage(error_prefix + profile->lastError());
event.Veto();
}
}
else if(_mbManager.gameState() == GameState::Unknown) {
event.SetValidationFailureMessage(error_prefix + "For security reasons, changing the inventory is disabled if the game's status is unknown.");
event.Veto();
}
else if(_mbManager.gameState() == GameState::Running) {
event.SetValidationFailureMessage(error_prefix + "For security reasons, changing the inventory is disabled if the game is running.");
event.Veto();
}
}
void EvtMainFrame::importMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Importing failed:\n\n";
long selected_hangar = _installedListView->GetFirstSelected();
MassState mass_state = _massManager->massState(selected_hangar);
int staged_selection = _stagingList->GetSelection();
int confirmation;
if(mass_state == MassState::Valid) {
confirmation = wxMessageBox(wxString::Format("Hangar %.2d is already occupied by the M.A.S.S. named \"%s\". Are you sure you want to import the M.A.S.S. named \"%s\" to this hangar ?",
selected_hangar + 1, _massManager->massName(selected_hangar), _massManager->stagedMassName(staged_selection)),
"Question", wxYES_NO|wxCENTRE|wxICON_QUESTION, this);
}
else {
confirmation = wxMessageBox(wxString::Format("Are you sure you want to import the M.A.S.S. named \"%s\" to hangar %.2d ?", _massManager->stagedMassName(staged_selection), selected_hangar + 1),
"Question", wxYES_NO|wxCENTRE|wxICON_QUESTION, this);
}
if(confirmation == wxNO) {
return;
}
if(_unsafeMode == false) {
switch(_mbManager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, importing is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_massManager->importMass(staged_selection, selected_hangar)) {
errorMessage(error_prefix + _massManager->lastError());
}
break;
case GameState::Running:
errorMessage(error_prefix + "Importing a M.A.S.S. is disabled while the game is running.");
break;
}
}
else {
if(!_massManager->importMass(staged_selection, selected_hangar)) {
errorMessage(error_prefix + _massManager->lastError());
}
}
}
void EvtMainFrame::exportMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Export failed:\n\n";
long slot = _installedListView->GetFirstSelected();
if(!_massManager->exportMass(slot)) {
errorMessage(error_prefix + _massManager->lastError());
}
}
void EvtMainFrame::moveMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Move failed:\n\n";
long source_slot = _installedListView->GetFirstSelected();
long choice = wxGetNumberFromUser(wxString::Format("Which hangar do you want to move the M.A.S.S. named \"%s\" to ?\nNotes:\n"
"- If the destination hangar is the same as the source, nothing will happen.\n"
"- If the destination already contains a M.A.S.S., the two will be swapped.\n"
"- If the destination contains invalid data, it will be cleared first.",
_massManager->massName(source_slot)),
"Slot", "Choose a slot", source_slot + 1, 1, 32, this);
if(choice == -1 || choice == source_slot) {
return;
}
if(_unsafeMode == false) {
switch(_mbManager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, moving a M.A.S.S. is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_massManager->moveMass(source_slot, choice - 1)) {
errorMessage(error_prefix + _massManager->lastError());
}
break;
case GameState::Running:
errorMessage(error_prefix + "Moving a M.A.S.S. is disabled while the game is running.");
break;
}
}
else {
if(!_massManager->moveMass(source_slot, choice - 1)) {
errorMessage(error_prefix + _massManager->lastError());
}
}
}
void EvtMainFrame::deleteMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Deletion failed:\n\n";
if(wxMessageBox(wxString::Format("Are you sure you want to delete the data in hangar %.2d ? This operation cannot be undone.", _installedListView->GetFirstSelected() + 1),
"Are you sure ?", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
return;
}
if(_unsafeMode == false) {
switch(_mbManager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, deleting a M.A.S.S. is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_massManager->deleteMass(_installedListView->GetFirstSelected())) {
errorMessage(error_prefix + _massManager->lastError());
}
break;
case GameState::Running:
errorMessage(error_prefix + "Deleting a M.A.S.S. is disabled while the game is running.");
break;
}
}
else {
if(!_massManager->deleteMass(_installedListView->GetFirstSelected())) {
errorMessage(error_prefix + _massManager->lastError());
}
}
}
void EvtMainFrame::renameMassEvent(wxCommandEvent&) {
const static std::string error_prefix = "Rename failed:\n\n";
EvtNameChangeDialog dialog{this};
dialog.setName(_massManager->massName(_installedListView->GetFirstSelected()));
int result = dialog.ShowModal();
if(result == wxID_OK) {
if(_unsafeMode == false) {
switch(_mbManager.gameState()) {
case GameState::Unknown:
errorMessage(error_prefix + "For security reasons, renaming a M.A.S.S. is disabled if the game's status is unknown.");
break;
case GameState::NotRunning:
if(!_massManager->renameMass(_installedListView->GetFirstSelected(), dialog.getName())) {
errorMessage(error_prefix + _massManager->lastError());
}
break;
case GameState::Running:
errorMessage(error_prefix + "Renaming a M.A.S.S. is disabled while the game is running.");
break;
}
}
else {
if(!_massManager->renameMass(_installedListView->GetFirstSelected(), dialog.getName())) {
errorMessage(error_prefix + _massManager->lastError());
}
}
}
}
void EvtMainFrame::openSaveDirEvent(wxCommandEvent&) {
wxExecute("explorer.exe " + Utility::Directory::toNativeSeparators(_massManager->saveDirectory()));
}
void EvtMainFrame::stagingSelectionEvent(wxCommandEvent&) {
updateCommandsState();
}
void EvtMainFrame::deleteStagedEvent(wxCommandEvent&) {
if(wxMessageBox("Are you sure you want to delete the selected M.A.S.S. ? This operation cannot be undone.",
"Are you sure ?", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
return;
}
int selection = _stagingList->GetSelection();
if(selection != wxNOT_FOUND) {
_massManager->deleteStagedMass(selection);
}
}
void EvtMainFrame::openStagingDirEvent(wxCommandEvent&) {
wxExecute("explorer.exe " + Utility::Directory::toNativeSeparators(_massManager->stagingAreaDirectory()));
}
void EvtMainFrame::installedSelectionEvent(wxListEvent&) {
updateCommandsState();
}
void EvtMainFrame::listColumnDragEvent(wxListEvent& event) {
event.Veto();
}
void EvtMainFrame::openScreenshotDirEvent(wxCommandEvent&) {
wxExecute("explorer.exe " + Utility::Directory::toNativeSeparators(Utility::Directory::join(_profileManager.profileDirectory(), "../Screenshots/WindowsNoEditor")));
}
void EvtMainFrame::fileUpdateEvent(wxFileSystemWatcherEvent& event) {
int event_type = event.GetChangeType();
wxString event_file = event.GetPath().GetFullName();
if(event_type == wxFSW_EVENT_MODIFY && _lastWatcherEventType == wxFSW_EVENT_RENAME) {
_lastWatcherEventType = event_type;
return;
}
wxMilliSleep(100);
wxString event_path = event.GetPath().GetPath(wxPATH_GET_VOLUME, wxPATH_WIN);
if(event_path == Utility::Directory::toNativeSeparators(_massManager->saveDirectory())) {
saveFileEventHandler(event_type, event_file, event);
}
else if(_massManager && event_path == Utility::Directory::toNativeSeparators(_massManager->stagingAreaDirectory())) {
stagingFileEventHandler(event_type, event_file, event);
}
_lastWatcherEventType = event_type;
updateCommandsState();
}
void EvtMainFrame::gameCheckTimerEvent(wxTimerEvent&) {
isGameRunning();
}
void EvtMainFrame::unsafeCheckboxEvent(wxCommandEvent& event) {
if(event.IsChecked() == true) {
int confirmation = wxMessageBox("Are you sure you want to enable unsafe mode ?\n\n"
"Unsafe mode will allow you to perform changes even while the game is running, which can result in weird behaviour or even data corruption.",
"Question", wxYES_NO|wxCENTRE|wxICON_WARNING, this);
if(confirmation == wxYES) {
_unsafeMode = true;
}
else {
_unsafeCheckbox->SetValue(false);
}
}
else {
_unsafeMode = false;
}
updateCommandsState();
}
void EvtMainFrame::saveFileEventHandler(int event_type, const wxString& event_file, const wxFileSystemWatcherEvent& event) {
wxRegEx regex;
switch (event_type) {
case wxFSW_EVENT_CREATE:
case wxFSW_EVENT_DELETE:
regex.Compile(wxString::Format("%sUnit([0-3][0-9])%s\\.sav", _profileManager.currentProfile()->type() == ProfileType::Demo ? "Demo" : "", _profileManager.currentProfile()->steamId()), wxRE_ADVANCED);
if(regex.Matches(event_file)) {
long slot;
if(regex.GetMatch(event_file, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
}
}
break;
case wxFSW_EVENT_MODIFY:
if(_lastWatcherEventType == wxFSW_EVENT_RENAME) {
break;
}
if(event_file == _profileManager.currentProfile()->filename()) {
updateProfileStats();
getActiveSlot();
}
else {
regex.Compile(wxString::Format("%sUnit([0-3][0-9])%s\\.sav", _profileManager.currentProfile()->type() == ProfileType::Demo ? "Demo" : "", _profileManager.currentProfile()->steamId()), wxRE_ADVANCED);
if(regex.Matches(event_file)) {
long slot;
if(regex.GetMatch(event_file, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
}
}
}
break;
case wxFSW_EVENT_RENAME:
wxString new_name = event.GetNewPath().GetFullName();
long slot;
if(regex.Compile(wxString::Format("%sUnit([0-3][0-9])%s\\.sav\\.tmp", _profileManager.currentProfile()->type() == ProfileType::Demo ? "Demo" : "", _profileManager.currentProfile()->steamId()), wxRE_ADVANCED), regex.Matches(new_name)) {
if(regex.GetMatch(new_name, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
}
}
else if(regex.Compile(wxString::Format("%sUnit([0-3][0-9])%s\\.sav", _profileManager.currentProfile()->type() == ProfileType::Demo ? "Demo" : "", _profileManager.currentProfile()->steamId()), wxRE_ADVANCED), regex.Matches(new_name)) {
if(regex.GetMatch(new_name, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
if(regex.Matches(event_file)) {
if(regex.GetMatch(event_file, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
refreshHangar(slot);
}
}
}
}
break;
}
}
void EvtMainFrame::stagingFileEventHandler(int event_type, const wxString& event_file, const wxFileSystemWatcherEvent& event) {
int index;
switch(event_type) {
case wxFSW_EVENT_CREATE:
index = _massManager->updateStagedMass(event_file.ToUTF8().data());
if(index != -1) {
_stagingList->Insert(wxString::Format("%s (%s)", _massManager->stagedMassName(index), event_file), index);
}
break;
case wxFSW_EVENT_DELETE:
index = _massManager->removeStagedMass(event_file.ToUTF8().data());
if(index != -1) {
_stagingList->Delete(index);
}
break;
case wxFSW_EVENT_MODIFY:
index = _massManager->updateStagedMass(event_file.ToUTF8().data());
if(index != -1) {
_stagingList->SetString(index, wxString::Format("%s (%s)", _massManager->stagedMassName(index), event_file));
}
break;
case wxFSW_EVENT_RENAME:
index = _massManager->removeStagedMass(event_file.ToUTF8().data());
if(index != -1) {
_stagingList->Delete(index);
}
index = _massManager->updateStagedMass(event.GetNewPath().GetFullName().ToUTF8().data());
if(index != -1) {
_stagingList->Insert(wxString::Format("%s (%s)", _massManager->stagedMassName(index), event.GetNewPath().GetFullName()), index);
}
break;
}
}
void EvtMainFrame::updateProfileStats() {
Profile* current_profile = _profileManager.currentProfile();
_companyName->SetLabel(current_profile->getCompanyName());
_credits->SetLabel(wxString::Format("%i", current_profile->getCredits()));
if(story_progress_map.find(current_profile->getStoryProgress()) != story_progress_map.end()) {
_storyProgress->SetLabel(story_progress_map.at(current_profile->storyProgress()));
}
else {
_storyProgress->SetLabel(wxString::Format("0x%X", current_profile->storyProgress()));
}
if(mission_id_map.find(current_profile->getLastMissionId()) != mission_id_map.end()) {
_lastMissionId->SetLabel(mission_id_map.at(current_profile->lastMissionId()));
}
else {
_lastMissionId->SetLabel(wxString::Format("0x%X", current_profile->lastMissionId()));
}
_verseSteel->SetValueFromInt(current_profile->getVerseSteel());
_undinium->SetValueFromInt(current_profile->getUndinium());
_necriumAlloy->SetValueFromInt(current_profile->getNecriumAlloy());
_lunarite->SetValueFromInt(current_profile->getLunarite());
_asterite->SetValueFromInt(current_profile->getAsterite());
_ednil->SetValueFromInt(current_profile->getEdnil());
_nuflalt->SetValueFromInt(current_profile->getNuflalt());
_aurelene->SetValueFromInt(current_profile->getAurelene());
_soldus->SetValueFromInt(current_profile->getSoldus());
_synthesizedN->SetValueFromInt(current_profile->getSynthesizedN());
_alcarbonite->SetValueFromInt(current_profile->getAlcarbonite());
_keriphene->SetValueFromInt(current_profile->getKeriphene());
_nitinolCM->SetValueFromInt(current_profile->getNitinolCM());
_quarkium->SetValueFromInt(current_profile->getQuarkium());
_alterene->SetValueFromInt(current_profile->getAlterene());
_mixedComposition->SetValueFromInt(current_profile->getMixedComposition());
_voidResidue->SetValueFromInt(current_profile->getVoidResidue());
_muscularConstruction->SetValueFromInt(current_profile->getMuscularConstruction());
_mineralExoskeletology->SetValueFromInt(current_profile->getMineralExoskeletology());
_carbonizedSkin->SetValueFromInt(current_profile->getCarbonizedSkin());
updateCommandsState();
}
void EvtMainFrame::initStoryProgressMenu() {
_storyProgressSelectionMenu.emplace();
if(!_storyProgressSelectionMenu) {
errorMessage("Error initialising the story progress selection menu.");
this->Destroy();
return;
}
wxMenu* submenu = nullptr;
for(const auto& pair : story_progress_map) {
if(std::strncmp(pair.second + 10, "start", 5) == 0) {
submenu = new wxMenu();
if(!submenu) {
errorMessage("Error initialising the story progress selection menu.");
this->Destroy();
return;
}
_storyProgressSelectionMenu->Append(wxID_ANY, wxString{pair.second, 9}, submenu);
wxMenuItem* item = submenu->Append(pair.first ^ (-10000), "Chapter start");
if(!item) {
errorMessage("Error initialising the story progress selection menu.");
this->Destroy();
return;
}
}
else {
if(!submenu) {
errorMessage("Error initialising the story progress selection menu.");
this->Destroy();
return;
}
wxMenuItem* item = submenu->Append(pair.first ^ (-10000), wxString{pair.second + 12});
if(!item) {
errorMessage("Error initialising the story progress selection menu.");
this->Destroy();
return;
}
}
}
_storyProgressSelectionMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(EvtMainFrame::storyProgressSelectionEvent), nullptr, this);
}
void EvtMainFrame::initialiseListView() {
for(long i = 0; i < 32; i++) {
_installedListView->InsertItem(i, wxString::Format("%.2i", i + 1));
}
_installedListView->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
_installedListView->SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER);
refreshListView();
getActiveSlot();
}
void EvtMainFrame::isGameRunning() {
_gameStatus->SetLabel("checking...");
_gameStatus->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_CAPTIONTEXT));
GameState state = _mbManager.checkGameState();
switch(state) {
case GameState::Unknown:
_gameStatus->SetLabel("unknown");
_gameStatus->SetForegroundColour(wxColour("orange"));
break;
case GameState::NotRunning:
_gameStatus->SetLabel("not running");
_gameStatus->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_CAPTIONTEXT));
break;
case GameState::Running:
_gameStatus->SetLabel("running");
_gameStatus->SetForegroundColour(wxColour("red"));
break;
}
updateCommandsState();
}
void EvtMainFrame::refreshListView() {
for(int i = 0; i < 32; i++) {
refreshHangar(i);
}
updateCommandsState();
}
void EvtMainFrame::getActiveSlot() {
auto slot = _profileManager.currentProfile()->activeFrameSlot();
if(slot != -1) {
wxFont tmp_font = _installedListView->GetItemFont(slot);
tmp_font.SetWeight(wxFONTWEIGHT_NORMAL);
_installedListView->SetItemFont(slot, tmp_font);
}
slot = _profileManager.currentProfile()->getActiveFrameSlot();
if(slot != -1) {
_installedListView->SetItemFont(slot, _installedListView->GetItemFont(slot).Bold());
}
}
void EvtMainFrame::updateCommandsState() {
long selection = _installedListView->GetFirstSelected();
int staged_selection = _stagingList->GetSelection();
GameState game_state = _mbManager.gameState();
MassState mass_state = _massManager->massState(selection);
_companyRenameButton->Enable(_unsafeMode == true || game_state == GameState::NotRunning);
_creditsEditButton->Enable(_unsafeMode == true || game_state == GameState::NotRunning);
_storyProgressChangeButton->Enable(_unsafeMode == true || game_state == GameState::NotRunning);
wxPropertyGridConstIterator it = _researchInventoryPropGrid->GetIterator(wxPG_ITERATE_NORMAL);
while(!it.AtEnd()) {
if(it.GetProperty()->IsCategory() == false) {
it.GetProperty()->Enable(it.GetProperty()->GetValue().GetInteger() != -1 && (_unsafeMode == true || game_state == GameState::NotRunning));
}
it.Next();
}
_importButton->Enable(selection != -1 && staged_selection != -1 && (_unsafeMode == true || game_state == GameState::NotRunning));
_exportButton->Enable(selection != -1);
_moveButton->Enable(selection != -1 && (_unsafeMode == true || game_state == GameState::NotRunning) && mass_state == MassState::Valid);
_deleteButton->Enable(selection != -1 && (_unsafeMode == true || game_state == GameState::NotRunning) && mass_state != MassState::Empty);
_renameButton->Enable(selection != -1 && (_unsafeMode == true || game_state == GameState::NotRunning) && mass_state == MassState::Valid);
_deleteStagedButton->Enable(staged_selection != -1);
}
void EvtMainFrame::refreshHangar(int slot) {
if(slot < 0 && slot >= 32) {
return;
}
_massManager->refreshHangar(slot);
switch(_massManager->massState(slot)) {
case MassState::Empty:
_installedListView->SetItem(slot, 1, "<Empty>");
break;
case MassState::Invalid:
_installedListView->SetItem(slot, 1, "<Invalid>");
break;
case MassState::Valid:
_installedListView->SetItem(slot, 1, _massManager->massName(slot));
break;
}
}
void EvtMainFrame::infoMessage(const wxString& message) {
wxMessageBox(message, "Information", wxOK|wxCENTRE|wxICON_INFORMATION, this);
}
void EvtMainFrame::warningMessage(const wxString& message) {
wxMessageBox(message, "Warning", wxOK|wxCENTRE|wxICON_WARNING, this);
}
void EvtMainFrame::errorMessage(const wxString& message) {
wxMessageBox(message, "Error", wxOK|wxCENTRE|wxICON_ERROR, this);
}

View File

@ -2,7 +2,7 @@
#define __EvtMainFrame__
// wxMASSManager
// Copyright (C) 2020 Guillaume Jacquemin
// Copyright (C) 2020-2021 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
@ -19,13 +19,20 @@
#include <string>
#include <Corrade/Containers/Pointer.h>
#include <wx/fswatcher.h>
#include <wx/imaglist.h>
#include "../MassManager/MassManager.h"
#include "../MassBuilderManager/MassBuilderManager.h"
#include "../Profile/Profile.h"
#include "../ProfileManager/ProfileManager.h"
#include "MainFrame.h"
using namespace Corrade;
class EvtMainFrame: public MainFrame {
public:
EvtMainFrame(wxWindow* parent);
@ -34,39 +41,42 @@ class EvtMainFrame: public MainFrame {
auto ready() -> bool;
protected:
// Profile-related events
void profileSelectionEvent(wxCommandEvent&) override;
void backupSelectedProfileEvent(wxCommandEvent&) override;
void companyRenameEvent(wxCommandEvent&) override;
void creditsEditEvent(wxCommandEvent&) override;
void storyProgressSelectionEvent(wxCommandEvent& event);
void openStoryProgressMenuEvent(wxCommandEvent&) override;
void inventoryChangeEvent(wxPropertyGridEvent& event) override;
// M.A.S.S.-related events
void importMassEvent(wxCommandEvent&);
void exportMassEvent(wxCommandEvent&);
void moveMassEvent(wxCommandEvent&);
void deleteMassEvent(wxCommandEvent&);
void renameMassEvent(wxCommandEvent&);
void backupEvent(wxCommandEvent&);
void openSaveDirEvent(wxCommandEvent&);
void stagingSelectionEvent(wxCommandEvent&);
void deleteStagedEvent(wxCommandEvent&);
void openStagingDirEvent(wxCommandEvent&);
void importMassEvent(wxCommandEvent&) override;
void exportMassEvent(wxCommandEvent&) override;
void moveMassEvent(wxCommandEvent&) override;
void deleteMassEvent(wxCommandEvent&) override;
void renameMassEvent(wxCommandEvent&) override;
void openSaveDirEvent(wxCommandEvent&) override;
void stagingSelectionEvent(wxCommandEvent&) override;
void deleteStagedEvent(wxCommandEvent&) override;
void openStagingDirEvent(wxCommandEvent&) override;
void installedSelectionEvent(wxListEvent&);
void listColumnDragEvent(wxListEvent&);
// Screenshot events
void screenshotListSelectionEvent(wxListEvent&);
void screenshotFilenameSortingEvent(wxCommandEvent&);
void screenshotCreationDateSortingEvent(wxCommandEvent&);
void screenshotAscendingSortingEvent(wxCommandEvent&);
void screenshotDescendingSortingEvent(wxCommandEvent&);
void viewScreenshotEvent(wxCommandEvent&);
void viewScreenshotEvent(wxListEvent&);
void deleteScreenshotEvent(wxCommandEvent&);
void openScreenshotDirEvent(wxCommandEvent&);
// Screenshot-related events
void openScreenshotDirEvent(wxCommandEvent&) override;
// General events
void fileUpdateEvent(wxFileSystemWatcherEvent& event);
void gameCheckTimerEvent(wxTimerEvent&);
void gameCheckTimerEvent(wxTimerEvent&) override;
void unsafeCheckboxEvent(wxCommandEvent& event) override;
private:
void unitFileEventHandler(int event_type, const wxString& event_file, const wxFileSystemWatcherEvent& event);
void saveFileEventHandler(int event_type, const wxString& event_file, const wxFileSystemWatcherEvent& event);
void stagingFileEventHandler(int event_type, const wxString& event_file, const wxFileSystemWatcherEvent& event);
void screenshotFileEventHandler(int event_type, const wxString& event_file);
void updateProfileStats();
void initStoryProgressMenu();
void initialiseListView();
void isGameRunning();
@ -75,19 +85,20 @@ class EvtMainFrame: public MainFrame {
void updateCommandsState();
void refreshHangar(int slot);
void updateScreenshotList();
void viewScreenshot();
void infoMessage(const wxString& message);
void warningMessage(const wxString& message);
void errorMessage(const wxString& message);
MassManager _manager;
bool _unsafeMode = false;
MassBuilderManager _mbManager;
ProfileManager _profileManager;
Containers::Pointer<MassManager> _massManager;
Containers::Pointer<wxMenu> _storyProgressSelectionMenu;
wxFileSystemWatcher _watcher;
int _lastWatcherEventType = 0;
wxImageList _screenshotThumbs{160, 160, true, 0};
};
#endif // __EvtMainFrame__

View File

@ -1,5 +1,5 @@
// wxMASSManager
// Copyright (C) 2020 Guillaume Jacquemin
// Copyright (C) 2020-2021 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
@ -46,7 +46,7 @@ void EvtNameChangeDialog::textEditEvent(wxCommandEvent&) {
_lengthBitmap->SetBitmap(wxArtProvider::GetBitmap(wxART_CROSS_MARK, wxART_BUTTON));
}
_nameLength->SetLabel(wxString::Format("%u", value.length()));
_nameLength->SetLabel(wxString::Format("%zu", value.length()));
if(_nameInput->Validate() == true) {
_charsBitmap->SetBitmap(wxArtProvider::GetBitmap(wxART_TICK_MARK, wxART_BUTTON));

View File

@ -2,7 +2,7 @@
#define __EvtNameChangeDialog__
// wxMASSManager
// Copyright (C) 2020 Guillaume Jacquemin
// Copyright (C) 2020-2021 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

341
src/GUI/MainFrame.cpp Normal file
View File

@ -0,0 +1,341 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "MainFrame.h"
///////////////////////////////////////////////////////////////////////////
MainFrame::MainFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxSize( -1,600 ), wxSize( -1,600 ) );
wxBoxSizer* bSizerMain;
bSizerMain = new wxBoxSizer( wxVERTICAL );
_mainPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizerMainPanel;
bSizerMainPanel = new wxBoxSizer( wxVERTICAL );
wxBoxSizer* bSizerProfile;
bSizerProfile = new wxBoxSizer( wxHORIZONTAL );
_profileLabel = new wxStaticText( _mainPanel, wxID_ANY, wxT("Profile to manage:"), wxDefaultPosition, wxDefaultSize, 0 );
_profileLabel->Wrap( -1 );
bSizerProfile->Add( _profileLabel, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
wxArrayString _profileChoiceChoices;
_profileChoice = new wxChoice( _mainPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, _profileChoiceChoices, 0 );
_profileChoice->SetSelection( 0 );
_profileChoice->SetMinSize( wxSize( 150,-1 ) );
bSizerProfile->Add( _profileChoice, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
_backupSelectedButton = new wxButton( _mainPanel, wxID_ANY, wxT("Backup selected profile"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerProfile->Add( _backupSelectedButton, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
_openScreenshotDirButton = new wxButton( _mainPanel, wxID_ANY, wxT("Open screenshots folder"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerProfile->Add( _openScreenshotDirButton, 0, wxALL, 5 );
bSizerProfile->Add( 0, 0, 1, wxEXPAND, 5 );
_unsafeCheckbox = new wxCheckBox( _mainPanel, wxID_ANY, wxT("Unsafe mode"), wxDefaultPosition, wxDefaultSize, 0 );
_unsafeCheckbox->SetToolTip( wxT("CLICK AT YOUR OWN RISK!") );
bSizerProfile->Add( _unsafeCheckbox, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
bSizerMainPanel->Add( bSizerProfile, 0, wxEXPAND, 5 );
_managerNotebook = new wxNotebook( _mainPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
_profilePanel = new wxPanel( _managerNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizerProfilePanel;
bSizerProfilePanel = new wxBoxSizer( wxVERTICAL );
wxStaticBoxSizer* sbSizerGeneralInfo;
sbSizerGeneralInfo = new wxStaticBoxSizer( new wxStaticBox( _profilePanel, wxID_ANY, wxT("General information") ), wxVERTICAL );
wxFlexGridSizer* fgSizerGeneralStats;
fgSizerGeneralStats = new wxFlexGridSizer( 0, 2, 0, 0 );
fgSizerGeneralStats->AddGrowableCol( 1 );
fgSizerGeneralStats->SetFlexibleDirection( wxBOTH );
fgSizerGeneralStats->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
_companyNameLabel = new wxStaticText( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("Company name:"), wxDefaultPosition, wxDefaultSize, 0 );
_companyNameLabel->Wrap( -1 );
fgSizerGeneralStats->Add( _companyNameLabel, 0, wxALL, 5 );
_companyName = new wxStaticText( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("<blank>"), wxDefaultPosition, wxDefaultSize, 0 );
_companyName->Wrap( -1 );
fgSizerGeneralStats->Add( _companyName, 0, wxALL|wxEXPAND, 5 );
_creditsLabel = new wxStaticText( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("Credits:"), wxDefaultPosition, wxDefaultSize, 0 );
_creditsLabel->Wrap( -1 );
fgSizerGeneralStats->Add( _creditsLabel, 0, wxALL, 5 );
_credits = new wxStaticText( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("0"), wxDefaultPosition, wxDefaultSize, 0 );
_credits->Wrap( -1 );
fgSizerGeneralStats->Add( _credits, 0, wxALL|wxEXPAND, 5 );
_storyProgressLabel = new wxStaticText( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("Story progress:"), wxDefaultPosition, wxDefaultSize, 0 );
_storyProgressLabel->Wrap( -1 );
_storyProgressLabel->SetToolTip( wxT("The current point in the story.") );
fgSizerGeneralStats->Add( _storyProgressLabel, 0, wxALL, 5 );
_storyProgress = new wxStaticText( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("0"), wxDefaultPosition, wxDefaultSize, 0 );
_storyProgress->Wrap( -1 );
_storyProgress->SetToolTip( wxT("The current point in the story.") );
fgSizerGeneralStats->Add( _storyProgress, 0, wxALL|wxEXPAND, 5 );
_lastMissionIdLabel = new wxStaticText( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("Last mission ID:"), wxDefaultPosition, wxDefaultSize, 0 );
_lastMissionIdLabel->Wrap( -1 );
_lastMissionIdLabel->SetToolTip( wxT("This corresponds to the last mission menu selection, not the last mission played.\nIf you see just a number instead of the mission name, please report it to the creator of this application.") );
fgSizerGeneralStats->Add( _lastMissionIdLabel, 0, wxALL, 5 );
_lastMissionId = new wxStaticText( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("0"), wxDefaultPosition, wxDefaultSize, 0 );
_lastMissionId->Wrap( -1 );
_lastMissionId->SetToolTip( wxT("This corresponds to the last mission menu selection, not the last mission played.\nIf you see just a number instead of the mission name, please report it to the creator of this application.") );
fgSizerGeneralStats->Add( _lastMissionId, 0, wxALL|wxEXPAND, 5 );
sbSizerGeneralInfo->Add( fgSizerGeneralStats, 1, wxEXPAND, 5 );
wxBoxSizer* bSizerProfileCommands;
bSizerProfileCommands = new wxBoxSizer( wxHORIZONTAL );
_companyRenameButton = new wxButton( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("Rename company"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerProfileCommands->Add( _companyRenameButton, 0, wxALL, 5 );
_creditsEditButton = new wxButton( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("Edit credits"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerProfileCommands->Add( _creditsEditButton, 0, wxALL, 5 );
_storyProgressChangeButton = new wxButton( sbSizerGeneralInfo->GetStaticBox(), wxID_ANY, wxT("Change story progress"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerProfileCommands->Add( _storyProgressChangeButton, 0, wxALL, 5 );
sbSizerGeneralInfo->Add( bSizerProfileCommands, 0, wxEXPAND, 5 );
bSizerProfilePanel->Add( sbSizerGeneralInfo, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
wxBoxSizer* bSizerBottomHalf;
bSizerBottomHalf = new wxBoxSizer( wxHORIZONTAL );
wxStaticBoxSizer* sbSizerResearchInv;
sbSizerResearchInv = new wxStaticBoxSizer( new wxStaticBox( _profilePanel, wxID_ANY, wxT("Research inventory") ), wxVERTICAL );
_researchInventoryPropGrid = new wxPropertyGrid(sbSizerResearchInv->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_HIDE_MARGIN|wxPG_SPLITTER_AUTO_CENTER|wxPG_STATIC_LAYOUT|wxPG_STATIC_SPLITTER);
_materialsCategory = _researchInventoryPropGrid->Append( new wxPropertyCategory( wxT("Materials"), wxT("Materials") ) );
_verseSteel = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Verse Steel"), wxT("Verse Steel") ) );
_undinium = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Undinium"), wxT("Undinium") ) );
_necriumAlloy = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Necrium Alloy"), wxT("Necrium Alloy") ) );
_lunarite = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Lunarite"), wxT("Lunarite") ) );
_asterite = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Asterite"), wxT("Asterite") ) );
_ednil = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Ednil"), wxT("Ednil") ) );
_nuflalt = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Nuflalt"), wxT("Nuflalt") ) );
_aurelene = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Aurelene"), wxT("Aurelene") ) );
_soldus = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Soldus"), wxT("Soldus") ) );
_synthesizedN = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Synthesized N."), wxT("Synthesized N.") ) );
_alcarbonite = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Alcarbonite"), wxT("Alcarbonite") ) );
_keriphene = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Keriphene"), wxT("Keriphene") ) );
_nitinolCM = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Nitinol-CM"), wxT("Nitinol-CM") ) );
_quarkium = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Quarkium"), wxT("Quarkium") ) );
_alterene = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Alterene"), wxT("Alterene") ) );
_quarkDataCategory = _researchInventoryPropGrid->Append( new wxPropertyCategory( wxT("Quark Data"), wxT("Quark Data") ) );
_mixedComposition = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Mixed Composition"), wxT("Mixed Composition") ) );
_voidResidue = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Void Residue"), wxT("Void Residue") ) );
_muscularConstruction = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Muscular Construction"), wxT("Muscular Construction") ) );
_mineralExoskeletology = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Mineral Exoskeletology"), wxT("Mineral Exoskeletology") ) );
_carbonizedSkin = _researchInventoryPropGrid->Append( new wxIntProperty( wxT("Carbonized Skin"), wxT("Carbonized Skin") ) );
sbSizerResearchInv->Add( _researchInventoryPropGrid, 1, wxALL|wxEXPAND, 5 );
bSizerBottomHalf->Add( sbSizerResearchInv, 1, wxEXPAND|wxALL, 5 );
bSizerBottomHalf->Add( 0, 0, 1, wxEXPAND, 5 );
bSizerProfilePanel->Add( bSizerBottomHalf, 1, wxEXPAND, 5 );
_profilePanel->SetSizer( bSizerProfilePanel );
_profilePanel->Layout();
bSizerProfilePanel->Fit( _profilePanel );
_managerNotebook->AddPage( _profilePanel, wxT("Profile details and stats"), true );
_massPanel = new wxPanel( _managerNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizerMassPanel;
bSizerMassPanel = new wxBoxSizer( wxHORIZONTAL );
wxStaticBoxSizer* sbSizerInstalled;
sbSizerInstalled = new wxStaticBoxSizer( new wxStaticBox( _massPanel, wxID_ANY, wxT("Installed M.A.S.S.es") ), wxVERTICAL );
_installedListView = new wxListView(_massPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxLC_SINGLE_SEL|wxLC_HRULES);
_installedListView->AppendColumn("Hangar", wxLIST_FORMAT_LEFT);
_installedListView->AppendColumn("M.A.S.S. name", wxLIST_FORMAT_LEFT);
sbSizerInstalled->Add( _installedListView, 1, wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
wxStaticBoxSizer* sbSizerButtons;
sbSizerButtons = new wxStaticBoxSizer( new wxStaticBox( sbSizerInstalled->GetStaticBox(), wxID_ANY, wxT("Hangar actions") ), wxHORIZONTAL );
_moveButton = new wxButton( sbSizerButtons->GetStaticBox(), wxID_ANY, wxT("Move"), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerButtons->Add( _moveButton, 1, wxALL|wxEXPAND, 5 );
_deleteButton = new wxButton( sbSizerButtons->GetStaticBox(), wxID_ANY, wxT("Delete"), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerButtons->Add( _deleteButton, 1, wxALL|wxEXPAND, 5 );
_renameButton = new wxButton( sbSizerButtons->GetStaticBox(), wxID_ANY, wxT("Rename"), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerButtons->Add( _renameButton, 1, wxALL|wxEXPAND, 5 );
sbSizerInstalled->Add( sbSizerButtons, 0, wxEXPAND, 5 );
wxBoxSizer* bSizerSecondRow;
bSizerSecondRow = new wxBoxSizer( wxHORIZONTAL );
_openSaveDirButton = new wxButton( sbSizerInstalled->GetStaticBox(), wxID_ANY, wxT("Open save directory"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerSecondRow->Add( _openSaveDirButton, 2, wxEXPAND|wxALL, 5 );
sbSizerInstalled->Add( bSizerSecondRow, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 );
bSizerMassPanel->Add( sbSizerInstalled, 1, wxEXPAND|wxALL, 5 );
wxBoxSizer* bSizerImportExport;
bSizerImportExport = new wxBoxSizer( wxVERTICAL );
_importButton = new wxButton( _massPanel, wxID_ANY, wxT("Import"), wxDefaultPosition, wxDefaultSize, 0 );
_importButton->SetBitmap( wxArtProvider::GetBitmap( wxART_GO_BACK, wxART_BUTTON ) );
bSizerImportExport->Add( _importButton, 1, wxALL|wxEXPAND, 5 );
_exportButton = new wxButton( _massPanel, wxID_ANY, wxT("Export"), wxDefaultPosition, wxDefaultSize, 0 );
_exportButton->SetBitmap( wxArtProvider::GetBitmap( wxART_GO_FORWARD, wxART_BUTTON ) );
_exportButton->SetBitmapPosition( wxRIGHT );
bSizerImportExport->Add( _exportButton, 0, wxALL|wxEXPAND, 5 );
bSizerMassPanel->Add( bSizerImportExport, 0, wxALIGN_CENTER_VERTICAL, 5 );
wxStaticBoxSizer* sbSizerStagingArea;
sbSizerStagingArea = new wxStaticBoxSizer( new wxStaticBox( _massPanel, wxID_ANY, wxT("Staging area") ), wxVERTICAL );
_stagingList = new wxListBox( sbSizerStagingArea->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_NEEDED_SB|wxLB_SINGLE );
sbSizerStagingArea->Add( _stagingList, 1, wxALL|wxEXPAND, 5 );
_deleteStagedButton = new wxButton( sbSizerStagingArea->GetStaticBox(), wxID_ANY, wxT("Delete staged M.A.S.S."), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerStagingArea->Add( _deleteStagedButton, 0, wxALL|wxEXPAND, 5 );
_stagingAreaButton = new wxButton( sbSizerStagingArea->GetStaticBox(), wxID_ANY, wxT("Open staging area directory"), wxDefaultPosition, wxDefaultSize, 0 );
sbSizerStagingArea->Add( _stagingAreaButton, 0, wxALL|wxEXPAND, 5 );
bSizerMassPanel->Add( sbSizerStagingArea, 1, wxEXPAND|wxALL, 5 );
_massPanel->SetSizer( bSizerMassPanel );
_massPanel->Layout();
bSizerMassPanel->Fit( _massPanel );
_managerNotebook->AddPage( _massPanel, wxT("M.A.S.S.es"), false );
bSizerMainPanel->Add( _managerNotebook, 1, wxEXPAND, 5 );
_riskLabel = new wxStaticText( _mainPanel, wxID_ANY, wxT("USE THIS TOOL AT YOUR OWN RISK!"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
_riskLabel->Wrap( -1 );
_riskLabel->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
_riskLabel->SetForegroundColour( wxColour( 255, 0, 0 ) );
bSizerMainPanel->Add( _riskLabel, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT, 5 );
wxBoxSizer* bSizerGameStatus;
bSizerGameStatus = new wxBoxSizer( wxHORIZONTAL );
_gameStatusLabel = new wxStaticText( _mainPanel, wxID_ANY, wxT("Game status:"), wxDefaultPosition, wxDefaultSize, 0 );
_gameStatusLabel->Wrap( -1 );
bSizerGameStatus->Add( _gameStatusLabel, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxEXPAND|wxTOP|wxBOTTOM|wxLEFT, 5 );
_gameStatus = new wxStaticText( _mainPanel, wxID_ANY, wxT("not running"), wxDefaultPosition, wxDefaultSize, 0 );
_gameStatus->Wrap( -1 );
_gameStatus->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
_gameStatus->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_CAPTIONTEXT ) );
bSizerGameStatus->Add( _gameStatus, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
bSizerMainPanel->Add( bSizerGameStatus, 0, wxALIGN_CENTER_HORIZONTAL, 5 );
_aboutText = new wxStaticText( _mainPanel, wxID_ANY, wxT("This version of the application was tested on M.A.S.S. Builder early access version 0.6.5.\nIt may or may not work with other versions of the game.\nMade for the M.A.S.S. Builder community by Guillaume Jacquemin."), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
_aboutText->Wrap( -1 );
bSizerMainPanel->Add( _aboutText, 0, wxEXPAND|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 );
_githubLink = new wxHyperlinkCtrl( _mainPanel, wxID_ANY, wxT("https://github.com/williamjcm/wxMASSManager"), wxT("https://github.com/williamjcm/wxMASSManager"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE );
bSizerMainPanel->Add( _githubLink, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
_mainPanel->SetSizer( bSizerMainPanel );
_mainPanel->Layout();
bSizerMainPanel->Fit( _mainPanel );
bSizerMain->Add( _mainPanel, 1, wxEXPAND, 5 );
this->SetSizer( bSizerMain );
this->Layout();
bSizerMain->Fit( this );
_gameCheckTimer.SetOwner( this, wxID_ANY );
this->Centre( wxBOTH );
// Connect Events
_profileChoice->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( MainFrame::profileSelectionEvent ), NULL, this );
_backupSelectedButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::backupSelectedProfileEvent ), NULL, this );
_openScreenshotDirButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openScreenshotDirEvent ), NULL, this );
_unsafeCheckbox->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainFrame::unsafeCheckboxEvent ), NULL, this );
_companyRenameButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::companyRenameEvent ), NULL, this );
_creditsEditButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::creditsEditEvent ), NULL, this );
_storyProgressChangeButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openStoryProgressMenuEvent ), NULL, this );
_researchInventoryPropGrid->Connect( wxEVT_PG_CHANGING, wxPropertyGridEventHandler( MainFrame::inventoryChangeEvent ), NULL, this );
_moveButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::moveMassEvent ), NULL, this );
_deleteButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteMassEvent ), NULL, this );
_renameButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::renameMassEvent ), NULL, this );
_openSaveDirButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openSaveDirEvent ), NULL, this );
_importButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::importMassEvent ), NULL, this );
_exportButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::exportMassEvent ), NULL, this );
_stagingList->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainFrame::stagingSelectionEvent ), NULL, this );
_deleteStagedButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteStagedEvent ), NULL, this );
_stagingAreaButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openStagingDirEvent ), NULL, this );
this->Connect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( MainFrame::gameCheckTimerEvent ) );
}
MainFrame::~MainFrame()
{
// Disconnect Events
_profileChoice->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( MainFrame::profileSelectionEvent ), NULL, this );
_backupSelectedButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::backupSelectedProfileEvent ), NULL, this );
_openScreenshotDirButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openScreenshotDirEvent ), NULL, this );
_unsafeCheckbox->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainFrame::unsafeCheckboxEvent ), NULL, this );
_companyRenameButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::companyRenameEvent ), NULL, this );
_creditsEditButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::creditsEditEvent ), NULL, this );
_storyProgressChangeButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openStoryProgressMenuEvent ), NULL, this );
_researchInventoryPropGrid->Disconnect( wxEVT_PG_CHANGING, wxPropertyGridEventHandler( MainFrame::inventoryChangeEvent ), NULL, this );
_moveButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::moveMassEvent ), NULL, this );
_deleteButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteMassEvent ), NULL, this );
_renameButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::renameMassEvent ), NULL, this );
_openSaveDirButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openSaveDirEvent ), NULL, this );
_importButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::importMassEvent ), NULL, this );
_exportButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::exportMassEvent ), NULL, this );
_stagingList->Disconnect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainFrame::stagingSelectionEvent ), NULL, this );
_deleteStagedButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::deleteStagedEvent ), NULL, this );
_stagingAreaButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::openStagingDirEvent ), NULL, this );
this->Disconnect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( MainFrame::gameCheckTimerEvent ) );
}

File diff suppressed because it is too large Load Diff

View File

@ -9,23 +9,26 @@
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/listctrl.h>
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/choice.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/listbox.h>
#include <wx/propgrid/propgrid.h>
#include <wx/propgrid/advprops.h>
#include <wx/panel.h>
#include <wx/radiobut.h>
#include <wx/listctrl.h>
#include <wx/listbox.h>
#include <wx/notebook.h>
#include <wx/stattext.h>
#include <wx/hyperlink.h>
#include <wx/timer.h>
#include <wx/frame.h>
@ -42,28 +45,58 @@ class MainFrame : public wxFrame
protected:
wxPanel* _mainPanel;
wxStaticText* _profileLabel;
wxChoice* _profileChoice;
wxButton* _backupSelectedButton;
wxButton* _openScreenshotDirButton;
wxCheckBox* _unsafeCheckbox;
wxNotebook* _managerNotebook;
wxPanel* _profilePanel;
wxStaticText* _companyNameLabel;
wxStaticText* _companyName;
wxStaticText* _creditsLabel;
wxStaticText* _credits;
wxStaticText* _storyProgressLabel;
wxStaticText* _storyProgress;
wxStaticText* _lastMissionIdLabel;
wxStaticText* _lastMissionId;
wxButton* _companyRenameButton;
wxButton* _creditsEditButton;
wxButton* _storyProgressChangeButton;
wxPropertyGrid* _researchInventoryPropGrid;
wxPGProperty* _materialsCategory;
wxPGProperty* _verseSteel;
wxPGProperty* _undinium;
wxPGProperty* _necriumAlloy;
wxPGProperty* _lunarite;
wxPGProperty* _asterite;
wxPGProperty* _ednil;
wxPGProperty* _nuflalt;
wxPGProperty* _aurelene;
wxPGProperty* _soldus;
wxPGProperty* _synthesizedN;
wxPGProperty* _alcarbonite;
wxPGProperty* _keriphene;
wxPGProperty* _nitinolCM;
wxPGProperty* _quarkium;
wxPGProperty* _alterene;
wxPGProperty* _quarkDataCategory;
wxPGProperty* _mixedComposition;
wxPGProperty* _voidResidue;
wxPGProperty* _muscularConstruction;
wxPGProperty* _mineralExoskeletology;
wxPGProperty* _carbonizedSkin;
wxPanel* _massPanel;
wxListView* _installedListView;
wxButton* _moveButton;
wxButton* _deleteButton;
wxButton* _renameButton;
wxButton* _zipButton;
wxButton* _openSaveDirButton;
wxButton* _importButton;
wxButton* _exportButton;
wxListBox* _stagingList;
wxButton* _deleteStagedButton;
wxButton* _stagingAreaButton;
wxPanel* _screenshotsPanel;
wxListCtrl* _screenshotsList;
wxRadioButton* _nameRadio;
wxRadioButton* _creationDateRadio;
wxRadioButton* _ascendingRadio;
wxRadioButton* _descendingRadio;
wxButton* _viewScreenshotButton;
wxButton* _deleteScreenshotButton;
wxButton* _screenshotDirButton;
wxStaticText* _riskLabel;
wxStaticText* _gameStatusLabel;
wxStaticText* _gameStatus;
@ -72,31 +105,29 @@ class MainFrame : public wxFrame
wxTimer _gameCheckTimer;
// Virtual event handlers, overide them in your derived class
virtual void profileSelectionEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void backupSelectedProfileEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void openScreenshotDirEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void unsafeCheckboxEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void companyRenameEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void creditsEditEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void openStoryProgressMenuEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void inventoryChangeEvent( wxPropertyGridEvent& event ) { event.Skip(); }
virtual void moveMassEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void deleteMassEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void renameMassEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void backupSavesEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void openSaveDirEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void importMassEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void exportMassEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void stagingSelectionEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void deleteStagedEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void openStagingDirEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void viewScreenshotEvent( wxListEvent& event ) { event.Skip(); }
virtual void screenshotListSelectionEvent( wxListEvent& event ) { event.Skip(); }
virtual void screenshotFilenameSortingEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void screenshotCreationDateSortingEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void screenshotAscendingSortingEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void screenshotDescendingSortingEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void viewScreenshotEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void deleteScreenshotEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void openScreenshotDirEvent( wxCommandEvent& event ) { event.Skip(); }
virtual void gameCheckTimerEvent( wxTimerEvent& event ) { event.Skip(); }
public:
MainFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("M.A.S.S. Manager 1.1.2"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxCAPTION|wxCLOSE_BOX|wxMINIMIZE_BOX|wxSYSTEM_MENU|wxCLIP_CHILDREN|wxTAB_TRAVERSAL );
MainFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("M.A.S.S. Builder Save Tool 2.3.0"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxCAPTION|wxCLOSE_BOX|wxMINIMIZE_BOX|wxSYSTEM_MENU|wxCLIP_CHILDREN|wxTAB_TRAVERSAL );
~MainFrame();

49
src/Maps/LastMissionId.h Normal file
View File

@ -0,0 +1,49 @@
// wxMASSManager
// Copyright (C) 2020-2021 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/>.
#pragma once
#include <cstdint>
#include <map>
static const std::map<std::int32_t, const char*> mission_id_map {{
// Story missions
{0x64, "Mission 1 - Training"},
{0x65, "Mission 2 - Patrol Operation"},
{0x66, "Mission 3 - Fusion Cells in the Snow"},
{0x67, "Mission 4 - Earning Changes"},
{0x68, "Mission 5 - Unexpected Coordination"},
{0x69, "Mission 6 - Empowering Void"},
{0x6A, "Mission 7 - Logisitics Obstacles"},
{0x6B, "Mission 8 - Wrath of the Wastelands"},
{0x6C, "Mission 9 - Suspicious Originator"},
{0x6D, "Mission 10 - Researchers Data Recovery"},
{0x6E, "Mission 11 - Tempestuous Sector"},
{0x6F, "Mission 12 - Clashes of Metal"},
{0x70, "Mission 13 - The Sandstorm Glutton"},
// Hunting grounds
{0xC8, "Hunt 1 - Desert Pathway Safety"},
{0xC9, "Hunt 2 - Snowfield Custodian"},
{0xCA, "Hunt 3 - Abandoned Valley Raid"},
{0xCB, "Hunt 4 - Depths of the Machineries"},
// Challenges
{0x12C, "Challenge 1 - Redline Battlefront"},
{0x140, "Challenge 2 - Void Convergence"},
{0x190, "Challenge 3 - Gates of Ascension"}
}};

96
src/Maps/StoryProgress.h Normal file
View File

@ -0,0 +1,96 @@
// wxMASSManager
// Copyright (C) 2020-2021 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/>.
#pragma once
#include <cstdint>
#include <map>
static const std::map<std::int32_t, const char*> story_progress_map {{
{0x00, "Chapter 1 start - Company isn't named yet"},
{0x64, "Chapter 1 - First time in the hangar"},
{0x65, "Chapter 1 - After 1st meeting with Quin in mission section"},
{0x66, "Chapter 1 - After training, talking with Reina and Quin in hangar"},
{0x67, "Chapter 1 - After training, returned to hangar"},
{0x68, "Chapter 1 - After training, talked with Quin in development section"},
{0x69, "Chapter 1 - After training, talked with Waltz in armour section"},
{0xC8, "Chapter 1 - After training, talked with Kael in tuning section"},
{0xC9, "Chapter 1 - After training, got mission 2 briefing"},
{0x12C, "Chapter 1 - After mission 2, talking with Reina"},
{0x12D, "Chapter 1 - After mission 2, returned to hangar"},
{0x12E, "Chapter 1 - After mission 2, talked with Kael in tuning section"},
{0x12F, "Chapter 1 - After mission 2, talked with Reina in hangar"},
{0x130, "Chapter 1 - After mission 2, got mission 3 briefing"},
{0x190, "Chapter 1 - After mission 3, talking with Reina"},
{0x191, "Chapter 1 - After mission 3, returned to hangar"},
{0x192, "Chapter 1 - After mission 3, talked with Waltz in armour section"},
{0x193, "Chapter 1 - After mission 3, got mission 4 briefing"},
{0x1F4, "Chapter 1 - After mission 4, talking with Reina"},
{0x1F5, "Chapter 1 - After mission 4, returned to hangar"},
{0x1F6, "Chapter 1 - After mission 4, talked with Waltz in armour section"},
{0x1F7, "Chapter 1 - After mission 4, talked with Reina in hangar"},
{0x1F8, "Chapter 1 - After mission 4, got mission 5 and hunt 1 briefing"},
{0x258, "Chapter 1 - After mission 5, meeting Neon and Aine"},
{0x259, "Chapter 1 - After mission 5, returned to hangar"},
{0x25A, "Chapter 1 - After mission 5, got mission 6 briefing"},
{0x2BC, "Chapter 1 - After mission 6, talking with Reina"},
{0x2BD, "Chapter 1 - After mission 6, returned to hangar"},
{0x2BE, "Chapter 1 - After mission 6, got hunt 2 briefing"},
{0x2BF, "Chapter 1 - After mission 6, met Ellenier"},
{0x2C0, "Chapter 1 - After mission 6, got mission 7 briefing"},
{0x320, "Chapter 1 - After mission 7, talking with Nier"},
{0x321, "Chapter 1 - After mission 7, returned to hangar"},
{0x322, "Chapter 1 - After mission 7, talked with Quin, Reina, and Nier in development section"},
{0x323, "Chapter 1 - After mission 7, got mission 8 briefing"},
{0x384, "Chapter 1 - After mission 8, talking with crew in hangar"},
{0x385, "Chapter 1 - After mission 8, returned to hangar"},
{0x386, "Chapter 1 - After mission 8, got hunt 3 briefing"},
{0x387, "Chapter 1 - After mission 8, talked with Reina, Nier, and Quin in development section"},
{0x388, "Chapter 2 start"},
{0x389, "Chapter 2 - Got mission 9 briefing"},
{0x3E8, "Chapter 2 - After mission 9, talking with Reina in hangar"},
{0x3E9, "Chapter 2 - After mission 9, returned to hangar"},
{0x3EA, "Chapter 2 - After mission 9, talked with crew in armour section"},
{0x3EB, "Chapter 2 - After mission 9, got mission 10 briefing"},
{0x44C, "Chapter 2 - After mission 10, talking with Reina in hangar"},
{0x44D, "Chapter 2 - After mission 10, returned to hangar"},
{0x44E, "Chapter 2 - After mission 10, got mission 11 briefing"},
{0x4B0, "Chapter 2 - After mission 11, talking with Reina and Nier in hangar"},
{0x4B1, "Chapter 2 - After mission 11, returned to hangar"},
{0x4B2, "Chapter 2 - After mission 11, got mission 12 briefing"},
{0x514, "Chapter 2 - After mission 12, talking with Reina and Waltz in hangar"},
{0x515, "Chapter 2 - After mission 12, returned to hangar"},
{0x516, "Chapter 2 - After mission 12, got hunt 4 and mission 13 briefing"},
{0x578, "Chapter 2 - After mission 13, talking with Reina in hangar"},
{0x579, "Chapter 2 - After mission 13, returned to hangar"},
{0x57A, "Chapter 2 - After mission 13, talked with Reina in development section"},
{0x57B, "Chapter 2 - After mission 13, got briefing for challenges 1, 2, and 3"},
}};

187
src/Mass/Mass.cpp Normal file
View File

@ -0,0 +1,187 @@
// wxMASSManager
// Copyright (C) 2020-2021 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/version.h>
#if !(CORRADE_VERSION_YEAR * 100 + CORRADE_VERSION_MONTH >= 202006)
#error This application requires Corrade 2020.06 or later to build.
#endif
#include <cstring>
#include <algorithm>
#include <Corrade/Containers/Array.h>
#include <Corrade/Utility/Directory.h>
#include "Mass.h"
using namespace Corrade;
constexpr char mass_name_locator[] = "Name_45_A037C5D54E53456407BDF091344529BB\0\x0c\0\0\0StrProperty";
constexpr char steamid_locator[] = "Account\0\x0c\0\0\0StrProperty";
std::string Mass::_lastError = "";
Mass::Mass(const std::string& filename) {
_filename = filename;
if(!Utility::Directory::exists(_filename)) {
_lastError = "The file " + _filename + " couldn't be found.";
return;
}
auto mmap = Utility::Directory::mapRead(_filename);
auto iter = std::search(mmap.begin(), mmap.end(), &mass_name_locator[0], &mass_name_locator[56]);
if(iter != mmap.end()) {
_name = std::string{iter + 70};
_state = MassState::Valid;
}
else {
_lastError = "The name couldn't be found in " + filename;
_state = MassState::Invalid;
}
}
auto Mass::lastError() -> std::string const& {
return _lastError;
}
auto Mass::getNameFromFile(const std::string& filename) -> std::string {
if(!Utility::Directory::exists(filename)) {
_lastError = "The file " + filename + " couldn't be found.";
return "";
}
std::string name = "";
auto mmap = Utility::Directory::mapRead(filename);
auto iter = std::search(mmap.begin(), mmap.end(), &mass_name_locator[0], &mass_name_locator[56]);
if(iter != mmap.end()) {
name = std::string{iter + 70};
}
else {
_lastError = "The name couldn't be found in " + filename;
}
return name;
}
auto Mass::filename() -> std::string const&{
return _filename;
}
auto Mass::name() -> std::string const&{
return _name;
}
auto Mass::getName() -> std::string const& {
if(!Utility::Directory::exists(_filename)) {
_lastError = "The file " + _filename + " couldn't be found.";
_state = MassState::Empty;
return _name = "";
}
auto mmap = Utility::Directory::mapRead(_filename);
auto iter = std::search(mmap.begin(), mmap.end(), &mass_name_locator[0], &mass_name_locator[56]);
if(iter != mmap.end()) {
_state = MassState::Valid;
return _name = std::string{iter + 70};
}
else {
_lastError = "The name couldn't be found in " + _filename;
_state = MassState::Invalid;
return _name = "";
}
}
auto Mass::state() -> MassState {
return _state;
}
auto Mass::updateSteamId(const std::string& steam_id) -> bool {
if(!Utility::Directory::exists(_filename)) {
_lastError = "The file " + _filename + " couldn't be found.";
_state = MassState::Empty;
return false;
}
Utility::Directory::copy(_filename, _filename + ".tmp");
{
auto mmap = Utility::Directory::map(_filename + ".tmp");
auto iter = std::search(mmap.begin(), mmap.end(), &steamid_locator[0], &steamid_locator[23]);
if(iter == mmap.end()) {
_lastError = "The M.A.S.S. file at " + _filename + " seems to be corrupt.";
Utility::Directory::rm(_filename + ".tmp");
return false;
}
iter += 37;
if(std::strncmp(iter, steam_id.c_str(), steam_id.length()) != 0) {
for(int i = 0; i < 17; ++i) {
*(iter + i) = steam_id[i];
}
}
}
if(Utility::Directory::exists(_filename)) {
Utility::Directory::rm(_filename);
}
Utility::Directory::move(_filename + ".tmp", _filename);
return true;
}
auto Mass::rename(const std::string& new_name) -> bool {
char length_difference = static_cast<char>(_name.length() - new_name.length());
std::string mass_data = Utility::Directory::readString(_filename);
auto iter = std::search(mass_data.begin(), mass_data.end(), &mass_name_locator[0], &mass_name_locator[56]);
if(iter != mass_data.end()) {
*(iter - 45) = *(iter - 45) - length_difference;
*(iter + 57) = *(iter + 57) - length_difference;
*(iter + 66) = *(iter + 66) - length_difference;
while(*(iter + 70) != '\0') {
mass_data.erase(iter + 70);
}
mass_data.insert(iter + 70, new_name.cbegin(), new_name.cend());
if(!Utility::Directory::writeString(_filename, mass_data)) {
_lastError = "The file" + _filename + " couldn't be written to.";
return false;
}
return true;
}
else {
_lastError = "Couldn't find the M.A.S.S. name in " + _filename;
return false;
}
}

59
src/Mass/Mass.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef MASS_H
#define MASS_H
// wxMASSManager
// Copyright (C) 2020-2021 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 <string>
enum class MassState : std::uint8_t {
Empty, Invalid, Valid
};
class Mass {
public:
Mass(const std::string& filename);
Mass(const Mass&) = delete;
Mass& operator=(const Mass&) = delete;
Mass(Mass&&) = default;
Mass& operator=(Mass&&) = default;
static auto lastError() -> std::string const&;
static auto getNameFromFile(const std::string& filename) -> std::string;
auto filename() -> std::string const&;
auto name() -> std::string const&;
auto getName() -> std::string const&;
auto state() -> MassState;
auto updateSteamId(const std::string& steam_id) -> bool;
auto rename(const std::string& new_name) -> bool;
private:
static std::string _lastError;
std::string _filename = "";
std::string _name = "";
MassState _state = MassState::Empty;
};
#endif // MASS_H

View File

@ -0,0 +1,91 @@
// wxMASSManager
// Copyright (C) 2020-2021 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/version.h>
#if !(CORRADE_VERSION_YEAR * 100 + CORRADE_VERSION_MONTH >= 202006)
#error This application requires Corrade 2020.06 or later to build.
#endif
#include <shlobj.h>
#include <wtsapi32.h>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/Unicode.h>
#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)) {
Containers::ScopeGuard guard{process_infos, WTSFreeMemory};
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;
}
}
}
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;
}

View File

@ -0,0 +1,52 @@
#ifndef MASSBUILDERMANAGER_H
#define MASSBUILDERMANAGER_H
// wxMASSManager
// Copyright (C) 2020-2021 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 <cstdint>
#include <string>
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

View File

@ -0,0 +1,308 @@
// wxMASSManager
// Copyright (C) 2020-2021 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/version.h>
#if !(CORRADE_VERSION_YEAR * 100 + CORRADE_VERSION_MONTH >= 202006)
#error This application requires Corrade 2020.06 or later to build.
#endif
#include <algorithm>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/FormatStl.h>
#include <Corrade/Utility/String.h>
#include "MassManager.h"
static const std::string empty_string = "";
const std::string MassManager::_stagingAreaDirectory =
Utility::Directory::join(Utility::Directory::path(Utility::Directory::executableLocation()), "staging");
MassManager::MassManager(const std::string& save_path, const std::string& steam_id, bool demo) {
_saveDirectory = save_path;
_steamId = steam_id;
_demo = demo;
Containers::arrayReserve(_hangars, 32);
std::string mass_filename = "";
for(int i = 0; i < 32; i++) {
mass_filename =
Utility::Directory::join(_saveDirectory, Utility::formatString("{}Unit{:.2d}{}.sav", demo ? "Demo" : "", i, _steamId));
Containers::arrayAppend(_hangars, Mass{mass_filename});
}
if(!Utility::Directory::exists(_stagingAreaDirectory)) {
Utility::Directory::mkpath(_stagingAreaDirectory);
}
using Utility::Directory::Flag;
std::vector<std::string> file_list =
Utility::Directory::list(_stagingAreaDirectory, 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, ".sav");
});
file_list.erase(iter, file_list.end());
for(const std::string& file : file_list) {
std::string name = Mass::getNameFromFile(Utility::Directory::join(_stagingAreaDirectory, file));
if(name != "") {
_stagedMasses[file] = name;
}
}
}
auto MassManager::saveDirectory() -> std::string const& {
return _saveDirectory;
}
auto MassManager::stagingAreaDirectory() -> std::string const& {
return _stagingAreaDirectory;
}
auto MassManager::lastError() -> std::string const& {
return _lastError;
}
auto MassManager::massName(int hangar) -> std::string const& {
if(hangar < 0 || hangar >= 32) {
return empty_string;
}
return _hangars[hangar].name();
}
auto MassManager::massState(int hangar) -> MassState {
if(hangar < 0 || hangar >= 32) {
return MassState::Empty;
}
return _hangars[hangar].state();
}
void MassManager::refreshHangar(int hangar) {
if(hangar < 0 || hangar >= 32) {
return;
}
std::string mass_filename =
Utility::Directory::join(_saveDirectory, Utility::formatString("{}Unit{:.2d}{}.sav", _demo ? "Demo" : "", hangar, _steamId));
_hangars[hangar] = Mass{mass_filename};
}
auto MassManager::importMass(int staged_index, int hangar) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar out of range in MassManager::importMass()";
return false;
}
int i = 0;
for(const auto& mass_info : _stagedMasses) {
if(i != staged_index) {
++i;
continue;
}
std::string source = Utility::Directory::join(_stagingAreaDirectory, mass_info.first);
Utility::Directory::copy(source, source + ".tmp");
if(!Mass{source + ".tmp"}.updateSteamId(_steamId))
{
_lastError = "The M.A.S.S. file at " + source + " seems to be corrupt.";
Utility::Directory::rm(source + ".tmp");
return false;
}
if(Utility::Directory::exists(_hangars[hangar].filename())) {
Utility::Directory::rm(_hangars[hangar].filename());
}
Utility::Directory::move(source + ".tmp", _hangars[hangar].filename());
return true;
}
_lastError = "";
return false;
}
auto MassManager::exportMass(int hangar) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar out of range in MassManager::exportMass()";
return false;
}
if(_hangars[hangar].state() == MassState::Empty ||
_hangars[hangar].state() == MassState::Invalid) {
_lastError = Utility::formatString("There is no valid data to export in hangar {:.2d}", hangar);
return false;
}
auto name = _hangars[hangar].name();
std::string source = Utility::Directory::join(_saveDirectory, _hangars[hangar].filename());
std::string dest = Utility::Directory::join(_stagingAreaDirectory, Utility::formatString("{}_{}.sav", _steamId, _hangars[hangar].name()));
if(!Utility::Directory::copy(source, dest)) {
_lastError = Utility::formatString("Couldn't export data from hangar {:.2d} to {}", hangar, dest);
return false;
}
return true;
}
auto MassManager::moveMass(int source, int destination) -> bool {
if(source < 0 || source >= 32) {
_lastError = "Source hangar out of range.";
return false;
}
if(destination < 0 || destination >= 32) {
_lastError = "Destination hangar out of range.";
return false;
}
std::string source_file = _hangars[source].filename();
std::string dest_file = _hangars[destination].filename();
MassState dest_state = _hangars[destination].state();
switch(dest_state) {
case MassState::Empty:
break;
case MassState::Invalid:
Utility::Directory::rm(dest_file);
break;
case MassState::Valid:
Utility::Directory::move(dest_file, dest_file + ".tmp");
break;
}
Utility::Directory::move(source_file, dest_file);
if(dest_state == MassState::Valid) {
Utility::Directory::move(dest_file + ".tmp", source_file);
}
return true;
}
auto MassManager::deleteMass(int hangar) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar out of bounds";
return false;
}
bool result = Utility::Directory::rm(_hangars[hangar].filename());
if(!result) {
_lastError = "Deletion failed. Maybe the file was already deleted, or it's locked by another application.";
}
return result;
}
auto MassManager::renameMass(int hangar, const std::string& new_name) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar number out of range.";
return false;
}
if(new_name.length() > 32) {
_lastError = "The new name is longer than 32 characters.";
return false;
}
if(!_hangars[hangar].rename(new_name)) {
_lastError = _hangars[hangar].lastError();
return false;
}
return true;
}
auto MassManager::stagedMasses() -> std::map<std::string, std::string> const& {
return _stagedMasses;
}
auto MassManager::stagedMassName(int index) -> std::string const& {
int i = 0;
for(const auto& mass_info : _stagedMasses) {
if(i != index) {
++i;
continue;
}
return mass_info.second;
}
return empty_string;
}
auto MassManager::updateStagedMass(const std::string& filename) -> int {
std::string file = Utility::Directory::join(_stagingAreaDirectory, filename);
if(!Utility::Directory::exists(file)) {
return -1;
}
auto name = Mass::getNameFromFile(file);
if(name == "") {
return -1;
}
_stagedMasses[filename] = name;
int index = 0;
for(const auto& mass: _stagedMasses) {
if(mass.first != filename) {
++index;
continue;
}
return index;
}
return -1;
}
auto MassManager::removeStagedMass(const std::string& filename) -> int {
int index = 0;
for(auto it = _stagedMasses.begin(); it != _stagedMasses.end(); ++it, ++index) {
if(it->first == filename) {
_stagedMasses.erase(it);
return index;
}
}
return -1;
}
void MassManager::deleteStagedMass(int index) {
int i = 0;
for(auto it = _stagedMasses.begin(); it != _stagedMasses.end(); ++it, ++i) {
if(i == index) {
Utility::Directory::rm(Utility::Directory::join(_stagingAreaDirectory, it->first));
break;
}
}
}

View File

@ -0,0 +1,72 @@
#ifndef MASSMANAGER_H
#define MASSMANAGER_H
// wxMASSManager
// Copyright (C) 2020-2021 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 <map>
#include <string>
#include <Corrade/Containers/GrowableArray.h>
#include "../Mass/Mass.h"
using namespace Corrade;
class MassManager {
public:
MassManager(const std::string& save_path, const std::string& steam_id, bool demo);
auto saveDirectory() -> std::string const&;
auto stagingAreaDirectory() -> std::string const&;
auto lastError() -> std::string const&;
auto massName(int hangar) -> std::string const&;
auto massState(int hangar) -> MassState;
void refreshHangar(int hangar);
auto importMass(int staged_index, int hangar) -> bool;
auto exportMass(int hangar) -> bool;
auto moveMass(int source, int destination) -> bool;
auto deleteMass(int hangar) -> bool;
auto renameMass(int hangar, const std::string& new_name) -> bool;
auto stagedMasses() -> std::map<std::string, std::string> const&;
auto stagedMassName(int index) -> std::string const&;
auto updateStagedMass(const std::string& filename) -> int;
auto removeStagedMass(const std::string& filename) -> int;
void deleteStagedMass(int index);
private:
std::string _saveDirectory;
std::string _steamId;
bool _demo;
std::string _lastError = "";
Containers::Array<Mass> _hangars;
static const std::string _stagingAreaDirectory;
std::map<std::string, std::string> _stagedMasses;
};
#endif //MASSMANAGER_H

85
src/Profile/Locators.h Normal file
View File

@ -0,0 +1,85 @@
// wxMASSManager
// Copyright (C) 2020-2021 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/>.
#pragma once
constexpr char company_name_locator[] = "CompanyName\0\x0c\0\0\0StrProperty";
constexpr char active_slot_locator[] = "ActiveFrameSlot\0\x0c\0\0\0IntProperty";
constexpr char credits_locator[] = "Credit\0\x0c\0\0\0IntProperty";
constexpr char story_progress_locator[] = "StoryProgress\0\x0c\0\0\0IntProperty";
constexpr char last_mission_id_locator[] = "LastMissionID\0\x0c\0\0\0IntProperty";
constexpr char verse_steel_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x00\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char undinium_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x01\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char necrium_alloy_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x02\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char lunarite_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x03\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char asterite_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x04\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char ednil_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x0a\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char nuflalt_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x0b\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char aurelene_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x0c\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char soldus_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x0d\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char synthesized_n_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x0e\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char alcarbonite_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x14\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char keriphene_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x15\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char nitinol_cm_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x16\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char quarkium_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x17\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char alterene_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\x18\x35\x0c\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char mixed_composition_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa0\xbb\x0d\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char void_residue_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa1\xbb\x0d\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char muscular_construction_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa2\xbb\x0d\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char mineral_exoskeletology_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa3\xbb\x0d\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";
constexpr char carbonized_skin_locator[] =
"ID_4_AAE08F17428E229EC7A2209F51081A21\0\x0c\0\0\0IntProperty\0\x04\0\0\0\0\0\0\0\0\xa4\xbb\x0d\0,\0\0\0"
"Quantity_3_560F09B5485C365D3041888910019CE3\0\x0c\0\0\0IntProperty";

1013
src/Profile/Profile.cpp Normal file

File diff suppressed because it is too large Load Diff

171
src/Profile/Profile.h Normal file
View File

@ -0,0 +1,171 @@
#ifndef PROFILE_H
#define PROFILE_H
#include <cstdint>
#include <string>
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 getCompanyName() -> std::string const&;
auto renameCompany(const std::string& new_name) -> bool;
auto activeFrameSlot() const -> std::int8_t;
auto getActiveFrameSlot() -> std::int8_t;
auto credits() const -> std::int32_t;
auto getCredits() -> std::int32_t;
auto setCredits(std::int32_t) -> bool;
auto storyProgress() const -> std::int32_t;
auto getStoryProgress() -> std::int32_t;
auto setStoryProgress(std::int32_t progress) -> bool;
auto lastMissionId() const -> std::int32_t;
auto getLastMissionId() -> std::int32_t;
auto verseSteel() const -> std::int32_t;
auto getVerseSteel() -> std::int32_t;
auto setVerseSteel(std::int32_t amount) -> bool;
auto undinium() const -> std::int32_t;
auto getUndinium() -> std::int32_t;
auto setUndinium(std::int32_t amount) -> bool;
auto necriumAlloy() const -> std::int32_t;
auto getNecriumAlloy() -> std::int32_t;
auto setNecriumAlloy(std::int32_t amount) -> bool;
auto lunarite() const -> std::int32_t;
auto getLunarite() -> std::int32_t;
auto setLunarite(std::int32_t amount) -> bool;
auto asterite() const -> std::int32_t;
auto getAsterite() -> std::int32_t;
auto setAsterite(std::int32_t amount) -> bool;
auto ednil() const -> std::int32_t;
auto getEdnil() -> std::int32_t;
auto setEdnil(std::int32_t amount) -> bool;
auto nuflalt() const -> std::int32_t;
auto getNuflalt() -> std::int32_t;
auto setNuflalt(std::int32_t amount) -> bool;
auto aurelene() const -> std::int32_t;
auto getAurelene() -> std::int32_t;
auto setAurelene(std::int32_t amount) -> bool;
auto soldus() const -> std::int32_t;
auto getSoldus() -> std::int32_t;
auto setSoldus(std::int32_t amount) -> bool;
auto synthesizedN() const -> std::int32_t;
auto getSynthesizedN() -> std::int32_t;
auto setSynthesizedN(std::int32_t amount) -> bool;
auto alcarbonite() const -> std::int32_t;
auto getAlcarbonite() -> std::int32_t;
auto setAlcarbonite(std::int32_t amount) -> bool;
auto keriphene() const -> std::int32_t;
auto getKeriphene() -> std::int32_t;
auto setKeriphene(std::int32_t amount) -> bool;
auto nitinolCM() const -> std::int32_t;
auto getNitinolCM() -> std::int32_t;
auto setNitinolCM(std::int32_t amount) -> bool;
auto quarkium() const -> std::int32_t;
auto getQuarkium() -> std::int32_t;
auto setQuarkium(std::int32_t amount) -> bool;
auto alterene() const -> std::int32_t;
auto getAlterene() -> std::int32_t;
auto setAlterene(std::int32_t amount) -> bool;
auto mixedComposition() const -> std::int32_t;
auto getMixedComposition() -> std::int32_t;
auto setMixedComposition(std::int32_t amount) -> bool;
auto voidResidue() const -> std::int32_t;
auto getVoidResidue() -> std::int32_t;
auto setVoidResidue(std::int32_t amount) -> bool;
auto muscularConstruction() const -> std::int32_t;
auto getMuscularConstruction() -> std::int32_t;
auto setMuscularConstruction(std::int32_t amount) -> bool;
auto mineralExoskeletology() const -> std::int32_t;
auto getMineralExoskeletology() -> std::int32_t;
auto setMineralExoskeletology(std::int32_t amount) -> bool;
auto carbonizedSkin() const -> std::int32_t;
auto getCarbonizedSkin() -> std::int32_t;
auto setCarbonizedSkin(std::int32_t amount) -> bool;
auto backup(const std::string& filename) -> bool;
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;
std::int32_t _storyProgress;
std::int32_t _lastMissionId;
std::int32_t _verseSteel;
std::int32_t _undinium;
std::int32_t _necriumAlloy;
std::int32_t _lunarite;
std::int32_t _asterite;
std::int32_t _ednil;
std::int32_t _nuflalt;
std::int32_t _aurelene;
std::int32_t _soldus;
std::int32_t _synthesizedN;
std::int32_t _alcarbonite;
std::int32_t _keriphene;
std::int32_t _nitinolCM;
std::int32_t _quarkium;
std::int32_t _alterene;
std::int32_t _mixedComposition;
std::int32_t _voidResidue;
std::int32_t _muscularConstruction;
std::int32_t _mineralExoskeletology;
std::int32_t _carbonizedSkin;
};
#endif //PROFILE_H

View File

@ -0,0 +1,106 @@
// wxMASSManager
// Copyright (C) 2020-2021 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/version.h>
#if !(CORRADE_VERSION_YEAR * 100 + CORRADE_VERSION_MONTH >= 202006)
#error This application requires Corrade 2020.06 or later to build.
#endif
#include <algorithm>
#include <regex>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/String.h>
#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<std::string> 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<Profile> 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;
}

View File

@ -0,0 +1,52 @@
#ifndef PROFILEMANAGER_H
#define PROFILEMANAGER_H
// wxMASSManager
// Copyright (C) 2020-2021 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 <string>
#include <vector>
#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<Profile> const&;
auto currentProfile() -> Profile*;
auto setProfile(std::size_t index) -> bool;
private:
bool _ready = false;
std::string _lastError = "";
std::string _profileDirectory;
std::vector<Profile> _profiles;
Profile* _currentProfile = nullptr;
};
#endif //PROFILEMANAGER_H

View File

@ -1,5 +1,5 @@
// wxMASSManager
// Copyright (C) 2020 Guillaume Jacquemin
// Copyright (C) 2020-2021 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
@ -14,6 +14,12 @@
// 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/version.h>
#if !(CORRADE_VERSION_YEAR * 100 + CORRADE_VERSION_MONTH >= 202006)
#error This application requires Corrade 2020.06 or later to build.
#endif
#include <wx/app.h>
#include "GUI/EvtMainFrame.h"
@ -22,7 +28,7 @@ class MyApp: public wxApp {
public:
bool OnInit() {
SetAppName("wxMASSManager");
SetAppDisplayName("wxMASSManager");
SetAppDisplayName("M.A.S.S. Builder Save Tool");
wxImage::AddHandler(new wxPNGHandler);

View File

Before

Width:  |  Height:  |  Size: 486 KiB

After

Width:  |  Height:  |  Size: 486 KiB

View File

@ -1,5 +1,5 @@
// wxMASSManager
// Copyright (C) 2020 Guillaume Jacquemin
// Copyright (C) 2020-2021 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