Add MassManager and move backend stuff to it.
This is a big commit, but a necessary one, as too many things were intertwined. As a result, the code is now cleaner.
This commit is contained in:
parent
cf3c7305c8
commit
fc9d898c54
6 changed files with 620 additions and 250 deletions
|
@ -39,6 +39,8 @@ add_executable(wxMASSManager WIN32
|
||||||
GUI/MainFrame.cpp
|
GUI/MainFrame.cpp
|
||||||
GUI/EvtMainFrame.h
|
GUI/EvtMainFrame.h
|
||||||
GUI/EvtMainFrame.cpp
|
GUI/EvtMainFrame.cpp
|
||||||
|
MassManager/MassManager.h
|
||||||
|
MassManager/MassManager.cpp
|
||||||
resource.rc)
|
resource.rc)
|
||||||
|
|
||||||
target_link_libraries(wxMASSManager PRIVATE
|
target_link_libraries(wxMASSManager PRIVATE
|
||||||
|
|
|
@ -14,43 +14,26 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <wx/filedlg.h>
|
#include <wx/filedlg.h>
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
#include <wx/numdlg.h>
|
#include <wx/numdlg.h>
|
||||||
#include <wx/regex.h>
|
#include <wx/regex.h>
|
||||||
#include <wx/wfstream.h>
|
|
||||||
#include <wx/zipstrm.h>
|
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#include <Corrade/Containers/Optional.h>
|
||||||
#include <shlobj.h>
|
|
||||||
#include <wtsapi32.h>
|
|
||||||
|
|
||||||
#include <Corrade/Containers/Array.h>
|
|
||||||
#include <Corrade/Utility/Directory.h>
|
#include <Corrade/Utility/Directory.h>
|
||||||
#include <Corrade/Utility/FormatStl.h>
|
|
||||||
#include <Corrade/Utility/String.h>
|
|
||||||
#include <Corrade/Utility/Unicode.h>
|
|
||||||
|
|
||||||
#include "EvtMainFrame.h"
|
#include "EvtMainFrame.h"
|
||||||
|
|
||||||
using namespace Corrade;
|
using namespace Corrade;
|
||||||
|
|
||||||
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' };
|
|
||||||
|
|
||||||
EvtMainFrame::EvtMainFrame(wxWindow* parent): MainFrame(parent) {
|
EvtMainFrame::EvtMainFrame(wxWindow* parent): MainFrame(parent) {
|
||||||
SetIcon(wxIcon("MAINICON"));
|
SetIcon(wxIcon("MAINICON"));
|
||||||
|
|
||||||
getSaveDirectory();
|
if(!_manager.ready()) {
|
||||||
getLocalSteamId();
|
errorMessage("There was an issue initialising the manager:\n\n" + _manager.lastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
initialiseListView();
|
initialiseListView();
|
||||||
|
|
||||||
isGameRunning();
|
isGameRunning();
|
||||||
|
@ -62,13 +45,12 @@ EvtMainFrame::EvtMainFrame(wxWindow* parent): MainFrame(parent) {
|
||||||
|
|
||||||
warningMessage(wxString::FromUTF8("Before you start using this app, a few things you should know:\n\n"
|
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"
|
"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"
|
||||||
"Please avoid using this application while the game is running. Bad Things™ could happen to your data.\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"
|
"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."));
|
"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.Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(EvtMainFrame::fileUpdateEvent), nullptr, this);
|
||||||
_watcher.AddTree(wxFileName(Utility::Directory::toNativeSeparators(_saveDirectory), wxPATH_WIN),
|
_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", _localSteamId));
|
wxFSW_EVENT_CREATE|wxFSW_EVENT_DELETE|wxFSW_EVENT_MODIFY|wxFSW_EVENT_RENAME, wxString::Format("*%s.sav", _manager.steamId()));
|
||||||
|
|
||||||
_gameCheckTimer.Start(3000);
|
_gameCheckTimer.Start(3000);
|
||||||
}
|
}
|
||||||
|
@ -81,11 +63,19 @@ EvtMainFrame::~EvtMainFrame() {
|
||||||
_watcher.Disconnect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(EvtMainFrame::fileUpdateEvent), nullptr, this);
|
_watcher.Disconnect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(EvtMainFrame::fileUpdateEvent), nullptr, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::importEvent(wxCommandEvent&) {
|
bool EvtMainFrame::ready() {
|
||||||
std::string slot_state = _installedListView->GetItemText(_installedListView->GetFirstSelected(), 1).ToStdString();
|
return _manager.ready();
|
||||||
|
}
|
||||||
|
|
||||||
if(slot_state != "<Empty>" && slot_state != "<Invalid data>" &&
|
void EvtMainFrame::importEvent(wxCommandEvent&) {
|
||||||
wxMessageBox(wxString::Format("Hangar %.2d is already occupied by the M.A.S.S. named \"%s\". Are you sure you want to import a M.A.S.S. to this hangar ?", _installedListView->GetFirstSelected() + 1, slot_state),
|
const static std::string error_prefix = "Importing failed:\n\n";
|
||||||
|
|
||||||
|
long selected_hangar = _installedListView->GetFirstSelected();
|
||||||
|
HangarState hangar_state = _manager.hangarState(selected_hangar);
|
||||||
|
|
||||||
|
if(hangar_state == HangarState::Filled &&
|
||||||
|
wxMessageBox(wxString::Format("Hangar %.2d is already occupied by the M.A.S.S. named \"%s\". Are you sure you want to import a M.A.S.S. to this hangar ?",
|
||||||
|
selected_hangar + 1, *(_manager.massName(selected_hangar))),
|
||||||
"Question", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
|
"Question", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -98,132 +88,107 @@ void EvtMainFrame::importEvent(wxCommandEvent&) {
|
||||||
|
|
||||||
const std::string source_file = dialog.GetPath().ToUTF8().data();
|
const std::string source_file = dialog.GetPath().ToUTF8().data();
|
||||||
|
|
||||||
const std::string mass_name = getMassName(source_file);
|
Containers::Optional<std::string> mass_name = _manager.getMassName(source_file);
|
||||||
|
|
||||||
if(mass_name == "") {
|
if(!mass_name) {
|
||||||
|
errorMessage(error_prefix + _manager.lastError());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wxMessageBox(wxString::Format("Are you sure you want to import the M.A.S.S. named \"%s\" to hangar %.2d ?", mass_name, _installedListView->GetFirstSelected() + 1),
|
if(wxMessageBox(wxString::Format("Are you sure you want to import the M.A.S.S. named \"%s\" to hangar %.2d ?", *mass_name, selected_hangar + 1),
|
||||||
"Question", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
|
"Question", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_isGameRunning) {
|
switch(_manager.gameState()) {
|
||||||
errorMessage("The game is running. Aborting...");
|
case GameState::Unknown:
|
||||||
return;
|
errorMessage(error_prefix + "For security reasons, importing is disabled if the game's status is unknown.");
|
||||||
}
|
break;
|
||||||
|
case GameState::NotRunning:
|
||||||
const std::string dest_file = _saveDirectory + Utility::formatString("/Unit{:.2d}{}.sav", _installedListView->GetFirstSelected(), _localSteamId);
|
if(!_manager.importMass(source_file, selected_hangar)) {
|
||||||
|
errorMessage(error_prefix + _manager.lastError());
|
||||||
if(Utility::Directory::exists(dest_file)) {
|
}
|
||||||
Utility::Directory::rm(dest_file);
|
break;
|
||||||
}
|
case GameState::Running:
|
||||||
|
errorMessage(error_prefix + "Importing a M.A.S.S. is disabled while the game is running.");
|
||||||
Utility::Directory::copy(source_file, dest_file);
|
break;
|
||||||
|
|
||||||
{
|
|
||||||
auto mmap = Utility::Directory::map(dest_file);
|
|
||||||
|
|
||||||
auto iter = std::search(mmap.begin(), mmap.end(), &steamid_locator[0], &steamid_locator[23]);
|
|
||||||
|
|
||||||
if(iter == mmap.end()) {
|
|
||||||
errorMessage("Couldn't find the SteamID in the unit file at " + source_file + ". Aborting...");
|
|
||||||
Utility::Directory::rm(dest_file);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
iter += 37;
|
|
||||||
|
|
||||||
for(int i = 0; i < 17; ++i) {
|
|
||||||
*(iter + i) = _localSteamId[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::moveEvent(wxCommandEvent&) {
|
void EvtMainFrame::moveEvent(wxCommandEvent&) {
|
||||||
|
const static std::string error_prefix = "Move failed:\n\n";
|
||||||
|
|
||||||
long source_slot = _installedListView->GetFirstSelected();
|
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.", _installedListView->GetItemText(source_slot, 1)),
|
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) - 1;
|
"Slot", "Choose a slot", source_slot + 1, 1, 32, this) - 1;
|
||||||
|
|
||||||
if(choice == -1 || choice == source_slot) {
|
if(choice == -1 || choice == source_slot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_isGameRunning) {
|
switch(_manager.gameState()) {
|
||||||
errorMessage("The game is running. Aborting...");
|
case GameState::Unknown:
|
||||||
return;
|
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:
|
||||||
std::string orig_file = Utility::formatString("{}/Unit{:.2d}{}.sav", _saveDirectory, source_slot, _localSteamId);
|
if(!_manager.moveMass(source_slot, choice)) {
|
||||||
std::string dest_status = _installedListView->GetItemText(choice, 1).ToStdString();
|
errorMessage(error_prefix + _manager.lastError());
|
||||||
std::string dest_file = Utility::formatString("{}/Unit{:.2d}{}.sav", _saveDirectory, choice, _localSteamId);
|
}
|
||||||
|
break;
|
||||||
if(dest_status == "<Invalid data>") {
|
case GameState::Running:
|
||||||
Utility::Directory::rm(dest_file);
|
errorMessage(error_prefix + "Moving a M.A.S.S. is disabled while the game is running.");
|
||||||
}
|
break;
|
||||||
else if(dest_status != "<Empty>") {
|
|
||||||
Utility::Directory::move(dest_file, dest_file + ".tmp");
|
|
||||||
}
|
|
||||||
|
|
||||||
Utility::Directory::move(orig_file, dest_file);
|
|
||||||
|
|
||||||
if(dest_status != "<Empty>") {
|
|
||||||
Utility::Directory::move(dest_file + ".tmp", orig_file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::deleteEvent(wxCommandEvent&) {
|
void EvtMainFrame::deleteEvent(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),
|
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) {
|
"Are you sure ?", wxYES_NO|wxCENTRE|wxICON_QUESTION, this) == wxNO) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_isGameRunning) {
|
switch(_manager.gameState()) {
|
||||||
errorMessage("The game is running. Aborting...");
|
case GameState::Unknown:
|
||||||
return;
|
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:
|
||||||
std::string file = Utility::formatString("{}/Unit{:.2d}{}.sav", _saveDirectory, _installedListView->GetFirstSelected(), _localSteamId);
|
if(!_manager.deleteMass(_installedListView->GetFirstSelected())) {
|
||||||
|
errorMessage(error_prefix + _manager.lastError());
|
||||||
if(Utility::Directory::exists(file)) {
|
}
|
||||||
Utility::Directory::rm(file);
|
break;
|
||||||
|
case GameState::Running:
|
||||||
|
errorMessage(error_prefix + "Deleting a M.A.S.S. is disabled while the game is running.");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::backupEvent(wxCommandEvent&) {
|
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");
|
wxString current_timestamp = wxDateTime::Now().Format("%Y-%m-%d_%H-%M-%S");
|
||||||
|
|
||||||
wxFileDialog save_dialog{this, "Choose output location", _saveDirectory,
|
wxFileDialog save_dialog{this, "Choose output location", _manager.saveDirectory(),
|
||||||
wxString::Format("backup_%s_%s.zip", _localSteamId, current_timestamp), "Zip archive (*zip)|*zip",
|
wxString::Format("backup_%s_%s.zip", _manager.steamId(), current_timestamp), "Zip archive (*zip)|*zip",
|
||||||
wxFD_SAVE|wxFD_OVERWRITE_PROMPT};
|
wxFD_SAVE|wxFD_OVERWRITE_PROMPT};
|
||||||
|
|
||||||
if(save_dialog.ShowModal() == wxID_CANCEL) {
|
if(save_dialog.ShowModal() == wxID_CANCEL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxFFileOutputStream out{save_dialog.GetPath()};
|
if(!_manager.backupSaves(save_dialog.GetPath().ToStdString())) {
|
||||||
wxZipOutputStream zip{out};
|
errorMessage(error_prefix + _manager.lastError());
|
||||||
|
|
||||||
{
|
|
||||||
zip.PutNextEntry(wxString::Format("Profile%s.sav", _localSteamId));
|
|
||||||
wxFFileInputStream profile_stream{wxString::Format("%s\\Profile%s.sav", Utility::Directory::toNativeSeparators(_saveDirectory), _localSteamId), "rb"};
|
|
||||||
zip.Write(profile_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < 32; ++i) {
|
|
||||||
std::string unit_file = Utility::formatString("Unit{:.2d}{}.sav", i, _localSteamId);
|
|
||||||
if(Utility::Directory::exists(Utility::Directory::join(_saveDirectory, unit_file))) {
|
|
||||||
zip.PutNextEntry(unit_file);
|
|
||||||
wxFFileInputStream unit_stream{Utility::Directory::toNativeSeparators(Utility::Directory::join(_saveDirectory, unit_file))};
|
|
||||||
zip.Write(unit_stream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::openSaveDirEvent(wxCommandEvent&) {
|
void EvtMainFrame::openSaveDirEvent(wxCommandEvent&) {
|
||||||
wxExecute("explorer.exe " + Utility::Directory::toNativeSeparators(_saveDirectory));
|
wxExecute("explorer.exe " + Utility::Directory::toNativeSeparators(_manager.saveDirectory()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::installedSelectionEvent(wxListEvent&) {
|
void EvtMainFrame::installedSelectionEvent(wxListEvent&) {
|
||||||
|
@ -236,37 +201,71 @@ void EvtMainFrame::listColumnDragEvent(wxListEvent& event) {
|
||||||
|
|
||||||
void EvtMainFrame::fileUpdateEvent(wxFileSystemWatcherEvent& event) {
|
void EvtMainFrame::fileUpdateEvent(wxFileSystemWatcherEvent& event) {
|
||||||
int event_type = event.GetChangeType();
|
int event_type = event.GetChangeType();
|
||||||
|
wxString event_file = event.GetPath().GetFullName();
|
||||||
|
|
||||||
if(event_type == wxFSW_EVENT_MODIFY && _lastWatcherEventType == wxFSW_EVENT_RENAME) {
|
if(event_type == wxFSW_EVENT_MODIFY && _lastWatcherEventType == wxFSW_EVENT_RENAME) {
|
||||||
_lastWatcherEventType = event_type;
|
_lastWatcherEventType = event_type;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastWatcherEventType = event_type;
|
|
||||||
|
|
||||||
wxString event_file = event.GetNewPath().GetFullName();
|
|
||||||
|
|
||||||
if(!event_file.EndsWith(".sav")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
wxMilliSleep(50);
|
wxMilliSleep(50);
|
||||||
|
|
||||||
if(event_file == wxString::Format("Profile%s.sav", _localSteamId)) {
|
wxRegEx regex;
|
||||||
getActiveSlot();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
wxRegEx regex(wxString::Format("Unit([0-3][0-9])%s.sav", _localSteamId), wxRE_ADVANCED);
|
|
||||||
|
|
||||||
if(regex.Matches(event_file)) {
|
switch (event_type) {
|
||||||
long slot;
|
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)) {
|
if(regex.GetMatch(event_file, 1).ToLong(&slot) && slot >= 0 && slot < 32) {
|
||||||
_installedListView->SetItem(slot, 1, getSlotMassName(slot));
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lastWatcherEventType = event_type;
|
||||||
|
|
||||||
updateCommandsState();
|
updateCommandsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,43 +273,6 @@ void EvtMainFrame::gameCheckTimerEvent(wxTimerEvent&) {
|
||||||
isGameRunning();
|
isGameRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::getSaveDirectory() {
|
|
||||||
wchar_t h[MAX_PATH];
|
|
||||||
if(!SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, h))) {
|
|
||||||
errorMessage("Couldn't get the path for %LOCALAPPDATA%. :/");
|
|
||||||
Close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_saveDirectory = Utility::Directory::join(Utility::Directory::fromNativeSeparators(Utility::Unicode::narrow(h)), "MASS_Builder/Saved/SaveGames");
|
|
||||||
|
|
||||||
if(!Utility::Directory::exists(_saveDirectory)) {
|
|
||||||
errorMessage("Couldn't find the M.A.S.S. Builder save directory at " + _saveDirectory + ". Please run the game at least once to create it.");
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvtMainFrame::getLocalSteamId() {
|
|
||||||
std::vector<std::string> listing = Utility::Directory::list(_saveDirectory);
|
|
||||||
|
|
||||||
wxRegEx regex;
|
|
||||||
if(!regex.Compile("Profile([0-9]{17}).sav", wxRE_ADVANCED)) {
|
|
||||||
errorMessage("Couldn't compile the regex.");
|
|
||||||
Close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const std::string& s : listing) {
|
|
||||||
if(regex.Matches(s)) {
|
|
||||||
_localSteamId = regex.GetMatch(s, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorMessage("Couldn't find your save files. Please play at least once.");
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvtMainFrame::initialiseListView() {
|
void EvtMainFrame::initialiseListView() {
|
||||||
for(long i = 0; i < 32; i++) {
|
for(long i = 0; i < 32; i++) {
|
||||||
_installedListView->InsertItem(i, wxString::Format("%.2i", i + 1));
|
_installedListView->InsertItem(i, wxString::Format("%.2i", i + 1));
|
||||||
|
@ -325,110 +287,76 @@ void EvtMainFrame::initialiseListView() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::isGameRunning() {
|
void EvtMainFrame::isGameRunning() {
|
||||||
WTS_PROCESS_INFOW* process_infos = nullptr;
|
GameState state = _manager.checkGameState();
|
||||||
unsigned long process_count = 0;
|
|
||||||
|
|
||||||
if(WTSEnumerateProcessesW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &process_infos, &process_count)) {
|
switch(state) {
|
||||||
for(unsigned long i = 0; i < process_count; ++i) {
|
case GameState::Unknown:
|
||||||
if(std::wcscmp(process_infos[i].pProcessName, L"MASS_Builder-Win64-Shipping.exe") == 0) {
|
_gameStatus->SetLabel("unknown");
|
||||||
_isGameRunning = true;
|
_gameStatus->SetForegroundColour(wxColour("orange"));
|
||||||
break;
|
break;
|
||||||
}
|
case GameState::NotRunning:
|
||||||
else {
|
|
||||||
_isGameRunning = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_isGameRunning) {
|
|
||||||
_gameStatus->SetLabel("running");
|
|
||||||
_gameStatus->SetForegroundColour(wxColour("red"));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_gameStatus->SetLabel("not running");
|
_gameStatus->SetLabel("not running");
|
||||||
_gameStatus->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_CAPTIONTEXT));
|
_gameStatus->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_CAPTIONTEXT));
|
||||||
}
|
break;
|
||||||
}
|
case GameState::Running:
|
||||||
else {
|
_gameStatus->SetLabel("running");
|
||||||
_isGameRunning = false;
|
_gameStatus->SetForegroundColour(wxColour("red"));
|
||||||
_gameStatus->SetLabel("unknown");
|
break;
|
||||||
_gameStatus->SetForegroundColour(wxColour("orange"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(process_infos != nullptr) {
|
|
||||||
WTSFreeMemory(process_infos);
|
|
||||||
process_infos = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCommandsState();
|
updateCommandsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::refreshListView() {
|
void EvtMainFrame::refreshListView() {
|
||||||
for(long i = 0; i < 32; i++) {
|
for(int i = 0; i < 32; i++) {
|
||||||
_installedListView->SetItem(i, 1, getSlotMassName(i));
|
refreshHangar(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCommandsState();
|
updateCommandsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::getActiveSlot() {
|
void EvtMainFrame::getActiveSlot() {
|
||||||
auto mmap = Utility::Directory::mapRead(Utility::formatString("{}/Profile{}.sav", _saveDirectory, _localSteamId));
|
char slot = _manager.activeSlot();
|
||||||
|
|
||||||
auto iter = std::search(mmap.begin(), mmap.end(), &active_slot_locator[0], &active_slot_locator[31]);
|
wxFont tmp_font = _installedListView->GetItemFont(slot);
|
||||||
|
|
||||||
wxFont tmp_font = _installedListView->GetItemFont(_activeSlot);
|
|
||||||
tmp_font.SetWeight(wxFONTWEIGHT_NORMAL);
|
tmp_font.SetWeight(wxFONTWEIGHT_NORMAL);
|
||||||
_installedListView->SetItemFont(_activeSlot, tmp_font);
|
_installedListView->SetItemFont(slot, tmp_font);
|
||||||
|
|
||||||
if(iter == mmap.end()) {
|
slot = _manager.getActiveSlot();
|
||||||
if(std::strncmp(&mmap[0x3F6], "Credit", 6) == 0) {
|
|
||||||
_activeSlot = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_activeSlot = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_activeSlot = *(iter + 41);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_activeSlot != -1) {
|
if(slot != -1) {
|
||||||
_installedListView->SetItemFont(_activeSlot, _installedListView->GetItemFont(_activeSlot).Bold());
|
_installedListView->SetItemFont(slot, _installedListView->GetItemFont(slot).Bold());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvtMainFrame::updateCommandsState() {
|
void EvtMainFrame::updateCommandsState() {
|
||||||
long selection = _installedListView->GetFirstSelected();
|
long selection = _installedListView->GetFirstSelected();
|
||||||
wxString state = "";
|
GameState game_state = _manager.gameState();
|
||||||
if(selection != -1) {
|
HangarState hangar_state = _manager.hangarState(selection);
|
||||||
state = _installedListView->GetItemText(selection, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
_importButton->Enable(selection != -1 && !_isGameRunning);
|
_importButton->Enable(selection != -1 && game_state != GameState::Running);
|
||||||
_moveButton->Enable(selection != -1 && !_isGameRunning && state != "<Empty>" && state != "<Invalid data>");
|
_moveButton->Enable(selection != -1 && game_state != GameState::Running && hangar_state != HangarState::Empty && hangar_state != HangarState::Invalid);
|
||||||
_deleteButton->Enable(selection != -1 && !_isGameRunning && state != "<Empty>");
|
_deleteButton->Enable(selection != -1 && game_state != GameState::Running && hangar_state != HangarState::Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string EvtMainFrame::getSlotMassName(int index) {
|
void EvtMainFrame::refreshHangar(int slot) {
|
||||||
std::string unit_file = Utility::formatString("{}/Unit{:.2d}{}.sav", _saveDirectory, index, _localSteamId);
|
if(slot < 0 && slot >= 32) {
|
||||||
if(Utility::Directory::exists(unit_file)) {
|
return;
|
||||||
std::string mass_name = getMassName(unit_file);
|
|
||||||
return (mass_name == "" ? "<Invalid data>" : mass_name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "<Empty>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string EvtMainFrame::getMassName(const std::string& filename) {
|
|
||||||
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()) {
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return iter + 70;
|
_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::infoMessage(const wxString& message) {
|
void EvtMainFrame::infoMessage(const wxString& message) {
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
#include <wx/fswatcher.h>
|
#include <wx/fswatcher.h>
|
||||||
|
|
||||||
|
#include "../MassManager/MassManager.h"
|
||||||
|
|
||||||
#include "MainFrame.h"
|
#include "MainFrame.h"
|
||||||
|
|
||||||
class EvtMainFrame: public MainFrame {
|
class EvtMainFrame: public MainFrame {
|
||||||
|
@ -28,6 +30,8 @@ class EvtMainFrame: public MainFrame {
|
||||||
EvtMainFrame(wxWindow* parent);
|
EvtMainFrame(wxWindow* parent);
|
||||||
~EvtMainFrame();
|
~EvtMainFrame();
|
||||||
|
|
||||||
|
auto ready() -> bool;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void importEvent(wxCommandEvent&);
|
void importEvent(wxCommandEvent&);
|
||||||
void moveEvent(wxCommandEvent&);
|
void moveEvent(wxCommandEvent&);
|
||||||
|
@ -40,24 +44,18 @@ class EvtMainFrame: public MainFrame {
|
||||||
void gameCheckTimerEvent(wxTimerEvent&);
|
void gameCheckTimerEvent(wxTimerEvent&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void getSaveDirectory();
|
|
||||||
void getLocalSteamId();
|
|
||||||
void initialiseListView();
|
void initialiseListView();
|
||||||
void isGameRunning();
|
void isGameRunning();
|
||||||
void refreshListView();
|
void refreshListView();
|
||||||
void getActiveSlot();
|
void getActiveSlot();
|
||||||
void updateCommandsState();
|
void updateCommandsState();
|
||||||
std::string getSlotMassName(int index);
|
void refreshHangar(int slot);
|
||||||
std::string getMassName(const std::string& filename);
|
|
||||||
|
|
||||||
void infoMessage(const wxString& message);
|
void infoMessage(const wxString& message);
|
||||||
void warningMessage(const wxString& message);
|
void warningMessage(const wxString& message);
|
||||||
void errorMessage(const wxString& message);
|
void errorMessage(const wxString& message);
|
||||||
|
|
||||||
std::string _saveDirectory;
|
MassManager _manager;
|
||||||
std::string _localSteamId;
|
|
||||||
bool _isGameRunning = false;
|
|
||||||
char _activeSlot = 0;
|
|
||||||
|
|
||||||
wxFileSystemWatcher _watcher;
|
wxFileSystemWatcher _watcher;
|
||||||
int _lastWatcherEventType = 0;
|
int _lastWatcherEventType = 0;
|
||||||
|
|
347
MassManager/MassManager.cpp
Normal file
347
MassManager/MassManager.cpp
Normal file
|
@ -0,0 +1,347 @@
|
||||||
|
// 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 <vector>
|
||||||
|
|
||||||
|
#include <wx/regex.h>
|
||||||
|
#include <wx/wfstream.h>
|
||||||
|
#include <wx/zipstrm.h>
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#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' };
|
||||||
|
|
||||||
|
MassManager::MassManager() {
|
||||||
|
_ready = findSaveDirectory() && findSteamId();
|
||||||
|
|
||||||
|
if(!_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_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::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::strncmp(&mmap[0x3F6], "Credit", 6) == 0) {
|
||||||
|
_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::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MassManager::deleteMass(int hangar) {
|
||||||
|
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::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::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;
|
||||||
|
}
|
91
MassManager/MassManager.h
Normal file
91
MassManager/MassManager.h
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
#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 <string>
|
||||||
|
|
||||||
|
#include <Corrade/Containers/StaticArray.h>
|
||||||
|
#include <Corrade/Containers/Optional.h>
|
||||||
|
|
||||||
|
using namespace Corrade;
|
||||||
|
|
||||||
|
enum class GameState : uint8_t {
|
||||||
|
Unknown, NotRunning, Running
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HangarState : uint8_t {
|
||||||
|
Empty, Invalid, Filled
|
||||||
|
};
|
||||||
|
|
||||||
|
class MassManager {
|
||||||
|
public:
|
||||||
|
MassManager();
|
||||||
|
|
||||||
|
auto ready() -> bool;
|
||||||
|
auto lastError() -> std::string const&;
|
||||||
|
|
||||||
|
auto saveDirectory() -> 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 moveMass(int source, int destination) -> bool;
|
||||||
|
|
||||||
|
auto deleteMass(int hangar) -> 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>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto findSaveDirectory() -> bool;
|
||||||
|
auto findSteamId() -> bool;
|
||||||
|
|
||||||
|
bool _ready;
|
||||||
|
|
||||||
|
std::string _lastError = "";
|
||||||
|
|
||||||
|
std::string _saveDirectory = "";
|
||||||
|
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};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //MASSMANAGER_H
|
6
main.cpp
6
main.cpp
|
@ -25,8 +25,12 @@ class MyApp: public wxApp {
|
||||||
SetAppDisplayName("wxMASSManager");
|
SetAppDisplayName("wxMASSManager");
|
||||||
|
|
||||||
EvtMainFrame* main_frame = new EvtMainFrame(nullptr);
|
EvtMainFrame* main_frame = new EvtMainFrame(nullptr);
|
||||||
main_frame->Show();
|
|
||||||
|
|
||||||
|
if(!main_frame->ready()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
main_frame->Show();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue