diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index bae521d0..e5bf2683 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -980,7 +980,7 @@ pfnGetGamesList */ static GAMEINFO ** GAME_EXPORT pfnGetGamesList( int *numGames ) { - if( numGames ) *numGames = SI.numgames; + if( numGames ) *numGames = FI->numgames; return gameui.modsInfo; } @@ -1117,6 +1117,30 @@ static char *pfnParseFile( char *buf, char *token ) return COM_ParseFile( buf, token, INT_MAX ); } +/* +============= +pfnFileExists + +legacy wrapper +============= +*/ +static int pfnFileExists( const char *path, int gamedironly ) +{ + return FS_FileExists( path, gamedironly ); +} + +/* +============= +pfnDelete + +legacy wrapper +============= +*/ +static int pfnDelete( const char *path ) +{ + return FS_Delete( path ); +} + // engine callbacks static ui_enginefuncs_t gEngfuncs = { @@ -1163,7 +1187,7 @@ static ui_enginefuncs_t gEngfuncs = pfnRenderScene, pfnAddEntity, Host_Error, - FS_FileExists, + pfnFileExists, pfnGetGameDir, Cmd_CheckMapsList, CL_Active, @@ -1202,7 +1226,7 @@ static ui_enginefuncs_t gEngfuncs = COM_CompareFileTime, VID_GetModeString, (void*)COM_SaveFile, - (void*)FS_Delete + pfnDelete }; static void pfnEnableTextInput( int enable ) @@ -1358,13 +1382,13 @@ qboolean UI_LoadProgs( void ) Cvar_FullSet( "host_gameuiloaded", "1", FCVAR_READ_ONLY ); // setup gameinfo - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI->numgames; i++ ) { gameui.modsInfo[i] = Mem_Calloc( gameui.mempool, sizeof( GAMEINFO )); - UI_ConvertGameInfo( gameui.modsInfo[i], SI.games[i] ); + UI_ConvertGameInfo( gameui.modsInfo[i], FI->games[i] ); } - UI_ConvertGameInfo( &gameui.gameInfo, SI.GameInfo ); // current gameinfo + UI_ConvertGameInfo( &gameui.gameInfo, FI->GameInfo ); // current gameinfo // setup globals gameui.globals->developer = host.allow_console; diff --git a/engine/client/ref_common.c b/engine/client/ref_common.c index 0760d904..57388705 100644 --- a/engine/client/ref_common.c +++ b/engine/client/ref_common.c @@ -334,10 +334,6 @@ static ref_api_t gEngfuncs = COM_FreeLibrary, COM_GetProcAddress, - FS_LoadFile, - FS_FileExists, - FS_AllowDirectPaths, - R_Init_Video_, R_Free_Video, @@ -383,7 +379,9 @@ static ref_api_t gEngfuncs = pfnDrawNormalTriangles, pfnDrawTransparentTriangles, - &clgame.drawFuncs + &clgame.drawFuncs, + + &g_fsapi, }; static void R_UnloadProgs( void ) diff --git a/engine/common/common.c b/engine/common/common.c index 2e00df6a..684718dd 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -585,23 +585,6 @@ void COM_TrimSpace( const char *source, char *dest ) dest[length] = 0; } -/* -============ -COM_FixSlashes - -Changes all '/' characters into '\' characters, in place. -============ -*/ -void COM_FixSlashes( char *pname ) -{ - while( *pname ) - { - if( *pname == '\\' ) - *pname = '/'; - pname++; - } -} - /* ================== COM_Nibble diff --git a/engine/common/common.h b/engine/common/common.h index 8b112178..58741bf3 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -76,13 +76,6 @@ XASH SPECIFIC - sort of hack that works only in Xash3D not in GoldSrc #define HACKS_RELATED_HLMODS // some HL-mods works differently under Xash and can't be fixed without some hacks at least at current time -typedef struct -{ - int numfilenames; - char **filenames; - char *filenamesbuffer; -} search_t; - enum { DEV_NONE = 0, @@ -118,6 +111,8 @@ typedef enum #include "cvar.h" #include "con_nprint.h" #include "crclib.h" +#include "ref_api.h" +#include "fscallback.h" // PERFORMANCE INFO #define MIN_FPS 20.0f // host minimum fps value for maxfps. @@ -147,18 +142,6 @@ typedef enum #define MAX_STATIC_ENTITIES 32 // static entities that moved on the client when level is spawn #endif -// filesystem flags -#define FS_STATIC_PATH ( 1U << 0 ) // FS_ClearSearchPath will be ignore this path -#define FS_NOWRITE_PATH ( 1U << 1 ) // default behavior - last added gamedir set as writedir. This flag disables it -#define FS_GAMEDIR_PATH ( 1U << 2 ) // just a marker for gamedir path -#define FS_CUSTOM_PATH ( 1U << 3 ) // custom directory -#define FS_GAMERODIR_PATH ( 1U << 4 ) // caseinsensitive - -#define FS_GAMEDIRONLY_SEARCH_FLAGS ( FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH ) - -#define GI SI.GameInfo -#define FS_Gamedir() SI.GameInfo->gamefolder -#define FS_Title() SI.GameInfo->title #define GameState (&host.game) #define FORCE_DRAW_VERSION_TIME 5.0f // draw version for 5 seconds @@ -199,61 +182,6 @@ GAMEINFO stuff internal shared gameinfo structure (readonly for engine parts) ======================================================================== */ -typedef struct gameinfo_s -{ - // filesystem info - char gamefolder[MAX_QPATH]; // used for change game '-game x' - char basedir[MAX_QPATH]; // base game directory (like 'id1' for Quake or 'valve' for Half-Life) - char falldir[MAX_QPATH]; // used as second basedir - char startmap[MAX_QPATH];// map to start singleplayer game - char trainmap[MAX_QPATH];// map to start hazard course (if specified) - char title[64]; // Game Main Title - float version; // game version (optional) - - // .dll pathes - char dll_path[MAX_QPATH]; // e.g. "bin" or "cl_dlls" - char game_dll[MAX_QPATH]; // custom path for game.dll - - // .ico path - char iconpath[MAX_QPATH]; // "game.ico" by default - - // about mod info - string game_url; // link to a developer's site - string update_url; // link to updates page - char type[MAX_QPATH]; // single, toolkit, multiplayer etc - char date[MAX_QPATH]; - size_t size; - - int gamemode; - qboolean secure; // prevent to console acess - qboolean nomodels; // don't let player to choose model (use player.mdl always) - qboolean noskills; // disable skill menu selection - qboolean render_picbutton_text; // use font renderer to render WON buttons - - char sp_entity[32]; // e.g. info_player_start - char mp_entity[32]; // e.g. info_player_deathmatch - char mp_filter[32]; // filtering multiplayer-maps - - char ambientsound[NUM_AMBIENTS][MAX_QPATH]; // quake ambient sounds - - int max_edicts; // min edicts is 600, max edicts is 8196 - int max_tents; // min temp ents is 300, max is 2048 - int max_beams; // min beams is 64, max beams is 512 - int max_particles; // min particles is 4096, max particles is 32768 - - char game_dll_linux[64]; // custom path for game.dll - char game_dll_osx[64]; // custom path for game.dll - - qboolean added; -} gameinfo_t; - -typedef enum -{ - GAME_NORMAL, - GAME_SINGLEPLAYER_ONLY, - GAME_MULTIPLAYER_ONLY -} gametype_t; - typedef struct sysinfo_s { string exeName; // exe.filename @@ -261,9 +189,6 @@ typedef struct sysinfo_s string basedirName; // name of base directory string gamedll; string clientlib; - gameinfo_t *GameInfo; // current GameInfo - gameinfo_t *games[MAX_MODS]; // environment games (founded at each engine start) - int numgames; } sysinfo_t; typedef enum @@ -470,6 +395,14 @@ extern sysinfo_t SI; typedef void (*xcommand_t)( void ); +// +// filesystem_engine.c +// +#define FILESYSTEM_STDIO_DLL "filesystem_stdio." OS_LIB_EXT +qboolean FS_LoadProgs( const char *name ); +void FS_Init( void ); +void FS_Shutdown( void ); + // // cmd.c // @@ -529,56 +462,6 @@ void Mem_PrintStats( void ); #define Mem_IsAllocated( mem ) Mem_IsAllocatedExt( NULL, mem ) #define Mem_Check() _Mem_Check( __FILE__, __LINE__ ) -// -// filesystem.c -// -void FS_Init( void ); -void FS_Path( void ); -void FS_Rescan( void ); -void FS_Shutdown( void ); -void FS_ClearSearchPath( void ); -void FS_AllowDirectPaths( qboolean enable ); -void FS_AddGameDirectory( const char *dir, uint flags ); -void FS_AddGameHierarchy( const char *dir, uint flags ); -void FS_LoadGameInfo( const char *rootfolder ); -const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); -byte *W_LoadLump( wfile_t *wad, const char *lumpname, size_t *lumpsizeptr, const char type ); -void W_Close( wfile_t *wad ); -byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); -qboolean CRC32_File( dword *crcvalue, const char *filename ); -qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); -byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ); -qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ); -qboolean COM_ParseVector( char **pfile, float *v, size_t size ); -void COM_NormalizeAngles( vec3_t angles ); -int COM_FileSize( const char *filename ); -void COM_FixSlashes( char *pname ); -void COM_FreeFile( void *buffer ); -int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); -search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); -file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); -fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ); -fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ); -int FS_VPrintf( file_t *file, const char *format, va_list ap ); -int FS_Seek( file_t *file, fs_offset_t offset, int whence ); -int FS_Gets( file_t *file, byte *string, size_t bufsize ); -int FS_Printf( file_t *file, const char *format, ... ) _format( 2 ); -fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ); -int FS_FileTime( const char *filename, qboolean gamedironly ); -int FS_Print( file_t *file, const char *msg ); -qboolean FS_Rename( const char *oldname, const char *newname ); -int FS_FileExists( const char *filename, int gamedironly ); -int FS_SetCurrentDirectory( const char *path ); -qboolean FS_SysFileExists( const char *path, qboolean casesensitive ); -qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); -qboolean FS_Delete( const char *path ); -int FS_UnGetc( file_t *file, byte c ); -fs_offset_t FS_Tell( file_t *file ); -qboolean FS_Eof( file_t *file ); -int FS_Close( file_t *file ); -int FS_Getc( file_t *file ); -fs_offset_t FS_FileLength( file_t *f ); - // // imagelib // @@ -942,6 +825,11 @@ void UI_SetActiveMenu( qboolean fActive ); void UI_ShowConnectionWarning( void ); void Cmd_Null_f( void ); void Rcon_Print( const char *pMsg ); +qboolean COM_ParseVector( char **pfile, float *v, size_t size ); +void COM_NormalizeAngles( vec3_t angles ); +int COM_FileSize( const char *filename ); +void COM_FreeFile( void *buffer ); +int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); // soundlib shared exports qboolean S_Init( void ); diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index b07374e4..8bafcb31 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -761,10 +761,10 @@ qboolean Cmd_GetGamesList( const char *s, char *completedname, int length ) // compare gamelist with current keyword len = Q_strlen( s ); - for( i = 0, numgamedirs = 0; i < SI.numgames; i++ ) + for( i = 0, numgamedirs = 0; i < FI->numgames; i++ ) { - if(( *s == '*' ) || !Q_strnicmp( SI.games[i]->gamefolder, s, len)) - Q_strcpy( gamedirs[numgamedirs++], SI.games[i]->gamefolder ); + if(( *s == '*' ) || !Q_strnicmp( FI->games[i]->gamefolder, s, len)) + Q_strcpy( gamedirs[numgamedirs++], FI->games[i]->gamefolder ); } if( !numgamedirs ) return false; diff --git a/engine/common/filesystem.h b/engine/common/filesystem.h deleted file mode 100644 index 3abba403..00000000 --- a/engine/common/filesystem.h +++ /dev/null @@ -1,200 +0,0 @@ -/* -filesystem.h - engine FS -Copyright (C) 2007 Uncle Mike - -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. -*/ - -#ifndef FILESYSTEM_H -#define FILESYSTEM_H - -/* -======================================================================== -PAK FILES - -The .pak files are just a linear collapse of a directory tree -======================================================================== -*/ -// header -#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" - -#define MAX_FILES_IN_PACK 65536 // pak - -typedef struct -{ - int ident; - int dirofs; - int dirlen; -} dpackheader_t; - -typedef struct -{ - char name[56]; // total 64 bytes - int filepos; - int filelen; -} dpackfile_t; - -/* -======================================================================== -.WAD archive format (WhereAllData - WAD) - -List of compressed files, that can be identify only by TYPE_* - - -header: dwadinfo_t[dwadinfo_t] -file_1: byte[dwadinfo_t[num]->disksize] -file_2: byte[dwadinfo_t[num]->disksize] -file_3: byte[dwadinfo_t[num]->disksize] -... -file_n: byte[dwadinfo_t[num]->disksize] -infotable dlumpinfo_t[dwadinfo_t->numlumps] -======================================================================== -*/ -#define WAD3_NAMELEN 16 -#define HINT_NAMELEN 5 // e.g. _mask, _norm -#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount - -#include "const.h" - -typedef struct -{ - int ident; // should be WAD3 - int numlumps; // num files - int infotableofs; // LUT offset -} dwadinfo_t; - -typedef struct -{ - int filepos; // file offset in WAD - int disksize; // compressed or uncompressed - int size; // uncompressed - signed char type; // TYP_* - signed char attribs; // file attribs - signed char pad0; - signed char pad1; - char name[WAD3_NAMELEN]; // must be null terminated -} dlumpinfo_t; - -#include "custom.h" - -/* -======================================================================== -.HPK archive format (Hash PAK - HPK) - -List of compressed files, that can be identify only by TYPE_* - - -header: dwadinfo_t[dwadinfo_t] -file_1: byte[dwadinfo_t[num]->disksize] -file_2: byte[dwadinfo_t[num]->disksize] -file_3: byte[dwadinfo_t[num]->disksize] -... -file_n: byte[dwadinfo_t[num]->disksize] -infotable dlumpinfo_t[dwadinfo_t->numlumps] -======================================================================== -*/ - -#define IDHPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian "HPAK" -#define IDHPAK_VERSION 1 - -typedef struct -{ - int ident; // should be equal HPAK - int version; - int infotableofs; -} hpak_header_t; - -typedef struct -{ - resource_t resource; - int filepos; - int disksize; -} hpak_lump_t; - -typedef struct -{ - int count; - hpak_lump_t *entries; // variable sized. -} hpak_info_t; - -#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) -#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P') - -#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P') -#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P') - -#define ZIP_COMPRESSION_NO_COMPRESSION 0 -#define ZIP_COMPRESSION_DEFLATED 8 - -#define ZIP_ZIP64 0xffffffff - -#pragma pack( push, 1 ) -typedef struct zip_header_s -{ - unsigned int signature; // little endian ZIP_HEADER - unsigned short version; // version of pkzip need to unpack - unsigned short flags; // flags (16 bits == 16 flags) - unsigned short compression_flags; // compression flags (bits) - unsigned int dos_date; // file modification time and file modification date - unsigned int crc32; //crc32 - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; -} zip_header_t; - -/* - in zip64 comp and uncompr size == 0xffffffff remeber this - compressed and uncompress filesize stored in extra field -*/ - -typedef struct zip_header_extra_s -{ - unsigned int signature; // ZIP_HEADER_SPANNED - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; -} zip_header_extra_t; - -typedef struct zip_cdf_header_s -{ - unsigned int signature; - unsigned short version; - unsigned short version_need; - unsigned short generalPurposeBitFlag; - unsigned short flags; - unsigned short modification_time; - unsigned short modification_date; - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; - unsigned short file_commentary_len; - unsigned short disk_start; - unsigned short internal_attr; - unsigned int external_attr; - unsigned int local_header_offset; -} zip_cdf_header_t; - -typedef struct zip_header_eocd_s -{ - unsigned short disk_number; - unsigned short start_disk_number; - unsigned short number_central_directory_record; - unsigned short total_central_directory_record; - unsigned int size_of_central_directory; - unsigned int central_directory_offset; - unsigned short commentary_len; -} zip_header_eocd_t; -#pragma pack( pop ) - -#endif//FILESYSTEM_H diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c new file mode 100644 index 00000000..42754134 --- /dev/null +++ b/engine/common/filesystem_engine.c @@ -0,0 +1,146 @@ + /* +filesystem.c - game filesystem based on DP fs +Copyright (C) 2007 Uncle Mike + +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. +*/ + +#include "common.h" +#include "library.h" + +fs_api_t g_fsapi; +fs_globals_t *FI; + +static HINSTANCE fs_hInstance; + +static void FS_Rescan_f( void ) +{ + FS_Rescan(); +} + +static void FS_ClearPaths_f( void ) +{ + FS_ClearSearchPath(); +} + +static void FS_Path_f_( void ) +{ + FS_Path_f(); +} + +static fs_interface_t fs_memfuncs = +{ + Con_Printf, + Con_DPrintf, + Con_Reportf, + Sys_Error, + + _Mem_AllocPool, + _Mem_FreePool, + _Mem_Alloc, + _Mem_Realloc, + _Mem_Free, +}; + +static void FS_UnloadProgs( void ) +{ + COM_FreeLibrary( fs_hInstance ); + fs_hInstance = 0; +} + +qboolean FS_LoadProgs( const char *name ) +{ + FSAPI GetFSAPI; + + fs_hInstance = COM_LoadLibrary( name, false, true ); + + if( !fs_hInstance ) + { + Host_Error( "FS_LoadProgs: can't load filesystem library %s: %s\n", name, COM_GetLibraryError() ); + return false; + } + + if( !( GetFSAPI = (FSAPI)COM_GetProcAddress( fs_hInstance, GET_FS_API ))) + { + FS_UnloadProgs(); + Host_Error( "FS_LoadProgs: can't find GetFSAPI entry point in %s\n", name ); + return false; + } + + if( !GetFSAPI( FS_API_VERSION, &g_fsapi, &FI, &fs_memfuncs )) + { + FS_UnloadProgs(); + Host_Error( "FS_LoadProgs: can't initialize filesystem API: wrong version\n" ); + return false; + } + + Con_DPrintf( "FS_LoadProgs: filesystem_stdio successfully loaded\n" ); + + return true; +} + +/* +================ +FS_Init +================ +*/ +void FS_Init( void ) +{ + qboolean hasBaseDir = false; + qboolean hasGameDir = false; + qboolean caseinsensitive = true; + int i; + string gamedir; + + 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" ); + +#if !XASH_WIN32 + if( Sys_CheckParm( "-casesensitive" ) ) + caseinsensitive = false; +#endif + + if( !Sys_GetParmFromCmdLine( "-game", gamedir )) + Q_strncpy( gamedir, SI.basedirName, sizeof( gamedir )); // gamedir == basedir + + if( !FS_InitStdio( caseinsensitive, host.rootdir, SI.basedirName, gamedir, host.rodir )) + { + Host_Error( "Can't init filesystem_stdio!\n" ); + return; + } + + if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll )) + SI.gamedll[0] = 0; + + if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib )) + SI.clientlib[0] = 0; +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown( void ) +{ + int i; + + FS_ShutdownStdio(); + + memset( &SI, 0, sizeof( sysinfo_t )); + + FS_UnloadProgs(); +} + + + + diff --git a/engine/common/host.c b/engine/common/host.c index fee751ce..3f89d176 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -328,13 +328,13 @@ void Host_ChangeGame_f( void ) } // validate gamedir - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI->numgames; i++ ) { - if( !Q_stricmp( SI.games[i]->gamefolder, Cmd_Argv( 1 ))) + if( !Q_stricmp( FI->games[i]->gamefolder, Cmd_Argv( 1 ))) break; } - if( i == SI.numgames ) + if( i == FI->numgames ) { Con_Printf( "%s not exist\n", Cmd_Argv( 1 )); } @@ -345,7 +345,7 @@ void Host_ChangeGame_f( void ) else { const char *arg1 = va( "%s%s", (host.type == HOST_NORMAL) ? "" : "#", Cmd_Argv( 1 )); - const char *arg2 = va( "change game to '%s'", SI.games[i]->title ); + const char *arg2 = va( "change game to '%s'", FI->games[i]->title ); Host_NewInstance( arg1, arg2 ); } @@ -1027,18 +1027,27 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha if( len && host.rodir[len - 1] == '/' ) host.rodir[len - 1] = 0; - if( !COM_CheckStringEmpty( host.rootdir ) || FS_SetCurrentDirectory( host.rootdir ) != 0 ) + if( !COM_CheckStringEmpty( host.rootdir )) + { + Sys_Error( "Changing working directory failed (empty working directory)\n" ); + return; + } + + FS_LoadProgs( FILESYSTEM_STDIO_DLL ); + + if( FS_SetCurrentDirectory( host.rootdir ) != 0 ) Con_Reportf( "%s is working directory now\n", host.rootdir ); else Sys_Error( "Changing working directory to %s failed.\n", host.rootdir ); + FS_Init(); + Sys_InitLog(); Cmd_AddCommand( "exec", Host_Exec_f, "execute a script file" ); Cmd_AddCommand( "memlist", Host_MemStats_f, "prints memory pool information" ); Cmd_AddRestrictedCommand( "userconfigd", Host_Userconfigd_f, "execute all scripts from userconfig.d" ); - FS_Init(); Image_Init(); Sound_Init(); @@ -1048,8 +1057,16 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha #endif FS_LoadGameInfo( NULL ); + + if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) + Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc + else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc + Q_strncpy( host.gamefolder, GI->gamefolder, sizeof( host.gamefolder )); + Image_CheckPaletteQ1 (); + Host_InitDecals (); // reload decals + // DEPRECATED: by FWGS fork #if 0 if( GI->secure ) diff --git a/engine/common/hpak.c b/engine/common/hpak.c index 0da8e8a7..076c3428 100644 --- a/engine/common/hpak.c +++ b/engine/common/hpak.c @@ -14,7 +14,7 @@ GNU General Public License for more details. */ #include "common.h" -#include "filesystem.h" +#include "hpak.h" #define HPAK_MAX_ENTRIES 0x8000 #define HPAK_MIN_SIZE (1 * 1024) @@ -402,7 +402,7 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) FS_Seek( f, hdr.infotableofs, SEEK_SET ); FS_Read( f, &num_lumps, sizeof( num_lumps )); - if( num_lumps < 1 || num_lumps > MAX_FILES_IN_WAD ) + if( num_lumps < 1 || num_lumps > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s has too many lumps %u.\n", pakname, num_lumps ); FS_Close( f ); diff --git a/engine/common/hpak.h b/engine/common/hpak.h new file mode 100644 index 00000000..4d5c8fde --- /dev/null +++ b/engine/common/hpak.h @@ -0,0 +1,60 @@ +/* +hpak.c - custom user package to send other clients +Copyright (C) 2010 Uncle Mike + +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. +*/ +#ifndef HPAK_H +#define HPAK_H + +#include "custom.h" + +/* +======================================================================== +.HPK archive format (Hash PAK - HPK) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ + +#define IDHPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian "HPAK" +#define IDHPAK_VERSION 1 + +typedef struct +{ + int ident; // should be equal HPAK + int version; + int infotableofs; +} hpak_header_t; + +typedef struct +{ + resource_t resource; + int filepos; + int disksize; +} hpak_lump_t; + +typedef struct +{ + int count; + hpak_lump_t *entries; // variable sized. +} hpak_info_t; + +#endif // HPAK_H diff --git a/engine/common/imagelib/img_png.c b/engine/common/imagelib/img_png.c index 9569a241..2c129ba1 100644 --- a/engine/common/imagelib/img_png.c +++ b/engine/common/imagelib/img_png.c @@ -13,7 +13,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#define MINIZ_HEADER_FILE_ONLY #include "miniz.h" #include "imagelib.h" #include "xash3d_mathlib.h" diff --git a/engine/common/lib_common.c b/engine/common/lib_common.c index e18161db..5b86df77 100644 --- a/engine/common/lib_common.c +++ b/engine/common/lib_common.c @@ -86,6 +86,31 @@ const char *COM_OffsetNameForFunction( void *function ) return sname; } +dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) +{ + dll_user_t *p; + fs_dllinfo_t dllInfo; + + // no fs loaded, can't search + if( !g_fsapi.FindLibrary ) + return NULL; + + // fs can't find library + if( !g_fsapi.FindLibrary( dllname, directpath, &dllInfo )) + return NULL; + + // NOTE: for libraries we not fail even if search is NULL + // let the OS find library himself + p = Mem_Calloc( host.mempool, sizeof( dll_user_t )); + Q_strncpy( p->shortPath, dllInfo.shortPath, sizeof( p->shortPath )); + Q_strncpy( p->fullPath, dllInfo.fullPath, sizeof( p->fullPath )); + Q_strncpy( p->dllName, dllname, sizeof( p->dllName )); + p->custom_loader = dllInfo.custom_loader; + p->encrypted = dllInfo.encrypted; + + return p; +} + /* ============================================================================= diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index 21ee1fab..b6cf9cf8 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -391,7 +391,7 @@ qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname dll_user_t *hInst; qboolean ret = FALSE; - hInst = FS_FindLibrary( name, directpath ); + hInst = COM_FindLibrary( name, directpath ); if ( !hInst ) return FALSE; data = FS_LoadFile( name, NULL, false ); @@ -439,7 +439,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d COM_ResetLibraryError(); - hInst = FS_FindLibrary( dllname, directpath ); + hInst = COM_FindLibrary( dllname, directpath ); if( !hInst ) { COM_PushLibraryError( va( "Failed to find library %s", dllname ) ); diff --git a/engine/ref_api.h b/engine/ref_api.h index 3c3119d3..2f78b709 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -27,8 +27,12 @@ GNU General Public License for more details. #include "studio.h" #include "r_efx.h" #include "com_image.h" +#include "filesystem.h" -#define REF_API_VERSION 1 +// RefAPI changelog: +// 1. Initial release +// 2. FS functions are removed, instead we have full fs_api_t +#define REF_API_VERSION 2 #define TF_SKY (TF_SKYSIDE|TF_NOMIPMAP) @@ -367,13 +371,6 @@ typedef struct ref_api_s void (*COM_FreeLibrary)( void *handle ); void *(*COM_GetProcAddress)( void *handle, const char *name ); - // filesystem - byte* (*COM_LoadFile)( const char *path, fs_offset_t *pLength, qboolean gamedironly ); - // use Mem_Free instead - // void (*COM_FreeFile)( void *buffer ); - int (*FS_FileExists)( const char *filename, int gamedironly ); - void (*FS_AllowDirectPaths)( qboolean enable ); - // video init // try to create window // will call GL_SetupAttributes in case of REF_GL @@ -430,6 +427,9 @@ typedef struct ref_api_s void (*pfnDrawNormalTriangles)( void ); void (*pfnDrawTransparentTriangles)( void ); render_interface_t *drawFuncs; + + // filesystem exports + fs_api_t *fsapi; } ref_api_t; struct mip_s; diff --git a/engine/wscript b/engine/wscript index 4d2c9eed..972d8804 100644 --- a/engine/wscript +++ b/engine/wscript @@ -166,7 +166,7 @@ def build(bld): 'client/vgui/*.c', 'client/avi/*.c']) - includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../pm_shared' ] + includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../filesystem', '../pm_shared' ] if bld.env.SINGLE_BINARY: install_path = bld.env.BINDIR diff --git a/engine/common/filesystem.c b/filesystem/filesystem.c similarity index 56% rename from engine/common/filesystem.c rename to filesystem/filesystem.c index 734255e7..6f755d92 100644 --- a/engine/common/filesystem.c +++ b/filesystem/filesystem.c @@ -27,139 +27,39 @@ GNU General Public License for more details. #include #include #endif -#include "miniz.h" // header-only zlib replacement -#include "common.h" -#include "wadfile.h" +#include +#include +#include "port.h" +#include "const.h" +#include "crtlib.h" +#include "crclib.h" #include "filesystem.h" -#include "library.h" +#include "filesystem_internal.h" #include "xash3d_mathlib.h" -#include "protocol.h" +#include "common/com_strings.h" +#include "common/protocol.h" #define FILE_COPY_SIZE (1024 * 1024) -#define FILE_BUFF_SIZE (2048) - -// PAK errors -#define PAK_LOAD_OK 0 -#define PAK_LOAD_COULDNT_OPEN 1 -#define PAK_LOAD_BAD_HEADER 2 -#define PAK_LOAD_BAD_FOLDERS 3 -#define PAK_LOAD_TOO_MANY_FILES 4 -#define PAK_LOAD_NO_FILES 5 -#define PAK_LOAD_CORRUPTED 6 - -// WAD errors -#define WAD_LOAD_OK 0 -#define WAD_LOAD_COULDNT_OPEN 1 -#define WAD_LOAD_BAD_HEADER 2 -#define WAD_LOAD_BAD_FOLDERS 3 -#define WAD_LOAD_TOO_MANY_FILES 4 -#define WAD_LOAD_NO_FILES 5 -#define WAD_LOAD_CORRUPTED 6 - -// ZIP errors -#define ZIP_LOAD_OK 0 -#define ZIP_LOAD_COULDNT_OPEN 1 -#define ZIP_LOAD_BAD_HEADER 2 -#define ZIP_LOAD_BAD_FOLDERS 3 -#define ZIP_LOAD_NO_FILES 5 -#define ZIP_LOAD_CORRUPTED 6 - -typedef struct stringlist_s -{ - // maxstrings changes as needed, causing reallocation of strings[] array - int maxstrings; - int numstrings; - char **strings; -} stringlist_t; - -typedef struct wadtype_s -{ - const char *ext; - signed char type; -} wadtype_t; - -struct file_s -{ - int handle; // file descriptor - int ungetc; // single stored character from ungetc, cleared to EOF when read - fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) - fs_offset_t position; // current position in the file - fs_offset_t offset; // offset into the package (0 if external file) - time_t filetime; // pak, wad or real filetime - // contents buffer - fs_offset_t buff_ind, buff_len; // buffer current index and length - byte buff[FILE_BUFF_SIZE]; // intermediate buffer -#ifdef XASH_REDUCE_FD - const char *backup_path; - fs_offset_t backup_position; - uint backup_options; -#endif -}; - -struct wfile_s -{ - string filename; - int infotableofs; - int numlumps; - poolhandle_t mempool; // W_ReadLump temp buffers - file_t *handle; - dlumpinfo_t *lumps; - time_t filetime; -}; - -typedef struct pack_s -{ - string filename; - int handle; - int numfiles; - time_t filetime; // common for all packed files - dpackfile_t *files; -} pack_t; - -typedef struct zipfile_s -{ - char name[MAX_SYSPATH]; - fs_offset_t offset; // offset of local file header - fs_offset_t size; //original file size - fs_offset_t compressed_size; // compressed file size - unsigned short flags; -} zipfile_t; - -typedef struct zip_s -{ - string filename; - int handle; - int numfiles; - time_t filetime; - zipfile_t *files; -} zip_t; - -typedef struct searchpath_s -{ - string filename; - pack_t *pack; - wfile_t *wad; - zip_t *zip; - int flags; - struct searchpath_s *next; -} searchpath_t; +qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes +poolhandle_t fs_mempool; +searchpath_t *fs_searchpaths = NULL; // chain +char fs_rodir[MAX_SYSPATH]; +char fs_rootdir[MAX_SYSPATH]; -static poolhandle_t fs_mempool; -static searchpath_t *fs_searchpaths = NULL; // chain static searchpath_t fs_directpath; // static direct path static char fs_basedir[MAX_SYSPATH]; // base game directory static char fs_gamedir[MAX_SYSPATH]; // game current directory static char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) - -static qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes #if !XASH_WIN32 static qboolean fs_caseinsensitive = true; // try to search missing files #endif +static fs_globals_t FI; +#define GI FI.GameInfo + #ifdef XASH_REDUCE_FD static file_t *fs_last_readfile; static zip_t *fs_last_zip; -static pack_t *fs_last_pak; static void FS_EnsureOpenFile( file_t *file ) { @@ -183,21 +83,6 @@ static void FS_EnsureOpenFile( file_t *file ) } } -static void FS_EnsureOpenZip( zip_t *zip ) -{ - if( fs_last_zip == zip ) - return; - - if( fs_last_zip && (fs_last_zip->handle != -1) ) - { - close( fs_last_zip->handle ); - fs_last_zip->handle = -1; - } - fs_last_zip = zip; - if( zip && (zip->handle == -1) ) - zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); -} - static void FS_BackupFileName( file_t *file, const char *path, uint options ) { if( path == NULL ) @@ -217,21 +102,10 @@ static void FS_BackupFileName( file_t *file, const char *path, uint options ) #else static void FS_EnsureOpenFile( file_t *file ) {} -static void FS_EnsureOpenZip( zip_t *zip ) {} static void FS_BackupFileName( file_t *file, const char *path, uint options ) {} #endif static void FS_InitMemory( void ); -static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); -static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ); -static dpackfile_t *FS_AddFileToPack( const char* name, pack_t *pack, fs_offset_t offset, fs_offset_t size ); -void Zip_Close( zip_t *zip ); -static byte *W_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); -static wfile_t *W_Open( const char *filename, int *errorcode ); -static qboolean FS_SysFolderExists( const char *path ); -static int FS_SysFileTime( const char *filename ); -static signed char W_TypeFromExt( const char *lumpname ); -static const char *W_ExtFromType( signed char lumptype ); static void FS_Purge( file_t* file ); /* @@ -265,7 +139,7 @@ static void stringlistfreecontents( stringlist_t *list ) list->strings = NULL; } -static void stringlistappend( stringlist_t *list, char *text ) +void stringlistappend( stringlist_t *list, char *text ) { size_t textlen; @@ -374,7 +248,7 @@ FS_FixFileCase emulate WIN32 FS behaviour when opening local file ================== */ -static const char *FS_FixFileCase( const char *path ) +const char *FS_FixFileCase( const char *path ) { #if defined __DOS__ & !defined __WATCOM_LFN__ // not fix, but convert to 8.3 CAPS and rotate slashes @@ -428,7 +302,7 @@ static const char *FS_FixFileCase( const char *path ) } /* android has too slow directory scanning, - so drop out some not useful cases */ + so drop out some not useful cases */ if( fname - path2 > 4 ) { char *point; @@ -472,7 +346,7 @@ FS_PathToWideChar Converts input UTF-8 string to wide char string. ==================== */ -const wchar_t *FS_PathToWideChar( const char *path ) +static const wchar_t *FS_PathToWideChar( const char *path ) { static wchar_t pathBuffer[MAX_PATH]; MultiByteToWideChar( CP_UTF8, 0, path, -1, pathBuffer, MAX_PATH ); @@ -480,49 +354,6 @@ const wchar_t *FS_PathToWideChar( const char *path ) } #endif -/* -==================== -FS_AddFileToPack - -Add a file to the list of files contained into a package -==================== -*/ -static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) -{ - int left, right, middle; - dpackfile_t *pfile; - - // look for the slot we should put that file into (binary search) - left = 0; - right = pack->numfiles - 1; - - while( left <= right ) - { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( pack->files[middle].name, name ); - - // If we found the file, there's a problem - if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // We have to move the right of the list by one slot to free the one we need - pfile = &pack->files[left]; - memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); - pack->numfiles++; - - Q_strncpy( pfile->name, name, sizeof( pfile->name )); - pfile->filepos = offset; - pfile->filelen = size; - - return pfile; -} - /* ============ FS_CreatePath @@ -547,1011 +378,210 @@ void FS_CreatePath( char *path ) } } -/* -============ -FS_Path_f - -debug info -============ -*/ -void FS_Path_f( void ) -{ - searchpath_t *s; - - Con_Printf( "Current search path:\n" ); - - for( s = fs_searchpaths; s; s = s->next ) - { - if( s->pack ) Con_Printf( "%s (%i files)", s->pack->filename, s->pack->numfiles ); - else if( s->wad ) Con_Printf( "%s (%i files)", s->wad->filename, s->wad->numlumps ); - else if( s->zip ) Con_Printf( "%s (%i files)", s->zip->filename, s->zip->numfiles ); - else Con_Printf( "%s", s->filename ); - - if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); - if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); - if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); - if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); - if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); - Con_Printf( "\n" ); - } -} /* -============ -FS_ClearPath_f - -only for debug targets -============ +================ +FS_AddArchive_Fullpath +================ */ -void FS_ClearPaths_f( void ) +static qboolean FS_AddArchive_Fullpath( const char *file, qboolean *already_loaded, int flags ) { - FS_ClearSearchPath(); + const char *ext = COM_FileExtension( file ); + + if( !Q_stricmp( ext, "pk3" ) ) + return FS_AddZip_Fullpath( file, already_loaded, flags ); + else if ( !Q_stricmp( ext, "pak" )) + return FS_AddPak_Fullpath( file, already_loaded, flags ); + + // skip wads, this function only meant to be used for extras + return false; } /* -================= -FS_LoadPackPAK - -Takes an explicit (not game tree related) path to a pak file. +================ +FS_AddGameDirectory -Loads the header and directory, adding the files at the beginning -of the list so they override previous pack files. -================= +Sets fs_writedir, adds the directory to the head of the path, +then loads and adds pak1.pak pak2.pak ... +================ */ -pack_t *FS_LoadPackPAK( const char *packfile, int *error ) +void FS_AddGameDirectory( const char *dir, uint flags ) { - dpackheader_t header; - int packhandle; - int i, numpackfiles; - pack_t *pack; - dpackfile_t *info; - fs_size_t c; - - packhandle = open( packfile, O_RDONLY|O_BINARY ); - -#if !XASH_WIN32 - if( packhandle < 0 ) - { - const char *fpackfile = FS_FixFileCase( packfile ); - if( fpackfile != packfile ) - packhandle = open( fpackfile, O_RDONLY|O_BINARY ); - } -#endif - - if( packhandle < 0 ) - { - Con_Reportf( "%s couldn't open: %s\n", packfile, strerror( errno )); - if( error ) *error = PAK_LOAD_COULDNT_OPEN; - return NULL; - } + stringlist_t list; + searchpath_t *search; + string fullpath; + int i; - c = read( packhandle, (void *)&header, sizeof( header )); + if( !FBitSet( flags, FS_NOWRITE_PATH )) + Q_strncpy( fs_writedir, dir, sizeof( fs_writedir )); + stringlistinit( &list ); + listdirectory( &list, dir, false ); + stringlistsort( &list ); - if( c != sizeof( header ) || header.ident != IDPACKV1HEADER ) + // add any PAK package in the directory + for( i = 0; i < list.numstrings; i++ ) { - Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_BAD_HEADER; - close( packhandle ); - return NULL; + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pak" )) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddPak_Fullpath( fullpath, NULL, flags ); + } } - if( header.dirlen % sizeof( dpackfile_t )) + // add any Zip package in the directory + for( i = 0; i < list.numstrings; i++ ) { - Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_BAD_FOLDERS; - close( packhandle ); - return NULL; - } + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" ) ) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddZip_Fullpath( fullpath, NULL, flags ); + } + } - numpackfiles = header.dirlen / sizeof( dpackfile_t ); + FS_AllowDirectPaths( true ); - if( numpackfiles > MAX_FILES_IN_PACK ) + // add any WAD package in the directory + for( i = 0; i < list.numstrings; i++ ) { - Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); - if( error ) *error = PAK_LOAD_TOO_MANY_FILES; - close( packhandle ); - return NULL; + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "wad" )) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } } - if( numpackfiles <= 0 ) - { - Con_Reportf( "%s has no files. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_NO_FILES; - close( packhandle ); - return NULL; - } + stringlistfreecontents( &list ); + FS_AllowDirectPaths( false ); - info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); - lseek( packhandle, header.dirofs, SEEK_SET ); + // add the directory to the search path + // (unpacked files have the priority over packed files) + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + Q_strncpy( search->filename, dir, sizeof ( search->filename )); + search->next = fs_searchpaths; + search->type = SEARCHPATH_PLAIN; + search->flags = flags; + fs_searchpaths = search; +} - if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) +/* +================ +FS_ClearSearchPath +================ +*/ +void FS_ClearSearchPath( void ) +{ + while( fs_searchpaths ) { - Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); - if( error ) *error = PAK_LOAD_CORRUPTED; - close( packhandle ); - Mem_Free( info ); - return NULL; - } - - pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); - Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); - pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); - pack->filetime = FS_SysFileTime( packfile ); - pack->handle = packhandle; - pack->numfiles = 0; + searchpath_t *search = fs_searchpaths; - // parse the directory - for( i = 0; i < numpackfiles; i++ ) - FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); + if( !search ) break; -#ifdef XASH_REDUCE_FD - // will reopen when needed - close( pack->handle ); - pack->handle = -1; -#endif + if( FBitSet( search->flags, FS_STATIC_PATH )) + { + // skip read-only pathes + if( search->next ) + fs_searchpaths = search->next->next; + else break; + } + else fs_searchpaths = search->next; - if( error ) *error = PAK_LOAD_OK; - Mem_Free( info ); + switch( search->type ) + { + case SEARCHPATH_PAK: + FS_ClosePAK( search->pack ); + break; + case SEARCHPATH_WAD: + FS_CloseWAD( search->wad ); + break; + case SEARCHPATH_ZIP: + FS_CloseZIP( search->zip ); + break; + default: + break; + } - return pack; + Mem_Free( search ); + } } /* -============ -FS_SortZip -============ +==================== +FS_CheckNastyPath +Return true if the path should be rejected due to one of the following: +1: path elements that are non-portable +2: path elements that would allow access to files outside the game directory, + or are just not a good idea for a mod to be using. +==================== */ -static int FS_SortZip( const void *a, const void *b ) +int FS_CheckNastyPath (const char *path, qboolean isgamedir) { - return Q_stricmp( ( ( zipfile_t* )a )->name, ( ( zipfile_t* )b )->name ); -} + // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless + if( !COM_CheckString( path )) return 2; -/* -============ -FS_LoadZip -============ -*/ -static zip_t *FS_LoadZip( const char *zipfile, int *error ) -{ - int numpackfiles = 0, i; - zip_cdf_header_t header_cdf; - zip_header_eocd_t header_eocd; - uint32_t signature; - fs_offset_t filepos = 0, length; - zipfile_t *info = NULL; - char filename_buffer[MAX_SYSPATH]; - zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); - fs_size_t c; - - zip->handle = open( zipfile, O_RDONLY|O_BINARY ); + if( fs_ext_path ) return 0; // allow any path -#if !XASH_WIN32 - if( zip->handle < 0 ) - { - const char *fzipfile = FS_FixFileCase( zipfile ); - if( fzipfile != zipfile ) - zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); - } -#endif + // Mac: don't allow Mac-only filenames - : is a directory separator + // instead of /, but we rely on / working already, so there's no reason to + // support a Mac-only path + // Amiga and Windows: : tries to go to root of drive + if( Q_strchr( path, ':' )) return 1; // non-portable attempt to go to root of drive - if( zip->handle < 0 ) - { - Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); + // Amiga: // is parent directory + if( Q_strstr( path, "//")) return 1; // non-portable attempt to go to parent directory - if( error ) - *error = ZIP_LOAD_COULDNT_OPEN; + // all: don't allow going to parent directory (../ or /../) + if( Q_strstr( path, "..")) return 2; // attempt to go outside the game directory - Zip_Close( zip ); - return NULL; - } + // Windows and UNIXes: don't allow absolute paths + if( path[0] == '/') return 2; // attempt to go outside the game directory - length = lseek( zip->handle, 0, SEEK_END ); + // all: forbid trailing slash on gamedir + if( isgamedir && path[Q_strlen(path)-1] == '/' ) return 2; - if( length > UINT_MAX ) - { - Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); + // all: forbid leading dot on any filename for any reason + if( Q_strstr(path, "/.")) return 2; // attempt to go outside the game directory - if( error ) - *error = ZIP_LOAD_COULDNT_OPEN; + // after all these checks we're pretty sure it's a / separated filename + // and won't do much if any harm + return false; +} - Zip_Close( zip ); - return NULL; - } +/* +================ +FS_WriteGameInfo - lseek( zip->handle, 0, SEEK_SET ); +assume GameInfo is valid +================ +*/ +static void 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; - c = read( zip->handle, &signature, sizeof( signature ) ); + if( !f ) Sys_Error( "FS_WriteGameInfo: can't write %s\n", filepath ); // may be disk-space is out? - if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) - { - Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); + FS_Printf( f, "// generated by %s %s-%s (%s-%s)\n\n\n", XASH_ENGINE_NAME, XASH_VERSION, Q_buildcommit(), Q_buildos(), Q_buildarch() ); - if( error ) - *error = ZIP_LOAD_NO_FILES; + if( COM_CheckStringEmpty( GameInfo->basedir ) ) + FS_Printf( f, "basedir\t\t\"%s\"\n", GameInfo->basedir ); - Zip_Close( zip ); - return NULL; - } + // DEPRECATED: gamedir key isn't supported by FWGS fork + // but write it anyway to keep compability with original Xash3D + if( COM_CheckStringEmpty( GameInfo->gamefolder ) ) + FS_Printf( f, "gamedir\t\t\"%s\"\n", GameInfo->gamefolder ); - if( signature != ZIP_HEADER_LF ) - { - Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile ); + if( COM_CheckStringEmpty( GameInfo->falldir ) ) + FS_Printf( f, "fallback_dir\t\"%s\"\n", GameInfo->falldir ); - if( error ) - *error = ZIP_LOAD_BAD_HEADER; + if( COM_CheckStringEmpty( GameInfo->title ) ) + FS_Printf( f, "title\t\t\"%s\"\n", GameInfo->title ); - Zip_Close( zip ); - return NULL; - } + if( COM_CheckStringEmpty( GameInfo->startmap ) ) + FS_Printf( f, "startmap\t\t\"%s\"\n", GameInfo->startmap ); - // Find oecd - lseek( zip->handle, 0, SEEK_SET ); - filepos = length; - - while ( filepos > 0 ) - { - lseek( zip->handle, filepos, SEEK_SET ); - c = read( zip->handle, &signature, sizeof( signature ) ); - - if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD ) - break; - - filepos -= sizeof( char ); // step back one byte - } - - if( ZIP_HEADER_EOCD != signature ) - { - Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - c = read( zip->handle, &header_eocd, sizeof( header_eocd ) ); - - if( c != sizeof( header_eocd )) - { - Con_Reportf( S_ERROR "invalid EOCD header in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - // Move to CDF start - lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); - - // Calc count of files in archive - info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( *info ) * header_eocd.total_central_directory_record ); - - for( i = 0; i < header_eocd.total_central_directory_record; i++ ) - { - c = read( zip->handle, &header_cdf, sizeof( header_cdf ) ); - - if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF ) - { - Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) - { - memset( &filename_buffer, '\0', MAX_SYSPATH ); - c = read( zip->handle, &filename_buffer, header_cdf.filename_len ); - - if( c != header_cdf.filename_len ) - { - Con_Reportf( S_ERROR "filename length mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_CORRUPTED; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); - - info[numpackfiles].size = header_cdf.uncompressed_size; - info[numpackfiles].compressed_size = header_cdf.compressed_size; - info[numpackfiles].offset = header_cdf.local_header_offset; - numpackfiles++; - } - else - lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); - - if( header_cdf.extrafield_len ) - lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); - - if( header_cdf.file_commentary_len ) - lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); - } - - // recalculate offsets - for( i = 0; i < numpackfiles; i++ ) - { - zip_header_t header; - - lseek( zip->handle, info[i].offset, SEEK_SET ); - c = read( zip->handle, &header, sizeof( header ) ); - - if( c != sizeof( header )) - { - Con_Reportf( S_ERROR "header length mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_CORRUPTED; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - info[i].flags = header.compression_flags; - info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); - } - - Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); - zip->filetime = FS_SysFileTime( zipfile ); - zip->numfiles = numpackfiles; - zip->files = info; - - qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip ); - -#ifdef XASH_REDUCE_FD - // will reopen when needed - close(zip->handle); - zip->handle = -1; -#endif - - if( error ) - *error = ZIP_LOAD_OK; - - return zip; -} - -void Zip_Close( zip_t *zip ) -{ - if( !zip ) - return; - - if( zip->files ) - Mem_Free( zip->files ); - - FS_EnsureOpenZip( NULL ); - - if( zip->handle >= 0 ) - close( zip->handle ); - - Mem_Free( zip ); -} - -static byte *Zip_LoadFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) -{ - searchpath_t *search; - int index; - zipfile_t *file = NULL; - byte *compressed_buffer = NULL, *decompressed_buffer = NULL; - int zlib_result = 0; - dword test_crc, final_crc; - z_stream decompress_stream; - size_t c; - - if( sizeptr ) *sizeptr = 0; - - search = FS_FindFile( path, &index, gamedironly ); - - if( !search || !search->zip ) - return NULL; - - file = &search->zip->files[index]; - - FS_EnsureOpenZip( search->zip ); - - if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) - return NULL; - - /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) - return NULL; - - if( header.signature != ZIP_HEADER_LF ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); - return NULL; - }*/ - - if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) - { - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; - - c = read( search->zip->handle, decompressed_buffer, file->size ); - if( c != file->size ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s size doesn't match\n", file->name ); - return NULL; - } - -#if 0 - CRC32_Init( &test_crc ); - CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); - - final_crc = CRC32_Final( test_crc ); - - if( final_crc != file->crc32 ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); - return NULL; - } -#endif - if( sizeptr ) *sizeptr = file->size; - - FS_EnsureOpenZip( NULL ); - return decompressed_buffer; - } - else if( file->flags == ZIP_COMPRESSION_DEFLATED ) - { - compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; - - c = read( search->zip->handle, compressed_buffer, file->compressed_size ); - if( c != file->compressed_size ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s compressed size doesn't match\n", file->name ); - return NULL; - } - - memset( &decompress_stream, 0, sizeof( decompress_stream ) ); - - decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size; - decompress_stream.next_in = (Bytef *)compressed_buffer; - decompress_stream.total_out = decompress_stream.avail_out = file->size; - decompress_stream.next_out = (Bytef *)decompressed_buffer; - - decompress_stream.zalloc = Z_NULL; - decompress_stream.zfree = Z_NULL; - decompress_stream.opaque = Z_NULL; - - if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) - { - Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); - Mem_Free( compressed_buffer ); - Mem_Free( decompressed_buffer ); - return NULL; - } - - zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); - inflateEnd( &decompress_stream ); - - if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) - { - Mem_Free( compressed_buffer ); // finaly free compressed buffer -#if 0 - CRC32_Init( &test_crc ); - CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); - - final_crc = CRC32_Final( test_crc ); - - if( final_crc != file->crc32 ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); - return NULL; - } -#endif - if( sizeptr ) *sizeptr = file->size; - - FS_EnsureOpenZip( NULL ); - return decompressed_buffer; - } - else - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); - Mem_Free( compressed_buffer ); - Mem_Free( decompressed_buffer ); - return NULL; - } - - } - else - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); - return NULL; - } - - FS_EnsureOpenZip( NULL ); - return NULL; -} - -/* -==================== -FS_AddWad_Fullpath -==================== -*/ -static qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - wfile_t *wad = NULL; - const char *ext = COM_FileExtension( wadfile ); - int errorcode = WAD_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->wad && !Q_stricmp( search->wad->filename, wadfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "wad" )) - wad = W_Open( wadfile, &errorcode ); - - if( wad ) - { - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->wad = wad; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); - return true; - } - else - { - if( errorcode != WAD_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); - return false; - } -} - -/* -================ -FS_AddPak_Fullpath - -Adds the given pack to the search path. -The pack type is autodetected by the file extension. - -Returns true if the file was successfully added to the -search path or if it was already included. - -If keep_plain_dirs is set, the pack will be added AFTER the first sequence of -plain directories. -================ -*/ -static qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - pack_t *pak = NULL; - const char *ext = COM_FileExtension( pakfile ); - int i, errorcode = PAK_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->pack && !Q_stricmp( search->pack->filename, pakfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "pak" )) - pak = FS_LoadPackPAK( pakfile, &errorcode ); - - if( pak ) - { - string fullpath; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->pack = pak; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < pak->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - - return true; - } - else - { - if( errorcode != PAK_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); - return false; - } -} - -static qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - zip_t *zip = NULL; - const char *ext = COM_FileExtension( zipfile ); - int errorcode = ZIP_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->pack && !Q_stricmp( search->pack->filename, zipfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) *already_loaded = false; - - if( !Q_stricmp( ext, "pk3" ) ) - zip = FS_LoadZip( zipfile, &errorcode ); - - if( zip ) - { - string fullpath; - int i; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); - search->zip = zip; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < zip->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - return true; - } - else - { - if( errorcode != ZIP_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); - return false; - } -} - -/* -================ -FS_AddArchive_Fullpath -================ -*/ -static qboolean FS_AddArchive_Fullpath( const char *file, qboolean *already_loaded, int flags ) -{ - const char *ext = COM_FileExtension( file ); - - if( !Q_stricmp( ext, "pk3" ) ) - return FS_AddZip_Fullpath( file, already_loaded, flags ); - else if ( !Q_stricmp( ext, "pak" )) - return FS_AddPak_Fullpath( file, already_loaded, flags ); - - // skip wads, this function only meant to be used for extras - return false; -} - -/* -================ -FS_AddGameDirectory - -Sets fs_writedir, adds the directory to the head of the path, -then loads and adds pak1.pak pak2.pak ... -================ -*/ -void FS_AddGameDirectory( const char *dir, uint flags ) -{ - stringlist_t list; - searchpath_t *search; - string fullpath; - int i; - - if( !FBitSet( flags, FS_NOWRITE_PATH )) - Q_strncpy( fs_writedir, dir, sizeof( fs_writedir )); - stringlistinit( &list ); - listdirectory( &list, dir, false ); - stringlistsort( &list ); - - // add any PAK package in the directory - for( i = 0; i < list.numstrings; i++ ) - { - if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pak" )) - { - Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); - FS_AddPak_Fullpath( fullpath, NULL, flags ); - } - } - - // add any Zip package in the directory - for( i = 0; i < list.numstrings; i++ ) - { - if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" ) ) - { - Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); - FS_AddZip_Fullpath( fullpath, NULL, flags ); - } - } - - FS_AllowDirectPaths( true ); - - // add any WAD package in the directory - for( i = 0; i < list.numstrings; i++ ) - { - if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "wad" )) - { - Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - - stringlistfreecontents( &list ); - FS_AllowDirectPaths( false ); - - // add the directory to the search path - // (unpacked files have the priority over packed files) - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - Q_strncpy( search->filename, dir, sizeof ( search->filename )); - search->next = fs_searchpaths; - search->flags = flags; - fs_searchpaths = search; -} - -/* -================ -FS_AddGameHierarchy -================ -*/ -void FS_AddGameHierarchy( const char *dir, uint flags ) -{ - int i; - qboolean isGameDir = flags & FS_GAMEDIR_PATH; - - GI->added = true; - - if( !COM_CheckString( dir )) - return; - - // add the common game directory - - // recursive gamedirs - // for example, czeror->czero->cstrike->valve - for( i = 0; i < SI.numgames; i++ ) - { - if( !Q_strnicmp( SI.games[i]->gamefolder, dir, 64 )) - { - Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, SI.games[i]->gamefolder, SI.games[i]->basedir ); - if( !SI.games[i]->added && Q_stricmp( SI.games[i]->gamefolder, SI.games[i]->basedir ) ) - { - SI.games[i]->added = true; - FS_AddGameHierarchy( SI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); - } - break; - } - } - - if( COM_CheckStringEmpty( host.rodir ) ) - { - // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH - uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); - if( isGameDir ) - newFlags |= FS_GAMERODIR_PATH; - - FS_AllowDirectPaths( true ); - FS_AddGameDirectory( va( "%s/%s/", host.rodir, dir ), newFlags ); - FS_AllowDirectPaths( false ); - } - - if( isGameDir ) - FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); - FS_AddGameDirectory( va( "%s/", dir ), flags ); - if( isGameDir ) - FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); -} - -/* -================ -FS_ClearSearchPath -================ -*/ -void FS_ClearSearchPath( void ) -{ - while( fs_searchpaths ) - { - searchpath_t *search = fs_searchpaths; - - if( !search ) break; - - if( FBitSet( search->flags, FS_STATIC_PATH )) - { - // skip read-only pathes - if( search->next ) - fs_searchpaths = search->next->next; - else break; - } - else fs_searchpaths = search->next; - - if( search->pack ) - { - if( search->pack->files ) - Mem_Free( search->pack->files ); - if( search->pack->handle >= 0 ) - close( search->pack->handle ); - Mem_Free( search->pack ); - } - - if( search->wad ) - { - W_Close( search->wad ); - } - - if( search->zip ) - { - Zip_Close(search->zip); - } - - Mem_Free( search ); - } -} - -/* -==================== -FS_CheckNastyPath - -Return true if the path should be rejected due to one of the following: -1: path elements that are non-portable -2: path elements that would allow access to files outside the game directory, - or are just not a good idea for a mod to be using. -==================== -*/ -int FS_CheckNastyPath( const char *path, qboolean isgamedir ) -{ - // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless - if( !COM_CheckString( path )) return 2; - - if( fs_ext_path ) return 0; // allow any path - - // Mac: don't allow Mac-only filenames - : is a directory separator - // instead of /, but we rely on / working already, so there's no reason to - // support a Mac-only path - // Amiga and Windows: : tries to go to root of drive - if( Q_strchr( path, ':' )) return 1; // non-portable attempt to go to root of drive - - // Amiga: // is parent directory - if( Q_strstr( path, "//" )) return 1; // non-portable attempt to go to parent directory - - // all: don't allow going to parent directory (../ or /../) - if( Q_strstr( path, ".." )) return 2; // attempt to go outside the game directory - - // Windows and UNIXes: don't allow absolute paths - if( path[0] == '/' ) return 2; // attempt to go outside the game directory - - // all: forbid trailing slash on gamedir - if( isgamedir && path[Q_strlen( path )-1] == '/' ) return 2; - - // all: forbid leading dot on any filename for any reason - if( Q_strstr( path, "/." )) return 2; // attempt to go outside the game directory - - // after all these checks we're pretty sure it's a / separated filename - // and won't do much if any harm - return 0; -} - -/* -================ -FS_Rescan -================ -*/ -void FS_Rescan( void ) -{ - const char *str; - const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; - Con_Reportf( "FS_Rescan( %s )\n", GI->title ); - - FS_ClearSearchPath(); - -#if XASH_IOS - { - FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); - FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); - } -#else - str = getenv( "XASH3D_EXTRAS_PAK1" ); - if( COM_CheckString( str )) - FS_AddArchive_Fullpath( str, NULL, extrasFlags ); - - str = getenv( "XASH3D_EXTRAS_PAK2" ); - if( COM_CheckString( str )) - FS_AddArchive_Fullpath( str, NULL, extrasFlags ); -#endif - - if( Q_stricmp( GI->basedir, GI->gamefolder )) - FS_AddGameHierarchy( GI->basedir, 0 ); - if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) - FS_AddGameHierarchy( GI->falldir, 0 ); - FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); - - if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) - Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc - else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc -} - -/* -================ -FS_Rescan_f -================ -*/ -void FS_Rescan_f( void ) -{ - FS_Rescan(); -} - -/* -================ -FS_WriteGameInfo - -assume GameInfo is valid -================ -*/ -static void 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( "FS_WriteGameInfo: can't write %s\n", filepath ); // may be disk-space is out? - - FS_Printf( f, "// generated by %s %s-%s (%s-%s)\n\n\n", XASH_ENGINE_NAME, XASH_VERSION, Q_buildcommit(), Q_buildos(), Q_buildarch() ); - - if( COM_CheckStringEmpty( GameInfo->basedir ) ) - FS_Printf( f, "basedir\t\t\"%s\"\n", GameInfo->basedir ); - - // DEPRECATED: gamedir key isn't supported by FWGS fork - // but write it anyway to keep compability with original Xash3D - if( COM_CheckStringEmpty( GameInfo->gamefolder ) ) - FS_Printf( f, "gamedir\t\t\"%s\"\n", GameInfo->gamefolder ); - - if( COM_CheckStringEmpty( GameInfo->falldir ) ) - FS_Printf( f, "fallback_dir\t\"%s\"\n", GameInfo->falldir ); - - if( COM_CheckStringEmpty( GameInfo->title ) ) - FS_Printf( f, "title\t\t\"%s\"\n", GameInfo->title ); - - if( COM_CheckStringEmpty( GameInfo->startmap ) ) - FS_Printf( f, "startmap\t\t\"%s\"\n", GameInfo->startmap ); - - if( COM_CheckStringEmpty( GameInfo->trainmap ) ) - FS_Printf( f, "trainmap\t\t\"%s\"\n", GameInfo->trainmap ); + if( COM_CheckStringEmpty( GameInfo->trainmap ) ) + FS_Printf( f, "trainmap\t\t\"%s\"\n", GameInfo->trainmap ); if( GameInfo->version != 0.0f ) FS_Printf( f, "version\t\t%g\n", GameInfo->version ); @@ -1886,7 +916,7 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool } // make sure what gamedir is really exist - if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", host.rootdir, GameInfo->falldir ))) + if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", fs_rootdir, GameInfo->falldir ))) GameInfo->falldir[0] = '\0'; } @@ -2011,15 +1041,15 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir ); // here goes some RoDir magic... - if( COM_CheckStringEmpty( host.rodir ) ) + if( COM_CheckStringEmpty( fs_rodir ) ) { string filepath_ro, liblist_ro; fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime; FS_AllowDirectPaths( true ); - Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", host.rodir, gamedir ); - Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", host.rodir, gamedir ); + Q_snprintf( filepath_ro, sizeof( filepath_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( filepath_ro ); @@ -2060,76 +1090,300 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) if( FS_ReadGameInfo( default_gameinfo_path, gamedir, &tmpGameInfo )) { - // 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 ); + // 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 ); + } + else FS_CreateDefaultGameInfo( gameinfo_path ); + } + + if( !GameInfo || !FS_FileExists( gameinfo_path, false )) + return false; // no dest + + if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo )) + return true; + return false; +} + +/* +================ +FS_AddGameHierarchy +================ +*/ +void FS_AddGameHierarchy( const char *dir, uint flags ) +{ + int i; + qboolean isGameDir = flags & FS_GAMEDIR_PATH; + + GI->added = true; + + if( !COM_CheckString( dir )) + return; + + // add the common game directory + + // recursive gamedirs + // for example, czeror->czero->cstrike->valve + for( i = 0; i < FI.numgames; i++ ) + { + if( !Q_strnicmp( FI.games[i]->gamefolder, dir, 64 )) + { + Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, FI.games[i]->gamefolder, FI.games[i]->basedir ); + if( !FI.games[i]->added && Q_stricmp( FI.games[i]->gamefolder, FI.games[i]->basedir )) + { + FI.games[i]->added = true; + FS_AddGameHierarchy( FI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); + } + break; + } + } + + if( COM_CheckStringEmpty( fs_rodir ) ) + { + // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH + uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); + if( isGameDir ) + newFlags |= FS_GAMERODIR_PATH; + + FS_AllowDirectPaths( true ); + FS_AddGameDirectory( va( "%s/%s/", fs_rodir, dir ), newFlags ); + FS_AllowDirectPaths( false ); + } + + if( isGameDir ) + FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + FS_AddGameDirectory( va( "%s/", dir ), flags ); + if( isGameDir ) + FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); +} + +/* +================ +FS_Rescan +================ +*/ +void FS_Rescan( void ) +{ + const char *str; + const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; + Con_Reportf( "FS_Rescan( %s )\n", GI->title ); + + FS_ClearSearchPath(); + +#if XASH_IOS + { + FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); + FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); + } +#else + str = getenv( "XASH3D_EXTRAS_PAK1" ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); + + str = getenv( "XASH3D_EXTRAS_PAK2" ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); +#endif + + if( Q_stricmp( GI->basedir, GI->gamefolder )) + FS_AddGameHierarchy( GI->basedir, 0 ); + if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) + FS_AddGameHierarchy( GI->falldir, 0 ); + FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); +} + +/* +================ +FS_LoadGameInfo + +can be passed null arg +================ +*/ +void FS_LoadGameInfo( const char *rootfolder ) +{ + int i; + + // lock uplevel of gamedir for read\write + fs_ext_path = false; + + if( rootfolder ) Q_strcpy( fs_gamedir, rootfolder ); + Con_Reportf( "FS_LoadGameInfo( %s )\n", fs_gamedir ); + + // clear any old pathes + FS_ClearSearchPath(); + + // validate gamedir + for( i = 0; i < FI.numgames; i++ ) + { + if( !Q_stricmp( FI.games[i]->gamefolder, fs_gamedir )) + break; + } + + if( i == FI.numgames ) + Sys_Error( "Couldn't find game directory '%s'\n", fs_gamedir ); + + FI.GameInfo = FI.games[i]; + + FS_Rescan(); // create new filesystem +} + +/* +================== +FS_CheckForCrypt + +return true if library is crypted +================== +*/ +static qboolean FS_CheckForCrypt( const char *dllname ) +{ + file_t *f; + int key; + + f = FS_Open( dllname, "rb", false ); + if( !f ) return false; + + FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes + FS_Read( f, &key, sizeof( key )); + FS_Close( f ); + + return ( key == 0x12345678 ) ? true : false; +} + +/* +================== +FS_FindLibrary + +search for library, assume index is valid +================== +*/ +static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dllinfo_t *dllInfo ) +{ + searchpath_t *search; + int index, start = 0, i, len; + + fs_ext_path = directpath; + + // check for bad exports + if( !COM_CheckString( dllname )) + return false; + + // HACKHACK remove absoulte path to valve folder + if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) + start += 9; + + // replace all backward slashes + len = Q_strlen( dllname ); + + for( i = 0; i < len; i++ ) + { + if( dllname[i+start] == '\\' ) dllInfo->shortPath[i] = '/'; + else dllInfo->shortPath[i] = Q_tolower( dllname[i+start] ); + } + dllInfo->shortPath[i] = '\0'; + + COM_DefaultExtension( dllInfo->shortPath, "."OS_LIB_EXT ); // apply ext if forget + + search = FS_FindFile( dllInfo->shortPath, &index, false ); + + if( !search && !directpath ) + { + fs_ext_path = false; + + // trying check also 'bin' folder for indirect paths + Q_strncpy( dllInfo->shortPath, dllname, sizeof( dllInfo->shortPath )); + search = FS_FindFile( dllInfo->shortPath, &index, false ); + if( !search ) return false; // unable to find + } + + dllInfo->encrypted = FS_CheckForCrypt( dllInfo->shortPath ); + + if( index < 0 && !dllInfo->encrypted && search ) + { + Q_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), + "%s%s", search->filename, dllInfo->shortPath ); + dllInfo->custom_loader = false; // we can loading from disk and use normal debugging + } + else + { + // NOTE: if search is NULL let the OS found library himself + Q_strncpy( dllInfo->fullPath, dllInfo->shortPath, sizeof( dllInfo->fullPath )); + + if( search && search->type != SEARCHPATH_PLAIN ) + { +#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) + Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " + "and will be removed in the future\n", __FUNCTION__ ); + *custom_loader = true; +#else + Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " + "this platform\n", __FUNCTION__ ); + dllInfo->custom_loader = false; +#endif + } + else + { + dllInfo->custom_loader = false; } - else FS_CreateDefaultGameInfo( gameinfo_path ); } + fs_ext_path = false; // always reset direct paths - if( !GameInfo || !FS_FileExists( gameinfo_path, false )) - return false; // no dest - - if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo )) - return true; - return false; + return true; } -/* -================ -FS_LoadGameInfo - -can be passed null arg -================ -*/ -void FS_LoadGameInfo( const char *rootfolder ) +poolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline ) { - int i; - - // lock uplevel of gamedir for read\write - fs_ext_path = false; + return 0xDEADC0DE; +} - if( rootfolder ) Q_strcpy( fs_gamedir, rootfolder ); - Con_Reportf( "FS_LoadGameInfo( %s )\n", fs_gamedir ); +void _Mem_FreePool( poolhandle_t *poolptr, const char *filename, int fileline ) +{ + // stub +} - // clear any old pathes - FS_ClearSearchPath(); +void* _Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + if( clear ) return calloc( 1, size ); + return malloc( size ); +} - // validate gamedir - for( i = 0; i < SI.numgames; i++ ) - { - if( !Q_stricmp( SI.games[i]->gamefolder, fs_gamedir )) - break; - } +void* _Mem_Realloc( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + return realloc( memptr, size ); +} - if( i == SI.numgames ) - Sys_Error( "Couldn't find game directory '%s'\n", fs_gamedir ); +void _Mem_Free( void *data, const char *filename, int fileline ) +{ + free( data ); +} - SI.GameInfo = SI.games[i]; +void _Con_Printf( const char *fmt, ... ) +{ + va_list ap; - if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll ) ) - { - SI.gamedll[0] = 0; - } + va_start( ap, fmt ); + vprintf( fmt, ap ); + va_end( ap ); +} - if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib ) ) - { - SI.clientlib[0] = 0; - } +void _Sys_Error( const char *fmt, ... ) +{ + va_list ap; - FS_Rescan(); // create new filesystem + va_start( ap, fmt ); + vfprintf( stderr, fmt, ap ); + va_end( ap ); - Image_CheckPaletteQ1 (); - Host_InitDecals (); // reload decals + exit( 1 ); } + /* ================ FS_Init ================ */ -void FS_Init( void ) +qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ) { stringlist_t dirs; qboolean hasBaseDir = false; @@ -2138,61 +1392,30 @@ void FS_Init( void ) FS_InitMemory(); - 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" ); + fs_caseinsensitive = caseinsensitive; -#if !XASH_WIN32 - if( Sys_CheckParm( "-casesensitive" ) ) - fs_caseinsensitive = false; + Q_strncpy( fs_rootdir, rootdir, sizeof( fs_rootdir )); + Q_strncpy( fs_gamedir, gamedir, sizeof( fs_gamedir )); + Q_strncpy( fs_basedir, basedir, sizeof( fs_basedir )); + Q_strncpy( fs_rodir, rodir, sizeof( fs_rodir )); - if( !fs_caseinsensitive ) - { - if( COM_CheckStringEmpty( host.rodir ) && !Q_strcmp( host.rodir, host.rootdir ) ) - { - Sys_Error( "RoDir and default rootdir can't point to same directory!" ); - } - } - else -#endif + // add readonly directories first + if( COM_CheckStringEmpty( fs_rodir )) { - if( COM_CheckStringEmpty( host.rodir ) && !Q_stricmp( host.rodir, host.rootdir ) ) + if( !Q_stricmp( fs_rodir, fs_rootdir )) { Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + return false; } - } - - // ignore commandlineoption "-game" for other stuff - SI.numgames = 0; - - Q_strncpy( fs_basedir, SI.basedirName, sizeof( fs_basedir )); // default dir - - if( !Sys_GetParmFromCmdLine( "-game", fs_gamedir )) - Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // gamedir == basedir - - if( FS_CheckNastyPath( fs_basedir, true )) - { - // this is completely fatal... - Sys_Error( "invalid base directory \"%s\"\n", fs_basedir ); - } - - if( FS_CheckNastyPath( fs_gamedir, true )) - { - Con_Printf( S_ERROR "invalid game directory \"%s\"\n", fs_gamedir ); - Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // default dir - } - // add readonly directories first - if( COM_CheckStringEmpty( host.rodir ) ) - { stringlistinit( &dirs ); - listdirectory( &dirs, host.rodir, false ); + listdirectory( &dirs, fs_rodir, false ); stringlistsort( &dirs ); for( i = 0; i < dirs.numstrings; i++ ) { - char *roPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, host.rodir, dirs.strings[i] ); - char *rwPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, host.rootdir, dirs.strings[i] ); + char *roPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rodir, dirs.strings[i] ); + char *rwPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rootdir, dirs.strings[i] ); // check if it's a directory if( !FS_SysFolderExists( roPath )) @@ -2233,15 +1456,17 @@ void FS_Init( void ) if( !FS_SysFolderExists( dirs.strings[i] ) || ( !Q_strcmp( dirs.strings[i], ".." ) && !fs_ext_path )) continue; - if( SI.games[SI.numgames] == NULL ) - SI.games[SI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); + if( FI.games[FI.numgames] == NULL ) + FI.games[FI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); - if( FS_ParseGameInfo( dirs.strings[i], SI.games[SI.numgames] )) - SI.numgames++; // added + if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames] )) + FI.numgames++; // added } stringlistfreecontents( &dirs ); Con_Reportf( "FS_Init: done\n" ); + + return true; } void FS_AllowDirectPaths( qboolean enable ) @@ -2254,20 +1479,62 @@ void FS_AllowDirectPaths( qboolean enable ) FS_Shutdown ================ */ -void FS_Shutdown( void ) +void FS_ShutdownStdio( void ) { - int i; - + int i; // release gamedirs - for( i = 0; i < SI.numgames; i++ ) - if( SI.games[i] ) Mem_Free( SI.games[i] ); - - memset( &SI, 0, sizeof( sysinfo_t )); + for( i = 0; i < FI.numgames; i++ ) + if( FI.games[i] ) Mem_Free( FI.games[i] ); FS_ClearSearchPath(); // release all wad files too Mem_FreePool( &fs_mempool ); } +/* +============ +FS_Path_f + +debug info +============ +*/ +void FS_Path_f( void ) +{ + searchpath_t *s; + + Con_Printf( "Current search path:\n" ); + + for( s = fs_searchpaths; s; s = s->next ) + { + string info; + + switch( s->type ) + { + case SEARCHPATH_PAK: + FS_PrintPAKInfo( info, sizeof( info ), s->pack ); + break; + case SEARCHPATH_WAD: + FS_PrintWADInfo( info, sizeof( info ), s->wad ); + break; + case SEARCHPATH_ZIP: + FS_PrintZIPInfo( info, sizeof( info ), s->zip ); + break; + case SEARCHPATH_PLAIN: + Q_strncpy( info, s->filename, sizeof( info )); + break; + } + + Con_Printf( "%s", info ); + + if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); + if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); + if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); + if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); + if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); + + Con_Printf( "\n" ); + } +} + /* ==================== FS_SysFileTime @@ -2275,7 +1542,7 @@ FS_SysFileTime Internal function used to determine filetime ==================== */ -static int FS_SysFileTime( const char *filename ) +int FS_SysFileTime( const char *filename ) { struct stat buf; @@ -2292,7 +1559,7 @@ FS_SysOpen Internal function used to create a file_t and open the relevant non-packed file on disk ==================== */ -static file_t *FS_SysOpen( const char *filepath, const char *mode ) +file_t *FS_SysOpen( const char *filepath, const char *mode ) { file_t *file; int mod, opt; @@ -2391,7 +1658,7 @@ static int FS_DuplicateHandle( const char *filename, int handle, fs_offset_t pos } */ -static file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) +file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) { file_t *file = (file_t *)Mem_Calloc( fs_mempool, sizeof( file_t )); #ifndef XASH_REDUCE_FD @@ -2422,44 +1689,6 @@ static file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offse return file; } -/* -=========== -FS_OpenPackedFile - -Open a packed file using its package file descriptor -=========== -*/ -file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) -{ - dpackfile_t *pfile; - - pfile = &pack->files[pack_ind]; - - return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); -} - -/* -=========== -FS_OpenZipFile - -Open a packed file using its package file descriptor -=========== -*/ -file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) -{ - zipfile_t *pfile; - pfile = &zip->files[pack_ind]; - - // compressed files handled in Zip_LoadFile - if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) - { - Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __FUNCTION__, pfile->name ); - return NULL; - } - - return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); -} - /* ================== FS_SysFileExists @@ -2535,11 +1764,7 @@ qboolean FS_SysFolderExists( const char *path ) struct stat buf; if( stat( path, &buf ) < 0 ) - { - if( errno != ENOTDIR ) - Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %s\n", strerror( errno )); return false; - } return S_ISDIR( buf.st_mode ); #else @@ -2557,7 +1782,7 @@ Return the searchpath where the file was found (or NULL) and the file index in the package if relevant ==================== */ -static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) +searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) { searchpath_t *search; char *pEnvPath; @@ -2569,107 +1794,31 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir continue; // is the element a pak file? - if( search->pack ) + if( search->type == SEARCHPATH_PAK ) { - int left, right, middle; - pack_t *pak; - - pak = search->pack; - - // look for the file (binary search) - left = 0; - right = pak->numfiles - 1; - while( left <= right ) + int pack_ind = FS_FindFilePAK( search->pack, name ); + if( pack_ind >= 0 ) { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( pak->files[middle].name, name ); - - // Found it - if( !diff ) - { - if( index ) *index = middle; - return search; - } - - // if we're too far in the list - if( diff > 0 ) - right = middle - 1; - else left = middle + 1; + if( index ) *index = pack_ind; + return search; } } - else if( search->wad ) + else if( search->type == SEARCHPATH_WAD ) { - dlumpinfo_t *lump; - signed char type = W_TypeFromExt( name ); - qboolean anywadname = true; - string wadname, wadfolder; - string shortname; - - // quick reject by filetype - if( type == TYP_NONE ) continue; - COM_ExtractFilePath( name, wadname ); - wadfolder[0] = '\0'; - - if( COM_CheckStringEmpty( wadname ) ) - { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); - anywadname = false; - } - - // make wadname from wad fullpath - COM_FileBase( search->wad->filename, shortname ); - COM_DefaultExtension( shortname, ".wad" ); - - // quick reject by wadname - if( !anywadname && Q_stricmp( wadname, shortname )) - continue; - - // NOTE: we can't using long names for wad, - // because we using original wad names[16]; - COM_FileBase( name, shortname ); - - lump = W_FindLump( search->wad, shortname, type ); - - if( lump ) + int pack_ind = FS_FindFileWAD( search->wad, name ); + if( pack_ind >= 0 ) { - if( index ) - *index = lump - search->wad->lumps; + if( index ) *index = pack_ind; return search; } } - else if( search->zip ) + else if( search->type == SEARCHPATH_ZIP ) { - int left, right, middle; - zip_t *zip; - - zip = search->zip; - - // look for the file (binary search) - left = 0; - right = zip->numfiles - 1; - - while( left <= right ) + int pack_ind = FS_FindFileZIP( search->zip, name ); + if( pack_ind >= 0 ) { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( zip->files[middle].name, name ); - - // Found it - if( !diff ) - { - if( index ) *index = middle; - return search; - } - - // if we're too far in the list - if( diff > 0 ) - right = middle - 1; - else left = middle + 1; + if( index ) *index = pack_ind; + return search; } } else @@ -2695,7 +1844,7 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir memset( search, 0, sizeof( searchpath_t )); // root folder has a more priority than netpath - Q_strncpy( search->filename, host.rootdir, sizeof( search->filename )); + Q_strncpy( search->filename, fs_rootdir, sizeof( search->filename )); Q_strcat( search->filename, PATH_SPLITTER ); Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); @@ -2705,26 +1854,6 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir *index = -1; return search; } - -#if 0 - // search for environment path - while( ( pEnvPath = getenv( "Path" ) ) ) - { - char *end = Q_strchr( pEnvPath, ';' ); - if( !end ) break; - Q_strncpy( search->filename, pEnvPath, (end - pEnvPath) + 1 ); - Q_strcat( search->filename, PATH_SPLITTER ); - Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); - - if( FS_SysFileExists( netpath, !( search->flags & FS_CUSTOM_PATH ) )) - { - if( index != NULL ) - *index = -1; - return search; - } - pEnvPath += (end - pEnvPath) + 1; // move pointer - } -#endif // 0 } if( index != NULL ) @@ -2752,19 +1881,23 @@ file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedi if( search == NULL ) return NULL; - if( search->pack ) + switch( search->type ) + { + case SEARCHPATH_PAK: return FS_OpenPackedFile( search->pack, pack_ind ); - else if( search->wad ) + case SEARCHPATH_WAD: return NULL; // let W_LoadFile get lump correctly - else if( search->zip ) + case SEARCHPATH_ZIP: return FS_OpenZipFile( search->zip, pack_ind ); - else if( pack_ind < 0 ) - { - char path [MAX_SYSPATH]; + default: + if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; - // found in the filesystem? - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysOpen( path, mode ); + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysOpen( path, mode ); + } } return NULL; @@ -2832,6 +1965,27 @@ int FS_Close( file_t *file ) return 0; } +/* +==================== +FS_Flush + +flushes written data to disk +==================== +*/ +int FS_Flush( file_t *file ) +{ + if( !file ) return 0; + + // purge cached data + FS_Purge( file ); + + // sync + if( fsync( file->handle ) < 0 ) + return EOF; + + return 0; +} + /* ==================== FS_Write @@ -3159,7 +2313,7 @@ FS_Purge Erases any buffered input or output data ==================== */ -void FS_Purge( file_t *file ) +static void FS_Purge( file_t *file ) { file->buff_len = 0; file->buff_ind = 0; @@ -3193,10 +2347,10 @@ byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamediro } else { - buf = W_LoadFile( path, &filesize, gamedironly ); + buf = FS_LoadWADFile( path, &filesize, gamedironly ); if( !buf ) - buf = Zip_LoadFile( path, &filesize, gamedironly ); + buf = FS_LoadZIPFile( path, &filesize, gamedironly ); } @@ -3215,7 +2369,6 @@ qboolean CRC32_File( dword *crcvalue, const char *filename ) f = FS_Open( filename, "rb", false ); if( !f ) return false; - Assert( crcvalue != NULL ); CRC32_Init( crcvalue ); while( 1 ) @@ -3340,154 +2493,38 @@ OTHERS PUBLIC FUNCTIONS FS_FileExists Look for a file in the packages and in the filesystem -================== -*/ -int GAME_EXPORT FS_FileExists( const char *filename, int gamedironly ) -{ - if( FS_FindFile( filename, NULL, gamedironly )) - return true; - return false; -} - -/* -================== -FS_GetDiskPath - -Build direct path for file in the filesystem -return NULL for file in pack -================== -*/ -const char *FS_GetDiskPath( const char *name, qboolean gamedironly ) -{ - int index; - searchpath_t *search; - - search = FS_FindFile( name, &index, gamedironly ); - - if( search ) - { - if( index != -1 ) // file in pack or wad - return NULL; - return va( "%s%s", search->filename, name ); - } - - return NULL; -} - -/* -================== -FS_CheckForCrypt - -return true if library is crypted -================== -*/ -qboolean FS_CheckForCrypt( const char *dllname ) -{ - file_t *f; - int key; - - f = FS_Open( dllname, "rb", false ); - if( !f ) return false; - - FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes - FS_Read( f, &key, sizeof( key )); - FS_Close( f ); - - return ( key == 0x12345678 ) ? true : false; +================== +*/ +int GAME_EXPORT FS_FileExists( const char *filename, int gamedironly ) +{ + if( FS_FindFile( filename, NULL, gamedironly )) + return true; + return false; } /* ================== -FS_FindLibrary +FS_GetDiskPath -search for library, assume index is valid -only for internal use +Build direct path for file in the filesystem +return NULL for file in pack ================== */ -dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) +const char *FS_GetDiskPath( const char *name, qboolean gamedironly ) { - string dllpath; + int index; searchpath_t *search; - dll_user_t *hInst; - int i, index; - int start = 0; - int len; - - // check for bad exports - if( !COM_CheckString( dllname )) - return NULL; - - fs_ext_path = directpath; - - // HACKHACK remove absoulte path to valve folder - if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) - start += 9; - - // replace all backward slashes - len = Q_strlen( dllname ); - - for( i = 0; i < len; i++ ) - { - if( dllname[i+start] == '\\' ) dllpath[i] = '/'; - else dllpath[i] = Q_tolower( dllname[i+start] ); - } - dllpath[i] = '\0'; - - COM_DefaultExtension( dllpath, "."OS_LIB_EXT ); // apply ext if forget - search = FS_FindFile( dllpath, &index, false ); - if( !search && !directpath ) - { - fs_ext_path = false; - - // trying check also 'bin' folder for indirect paths - Q_strncpy( dllpath, dllname, sizeof( dllpath )); - search = FS_FindFile( dllpath, &index, false ); - if( !search ) return NULL; // unable to find - } - - // NOTE: for libraries we not fail even if search is NULL - // let the OS find library himself - hInst = Mem_Calloc( host.mempool, sizeof( dll_user_t )); - - // save dllname for debug purposes - Q_strncpy( hInst->dllName, dllname, sizeof( hInst->dllName )); - - // shortPath is used for LibraryLoadSymbols only - Q_strncpy( hInst->shortPath, dllpath, sizeof( hInst->shortPath )); - - hInst->encrypted = FS_CheckForCrypt( dllpath ); + search = FS_FindFile( name, &index, gamedironly ); - if( index < 0 && !hInst->encrypted && search ) - { - Q_snprintf( hInst->fullPath, sizeof( hInst->fullPath ), "%s%s", search->filename, dllpath ); - hInst->custom_loader = false; // we can loading from disk and use normal debugging - } - else + if( search ) { - // NOTE: if search is NULL let the OS found library himself - Q_strncpy( hInst->fullPath, dllpath, sizeof( hInst->fullPath )); - - if( search && ( search->wad || search->pack || search->zip ) ) - { -#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) - Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " - "and will be removed in the future\n", __FUNCTION__ ); - hInst->custom_loader = true; -#else - Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " - "this platform\n", __FUNCTION__ ); - hInst->custom_loader = false; -#endif - } - else - { - hInst->custom_loader = false; - } + if( index != -1 ) // file in pack or wad + return NULL; + return va( "%s%s", search->filename, name ); } - fs_ext_path = false; // always reset direct paths - return hInst; + return NULL; } /* @@ -3543,19 +2580,23 @@ int FS_FileTime( const char *filename, qboolean gamedironly ) search = FS_FindFile( filename, &pack_ind, gamedironly ); if( !search ) return -1; // doesn't exist - if( search->pack ) // grab pack filetime - return search->pack->filetime; - else if( search->wad ) // grab wad filetime - return search->wad->filetime; - else if( search->zip ) - return search->zip->filetime; - else if( pack_ind < 0 ) + switch( search->type ) { - // found in the filesystem? - char path [MAX_SYSPATH]; + case SEARCHPATH_PAK: + return FS_FileTimePAK( search->pack ); + case SEARCHPATH_WAD: + return FS_FileTimeWAD( search->wad ); + case SEARCHPATH_ZIP: + return FS_FileTimeZIP( search->zip ); + default: + if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysFileTime( path ); + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysFileTime( path ); + } } return -1; // doesn't exist @@ -3687,154 +2728,18 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) continue; // is the element a pak file? - if( searchpath->pack ) + if( searchpath->type == SEARCHPATH_PAK ) { // look through all the pak file elements - pak = searchpath->pack; - for( i = 0; i < pak->numfiles; i++ ) - { - Q_strncpy( temp, pak->files[i].name, sizeof( temp )); - while( temp[0] ) - { - if( matchpattern( temp, (char *)pattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - stringlistappend( &resultlist, temp ); - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchPAK( &resultlist, searchpath->pack, pattern ); } - else if( searchpath->zip ) + else if( searchpath->type == SEARCHPATH_ZIP ) { - zip = searchpath->zip; - for( i = 0; i < zip->numfiles; i++ ) - { - Q_strncpy( temp, zip->files[i].name, sizeof(temp) ); - while( temp[0] ) - { - if( matchpattern( temp, (char *)pattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - stringlistappend( &resultlist, temp ); - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchZIP( &resultlist, searchpath->zip, pattern ); } - else if( searchpath->wad ) + else if( searchpath->type == SEARCHPATH_WAD ) { - string wadpattern, wadname, temp2; - signed char type = W_TypeFromExt( pattern ); - qboolean anywadname = true; - string wadfolder; - - // quick reject by filetype - if( type == TYP_NONE ) continue; - COM_ExtractFilePath( pattern, wadname ); - COM_FileBase( pattern, wadpattern ); - wadfolder[0] = '\0'; - - if( COM_CheckStringEmpty( wadname )) - { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); - anywadname = false; - } - - // make wadname from wad fullpath - COM_FileBase( searchpath->wad->filename, temp2 ); - COM_DefaultExtension( temp2, ".wad" ); - - // quick reject by wadname - if( !anywadname && Q_stricmp( wadname, temp2 )) - continue; - - // look through all the wad file elements - wad = searchpath->wad; - - for( i = 0; i < wad->numlumps; i++ ) - { - // if type not matching, we already have no chance ... - if( type != TYP_ANY && wad->lumps[i].type != type ) - continue; - - // build the lumpname with image suffix (if present) - Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); - - while( temp[0] ) - { - if( matchpattern( temp, wadpattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - { - // build path: wadname/lumpname.ext - Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); - COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); - stringlistappend( &resultlist, temp2 ); - } - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchWAD( &resultlist, searchpath->wad, pattern ); } else { @@ -3903,376 +2808,124 @@ void FS_InitMemory( void ) fs_searchpaths = NULL; } -/* -============================================================================= - -WADSYSTEM PRIVATE ROUTINES - -============================================================================= -*/ -// associate extension with wad type -static const wadtype_t wad_types[7] = +fs_interface_t g_engfuncs = { -{ "pal", TYP_PALETTE }, // palette -{ "dds", TYP_DDSTEX }, // DDS image -{ "lmp", TYP_GFXPIC }, // quake1, hl pic -{ "fnt", TYP_QFONT }, // hl qfonts -{ "mip", TYP_MIPTEX }, // hl/q1 mip -{ "txt", TYP_SCRIPT }, // scripts -{ NULL, TYP_NONE } + _Con_Printf, + _Con_Printf, + _Con_Printf, + _Sys_Error, + _Mem_AllocPool, + _Mem_FreePool, + _Mem_Alloc, + _Mem_Realloc, + _Mem_Free }; -/* -=========== -W_TypeFromExt - -Extracts file type from extension -=========== -*/ -static signed char W_TypeFromExt( const char *lumpname ) -{ - const char *ext = COM_FileExtension( lumpname ); - const wadtype_t *type; - - // we not known about filetype, so match only by filename - if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) - return TYP_ANY; - - for( type = wad_types; type->ext; type++ ) - { - if( !Q_stricmp( ext, type->ext )) - return type->type; - } - return TYP_NONE; -} - -/* -=========== -W_ExtFromType - -Convert type to extension -=========== -*/ -static const char *W_ExtFromType( signed char lumptype ) -{ - const wadtype_t *type; - - // we not known aboyt filetype, so match only by filename - if( lumptype == TYP_NONE || lumptype == TYP_ANY ) - return ""; - - for( type = wad_types; type->ext; type++ ) - { - if( lumptype == type->type ) - return type->ext; - } - return ""; -} - -/* -=========== -W_FindLump - -Serach for already existed lump -=========== -*/ -static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ) -{ - int left, right; - - if( !wad || !wad->lumps || matchtype == TYP_NONE ) - return NULL; - - // look for the file (binary search) - left = 0; - right = wad->numlumps - 1; - - while( left <= right ) - { - int middle = (left + right) / 2; - int diff = Q_stricmp( wad->lumps[middle].name, name ); - - if( !diff ) - { - if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) - return &wad->lumps[middle]; // found - else if( wad->lumps[middle].type < matchtype ) - diff = 1; - else if( wad->lumps[middle].type > matchtype ) - diff = -1; - else break; // not found - } - - // if we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - return NULL; -} - -/* -==================== -W_AddFileToWad - -Add a file to the list of files contained into a package -and sort LAT in alpha-bethical order -==================== -*/ -static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) -{ - int left, right; - dlumpinfo_t *plump; - - // look for the slot we should put that file into (binary search) - left = 0; - right = wad->numlumps - 1; - - while( left <= right ) - { - int middle = ( left + right ) / 2; - int diff = Q_stricmp( wad->lumps[middle].name, name ); - - if( !diff ) - { - if( wad->lumps[middle].type < newlump->type ) - diff = 1; - else if( wad->lumps[middle].type > newlump->type ) - diff = -1; - else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); - } - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // we have to move the right of the list by one slot to free the one we need - plump = &wad->lumps[left]; - memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); - wad->numlumps++; - - *plump = *newlump; - memcpy( plump->name, name, sizeof( plump->name )); - - return plump; -} - -/* -=========== -W_ReadLump - -reading lump into temp buffer -=========== -*/ -byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) -{ - size_t oldpos, size = 0; - byte *buf; - - // assume error - if( lumpsizeptr ) *lumpsizeptr = 0; - - // no wads loaded - if( !wad || !lump ) return NULL; - - oldpos = FS_Tell( wad->handle ); // don't forget restore original position - - if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) - { - Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - return NULL; - } - - buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); - size = FS_Read( wad->handle, buf, lump->disksize ); - - if( size < lump->disksize ) - { - Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - Mem_Free( buf ); - return NULL; - } - - if( lumpsizeptr ) *lumpsizeptr = lump->disksize; - FS_Seek( wad->handle, oldpos, SEEK_SET ); - - return buf; -} - -/* -============================================================================= - -WADSYSTEM PUBLIC BASE FUNCTIONS - -============================================================================= -*/ -/* -=========== -W_Open - -open the wad for reading & writing -=========== -*/ -wfile_t *W_Open( const char *filename, int *error ) +static qboolean FS_InitInterface( int version, fs_interface_t *engfuncs ) { - wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); - const char *basename; - int i, lumpcount; - dlumpinfo_t *srclumps; - size_t lat_size; - dwadinfo_t header; - - // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) - if( fs_ext_path ) basename = filename; - else basename = COM_FileWithoutPath( filename ); - - wad->handle = FS_Open( basename, "rb", false ); - - // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready - if( COM_CheckStringEmpty( host.rodir ) && fs_ext_path && wad->handle == NULL ) - wad->handle = FS_SysOpen( filename, "rb" ); - - if( wad->handle == NULL ) - { - Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); - if( error ) *error = WAD_LOAD_COULDNT_OPEN; - W_Close( wad ); - return NULL; - } - - // copy wad name - Q_strncpy( wad->filename, filename, sizeof( wad->filename )); - wad->filetime = FS_SysFileTime( filename ); - wad->mempool = Mem_AllocPool( filename ); - - if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) + // to be extended in future interface revisions + if( version != FS_API_VERSION ) { - Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - W_Close( wad ); - return NULL; + Con_Printf( S_ERROR "filesystem optional interface version mismatch: expected %d, got %d\n", + FS_API_VERSION, version ); + return false; } - if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) - { - Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - W_Close( wad ); - return NULL; - } + if( engfuncs->_Con_Printf ) + g_engfuncs._Con_Printf = engfuncs->_Con_Printf; - lumpcount = header.numlumps; + if( engfuncs->_Con_DPrintf ) + g_engfuncs._Con_DPrintf = engfuncs->_Con_DPrintf; - if( lumpcount >= MAX_FILES_IN_WAD ) - { - Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); - if( error ) *error = WAD_LOAD_TOO_MANY_FILES; - } - else if( lumpcount <= 0 ) - { - Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); - if( error ) *error = WAD_LOAD_NO_FILES; - W_Close( wad ); - return NULL; - } - else if( error ) *error = WAD_LOAD_OK; + if( engfuncs->_Con_Reportf ) + g_engfuncs._Con_Reportf = engfuncs->_Con_Reportf; - wad->infotableofs = header.infotableofs; // save infotableofs position + if( engfuncs->_Sys_Error ) + g_engfuncs._Sys_Error = engfuncs->_Sys_Error; - if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) + if( engfuncs->_Mem_AllocPool && engfuncs->_Mem_FreePool ) { - Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); - if( error ) *error = WAD_LOAD_BAD_FOLDERS; - W_Close( wad ); - return NULL; - } - - lat_size = lumpcount * sizeof( dlumpinfo_t ); + g_engfuncs._Mem_AllocPool = engfuncs->_Mem_AllocPool; + g_engfuncs._Mem_FreePool = engfuncs->_Mem_FreePool; - // NOTE: lumps table can be reallocated for O_APPEND mode - srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); - - if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) - { - Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); - if( error ) *error = WAD_LOAD_CORRUPTED; - Mem_Free( srclumps ); - W_Close( wad ); - return NULL; + Con_Reportf( "filesystem_stdio: custom pool allocation functions found\n" ); } - // starting to add lumps - wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); - wad->numlumps = 0; - - // sort lumps for binary search - for( i = 0; i < lumpcount; i++ ) + if( engfuncs->_Mem_Alloc && engfuncs->_Mem_Realloc && engfuncs->_Mem_Free ) { - char name[16]; - int k; + g_engfuncs._Mem_Alloc = engfuncs->_Mem_Alloc; + g_engfuncs._Mem_Realloc = engfuncs->_Mem_Realloc; + g_engfuncs._Mem_Free = engfuncs->_Mem_Free; - // cleanup lumpname - Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); - - // check for '*' symbol issues (quake1) - k = Q_strlen( Q_strrchr( name, '*' )); - if( k ) name[Q_strlen( name ) - k] = '!'; - - // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) - if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) - srclumps[i].type = TYP_GFXPIC; - - W_AddFileToWad( name, wad, &srclumps[i] ); + Con_Reportf( "filesystem_stdio: custom memory allocation functions found\n" ); } - // release source lumps - Mem_Free( srclumps ); - - // and leave the file open - return wad; + return true; } -/* -=========== -W_Close +static fs_api_t g_api = +{ + FS_InitStdio, + FS_ShutdownStdio, + + // search path utils + FS_Rescan, + FS_ClearSearchPath, + FS_AllowDirectPaths, + FS_AddGameDirectory, + FS_AddGameHierarchy, + FS_Search, + FS_SetCurrentDirectory, + FS_FindLibrary, + FS_Path_f, + + // gameinfo utils + FS_LoadGameInfo, + + // file ops + FS_Open, + FS_Write, + FS_Read, + FS_Seek, + FS_Tell, + FS_Eof, + FS_Flush, + FS_Close, + FS_Gets, + FS_UnGetc, + FS_Getc, + FS_VPrintf, + FS_Printf, + FS_Print, + FS_FileLength, + FS_FileCopy, + + // file buffer ops + FS_LoadFile, + FS_LoadDirectFile, + FS_WriteFile, + + // file hashing + CRC32_File, + MD5_HashFile, + + // filesystem ops + FS_FileExists, + FS_FileTime, + FS_FileSize, + FS_Rename, + FS_Delete, + FS_SysFileExists, + FS_GetDiskPath, +}; -finalize wad or just close -=========== -*/ -void W_Close( wfile_t *wad ) +int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs ) { - if( !wad ) return; - - Mem_FreePool( &wad->mempool ); - if( wad->handle != NULL ) - FS_Close( wad->handle ); - Mem_Free( wad ); // free himself -} - -/* -============================================================================= - -FILESYSTEM IMPLEMENTATION - -============================================================================= -*/ -/* -=========== -W_LoadFile + if( !FS_InitInterface( version, engfuncs )) + return 0; -loading lump into the tmp buffer -=========== -*/ -static byte *W_LoadFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) -{ - searchpath_t *search; - int index; + memcpy( api, &g_api, sizeof( *api )); + *globals = &FI; - search = FS_FindFile( path, &index, gamedironly ); - if( search && search->wad ) - return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); - return NULL; + return FS_API_VERSION; } diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h new file mode 100644 index 00000000..6a7a1f4c --- /dev/null +++ b/filesystem/filesystem.h @@ -0,0 +1,202 @@ +/* +filesystem.h - engine FS +Copyright (C) 2007 Uncle Mike + +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. +*/ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include +#include +#include +#include "xash3d_types.h" +#include "const.h" +#include "com_model.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define FS_API_VERSION 1 // not stable yet! + +// search path flags +enum +{ + FS_STATIC_PATH = BIT( 0 ), // FS_ClearSearchPath will be ignore this path + FS_NOWRITE_PATH = BIT( 1 ), // default behavior - last added gamedir set as writedir. This flag disables it + FS_GAMEDIR_PATH = BIT( 2 ), // just a marker for gamedir path + FS_CUSTOM_PATH = BIT( 3 ), // gamedir but with custom/mod data + FS_GAMERODIR_PATH = BIT( 4 ), // gamedir but read-only + + FS_GAMEDIRONLY_SEARCH_FLAGS = FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH +}; + +typedef struct +{ + int numfilenames; + char **filenames; + char *filenamesbuffer; +} search_t; + +typedef struct gameinfo_s +{ + // filesystem info + char gamefolder[MAX_QPATH]; // used for change game '-game x' + char basedir[MAX_QPATH]; // base game directory (like 'id1' for Quake or 'valve' for Half-Life) + char falldir[MAX_QPATH]; // used as second basedir + char startmap[MAX_QPATH];// map to start singleplayer game + char trainmap[MAX_QPATH];// map to start hazard course (if specified) + char title[64]; // Game Main Title + float version; // game version (optional) + + // .dll pathes + char dll_path[MAX_QPATH]; // e.g. "bin" or "cl_dlls" + char game_dll[MAX_QPATH]; // custom path for game.dll + + // .ico path + char iconpath[MAX_QPATH]; // "game.ico" by default + + // about mod info + string game_url; // link to a developer's site + string update_url; // link to updates page + char type[MAX_QPATH]; // single, toolkit, multiplayer etc + char date[MAX_QPATH]; + size_t size; + + int gamemode; + qboolean secure; // prevent to console acess + qboolean nomodels; // don't let player to choose model (use player.mdl always) + qboolean noskills; // disable skill menu selection + qboolean render_picbutton_text; // use font renderer to render WON buttons + + char sp_entity[32]; // e.g. info_player_start + char mp_entity[32]; // e.g. info_player_deathmatch + char mp_filter[32]; // filtering multiplayer-maps + + char ambientsound[NUM_AMBIENTS][MAX_QPATH]; // quake ambient sounds + + int max_edicts; // min edicts is 600, max edicts is 8196 + int max_tents; // min temp ents is 300, max is 2048 + int max_beams; // min beams is 64, max beams is 512 + int max_particles; // min particles is 4096, max particles is 32768 + + char game_dll_linux[64]; // custom path for game.dll + char game_dll_osx[64]; // custom path for game.dll + + qboolean added; +} gameinfo_t; + +typedef enum +{ + GAME_NORMAL, + GAME_SINGLEPLAYER_ONLY, + GAME_MULTIPLAYER_ONLY +} gametype_t; + +typedef struct fs_dllinfo_t +{ + string fullPath; + string shortPath; + qboolean encrypted; + qboolean custom_loader; +} fs_dllinfo_t; + +typedef struct fs_globals_t +{ + gameinfo_t *GameInfo; // current GameInfo + gameinfo_t *games[MAX_MODS]; // environment games (founded at each engine start) + int numgames; +} fs_globals_t; + +typedef struct fs_api_t +{ + qboolean (*InitStdio)( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); + void (*ShutdownStdio)( void ); + + // search path utils + void (*Rescan)( void ); + void (*ClearSearchPath)( void ); + void (*AllowDirectPaths)( qboolean enable ); + void (*AddGameDirectory)( const char *dir, uint flags ); + void (*AddGameHierarchy)( const char *dir, uint flags ); + search_t *(*Search)( const char *pattern, int caseinsensitive, int gamedironly ); + int (*SetCurrentDirectory)( const char *path ); + qboolean (*FindLibrary)( const char *dllname, qboolean directpath, fs_dllinfo_t *dllinfo ); + void (*Path_f)( void ); + + // gameinfo utils + void (*LoadGameInfo)( const char *rootfolder ); + + // file ops + file_t *(*Open)( const char *filepath, const char *mode, qboolean gamedironly ); + fs_offset_t (*Write)( file_t *file, const void *data, size_t datasize ); + fs_offset_t (*Read)( file_t *file, void *buffer, size_t buffersize ); + int (*Seek)( file_t *file, fs_offset_t offset, int whence ); + fs_offset_t (*Tell)( file_t *file ); + qboolean (*Eof)( file_t *file ); + int (*Flush)( file_t *file ); + int (*Close)( file_t *file ); + int (*Gets)( file_t *file, byte *string, size_t bufsize ); + int (*UnGetc)( file_t *file, byte c ); + int (*Getc)( file_t *file ); + int (*VPrintf)( file_t *file, const char *format, va_list ap ); + int (*Printf)( file_t *file, const char *format, ... ) _format( 2 ); + int (*Print)( file_t *file, const char *msg ); + fs_offset_t (*FileLength)( file_t *f ); + qboolean (*FileCopy)( file_t *pOutput, file_t *pInput, int fileSize ); + + // file buffer ops + byte *(*LoadFile)( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); + byte *(*LoadDirectFile)( const char *path, fs_offset_t *filesizeptr ); + qboolean (*WriteFile)( const char *filename, const void *data, fs_offset_t len ); + + // file hashing + qboolean (*CRC32_File)( dword *crcvalue, const char *filename ); + qboolean (*MD5_HashFile)( byte digest[16], const char *pszFileName, uint seed[4] ); + + // filesystem ops + int (*FileExists)( const char *filename, int gamedironly ); + int (*FileTime)( const char *filename, qboolean gamedironly ); + fs_offset_t (*FileSize)( const char *filename, qboolean gamedironly ); + qboolean (*Rename)( const char *oldname, const char *newname ); + qboolean (*Delete)( const char *path ); + qboolean (*SysFileExists)( const char *path, qboolean casesensitive ); + const char *(*GetDiskPath)( const char *name, qboolean gamedironly ); +} fs_api_t; + +typedef struct fs_interface_t +{ + // logging + void (*_Con_Printf)( const char *fmt, ... ) _format( 1 ); // typical console allowed messages + void (*_Con_DPrintf)( const char *fmt, ... ) _format( 1 ); // -dev 1 + void (*_Con_Reportf)( const char *fmt, ... ) _format( 1 ); // -dev 2 + + void (*_Sys_Error)( const char *fmt, ... ) _format( 1 ); + + // memory + poolhandle_t (*_Mem_AllocPool)( const char *name, const char *filename, int fileline ); + void (*_Mem_FreePool)( poolhandle_t *poolptr, const char *filename, int fileline ); + void *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ); + void *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ); + void (*_Mem_Free)( void *data, const char *filename, int fileline ); +} fs_interface_t; + +typedef int (*FSAPI)( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *interface ); +#define GET_FS_API "GetFSAPI" + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif//FILESYSTEM_H diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h new file mode 100644 index 00000000..c466b1db --- /dev/null +++ b/filesystem/filesystem_internal.h @@ -0,0 +1,204 @@ +/* +filesystem.h - engine FS +Copyright (C) 2007 Uncle Mike + +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. +*/ + +#ifndef FILESYSTEM_INTERNAL_H +#define FILESYSTEM_INTERNAL_H + +#include "xash3d_types.h" +#include "filesystem.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct zip_s zip_t; +typedef struct pack_s pack_t; +typedef struct wfile_s wfile_t; + + +#define FILE_BUFF_SIZE (2048) + +struct file_s +{ + int handle; // file descriptor + int ungetc; // single stored character from ungetc, cleared to EOF when read + fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) + fs_offset_t position; // current position in the file + fs_offset_t offset; // offset into the package (0 if external file) + time_t filetime; // pak, wad or real filetime + // contents buffer + fs_offset_t buff_ind, buff_len; // buffer current index and length + byte buff[FILE_BUFF_SIZE]; // intermediate buffer +#ifdef XASH_REDUCE_FD + const char *backup_path; + fs_offset_t backup_position; + uint backup_options; +#endif +}; + +enum +{ + SEARCHPATH_PLAIN = 0, + SEARCHPATH_PAK, + SEARCHPATH_WAD, + SEARCHPATH_ZIP +}; + +typedef struct stringlist_s +{ + // maxstrings changes as needed, causing reallocation of strings[] array + int maxstrings; + int numstrings; + char **strings; +} stringlist_t; + +typedef struct searchpath_s +{ + string filename; + int type; + int flags; + union + { + pack_t *pack; + wfile_t *wad; + zip_t *zip; + }; + struct searchpath_s *next; +} searchpath_t; + +extern searchpath_t *fs_searchpaths; +extern poolhandle_t fs_mempool; +extern fs_interface_t g_engfuncs; +extern qboolean fs_ext_path; +extern char fs_rodir[MAX_SYSPATH]; +extern char fs_rootdir[MAX_SYSPATH]; + +#define Mem_Malloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) +#define Mem_Calloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) +#define Mem_Realloc( pool, ptr, size ) g_engfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ ) +#define Mem_Free( mem ) g_engfuncs._Mem_Free( mem, __FILE__, __LINE__ ) +#define Mem_AllocPool( name ) g_engfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) +#define Mem_FreePool( pool ) g_engfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) + +#define Con_Printf (*g_engfuncs._Con_Printf) +#define Con_DPrintf (*g_engfuncs._Con_DPrintf) +#define Con_Reportf (*g_engfuncs._Con_Reportf) +#define Sys_Error (*g_engfuncs._Sys_Error) + +// +// filesystem.c +// +qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); +void FS_ShutdownStdio( void ); + +// search path utils +void FS_Rescan( void ); +void FS_ClearSearchPath( void ); +void FS_AllowDirectPaths( qboolean enable ); +void FS_AddGameDirectory( const char *dir, uint flags ); +void FS_AddGameHierarchy( const char *dir, uint flags ); +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); +int FS_SetCurrentDirectory( const char *path ); +void FS_Path_f( void ); + +// gameinfo utils +void FS_LoadGameInfo( const char *rootfolder ); + +// file ops +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); +fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ); +fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ); +int FS_Seek( file_t *file, fs_offset_t offset, int whence ); +fs_offset_t FS_Tell( file_t *file ); +qboolean FS_Eof( file_t *file ); +int FS_Flush( file_t *file ); +int FS_Close( file_t *file ); +int FS_Gets( file_t *file, byte *string, size_t bufsize ); +int FS_UnGetc( file_t *file, byte c ); +int FS_Getc( file_t *file ); +int FS_VPrintf( file_t *file, const char *format, va_list ap ); +int FS_Printf( file_t *file, const char *format, ... ) _format( 2 ); +int FS_Print( file_t *file, const char *msg ); +fs_offset_t FS_FileLength( file_t *f ); +qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); + +// file buffer ops +byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); +byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ); +qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ); + +// file hashing +qboolean CRC32_File( dword *crcvalue, const char *filename ); +qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); + +// filesystem ops +int FS_FileExists( const char *filename, int gamedironly ); +int FS_FileTime( const char *filename, qboolean gamedironly ); +fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ); +qboolean FS_Rename( const char *oldname, const char *newname ); +qboolean FS_Delete( const char *path ); +qboolean FS_SysFileExists( const char *path, qboolean casesensitive ); +const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); +void stringlistappend( stringlist_t *list, char *text ); +void FS_CreatePath( char *path ); +qboolean FS_SysFolderExists( const char *path ); +file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly ); + +int FS_SysFileTime( const char *filename ); +file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ); +file_t *FS_SysOpen( const char *filepath, const char *mode ); +const char *FS_FixFileCase( const char *path ); +searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); + +// +// pak.c +// +int FS_FileTimePAK( pack_t *pack ); +int FS_FindFilePAK( pack_t *pack, const char *name ); +void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ); +void FS_ClosePAK( pack_t *pack ); +void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ); +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ); +qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ); + +// +// wad.c +// +int FS_FileTimeWAD( wfile_t *wad ); +int FS_FindFileWAD( wfile_t *wad, const char *name ); +void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ); +void FS_CloseWAD( wfile_t *wad ); +void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ); +byte *FS_LoadWADFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); +qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ); + +// +// zip.c +// +int FS_FileTimeZIP( zip_t *zip ); +int FS_FindFileZIP( zip_t *zip, const char *name ); +void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ); +void FS_CloseZIP( zip_t *zip ); +void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ); +byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); +file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ); +qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ); + +#ifdef __cplusplus +} +#endif + +#endif // FILESYSTEM_INTERNAL_H diff --git a/filesystem/fscallback.h b/filesystem/fscallback.h new file mode 100644 index 00000000..bebfdd29 --- /dev/null +++ b/filesystem/fscallback.h @@ -0,0 +1,80 @@ +/* +fscallback.h - common filesystem callbacks +Copyright (C) 2022 Alibek Omarov + +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. +*/ +#ifndef FSCALLBACK_H +#define FSCALLBACK_H + +#include "filesystem.h" + +extern fs_api_t g_fsapi; +extern fs_globals_t *FI; + +#define GI FI->GameInfo +#define FS_Gamedir() GI->gamefolder +#define FS_Title() GI->title + +#define FS_InitStdio (*g_fsapi.InitStdio) +#define FS_ShutdownStdio (*g_fsapi.ShutdownStdio) + +// search path utils +#define FS_Rescan (*g_fsapi.Rescan) +#define FS_ClearSearchPath (*g_fsapi.ClearSearchPath) +#define FS_AllowDirectPaths (*g_fsapi.AllowDirectPaths) +#define FS_AddGameDirectory (*g_fsapi.AddGameDirectory) +#define FS_AddGameHierarchy (*g_fsapi.AddGameHierarchy) +#define FS_Search (*g_fsapi.Search) +#define FS_SetCurrentDirectory (*g_fsapi.SetCurrentDirectory) +#define FS_Path_f (*g_fsapi.Path_f) + +// gameinfo utils +#define FS_LoadGameInfo (*g_fsapi.LoadGameInfo) + +// file ops +#define FS_Open (*g_fsapi.Open) +#define FS_Write (*g_fsapi.Write) +#define FS_Read (*g_fsapi.Read) +#define FS_Seek (*g_fsapi.Seek) +#define FS_Tell (*g_fsapi.Tell) +#define FS_Eof (*g_fsapi.Eof) +#define FS_Flush (*g_fsapi.Flush) +#define FS_Close (*g_fsapi.Close) +#define FS_Gets (*g_fsapi.Gets) +#define FS_UnGetc (*g_fsapi.UnGetc) +#define FS_Getc (*g_fsapi.Getc) +#define FS_VPrintf (*g_fsapi.VPrintf) +#define FS_Printf (*g_fsapi.Printf) +#define FS_Print (*g_fsapi.Print) +#define FS_FileLength (*g_fsapi.FileLength) +#define FS_FileCopy (*g_fsapi.FileCopy) + +// file buffer ops +#define FS_LoadFile (*g_fsapi.LoadFile) +#define FS_LoadDirectFile (*g_fsapi.LoadDirectFile) +#define FS_WriteFile (*g_fsapi.WriteFile) + +// file hashing +#define CRC32_File (*g_fsapi.CRC32_File) +#define MD5_HashFile (*g_fsapi.MD5_HashFile) + +// filesystem ops +#define FS_FileExists (*g_fsapi.FileExists) +#define FS_FileTime (*g_fsapi.FileTime) +#define FS_FileSize (*g_fsapi.FileSize) +#define FS_Rename (*g_fsapi.Rename) +#define FS_Delete (*g_fsapi.Delete) +#define FS_SysFileExists (*g_fsapi.SysFileExists) +#define FS_GetDiskPath (*g_fsapi.GetDiskPath) + + +#endif // FSCALLBACK_H diff --git a/filesystem/pak.c b/filesystem/pak.c new file mode 100644 index 00000000..7dac5b8f --- /dev/null +++ b/filesystem/pak.c @@ -0,0 +1,394 @@ +/* +pak.c - PAK support for filesystem +Copyright (C) 2007 Uncle Mike +Copyright (C) 2022 Alibek Omarov + +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. +*/ + +#include +#include +#include +#include +#include +#include +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" + +/* +======================================================================== +PAK FILES + +The .pak files are just a linear collapse of a directory tree +======================================================================== +*/ +// header +#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" + +#define MAX_FILES_IN_PACK 65536 // pak + +typedef struct +{ + int ident; + int dirofs; + int dirlen; +} dpackheader_t; + +typedef struct +{ + char name[56]; // total 64 bytes + int filepos; + int filelen; +} dpackfile_t; + +// PAK errors +#define PAK_LOAD_OK 0 +#define PAK_LOAD_COULDNT_OPEN 1 +#define PAK_LOAD_BAD_HEADER 2 +#define PAK_LOAD_BAD_FOLDERS 3 +#define PAK_LOAD_TOO_MANY_FILES 4 +#define PAK_LOAD_NO_FILES 5 +#define PAK_LOAD_CORRUPTED 6 + +typedef struct pack_s +{ + string filename; + int handle; + int numfiles; + time_t filetime; // common for all packed files + dpackfile_t *files; +} pack_t; + +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) +{ + int left, right, middle; + dpackfile_t *pfile; + + // look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // If we found the file, there's a problem + if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + pfile = &pack->files[left]; + memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); + pack->numfiles++; + + Q_strncpy( pfile->name, name, sizeof( pfile->name )); + pfile->filepos = offset; + pfile->filelen = size; + + return pfile; +} + +/* +================= +FS_LoadPackPAK + +Takes an explicit (not game tree related) path to a pak file. + +Loads the header and directory, adding the files at the beginning +of the list so they override previous pack files. +================= +*/ +static pack_t *FS_LoadPackPAK( const char *packfile, int *error ) +{ + dpackheader_t header; + int packhandle; + int i, numpackfiles; + pack_t *pack; + dpackfile_t *info; + fs_size_t c; + + packhandle = open( packfile, O_RDONLY|O_BINARY ); + +#if !XASH_WIN32 + if( packhandle < 0 ) + { + const char *fpackfile = FS_FixFileCase( packfile ); + if( fpackfile != packfile ) + packhandle = open( fpackfile, O_RDONLY|O_BINARY ); + } +#endif + + if( packhandle < 0 ) + { + Con_Reportf( "%s couldn't open: %s\n", packfile, strerror( errno )); + if( error ) *error = PAK_LOAD_COULDNT_OPEN; + return NULL; + } + + c = read( packhandle, (void *)&header, sizeof( header )); + + if( c != sizeof( header ) || header.ident != IDPACKV1HEADER ) + { + Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_HEADER; + close( packhandle ); + return NULL; + } + + if( header.dirlen % sizeof( dpackfile_t )) + { + Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_FOLDERS; + close( packhandle ); + return NULL; + } + + numpackfiles = header.dirlen / sizeof( dpackfile_t ); + + if( numpackfiles > MAX_FILES_IN_PACK ) + { + Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); + if( error ) *error = PAK_LOAD_TOO_MANY_FILES; + close( packhandle ); + return NULL; + } + + if( numpackfiles <= 0 ) + { + Con_Reportf( "%s has no files. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_NO_FILES; + close( packhandle ); + return NULL; + } + + info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); + lseek( packhandle, header.dirofs, SEEK_SET ); + + if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) + { + Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); + if( error ) *error = PAK_LOAD_CORRUPTED; + close( packhandle ); + Mem_Free( info ); + return NULL; + } + + pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); + Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); + pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); + pack->filetime = FS_SysFileTime( packfile ); + pack->handle = packhandle; + pack->numfiles = 0; + + // parse the directory + for( i = 0; i < numpackfiles; i++ ) + FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); + +#ifdef XASH_REDUCE_FD + // will reopen when needed + close( pack->handle ); + pack->handle = -1; +#endif + + if( error ) *error = PAK_LOAD_OK; + Mem_Free( info ); + + return pack; +} + +/* +=========== +FS_OpenPackedFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) +{ + dpackfile_t *pfile; + + pfile = &pack->files[pack_ind]; + + return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); +} + +/* +================ +FS_AddPak_Fullpath + +Adds the given pack to the search path. +The pack type is autodetected by the file extension. + +Returns true if the file was successfully added to the +search path or if it was already included. + +If keep_plain_dirs is set, the pack will be added AFTER the first sequence of +plain directories. +================ +*/ +qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + pack_t *pak = NULL; + const char *ext = COM_FileExtension( pakfile ); + int i, errorcode = PAK_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_PAK && !Q_stricmp( search->pack->filename, pakfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "pak" )) + pak = FS_LoadPackPAK( pakfile, &errorcode ); + + if( pak ) + { + string fullpath; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->pack = pak; + search->type = SEARCHPATH_PAK; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < pak->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + return true; + } + else + { + if( errorcode != PAK_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); + return false; + } +} + +int FS_FindFilePAK( pack_t *pack, const char *name ) +{ + int left, right, middle; + + // look for the file (binary search) + left = 0; + right = pack->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // Found it + if( !diff ) + { + return middle; + } + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + + return -1; +} + +void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ) +{ + string temp; + const char *slash, *backslash, *colon, *separator; + int j, i; + + for( i = 0; i < pack->numfiles; i++ ) + { + Q_strncpy( temp, pack->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, pattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + stringlistappend( list, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} + +int FS_FileTimePAK( pack_t *pack ) +{ + return pack->filetime; +} + +void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ) +{ + Q_snprintf( dst, size, "%s (%i files)", pack->filename, pack->numfiles ); +} + +void FS_ClosePAK( pack_t *pack ) +{ + if( pack->files ) + Mem_Free( pack->files ); + if( pack->handle >= 0 ) + close( pack->handle ); + Mem_Free( pack ); +} diff --git a/filesystem/wad.c b/filesystem/wad.c new file mode 100644 index 00000000..9cbfa886 --- /dev/null +++ b/filesystem/wad.c @@ -0,0 +1,634 @@ +/* +wad.c - WAD support for filesystem +Copyright (C) 2007 Uncle Mike +Copyright (C) 2022 Alibek Omarov + +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. +*/ + +#include +#include +#include +#include +#include +#include +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" +#include "wadfile.h" + +/* +======================================================================== +.WAD archive format (WhereAllData - WAD) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ +#define WAD3_NAMELEN 16 +#define HINT_NAMELEN 5 // e.g. _mask, _norm +#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount + +#include "const.h" + +typedef struct +{ + int ident; // should be WAD3 + int numlumps; // num files + int infotableofs; // LUT offset +} dwadinfo_t; + +typedef struct +{ + int filepos; // file offset in WAD + int disksize; // compressed or uncompressed + int size; // uncompressed + signed char type; // TYP_* + signed char attribs; // file attribs + signed char pad0; + signed char pad1; + char name[WAD3_NAMELEN]; // must be null terminated +} dlumpinfo_t; + +typedef struct wfile_s +{ + string filename; + int infotableofs; + int numlumps; + poolhandle_t mempool; // W_ReadLump temp buffers + file_t *handle; + dlumpinfo_t *lumps; + time_t filetime; +} wfile_t; + +// WAD errors +#define WAD_LOAD_OK 0 +#define WAD_LOAD_COULDNT_OPEN 1 +#define WAD_LOAD_BAD_HEADER 2 +#define WAD_LOAD_BAD_FOLDERS 3 +#define WAD_LOAD_TOO_MANY_FILES 4 +#define WAD_LOAD_NO_FILES 5 +#define WAD_LOAD_CORRUPTED 6 + +typedef struct wadtype_s +{ + const char *ext; + signed char type; +} wadtype_t; + +// associate extension with wad type +static const wadtype_t wad_types[7] = +{ +{ "pal", TYP_PALETTE }, // palette +{ "dds", TYP_DDSTEX }, // DDS image +{ "lmp", TYP_GFXPIC }, // quake1, hl pic +{ "fnt", TYP_QFONT }, // hl qfonts +{ "mip", TYP_MIPTEX }, // hl/q1 mip +{ "txt", TYP_SCRIPT }, // scripts +{ NULL, TYP_NONE } +}; + +/* +=========== +W_TypeFromExt + +Extracts file type from extension +=========== +*/ +static signed char W_TypeFromExt( const char *lumpname ) +{ + const char *ext = COM_FileExtension( lumpname ); + const wadtype_t *type; + + // we not known about filetype, so match only by filename + if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) + return TYP_ANY; + + for( type = wad_types; type->ext; type++ ) + { + if( !Q_stricmp( ext, type->ext )) + return type->type; + } + return TYP_NONE; +} + +/* +=========== +W_ExtFromType + +Convert type to extension +=========== +*/ +static const char *W_ExtFromType( signed char lumptype ) +{ + const wadtype_t *type; + + // we not known aboyt filetype, so match only by filename + if( lumptype == TYP_NONE || lumptype == TYP_ANY ) + return ""; + + for( type = wad_types; type->ext; type++ ) + { + if( lumptype == type->type ) + return type->ext; + } + return ""; +} + +/* +==================== +W_AddFileToWad + +Add a file to the list of files contained into a package +and sort LAT in alpha-bethical order +==================== +*/ +static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) +{ + int left, right; + dlumpinfo_t *plump; + + // look for the slot we should put that file into (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = ( left + right ) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if( wad->lumps[middle].type < newlump->type ) + diff = 1; + else if( wad->lumps[middle].type > newlump->type ) + diff = -1; + else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); + } + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // we have to move the right of the list by one slot to free the one we need + plump = &wad->lumps[left]; + memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); + wad->numlumps++; + + *plump = *newlump; + memcpy( plump->name, name, sizeof( plump->name )); + + return plump; +} + +/* +=========== +FS_CloseWAD + +finalize wad or just close +=========== +*/ +void FS_CloseWAD( wfile_t *wad ) +{ + Mem_FreePool( &wad->mempool ); + if( wad->handle != NULL ) + FS_Close( wad->handle ); + Mem_Free( wad ); // free himself +} + +/* +=========== +W_Open + +open the wad for reading & writing +=========== +*/ +static wfile_t *W_Open( const char *filename, int *error ) +{ + wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); + const char *basename; + int i, lumpcount; + dlumpinfo_t *srclumps; + size_t lat_size; + dwadinfo_t header; + + // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) + if( fs_ext_path ) basename = filename; + else basename = COM_FileWithoutPath( filename ); + + wad->handle = FS_Open( basename, "rb", false ); + + // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready + if( COM_CheckStringEmpty( fs_rodir ) && fs_ext_path && wad->handle == NULL ) + wad->handle = FS_SysOpen( filename, "rb" ); + + if( wad->handle == NULL ) + { + Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); + if( error ) *error = WAD_LOAD_COULDNT_OPEN; + FS_CloseWAD( wad ); + return NULL; + } + + // copy wad name + Q_strncpy( wad->filename, filename, sizeof( wad->filename )); + wad->filetime = FS_SysFileTime( filename ); + wad->mempool = Mem_AllocPool( filename ); + + if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) + { + Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) + { + Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + lumpcount = header.numlumps; + + if( lumpcount >= MAX_FILES_IN_WAD ) + { + Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); + if( error ) *error = WAD_LOAD_TOO_MANY_FILES; + } + else if( lumpcount <= 0 ) + { + Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); + if( error ) *error = WAD_LOAD_NO_FILES; + FS_CloseWAD( wad ); + return NULL; + } + else if( error ) *error = WAD_LOAD_OK; + + wad->infotableofs = header.infotableofs; // save infotableofs position + + if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); + if( error ) *error = WAD_LOAD_BAD_FOLDERS; + FS_CloseWAD( wad ); + return NULL; + } + + lat_size = lumpcount * sizeof( dlumpinfo_t ); + + // NOTE: lumps table can be reallocated for O_APPEND mode + srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); + + if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) + { + Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); + if( error ) *error = WAD_LOAD_CORRUPTED; + Mem_Free( srclumps ); + FS_CloseWAD( wad ); + return NULL; + } + + // starting to add lumps + wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); + wad->numlumps = 0; + + // sort lumps for binary search + for( i = 0; i < lumpcount; i++ ) + { + char name[16]; + int k; + + // cleanup lumpname + Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); + + // check for '*' symbol issues (quake1) + k = Q_strlen( Q_strrchr( name, '*' )); + if( k ) name[Q_strlen( name ) - k] = '!'; + + // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) + if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) + srclumps[i].type = TYP_GFXPIC; + + W_AddFileToWad( name, wad, &srclumps[i] ); + } + + // release source lumps + Mem_Free( srclumps ); + + // and leave the file open + return wad; +} + +/* +==================== +FS_AddWad_Fullpath +==================== +*/ +qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + wfile_t *wad = NULL; + const char *ext = COM_FileExtension( wadfile ); + int errorcode = WAD_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_WAD && !Q_stricmp( search->wad->filename, wadfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "wad" )) + wad = W_Open( wadfile, &errorcode ); + + if( wad ) + { + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->wad = wad; + search->type = SEARCHPATH_WAD; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); + return true; + } + else + { + if( errorcode != WAD_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); + return false; + } +} + +/* +============================================================================= + +WADSYSTEM PRIVATE ROUTINES + +============================================================================= +*/ + +/* +=========== +W_FindLump + +Serach for already existed lump +=========== +*/ +static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ) +{ + int left, right; + + if( !wad || !wad->lumps || matchtype == TYP_NONE ) + return NULL; + + // look for the file (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = (left + right) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) + return &wad->lumps[middle]; // found + else if( wad->lumps[middle].type < matchtype ) + diff = 1; + else if( wad->lumps[middle].type > matchtype ) + diff = -1; + else break; // not found + } + + // if we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + return NULL; +} + +/* +=========== +W_ReadLump + +reading lump into temp buffer +=========== +*/ +static byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) +{ + size_t oldpos, size = 0; + byte *buf; + + // assume error + if( lumpsizeptr ) *lumpsizeptr = 0; + + // no wads loaded + if( !wad || !lump ) return NULL; + + oldpos = FS_Tell( wad->handle ); // don't forget restore original position + + if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return NULL; + } + + buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); + size = FS_Read( wad->handle, buf, lump->disksize ); + + if( size < lump->disksize ) + { + Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + Mem_Free( buf ); + return NULL; + } + + if( lumpsizeptr ) *lumpsizeptr = lump->disksize; + FS_Seek( wad->handle, oldpos, SEEK_SET ); + + return buf; +} + +/* +=========== +FS_LoadWADFile + +loading lump into the tmp buffer +=========== +*/ +byte *FS_LoadWADFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + + search = FS_FindFile( path, &index, gamedironly ); + if( search && search->type == SEARCHPATH_WAD ) + return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); + return NULL; +} + +int FS_FileTimeWAD( wfile_t *wad ) +{ + return wad->filetime; +} + +void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ) +{ + Q_snprintf( dst, size, "%s (%i files)", wad->filename, wad->numlumps ); +} + +int FS_FindFileWAD( wfile_t *wad, const char *name ) +{ + dlumpinfo_t *lump; + signed char type = W_TypeFromExt( name ); + qboolean anywadname = true; + string wadname, wadfolder; + string shortname; + + // quick reject by filetype + if( type == TYP_NONE ) + return -1; + + COM_ExtractFilePath( name, wadname ); + wadfolder[0] = '\0'; + + if( COM_CheckStringEmpty( wadname ) ) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( wad->filename, shortname ); + COM_DefaultExtension( shortname, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, shortname )) + return -1; + + // NOTE: we can't using long names for wad, + // because we using original wad names[16]; + COM_FileBase( name, shortname ); + + lump = W_FindLump( wad, shortname, type ); + + if( lump ) + { + return lump - wad->lumps; + } + + return -1; + +} + +void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ) +{ + string wadpattern, wadname, temp2; + signed char type = W_TypeFromExt( pattern ); + qboolean anywadname = true; + string wadfolder, temp; + int j, i; + const char *slash, *backslash, *colon, *separator; + + // quick reject by filetype + if( type == TYP_NONE ) + return; + + COM_ExtractFilePath( pattern, wadname ); + COM_FileBase( pattern, wadpattern ); + wadfolder[0] = '\0'; + + if( COM_CheckStringEmpty( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( wad->filename, temp2 ); + COM_DefaultExtension( temp2, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, temp2 )) + return; + + for( i = 0; i < wad->numlumps; i++ ) + { + // if type not matching, we already have no chance ... + if( type != TYP_ANY && wad->lumps[i].type != type ) + continue; + + // build the lumpname with image suffix (if present) + Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); + + while( temp[0] ) + { + if( matchpattern( temp, wadpattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + { + // build path: wadname/lumpname.ext + Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); + COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); + stringlistappend( list, temp2 ); + } + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} diff --git a/filesystem/wscript b/filesystem/wscript new file mode 100644 index 00000000..bfcf35c0 --- /dev/null +++ b/filesystem/wscript @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +def options(opt): + pass + +def configure(conf): + if conf.env.cxxshlib_PATTERN.startswith('lib'): + conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:] + +def build(bld): + bld.shlib(target = 'filesystem_stdio', + features = 'c', + source = bld.path.ant_glob(['*.c']), + includes = ['.', '../common', '../public', '../engine'], + use = ['public'], + install_path = bld.env.LIBDIR, + subsystem = bld.env.MSVC_SUBSYSTEM) diff --git a/filesystem/zip.c b/filesystem/zip.c new file mode 100644 index 00000000..dcad291b --- /dev/null +++ b/filesystem/zip.c @@ -0,0 +1,678 @@ +/* +zip.c - ZIP support for filesystem +Copyright (C) 2019 Mr0maks +Copyright (C) 2022 Alibek Omarov + +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. +*/ + +#include +#include +#include +#include +#include +#include +#include STDINT_H +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" +#include "miniz.h" + +#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) +#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P') + +#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P') +#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P') + +#define ZIP_COMPRESSION_NO_COMPRESSION 0 +#define ZIP_COMPRESSION_DEFLATED 8 + +#define ZIP_ZIP64 0xffffffff + +#pragma pack( push, 1 ) +typedef struct zip_header_s +{ + unsigned int signature; // little endian ZIP_HEADER + unsigned short version; // version of pkzip need to unpack + unsigned short flags; // flags (16 bits == 16 flags) + unsigned short compression_flags; // compression flags (bits) + unsigned int dos_date; // file modification time and file modification date + unsigned int crc32; //crc32 + unsigned int compressed_size; + unsigned int uncompressed_size; + unsigned short filename_len; + unsigned short extrafield_len; +} zip_header_t; + +/* + in zip64 comp and uncompr size == 0xffffffff remeber this + compressed and uncompress filesize stored in extra field +*/ + +typedef struct zip_header_extra_s +{ + unsigned int signature; // ZIP_HEADER_SPANNED + unsigned int crc32; + unsigned int compressed_size; + unsigned int uncompressed_size; +} zip_header_extra_t; + +typedef struct zip_cdf_header_s +{ + unsigned int signature; + unsigned short version; + unsigned short version_need; + unsigned short generalPurposeBitFlag; + unsigned short flags; + unsigned short modification_time; + unsigned short modification_date; + unsigned int crc32; + unsigned int compressed_size; + unsigned int uncompressed_size; + unsigned short filename_len; + unsigned short extrafield_len; + unsigned short file_commentary_len; + unsigned short disk_start; + unsigned short internal_attr; + unsigned int external_attr; + unsigned int local_header_offset; +} zip_cdf_header_t; + +typedef struct zip_header_eocd_s +{ + unsigned short disk_number; + unsigned short start_disk_number; + unsigned short number_central_directory_record; + unsigned short total_central_directory_record; + unsigned int size_of_central_directory; + unsigned int central_directory_offset; + unsigned short commentary_len; +} zip_header_eocd_t; +#pragma pack( pop ) + +// ZIP errors +#define ZIP_LOAD_OK 0 +#define ZIP_LOAD_COULDNT_OPEN 1 +#define ZIP_LOAD_BAD_HEADER 2 +#define ZIP_LOAD_BAD_FOLDERS 3 +#define ZIP_LOAD_NO_FILES 5 +#define ZIP_LOAD_CORRUPTED 6 + +typedef struct zipfile_s +{ + char name[MAX_SYSPATH]; + fs_offset_t offset; // offset of local file header + fs_offset_t size; //original file size + fs_offset_t compressed_size; // compressed file size + unsigned short flags; +} zipfile_t; + +typedef struct zip_s +{ + string filename; + int handle; + int numfiles; + time_t filetime; + zipfile_t *files; +} zip_t; + +#ifdef XASH_REDUCE_FD +static void FS_EnsureOpenZip( zip_t *zip ) +{ + if( fs_last_zip == zip ) + return; + + if( fs_last_zip && (fs_last_zip->handle != -1) ) + { + close( fs_last_zip->handle ); + fs_last_zip->handle = -1; + } + fs_last_zip = zip; + if( zip && (zip->handle == -1) ) + zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); +} +#else +static void FS_EnsureOpenZip( zip_t *zip ) {} +#endif + +void FS_CloseZIP( zip_t *zip ) +{ + if( zip->files ) + Mem_Free( zip->files ); + + FS_EnsureOpenZip( NULL ); + + if( zip->handle >= 0 ) + close( zip->handle ); + + Mem_Free( zip ); +} + +/* +============ +FS_SortZip +============ +*/ +static int FS_SortZip( const void *a, const void *b ) +{ + return Q_stricmp( ( ( zipfile_t* )a )->name, ( ( zipfile_t* )b )->name ); +} + +/* +============ +FS_LoadZip +============ +*/ +static zip_t *FS_LoadZip( const char *zipfile, int *error ) +{ + int numpackfiles = 0, i; + zip_cdf_header_t header_cdf; + zip_header_eocd_t header_eocd; + uint32_t signature; + fs_offset_t filepos = 0, length; + zipfile_t *info = NULL; + char filename_buffer[MAX_SYSPATH]; + zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); + fs_size_t c; + + zip->handle = open( zipfile, O_RDONLY|O_BINARY ); + +#if !XASH_WIN32 + if( zip->handle < 0 ) + { + const char *fzipfile = FS_FixFileCase( zipfile ); + if( fzipfile != zipfile ) + zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); + } +#endif + + if( zip->handle < 0 ) + { + Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + FS_CloseZIP( zip ); + return NULL; + } + + length = lseek( zip->handle, 0, SEEK_END ); + + if( length > UINT_MAX ) + { + Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + FS_CloseZIP( zip ); + return NULL; + } + + lseek( zip->handle, 0, SEEK_SET ); + + c = read( zip->handle, &signature, sizeof( signature ) ); + + if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) + { + Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_NO_FILES; + + FS_CloseZIP( zip ); + return NULL; + } + + if( signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + // Find oecd + lseek( zip->handle, 0, SEEK_SET ); + filepos = length; + + while ( filepos > 0 ) + { + lseek( zip->handle, filepos, SEEK_SET ); + c = read( zip->handle, &signature, sizeof( signature ) ); + + if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD ) + break; + + filepos -= sizeof( char ); // step back one byte + } + + if( ZIP_HEADER_EOCD != signature ) + { + Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + c = read( zip->handle, &header_eocd, sizeof( header_eocd ) ); + + if( c != sizeof( header_eocd )) + { + Con_Reportf( S_ERROR "invalid EOCD header in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + // Move to CDF start + lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); + + // Calc count of files in archive + info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( *info ) * header_eocd.total_central_directory_record ); + + for( i = 0; i < header_eocd.total_central_directory_record; i++ ) + { + c = read( zip->handle, &header_cdf, sizeof( header_cdf ) ); + + if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF ) + { + Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) + { + memset( &filename_buffer, '\0', MAX_SYSPATH ); + c = read( zip->handle, &filename_buffer, header_cdf.filename_len ); + + if( c != header_cdf.filename_len ) + { + Con_Reportf( S_ERROR "filename length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); + + info[numpackfiles].size = header_cdf.uncompressed_size; + info[numpackfiles].compressed_size = header_cdf.compressed_size; + info[numpackfiles].offset = header_cdf.local_header_offset; + numpackfiles++; + } + else + lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); + + if( header_cdf.extrafield_len ) + lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); + + if( header_cdf.file_commentary_len ) + lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); + } + + // recalculate offsets + for( i = 0; i < numpackfiles; i++ ) + { + zip_header_t header; + + lseek( zip->handle, info[i].offset, SEEK_SET ); + c = read( zip->handle, &header, sizeof( header ) ); + + if( c != sizeof( header )) + { + Con_Reportf( S_ERROR "header length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + info[i].flags = header.compression_flags; + info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); + } + + Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); + zip->filetime = FS_SysFileTime( zipfile ); + zip->numfiles = numpackfiles; + zip->files = info; + + qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip ); + +#ifdef XASH_REDUCE_FD + // will reopen when needed + close(zip->handle); + zip->handle = -1; +#endif + + if( error ) + *error = ZIP_LOAD_OK; + + return zip; +} + +/* +=========== +FS_OpenZipFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) +{ + zipfile_t *pfile; + pfile = &zip->files[pack_ind]; + + // compressed files handled in Zip_LoadFile + if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) + { + Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __FUNCTION__, pfile->name ); + return NULL; + } + + return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); +} + +byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + zipfile_t *file = NULL; + byte *compressed_buffer = NULL, *decompressed_buffer = NULL; + int zlib_result = 0; + dword test_crc, final_crc; + z_stream decompress_stream; + size_t c; + + if( sizeptr ) *sizeptr = 0; + + search = FS_FindFile( path, &index, gamedironly ); + + if( !search || search->type != SEARCHPATH_ZIP ) + return NULL; + + file = &search->zip->files[index]; + + FS_EnsureOpenZip( search->zip ); + + if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) + return NULL; + + /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) + return NULL; + + if( header.signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); + return NULL; + }*/ + + if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) + { + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; + + c = read( search->zip->handle, decompressed_buffer, file->size ); + if( c != file->size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s size doesn't match\n", file->name ); + return NULL; + } + +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else if( file->flags == ZIP_COMPRESSION_DEFLATED ) + { + compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; + + c = read( search->zip->handle, compressed_buffer, file->compressed_size ); + if( c != file->compressed_size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s compressed size doesn't match\n", file->name ); + return NULL; + } + + memset( &decompress_stream, 0, sizeof( decompress_stream ) ); + + decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size; + decompress_stream.next_in = (Bytef *)compressed_buffer; + decompress_stream.total_out = decompress_stream.avail_out = file->size; + decompress_stream.next_out = (Bytef *)decompressed_buffer; + + decompress_stream.zalloc = Z_NULL; + decompress_stream.zfree = Z_NULL; + decompress_stream.opaque = Z_NULL; + + if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) + { + Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); + inflateEnd( &decompress_stream ); + + if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) + { + Mem_Free( compressed_buffer ); // finaly free compressed buffer +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); + return NULL; + } + + FS_EnsureOpenZip( NULL ); + return NULL; +} + + +qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + zip_t *zip = NULL; + const char *ext = COM_FileExtension( zipfile ); + int errorcode = ZIP_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_ZIP && !Q_stricmp( search->zip->filename, zipfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) *already_loaded = false; + + if( !Q_stricmp( ext, "pk3" ) ) + zip = FS_LoadZip( zipfile, &errorcode ); + + if( zip ) + { + string fullpath; + int i; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); + search->zip = zip; + search->type = SEARCHPATH_ZIP; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < zip->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + return true; + } + else + { + if( errorcode != ZIP_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); + return false; + } +} + +int FS_FileTimeZIP( zip_t *zip ) +{ + return zip->filetime; +} + +void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ) +{ + Q_snprintf( dst, size, "%s (%i files)", zip->filename, zip->numfiles ); +} + +int FS_FindFileZIP( zip_t *zip, const char *name ) +{ + int left, right, middle; + + // look for the file (binary search) + left = 0; + right = zip->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( zip->files[middle].name, name ); + + // Found it + if( !diff ) + return middle; + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + + return -1; +} + +void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ) +{ + string temp; + const char *slash, *backslash, *colon, *separator; + int j, i; + + for( i = 0; i < zip->numfiles; i++ ) + { + Q_strncpy( temp, zip->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, pattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + stringlistappend( list, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} + diff --git a/public/crtlib.c b/public/crtlib.c index d183c7a5..652ae917 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -819,6 +819,23 @@ void COM_RemoveLineFeed( char *str ) } } +/* +============ +COM_FixSlashes + +Changes all '/' characters into '\' characters, in place. +============ +*/ +void COM_FixSlashes( char *pname ) +{ + while( *pname ) + { + if( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + /* ============ COM_PathSlashFix diff --git a/public/crtlib.h b/public/crtlib.h index 2d44939f..84027ea6 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -95,6 +95,7 @@ void COM_ExtractFilePath( const char *path, char *dest ); const char *COM_FileWithoutPath( const char *in ); void COM_StripExtension( char *path ); void COM_RemoveLineFeed( char *str ); +void COM_FixSlashes( char *pname ); void COM_PathSlashFix( char *path ); char COM_Hex2Char( uint8_t hex ); void COM_Hex2String( uint8_t hex, char *str ); diff --git a/engine/common/miniz.h b/public/miniz.h similarity index 100% rename from engine/common/miniz.h rename to public/miniz.h diff --git a/ref_gl/gl_alias.c b/ref_gl/gl_alias.c index 77b27b77..d9766755 100644 --- a/ref_gl/gl_alias.c +++ b/ref_gl/gl_alias.c @@ -486,7 +486,7 @@ void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) Q_snprintf( name, sizeof( name ), "%s:frame%i", loadmodel->name, skinnum ); Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); Q_snprintf( checkname, sizeof( checkname ), "%s_%i.tga", loadmodel->name, skinnum ); - if( !gEngfuncs.FS_FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) + if( !gEngfuncs.fsapi->FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->gl_texturenum[skinnum][0] = diff --git a/ref_gl/gl_backend.c b/ref_gl/gl_backend.c index 88881f9f..da530405 100644 --- a/ref_gl/gl_backend.c +++ b/ref_gl/gl_backend.c @@ -478,7 +478,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) case VID_SCREENSHOT: break; case VID_SNAPSHOT: - gEngfuncs.FS_AllowDirectPaths( true ); + gEngfuncs.fsapi->AllowDirectPaths( true ); break; case VID_LEVELSHOT: flags |= IMAGE_RESAMPLE; @@ -509,7 +509,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) // write image result = gEngfuncs.FS_SaveImage( filename, r_shot ); - gEngfuncs.FS_AllowDirectPaths( false ); // always reset after store screenshot + gEngfuncs.fsapi->AllowDirectPaths( false ); // always reset after store screenshot gEngfuncs.FS_FreeImage( r_shot ); return result; diff --git a/ref_gl/gl_rmisc.c b/ref_gl/gl_rmisc.c index c402e337..230e66b9 100644 --- a/ref_gl/gl_rmisc.c +++ b/ref_gl/gl_rmisc.c @@ -29,7 +29,7 @@ static void R_ParseDetailTextures( const char *filename ) texture_t *tex; int i; - afile = gEngfuncs.COM_LoadFile( filename, NULL, false ); + afile = gEngfuncs.fsapi->LoadFile( filename, NULL, false ); if( !afile ) return; pfile = (char *)afile; diff --git a/ref_gl/gl_studio.c b/ref_gl/gl_studio.c index bb52c262..c71630d9 100644 --- a/ref_gl/gl_studio.c +++ b/ref_gl/gl_studio.c @@ -2699,7 +2699,7 @@ static model_t *R_StudioSetupPlayerModel( int index ) Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); - if( gEngfuncs.FS_FileExists( state->modelname, false )) + if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); else state->model = NULL; diff --git a/ref_gl/gl_warp.c b/ref_gl/gl_warp.c index 32359d89..72e77c1c 100644 --- a/ref_gl/gl_warp.c +++ b/ref_gl/gl_warp.c @@ -78,7 +78,7 @@ static int CheckSkybox( const char *name ) { // build side name sidename = va( "%s%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.FS_FileExists( sidename, false )) + if( gEngfuncs.fsapi->FileExists( sidename, false )) num_checked_sides++; } @@ -90,7 +90,7 @@ static int CheckSkybox( const char *name ) { // build side name sidename = va( "%s_%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.FS_FileExists( sidename, false )) + if( gEngfuncs.fsapi->FileExists( sidename, false )) num_checked_sides++; } diff --git a/ref_gl/wscript b/ref_gl/wscript index af60caea..207e7ec6 100644 --- a/ref_gl/wscript +++ b/ref_gl/wscript @@ -59,6 +59,7 @@ def build(bld): source = bld.path.ant_glob(['*.c']) includes = ['.', + '../filesystem', '../engine', '../engine/common', '../engine/server', diff --git a/ref_soft/r_studio.c b/ref_soft/r_studio.c index 3dad15b6..0a68035a 100644 --- a/ref_soft/r_studio.c +++ b/ref_soft/r_studio.c @@ -2462,7 +2462,7 @@ static model_t *R_StudioSetupPlayerModel( int index ) Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); - if( gEngfuncs.FS_FileExists( state->modelname, false )) + if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); else state->model = NULL; diff --git a/ref_soft/wscript b/ref_soft/wscript index 93933140..9504cd84 100644 --- a/ref_soft/wscript +++ b/ref_soft/wscript @@ -30,6 +30,7 @@ def build(bld): source = bld.path.ant_glob(['*.c']) includes = ['.', + '../filesystem', '../engine', '../engine/common', '../engine/server', diff --git a/wscript b/wscript index f87fc7ff..6565e53c 100644 --- a/wscript +++ b/wscript @@ -55,6 +55,7 @@ class Subproject: SUBDIRS = [ Subproject('public', dedicated=False, mandatory = True), + Subproject('filesystem', dedicated=False, mandatory = True), Subproject('game_launch', singlebin=True), Subproject('ref_gl',), Subproject('ref_soft'),