|
|
|
/*
|
|
|
|
VFileSystem009.h - C++ interface for filesystem_stdio
|
|
|
|
Copyright (C) 2022-2023 Xash3D FWGS contributors
|
|
|
|
|
|
|
|
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 <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include ALLOCA_H
|
|
|
|
#include "crtlib.h"
|
|
|
|
#include "filesystem.h"
|
|
|
|
#include "filesystem_internal.h"
|
|
|
|
#include "VFileSystem009.h"
|
|
|
|
#include "common/com_strings.h"
|
|
|
|
|
|
|
|
#if __cplusplus < 201103L
|
|
|
|
#define override
|
|
|
|
#define nullptr NULL
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// GoldSrc Directories and ID
|
|
|
|
// GAME gamedir
|
|
|
|
// GAMECONFIG gamedir (rodir integration?)
|
|
|
|
// GAMEDOWNLOAD gamedir_downloads (gamedir/downloads for us)
|
|
|
|
// GAME_FALLBACK liblist.gam's fallback_dir
|
|
|
|
// ROOT and BASE rootdir
|
|
|
|
// PLATFORM platform
|
|
|
|
// CONFIG platform/config
|
|
|
|
|
|
|
|
// This is a macro because pointers returned by alloca
|
|
|
|
// shouldn't leave current scope
|
|
|
|
#define FixupPath( var, str ) \
|
|
|
|
const size_t var ## _size = Q_strlen(( str )) + 1; \
|
|
|
|
char * const var = static_cast<char *>( alloca( var ## _size )); \
|
|
|
|
CopyAndFixSlashes( var, ( str ), var ## _size )
|
|
|
|
|
|
|
|
static inline bool IsIdGamedir( const char *id )
|
|
|
|
{
|
|
|
|
return !Q_strcmp( id, "GAME" ) ||
|
|
|
|
!Q_strcmp( id, "GAMECONFIG" ) ||
|
|
|
|
!Q_strcmp( id, "GAMEDOWNLOAD" );
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline const char *IdToDir( char *dir, size_t size, const char *id )
|
|
|
|
{
|
|
|
|
if( !Q_strcmp( id, "GAME" ))
|
|
|
|
return GI->gamefolder;
|
|
|
|
|
|
|
|
if( !Q_strcmp( id, "GAMEDOWNLOAD" ))
|
|
|
|
{
|
|
|
|
Q_snprintf( dir, size, "%s/downloaded", GI->gamefolder );
|
|
|
|
return dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !Q_strcmp( id, "GAMECONFIG" ))
|
|
|
|
return fs_writepath->filename; // full path here so it's totally our write allowed directory
|
|
|
|
|
|
|
|
if( !Q_strcmp( id, "PLATFORM" ))
|
|
|
|
return "platform"; // stub
|
|
|
|
|
|
|
|
if( !Q_strcmp( id, "CONFIG" ))
|
|
|
|
return "platform/config"; // stub
|
|
|
|
|
|
|
|
// ROOT || BASE
|
|
|
|
return fs_rootdir; // give at least root directory
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void CopyAndFixSlashes( char *p, const char *in, size_t size )
|
|
|
|
{
|
|
|
|
Q_strncpy( p, in, size );
|
|
|
|
COM_FixSlashes( p );
|
|
|
|
}
|
|
|
|
|
|
|
|
class CXashFS : public IVFileSystem009
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
class CSearchState
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
CSearchState( CSearchState **head, search_t *search ) :
|
|
|
|
next( *head ),
|
|
|
|
search( search ),
|
|
|
|
index( 0 ),
|
|
|
|
handle( *head ? ( *head )->handle + 1 : 0 )
|
|
|
|
{
|
|
|
|
*head = this;
|
|
|
|
}
|
|
|
|
~CSearchState()
|
|
|
|
{
|
|
|
|
Mem_Free( search );
|
|
|
|
}
|
|
|
|
|
|
|
|
CSearchState *next;
|
|
|
|
search_t *search;
|
|
|
|
int index;
|
|
|
|
FileFindHandle_t handle;
|
|
|
|
};
|
|
|
|
|
|
|
|
CSearchState *searchHead;
|
|
|
|
|
|
|
|
CSearchState *GetSearchStateByHandle( FileFindHandle_t handle )
|
|
|
|
{
|
|
|
|
for( CSearchState *state = searchHead; state; state = state->next )
|
|
|
|
{
|
|
|
|
if( state->handle == handle )
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
Con_DPrintf( "Can't find search state by handle %d\n", handle );
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
CXashFS() : searchHead( nullptr )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void RemoveAllSearchPaths() override
|
|
|
|
{
|
|
|
|
FS_ClearSearchPath();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddSearchPath( const char *path, const char *id ) override
|
|
|
|
{
|
|
|
|
FixupPath( p, path );
|
|
|
|
FS_AddGameDirectory( p, FS_CUSTOM_PATH );
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddSearchPathNoWrite( const char *path, const char *id ) override
|
|
|
|
{
|
|
|
|
FixupPath( p, path );
|
|
|
|
FS_AddGameDirectory( p, FS_NOWRITE_PATH | FS_CUSTOM_PATH );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool RemoveSearchPath( const char *id ) override
|
|
|
|
{
|
|
|
|
// TODO:
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RemoveFile( const char *path, const char *id ) override
|
|
|
|
{
|
|
|
|
char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING];
|
|
|
|
|
|
|
|
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", IdToDir( dir, sizeof( dir ), id ), path );
|
|
|
|
FS_Delete( fullpath ); // FS_Delete is aware of slashes
|
|
|
|
}
|
|
|
|
|
|
|
|
void CreateDirHierarchy( const char *path, const char *id ) override
|
|
|
|
{
|
|
|
|
char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING];
|
|
|
|
|
|
|
|
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", IdToDir( dir, sizeof( dir ), id ), path );
|
|
|
|
FS_CreatePath( fullpath ); // FS_CreatePath is aware of slashes
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FileExists( const char *path ) override
|
|
|
|
{
|
|
|
|
FixupPath( p, path );
|
|
|
|
return FS_FileExists( p, false );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsDirectory( const char *path ) override
|
|
|
|
{
|
|
|
|
FixupPath( p, path );
|
|
|
|
return FS_SysFolderExists( p );
|
|
|
|
}
|
|
|
|
|
|
|
|
FileHandle_t Open( const char *path, const char *mode, const char *id ) override
|
|
|
|
{
|
|
|
|
file_t *fd;
|
|
|
|
|
|
|
|
FixupPath( p, path );
|
|
|
|
fd = FS_Open( p, mode, IsIdGamedir( id ));
|
|
|
|
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Close( FileHandle_t handle ) override
|
|
|
|
{
|
|
|
|
FS_Close( static_cast<file_t *>( handle ));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Seek( FileHandle_t handle, int offset, FileSystemSeek_t whence ) override
|
|
|
|
{
|
|
|
|
int whence_ = SEEK_SET;
|
|
|
|
|
|
|
|
switch( whence )
|
|
|
|
{
|
|
|
|
case FILESYSTEM_SEEK_HEAD:
|
|
|
|
whence_ = SEEK_SET;
|
|
|
|
break;
|
|
|
|
case FILESYSTEM_SEEK_CURRENT:
|
|
|
|
whence_ = SEEK_CUR;
|
|
|
|
break;
|
|
|
|
case FILESYSTEM_SEEK_TAIL:
|
|
|
|
whence_ = SEEK_END;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
FS_Seek( static_cast<file_t *>( handle ), offset, whence_ );
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int Tell( FileHandle_t handle ) override
|
|
|
|
{
|
|
|
|
return FS_Tell( static_cast<file_t *>( handle ));
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int Size( FileHandle_t handle ) override
|
|
|
|
{
|
|
|
|
return static_cast<file_t *>( handle )->real_length;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int Size( const char *path ) override
|
|
|
|
{
|
|
|
|
FixupPath( p, path );
|
|
|
|
return FS_FileSize( p, false );
|
|
|
|
}
|
|
|
|
|
|
|
|
long int GetFileTime( const char *path ) override
|
|
|
|
{
|
|
|
|
FixupPath( p, path );
|
|
|
|
return FS_FileTime( p, false );
|
|
|
|
}
|
|
|
|
|
|
|
|
long int GetFileModificationTime( const char *path )
|
|
|
|
{
|
|
|
|
// TODO: properly reverse-engineer this
|
|
|
|
FixupPath( p, path );
|
|
|
|
return FS_FileTime( p, false );
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileTimeToString( char *p, int size, long int time ) override
|
|
|
|
{
|
|
|
|
const time_t curtime = time;
|
|
|
|
char *buf = ctime( &curtime );
|
|
|
|
|
|
|
|
Q_strncpy( p, buf, size );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsOk( FileHandle_t handle ) override
|
|
|
|
{
|
|
|
|
return !FS_Eof( static_cast<file_t *>( handle ));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Flush( FileHandle_t handle ) override
|
|
|
|
{
|
|
|
|
FS_Flush( static_cast<file_t *>( handle ));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EndOfFile( FileHandle_t handle ) override
|
|
|
|
{
|
|
|
|
return FS_Eof( static_cast<file_t *>( handle ));
|
|
|
|
}
|
|
|
|
|
|
|
|
int Read( void *buf, int size, FileHandle_t handle ) override
|
|
|
|
{
|
|
|
|
return FS_Read( static_cast<file_t *>( handle ), buf, size );
|
|
|
|
}
|
|
|
|
|
|
|
|
int Write( const void *buf, int size, FileHandle_t handle ) override
|
|
|
|
{
|
|
|
|
return FS_Write( static_cast<file_t *>( handle ), buf, size );
|
|
|
|
}
|
|
|
|
|
|
|
|
char *ReadLine( char *buf, int size, FileHandle_t handle ) override
|
|
|
|
{
|
|
|
|
const int c = FS_Gets( static_cast<file_t *>( handle ), buf, size );
|
|
|
|
|
|
|
|
return c >= 0 ? buf : nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FPrintf( FileHandle_t handle, char *fmt, ... ) override
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
va_start( ap, fmt );
|
|
|
|
ret = FS_VPrintf( static_cast<file_t *>( handle ), fmt, ap );
|
|
|
|
va_end( ap );
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *GetReadBuffer( FileHandle_t, int *size, bool ) override
|
|
|
|
{
|
|
|
|
// deprecated by Valve
|
|
|
|
*size = 0;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReleaseReadBuffer( FileHandle_t, void * ) override
|
|
|
|
{
|
|
|
|
// deprecated by Valve
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *FindFirst( const char *pattern, FileFindHandle_t *handle, const char *id ) override
|
|
|
|
{
|
|
|
|
CSearchState *state;
|
|
|
|
search_t *search;
|
|
|
|
|
|
|
|
if( !handle || !pattern )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
FixupPath( p, pattern );
|
|
|
|
search = FS_Search( p, true, IsIdGamedir( id ));
|
|
|
|
|
|
|
|
if( !search )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
state = new CSearchState( &searchHead, search );
|
|
|
|
*handle = state->handle;
|
|
|
|
return state->search->filenames[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *FindNext( FileFindHandle_t handle ) override
|
|
|
|
{
|
|
|
|
CSearchState *state = GetSearchStateByHandle( handle );
|
|
|
|
|
|
|
|
if( !state )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
if( state->index + 1 >= state->search->numfilenames )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
return state->search->filenames[++state->index];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FindIsDirectory( FileFindHandle_t handle ) override
|
|
|
|
{
|
|
|
|
CSearchState *state = GetSearchStateByHandle( handle );
|
|
|
|
|
|
|
|
if( !state )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( state->index >= state->search->numfilenames )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return IsDirectory( state->search->filenames[state->index] );
|
|
|
|
}
|
|
|
|
|
|
|
|
void FindClose( FileFindHandle_t handle ) override
|
|
|
|
{
|
|
|
|
CSearchState *prev;
|
|
|
|
CSearchState *i;
|
|
|
|
|
|
|
|
for( prev = nullptr, i = searchHead;
|
|
|
|
i != nullptr && i->handle != handle;
|
|
|
|
prev = i, i = i->next );
|
|
|
|
|
|
|
|
if( i == nullptr )
|
|
|
|
{
|
|
|
|
Con_DPrintf( "FindClose: Can't find search state by handle %d\n", handle );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( prev != nullptr )
|
|
|
|
prev->next = i->next;
|
|
|
|
else
|
|
|
|
searchHead = i->next;
|
|
|
|
|
|
|
|
delete i;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *GetLocalPath( const char *name, char *buf, int size ) override
|
|
|
|
{
|
|
|
|
const char *fullpath;
|
|
|
|
|
|
|
|
if( !name )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
FixupPath( p, name );
|
|
|
|
|
|
|
|
#if !XASH_WIN32
|
|
|
|
if( p[0] == '/' )
|
|
|
|
#else
|
|
|
|
if( Q_strchr( p, ':' ))
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
Q_strncpy( buf, p, size );
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
fullpath = FS_GetDiskPath( p, false );
|
|
|
|
if( !fullpath )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
Q_strncpy( buf, fullpath, size );
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *ParseFile( char *buf, char *token, bool *quoted ) override
|
|
|
|
{
|
|
|
|
qboolean qquoted;
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
p = COM_ParseFileSafe( buf, token, PFILE_FS_TOKEN_MAX_LENGTH, 0, nullptr, &qquoted );
|
|
|
|
|
|
|
|
if( quoted )
|
|
|
|
*quoted = qquoted;
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FullPathToRelativePath( const char *path, char *out ) override
|
|
|
|
{
|
|
|
|
if( !COM_CheckString( path ))
|
|
|
|
{
|
|
|
|
*out = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
FixupPath( p, path );
|
|
|
|
|
|
|
|
return FS_FullPathToRelativePath( out, p, 512 );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GetCurrentDirectory( char *p, int size ) override
|
|
|
|
{
|
|
|
|
Q_strncpy( p, fs_rootdir, size );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrintOpenedFiles() override
|
|
|
|
{
|
|
|
|
// we don't track this yet
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetWarningFunc( void (*)( const char *, ... )) override
|
|
|
|
{
|
|
|
|
// TODO:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetWarningLevel( FileWarningLevel_t ) override
|
|
|
|
{
|
|
|
|
// TODO:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int SetVBuf( FileHandle_t handle, char *buf, int mode, long int size ) override
|
|
|
|
{
|
|
|
|
// TODO:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GetInterfaceVersion( char *p, int size ) override
|
|
|
|
{
|
|
|
|
Q_strncpy( p, "Stdio", size );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AddPackFile( const char *path, const char *id ) override
|
|
|
|
{
|
|
|
|
char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING];
|
|
|
|
const char *ext = COM_FileExtension( path );
|
|
|
|
|
|
|
|
IdToDir( dir, sizeof( dir ), id );
|
|
|
|
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", dir, path );
|
|
|
|
COM_FixSlashes( fullpath );
|
|
|
|
|
|
|
|
for( const fs_archive_t *archive = g_archives; archive->ext; archive++ )
|
|
|
|
{
|
|
|
|
if( archive->type == SEARCHPATH_PAK && !Q_stricmp( ext, archive->ext ))
|
|
|
|
return FS_AddArchive_Fullpath( archive, fullpath, FS_CUSTOM_PATH );
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
FileHandle_t OpenFromCacheForRead( const char *path , const char *mode, const char *id ) override
|
|
|
|
{
|
|
|
|
FixupPath( p, path );
|
|
|
|
|
|
|
|
return FS_OpenReadFile( p, mode, IsIdGamedir( id ));
|
|
|
|
}
|
|
|
|
|
|
|
|
// stubs
|
|
|
|
void Mount() override {}
|
|
|
|
void Unmount() override {}
|
|
|
|
void GetLocalCopy( const char * ) override {}
|
|
|
|
void LogLevelLoadStarted( const char * ) override {}
|
|
|
|
void LogLevelLoadFinished( const char * ) override {}
|
|
|
|
void CancelWaitForResources( WaitForResourcesHandle_t ) override {}
|
|
|
|
int HintResourceNeed( const char *, int ) override { return 0; }
|
|
|
|
WaitForResourcesHandle_t WaitForResources( const char * ) override { return 0; }
|
|
|
|
int PauseResourcePreloading() override { return 0; }
|
|
|
|
int ResumeResourcePreloading() override { return 0; }
|
|
|
|
bool IsAppReadyForOfflinePlay( int ) override { return true; }
|
|
|
|
bool IsFileImmediatelyAvailable( const char * ) override { return true; }
|
|
|
|
bool GetWaitForResourcesProgress( WaitForResourcesHandle_t, float *pProgress, bool *pOverride ) override
|
|
|
|
{
|
|
|
|
if( pProgress )
|
|
|
|
*pProgress = 0;
|
|
|
|
|
|
|
|
if( pOverride )
|
|
|
|
*pOverride = true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} g_VFileSystem009;
|
|
|
|
|
|
|
|
extern "C" void EXPORT *CreateInterface( const char *interface, int *retval )
|
|
|
|
{
|
|
|
|
if( !Q_strcmp( interface, FILESYSTEM_INTERFACE_VERSION ))
|
|
|
|
{
|
|
|
|
if( retval )
|
|
|
|
*retval = 0;
|
|
|
|
|
|
|
|
return &g_VFileSystem009;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !Q_strcmp( interface, FS_API_CREATEINTERFACE_TAG ))
|
|
|
|
{
|
|
|
|
static fs_api_t copy = { 0 }; // return a copy, to disallow overriding
|
|
|
|
|
|
|
|
memcpy( ©, &g_api, sizeof( copy ));
|
|
|
|
|
|
|
|
if( retval )
|
|
|
|
*retval = 0;
|
|
|
|
|
|
|
|
return ©
|
|
|
|
}
|
|
|
|
|
|
|
|
if( retval )
|
|
|
|
*retval = 1;
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|