2020-11-21 08:21:10 -08:00
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers , whose names
* are too numerous to list here . Please refer to the COPYRIGHT
* file distributed with this source distribution .
*
* 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 2
* 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 , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*
*/
# include "base/plugins.h"
2021-02-09 19:53:55 -08:00
# include "common/config-manager.h"
2021-01-30 10:22:31 -08:00
# include "common/file.h"
2021-02-02 20:45:09 -08:00
# include "common/md5.h"
2021-02-14 19:15:21 +00:00
# include "common/str-array.h"
# include "common/translation.h"
2020-11-21 08:21:10 -08:00
# include "ags/detection.h"
# include "ags/detection_tables.h"
2021-02-14 19:15:21 +00:00
# include "gui/ThemeEval.h"
# include "gui/widget.h"
# include "gui/widgets/popup.h"
2021-01-30 10:22:31 -08:00
namespace AGS3 {
static const char * const HEAD_SIG = " CLIB \x1a " ;
static const char * const TAIL_SIG = " CLIB \ x1 \ x2 \ x3 \ x4SIGE " ;
# define HEAD_SIG_SIZE 5
# define TAIL_SIG_SIZE 12
/**
* Detect the presence of an AGS game
* TODO : This is a compact version of MFLUtil : : ReadSigsAndVersion . I didn ' t
* use the full version due to the complexities of including it when
* plugins are enabled . In the future , though , it would be nice to figure
* out , since the full version can handle not detecting on files that are
* AGS , but only contain sounds , etc . rather than a game
*/
static bool isAGSFile ( Common : : File & f ) {
// Check for signature at beginning of file
char buffer [ 16 ] ;
if ( f . read ( buffer , HEAD_SIG_SIZE ) = = HEAD_SIG_SIZE & &
! memcmp ( buffer , HEAD_SIG , HEAD_SIG_SIZE ) )
return true ;
// Check for signature at end of EXE files
f . seek ( - TAIL_SIG_SIZE , SEEK_END ) ;
if ( f . read ( buffer , TAIL_SIG_SIZE ) = = TAIL_SIG_SIZE & &
! memcmp ( buffer , TAIL_SIG , TAIL_SIG_SIZE ) )
return true ;
return false ;
}
2021-02-14 19:15:21 +00:00
class AGSOptionsWidget : public GUI : : OptionsContainerWidget {
public :
explicit AGSOptionsWidget ( GuiObject * boss , const Common : : String & name , const Common : : String & domain ) ;
// OptionsContainerWidget API
void load ( ) override ;
bool save ( ) override ;
private :
// OptionsContainerWidget API
void defineLayout ( GUI : : ThemeEval & layouts , const Common : : String & layoutName , const Common : : String & overlayedLayout ) const override ;
GUI : : PopUpWidget * _langPopUp ;
Common : : StringArray _traFileNames ;
} ;
AGSOptionsWidget : : AGSOptionsWidget ( GuiObject * boss , const Common : : String & name , const Common : : String & domain ) :
OptionsContainerWidget ( boss , name , " AGSGameOptionsDialog " , false , domain ) {
GUI : : StaticTextWidget * textWidget = new GUI : : StaticTextWidget ( widgetsBoss ( ) , _dialogLayout + " .translation_desc " , _ ( " Game language: " ) , _ ( " Language to use for multilingual games " ) ) ;
textWidget - > setAlign ( Graphics : : kTextAlignRight ) ;
_langPopUp = new GUI : : PopUpWidget ( widgetsBoss ( ) , _dialogLayout + " .translation " ) ;
_langPopUp - > appendEntry ( _ ( " <default> " ) , ( uint32 ) - 1 ) ;
Common : : String path = ConfMan . get ( " path " , _domain ) ;
Common : : FSDirectory dir ( path ) ;
Common : : ArchiveMemberList traFileList ;
dir . listMatchingMembers ( traFileList , " *.tra " ) ;
int i = 0 ;
for ( Common : : ArchiveMemberList : : iterator iter = traFileList . begin ( ) ; iter ! = traFileList . end ( ) ; + + iter ) {
Common : : String traFileName = ( * iter ) - > getName ( ) ;
traFileName . erase ( traFileName . size ( ) - 4 ) ; // remove .tra extension
_traFileNames . push_back ( traFileName ) ;
_langPopUp - > appendEntry ( traFileName , i + + ) ;
}
}
void AGSOptionsWidget : : defineLayout ( GUI : : ThemeEval & layouts , const Common : : String & layoutName , const Common : : String & overlayedLayout ) const {
layouts . addDialog ( layoutName , overlayedLayout ) ;
layouts . addLayout ( GUI : : ThemeLayout : : kLayoutVertical ) . addPadding ( 16 , 16 , 16 , 16 ) ;
layouts . addLayout ( GUI : : ThemeLayout : : kLayoutHorizontal ) . addPadding ( 0 , 0 , 0 , 0 ) ;
layouts . addWidget ( " translation_desc " , " OptionsLabel " ) ;
layouts . addWidget ( " translation " , " PopUp " ) . closeLayout ( ) ;
layouts . closeLayout ( ) . closeDialog ( ) ;
}
void AGSOptionsWidget : : load ( ) {
Common : : ConfigManager : : Domain * gameConfig = ConfMan . getDomain ( _domain ) ;
if ( ! gameConfig )
return ;
uint32 curLangIndex = ( uint32 ) - 1 ;
Common : : String curLang ;
gameConfig - > tryGetVal ( " translation " , curLang ) ;
if ( ! curLang . empty ( ) ) {
for ( uint i = 0 ; i < _traFileNames . size ( ) ; + + i ) {
if ( _traFileNames [ i ] . equalsIgnoreCase ( curLang ) ) {
curLangIndex = i ;
break ;
}
}
}
_langPopUp - > setSelectedTag ( curLangIndex ) ;
}
bool AGSOptionsWidget : : save ( ) {
uint langIndex = _langPopUp - > getSelectedTag ( ) ;
if ( langIndex < _traFileNames . size ( ) )
ConfMan . set ( " translation " , _traFileNames [ langIndex ] , _domain ) ;
else
ConfMan . removeKey ( " translation " , _domain ) ;
return true ;
}
2021-01-30 10:22:31 -08:00
} // namespace AGS3
2020-11-21 08:21:10 -08:00
AGSMetaEngineDetection : : AGSMetaEngineDetection ( ) : AdvancedMetaEngineDetection ( AGS : : GAME_DESCRIPTIONS ,
2021-01-10 20:34:51 -08:00
sizeof ( AGS : : AGSGameDescription ) , AGS : : GAME_NAMES ) {
2020-11-21 08:21:10 -08:00
}
2021-01-30 10:22:31 -08:00
ADDetectedGame AGSMetaEngineDetection : : fallbackDetect ( const FileMap & allFiles , const Common : : FSList & fslist ) const {
// Set the default values for the fallback descriptor's ADGameDescription part.
AGS : : g_fallbackDesc . desc . language = Common : : UNK_LANG ;
2021-02-09 20:17:15 -08:00
AGS : : g_fallbackDesc . desc . platform = Common : : kPlatformUnknown ;
2021-01-30 10:22:31 -08:00
AGS : : g_fallbackDesc . desc . flags = ADGF_NO_FLAGS ;
2021-02-09 19:53:55 -08:00
// FIXME: Hack to return match without checking for game data,
// so that the command line game scanner will work
if ( ConfMan . get ( " gameid " ) = = " ags-scan " ) {
_gameid = " ags-scan " ;
AGS : : g_fallbackDesc . desc . gameId = " ags-scan " ;
return ADDetectedGame ( & AGS : : g_fallbackDesc . desc ) ;
}
// Set the defaults for gameid and extra
2021-01-30 10:22:31 -08:00
_gameid = " ags " ;
_extra . clear ( ) ;
2021-02-11 19:29:56 -08:00
bool hasUnknownFiles = true ;
2021-01-30 10:22:31 -08:00
// Scan for AGS games
for ( Common : : FSList : : const_iterator file = fslist . begin ( ) ; file ! = fslist . end ( ) ; + + file ) {
if ( file - > isDirectory ( ) )
continue ;
Common : : String filename = file - > getName ( ) ;
if ( ! filename . hasSuffixIgnoreCase ( " .exe " ) & &
! filename . hasSuffixIgnoreCase ( " .ags " ) & &
! filename . equalsIgnoreCase ( " ac2game.dat " ) )
// Neither, so move on
continue ;
Common : : File f ;
if ( ! f . open ( allFiles [ filename ] ) )
continue ;
if ( AGS3 : : isAGSFile ( f ) ) {
_filename = filename ;
2021-02-02 20:45:09 -08:00
f . seek ( 0 ) ;
_md5 = Common : : computeStreamMD5AsString ( f , 5000 ) ;
2021-01-30 10:22:31 -08:00
2021-02-11 19:29:56 -08:00
// Check whether the game is in the detection list with a different filename
for ( const : : AGS : : AGSGameDescription * gameP = : : AGS : : GAME_DESCRIPTIONS ;
gameP - > desc . gameId ; + + gameP ) {
if ( _md5 = = gameP - > desc . filesDescriptions [ 0 ] . md5 & &
f . size ( ) = = gameP - > desc . filesDescriptions [ 0 ] . fileSize ) {
hasUnknownFiles = false ;
_gameid = gameP - > desc . gameId ;
break ;
}
}
AGS : : g_fallbackDesc . desc . gameId = _gameid . c_str ( ) ;
AGS : : g_fallbackDesc . desc . extra = _extra . c_str ( ) ;
2021-01-30 10:22:31 -08:00
AGS : : g_fallbackDesc . desc . filesDescriptions [ 0 ] . fileName = _filename . c_str ( ) ;
AGS : : g_fallbackDesc . desc . filesDescriptions [ 0 ] . fileSize = f . size ( ) ;
2021-02-02 20:45:09 -08:00
AGS : : g_fallbackDesc . desc . filesDescriptions [ 0 ] . md5 = _md5 . c_str ( ) ;
2021-01-30 10:22:31 -08:00
2021-02-02 20:45:09 -08:00
ADDetectedGame game ( & AGS : : g_fallbackDesc . desc ) ;
game . matchedFiles [ _filename ] . md5 = _md5 ;
game . matchedFiles [ _filename ] . size = f . size ( ) ;
2021-02-11 19:29:56 -08:00
game . hasUnknownFiles = hasUnknownFiles ;
2021-02-02 20:45:09 -08:00
return game ;
2021-01-30 10:22:31 -08:00
}
}
return ADDetectedGame ( ) ;
}
2021-02-14 19:15:21 +00:00
GUI : : OptionsContainerWidget * AGSMetaEngineDetection : : buildEngineOptionsWidgetStatic ( GUI : : GuiObject * boss , const Common : : String & name , const Common : : String & target ) const {
return new AGS3 : : AGSOptionsWidget ( boss , name , target ) ;
}
2020-11-21 08:21:10 -08:00
REGISTER_PLUGIN_STATIC ( AGS_DETECTION , PLUGIN_TYPE_ENGINE_DETECTION , AGSMetaEngineDetection ) ;