2025-02-05 18:41:09 +03:00
|
|
|
/*
|
2022-07-01 19:37:21 +03:00
|
|
|
filesystem.c - game filesystem based on DP fs
|
2023-04-15 02:28:04 +03:00
|
|
|
Copyright (C) 2003-2006 Mathieu Olivier
|
|
|
|
Copyright (C) 2000-2007 DarkPlaces contributors
|
2022-07-01 19:37:21 +03:00
|
|
|
Copyright (C) 2007 Uncle Mike
|
2023-04-15 02:28:04 +03:00
|
|
|
Copyright (C) 2015-2023 Xash3D FWGS contributors
|
2022-07-01 19:37:21 +03:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2025-02-28 13:14:47 +03:00
|
|
|
#if XASH_SDL
|
|
|
|
#include <SDL.h> // SDL_GetBasePath
|
|
|
|
#endif
|
|
|
|
|
2024-06-12 08:28:22 +03:00
|
|
|
#include <errno.h>
|
2022-07-01 19:37:21 +03:00
|
|
|
#include "common.h"
|
|
|
|
#include "library.h"
|
2023-06-05 17:42:41 +03:00
|
|
|
#include "platform/platform.h"
|
2022-07-01 19:37:21 +03:00
|
|
|
|
2025-02-05 18:41:09 +03:00
|
|
|
CVAR_DEFINE_AUTO( fs_mount_hd, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "mount high definition content folder" );
|
|
|
|
CVAR_DEFINE_AUTO( fs_mount_lv, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "mount low violence models content folder" );
|
|
|
|
CVAR_DEFINE_AUTO( fs_mount_addon, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "mount addon content folder" );
|
|
|
|
CVAR_DEFINE_AUTO( fs_mount_l10n, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "mount localization content folder" );
|
|
|
|
CVAR_DEFINE_AUTO( ui_language, "english", FCVAR_ARCHIVE|FCVAR_PRIVILEGED|FCVAR_LATCH, "selected game language" );
|
|
|
|
|
2022-07-01 19:37:21 +03:00
|
|
|
fs_api_t g_fsapi;
|
|
|
|
fs_globals_t *FI;
|
|
|
|
|
2023-10-31 21:25:11 +03:00
|
|
|
static pfnCreateInterface_t fs_pfnCreateInterface;
|
2022-07-01 19:37:21 +03:00
|
|
|
static HINSTANCE fs_hInstance;
|
|
|
|
|
2024-09-30 01:10:42 +03:00
|
|
|
search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly )
|
|
|
|
{
|
|
|
|
return g_fsapi.Search( pattern, caseinsensitive, gamedironly );
|
|
|
|
}
|
|
|
|
|
|
|
|
int FS_Close( file_t *file )
|
|
|
|
{
|
|
|
|
return g_fsapi.Close( file );
|
|
|
|
}
|
|
|
|
|
|
|
|
file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly )
|
|
|
|
{
|
|
|
|
return g_fsapi.Open( filepath, mode, gamedironly );
|
|
|
|
}
|
|
|
|
|
|
|
|
byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly )
|
|
|
|
{
|
|
|
|
return g_fsapi.LoadFile( path, filesizeptr, gamedironly );
|
|
|
|
}
|
|
|
|
|
|
|
|
byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr )
|
|
|
|
{
|
|
|
|
return g_fsapi.LoadDirectFile( path, filesizeptr );
|
|
|
|
}
|
|
|
|
|
2024-06-12 08:28:22 +03:00
|
|
|
static void COM_StripDirectorySlash( char *pname )
|
|
|
|
{
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
len = Q_strlen( pname );
|
|
|
|
if( len > 0 && pname[len - 1] == '/' )
|
|
|
|
pname[len - 1] = 0;
|
|
|
|
}
|
|
|
|
|
2023-10-31 21:25:11 +03:00
|
|
|
void *FS_GetNativeObject( const char *obj )
|
|
|
|
{
|
|
|
|
if( fs_pfnCreateInterface )
|
|
|
|
return fs_pfnCreateInterface( obj, NULL );
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2025-02-05 18:22:48 +03:00
|
|
|
void FS_Rescan_f( void )
|
2022-07-01 19:37:21 +03:00
|
|
|
{
|
2025-02-05 18:41:09 +03:00
|
|
|
uint32_t flags = 0;
|
|
|
|
|
|
|
|
// FIXME: VFS shouldn't care about this, allow engine to mount gamedirs
|
|
|
|
if( fs_mount_lv.value ) SetBits( flags, FS_MOUNT_LV );
|
|
|
|
if( fs_mount_hd.value ) SetBits( flags, FS_MOUNT_HD );
|
|
|
|
if( fs_mount_addon.value ) SetBits( flags, FS_MOUNT_ADDON );
|
|
|
|
if( fs_mount_l10n.value ) SetBits( flags, FS_MOUNT_L10N );
|
|
|
|
|
|
|
|
g_fsapi.Rescan( flags, ui_language.string );
|
|
|
|
|
|
|
|
ClearBits( fs_mount_lv.flags, FCVAR_CHANGED );
|
|
|
|
ClearBits( fs_mount_hd.flags, FCVAR_CHANGED );
|
|
|
|
ClearBits( fs_mount_addon.flags, FCVAR_CHANGED );
|
|
|
|
ClearBits( fs_mount_l10n.flags, FCVAR_CHANGED );
|
|
|
|
ClearBits( ui_language.flags, FCVAR_CHANGED );
|
2022-07-01 19:37:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static void FS_ClearPaths_f( void )
|
|
|
|
{
|
|
|
|
FS_ClearSearchPath();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void FS_Path_f_( void )
|
|
|
|
{
|
|
|
|
FS_Path_f();
|
|
|
|
}
|
|
|
|
|
2025-01-07 08:50:29 +03:00
|
|
|
static void FS_MakeGameInfo_f( void )
|
|
|
|
{
|
|
|
|
g_fsapi.MakeGameInfo();
|
|
|
|
}
|
|
|
|
|
2024-07-16 15:18:14 +03:00
|
|
|
static const fs_interface_t fs_memfuncs =
|
2022-07-01 19:37:21 +03:00
|
|
|
{
|
|
|
|
Con_Printf,
|
|
|
|
Con_DPrintf,
|
|
|
|
Con_Reportf,
|
|
|
|
Sys_Error,
|
|
|
|
|
|
|
|
_Mem_AllocPool,
|
|
|
|
_Mem_FreePool,
|
|
|
|
_Mem_Alloc,
|
|
|
|
_Mem_Realloc,
|
|
|
|
_Mem_Free,
|
2023-06-05 17:42:41 +03:00
|
|
|
|
2023-10-31 21:25:11 +03:00
|
|
|
Sys_GetNativeObject,
|
2022-07-01 19:37:21 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static void FS_UnloadProgs( void )
|
|
|
|
{
|
2023-04-28 04:40:44 +03:00
|
|
|
if( fs_hInstance )
|
|
|
|
{
|
|
|
|
COM_FreeLibrary( fs_hInstance );
|
|
|
|
fs_hInstance = 0;
|
|
|
|
}
|
2022-07-01 19:37:21 +03:00
|
|
|
}
|
|
|
|
|
2022-08-06 20:15:18 +03:00
|
|
|
#ifdef XASH_INTERNAL_GAMELIBS
|
|
|
|
#define FILESYSTEM_STDIO_DLL "filesystem_stdio"
|
|
|
|
#else
|
|
|
|
#define FILESYSTEM_STDIO_DLL "filesystem_stdio." OS_LIB_EXT
|
|
|
|
#endif
|
|
|
|
|
2024-06-12 08:28:22 +03:00
|
|
|
static qboolean FS_LoadProgs( void )
|
2022-07-01 19:37:21 +03:00
|
|
|
{
|
2022-08-06 20:15:18 +03:00
|
|
|
const char *name = FILESYSTEM_STDIO_DLL;
|
2022-07-01 19:37:21 +03:00
|
|
|
FSAPI GetFSAPI;
|
|
|
|
|
|
|
|
fs_hInstance = COM_LoadLibrary( name, false, true );
|
|
|
|
|
|
|
|
if( !fs_hInstance )
|
|
|
|
{
|
2024-06-12 08:28:22 +03:00
|
|
|
Sys_Error( "%s: can't load filesystem library %s: %s\n", __func__, name, COM_GetLibraryError() );
|
2022-07-01 19:37:21 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !( GetFSAPI = (FSAPI)COM_GetProcAddress( fs_hInstance, GET_FS_API )))
|
|
|
|
{
|
|
|
|
FS_UnloadProgs();
|
2024-06-12 08:28:22 +03:00
|
|
|
Sys_Error( "%s: can't find GetFSAPI entry point in %s\n", __func__, name );
|
2022-07-01 19:37:21 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-04-22 03:39:21 +03:00
|
|
|
if( GetFSAPI( FS_API_VERSION, &g_fsapi, &FI, &fs_memfuncs ) != FS_API_VERSION )
|
2022-07-01 19:37:21 +03:00
|
|
|
{
|
|
|
|
FS_UnloadProgs();
|
2024-06-12 08:28:22 +03:00
|
|
|
Sys_Error( "%s: can't initialize filesystem API: wrong version\n", __func__ );
|
2022-07-01 19:37:21 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-10-31 21:25:11 +03:00
|
|
|
if( !( fs_pfnCreateInterface = (pfnCreateInterface_t)COM_GetProcAddress( fs_hInstance, "CreateInterface" )))
|
|
|
|
{
|
|
|
|
FS_UnloadProgs();
|
2024-06-12 08:28:22 +03:00
|
|
|
Sys_Error( "%s: can't find CreateInterface entry point in %s\n", __func__, name );
|
2023-10-31 21:25:11 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-04-22 03:39:21 +03:00
|
|
|
Con_DPrintf( "%s: filesystem_stdio successfully loaded\n", __func__ );
|
2024-06-12 08:28:22 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static qboolean FS_DetermineRootDirectory( char *out, size_t size )
|
|
|
|
{
|
|
|
|
const char *path = getenv( "XASH3D_BASEDIR" );
|
|
|
|
|
|
|
|
if( COM_CheckString( path ))
|
|
|
|
{
|
|
|
|
Q_strncpy( out, path, size );
|
|
|
|
return true;
|
|
|
|
}
|
2022-07-01 19:37:21 +03:00
|
|
|
|
2024-06-12 08:28:22 +03:00
|
|
|
#if TARGET_OS_IOS
|
|
|
|
Q_strncpy( out, IOS_GetDocsDir(), size );
|
2022-07-01 19:37:21 +03:00
|
|
|
return true;
|
2024-06-12 08:28:22 +03:00
|
|
|
#elif XASH_ANDROID && XASH_SDL
|
|
|
|
path = SDL_AndroidGetExternalStoragePath();
|
|
|
|
if( path != NULL )
|
|
|
|
{
|
|
|
|
Q_strncpy( out, path, size );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
Sys_Error( "couldn't determine Android external storage path: %s", SDL_GetError( ));
|
|
|
|
return false;
|
|
|
|
#elif XASH_PSVITA
|
|
|
|
if( PSVita_GetBasePath( out, size ))
|
|
|
|
return true;
|
2025-01-23 10:33:22 +03:00
|
|
|
Sys_Error( "couldn't find %s data directory", XASH_ENGINE_NAME );
|
2024-06-12 08:28:22 +03:00
|
|
|
return false;
|
|
|
|
#elif ( XASH_SDL == 2 ) && !XASH_NSWITCH // GetBasePath not impl'd in switch-sdl2
|
|
|
|
path = SDL_GetBasePath();
|
2025-01-23 22:57:11 +03:00
|
|
|
|
|
|
|
#if XASH_APPLE
|
|
|
|
if( path != NULL && Q_stristr( path, ".app" ))
|
|
|
|
{
|
|
|
|
SDL_free((void *)path );
|
|
|
|
path = SDL_GetPrefPath( NULL, XASH_ENGINE_NAME );
|
|
|
|
}
|
2025-01-19 22:00:45 -06:00
|
|
|
#endif
|
2025-01-23 22:57:11 +03:00
|
|
|
|
2024-06-12 08:28:22 +03:00
|
|
|
if( path != NULL )
|
|
|
|
{
|
|
|
|
Q_strncpy( out, path, size );
|
2025-01-23 22:57:11 +03:00
|
|
|
SDL_free((void *)path );
|
2024-06-12 08:28:22 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if XASH_POSIX || XASH_WIN32
|
|
|
|
if( getcwd( out, size ))
|
|
|
|
return true;
|
|
|
|
Sys_Error( "couldn't determine current directory: %s, getcwd: %s", SDL_GetError(), strerror( errno ));
|
|
|
|
#else // !( XASH_POSIX || XASH_WIN32 )
|
|
|
|
Sys_Error( "couldn't determine current directory: %s", SDL_GetError( ));
|
|
|
|
#endif // !( XASH_POSIX || XASH_WIN32 )
|
|
|
|
return false;
|
|
|
|
#else // generic case
|
|
|
|
if( getcwd( out, size ))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
Sys_Error( "couldn't determine current directory: %s", strerror( errno ));
|
|
|
|
return false;
|
|
|
|
#endif // generic case
|
|
|
|
}
|
|
|
|
|
|
|
|
static qboolean FS_DetermineReadOnlyRootDirectory( char *out, size_t size )
|
|
|
|
{
|
|
|
|
const char *env_rodir = getenv( "XASH3D_RODIR" );
|
|
|
|
|
|
|
|
if( _Sys_GetParmFromCmdLine( "-rodir", out, size ))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if( COM_CheckString( env_rodir ))
|
|
|
|
{
|
|
|
|
Q_strncpy( out, env_rodir, size );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2022-07-01 19:37:21 +03:00
|
|
|
}
|
|
|
|
|
2025-02-05 18:41:09 +03:00
|
|
|
void FS_CheckConfig( void )
|
|
|
|
{
|
2025-02-10 12:47:39 +03:00
|
|
|
if( fs_mount_lv.value || fs_mount_hd.value || fs_mount_addon.value || fs_mount_l10n.value )
|
2025-02-05 18:41:09 +03:00
|
|
|
FS_Rescan_f();
|
|
|
|
}
|
|
|
|
|
2022-07-01 19:37:21 +03:00
|
|
|
/*
|
|
|
|
================
|
|
|
|
FS_Init
|
|
|
|
================
|
|
|
|
*/
|
2024-06-12 09:50:28 +03:00
|
|
|
void FS_Init( const char *basedir )
|
2022-07-01 19:37:21 +03:00
|
|
|
{
|
|
|
|
string gamedir;
|
2024-06-12 08:28:22 +03:00
|
|
|
char rodir[MAX_OSPATH], rootdir[MAX_OSPATH];
|
|
|
|
rodir[0] = rootdir[0] = 0;
|
2022-07-01 19:37:21 +03:00
|
|
|
|
2024-06-12 08:28:22 +03:00
|
|
|
if( !FS_DetermineRootDirectory( rootdir, sizeof( rootdir )) || !COM_CheckStringEmpty( rootdir ))
|
|
|
|
{
|
|
|
|
Sys_Error( "couldn't determine current directory (empty string)" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
COM_FixSlashes( rootdir );
|
|
|
|
COM_StripDirectorySlash( rootdir );
|
|
|
|
|
|
|
|
FS_DetermineReadOnlyRootDirectory( rodir, sizeof( rodir ));
|
|
|
|
COM_FixSlashes( rodir );
|
|
|
|
COM_StripDirectorySlash( rodir );
|
2022-07-01 19:37:21 +03:00
|
|
|
|
|
|
|
if( !Sys_GetParmFromCmdLine( "-game", gamedir ))
|
2024-07-21 01:01:33 +03:00
|
|
|
{
|
|
|
|
char *env = getenv( "XASH3D_GAME" );
|
|
|
|
if( env )
|
|
|
|
Q_strncpy( gamedir, env, sizeof( gamedir ));
|
|
|
|
else
|
|
|
|
Q_strncpy( gamedir, basedir, sizeof( gamedir )); // gamedir == basedir
|
|
|
|
}
|
2022-07-01 19:37:21 +03:00
|
|
|
|
2024-06-12 08:28:22 +03:00
|
|
|
FS_LoadProgs();
|
2024-07-09 08:45:27 +03:00
|
|
|
|
|
|
|
// TODO: this function will cause engine to stop in case of fail
|
|
|
|
// when it will have an option to return string error, restore Sys_Error
|
|
|
|
// FIXME: why do we call this function before InitStdio?
|
|
|
|
// because InitStdio immediately scans all available game directories
|
|
|
|
// and this better be reworked at some point
|
|
|
|
g_fsapi.SetCurrentDirectory( rootdir );
|
|
|
|
|
2024-06-12 09:50:28 +03:00
|
|
|
if( !g_fsapi.InitStdio( true, rootdir, basedir, gamedir, rodir ))
|
2022-07-01 19:37:21 +03:00
|
|
|
{
|
2024-06-12 08:28:22 +03:00
|
|
|
Sys_Error( "Can't init filesystem_stdio!\n" );
|
2022-07-01 19:37:21 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-06-12 08:28:22 +03:00
|
|
|
Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" );
|
|
|
|
Cmd_AddRestrictedCommand( "fs_path", FS_Path_f_, "show filesystem search pathes" );
|
|
|
|
Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" );
|
2025-01-07 08:50:29 +03:00
|
|
|
Cmd_AddRestrictedCommand( "fs_make_gameinfo", FS_MakeGameInfo_f, "create gameinfo.txt for current running game" );
|
2024-06-12 08:28:22 +03:00
|
|
|
|
2025-02-05 18:41:09 +03:00
|
|
|
Cvar_RegisterVariable( &fs_mount_hd );
|
|
|
|
Cvar_RegisterVariable( &fs_mount_lv );
|
|
|
|
Cvar_RegisterVariable( &fs_mount_addon );
|
|
|
|
Cvar_RegisterVariable( &fs_mount_l10n );
|
|
|
|
|
2024-06-12 09:50:28 +03:00
|
|
|
if( !Sys_GetParmFromCmdLine( "-dll", host.gamedll ))
|
|
|
|
host.gamedll[0] = 0;
|
2022-07-01 19:37:21 +03:00
|
|
|
|
2024-06-12 09:50:28 +03:00
|
|
|
if( !Sys_GetParmFromCmdLine( "-clientlib", host.clientlib ))
|
|
|
|
host.clientlib[0] = 0;
|
2022-07-01 19:37:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
FS_Shutdown
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void FS_Shutdown( void )
|
|
|
|
{
|
2023-04-28 04:40:44 +03:00
|
|
|
if( g_fsapi.ShutdownStdio )
|
|
|
|
g_fsapi.ShutdownStdio();
|
2022-07-01 19:37:21 +03:00
|
|
|
|
|
|
|
FS_UnloadProgs();
|
|
|
|
}
|