2020-01-09 13:59:19 +01:00
// 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/>.
2020-01-10 12:23:25 +01:00
# include <cstring>
2020-01-09 13:59:19 +01:00
# include <algorithm>
# include <vector>
# include <wx/filedlg.h>
# include <wx/msgdlg.h>
# include <wx/numdlg.h>
# include <wx/regex.h>
2020-01-09 22:10:38 +01:00
# define WIN32_LEAN_AND_MEAN
# include <shlobj.h>
2020-01-10 12:23:25 +01:00
# include <wtsapi32.h>
2020-01-09 22:10:38 +01:00
2020-01-09 13:59:19 +01:00
# include <Corrade/Containers/Array.h>
# include <Corrade/Utility/Directory.h>
# include <Corrade/Utility/FormatStl.h>
2020-01-10 00:01:24 +01:00
# include <Corrade/Utility/String.h>
2020-01-09 22:10:38 +01:00
# include <Corrade/Utility/Unicode.h>
2020-01-09 13:59:19 +01:00
# include "EvtMainFrame.h"
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 ' } ;
EvtMainFrame : : EvtMainFrame ( wxWindow * parent ) : MainFrame ( parent ) {
SetIcon ( wxIcon ( " MAINICON " ) ) ;
getSaveDirectory ( ) ;
getLocalSteamId ( ) ;
initialiseListView ( ) ;
2020-01-10 12:23:25 +01:00
isGameRunning ( ) ;
2020-01-09 13:59:19 +01:00
_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. \n To 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 "
" 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. " ) ) ;
2020-01-10 00:01:24 +01:00
_watcher . Connect ( wxEVT_FSWATCHER , wxFileSystemWatcherEventHandler ( EvtMainFrame : : fileUpdateEvent ) , nullptr , this ) ;
_watcher . AddTree ( wxFileName ( Utility : : Directory : : toNativeSeparators ( _saveDirectory ) , wxPATH_WIN ) ,
wxFSW_EVENT_CREATE | wxFSW_EVENT_DELETE | wxFSW_EVENT_MODIFY | wxFSW_EVENT_RENAME , wxString : : Format ( " Unit??%s.sav " , _localSteamId ) ) ;
2020-01-10 12:23:25 +01:00
_gameCheckTimer . Start ( 3000 ) ;
2020-01-09 13:59:19 +01:00
}
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 ) ;
2020-01-10 00:01:24 +01:00
_watcher . Disconnect ( wxEVT_FSWATCHER , wxFileSystemWatcherEventHandler ( EvtMainFrame : : fileUpdateEvent ) , nullptr , this ) ;
2020-01-09 13:59:19 +01:00
}
void EvtMainFrame : : importEvent ( wxCommandEvent & ) {
std : : string slot_state = _installedListView - > GetItemText ( _installedListView - > GetFirstSelected ( ) , 1 ) . ToStdString ( ) ;
if ( slot_state ! = " <Empty> " & & slot_state ! = " <Invalid data> " & &
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 ) ,
" Question " , wxYES_NO | wxCENTRE | wxICON_QUESTION , this ) = = wxNO ) {
return ;
}
wxFileDialog dialog ( this , " Select a unit file " , wxEmptyString , wxEmptyString , " M.A.S.S. Builder unit files (*.sav) | * . sav " , wxFD_OPEN|wxFD_FILE_MUST_EXIST) ;
if ( dialog . ShowModal ( ) = = wxID_CANCEL ) {
return ;
}
const std : : string source_file = dialog . GetPath ( ) . ToUTF8 ( ) . data ( ) ;
const std : : string mass_name = getMassName ( source_file ) ;
if ( mass_name = = " " ) {
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 ) ,
" Question " , wxYES_NO | wxCENTRE | wxICON_QUESTION , this ) = = wxNO ) {
return ;
}
2020-01-10 12:23:25 +01:00
if ( _isGameRunning ) {
errorMessage ( " The game is running. Aborting... " ) ;
return ;
}
2020-01-09 13:59:19 +01:00
const std : : string dest_file = _saveDirectory + Utility : : formatString ( " /Unit{:.2d}{}.sav " , _installedListView - > GetFirstSelected ( ) , _localSteamId ) ;
if ( Utility : : Directory : : exists ( dest_file ) ) {
Utility : : Directory : : rm ( dest_file ) ;
}
Utility : : Directory : : copy ( source_file , dest_file ) ;
{
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 & ) {
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 ? \n Notes: \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 ) ) ,
" Slot " , " Choose a slot " , source_slot + 1 , 1 , 32 , this ) - 1 ;
if ( choice = = - 1 | | choice = = source_slot ) {
return ;
}
2020-01-10 12:23:25 +01:00
if ( _isGameRunning ) {
errorMessage ( " The game is running. Aborting... " ) ;
return ;
}
2020-01-09 13:59:19 +01:00
std : : string orig_file = Utility : : formatString ( " {}/Unit{:.2d}{}.sav " , _saveDirectory , source_slot , _localSteamId ) ;
std : : string dest_status = _installedListView - > GetItemText ( choice , 1 ) . ToStdString ( ) ;
std : : string dest_file = Utility : : formatString ( " {}/Unit{:.2d}{}.sav " , _saveDirectory , choice , _localSteamId ) ;
if ( dest_status = = " <Invalid data> " ) {
Utility : : Directory : : rm ( dest_file ) ;
}
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 & ) {
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 ;
}
2020-01-10 12:23:25 +01:00
if ( _isGameRunning ) {
errorMessage ( " The game is running. Aborting... " ) ;
return ;
}
2020-01-09 13:59:19 +01:00
std : : string file = Utility : : formatString ( " {}/Unit{:.2d}{}.sav " , _saveDirectory , _installedListView - > GetFirstSelected ( ) , _localSteamId ) ;
if ( Utility : : Directory : : exists ( file ) ) {
Utility : : Directory : : rm ( file ) ;
}
}
void EvtMainFrame : : openSaveDirEvent ( wxCommandEvent & ) {
wxExecute ( " explorer.exe " + Utility : : Directory : : toNativeSeparators ( _saveDirectory ) ) ;
}
void EvtMainFrame : : installedSelectionEvent ( wxListEvent & ) {
updateCommandsState ( ) ;
}
void EvtMainFrame : : listColumnDragEvent ( wxListEvent & event ) {
event . Veto ( ) ;
}
2020-01-10 00:01:24 +01:00
void EvtMainFrame : : fileUpdateEvent ( wxFileSystemWatcherEvent & event ) {
int event_type = event . GetChangeType ( ) ;
if ( event_type = = wxFSW_EVENT_MODIFY & & _lastWatcherEventType = = wxFSW_EVENT_RENAME ) {
_lastWatcherEventType = event_type ;
return ;
}
_lastWatcherEventType = event_type ;
std : : string event_file = ( Utility : : Directory : : fromNativeSeparators ( event . GetNewPath ( ) . GetFullName ( ) . ToStdString ( ) ) ) ;
if ( ! Utility : : String : : endsWith ( event_file , " .sav " ) ) {
return ;
}
long slot ;
try {
slot = std : : stol ( std : : string ( event_file . data ( ) + 4 , 2 ) ) ;
}
catch ( const std : : invalid_argument & ) {
return ;
}
_installedListView - > SetItem ( slot , 1 , getSlotMassName ( slot ) ) ;
updateCommandsState ( ) ;
2020-01-09 13:59:19 +01:00
}
2020-01-10 12:23:25 +01:00
void EvtMainFrame : : gameCheckTimerEvent ( wxTimerEvent & ) {
isGameRunning ( ) ;
}
2020-01-09 13:59:19 +01:00
void EvtMainFrame : : getSaveDirectory ( ) {
2020-01-09 22:10:38 +01:00
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 " ) ;
2020-01-09 13:59:19 +01:00
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 ( ) {
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 ( ) ;
}
2020-01-10 12:23:25 +01:00
void EvtMainFrame : : isGameRunning ( ) {
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 ) {
_isGameRunning = true ;
break ;
}
else {
_isGameRunning = false ;
}
}
if ( _isGameRunning ) {
_gameStatus - > SetLabel ( " running " ) ;
_gameStatus - > SetForegroundColour ( wxColour ( " red " ) ) ;
}
else {
_gameStatus - > SetLabel ( " not running " ) ;
_gameStatus - > SetForegroundColour ( wxSystemSettings : : GetColour ( wxSYS_COLOUR_CAPTIONTEXT ) ) ;
}
}
else {
_isGameRunning = false ;
_gameStatus - > SetLabel ( " unknown " ) ;
_gameStatus - > SetForegroundColour ( wxColour ( " orange " ) ) ;
}
if ( process_infos ! = nullptr ) {
WTSFreeMemory ( process_infos ) ;
process_infos = nullptr ;
}
updateCommandsState ( ) ;
}
2020-01-09 13:59:19 +01:00
void EvtMainFrame : : refreshListView ( ) {
for ( long i = 0 ; i < 32 ; i + + ) {
_installedListView - > SetItem ( i , 1 , getSlotMassName ( i ) ) ;
}
updateCommandsState ( ) ;
}
void EvtMainFrame : : updateCommandsState ( ) {
long selection = _installedListView - > GetFirstSelected ( ) ;
2020-01-10 12:23:25 +01:00
wxString state = " " ;
2020-01-09 13:59:19 +01:00
if ( selection ! = - 1 ) {
2020-01-10 12:23:25 +01:00
state = _installedListView - > GetItemText ( selection , 1 ) ;
2020-01-09 13:59:19 +01:00
}
2020-01-10 12:23:25 +01:00
_importButton - > Enable ( selection ! = - 1 & & ! _isGameRunning ) ;
_moveButton - > Enable ( selection ! = - 1 & & ! _isGameRunning & & state ! = " <Empty> " & & state ! = " <Invalid data> " ) ;
_deleteButton - > Enable ( selection ! = - 1 & & ! _isGameRunning & & state ! = " <Empty> " ) ;
2020-01-09 13:59:19 +01:00
}
std : : string EvtMainFrame : : getSlotMassName ( int index ) {
std : : string unit_file = Utility : : formatString ( " {}/Unit{:.2d}{}.sav " , _saveDirectory , index , _localSteamId ) ;
if ( Utility : : Directory : : exists ( unit_file ) ) {
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 ;
}
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 ) ;
}