|
|
|
|
@@ -225,18 +225,13 @@ static void listlowercase( stringlist_t *list )
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
void listdirectory( stringlist_t *list, const char *path )
|
|
|
|
|
void listdirectory( stringlist_t *list, const char *path, qboolean dirs_only )
|
|
|
|
|
{
|
|
|
|
|
#if XASH_WIN32
|
|
|
|
|
char pattern[4096];
|
|
|
|
|
struct _finddata_t n_file;
|
|
|
|
|
intptr_t hFile;
|
|
|
|
|
#else
|
|
|
|
|
DIR *dir;
|
|
|
|
|
struct dirent *entry;
|
|
|
|
|
#endif
|
|
|
|
|
struct _finddata_t n_file;
|
|
|
|
|
intptr_t hFile;
|
|
|
|
|
|
|
|
|
|
#if XASH_WIN32
|
|
|
|
|
Q_snprintf( pattern, sizeof( pattern ), "%s/*", path );
|
|
|
|
|
|
|
|
|
|
// ask for the directory listing handle
|
|
|
|
|
@@ -247,15 +242,32 @@ void listdirectory( stringlist_t *list, const char *path )
|
|
|
|
|
stringlistappend( list, n_file.name );
|
|
|
|
|
// iterate through the directory
|
|
|
|
|
while( _findnext( hFile, &n_file ) == 0 )
|
|
|
|
|
{
|
|
|
|
|
if( dirs_only && !FBitSet( n_file.attrib, _A_SUBDIR ))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
stringlistappend( list, n_file.name );
|
|
|
|
|
}
|
|
|
|
|
_findclose( hFile );
|
|
|
|
|
#else
|
|
|
|
|
if( !( dir = opendir( path ) ) )
|
|
|
|
|
DIR *dir;
|
|
|
|
|
struct dirent *entry;
|
|
|
|
|
|
|
|
|
|
dir = opendir( path );
|
|
|
|
|
|
|
|
|
|
if( !dir )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// iterate through the directory
|
|
|
|
|
while( ( entry = readdir( dir ) ))
|
|
|
|
|
while(( entry = readdir( dir )))
|
|
|
|
|
{
|
|
|
|
|
// FIXME: this is a BSD extension, add check to wscript
|
|
|
|
|
if( dirs_only && entry->d_type != DT_DIR )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
stringlistappend( list, entry->d_name );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
closedir( dir );
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
@@ -396,7 +408,7 @@ void FS_AddGameDirectory( const char *dir, uint flags )
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
stringlistinit( &list );
|
|
|
|
|
listdirectory( &list, dir );
|
|
|
|
|
listdirectory( &list, dir, false );
|
|
|
|
|
stringlistsort( &list );
|
|
|
|
|
|
|
|
|
|
for( archive = g_archives; archive->ext; archive++ )
|
|
|
|
|
@@ -508,12 +520,13 @@ FS_WriteGameInfo
|
|
|
|
|
assume GameInfo is valid
|
|
|
|
|
================
|
|
|
|
|
*/
|
|
|
|
|
static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )
|
|
|
|
|
static qboolean FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )
|
|
|
|
|
{
|
|
|
|
|
file_t *f = FS_Open( filepath, "w", false ); // we in binary-mode
|
|
|
|
|
int i, write_ambients = false;
|
|
|
|
|
|
|
|
|
|
if( !f ) Sys_Error( "%s: can't write %s\n", __func__, filepath ); // may be disk-space is out?
|
|
|
|
|
if( !f )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
FS_Printf( f, "// generated by " XASH_ENGINE_NAME " " XASH_VERSION "-%s (%s-%s)\n\n\n", Q_buildcommit(), Q_buildos(), Q_buildarch() );
|
|
|
|
|
|
|
|
|
|
@@ -637,25 +650,50 @@ static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )
|
|
|
|
|
FS_Printf( f, "demomap\t\t\"%s\"\n", GameInfo->demomap );
|
|
|
|
|
|
|
|
|
|
FS_Close( f ); // all done
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir )
|
|
|
|
|
static void FS_MakeGameInfo( void )
|
|
|
|
|
{
|
|
|
|
|
if( FS_WriteGameInfo( "gameinfo.txt", FI.GameInfo ))
|
|
|
|
|
Con_Printf( "Successfully generated %s/gameinfo.txt\n", FI.GameInfo->gamefolder );
|
|
|
|
|
else
|
|
|
|
|
Con_Printf( S_ERROR "Can't open %s/gameinfo.txt for write\n", FI.GameInfo->gamefolder );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir, qboolean quake, time_t mtime )
|
|
|
|
|
{
|
|
|
|
|
memset( GameInfo, 0, sizeof( *GameInfo ));
|
|
|
|
|
|
|
|
|
|
// filesystem info
|
|
|
|
|
Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title ));
|
|
|
|
|
GameInfo->mtime = mtime;
|
|
|
|
|
Q_strncpy( GameInfo->gamefolder, gamedir, sizeof( GameInfo->gamefolder ));
|
|
|
|
|
Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir ));
|
|
|
|
|
Q_strncpy( GameInfo->sp_entity, "info_player_start", sizeof( GameInfo->sp_entity ));
|
|
|
|
|
Q_strncpy( GameInfo->mp_entity, "info_player_deathmatch", sizeof( GameInfo->mp_entity ));
|
|
|
|
|
Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap ));
|
|
|
|
|
Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path ));
|
|
|
|
|
Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll ));
|
|
|
|
|
Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux ));
|
|
|
|
|
Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx ));
|
|
|
|
|
Q_strncpy( GameInfo->iconpath, "game.ico", sizeof( GameInfo->iconpath ));
|
|
|
|
|
|
|
|
|
|
if( quake )
|
|
|
|
|
{
|
|
|
|
|
Q_strncpy( GameInfo->basedir, "id1", sizeof( GameInfo->basedir ));
|
|
|
|
|
Q_strncpy( GameInfo->title, gamedir, sizeof( GameInfo->title ));
|
|
|
|
|
Q_strncpy( GameInfo->startmap, "start", sizeof( GameInfo->startmap ));
|
|
|
|
|
Q_strncpy( GameInfo->dll_path, "bin", sizeof( GameInfo->dll_path ));
|
|
|
|
|
Q_strncpy( GameInfo->game_dll, "bin/progs.dll", sizeof( GameInfo->game_dll ));
|
|
|
|
|
Q_strncpy( GameInfo->game_dll_linux, "bin/progs.so", sizeof( GameInfo->game_dll_linux ));
|
|
|
|
|
Q_strncpy( GameInfo->game_dll_osx, "bin/progs.dylib", sizeof( GameInfo->game_dll_osx ));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir ));
|
|
|
|
|
Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title ));
|
|
|
|
|
Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap ));
|
|
|
|
|
Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path ));
|
|
|
|
|
Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll ));
|
|
|
|
|
Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux ));
|
|
|
|
|
Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx ));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameInfo->max_edicts = DEFAULT_MAX_EDICTS; // default value if not specified
|
|
|
|
|
GameInfo->max_tents = 500;
|
|
|
|
|
GameInfo->max_beams = 128;
|
|
|
|
|
@@ -940,36 +978,19 @@ static void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, cons
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
================
|
|
|
|
|
FS_CreateDefaultGameInfo
|
|
|
|
|
================
|
|
|
|
|
*/
|
|
|
|
|
static void FS_CreateDefaultGameInfo( const char *filename )
|
|
|
|
|
{
|
|
|
|
|
gameinfo_t defGI;
|
|
|
|
|
|
|
|
|
|
FS_InitGameInfo( &defGI, fs_basedir );
|
|
|
|
|
|
|
|
|
|
// make simple gameinfo.txt
|
|
|
|
|
FS_WriteGameInfo( filename, &defGI );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
================
|
|
|
|
|
FS_ParseLiblistGam
|
|
|
|
|
================
|
|
|
|
|
*/
|
|
|
|
|
static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo )
|
|
|
|
|
static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime )
|
|
|
|
|
{
|
|
|
|
|
char *afile;
|
|
|
|
|
char *afile = FS_LoadDirectFile( filename, NULL );
|
|
|
|
|
|
|
|
|
|
if( !GameInfo ) return false;
|
|
|
|
|
afile = (char *)FS_LoadDirectFile( filename, NULL );
|
|
|
|
|
if( !afile ) return false;
|
|
|
|
|
|
|
|
|
|
FS_InitGameInfo( GameInfo, gamedir );
|
|
|
|
|
if( !afile )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
FS_InitGameInfo( GameInfo, gamedir, false, mtime );
|
|
|
|
|
FS_ParseGenericGameInfo( GameInfo, afile, false );
|
|
|
|
|
|
|
|
|
|
Mem_Free( afile );
|
|
|
|
|
@@ -982,18 +1003,15 @@ static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, g
|
|
|
|
|
FS_ConvertGameInfo
|
|
|
|
|
================
|
|
|
|
|
*/
|
|
|
|
|
static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path )
|
|
|
|
|
static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path, gameinfo_t *gi, time_t liblist_mtime )
|
|
|
|
|
{
|
|
|
|
|
gameinfo_t GameInfo;
|
|
|
|
|
memset( gi, 0, sizeof( *gi ));
|
|
|
|
|
|
|
|
|
|
memset( &GameInfo, 0, sizeof( GameInfo ));
|
|
|
|
|
|
|
|
|
|
if( FS_ParseLiblistGam( liblist_path, gamedir, &GameInfo ))
|
|
|
|
|
// liblist.gam to gameinfo.txt conversion is deprecated, only support for RwDir!
|
|
|
|
|
if( FS_ParseLiblistGam( liblist_path, gamedir, gi, liblist_mtime ))
|
|
|
|
|
{
|
|
|
|
|
Con_DPrintf( "Convert %s to %s\n", liblist_path, gameinfo_path );
|
|
|
|
|
FS_WriteGameInfo( gameinfo_path, &GameInfo );
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
return FS_WriteGameInfo( gameinfo_path, gi );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
@@ -1004,16 +1022,14 @@ static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_pa
|
|
|
|
|
FS_ReadGameInfo
|
|
|
|
|
================
|
|
|
|
|
*/
|
|
|
|
|
static qboolean FS_ReadGameInfo( const char *filepath, const char *gamedir, gameinfo_t *GameInfo )
|
|
|
|
|
static qboolean FS_ReadGameInfo( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime )
|
|
|
|
|
{
|
|
|
|
|
char *afile;
|
|
|
|
|
char *afile = FS_LoadDirectFile( filename, NULL );
|
|
|
|
|
|
|
|
|
|
afile = (char *)FS_LoadFile( filepath, NULL, false );
|
|
|
|
|
if( !afile )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
FS_InitGameInfo( GameInfo, gamedir );
|
|
|
|
|
|
|
|
|
|
FS_InitGameInfo( GameInfo, gamedir, false, mtime );
|
|
|
|
|
FS_ParseGenericGameInfo( GameInfo, afile, true );
|
|
|
|
|
|
|
|
|
|
Mem_Free( afile );
|
|
|
|
|
@@ -1029,20 +1045,32 @@ Checks if game directory resembles Quake Engine game directory
|
|
|
|
|
(some of checks may as well work with Xash gamedirs, it's not a bug)
|
|
|
|
|
================
|
|
|
|
|
*/
|
|
|
|
|
static qboolean FS_CheckForQuakeGameDir( const char *gamedir, qboolean direct )
|
|
|
|
|
static qboolean FS_CheckForQuakeGameDir( const char *gamedir )
|
|
|
|
|
{
|
|
|
|
|
// if directory contain config.cfg or progs.dat it's 100% gamedir
|
|
|
|
|
// quake mods probably always archived but can missed config.cfg before first running
|
|
|
|
|
const char *files[] = { "config.cfg", "progs.dat", "pak0.pak" };
|
|
|
|
|
// if directory contain quake.rc or progs.dat it's 100% quake gamedir
|
|
|
|
|
// quake mods probably always archived, so check pak0.pak too
|
|
|
|
|
const char *files[] = { "progs.dat", "quake.rc" };
|
|
|
|
|
char buf[MAX_SYSPATH];
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
// try to read pak0.pak first, most quake mods are archived
|
|
|
|
|
if( Q_snprintf( buf, sizeof( buf ), "%s/pak0.pak", gamedir ) > 0 )
|
|
|
|
|
{
|
|
|
|
|
if( FS_SysFileExists( buf ))
|
|
|
|
|
{
|
|
|
|
|
if( FS_CheckForQuakePak( buf, files, sizeof( files ) / sizeof( files[0] )))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// search it in the filesystem
|
|
|
|
|
for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ )
|
|
|
|
|
{
|
|
|
|
|
char buf[MAX_VA_STRING];
|
|
|
|
|
|
|
|
|
|
Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] );
|
|
|
|
|
if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false ))
|
|
|
|
|
return true;
|
|
|
|
|
if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 )
|
|
|
|
|
{
|
|
|
|
|
if( FS_SysFileExists( buf ))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
@@ -1055,7 +1083,7 @@ FS_CheckForXashGameDir
|
|
|
|
|
Checks if game directory resembles Xash3D game directory
|
|
|
|
|
===============
|
|
|
|
|
*/
|
|
|
|
|
static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct )
|
|
|
|
|
static qboolean FS_CheckForXashGameDir( const char *gamedir )
|
|
|
|
|
{
|
|
|
|
|
// if directory contain gameinfo.txt or liblist.gam it's 100% gamedir
|
|
|
|
|
const char *files[] = { "gameinfo.txt", "liblist.gam" };
|
|
|
|
|
@@ -1063,15 +1091,16 @@ static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct )
|
|
|
|
|
|
|
|
|
|
for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ )
|
|
|
|
|
{
|
|
|
|
|
char buf[MAX_SYSPATH];
|
|
|
|
|
char buf[MAX_SYSPATH];
|
|
|
|
|
|
|
|
|
|
Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] );
|
|
|
|
|
if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false ))
|
|
|
|
|
return true;
|
|
|
|
|
if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 )
|
|
|
|
|
{
|
|
|
|
|
if( FS_SysFileExists( buf ))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
@@ -1079,86 +1108,74 @@ static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct )
|
|
|
|
|
FS_ParseGameInfo
|
|
|
|
|
================
|
|
|
|
|
*/
|
|
|
|
|
static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo )
|
|
|
|
|
static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo, qboolean rodir )
|
|
|
|
|
{
|
|
|
|
|
string liblist_path, gameinfo_path;
|
|
|
|
|
string default_gameinfo_path;
|
|
|
|
|
qboolean haveUpdate = false;
|
|
|
|
|
char liblist_path[MAX_SYSPATH];
|
|
|
|
|
char gameinfo_path[MAX_SYSPATH];
|
|
|
|
|
char gamedir_path[MAX_SYSPATH];
|
|
|
|
|
time_t liblist_mtime = -1;
|
|
|
|
|
time_t gameinfo_mtime = -1;
|
|
|
|
|
|
|
|
|
|
Q_snprintf( default_gameinfo_path, sizeof( default_gameinfo_path ), "%s/gameinfo.txt", fs_basedir );
|
|
|
|
|
Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir );
|
|
|
|
|
Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir );
|
|
|
|
|
if( rodir )
|
|
|
|
|
Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s/%s", fs_rodir, gamedir );
|
|
|
|
|
else
|
|
|
|
|
Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s/%s", fs_rootdir, gamedir );
|
|
|
|
|
|
|
|
|
|
// here goes some RoDir magic...
|
|
|
|
|
if( COM_CheckStringEmpty( fs_rodir ))
|
|
|
|
|
if( !FS_CheckForXashGameDir( gamedir_path ))
|
|
|
|
|
{
|
|
|
|
|
string gameinfo_ro, liblist_ro;
|
|
|
|
|
fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime;
|
|
|
|
|
|
|
|
|
|
FS_AllowDirectPaths( true );
|
|
|
|
|
|
|
|
|
|
Q_snprintf( gameinfo_ro, sizeof( gameinfo_ro ), "%s/%s/gameinfo.txt", fs_rodir, gamedir );
|
|
|
|
|
Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", fs_rodir, gamedir );
|
|
|
|
|
|
|
|
|
|
roLibListTime = FS_SysFileTime( liblist_ro );
|
|
|
|
|
roGameInfoTime = FS_SysFileTime( gameinfo_ro );
|
|
|
|
|
rwGameInfoTime = FS_SysFileTime( gameinfo_path );
|
|
|
|
|
|
|
|
|
|
// if rodir's liblist.gam newer than rwdir's gameinfo.txt, then convert it
|
|
|
|
|
if( roLibListTime > rwGameInfoTime )
|
|
|
|
|
// check if we need to generate gameinfo for Quake
|
|
|
|
|
if( FS_CheckForQuakeGameDir( gamedir_path ))
|
|
|
|
|
{
|
|
|
|
|
haveUpdate = FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_ro );
|
|
|
|
|
}
|
|
|
|
|
// if rodir's gameinfo.txt newer than rwdir's gameinfo.txt, just copy the file
|
|
|
|
|
else if( roGameInfoTime > rwGameInfoTime )
|
|
|
|
|
{
|
|
|
|
|
file_t *ro, *rw;
|
|
|
|
|
fs_offset_t ro_size;
|
|
|
|
|
|
|
|
|
|
// read & write as binary to copy the exact file
|
|
|
|
|
ro = FS_SysOpen( gameinfo_ro, "rb" );
|
|
|
|
|
rw = FS_SysOpen( gameinfo_path, "wb" );
|
|
|
|
|
|
|
|
|
|
FS_Seek( ro, 0, SEEK_END );
|
|
|
|
|
ro_size = FS_Tell( ro );
|
|
|
|
|
FS_Seek( ro, 0, SEEK_SET );
|
|
|
|
|
|
|
|
|
|
FS_FileCopy( rw, ro, ro_size );
|
|
|
|
|
|
|
|
|
|
FS_Close( rw );
|
|
|
|
|
FS_Close( ro );
|
|
|
|
|
|
|
|
|
|
haveUpdate = true;
|
|
|
|
|
// just generate stub gameinfo in memory
|
|
|
|
|
FS_InitGameInfo( GameInfo, gamedir, true, -1 );
|
|
|
|
|
GameInfo->rodir = rodir;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FS_AllowDirectPaths( false );
|
|
|
|
|
// don't add empty or addon directories
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// do not update gameinfo.txt, if it was just copied from rodir's
|
|
|
|
|
// if user change liblist.gam update the gameinfo.txt
|
|
|
|
|
if( !haveUpdate && FS_FileTime( liblist_path, false ) > FS_FileTime( gameinfo_path, false ))
|
|
|
|
|
FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path );
|
|
|
|
|
Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir_path );
|
|
|
|
|
Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir_path );
|
|
|
|
|
|
|
|
|
|
// force to create gameinfo for specified game if missing
|
|
|
|
|
if(( FS_CheckForQuakeGameDir( gamedir, false ) || !Q_stricmp( fs_gamedir, gamedir )) && !FS_FileExists( gameinfo_path, false ))
|
|
|
|
|
liblist_mtime = FS_SysFileTime( liblist_path );
|
|
|
|
|
gameinfo_mtime = FS_SysFileTime( gameinfo_path );
|
|
|
|
|
|
|
|
|
|
// in this function we never write new files for RoDir compatibility
|
|
|
|
|
// since RoDir is only FWGS feature, do gameinfo.txt conversion only
|
|
|
|
|
// for RwDir for those who worked with original Xash3D
|
|
|
|
|
if( !rodir )
|
|
|
|
|
{
|
|
|
|
|
gameinfo_t tmpGameInfo;
|
|
|
|
|
memset( &tmpGameInfo, 0, sizeof( tmpGameInfo ));
|
|
|
|
|
|
|
|
|
|
if( FS_ReadGameInfo( default_gameinfo_path, gamedir, &tmpGameInfo ))
|
|
|
|
|
// !!!only if we have both liblist.gam and gameinfo.txt try to convert liblist.gam to gameinfo.txt if it's newer!!!
|
|
|
|
|
if( liblist_mtime >= 0 && gameinfo_mtime >= 0 && liblist_mtime > gameinfo_mtime )
|
|
|
|
|
{
|
|
|
|
|
// now we have copy of game info from basedir but needs to change gamedir
|
|
|
|
|
Con_DPrintf( "Convert %s to %s\n", default_gameinfo_path, gameinfo_path );
|
|
|
|
|
Q_strncpy( tmpGameInfo.gamefolder, gamedir, sizeof( tmpGameInfo.gamefolder ));
|
|
|
|
|
FS_WriteGameInfo( gameinfo_path, &tmpGameInfo );
|
|
|
|
|
if( FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path, GameInfo, liblist_mtime ))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else FS_CreateDefaultGameInfo( gameinfo_path );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( !GameInfo || !FS_FileExists( gameinfo_path, false ))
|
|
|
|
|
return false; // no dest
|
|
|
|
|
// can we parse gameinfo.txt?
|
|
|
|
|
if( gameinfo_mtime >= 0 )
|
|
|
|
|
{
|
|
|
|
|
if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo, gameinfo_mtime ))
|
|
|
|
|
{
|
|
|
|
|
GameInfo->rodir = rodir;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo );
|
|
|
|
|
// can we parse liblist.gam?
|
|
|
|
|
if( liblist_mtime >= 0 )
|
|
|
|
|
{
|
|
|
|
|
if( FS_ParseLiblistGam( liblist_path, gamedir, GameInfo, liblist_mtime ))
|
|
|
|
|
{
|
|
|
|
|
GameInfo->rodir = rodir;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
@@ -1197,7 +1214,7 @@ void FS_AddGameHierarchy( const char *dir, uint flags )
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( COM_CheckStringEmpty( fs_rodir ) )
|
|
|
|
|
if( COM_CheckStringEmpty( fs_rodir ))
|
|
|
|
|
{
|
|
|
|
|
// append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH
|
|
|
|
|
uint new_flags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH));
|
|
|
|
|
@@ -1266,10 +1283,12 @@ void FS_LoadGameInfo( const char *rootfolder )
|
|
|
|
|
// lock uplevel of gamedir for read\write
|
|
|
|
|
FS_AllowDirectPaths( false );
|
|
|
|
|
|
|
|
|
|
if( rootfolder ) Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir ));
|
|
|
|
|
if( rootfolder )
|
|
|
|
|
Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir ));
|
|
|
|
|
|
|
|
|
|
Con_Reportf( "%s( %s )\n", __func__, fs_gamedir );
|
|
|
|
|
|
|
|
|
|
// clear any old pathes
|
|
|
|
|
// clear any old paths
|
|
|
|
|
FS_ClearSearchPath();
|
|
|
|
|
|
|
|
|
|
// validate gamedir
|
|
|
|
|
@@ -1284,6 +1303,14 @@ void FS_LoadGameInfo( const char *rootfolder )
|
|
|
|
|
|
|
|
|
|
FI.GameInfo = FI.games[i];
|
|
|
|
|
|
|
|
|
|
if( FI.GameInfo->rodir )
|
|
|
|
|
{
|
|
|
|
|
// ensure we have directory in rwdir
|
|
|
|
|
char buf[MAX_SYSPATH + MAX_QPATH + 2]; // and have plenty of space
|
|
|
|
|
Q_snprintf( buf, sizeof( buf ), "%s/%s/", fs_rootdir, FI.GameInfo->gamefolder );
|
|
|
|
|
FS_CreatePath( buf );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FS_Rescan(); // create new filesystem
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1344,7 +1371,7 @@ static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dll
|
|
|
|
|
{
|
|
|
|
|
string fixedname;
|
|
|
|
|
searchpath_t *search;
|
|
|
|
|
int index, start = 0, i, len;
|
|
|
|
|
int index, start = 0, len;
|
|
|
|
|
|
|
|
|
|
// check for bad exports
|
|
|
|
|
if( !COM_CheckString( dllname ))
|
|
|
|
|
@@ -1479,6 +1506,35 @@ static void *Sys_GetNativeObjectStub( const char *object )
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void FS_ValidateDirectories( const char *path, qboolean *has_base_dir, qboolean *has_game_dir )
|
|
|
|
|
{
|
|
|
|
|
stringlist_t dirs;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
stringlistinit( &dirs );
|
|
|
|
|
listdirectory( &dirs, path, true );
|
|
|
|
|
stringlistsort( &dirs );
|
|
|
|
|
|
|
|
|
|
for( i = 0; i < dirs.numstrings; i++ )
|
|
|
|
|
{
|
|
|
|
|
#if 0 // re-enable if target doesn't support filtering directories only (missing d_type in struct dirent)
|
|
|
|
|
if( !FS_SysFolderExists( dirs.strings[i] ))
|
|
|
|
|
continue;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if( !Q_stricmp( fs_basedir, dirs.strings[i] ))
|
|
|
|
|
*has_base_dir = true;
|
|
|
|
|
|
|
|
|
|
if( !Q_stricmp( fs_gamedir, dirs.strings[i] ))
|
|
|
|
|
*has_game_dir = true;
|
|
|
|
|
|
|
|
|
|
if( *has_base_dir && *has_game_dir )
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stringlistfreecontents( &dirs );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
================
|
|
|
|
|
FS_Init
|
|
|
|
|
@@ -1486,11 +1542,9 @@ FS_Init
|
|
|
|
|
*/
|
|
|
|
|
qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir )
|
|
|
|
|
{
|
|
|
|
|
stringlist_t dirs;
|
|
|
|
|
qboolean hasBaseDir = false;
|
|
|
|
|
qboolean hasGameDir = false;
|
|
|
|
|
int i;
|
|
|
|
|
char buf[MAX_VA_STRING];
|
|
|
|
|
stringlist_t dirs;
|
|
|
|
|
int i, rodir_num_games;
|
|
|
|
|
char buf[MAX_SYSPATH];
|
|
|
|
|
|
|
|
|
|
FS_InitMemory();
|
|
|
|
|
|
|
|
|
|
@@ -1503,63 +1557,40 @@ qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const c
|
|
|
|
|
Q_strncpy( fs_basedir, basedir, sizeof( fs_basedir ));
|
|
|
|
|
Q_strncpy( fs_rodir, rodir, sizeof( fs_rodir ));
|
|
|
|
|
|
|
|
|
|
// add readonly directories first
|
|
|
|
|
if( COM_CheckStringEmpty( fs_rodir ))
|
|
|
|
|
// validate user input
|
|
|
|
|
if( COM_CheckStringEmpty( fs_rodir ) && !Q_stricmp( fs_rodir, fs_rootdir ))
|
|
|
|
|
{
|
|
|
|
|
if( !Q_stricmp( fs_rodir, fs_rootdir ))
|
|
|
|
|
Sys_Error( "RoDir and default rootdir can't point to same directory!" );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// look for game directories in RwDir first
|
|
|
|
|
// this whole check only required to fallback to base directory
|
|
|
|
|
// (i.e. default game directory hardcoded in the executable file)
|
|
|
|
|
{
|
|
|
|
|
qboolean has_base_dir = false;
|
|
|
|
|
qboolean has_game_dir = false;
|
|
|
|
|
|
|
|
|
|
FS_ValidateDirectories( "./", &has_base_dir, &has_game_dir );
|
|
|
|
|
|
|
|
|
|
if( !has_game_dir )
|
|
|
|
|
{
|
|
|
|
|
Sys_Error( "RoDir and default rootdir can't point to same directory!" );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// look for game directories in RoDir now
|
|
|
|
|
if( COM_CheckStringEmpty( fs_rodir ))
|
|
|
|
|
FS_ValidateDirectories( fs_rodir, &has_base_dir, &has_game_dir );
|
|
|
|
|
|
|
|
|
|
stringlistinit( &dirs );
|
|
|
|
|
listdirectory( &dirs, fs_rodir );
|
|
|
|
|
stringlistsort( &dirs );
|
|
|
|
|
|
|
|
|
|
for( i = 0; i < dirs.numstrings; i++ )
|
|
|
|
|
{
|
|
|
|
|
char roPath[MAX_SYSPATH];
|
|
|
|
|
|
|
|
|
|
Q_snprintf( roPath, sizeof( roPath ), "%s/%s/", fs_rodir, dirs.strings[i] );
|
|
|
|
|
|
|
|
|
|
// check if it's a directory
|
|
|
|
|
if( !FS_SysFolderExists( roPath ))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// check if it's gamedir
|
|
|
|
|
if( FS_CheckForXashGameDir( roPath, true ) || FS_CheckForQuakeGameDir( roPath, true ))
|
|
|
|
|
if( !has_game_dir )
|
|
|
|
|
{
|
|
|
|
|
char rwPath[MAX_SYSPATH];
|
|
|
|
|
Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir );
|
|
|
|
|
|
|
|
|
|
Q_snprintf( rwPath, sizeof( rwPath ), "%s/%s/", fs_rootdir, dirs.strings[i] );
|
|
|
|
|
FS_CreatePath( rwPath );
|
|
|
|
|
// revert to base game directory
|
|
|
|
|
if( has_base_dir )
|
|
|
|
|
Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir ));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stringlistfreecontents( &dirs );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// validate directories
|
|
|
|
|
stringlistinit( &dirs );
|
|
|
|
|
listdirectory( &dirs, "./" );
|
|
|
|
|
stringlistsort( &dirs );
|
|
|
|
|
|
|
|
|
|
for( i = 0; i < dirs.numstrings; i++ )
|
|
|
|
|
{
|
|
|
|
|
if( !Q_stricmp( fs_basedir, dirs.strings[i] ))
|
|
|
|
|
hasBaseDir = true;
|
|
|
|
|
|
|
|
|
|
if( !Q_stricmp( fs_gamedir, dirs.strings[i] ))
|
|
|
|
|
hasGameDir = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( !hasGameDir )
|
|
|
|
|
{
|
|
|
|
|
Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir );
|
|
|
|
|
if( hasBaseDir ) Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir ));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// build list of game directories here
|
|
|
|
|
// now start building first level of directory hierarchy
|
|
|
|
|
if( COM_CheckStringEmpty( fs_rodir ))
|
|
|
|
|
{
|
|
|
|
|
Q_snprintf( buf, sizeof( buf ), "%s/", fs_rodir );
|
|
|
|
|
@@ -1567,15 +1598,66 @@ qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const c
|
|
|
|
|
}
|
|
|
|
|
FS_AddGameDirectory( "./", FS_STATIC_PATH );
|
|
|
|
|
|
|
|
|
|
// but scan rodir for games first
|
|
|
|
|
if( COM_CheckStringEmpty( fs_rodir ))
|
|
|
|
|
{
|
|
|
|
|
stringlistinit( &dirs );
|
|
|
|
|
listdirectory( &dirs, fs_rodir, true );
|
|
|
|
|
stringlistsort( &dirs );
|
|
|
|
|
|
|
|
|
|
for( i = 0; i < dirs.numstrings; i++ )
|
|
|
|
|
{
|
|
|
|
|
#if 0 // re-enable if target doesn't support filtering directories only (missing d_type in struct dirent)
|
|
|
|
|
if( !FS_SysFolderExists( dirs.strings[i] ))
|
|
|
|
|
continue;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if( FI.games[FI.numgames] == NULL )
|
|
|
|
|
FI.games[FI.numgames] = Mem_Malloc( fs_mempool, sizeof( *FI.games[FI.numgames] ));
|
|
|
|
|
|
|
|
|
|
if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], true ))
|
|
|
|
|
FI.numgames++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stringlistfreecontents( &dirs );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rodir_num_games = FI.numgames;
|
|
|
|
|
|
|
|
|
|
stringlistinit( &dirs );
|
|
|
|
|
listdirectory( &dirs, "./", true );
|
|
|
|
|
stringlistsort( &dirs );
|
|
|
|
|
|
|
|
|
|
for( i = 0; i < dirs.numstrings; i++ )
|
|
|
|
|
{
|
|
|
|
|
int j;
|
|
|
|
|
|
|
|
|
|
#if 0 // re-enable if target doesn't support filtering directories only (missing d_type in struct dirent)
|
|
|
|
|
if( !FS_SysFolderExists( dirs.strings[i] ))
|
|
|
|
|
continue;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// update gameinfo from rwdir, if it's newer
|
|
|
|
|
// with rodir we should never create new gameinfos anymore,
|
|
|
|
|
// so this only catches possible user changes
|
|
|
|
|
for( j = 0; j < rodir_num_games; j++ )
|
|
|
|
|
{
|
|
|
|
|
if( !Q_stricmp( dirs.strings[i], FI.games[j]->gamefolder ))
|
|
|
|
|
{
|
|
|
|
|
gameinfo_t gi;
|
|
|
|
|
if( FS_ParseGameInfo( dirs.strings[i], &gi, false ))
|
|
|
|
|
{
|
|
|
|
|
if( gi.mtime > FI.games[j]->mtime )
|
|
|
|
|
*FI.games[j] = gi;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( FI.games[FI.numgames] == NULL )
|
|
|
|
|
FI.games[FI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t ));
|
|
|
|
|
FI.games[FI.numgames] = Mem_Calloc( fs_mempool, sizeof( *FI.games[FI.numgames] ));
|
|
|
|
|
|
|
|
|
|
if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames] ))
|
|
|
|
|
if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], false ))
|
|
|
|
|
FI.numgames++; // added
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -3187,6 +3269,8 @@ const fs_api_t g_api =
|
|
|
|
|
FS_LoadFileFromArchive,
|
|
|
|
|
|
|
|
|
|
FS_GetRootDirectory,
|
|
|
|
|
|
|
|
|
|
FS_MakeGameInfo,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs );
|
|
|
|
|
|